From 7b7cb961c0ed035d37b195350e65457f09c75e28 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 24 Oct 2019 16:57:45 +0300 Subject: [PATCH 001/241] Add newline at the end of file --- src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts index a362d720738..e4d299a43f5 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts @@ -129,4 +129,4 @@ export async function findExecutable(command: string, cwd?: string, paths?: stri } const fullPath = path.join(cwd, command); return await exists(fullPath) ? fullPath : undefined; -} \ No newline at end of file +} From c0bfde44eeeba91cb0f7f724d502a284b4247c5e Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 24 Oct 2019 21:47:57 +0300 Subject: [PATCH 002/241] Hide the debug console if it was opened just because of the debugging --- src/vs/workbench/contrib/debug/browser/debugService.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 64906667195..3abe9604d3c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -90,6 +90,8 @@ export class DebugService implements IDebugService { private previousState: State | undefined; private initCancellationToken: CancellationTokenSource | undefined; + private internalTerminalWasOpened = false; + constructor( @IStorageService private readonly storageService: IStorageService, @IEditorService private readonly editorService: IEditorService, @@ -452,6 +454,7 @@ export class DebugService implements IDebugService { await this.launchOrAttachToSession(session); const internalConsoleOptions = session.configuration.internalConsoleOptions || this.configurationService.getValue('debug').internalConsoleOptions; + this.internalTerminalWasOpened = this.layoutService.isVisible(Parts.PANEL_PART); if (internalConsoleOptions === 'openOnSessionStart' || (this.viewModel.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) { this.panelService.openPanel(REPL_ID, false); } @@ -566,6 +569,10 @@ export class DebugService implements IDebugService { dataBreakpoints.forEach(dbp => this.model.removeDataBreakpoints(dbp.getId())); } + if (!this.internalTerminalWasOpened) { + this.panelService.hideActivePanel(); + } + })); } From 78ea15e1e6cb5dde9c34cb73c802976523881f2e Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 24 Oct 2019 22:00:48 +0300 Subject: [PATCH 003/241] Add setting to control this feature --- src/vs/workbench/contrib/debug/browser/debug.contribution.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 3224a8678fd..7c3ef2ee91f 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -213,6 +213,11 @@ configurationRegistry.registerConfiguration({ default: 'onFirstSessionStart' }, 'debug.internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA, + 'debug.closeConsoleOnFinish': { + type: 'boolean', + description: nls.localize({ comment: ['This is the description for a setting'], key: 'closeConsoleOnFinish' }, "If set to true, the debug console will be closed automatically when opened because of the debugging (see `internalConsoleOptions`). Has no effect if `internalConsoleOptions` was set to `neverOpen`."), + default: true + }, 'debug.openDebug': { enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart', 'openOnDebugBreak'], default: 'openOnSessionStart', From f78b7e174332f716795b749a88875c469fc8189c Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 24 Oct 2019 22:04:03 +0300 Subject: [PATCH 004/241] Add settings to the interface (IDebugConfiguration) --- src/vs/workbench/contrib/debug/common/debug.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 1ef64079bba..6e152f6ec0d 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -456,6 +456,7 @@ export interface IDebugConfiguration { toolBarLocation: 'floating' | 'docked' | 'hidden'; showInStatusBar: 'never' | 'always' | 'onFirstSessionStart'; internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart'; + closeConsoleOnFinish: boolean; extensionHostDebugAdapter: boolean; enableAllHovers: boolean; showSubSessionsInToolBar: boolean; From 76d282feffafd188ce82caa6c4d141668e02feb1 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Thu, 24 Oct 2019 22:07:06 +0300 Subject: [PATCH 005/241] Honor the new setting --- src/vs/workbench/contrib/debug/browser/debugService.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 3abe9604d3c..17c916e9714 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -570,7 +570,10 @@ export class DebugService implements IDebugService { } if (!this.internalTerminalWasOpened) { - this.panelService.hideActivePanel(); + const closeConsoleOnFinish = this.configurationService.getValue('debug').closeConsoleOnFinish; + if (closeConsoleOnFinish) { + this.panelService.hideActivePanel(); + } } })); From 9eea70ac8891500f21d0e902e4889f33e3dce0cc Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 25 Oct 2019 13:23:13 +0300 Subject: [PATCH 006/241] Rename the new setting to 'closeConsoleOnEnd' --- src/vs/workbench/contrib/debug/browser/debug.contribution.ts | 2 +- src/vs/workbench/contrib/debug/common/debug.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 7c3ef2ee91f..f9063838993 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -213,7 +213,7 @@ configurationRegistry.registerConfiguration({ default: 'onFirstSessionStart' }, 'debug.internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA, - 'debug.closeConsoleOnFinish': { + 'debug.closeConsoleOnEnd': { type: 'boolean', description: nls.localize({ comment: ['This is the description for a setting'], key: 'closeConsoleOnFinish' }, "If set to true, the debug console will be closed automatically when opened because of the debugging (see `internalConsoleOptions`). Has no effect if `internalConsoleOptions` was set to `neverOpen`."), default: true diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 6e152f6ec0d..487b4275499 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -456,7 +456,7 @@ export interface IDebugConfiguration { toolBarLocation: 'floating' | 'docked' | 'hidden'; showInStatusBar: 'never' | 'always' | 'onFirstSessionStart'; internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart'; - closeConsoleOnFinish: boolean; + closeConsoleOnEnd: boolean; extensionHostDebugAdapter: boolean; enableAllHovers: boolean; showSubSessionsInToolBar: boolean; From dc2f7d939feba320c6fd0380aef462d2794f68f0 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 25 Oct 2019 13:24:36 +0300 Subject: [PATCH 007/241] Chanegd efault to keep consistency --- src/vs/workbench/contrib/debug/browser/debug.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index f9063838993..2c8e317b12b 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -216,7 +216,7 @@ configurationRegistry.registerConfiguration({ 'debug.closeConsoleOnEnd': { type: 'boolean', description: nls.localize({ comment: ['This is the description for a setting'], key: 'closeConsoleOnFinish' }, "If set to true, the debug console will be closed automatically when opened because of the debugging (see `internalConsoleOptions`). Has no effect if `internalConsoleOptions` was set to `neverOpen`."), - default: true + default: false }, 'debug.openDebug': { enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart', 'openOnDebugBreak'], From 8237f0d62266b2f19e80229151e4798b32add5ca Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 25 Oct 2019 13:34:38 +0300 Subject: [PATCH 008/241] Make setting has three options --- .../workbench/contrib/debug/browser/debug.contribution.ts | 4 ++-- src/vs/workbench/contrib/debug/browser/debugService.ts | 8 +++----- src/vs/workbench/contrib/debug/common/debug.ts | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 2c8e317b12b..71cb0c6c152 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -214,9 +214,9 @@ configurationRegistry.registerConfiguration({ }, 'debug.internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA, 'debug.closeConsoleOnEnd': { - type: 'boolean', + enum: ['never', 'always', 'whenOpenedByDebug'], description: nls.localize({ comment: ['This is the description for a setting'], key: 'closeConsoleOnFinish' }, "If set to true, the debug console will be closed automatically when opened because of the debugging (see `internalConsoleOptions`). Has no effect if `internalConsoleOptions` was set to `neverOpen`."), - default: false + default: 'never' }, 'debug.openDebug': { enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart', 'openOnDebugBreak'], diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 17c916e9714..187c0d8a5bb 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -569,11 +569,9 @@ export class DebugService implements IDebugService { dataBreakpoints.forEach(dbp => this.model.removeDataBreakpoints(dbp.getId())); } - if (!this.internalTerminalWasOpened) { - const closeConsoleOnFinish = this.configurationService.getValue('debug').closeConsoleOnFinish; - if (closeConsoleOnFinish) { - this.panelService.hideActivePanel(); - } + const closeConsoleOnEnd = this.configurationService.getValue('debug').closeConsoleOnEnd; + if (closeConsoleOnEnd === 'always' || (closeConsoleOnEnd === 'whenOpenedByDebug' && !this.internalTerminalWasOpened)) { + this.panelService.hideActivePanel(); } })); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 487b4275499..6649e8fe133 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -456,7 +456,7 @@ export interface IDebugConfiguration { toolBarLocation: 'floating' | 'docked' | 'hidden'; showInStatusBar: 'never' | 'always' | 'onFirstSessionStart'; internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart'; - closeConsoleOnEnd: boolean; + closeConsoleOnEnd: 'never' | 'always' | 'whenOpenedByDebug'; extensionHostDebugAdapter: boolean; enableAllHovers: boolean; showSubSessionsInToolBar: boolean; From c5c723bae8b83b507fa1242c83b849da67839d68 Mon Sep 17 00:00:00 2001 From: Daniel Beigi Date: Fri, 15 Nov 2019 23:46:01 -0500 Subject: [PATCH 009/241] Fixed #83983 added highlight collapsed --- src/vs/editor/contrib/folding/foldingDecorations.ts | 2 ++ src/vs/editor/contrib/folding/foldingModel.ts | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/folding/foldingDecorations.ts b/src/vs/editor/contrib/folding/foldingDecorations.ts index 333ad459db0..e3bbec8ae67 100644 --- a/src/vs/editor/contrib/folding/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/foldingDecorations.ts @@ -13,6 +13,8 @@ export class FoldingDecorationProvider implements IDecorationProvider { private static readonly COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, afterContentClassName: 'inline-folded', + className: 'folded-background', + isWholeLine: true, linesDecorationsClassName: 'codicon codicon-chevron-right' }); diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index 93a52a14728..4102adab824 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -6,6 +6,8 @@ import { ITextModel, IModelDecorationOptions, IModelDeltaDecoration, IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; import { Event, Emitter } from 'vs/base/common/event'; import { FoldingRegions, ILineRange, FoldingRegion } from './foldingRanges'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { editorSelectionBackground } from 'vs/platform/theme/common/colorRegistry'; export interface IDecorationProvider { getDecorationOption(isCollapsed: boolean): IModelDecorationOptions; @@ -84,13 +86,18 @@ export class FoldingModel { let maxColumn = this._textModel.getLineMaxColumn(startLineNumber); let decorationRange = { startLineNumber: startLineNumber, - startColumn: maxColumn, + startColumn: 0, endLineNumber: startLineNumber, endColumn: maxColumn }; newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed) }); }; - + registerThemingParticipant((theme, collector) => { + const highlightCollapsedBackground = theme.getColor(editorSelectionBackground); + if (highlightCollapsedBackground) { + collector.addRule(`.monaco-editor .folded-background { background-color: ${highlightCollapsedBackground}; }`); + } + }); let i = 0; let nextCollapsed = () => { while (i < this._regions.length) { From 86e5ec18789a020b74cb6912b52054136019f66a Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sat, 23 Nov 2019 22:08:40 +0200 Subject: [PATCH 010/241] Rename setting to 'debug.console.closeOnEnd' --- src/vs/workbench/contrib/debug/browser/debug.contribution.ts | 2 +- src/vs/workbench/contrib/debug/browser/debugService.ts | 2 +- src/vs/workbench/contrib/debug/common/debug.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index a2df78ff464..47bb0d3394c 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -213,7 +213,7 @@ configurationRegistry.registerConfiguration({ default: 'onFirstSessionStart' }, 'debug.internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA, - 'debug.closeConsoleOnEnd': { + 'debug.console.closeOnEnd': { enum: ['never', 'always', 'whenOpenedByDebug'], description: nls.localize({ comment: ['This is the description for a setting'], key: 'closeConsoleOnFinish' }, "If set to true, the debug console will be closed automatically when opened because of the debugging (see `internalConsoleOptions`). Has no effect if `internalConsoleOptions` was set to `neverOpen`."), default: 'never' diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 187c0d8a5bb..53d48bd5ba7 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -569,7 +569,7 @@ export class DebugService implements IDebugService { dataBreakpoints.forEach(dbp => this.model.removeDataBreakpoints(dbp.getId())); } - const closeConsoleOnEnd = this.configurationService.getValue('debug').closeConsoleOnEnd; + const closeConsoleOnEnd = this.configurationService.getValue('debug').console.closeOnEnd; if (closeConsoleOnEnd === 'always' || (closeConsoleOnEnd === 'whenOpenedByDebug' && !this.internalTerminalWasOpened)) { this.panelService.hideActivePanel(); } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 6649e8fe133..184573a5356 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -456,7 +456,6 @@ export interface IDebugConfiguration { toolBarLocation: 'floating' | 'docked' | 'hidden'; showInStatusBar: 'never' | 'always' | 'onFirstSessionStart'; internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart'; - closeConsoleOnEnd: 'never' | 'always' | 'whenOpenedByDebug'; extensionHostDebugAdapter: boolean; enableAllHovers: boolean; showSubSessionsInToolBar: boolean; @@ -465,6 +464,7 @@ export interface IDebugConfiguration { fontFamily: string; lineHeight: number; wordWrap: boolean; + closeOnEnd: 'never' | 'always' | 'whenOpenedByDebug'; }; focusWindowOnBreak: boolean; onTaskErrors: 'debugAnyway' | 'showErrors' | 'prompt'; From 1187306d9afe3833a12b99234b7b2203e309b871 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sat, 23 Nov 2019 22:28:07 +0200 Subject: [PATCH 011/241] Accurate description --- src/vs/workbench/contrib/debug/browser/debug.contribution.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 47bb0d3394c..0309853f083 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -215,7 +215,8 @@ configurationRegistry.registerConfiguration({ 'debug.internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA, 'debug.console.closeOnEnd': { enum: ['never', 'always', 'whenOpenedByDebug'], - description: nls.localize({ comment: ['This is the description for a setting'], key: 'closeConsoleOnFinish' }, "If set to true, the debug console will be closed automatically when opened because of the debugging (see `internalConsoleOptions`). Has no effect if `internalConsoleOptions` was set to `neverOpen`."), + description: nls.localize('debug.console.closeOnEnd', "Controls what to do with the debug console when the debug session ends."), + enumDescriptions: [nls.localize('neverClose', "Remain it as-is"), nls.localize('alwaysClose', "Close it (if opened)"), nls.localize('closeWhenOpenedByDebug', "Close if the debugging process opened it, see debug.internalConsoleOptions")], default: 'never' }, 'debug.openDebug': { From 5ddbda0172d80bfbb2529987ba9020848e8771f7 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 2 Dec 2019 16:05:53 +0100 Subject: [PATCH 012/241] add theming API --- src/vs/vscode.proposed.d.ts | 36 ++++++++++++++++++ .../api/browser/extensionHost.contribution.ts | 1 + .../api/browser/mainThreadTheming.ts | 33 ++++++++++++++++ .../workbench/api/common/extHost.api.impl.ts | 13 ++++++- .../workbench/api/common/extHost.protocol.ts | 12 +++++- src/vs/workbench/api/common/extHostTheming.ts | 38 +++++++++++++++++++ src/vs/workbench/api/common/extHostTypes.ts | 17 +++++++++ 7 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 src/vs/workbench/api/browser/mainThreadTheming.ts create mode 100644 src/vs/workbench/api/common/extHostTheming.ts diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 5d2f4c6e87c..6dbee43c706 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1412,4 +1412,40 @@ declare module 'vscode' { } //#endregion + + //#region color theme access + + /** + * Represents a color theme kind. + */ + export enum ColorThemeKind { + Light = 1, + Dark = 2, + HighContrast = 3 + } + + /** + * Represents a color theme. + */ + export interface ColorTheme { + + /** + * The kind of this color theme: light, dark or high contrast. + */ + readonly kind: ColorThemeKind; + } + + export namespace window { + /** + * The currently active color theme as configured in the settings. The active + * theme can be changed via the `workbench.colorTheme` setting. + */ + export let activeColorTheme: ColorTheme; + + /** + * An [event](#Event) which fires when the active theme changes or one of it's colors chnage. + */ + export const onDidChangeActiveColorTheme: Event; + } + } diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 2905c524113..dbdaa9b0509 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -47,6 +47,7 @@ import './mainThreadStatusBar'; import './mainThreadStorage'; import './mainThreadTelemetry'; import './mainThreadTerminalService'; +import './mainThreadTheming'; import './mainThreadTreeViews'; import './mainThreadDownloadService'; import './mainThreadUrls'; diff --git a/src/vs/workbench/api/browser/mainThreadTheming.ts b/src/vs/workbench/api/browser/mainThreadTheming.ts new file mode 100644 index 00000000000..3caaa560e30 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadTheming.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MainContext, IExtHostContext, ExtHostThemingShape, ExtHostContext, MainThreadThemingShape } from '../common/extHost.protocol'; +import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; + +@extHostNamedCustomer(MainContext.MainThreadTheming) +export class MainThreadTheming implements MainThreadThemingShape { + + private readonly _themeService: IThemeService; + private readonly _proxy: ExtHostThemingShape; + private readonly _themeChangeListener: IDisposable; + + constructor( + extHostContext: IExtHostContext, + @IThemeService themeService: IThemeService + ) { + this._themeService = themeService; + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTheming); + + this._themeChangeListener = this._themeService.onThemeChange(e => { + this._proxy.$onColorThemeChange(this._themeService.getTheme().type); + }); + } + + dispose(): void { + this._themeChangeListener.dispose(); + } +} diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 9e32aedf532..cfec00d5d01 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -67,6 +67,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -122,7 +123,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); - const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol)); + const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHostLabelService, new ExtHostLabelService(rpcProtocol)); + const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol)); // Check that no named customers are missing const expected: ProxyIdentifier[] = values(ExtHostContext); @@ -550,6 +552,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, createInputBox(): vscode.InputBox { return extHostQuickOpen.createInputBox(extension.identifier); + }, + get activeColorTheme(): vscode.ColorTheme { + return extHostTheming.activeColorTheme; + }, + onDidChangeActiveColorTheme(listener, thisArg?, disposables?) { + return extHostTheming.onDidChangeActiveColorTheme(listener, thisArg, disposables); } }; @@ -939,7 +947,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I DebugConsoleMode: extHostTypes.DebugConsoleMode, Decoration: extHostTypes.Decoration, WebviewContentState: extHostTypes.WebviewContentState, - UIKind: UIKind + UIKind: UIKind, + ColorThemeKind: extHostTypes.ColorThemeKind }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4bf6846bd20..fc81b8a2d59 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1385,6 +1385,12 @@ export interface ExtHostStorageShape { $acceptValue(shared: boolean, key: string, value: object | undefined): void; } +export interface ExtHostThemingShape { + $onColorThemeChange(themeType: string): void; +} + +export interface MainThreadThemingShape extends IDisposable { +} // --- proxy identifiers export const MainContext = { @@ -1425,7 +1431,8 @@ export const MainContext = { MainThreadSearch: createMainId('MainThreadSearch'), MainThreadTask: createMainId('MainThreadTask'), MainThreadWindow: createMainId('MainThreadWindow'), - MainThreadLabelService: createMainId('MainThreadLabelService') + MainThreadLabelService: createMainId('MainThreadLabelService'), + MainThreadTheming: createMainId('MainThreadTheming') }; export const ExtHostContext = { @@ -1459,5 +1466,6 @@ export const ExtHostContext = { ExtHostStorage: createMainId('ExtHostStorage'), ExtHostUrls: createExtId('ExtHostUrls'), ExtHostOutputService: createMainId('ExtHostOutputService'), - ExtHosLabelService: createMainId('ExtHostLabelService') + ExtHostLabelService: createMainId('ExtHostLabelService'), + ExtHostTheming: createMainId('ExtHostTheming') }; diff --git a/src/vs/workbench/api/common/extHostTheming.ts b/src/vs/workbench/api/common/extHostTheming.ts new file mode 100644 index 00000000000..04c3d517fdc --- /dev/null +++ b/src/vs/workbench/api/common/extHostTheming.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ColorTheme, ColorThemeKind } from './extHostTypes'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ExtHostThemingShape } from 'vs/workbench/api/common/extHost.protocol'; +import { Emitter, Event } from 'vs/base/common/event'; + +export class ExtHostTheming implements ExtHostThemingShape { + + readonly _serviceBrand: undefined; + + private _actual: ColorTheme; + private _onDidChangeActiveColorTheme: Emitter; + + constructor( + @IExtHostRpcService _extHostRpc: IExtHostRpcService + ) { + this._actual = new ColorTheme(ColorThemeKind.Dark); + this._onDidChangeActiveColorTheme = new Emitter(); + } + + public get activeColorTheme(): ColorTheme { + return this._actual; + } + + $onColorThemeChange(type: string): void { + let kind = type === 'light' ? ColorThemeKind.Light : type === 'dark' ? ColorThemeKind.Dark : ColorThemeKind.HighContrast; + this._actual = new ColorTheme(kind); + this._onDidChangeActiveColorTheme.fire(this._actual); + } + + public get onDidChangeActiveColorTheme(): Event { + return this._onDidChangeActiveColorTheme.event; + } +} diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 8f89d246b5a..18d93b3edef 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2503,3 +2503,20 @@ export enum WebviewContentState { Unchanged = 2, Dirty = 3, } + + +//#region Theming + +@es5ClassCompat +export class ColorTheme implements vscode.ColorTheme { + constructor(public readonly kind: ColorThemeKind) { + } +} + +export enum ColorThemeKind { + Light = 1, + Dark = 2, + HighContrast = 3 +} + +//#endregion Theming From 256f2e0965758b9b3b14e78ed6485339ae0c5dec Mon Sep 17 00:00:00 2001 From: Marvin Heilemann Date: Mon, 9 Dec 2019 16:41:29 +0100 Subject: [PATCH 013/241] First working version of native auto switching workspace theme This implements three new options to the workspace scope. One to enable/ disable the auto switch functionality and two others to set the dark and light theme. --- .../themes/browser/workbenchThemeService.ts | 402 ++++++++++++++---- .../themes/common/workbenchThemeService.ts | 36 +- 2 files changed, 345 insertions(+), 93 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index c0172a4cdd8..b567c70281f 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,13 +6,41 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { + IWorkbenchThemeService, + IColorTheme, + ITokenColorCustomizations, + IFileIconTheme, + ExtensionData, + VS_LIGHT_THEME, + VS_DARK_THEME, + VS_HC_THEME, + COLOR_THEME_SETTING, + ICON_THEME_SETTING, + CUSTOM_WORKBENCH_COLORS_SETTING, + CUSTOM_EDITOR_COLORS_SETTING, + DETECT_HC_SETTING, + HC_THEME_ID, + IColorCustomizations, + CUSTOM_EDITOR_TOKENSTYLES_SETTING, + IExperimentalTokenStyleCustomizations, + COLOR_THEME_DARK_SETTING, + COLOR_THEME_LIGHT_SETTING, + DETECT_AS_SETTING, + ColorScheme, + WINDOW_MATCH_PREFERS_COLOR_SCHEME +} from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; import * as errors from 'vs/base/common/errors'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; +import { + IConfigurationRegistry, + Extensions as ConfigurationExtensions, + IConfigurationPropertySchema, + IConfigurationNode +} from 'vs/platform/configuration/common/configurationRegistry'; import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; @@ -27,7 +55,11 @@ import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { textmateColorsSchemaId, registerColorThemeSchemas, textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; +import { + textmateColorsSchemaId, + registerColorThemeSchemas, + textmateColorSettingsSchemaId +} from 'vs/workbench/services/themes/common/colorThemeSchema'; import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -39,6 +71,9 @@ import { IExtensionResourceLoaderService } from 'vs/workbench/services/extension const DEFAULT_THEME_ID = 'vs-dark vscode-theme-defaults-themes-dark_plus-json'; const DEFAULT_THEME_SETTING_VALUE = 'Default Dark+'; +const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark+'; +const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light+'; +const DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE = true; const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; const PERSISTED_ICON_THEME_STORAGE_KEY = 'iconThemeData'; @@ -58,11 +93,16 @@ const themingRegistry = Registry.as(ThemingExtensions.ThemingC function validateThemeId(theme: string): string { // migrations switch (theme) { - case VS_LIGHT_THEME: return `vs ${defaultThemeExtensionId}-themes-light_vs-json`; - case VS_DARK_THEME: return `vs-dark ${defaultThemeExtensionId}-themes-dark_vs-json`; - case VS_HC_THEME: return `hc-black ${defaultThemeExtensionId}-themes-hc_black-json`; - case `vs ${oldDefaultThemeExtensionId}-themes-light_plus-tmTheme`: return `vs ${defaultThemeExtensionId}-themes-light_plus-json`; - case `vs-dark ${oldDefaultThemeExtensionId}-themes-dark_plus-tmTheme`: return `vs-dark ${defaultThemeExtensionId}-themes-dark_plus-json`; + case VS_LIGHT_THEME: + return `vs ${defaultThemeExtensionId}-themes-light_vs-json`; + case VS_DARK_THEME: + return `vs-dark ${defaultThemeExtensionId}-themes-dark_vs-json`; + case VS_HC_THEME: + return `hc-black ${defaultThemeExtensionId}-themes-hc_black-json`; + case `vs ${oldDefaultThemeExtensionId}-themes-light_plus-tmTheme`: + return `vs ${defaultThemeExtensionId}-themes-light_plus-json`; + case `vs-dark ${oldDefaultThemeExtensionId}-themes-dark_plus-tmTheme`: + return `vs-dark ${defaultThemeExtensionId}-themes-dark_plus-json`; } return theme; } @@ -71,6 +111,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { _serviceBrand: undefined; private colorThemeStore: ColorThemeStore; + private autoSwitchColorTheme = DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE; + private autoSwitchColorThemeListener: MediaQueryList | undefined = undefined; private currentColorTheme: ColorThemeData; private container: HTMLElement; private readonly onColorThemeChange: Emitter; @@ -94,7 +136,9 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private get tokenStylesCustomizations(): IExperimentalTokenStyleCustomizations { - return this.configurationService.getValue(CUSTOM_EDITOR_TOKENSTYLES_SETTING) || {}; + return ( + this.configurationService.getValue(CUSTOM_EDITOR_TOKENSTYLES_SETTING) || {} + ); } constructor( @@ -107,13 +151,13 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, @IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService ) { - this.container = layoutService.getWorkbenchContainer(); this.colorThemeStore = new ColorThemeStore(extensionService); this.onFileIconThemeChange = new Emitter(); this.iconThemeStore = new FileIconThemeStore(extensionService); this.onColorThemeChange = new Emitter({ leakWarningThreshold: 400 }); + this.onPreferColorSchemeChange = this.onPreferColorSchemeChange.bind(this); this.currentColorTheme = ColorThemeData.createUnloadedTheme(''); this.currentIconTheme = FileIconThemeData.createUnloadedTheme(''); @@ -146,9 +190,12 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } - this.initialize().then(undefined, errors.onUnexpectedError).then(_ => { - this.installConfigurationListener(); - }); + this.initialize() + .then(undefined, errors.onUnexpectedError) + .then(_ => { + this.installConfigurationListener(); + this.installColorThemeSwitch(); + }); let prevColorId: string | undefined = undefined; @@ -177,7 +224,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { tokenColorCustomizationSchema.allOf![1] = themeSpecificTokenColors; experimentalTokenStylingCustomizationSchema.allOf![1] = themeSpecificTokenStyling; - configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration); + configurationRegistry.notifyConfigurationSchemaUpdated( + themeSettingsConfiguration, + tokenColorCustomizationConfiguration + ); if (this.currentColorTheme.isLoaded) { const themeData = await this.colorThemeStore.findThemeData(this.currentColorTheme.id); @@ -186,7 +236,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { prevColorId = this.currentColorTheme.id; this.setColorTheme(DEFAULT_THEME_ID, 'auto'); } else { - if (this.currentColorTheme.id === DEFAULT_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeStore.findThemeData(prevColorId)) { + if ( + this.currentColorTheme.id === DEFAULT_THEME_ID && + !types.isUndefined(prevColorId) && + (await this.colorThemeStore.findThemeData(prevColorId)) + ) { // restore color this.setColorTheme(prevColorId, 'auto'); prevColorId = undefined; @@ -200,7 +254,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { let prevFileIconId: string | undefined = undefined; this.iconThemeStore.onDidChange(async event => { iconThemeSettingSchema.enum = [null, ...event.themes.map(t => t.settingsId)]; - iconThemeSettingSchema.enumDescriptions = [iconThemeSettingSchema.enumDescriptions![0], ...event.themes.map(t => t.description || '')]; + iconThemeSettingSchema.enumDescriptions = [ + iconThemeSettingSchema.enumDescriptions![0], + ...event.themes.map(t => t.description || '') + ]; configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration); if (this.currentIconTheme.isLoaded) { @@ -211,7 +268,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.setFileIconTheme(DEFAULT_ICON_THEME_ID, 'auto'); } else { // restore color - if (this.currentIconTheme.id === DEFAULT_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.iconThemeStore.findThemeData(prevFileIconId)) { + if ( + this.currentIconTheme.id === DEFAULT_ICON_THEME_ID && + !types.isUndefined(prevFileIconId) && + (await this.iconThemeStore.findThemeData(prevFileIconId)) + ) { this.setFileIconTheme(prevFileIconId, 'auto'); prevFileIconId = undefined; } else { @@ -222,10 +283,18 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); this.fileService.onFileChanges(async e => { - if (this.watchedColorThemeLocation && this.currentColorTheme && e.contains(this.watchedColorThemeLocation, FileChangeType.UPDATED)) { + if ( + this.watchedColorThemeLocation && + this.currentColorTheme && + e.contains(this.watchedColorThemeLocation, FileChangeType.UPDATED) + ) { this.reloadCurrentColorTheme(); } - if (this.watchedIconThemeLocation && this.currentIconTheme && e.contains(this.watchedIconThemeLocation, FileChangeType.UPDATED)) { + if ( + this.watchedIconThemeLocation && + this.currentIconTheme && + e.contains(this.watchedIconThemeLocation, FileChangeType.UPDATED) + ) { this.reloadCurrentFileIconTheme(); } }); @@ -248,16 +317,19 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private initialize(): Promise<[IColorTheme | null, IFileIconTheme | null]> { - let detectHCThemeSetting = this.configurationService.getValue(DETECT_HC_SETTING); + let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); + let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); - let colorThemeSetting: string; - if (this.environmentService.configuration.highContrast && detectHCThemeSetting) { - colorThemeSetting = HC_THEME_ID; - } else { - colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); + let detectThemeAutoSwitch = this.configurationService.getValue(DETECT_AS_SETTING); + this.autoSwitchColorTheme = detectThemeAutoSwitch; + if (detectThemeAutoSwitch) { + colorThemeSetting = this.getPreferredTheme(); } - let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); + let detectHCThemeSetting = this.configurationService.getValue(DETECT_HC_SETTING); + if (this.environmentService.configuration.highContrast && detectHCThemeSetting) { + colorThemeSetting = HC_THEME_ID; + } const extDevLocs = this.environmentService.extensionDevelopmentLocationURI; let uri: URI | undefined; @@ -267,7 +339,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } return Promise.all([ - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { + this.getColorThemeData(colorThemeSetting).then(theme => { return this.colorThemeStore.findThemeDataByParentLocation(uri).then(devThemes => { if (devThemes.length) { return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); @@ -284,20 +356,76 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined); } }); - }), + }) ]); } + private installColorThemeSwitch() { + console.log('INSTALL'); + this.autoSwitchColorThemeListener = window.matchMedia(WINDOW_MATCH_PREFERS_COLOR_SCHEME); + this.autoSwitchColorThemeListener.addListener(this.onPreferColorSchemeChange); + console.log('INSTALLED'); + } + + private deinstallColorThemeSwitch() { + console.log('DEINSTALL'); + if (this.autoSwitchColorThemeListener) { + this.autoSwitchColorThemeListener.removeListener(this.onPreferColorSchemeChange); + this.configurationService.updateValue(DETECT_AS_SETTING, false); + console.log('DEINSTALLED'); + } + } + + private onPreferColorSchemeChange({ matches }: MediaQueryListEvent) { + console.log('onPreferColorSchemeChange', matches); + let themeName = this.configurationService.getValue(COLOR_THEME_LIGHT_SETTING); + if (matches) { + // prefers dark mode + themeName = this.configurationService.getValue(COLOR_THEME_DARK_SETTING); + } + this.setTheme(themeName); + } + + private getPreferredTheme(): string { + let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_LIGHT_SETTING); + const darkMode = this.getPreferredColorScheme() === ColorScheme.DARK; + if (darkMode) { + colorThemeSetting = this.configurationService.getValue(COLOR_THEME_DARK_SETTING); + } + return colorThemeSetting; + } + + private setTheme(themeName: string) { + this.getColorThemeData(themeName).then(theme => { + if (theme) { + this.setColorTheme(theme.id, undefined); + this.configurationService.updateValue(COLOR_THEME_SETTING, themeName); + } + }); + } + private installConfigurationListener() { this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(COLOR_THEME_SETTING)) { let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => { - if (theme) { - this.setColorTheme(theme.id, undefined); - } - }); + this.setTheme(colorThemeSetting); + this.deinstallColorThemeSwitch(); + } + } + if (e.affectsConfiguration(DETECT_AS_SETTING)) { + let autoSwitchColorTheme = this.configurationService.getValue(DETECT_AS_SETTING); + console.log(autoSwitchColorTheme); + if (this.autoSwitchColorTheme !== autoSwitchColorTheme) { + this.autoSwitchColorTheme = autoSwitchColorTheme; + console.log('HAS CHANGED'); + if (autoSwitchColorTheme) { + this.installColorThemeSwitch(); + let themeName = this.getPreferredTheme(); + this.setTheme(themeName); + } else { + this.deinstallColorThemeSwitch(); + } } } if (e.affectsConfiguration(ICON_THEME_SETTING)) { @@ -338,11 +466,33 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.colorThemeStore.getColorThemes(); } + public getColorThemeData(colorThemeSetting: string): Promise { + return new Promise(resolve => { + this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { + resolve(theme); + }); + }); + } + + public getPreferredColorScheme(): ColorScheme { + const noPreference = window.matchMedia(`(prefers-color-scheme: ${ColorScheme.NO_PREFERENCE})`).matches; + const prefersDark = window.matchMedia(`(prefers-color-scheme: ${ColorScheme.DARK})`).matches; + if (noPreference) { + return ColorScheme.NO_PREFERENCE; + } else if (prefersDark) { + return ColorScheme.DARK; + } + return ColorScheme.LIGHT; + } + public getTheme(): ITheme { return this.getColorTheme(); } - public setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + public setColorTheme( + themeId: string | undefined, + settingsTarget: ConfigurationTarget | undefined | 'auto' + ): Promise { if (!themeId) { return Promise.resolve(null); } @@ -357,24 +507,40 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return null; } const themeData = data; - return themeData.ensureLoaded(this.extensionResourceLoaderService).then(_ => { - if (themeId === this.currentColorTheme.id && !this.currentColorTheme.isLoaded && this.currentColorTheme.hasEqualData(themeData)) { - this.currentColorTheme.clearCaches(); - // the loaded theme is identical to the perisisted theme. Don't need to send an event. - this.currentColorTheme = themeData; + return themeData.ensureLoaded(this.extensionResourceLoaderService).then( + _ => { + if ( + themeId === this.currentColorTheme.id && + !this.currentColorTheme.isLoaded && + this.currentColorTheme.hasEqualData(themeData) + ) { + this.currentColorTheme.clearCaches(); + // the loaded theme is identical to the perisisted theme. Don't need to send an event. + this.currentColorTheme = themeData; + themeData.setCustomColors(this.colorCustomizations); + themeData.setCustomTokenColors(this.tokenColorCustomizations); + themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); + return Promise.resolve(themeData); + } themeData.setCustomColors(this.colorCustomizations); themeData.setCustomTokenColors(this.tokenColorCustomizations); themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); - return Promise.resolve(themeData); + this.updateDynamicCSSRules(themeData); + return this.applyTheme(themeData, settingsTarget); + }, + error => { + return Promise.reject( + new Error( + nls.localize( + 'error.cannotloadtheme', + 'Unable to load {0}: {1}', + themeData.location!.toString(), + error.message + ) + ) + ); } - themeData.setCustomColors(this.colorCustomizations); - themeData.setCustomTokenColors(this.tokenColorCustomizations); - themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); - this.updateDynamicCSSRules(themeData); - return this.applyTheme(themeData, settingsTarget); - }, error => { - return Promise.reject(new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location!.toString(), error.message))); - }); + ); }); } @@ -390,11 +556,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { public restoreColorTheme() { let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => { - if (theme) { - this.setColorTheme(theme.id, undefined); - } - }); + this.setTheme(colorThemeSetting); } } @@ -411,7 +573,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { _applyRules([...cssRules].join('\n'), colorThemeRulesClassName); } - private applyTheme(newTheme: ColorThemeData, settingsTarget: ConfigurationTarget | undefined | 'auto', silent = false): Promise { + private applyTheme( + newTheme: ColorThemeData, + settingsTarget: ConfigurationTarget | undefined | 'auto', + silent = false + ): Promise { if (this.currentColorTheme.id) { removeClasses(this.container, this.currentColorTheme.id); } else { @@ -422,7 +588,9 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.currentColorTheme.clearCaches(); this.currentColorTheme = newTheme; if (!this.themingParticipantChangeListener) { - this.themingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(_ => this.updateDynamicCSSRules(this.currentColorTheme)); + this.themingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(_ => + this.updateDynamicCSSRules(this.currentColorTheme) + ); } if (this.fileService && !resources.isEqual(newTheme.location, this.watchedColorThemeLocation)) { @@ -453,7 +621,9 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private writeColorThemeConfiguration(settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { if (!types.isUndefinedOrNull(settingsTarget)) { - return this.writeConfiguration(COLOR_THEME_SETTING, this.currentColorTheme.settingsId, settingsTarget).then(_ => this.currentColorTheme); + return this.writeConfiguration(COLOR_THEME_SETTING, this.currentColorTheme.settingsId, settingsTarget).then( + _ => this.currentColorTheme + ); } return Promise.resolve(this.currentColorTheme); } @@ -464,11 +634,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { let key = themeType + themeData.extensionId; if (!this.themeExtensionsActivated.get(key)) { type ActivatePluginClassification = { - id: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; - name: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; - isBuiltin: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - publisherDisplayName: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - themeId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + id: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; + name: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; + isBuiltin: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; + publisherDisplayName: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + themeId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; }; type ActivatePluginEvent = { id: string; @@ -501,7 +671,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.currentIconTheme; } - public setFileIconTheme(iconTheme: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + public setFileIconTheme( + iconTheme: string | undefined, + settingsTarget: ConfigurationTarget | undefined | 'auto' + ): Promise { iconTheme = iconTheme || ''; if (iconTheme === this.currentIconTheme.id && this.currentIconTheme.isLoaded) { return this.writeFileIconConfiguration(settingsTarget); @@ -557,7 +730,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { dispose(this.watchedIconThemeDisposable); this.watchedIconThemeLocation = undefined; - if (iconThemeData.location && (iconThemeData.watch || !!this.environmentService.extensionDevelopmentLocationURI)) { + if ( + iconThemeData.location && + (iconThemeData.watch || !!this.environmentService.extensionDevelopmentLocationURI) + ) { this.watchedIconThemeLocation = iconThemeData.location; this.watchedIconThemeDisposable = this.fileService.watch(iconThemeData.location); } @@ -567,12 +743,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.sendTelemetry(iconThemeData.id, iconThemeData.extensionData, 'fileIcon'); } this.onFileIconThemeChange.fire(this.currentIconTheme); - } - private writeFileIconConfiguration(settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { + private writeFileIconConfiguration( + settingsTarget: ConfigurationTarget | undefined | 'auto' + ): Promise { if (!types.isUndefinedOrNull(settingsTarget)) { - return this.writeConfiguration(ICON_THEME_SETTING, this.currentIconTheme.settingsId, settingsTarget).then(_ => this.currentIconTheme); + return this.writeConfiguration(ICON_THEME_SETTING, this.currentIconTheme.settingsId, settingsTarget).then( + _ => this.currentIconTheme + ); } return Promise.resolve(this.currentIconTheme); } @@ -598,7 +777,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } value = undefined; // remove configuration from user settings } - } else if (settingsTarget === ConfigurationTarget.WORKSPACE || settingsTarget === ConfigurationTarget.WORKSPACE_FOLDER) { + } else if ( + settingsTarget === ConfigurationTarget.WORKSPACE || + settingsTarget === ConfigurationTarget.WORKSPACE_FOLDER + ) { if (value === settings.value) { return Promise.resolve(undefined); // nothing to do } @@ -617,7 +799,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } -function _applyIconTheme(data: FileIconThemeData, onApply: (theme: FileIconThemeData) => Promise): Promise { +function _applyIconTheme( + data: FileIconThemeData, + onApply: (theme: FileIconThemeData) => Promise +): Promise { _applyRules(data.styleSheetContent!, iconThemeRulesClassName); return onApply(data); } @@ -643,30 +828,49 @@ const configurationRegistry = Registry.as(ConfigurationE const colorThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', - description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."), + description: nls.localize('colorTheme', 'Specifies the color theme used in the workbench.'), default: DEFAULT_THEME_SETTING_VALUE, - enum: [], - enumDescriptions: [], - errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), + errorMessage: nls.localize('colorThemeError', 'Theme is unknown or not installed.') +}; +const colorThemeDarkSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('colorThemeDark', 'Specifies the color theme used for a dark OS appearance.'), + default: DEFAULT_THEME_DARK_SETTING_VALUE, + errorMessage: nls.localize('colorThemeError', 'Theme is unknown or not installed.') +}; +const colorThemeLightSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('colorThemeLight', 'Specifies the color theme used for a light OS appearance.'), + default: DEFAULT_THEME_LIGHT_SETTING_VALUE, + errorMessage: nls.localize('colorThemeError', 'Theme is unknown or not installed.') +}; +const colorThemeAutoSwitchSettingSchema: IConfigurationPropertySchema = { + type: 'boolean', + description: nls.localize('colorThemeAutoSwitch', 'Changes the color theme based on the OS appearance.'), + default: DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE }; const iconThemeSettingSchema: IConfigurationPropertySchema = { type: ['string', 'null'], default: DEFAULT_ICON_THEME_SETTING_VALUE, - description: nls.localize('iconTheme', "Specifies the icon theme used in the workbench or 'null' to not show any file icons."), + description: nls.localize( + 'iconTheme', + "Specifies the icon theme used in the workbench or 'null' to not show any file icons." + ), enum: [null], enumDescriptions: [nls.localize('noIconThemeDesc', 'No file icons')], - errorMessage: nls.localize('iconThemeError', "File icon theme is unknown or not installed.") + errorMessage: nls.localize('iconThemeError', 'File icon theme is unknown or not installed.') }; const colorCustomizationsSchema: IConfigurationPropertySchema = { type: 'object', - description: nls.localize('workbenchColors', "Overrides colors from the currently selected color theme."), + description: nls.localize('workbenchColors', 'Overrides colors from the currently selected color theme.'), allOf: [{ $ref: workbenchColorsSchemaId }], default: {}, - defaultSnippets: [{ - body: { + defaultSnippets: [ + { + body: {} } - }] + ] }; const themeSettingsConfiguration: IConfigurationNode = { @@ -675,6 +879,9 @@ const themeSettingsConfiguration: IConfigurationNode = { type: 'object', properties: { [COLOR_THEME_SETTING]: colorThemeSettingSchema, + [COLOR_THEME_DARK_SETTING]: colorThemeDarkSettingSchema, + [COLOR_THEME_LIGHT_SETTING]: colorThemeLightSettingSchema, + [DETECT_AS_SETTING]: colorThemeAutoSwitchSettingSchema, [ICON_THEME_SETTING]: iconThemeSettingSchema, [CUSTOM_WORKBENCH_COLORS_SETTING]: colorCustomizationsSchema } @@ -699,26 +906,45 @@ function tokenGroupSettings(description: string): IJSONSchema { const tokenColorSchema: IJSONSchema = { properties: { - comments: tokenGroupSettings(nls.localize('editorColors.comments', "Sets the colors and styles for comments")), - strings: tokenGroupSettings(nls.localize('editorColors.strings', "Sets the colors and styles for strings literals.")), - keywords: tokenGroupSettings(nls.localize('editorColors.keywords', "Sets the colors and styles for keywords.")), - numbers: tokenGroupSettings(nls.localize('editorColors.numbers', "Sets the colors and styles for number literals.")), - types: tokenGroupSettings(nls.localize('editorColors.types', "Sets the colors and styles for type declarations and references.")), - functions: tokenGroupSettings(nls.localize('editorColors.functions', "Sets the colors and styles for functions declarations and references.")), - variables: tokenGroupSettings(nls.localize('editorColors.variables', "Sets the colors and styles for variables declarations and references.")), + comments: tokenGroupSettings(nls.localize('editorColors.comments', 'Sets the colors and styles for comments')), + strings: tokenGroupSettings( + nls.localize('editorColors.strings', 'Sets the colors and styles for strings literals.') + ), + keywords: tokenGroupSettings(nls.localize('editorColors.keywords', 'Sets the colors and styles for keywords.')), + numbers: tokenGroupSettings( + nls.localize('editorColors.numbers', 'Sets the colors and styles for number literals.') + ), + types: tokenGroupSettings( + nls.localize('editorColors.types', 'Sets the colors and styles for type declarations and references.') + ), + functions: tokenGroupSettings( + nls.localize('editorColors.functions', 'Sets the colors and styles for functions declarations and references.') + ), + variables: tokenGroupSettings( + nls.localize('editorColors.variables', 'Sets the colors and styles for variables declarations and references.') + ), textMateRules: { - description: nls.localize('editorColors.textMateRules', 'Sets colors and styles using textmate theming rules (advanced).'), + description: nls.localize( + 'editorColors.textMateRules', + 'Sets colors and styles using textmate theming rules (advanced).' + ), $ref: textmateColorsSchemaId } } }; const tokenColorCustomizationSchema: IConfigurationPropertySchema = { - description: nls.localize('editorColors', "Overrides editor colors and font style from the currently selected color theme."), + description: nls.localize( + 'editorColors', + 'Overrides editor colors and font style from the currently selected color theme.' + ), default: {}, allOf: [tokenColorSchema] }; const experimentalTokenStylingCustomizationSchema: IConfigurationPropertySchema = { - description: nls.localize('editorColorsTokenStyles', "Overrides token color and styles from the currently selected color theme."), + description: nls.localize( + 'editorColorsTokenStyles', + 'Overrides token color and styles from the currently selected color theme.' + ), default: {}, allOf: [{ $ref: tokenStylingSchemaId }] }; diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 9937ca11be9..6577eb4a1d9 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -18,12 +18,22 @@ export const VS_HC_THEME = 'hc-black'; export const HC_THEME_ID = 'Default High Contrast'; export const COLOR_THEME_SETTING = 'workbench.colorTheme'; +export const COLOR_THEME_DARK_SETTING = 'workbench.colorThemeDark'; +export const COLOR_THEME_LIGHT_SETTING = 'workbench.colorThemeLight'; +export const DETECT_AS_SETTING = 'workbench.colorThemeAutoSwitch'; +export const WINDOW_MATCH_PREFERS_COLOR_SCHEME = '(prefers-color-scheme: dark)'; export const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; export const ICON_THEME_SETTING = 'workbench.iconTheme'; export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations'; export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations'; export const CUSTOM_EDITOR_TOKENSTYLES_SETTING = 'editor.tokenColorCustomizationsExperimental'; +export enum ColorScheme { + LIGHT = 'light', + DARK = 'dark', + NO_PREFERENCE = 'no-preference' +} + export interface IColorTheme extends ITheme { readonly id: string; readonly label: string; @@ -53,13 +63,20 @@ export interface IFileIconTheme extends IIconTheme { export interface IWorkbenchThemeService extends IThemeService { _serviceBrand: undefined; - setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; + setColorTheme( + themeId: string | undefined, + settingsTarget: ConfigurationTarget | undefined + ): Promise; getColorTheme(): IColorTheme; getColorThemes(): Promise; + getColorThemeData(colorThemeSetting: string): Promise; onDidColorThemeChange: Event; restoreColorTheme(): void; - setFileIconTheme(iconThemeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; + setFileIconTheme( + iconThemeId: string | undefined, + settingsTarget: ConfigurationTarget | undefined + ): Promise; getFileIconTheme(): IFileIconTheme; getFileIconThemes(): Promise; onDidFileIconThemeChange: Event; @@ -70,7 +87,12 @@ export interface IColorCustomizations { } export interface ITokenColorCustomizations { - [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITextMateThemingRule[]; + [groupIdOrThemeSettingsId: string]: + | string + | ITokenColorizationSetting + | ITokenColorCustomizations + | undefined + | ITextMateThemingRule[]; comments?: string | ITokenColorizationSetting; strings?: string | ITokenColorizationSetting; numbers?: string | ITokenColorizationSetting; @@ -82,7 +104,11 @@ export interface ITokenColorCustomizations { } export interface IExperimentalTokenStyleCustomizations { - [styleRuleOrThemeSettingsId: string]: string | ITokenColorizationSetting | IExperimentalTokenStyleCustomizations | undefined; + [styleRuleOrThemeSettingsId: string]: + | string + | ITokenColorizationSetting + | IExperimentalTokenStyleCustomizations + | undefined; } export interface ITextMateThemingRule { @@ -94,7 +120,7 @@ export interface ITextMateThemingRule { export interface ITokenColorizationSetting { foreground?: string; background?: string; - fontStyle?: string; /* [italic|underline|bold] */ + fontStyle?: string /* [italic|underline|bold] */; } export interface ExtensionData { From 6395537b79e8fd80202c7a40b3b8ed26f60eac90 Mon Sep 17 00:00:00 2001 From: Gustavo Cassel Date: Mon, 9 Dec 2019 22:23:33 -0300 Subject: [PATCH 014/241] Changed 'shift + click' folding behavior to collapse only inner ranges when current range is unfolded --- src/vs/editor/contrib/folding/folding.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 86f04e0db86..198a751ff01 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -32,6 +32,7 @@ import { InitializingRangeProvider, ID_INIT_PROVIDER } from 'vs/editor/contrib/f import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { onUnexpectedError } from 'vs/base/common/errors'; import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; const CONTEXT_FOLDING_ENABLED = new RawContextKey('foldingEnabled', false); @@ -421,8 +422,12 @@ export class FoldingController extends Disposable implements IEditorContribution if (region && region.startLineNumber === lineNumber) { let isCollapsed = region.isCollapsed; if (iconClicked || isCollapsed) { - let toToggle = [region]; - if (e.event.middleButton || e.event.shiftKey) { + let toToggle = []; + let considerRegionsInside = this.shouldConsiderRegionsInside(e.event); + if (isCollapsed || (!isCollapsed && !considerRegionsInside)) { + toToggle.push(region); + } + if (considerRegionsInside) { toToggle.push(...foldingModel.getRegionsInside(region, (r: FoldingRegion) => r.isCollapsed === isCollapsed)); } foldingModel.toggleCollapseState(toToggle); @@ -433,6 +438,10 @@ export class FoldingController extends Disposable implements IEditorContribution }).then(undefined, onUnexpectedError); } + private shouldConsiderRegionsInside(event: IMouseEvent): boolean { + return event.middleButton || event.shiftKey; + } + public reveal(position: IPosition): void { this.editor.revealPositionInCenterIfOutsideViewport(position, ScrollType.Smooth); } From fc8777550a90e176a72cd895a993581ab53c0e0b Mon Sep 17 00:00:00 2001 From: Marvin Heilemann Date: Tue, 10 Dec 2019 08:44:53 +0100 Subject: [PATCH 015/241] Rolled back changes to better see my changes (thanks to wrong configured prettier) --- .../themes/browser/workbenchThemeService.ts | 402 ++++-------------- .../themes/common/workbenchThemeService.ts | 38 +- 2 files changed, 94 insertions(+), 346 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index b567c70281f..c6c906e5cb0 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,41 +6,13 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { - IWorkbenchThemeService, - IColorTheme, - ITokenColorCustomizations, - IFileIconTheme, - ExtensionData, - VS_LIGHT_THEME, - VS_DARK_THEME, - VS_HC_THEME, - COLOR_THEME_SETTING, - ICON_THEME_SETTING, - CUSTOM_WORKBENCH_COLORS_SETTING, - CUSTOM_EDITOR_COLORS_SETTING, - DETECT_HC_SETTING, - HC_THEME_ID, - IColorCustomizations, - CUSTOM_EDITOR_TOKENSTYLES_SETTING, - IExperimentalTokenStyleCustomizations, - COLOR_THEME_DARK_SETTING, - COLOR_THEME_LIGHT_SETTING, - DETECT_AS_SETTING, - ColorScheme, - WINDOW_MATCH_PREFERS_COLOR_SCHEME -} from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; import * as errors from 'vs/base/common/errors'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { - IConfigurationRegistry, - Extensions as ConfigurationExtensions, - IConfigurationPropertySchema, - IConfigurationNode -} from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; @@ -55,11 +27,7 @@ import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { - textmateColorsSchemaId, - registerColorThemeSchemas, - textmateColorSettingsSchemaId -} from 'vs/workbench/services/themes/common/colorThemeSchema'; +import { textmateColorsSchemaId, registerColorThemeSchemas, textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -71,9 +39,6 @@ import { IExtensionResourceLoaderService } from 'vs/workbench/services/extension const DEFAULT_THEME_ID = 'vs-dark vscode-theme-defaults-themes-dark_plus-json'; const DEFAULT_THEME_SETTING_VALUE = 'Default Dark+'; -const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark+'; -const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light+'; -const DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE = true; const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; const PERSISTED_ICON_THEME_STORAGE_KEY = 'iconThemeData'; @@ -93,16 +58,11 @@ const themingRegistry = Registry.as(ThemingExtensions.ThemingC function validateThemeId(theme: string): string { // migrations switch (theme) { - case VS_LIGHT_THEME: - return `vs ${defaultThemeExtensionId}-themes-light_vs-json`; - case VS_DARK_THEME: - return `vs-dark ${defaultThemeExtensionId}-themes-dark_vs-json`; - case VS_HC_THEME: - return `hc-black ${defaultThemeExtensionId}-themes-hc_black-json`; - case `vs ${oldDefaultThemeExtensionId}-themes-light_plus-tmTheme`: - return `vs ${defaultThemeExtensionId}-themes-light_plus-json`; - case `vs-dark ${oldDefaultThemeExtensionId}-themes-dark_plus-tmTheme`: - return `vs-dark ${defaultThemeExtensionId}-themes-dark_plus-json`; + case VS_LIGHT_THEME: return `vs ${defaultThemeExtensionId}-themes-light_vs-json`; + case VS_DARK_THEME: return `vs-dark ${defaultThemeExtensionId}-themes-dark_vs-json`; + case VS_HC_THEME: return `hc-black ${defaultThemeExtensionId}-themes-hc_black-json`; + case `vs ${oldDefaultThemeExtensionId}-themes-light_plus-tmTheme`: return `vs ${defaultThemeExtensionId}-themes-light_plus-json`; + case `vs-dark ${oldDefaultThemeExtensionId}-themes-dark_plus-tmTheme`: return `vs-dark ${defaultThemeExtensionId}-themes-dark_plus-json`; } return theme; } @@ -111,8 +71,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { _serviceBrand: undefined; private colorThemeStore: ColorThemeStore; - private autoSwitchColorTheme = DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE; - private autoSwitchColorThemeListener: MediaQueryList | undefined = undefined; private currentColorTheme: ColorThemeData; private container: HTMLElement; private readonly onColorThemeChange: Emitter; @@ -136,9 +94,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private get tokenStylesCustomizations(): IExperimentalTokenStyleCustomizations { - return ( - this.configurationService.getValue(CUSTOM_EDITOR_TOKENSTYLES_SETTING) || {} - ); + return this.configurationService.getValue(CUSTOM_EDITOR_TOKENSTYLES_SETTING) || {}; } constructor( @@ -151,13 +107,13 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, @IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService ) { + this.container = layoutService.getWorkbenchContainer(); this.colorThemeStore = new ColorThemeStore(extensionService); this.onFileIconThemeChange = new Emitter(); this.iconThemeStore = new FileIconThemeStore(extensionService); this.onColorThemeChange = new Emitter({ leakWarningThreshold: 400 }); - this.onPreferColorSchemeChange = this.onPreferColorSchemeChange.bind(this); this.currentColorTheme = ColorThemeData.createUnloadedTheme(''); this.currentIconTheme = FileIconThemeData.createUnloadedTheme(''); @@ -190,12 +146,9 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } - this.initialize() - .then(undefined, errors.onUnexpectedError) - .then(_ => { - this.installConfigurationListener(); - this.installColorThemeSwitch(); - }); + this.initialize().then(undefined, errors.onUnexpectedError).then(_ => { + this.installConfigurationListener(); + }); let prevColorId: string | undefined = undefined; @@ -224,10 +177,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { tokenColorCustomizationSchema.allOf![1] = themeSpecificTokenColors; experimentalTokenStylingCustomizationSchema.allOf![1] = themeSpecificTokenStyling; - configurationRegistry.notifyConfigurationSchemaUpdated( - themeSettingsConfiguration, - tokenColorCustomizationConfiguration - ); + configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration); if (this.currentColorTheme.isLoaded) { const themeData = await this.colorThemeStore.findThemeData(this.currentColorTheme.id); @@ -236,11 +186,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { prevColorId = this.currentColorTheme.id; this.setColorTheme(DEFAULT_THEME_ID, 'auto'); } else { - if ( - this.currentColorTheme.id === DEFAULT_THEME_ID && - !types.isUndefined(prevColorId) && - (await this.colorThemeStore.findThemeData(prevColorId)) - ) { + if (this.currentColorTheme.id === DEFAULT_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeStore.findThemeData(prevColorId)) { // restore color this.setColorTheme(prevColorId, 'auto'); prevColorId = undefined; @@ -254,10 +200,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { let prevFileIconId: string | undefined = undefined; this.iconThemeStore.onDidChange(async event => { iconThemeSettingSchema.enum = [null, ...event.themes.map(t => t.settingsId)]; - iconThemeSettingSchema.enumDescriptions = [ - iconThemeSettingSchema.enumDescriptions![0], - ...event.themes.map(t => t.description || '') - ]; + iconThemeSettingSchema.enumDescriptions = [iconThemeSettingSchema.enumDescriptions![0], ...event.themes.map(t => t.description || '')]; configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration); if (this.currentIconTheme.isLoaded) { @@ -268,11 +211,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.setFileIconTheme(DEFAULT_ICON_THEME_ID, 'auto'); } else { // restore color - if ( - this.currentIconTheme.id === DEFAULT_ICON_THEME_ID && - !types.isUndefined(prevFileIconId) && - (await this.iconThemeStore.findThemeData(prevFileIconId)) - ) { + if (this.currentIconTheme.id === DEFAULT_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.iconThemeStore.findThemeData(prevFileIconId)) { this.setFileIconTheme(prevFileIconId, 'auto'); prevFileIconId = undefined; } else { @@ -283,18 +222,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); this.fileService.onFileChanges(async e => { - if ( - this.watchedColorThemeLocation && - this.currentColorTheme && - e.contains(this.watchedColorThemeLocation, FileChangeType.UPDATED) - ) { + if (this.watchedColorThemeLocation && this.currentColorTheme && e.contains(this.watchedColorThemeLocation, FileChangeType.UPDATED)) { this.reloadCurrentColorTheme(); } - if ( - this.watchedIconThemeLocation && - this.currentIconTheme && - e.contains(this.watchedIconThemeLocation, FileChangeType.UPDATED) - ) { + if (this.watchedIconThemeLocation && this.currentIconTheme && e.contains(this.watchedIconThemeLocation, FileChangeType.UPDATED)) { this.reloadCurrentFileIconTheme(); } }); @@ -317,20 +248,17 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private initialize(): Promise<[IColorTheme | null, IFileIconTheme | null]> { - let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); - let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); - - let detectThemeAutoSwitch = this.configurationService.getValue(DETECT_AS_SETTING); - this.autoSwitchColorTheme = detectThemeAutoSwitch; - if (detectThemeAutoSwitch) { - colorThemeSetting = this.getPreferredTheme(); - } - let detectHCThemeSetting = this.configurationService.getValue(DETECT_HC_SETTING); + + let colorThemeSetting: string; if (this.environmentService.configuration.highContrast && detectHCThemeSetting) { colorThemeSetting = HC_THEME_ID; + } else { + colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); } + let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); + const extDevLocs = this.environmentService.extensionDevelopmentLocationURI; let uri: URI | undefined; if (extDevLocs && extDevLocs.length > 0) { @@ -339,7 +267,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } return Promise.all([ - this.getColorThemeData(colorThemeSetting).then(theme => { + this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { return this.colorThemeStore.findThemeDataByParentLocation(uri).then(devThemes => { if (devThemes.length) { return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); @@ -356,76 +284,20 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined); } }); - }) + }), ]); } - private installColorThemeSwitch() { - console.log('INSTALL'); - this.autoSwitchColorThemeListener = window.matchMedia(WINDOW_MATCH_PREFERS_COLOR_SCHEME); - this.autoSwitchColorThemeListener.addListener(this.onPreferColorSchemeChange); - console.log('INSTALLED'); - } - - private deinstallColorThemeSwitch() { - console.log('DEINSTALL'); - if (this.autoSwitchColorThemeListener) { - this.autoSwitchColorThemeListener.removeListener(this.onPreferColorSchemeChange); - this.configurationService.updateValue(DETECT_AS_SETTING, false); - console.log('DEINSTALLED'); - } - } - - private onPreferColorSchemeChange({ matches }: MediaQueryListEvent) { - console.log('onPreferColorSchemeChange', matches); - let themeName = this.configurationService.getValue(COLOR_THEME_LIGHT_SETTING); - if (matches) { - // prefers dark mode - themeName = this.configurationService.getValue(COLOR_THEME_DARK_SETTING); - } - this.setTheme(themeName); - } - - private getPreferredTheme(): string { - let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_LIGHT_SETTING); - const darkMode = this.getPreferredColorScheme() === ColorScheme.DARK; - if (darkMode) { - colorThemeSetting = this.configurationService.getValue(COLOR_THEME_DARK_SETTING); - } - return colorThemeSetting; - } - - private setTheme(themeName: string) { - this.getColorThemeData(themeName).then(theme => { - if (theme) { - this.setColorTheme(theme.id, undefined); - this.configurationService.updateValue(COLOR_THEME_SETTING, themeName); - } - }); - } - private installConfigurationListener() { this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(COLOR_THEME_SETTING)) { let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.setTheme(colorThemeSetting); - this.deinstallColorThemeSwitch(); - } - } - if (e.affectsConfiguration(DETECT_AS_SETTING)) { - let autoSwitchColorTheme = this.configurationService.getValue(DETECT_AS_SETTING); - console.log(autoSwitchColorTheme); - if (this.autoSwitchColorTheme !== autoSwitchColorTheme) { - this.autoSwitchColorTheme = autoSwitchColorTheme; - console.log('HAS CHANGED'); - if (autoSwitchColorTheme) { - this.installColorThemeSwitch(); - let themeName = this.getPreferredTheme(); - this.setTheme(themeName); - } else { - this.deinstallColorThemeSwitch(); - } + this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => { + if (theme) { + this.setColorTheme(theme.id, undefined); + } + }); } } if (e.affectsConfiguration(ICON_THEME_SETTING)) { @@ -466,33 +338,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.colorThemeStore.getColorThemes(); } - public getColorThemeData(colorThemeSetting: string): Promise { - return new Promise(resolve => { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { - resolve(theme); - }); - }); - } - - public getPreferredColorScheme(): ColorScheme { - const noPreference = window.matchMedia(`(prefers-color-scheme: ${ColorScheme.NO_PREFERENCE})`).matches; - const prefersDark = window.matchMedia(`(prefers-color-scheme: ${ColorScheme.DARK})`).matches; - if (noPreference) { - return ColorScheme.NO_PREFERENCE; - } else if (prefersDark) { - return ColorScheme.DARK; - } - return ColorScheme.LIGHT; - } - public getTheme(): ITheme { return this.getColorTheme(); } - public setColorTheme( - themeId: string | undefined, - settingsTarget: ConfigurationTarget | undefined | 'auto' - ): Promise { + public setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { if (!themeId) { return Promise.resolve(null); } @@ -507,40 +357,24 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return null; } const themeData = data; - return themeData.ensureLoaded(this.extensionResourceLoaderService).then( - _ => { - if ( - themeId === this.currentColorTheme.id && - !this.currentColorTheme.isLoaded && - this.currentColorTheme.hasEqualData(themeData) - ) { - this.currentColorTheme.clearCaches(); - // the loaded theme is identical to the perisisted theme. Don't need to send an event. - this.currentColorTheme = themeData; - themeData.setCustomColors(this.colorCustomizations); - themeData.setCustomTokenColors(this.tokenColorCustomizations); - themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); - return Promise.resolve(themeData); - } + return themeData.ensureLoaded(this.extensionResourceLoaderService).then(_ => { + if (themeId === this.currentColorTheme.id && !this.currentColorTheme.isLoaded && this.currentColorTheme.hasEqualData(themeData)) { + this.currentColorTheme.clearCaches(); + // the loaded theme is identical to the perisisted theme. Don't need to send an event. + this.currentColorTheme = themeData; themeData.setCustomColors(this.colorCustomizations); themeData.setCustomTokenColors(this.tokenColorCustomizations); themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); - this.updateDynamicCSSRules(themeData); - return this.applyTheme(themeData, settingsTarget); - }, - error => { - return Promise.reject( - new Error( - nls.localize( - 'error.cannotloadtheme', - 'Unable to load {0}: {1}', - themeData.location!.toString(), - error.message - ) - ) - ); + return Promise.resolve(themeData); } - ); + themeData.setCustomColors(this.colorCustomizations); + themeData.setCustomTokenColors(this.tokenColorCustomizations); + themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); + this.updateDynamicCSSRules(themeData); + return this.applyTheme(themeData, settingsTarget); + }, error => { + return Promise.reject(new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location!.toString(), error.message))); + }); }); } @@ -556,7 +390,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { public restoreColorTheme() { let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.setTheme(colorThemeSetting); + this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => { + if (theme) { + this.setColorTheme(theme.id, undefined); + } + }); } } @@ -573,11 +411,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { _applyRules([...cssRules].join('\n'), colorThemeRulesClassName); } - private applyTheme( - newTheme: ColorThemeData, - settingsTarget: ConfigurationTarget | undefined | 'auto', - silent = false - ): Promise { + private applyTheme(newTheme: ColorThemeData, settingsTarget: ConfigurationTarget | undefined | 'auto', silent = false): Promise { if (this.currentColorTheme.id) { removeClasses(this.container, this.currentColorTheme.id); } else { @@ -588,9 +422,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.currentColorTheme.clearCaches(); this.currentColorTheme = newTheme; if (!this.themingParticipantChangeListener) { - this.themingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(_ => - this.updateDynamicCSSRules(this.currentColorTheme) - ); + this.themingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(_ => this.updateDynamicCSSRules(this.currentColorTheme)); } if (this.fileService && !resources.isEqual(newTheme.location, this.watchedColorThemeLocation)) { @@ -621,9 +453,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private writeColorThemeConfiguration(settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { if (!types.isUndefinedOrNull(settingsTarget)) { - return this.writeConfiguration(COLOR_THEME_SETTING, this.currentColorTheme.settingsId, settingsTarget).then( - _ => this.currentColorTheme - ); + return this.writeConfiguration(COLOR_THEME_SETTING, this.currentColorTheme.settingsId, settingsTarget).then(_ => this.currentColorTheme); } return Promise.resolve(this.currentColorTheme); } @@ -634,11 +464,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { let key = themeType + themeData.extensionId; if (!this.themeExtensionsActivated.get(key)) { type ActivatePluginClassification = { - id: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; - name: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; - isBuiltin: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - publisherDisplayName: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - themeId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' }; + id: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + name: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + isBuiltin: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + publisherDisplayName: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + themeId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; }; type ActivatePluginEvent = { id: string; @@ -671,10 +501,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.currentIconTheme; } - public setFileIconTheme( - iconTheme: string | undefined, - settingsTarget: ConfigurationTarget | undefined | 'auto' - ): Promise { + public setFileIconTheme(iconTheme: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { iconTheme = iconTheme || ''; if (iconTheme === this.currentIconTheme.id && this.currentIconTheme.isLoaded) { return this.writeFileIconConfiguration(settingsTarget); @@ -730,10 +557,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { dispose(this.watchedIconThemeDisposable); this.watchedIconThemeLocation = undefined; - if ( - iconThemeData.location && - (iconThemeData.watch || !!this.environmentService.extensionDevelopmentLocationURI) - ) { + if (iconThemeData.location && (iconThemeData.watch || !!this.environmentService.extensionDevelopmentLocationURI)) { this.watchedIconThemeLocation = iconThemeData.location; this.watchedIconThemeDisposable = this.fileService.watch(iconThemeData.location); } @@ -743,15 +567,12 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.sendTelemetry(iconThemeData.id, iconThemeData.extensionData, 'fileIcon'); } this.onFileIconThemeChange.fire(this.currentIconTheme); + } - private writeFileIconConfiguration( - settingsTarget: ConfigurationTarget | undefined | 'auto' - ): Promise { + private writeFileIconConfiguration(settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { if (!types.isUndefinedOrNull(settingsTarget)) { - return this.writeConfiguration(ICON_THEME_SETTING, this.currentIconTheme.settingsId, settingsTarget).then( - _ => this.currentIconTheme - ); + return this.writeConfiguration(ICON_THEME_SETTING, this.currentIconTheme.settingsId, settingsTarget).then(_ => this.currentIconTheme); } return Promise.resolve(this.currentIconTheme); } @@ -777,10 +598,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } value = undefined; // remove configuration from user settings } - } else if ( - settingsTarget === ConfigurationTarget.WORKSPACE || - settingsTarget === ConfigurationTarget.WORKSPACE_FOLDER - ) { + } else if (settingsTarget === ConfigurationTarget.WORKSPACE || settingsTarget === ConfigurationTarget.WORKSPACE_FOLDER) { if (value === settings.value) { return Promise.resolve(undefined); // nothing to do } @@ -799,10 +617,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } -function _applyIconTheme( - data: FileIconThemeData, - onApply: (theme: FileIconThemeData) => Promise -): Promise { +function _applyIconTheme(data: FileIconThemeData, onApply: (theme: FileIconThemeData) => Promise): Promise { _applyRules(data.styleSheetContent!, iconThemeRulesClassName); return onApply(data); } @@ -828,49 +643,30 @@ const configurationRegistry = Registry.as(ConfigurationE const colorThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', - description: nls.localize('colorTheme', 'Specifies the color theme used in the workbench.'), + description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."), default: DEFAULT_THEME_SETTING_VALUE, - errorMessage: nls.localize('colorThemeError', 'Theme is unknown or not installed.') -}; -const colorThemeDarkSettingSchema: IConfigurationPropertySchema = { - type: 'string', - description: nls.localize('colorThemeDark', 'Specifies the color theme used for a dark OS appearance.'), - default: DEFAULT_THEME_DARK_SETTING_VALUE, - errorMessage: nls.localize('colorThemeError', 'Theme is unknown or not installed.') -}; -const colorThemeLightSettingSchema: IConfigurationPropertySchema = { - type: 'string', - description: nls.localize('colorThemeLight', 'Specifies the color theme used for a light OS appearance.'), - default: DEFAULT_THEME_LIGHT_SETTING_VALUE, - errorMessage: nls.localize('colorThemeError', 'Theme is unknown or not installed.') -}; -const colorThemeAutoSwitchSettingSchema: IConfigurationPropertySchema = { - type: 'boolean', - description: nls.localize('colorThemeAutoSwitch', 'Changes the color theme based on the OS appearance.'), - default: DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE + enum: [], + enumDescriptions: [], + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), }; const iconThemeSettingSchema: IConfigurationPropertySchema = { type: ['string', 'null'], default: DEFAULT_ICON_THEME_SETTING_VALUE, - description: nls.localize( - 'iconTheme', - "Specifies the icon theme used in the workbench or 'null' to not show any file icons." - ), + description: nls.localize('iconTheme', "Specifies the icon theme used in the workbench or 'null' to not show any file icons."), enum: [null], enumDescriptions: [nls.localize('noIconThemeDesc', 'No file icons')], - errorMessage: nls.localize('iconThemeError', 'File icon theme is unknown or not installed.') + errorMessage: nls.localize('iconThemeError', "File icon theme is unknown or not installed.") }; const colorCustomizationsSchema: IConfigurationPropertySchema = { type: 'object', - description: nls.localize('workbenchColors', 'Overrides colors from the currently selected color theme.'), + description: nls.localize('workbenchColors', "Overrides colors from the currently selected color theme."), allOf: [{ $ref: workbenchColorsSchemaId }], default: {}, - defaultSnippets: [ - { - body: {} + defaultSnippets: [{ + body: { } - ] + }] }; const themeSettingsConfiguration: IConfigurationNode = { @@ -879,9 +675,6 @@ const themeSettingsConfiguration: IConfigurationNode = { type: 'object', properties: { [COLOR_THEME_SETTING]: colorThemeSettingSchema, - [COLOR_THEME_DARK_SETTING]: colorThemeDarkSettingSchema, - [COLOR_THEME_LIGHT_SETTING]: colorThemeLightSettingSchema, - [DETECT_AS_SETTING]: colorThemeAutoSwitchSettingSchema, [ICON_THEME_SETTING]: iconThemeSettingSchema, [CUSTOM_WORKBENCH_COLORS_SETTING]: colorCustomizationsSchema } @@ -906,45 +699,26 @@ function tokenGroupSettings(description: string): IJSONSchema { const tokenColorSchema: IJSONSchema = { properties: { - comments: tokenGroupSettings(nls.localize('editorColors.comments', 'Sets the colors and styles for comments')), - strings: tokenGroupSettings( - nls.localize('editorColors.strings', 'Sets the colors and styles for strings literals.') - ), - keywords: tokenGroupSettings(nls.localize('editorColors.keywords', 'Sets the colors and styles for keywords.')), - numbers: tokenGroupSettings( - nls.localize('editorColors.numbers', 'Sets the colors and styles for number literals.') - ), - types: tokenGroupSettings( - nls.localize('editorColors.types', 'Sets the colors and styles for type declarations and references.') - ), - functions: tokenGroupSettings( - nls.localize('editorColors.functions', 'Sets the colors and styles for functions declarations and references.') - ), - variables: tokenGroupSettings( - nls.localize('editorColors.variables', 'Sets the colors and styles for variables declarations and references.') - ), + comments: tokenGroupSettings(nls.localize('editorColors.comments', "Sets the colors and styles for comments")), + strings: tokenGroupSettings(nls.localize('editorColors.strings', "Sets the colors and styles for strings literals.")), + keywords: tokenGroupSettings(nls.localize('editorColors.keywords', "Sets the colors and styles for keywords.")), + numbers: tokenGroupSettings(nls.localize('editorColors.numbers', "Sets the colors and styles for number literals.")), + types: tokenGroupSettings(nls.localize('editorColors.types', "Sets the colors and styles for type declarations and references.")), + functions: tokenGroupSettings(nls.localize('editorColors.functions', "Sets the colors and styles for functions declarations and references.")), + variables: tokenGroupSettings(nls.localize('editorColors.variables', "Sets the colors and styles for variables declarations and references.")), textMateRules: { - description: nls.localize( - 'editorColors.textMateRules', - 'Sets colors and styles using textmate theming rules (advanced).' - ), + description: nls.localize('editorColors.textMateRules', 'Sets colors and styles using textmate theming rules (advanced).'), $ref: textmateColorsSchemaId } } }; const tokenColorCustomizationSchema: IConfigurationPropertySchema = { - description: nls.localize( - 'editorColors', - 'Overrides editor colors and font style from the currently selected color theme.' - ), + description: nls.localize('editorColors', "Overrides editor colors and font style from the currently selected color theme."), default: {}, allOf: [tokenColorSchema] }; const experimentalTokenStylingCustomizationSchema: IConfigurationPropertySchema = { - description: nls.localize( - 'editorColorsTokenStyles', - 'Overrides token color and styles from the currently selected color theme.' - ), + description: nls.localize('editorColorsTokenStyles', "Overrides token color and styles from the currently selected color theme."), default: {}, allOf: [{ $ref: tokenStylingSchemaId }] }; @@ -959,4 +733,4 @@ const tokenColorCustomizationConfiguration: IConfigurationNode = { }; configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration); -registerSingleton(IWorkbenchThemeService, WorkbenchThemeService); +registerSingleton(IWorkbenchThemeService, WorkbenchThemeService); \ No newline at end of file diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 6577eb4a1d9..9fc33ebab82 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -18,22 +18,12 @@ export const VS_HC_THEME = 'hc-black'; export const HC_THEME_ID = 'Default High Contrast'; export const COLOR_THEME_SETTING = 'workbench.colorTheme'; -export const COLOR_THEME_DARK_SETTING = 'workbench.colorThemeDark'; -export const COLOR_THEME_LIGHT_SETTING = 'workbench.colorThemeLight'; -export const DETECT_AS_SETTING = 'workbench.colorThemeAutoSwitch'; -export const WINDOW_MATCH_PREFERS_COLOR_SCHEME = '(prefers-color-scheme: dark)'; export const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; export const ICON_THEME_SETTING = 'workbench.iconTheme'; export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations'; export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations'; export const CUSTOM_EDITOR_TOKENSTYLES_SETTING = 'editor.tokenColorCustomizationsExperimental'; -export enum ColorScheme { - LIGHT = 'light', - DARK = 'dark', - NO_PREFERENCE = 'no-preference' -} - export interface IColorTheme extends ITheme { readonly id: string; readonly label: string; @@ -63,20 +53,13 @@ export interface IFileIconTheme extends IIconTheme { export interface IWorkbenchThemeService extends IThemeService { _serviceBrand: undefined; - setColorTheme( - themeId: string | undefined, - settingsTarget: ConfigurationTarget | undefined - ): Promise; + setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; getColorTheme(): IColorTheme; getColorThemes(): Promise; - getColorThemeData(colorThemeSetting: string): Promise; onDidColorThemeChange: Event; restoreColorTheme(): void; - setFileIconTheme( - iconThemeId: string | undefined, - settingsTarget: ConfigurationTarget | undefined - ): Promise; + setFileIconTheme(iconThemeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; getFileIconTheme(): IFileIconTheme; getFileIconThemes(): Promise; onDidFileIconThemeChange: Event; @@ -87,12 +70,7 @@ export interface IColorCustomizations { } export interface ITokenColorCustomizations { - [groupIdOrThemeSettingsId: string]: - | string - | ITokenColorizationSetting - | ITokenColorCustomizations - | undefined - | ITextMateThemingRule[]; + [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITextMateThemingRule[]; comments?: string | ITokenColorizationSetting; strings?: string | ITokenColorizationSetting; numbers?: string | ITokenColorizationSetting; @@ -104,11 +82,7 @@ export interface ITokenColorCustomizations { } export interface IExperimentalTokenStyleCustomizations { - [styleRuleOrThemeSettingsId: string]: - | string - | ITokenColorizationSetting - | IExperimentalTokenStyleCustomizations - | undefined; + [styleRuleOrThemeSettingsId: string]: string | ITokenColorizationSetting | IExperimentalTokenStyleCustomizations | undefined; } export interface ITextMateThemingRule { @@ -120,7 +94,7 @@ export interface ITextMateThemingRule { export interface ITokenColorizationSetting { foreground?: string; background?: string; - fontStyle?: string /* [italic|underline|bold] */; + fontStyle?: string; /* [italic|underline|bold] */ } export interface ExtensionData { @@ -137,4 +111,4 @@ export interface IThemeExtensionPoint { path: string; uiTheme?: typeof VS_LIGHT_THEME | typeof VS_DARK_THEME | typeof VS_HC_THEME; _watch: boolean; // unsupported options to watch location -} +} \ No newline at end of file From 6099ca39676d4ca225b035d62f3426568d4a70a1 Mon Sep 17 00:00:00 2001 From: Marvin Heilemann Date: Tue, 10 Dec 2019 09:02:16 +0100 Subject: [PATCH 016/241] Applied changes again --- .../themes/browser/workbenchThemeService.ts | 139 +++++++++++++++--- .../themes/common/workbenchThemeService.ts | 11 ++ 2 files changed, 131 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index c6c906e5cb0..59d2b9df791 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations, DETECT_AS_SETTING, COLOR_THEME_DARK_SETTING, WINDOW_MATCH_PREFERS_COLOR_SCHEME, COLOR_THEME_LIGHT_SETTING, ColorScheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -39,6 +39,9 @@ import { IExtensionResourceLoaderService } from 'vs/workbench/services/extension const DEFAULT_THEME_ID = 'vs-dark vscode-theme-defaults-themes-dark_plus-json'; const DEFAULT_THEME_SETTING_VALUE = 'Default Dark+'; +const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark+'; +const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light+'; +const DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE = true; const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; const PERSISTED_ICON_THEME_STORAGE_KEY = 'iconThemeData'; @@ -72,6 +75,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private colorThemeStore: ColorThemeStore; private currentColorTheme: ColorThemeData; + private autoSwitchColorTheme = DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE; + private autoSwitchColorThemeListener: MediaQueryList | undefined = undefined; private container: HTMLElement; private readonly onColorThemeChange: Emitter; private watchedColorThemeLocation: URI | undefined; @@ -114,6 +119,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.iconThemeStore = new FileIconThemeStore(extensionService); this.onColorThemeChange = new Emitter({ leakWarningThreshold: 400 }); + this.onPreferColorSchemeChange = this.onPreferColorSchemeChange.bind(this); this.currentColorTheme = ColorThemeData.createUnloadedTheme(''); this.currentIconTheme = FileIconThemeData.createUnloadedTheme(''); @@ -148,6 +154,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.initialize().then(undefined, errors.onUnexpectedError).then(_ => { this.installConfigurationListener(); + this.installColorThemeSwitch(); }); let prevColorId: string | undefined = undefined; @@ -248,16 +255,19 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private initialize(): Promise<[IColorTheme | null, IFileIconTheme | null]> { - let detectHCThemeSetting = this.configurationService.getValue(DETECT_HC_SETTING); + let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); + let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); - let colorThemeSetting: string; - if (this.environmentService.configuration.highContrast && detectHCThemeSetting) { - colorThemeSetting = HC_THEME_ID; - } else { - colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); + let detectThemeAutoSwitch = this.configurationService.getValue(DETECT_AS_SETTING); + this.autoSwitchColorTheme = detectThemeAutoSwitch; + if (detectThemeAutoSwitch) { + colorThemeSetting = this.getPreferredTheme(); } - let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); + let detectHCThemeSetting = this.configurationService.getValue(DETECT_HC_SETTING); + if (this.environmentService.configuration.highContrast && detectHCThemeSetting) { + colorThemeSetting = HC_THEME_ID; + } const extDevLocs = this.environmentService.extensionDevelopmentLocationURI; let uri: URI | undefined; @@ -267,7 +277,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } return Promise.all([ - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { + this.getColorThemeData(colorThemeSetting).then(theme => { return this.colorThemeStore.findThemeDataByParentLocation(uri).then(devThemes => { if (devThemes.length) { return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); @@ -288,16 +298,54 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { ]); } + private installColorThemeSwitch() { + console.log('INSTALL'); + this.autoSwitchColorThemeListener = window.matchMedia(WINDOW_MATCH_PREFERS_COLOR_SCHEME); + this.autoSwitchColorThemeListener.addListener(this.onPreferColorSchemeChange); + console.log('INSTALLED'); + } + + private deinstallColorThemeSwitch() { + console.log('DEINSTALL'); + if (this.autoSwitchColorThemeListener) { + this.autoSwitchColorThemeListener.removeListener(this.onPreferColorSchemeChange); + this.configurationService.updateValue(DETECT_AS_SETTING, false); + console.log('DEINSTALLED'); + } + } + + private onPreferColorSchemeChange({ matches }: MediaQueryListEvent) { + console.log('onPreferColorSchemeChange', matches); + let themeName = this.configurationService.getValue(COLOR_THEME_LIGHT_SETTING); + if (matches) { + // prefers dark mode + themeName = this.configurationService.getValue(COLOR_THEME_DARK_SETTING); + } + this.setTheme(themeName); + } + private installConfigurationListener() { this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(COLOR_THEME_SETTING)) { let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => { - if (theme) { - this.setColorTheme(theme.id, undefined); - } - }); + this.setTheme(colorThemeSetting); + this.deinstallColorThemeSwitch(); + } + } + if (e.affectsConfiguration(DETECT_AS_SETTING)) { + let autoSwitchColorTheme = this.configurationService.getValue(DETECT_AS_SETTING); + console.log(autoSwitchColorTheme); + if (this.autoSwitchColorTheme !== autoSwitchColorTheme) { + this.autoSwitchColorTheme = autoSwitchColorTheme; + console.log('HAS CHANGED'); + if (autoSwitchColorTheme) { + this.installColorThemeSwitch(); + let themeName = this.getPreferredTheme(); + this.setTheme(themeName); + } else { + this.deinstallColorThemeSwitch(); + } } } if (e.affectsConfiguration(ICON_THEME_SETTING)) { @@ -330,6 +378,34 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); } + private getPreferredTheme(): string { + let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_LIGHT_SETTING); + const darkMode = this.getPreferredColorScheme() === ColorScheme.DARK; + if (darkMode) { + colorThemeSetting = this.configurationService.getValue(COLOR_THEME_DARK_SETTING); + } + return colorThemeSetting; + } + + public getPreferredColorScheme(): ColorScheme { + const noPreference = window.matchMedia(`(prefers-color-scheme: ${ColorScheme.NO_PREFERENCE})`).matches; + const prefersDark = window.matchMedia(`(prefers-color-scheme: ${ColorScheme.DARK})`).matches; + if (noPreference) { + return ColorScheme.NO_PREFERENCE; + } else if (prefersDark) { + return ColorScheme.DARK; + } + return ColorScheme.LIGHT; + } + + public getColorThemeData(colorThemeSetting: string): Promise { + return new Promise(resolve => { + this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { + resolve(theme); + }); + }); + } + public getColorTheme(): IColorTheme { return this.currentColorTheme; } @@ -342,6 +418,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.getColorTheme(); } + public setTheme(themeName: string) { + this.getColorThemeData(themeName).then(theme => { + if (theme) { + this.setColorTheme(theme.id, undefined); + this.configurationService.updateValue(COLOR_THEME_SETTING, themeName); + } + }); + } + public setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { if (!themeId) { return Promise.resolve(null); @@ -390,11 +475,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { public restoreColorTheme() { let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => { - if (theme) { - this.setColorTheme(theme.id, undefined); - } - }); + this.setTheme(colorThemeSetting); } } @@ -649,6 +730,23 @@ const colorThemeSettingSchema: IConfigurationPropertySchema = { enumDescriptions: [], errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), }; +const colorThemeDarkSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('colorThemeDark', 'Specifies the color theme used for a dark OS appearance.'), + default: DEFAULT_THEME_DARK_SETTING_VALUE, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), +}; +const colorThemeLightSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('colorThemeLight', 'Specifies the color theme used for a light OS appearance.'), + default: DEFAULT_THEME_LIGHT_SETTING_VALUE, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), +}; +const colorThemeAutoSwitchSettingSchema: IConfigurationPropertySchema = { + type: 'boolean', + description: nls.localize('colorThemeAutoSwitch', 'Changes the color theme based on the OS appearance.'), + default: DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE +}; const iconThemeSettingSchema: IConfigurationPropertySchema = { type: ['string', 'null'], @@ -675,6 +773,9 @@ const themeSettingsConfiguration: IConfigurationNode = { type: 'object', properties: { [COLOR_THEME_SETTING]: colorThemeSettingSchema, + [COLOR_THEME_DARK_SETTING]: colorThemeDarkSettingSchema, + [COLOR_THEME_LIGHT_SETTING]: colorThemeLightSettingSchema, + [DETECT_AS_SETTING]: colorThemeAutoSwitchSettingSchema, [ICON_THEME_SETTING]: iconThemeSettingSchema, [CUSTOM_WORKBENCH_COLORS_SETTING]: colorCustomizationsSchema } diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 9fc33ebab82..faa27f7966d 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -18,12 +18,22 @@ export const VS_HC_THEME = 'hc-black'; export const HC_THEME_ID = 'Default High Contrast'; export const COLOR_THEME_SETTING = 'workbench.colorTheme'; +export const COLOR_THEME_DARK_SETTING = 'workbench.colorThemeDark'; +export const COLOR_THEME_LIGHT_SETTING = 'workbench.colorThemeLight'; +export const DETECT_AS_SETTING = 'workbench.colorThemeAutoSwitch'; +export const WINDOW_MATCH_PREFERS_COLOR_SCHEME = '(prefers-color-scheme: dark)'; export const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; export const ICON_THEME_SETTING = 'workbench.iconTheme'; export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations'; export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations'; export const CUSTOM_EDITOR_TOKENSTYLES_SETTING = 'editor.tokenColorCustomizationsExperimental'; +export enum ColorScheme { + LIGHT = 'light', + DARK = 'dark', + NO_PREFERENCE = 'no-preference' +} + export interface IColorTheme extends ITheme { readonly id: string; readonly label: string; @@ -56,6 +66,7 @@ export interface IWorkbenchThemeService extends IThemeService { setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; getColorTheme(): IColorTheme; getColorThemes(): Promise; + getColorThemeData(colorThemeSetting: string): Promise; onDidColorThemeChange: Event; restoreColorTheme(): void; From 7818af296ca80fbb51433c2a177c1b77f2e1cabe Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 13 Dec 2019 09:16:13 +0200 Subject: [PATCH 017/241] Rename 'internalConsoleWasOpened' to 'replWasOpened' --- src/vs/workbench/contrib/debug/browser/debugService.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 53d48bd5ba7..b969c171d68 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -90,7 +90,7 @@ export class DebugService implements IDebugService { private previousState: State | undefined; private initCancellationToken: CancellationTokenSource | undefined; - private internalTerminalWasOpened = false; + private replWasOpened = false; constructor( @IStorageService private readonly storageService: IStorageService, @@ -454,7 +454,7 @@ export class DebugService implements IDebugService { await this.launchOrAttachToSession(session); const internalConsoleOptions = session.configuration.internalConsoleOptions || this.configurationService.getValue('debug').internalConsoleOptions; - this.internalTerminalWasOpened = this.layoutService.isVisible(Parts.PANEL_PART); + this.replWasOpened = this.layoutService.isVisible(Parts.PANEL_PART); if (internalConsoleOptions === 'openOnSessionStart' || (this.viewModel.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) { this.panelService.openPanel(REPL_ID, false); } @@ -570,7 +570,7 @@ export class DebugService implements IDebugService { } const closeConsoleOnEnd = this.configurationService.getValue('debug').console.closeOnEnd; - if (closeConsoleOnEnd === 'always' || (closeConsoleOnEnd === 'whenOpenedByDebug' && !this.internalTerminalWasOpened)) { + if (closeConsoleOnEnd === 'always' || (closeConsoleOnEnd === 'whenOpenedByDebug' && !this.replWasOpened)) { this.panelService.hideActivePanel(); } From db52cd7e6702fd5b01f61ee53d27ae9359ac5bee Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 13 Dec 2019 09:36:18 +0200 Subject: [PATCH 018/241] Close only if the opened panel is the REPL --- src/vs/workbench/contrib/debug/browser/debugService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index b969c171d68..03bd8cea782 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -570,7 +570,8 @@ export class DebugService implements IDebugService { } const closeConsoleOnEnd = this.configurationService.getValue('debug').console.closeOnEnd; - if (closeConsoleOnEnd === 'always' || (closeConsoleOnEnd === 'whenOpenedByDebug' && !this.replWasOpened)) { + if (this.panelService.getLastActivePanelId() === REPL_ID && + (closeConsoleOnEnd === 'always' || (closeConsoleOnEnd === 'whenOpenedByDebug' && !this.replWasOpened))) { this.panelService.hideActivePanel(); } From e09cac9edc31c175b1c05c87bf6205fa11daf84a Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Fri, 13 Dec 2019 09:40:39 +0200 Subject: [PATCH 019/241] Remove trailing newline --- src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts index e4d299a43f5..a362d720738 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts @@ -129,4 +129,4 @@ export async function findExecutable(command: string, cwd?: string, paths?: stri } const fullPath = path.join(cwd, command); return await exists(fullPath) ? fullPath : undefined; -} +} \ No newline at end of file From 2d3c754f716f0aad4f1c97b31cc88495a99f131c Mon Sep 17 00:00:00 2001 From: Mohammad Rahhal Date: Sat, 14 Dec 2019 13:45:22 +0200 Subject: [PATCH 020/241] Fix typos --- .../client/src/mirrorCursor.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/html-language-features/client/src/mirrorCursor.ts b/extensions/html-language-features/client/src/mirrorCursor.ts index 43ea3169c97..fdd0eb813c2 100644 --- a/extensions/html-language-features/client/src/mirrorCursor.ts +++ b/extensions/html-language-features/client/src/mirrorCursor.ts @@ -66,13 +66,13 @@ export function activateMirrorCursor( if (event.selections[0].isEmpty) { matchingTagPositionProvider(event.textEditor.document, event.selections[0].active).then(matchingTagPosition => { if (matchingTagPosition && window.activeTextEditor) { - const charBeforeAndAfterPositionsRoughtlyEqual = isCharBeforeAndAfterPositionsRoughtlyEqual( + const charBeforeAndAfterPositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual( event.textEditor.document, event.selections[0].anchor, new Position(matchingTagPosition.line, matchingTagPosition.character) ); - if (charBeforeAndAfterPositionsRoughtlyEqual) { + if (charBeforeAndAfterPositionsRoughlyEqual) { inMirrorMode = true; const newCursor = new Selection( matchingTagPosition.line, @@ -103,13 +103,13 @@ export function activateMirrorCursor( return; } - const charBeforeAndAfterPositionsRoughtlyEqual = isCharBeforeAndAfterPositionsRoughtlyEqual( + const charBeforeAndAfterPositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual( event.textEditor.document, event.selections[0].anchor, event.selections[1].anchor ); - if (!charBeforeAndAfterPositionsRoughtlyEqual) { + if (!charBeforeAndAfterPositionsRoughlyEqual) { exitMirrorMode(); return; } else { @@ -154,8 +154,8 @@ function getCharAfter(document: TextDocument, position: Position) { } // Check if chars before and after the two positions are equal -// For the chars before, `<` and `/` are consiered equal to handle the case of `<|>` -function isCharBeforeAndAfterPositionsRoughtlyEqual(document: TextDocument, firstPos: Position, secondPos: Position) { +// For the chars before, `<` and `/` are considered equal to handle the case of `<|>` +function isCharBeforeAndAfterPositionsRoughlyEqual(document: TextDocument, firstPos: Position, secondPos: Position) { const charBeforePrimarySelection = getCharBefore(document, firstPos); const charAfterPrimarySelection = getCharAfter(document, firstPos); const charBeforeSecondarySelection = getCharBefore(document, secondPos); From e01bc49c57e0b5c71ee9e5f59090c8d9a1659a36 Mon Sep 17 00:00:00 2001 From: Mohammad Rahhal Date: Sat, 14 Dec 2019 16:26:47 +0200 Subject: [PATCH 021/241] Exit mirror cursor mode when there's a none-empty selection in an unmmatched state --- .../html-language-features/client/src/mirrorCursor.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/extensions/html-language-features/client/src/mirrorCursor.ts b/extensions/html-language-features/client/src/mirrorCursor.ts index fdd0eb813c2..4edf06bdc2b 100644 --- a/extensions/html-language-features/client/src/mirrorCursor.ts +++ b/extensions/html-language-features/client/src/mirrorCursor.ts @@ -128,6 +128,16 @@ export function activateMirrorCursor( workspace.applyEdit(cleanupEdit); } } + } else { + const charBeforeAndAfterPositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual( + event.textEditor.document, + event.selections[0].active, + event.selections[1].active + ); + + if (!charBeforeAndAfterPositionsRoughlyEqual) { + exitMirrorMode(); + } } } } From a5e2508f7cd6ab769522d4a1f9873d355c6089e8 Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sat, 14 Dec 2019 21:34:32 +0200 Subject: [PATCH 022/241] Set replWasOpened only when the REPL panel was opened --- src/vs/workbench/contrib/debug/browser/debugService.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 03bd8cea782..ea53c5eacb9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -90,7 +90,8 @@ export class DebugService implements IDebugService { private previousState: State | undefined; private initCancellationToken: CancellationTokenSource | undefined; - private replWasOpened = false; + // Enable undefined because that makes the test easier. + private replWasOpened : boolean | undefined = false; constructor( @IStorageService private readonly storageService: IStorageService, @@ -454,7 +455,8 @@ export class DebugService implements IDebugService { await this.launchOrAttachToSession(session); const internalConsoleOptions = session.configuration.internalConsoleOptions || this.configurationService.getValue('debug').internalConsoleOptions; - this.replWasOpened = this.layoutService.isVisible(Parts.PANEL_PART); + const activePanel = this.panelService.getActivePanel(); + this.replWasOpened = activePanel && activePanel.getId() === REPL_ID; if (internalConsoleOptions === 'openOnSessionStart' || (this.viewModel.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) { this.panelService.openPanel(REPL_ID, false); } From 103041ea8c068eddc1c9f1744e0a9079aa23b670 Mon Sep 17 00:00:00 2001 From: SPGoding Date: Sun, 15 Dec 2019 17:37:04 -0600 Subject: [PATCH 023/241] Fix wrong key in the extension package.json schema Fix #87041. --- src/vs/workbench/api/common/configurationExtensionPoint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 797cd6d8f9a..6d284f82386 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -57,12 +57,12 @@ const configurationEntrySchema: IJSONSchema = { }, description: nls.localize('scope.enumDescriptions', 'Descriptions for enum values') }, - markdownEnumDescription: { + markdownEnumDescriptions: { type: 'array', items: { type: 'string', }, - description: nls.localize('scope.markdownEnumDescription', 'Descriptions for enum values in the markdown format.') + description: nls.localize('scope.markdownEnumDescriptions', 'Descriptions for enum values in the markdown format.') }, markdownDescription: { type: 'string', From f316e456deaf0a78d13c722f5d3bd31e092b0932 Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Sun, 15 Dec 2019 23:03:23 -0500 Subject: [PATCH 024/241] Fixes #87044 --- .../files/browser/views/explorerView.ts | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index bd1e27a2150..a4730005eb7 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -83,7 +83,6 @@ export class ExplorerView extends ViewPane { private compressedFocusContext: IContextKey; private compressedFocusFirstContext: IContextKey; private compressedFocusLastContext: IContextKey; - private compressedNavigationController: ICompressedNavigationController | undefined; // Refresh is needed on the initial explorer open private shouldRefresh = true; @@ -286,18 +285,17 @@ export class ExplorerView extends ViewPane { getContext(respectMultiSelection: boolean): ExplorerItem[] { let focusedStat: ExplorerItem | undefined; - if (this.compressedNavigationController) { - focusedStat = this.compressedNavigationController.current; - } else { - const focus = this.tree.getFocus(); - focusedStat = focus.length ? focus[0] : undefined; - } + const focus = this.tree.getFocus(); + focusedStat = focus.length ? focus[0] : undefined; + + const compressedNavigationController = focusedStat && this.renderer.getCompressedNavigationController(focusedStat); + focusedStat = compressedNavigationController ? compressedNavigationController.current : focusedStat; const selectedStats: ExplorerItem[] = []; for (const stat of this.tree.getSelection()) { const controller = this.renderer.getCompressedNavigationController(stat); - if (controller && focusedStat && controller === this.compressedNavigationController) { + if (controller && focusedStat && controller === compressedNavigationController) { if (stat === focusedStat) { selectedStats.push(stat); } @@ -532,16 +530,15 @@ export class ExplorerView extends ViewPane { this.resourceMoveableToTrash.reset(); } - this.compressedNavigationController = stat && this.renderer.getCompressedNavigationController(stat); + const compressedNavigationController = stat && this.renderer.getCompressedNavigationController(stat); - if (!this.compressedNavigationController) { + if (!compressedNavigationController) { this.compressedFocusContext.set(false); return; } this.compressedFocusContext.set(true); - // this.compressedNavigationController.last(); - this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + this.updateCompressedNavigationContextKeys(compressedNavigationController); } // General methods @@ -691,39 +688,47 @@ export class ExplorerView extends ViewPane { } previousCompressedStat(): void { - if (!this.compressedNavigationController) { + const focused = this.tree.getFocus(); + if (!focused.length) { return; } - this.compressedNavigationController.previous(); - this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + const compressedNavigationController = this.renderer.getCompressedNavigationController(focused[0])!; + compressedNavigationController.previous(); + this.updateCompressedNavigationContextKeys(compressedNavigationController); } nextCompressedStat(): void { - if (!this.compressedNavigationController) { + const focused = this.tree.getFocus(); + if (!focused.length) { return; } - this.compressedNavigationController.next(); - this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + const compressedNavigationController = this.renderer.getCompressedNavigationController(focused[0])!; + compressedNavigationController.next(); + this.updateCompressedNavigationContextKeys(compressedNavigationController); } firstCompressedStat(): void { - if (!this.compressedNavigationController) { + const focused = this.tree.getFocus(); + if (!focused.length) { return; } - this.compressedNavigationController.first(); - this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + const compressedNavigationController = this.renderer.getCompressedNavigationController(focused[0])!; + compressedNavigationController.first(); + this.updateCompressedNavigationContextKeys(compressedNavigationController); } lastCompressedStat(): void { - if (!this.compressedNavigationController) { + const focused = this.tree.getFocus(); + if (!focused.length) { return; } - this.compressedNavigationController.last(); - this.updateCompressedNavigationContextKeys(this.compressedNavigationController); + const compressedNavigationController = this.renderer.getCompressedNavigationController(focused[0])!; + compressedNavigationController.last(); + this.updateCompressedNavigationContextKeys(compressedNavigationController); } private updateCompressedNavigationContextKeys(controller: ICompressedNavigationController): void { From 1762f5a049462ba789aa4f89d99ed8028c76aa73 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Dec 2019 13:31:48 +0100 Subject: [PATCH 025/241] enable api to read and write lang configs --- src/vs/vscode.d.ts | 8 ++- .../api/browser/mainThreadConfiguration.ts | 63 +++++++++++++++---- .../workbench/api/common/extHost.api.impl.ts | 6 +- .../workbench/api/common/extHost.protocol.ts | 6 +- .../api/common/extHostConfiguration.ts | 25 ++++---- .../api/mainThreadConfiguration.test.ts | 22 +++---- 6 files changed, 85 insertions(+), 45 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 90b446a8075..0ff59e4f98f 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -4237,6 +4237,8 @@ declare module 'vscode' { WorkspaceFolder = 3 } + export type ConfigurationScope = Uri | { resource: Uri, language?: string }; + /** * Represents the configuration. It is a merged view of * @@ -8683,13 +8685,13 @@ declare module 'vscode' { * is returned. Dots in the section-identifier are interpreted as child-access, * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. * - * When a resource is provided, configuration scoped to that resource is returned. + * When a scope is provided, configuration confined to that scope is returned. * * @param section A dot-separated identifier. - * @param resource A resource for which the configuration is asked for + * @param scope A scope for which the configuration is asked for. * @return The full configuration or a subset. */ - export function getConfiguration(section?: string, resource?: Uri | null): WorkspaceConfiguration; + export function getConfiguration(section?: string, scope?: ConfigurationScope | null): WorkspaceConfiguration; /** * An event that is emitted when the [configuration](#WorkspaceConfiguration) changed. diff --git a/src/vs/workbench/api/browser/mainThreadConfiguration.ts b/src/vs/workbench/api/browser/mainThreadConfiguration.ts index 2871b8d4ca7..e86e7c09263 100644 --- a/src/vs/workbench/api/browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/browser/mainThreadConfiguration.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, getScopes } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext, IConfigurationInitData } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService, IConfigurationOverrides, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @extHostNamedCustomer(MainContext.MainThreadConfiguration) @@ -45,23 +45,60 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { this._configurationListener.dispose(); } - $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, resourceUriComponenets: UriComponents | undefined): Promise { - const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null; - return this.writeConfiguration(target, key, value, resource); + $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined): Promise { + overrides = { resource: overrides?.resource ? URI.revive(overrides.resource) : undefined, overrideIdentifier: overrides?.overrideIdentifier }; + return this.writeConfiguration(target, key, value, overrides); } - $removeConfigurationOption(target: ConfigurationTarget | null, key: string, resourceUriComponenets: UriComponents | undefined): Promise { - const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null; - return this.writeConfiguration(target, key, undefined, resource); + $removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined): Promise { + overrides = { resource: overrides?.resource ? URI.revive(overrides.resource) : undefined, overrideIdentifier: overrides?.overrideIdentifier }; + return this.writeConfiguration(target, key, undefined, overrides); } - private writeConfiguration(target: ConfigurationTarget | null, key: string, value: any, resource: URI | null): Promise { - target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, resource); - return this.configurationService.updateValue(key, value, { resource }, target, true); + private writeConfiguration(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides): Promise { + const configurationValue = this.configurationService.inspect(key, overrides); + target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, configurationValue, overrides); + switch (target) { + case ConfigurationTarget.MEMORY: + return this._updateValue(key, value, target, configurationValue.memory?.override, overrides); + case ConfigurationTarget.WORKSPACE_FOLDER: + return this._updateValue(key, value, target, configurationValue.workspaceFolder?.override, overrides); + case ConfigurationTarget.WORKSPACE: + return this._updateValue(key, value, target, configurationValue.workspace?.override, overrides); + case ConfigurationTarget.USER_REMOTE: + return this._updateValue(key, value, target, configurationValue.userRemote?.override, overrides); + default: + return this._updateValue(key, value, target, configurationValue.userLocal?.override, overrides); + } } - private deriveConfigurationTarget(key: string, resource: URI | null): ConfigurationTarget { - if (resource && this._workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { + private _updateValue(key: string, value: any, configurationTarget: ConfigurationTarget, overriddenValue: any | undefined, overrides: IConfigurationOverrides): Promise { + if (overrides.overrideIdentifier && overriddenValue !== undefined) { + return this.configurationService.updateValue(key, value, overrides, configurationTarget); + } else { + return this.configurationService.updateValue(key, value, { resource: overrides.resource }, configurationTarget); + } + } + + private deriveConfigurationTarget(key: string, configurationValue: IConfigurationValue, overrides: IConfigurationOverrides): ConfigurationTarget { + if (overrides.overrideIdentifier) { + if (configurationValue.memory?.override !== undefined) { + return ConfigurationTarget.MEMORY; + } + if (configurationValue.workspaceFolder?.override !== undefined) { + return ConfigurationTarget.WORKSPACE_FOLDER; + } + if (configurationValue.workspace?.override !== undefined) { + return ConfigurationTarget.WORKSPACE; + } + if (configurationValue.userRemote?.override !== undefined) { + return ConfigurationTarget.USER_REMOTE; + } + if (configurationValue.userLocal?.override !== undefined) { + return ConfigurationTarget.USER_LOCAL; + } + } + if (overrides.resource && this._workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (configurationProperties[key] && (configurationProperties[key].scope === ConfigurationScope.RESOURCE || configurationProperties[key].scope === ConfigurationScope.RESOURCE_LANGUAGE)) { return ConfigurationTarget.WORKSPACE_FOLDER; diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 69fee57eace..82a186dd882 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -664,9 +664,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onDidChangeConfiguration: (listener: (_: any) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) => { return configProvider.onDidChangeConfiguration(listener, thisArgs, disposables); }, - getConfiguration(section?: string, resource?: vscode.Uri): vscode.WorkspaceConfiguration { - resource = arguments.length === 1 ? undefined : resource; - return configProvider.getConfiguration(section, resource, extension.identifier); + getConfiguration(section?: string, scope?: vscode.ConfigurationScope): vscode.WorkspaceConfiguration { + scope = arguments.length === 1 ? undefined : scope; + return configProvider.getConfiguration(section, scope, extension.identifier); }, registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider) { return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b79e49ac355..e7089d6daac 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -22,7 +22,7 @@ import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; import * as modes from 'vs/editor/common/modes'; import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/modes/languageConfiguration'; import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { ConfigurationTarget, IConfigurationData, IConfigurationChange } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationData, IConfigurationChange, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as files from 'vs/platform/files/common/files'; @@ -147,8 +147,8 @@ export interface MainThreadCommentsShape extends IDisposable { } export interface MainThreadConfigurationShape extends IDisposable { - $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, resource: UriComponents | undefined): Promise; - $removeConfigurationOption(target: ConfigurationTarget | null, key: string, resource: UriComponents | undefined): Promise; + $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined): Promise; + $removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined): Promise; } export interface MainThreadDiagnosticsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 7fe37f3fc97..414b3f8a38d 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -10,7 +10,7 @@ import * as vscode from 'vscode'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtHostConfigurationShape, MainThreadConfigurationShape, IConfigurationInitData, MainContext } from './extHost.protocol'; import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes'; -import { ConfigurationTarget, IConfigurationChange, IConfigurationData } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; import { isObject } from 'vs/base/common/types'; @@ -104,13 +104,14 @@ export class ExtHostConfigProvider { this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous)); } - getConfiguration(section?: string, resource?: URI, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { + getConfiguration(section?: string, scope?: vscode.ConfigurationScope, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { + const overrides: IConfigurationOverrides = scope ? scope instanceof vscode.Uri ? { resource: scope } : { resource: scope.resource, overrideIdentifier: scope.language } : {}; const config = this._toReadonlyValue(section - ? lookUp(this._configuration.getValue(undefined, { resource }, this._extHostWorkspace.workspace), section) - : this._configuration.getValue(undefined, { resource }, this._extHostWorkspace.workspace)); + ? lookUp(this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace), section) + : this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace)); if (section) { - this._validateConfigurationAccess(section, resource, extensionId); + this._validateConfigurationAccess(section, overrides, extensionId); } function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget | null { @@ -133,7 +134,7 @@ export class ExtHostConfigProvider { return typeof lookUp(config, key) !== 'undefined'; }, get: (key: string, defaultValue?: T) => { - this._validateConfigurationAccess(section ? `${section}.${key}` : key, resource, extensionId); + this._validateConfigurationAccess(section ? `${section}.${key}` : key, overrides, extensionId); let result = lookUp(config, key); if (typeof result === 'undefined') { result = defaultValue; @@ -193,14 +194,14 @@ export class ExtHostConfigProvider { key = section ? `${section}.${key}` : key; const target = parseConfigurationTarget(arg); if (value !== undefined) { - return this._proxy.$updateConfigurationOption(target, key, value, resource); + return this._proxy.$updateConfigurationOption(target, key, value, overrides); } else { - return this._proxy.$removeConfigurationOption(target, key, resource); + return this._proxy.$removeConfigurationOption(target, key, overrides); } }, inspect: (key: string): ConfigurationInspect | undefined => { key = section ? `${section}.${key}` : key; - const config = deepClone(this._configuration.inspect(key, { resource }, this._extHostWorkspace.workspace)); + const config = deepClone(this._configuration.inspect(key, overrides, this._extHostWorkspace.workspace)); if (config) { return { key, @@ -237,17 +238,17 @@ export class ExtHostConfigProvider { return readonlyProxy(result); } - private _validateConfigurationAccess(key: string, resource: URI | undefined, extensionId?: ExtensionIdentifier): void { + private _validateConfigurationAccess(key: string, overrides?: IConfigurationOverrides, extensionId?: ExtensionIdentifier): void { const scope = OVERRIDE_PROPERTY_PATTERN.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes.get(key); const extensionIdText = extensionId ? `[${extensionId.value}] ` : ''; if (ConfigurationScope.RESOURCE === scope) { - if (resource === undefined) { + if (overrides?.resource) { this._logService.warn(`${extensionIdText}Accessing a resource scoped configuration without providing a resource is not expected. To get the effective value for '${key}', provide the URI of a resource or 'null' for any resource.`); } return; } if (ConfigurationScope.WINDOW === scope) { - if (resource) { + if (overrides?.resource) { this._logService.warn(`${extensionIdText}Accessing a window scoped configuration for a resource is not expected. To associate '${key}' to a resource, define its scope to 'resource' in configuration contributions in 'package.json'.`); } return; diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts index c1132e64713..db3c46a30a4 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts @@ -72,7 +72,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', { resource: URI.file('abc') }); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -99,7 +99,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -108,7 +108,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -126,7 +126,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', { resource: URI.file('abc') }); assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]); }); @@ -135,7 +135,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(ConfigurationTarget.USER, 'extHostConfiguration.window', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(ConfigurationTarget.USER, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }); assert.equal(ConfigurationTarget.USER, target.args[0][3]); }); @@ -144,7 +144,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE, 'extHostConfiguration.window', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -153,7 +153,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE_FOLDER, 'extHostConfiguration.window', 'value', URI.file('abc')); + testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE_FOLDER, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }); assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]); }); @@ -171,7 +171,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', URI.file('abc')); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', { resource: URI.file('abc') }); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -198,7 +198,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', URI.file('abc')); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', { resource: URI.file('abc') }); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -207,7 +207,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', URI.file('abc')); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', { resource: URI.file('abc') }); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -225,7 +225,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', URI.file('abc')); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', { resource: URI.file('abc') }); assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]); }); From 624c9c20add9e94b552bb1aaf78a73179ecc88c2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Dec 2019 16:33:13 +0100 Subject: [PATCH 026/241] separate workspace folder and text document configuration --- src/vs/vscode.d.ts | 55 ++++++++++++++++++- .../api/common/extHostConfiguration.ts | 22 +++++++- 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 0ff59e4f98f..4146d2e8cde 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -4237,8 +4237,6 @@ declare module 'vscode' { WorkspaceFolder = 3 } - export type ConfigurationScope = Uri | { resource: Uri, language?: string }; - /** * Represents the configuration. It is a merged view of * @@ -4355,6 +4353,31 @@ declare module 'vscode' { readonly [key: string]: any; } + export interface WorkspaceFolderConfiguration extends WorkspaceConfiguration { + + update(section: string, value: any): Thenable; + } + + export interface TextDocumentConfiguration extends WorkspaceConfiguration { + + inspect(section: string): { + key: string; + + defaultValue?: T; + globalValue?: T; + workspaceValue?: T, + workspaceFolderValue?: T, + + defaultLanguageValue?: T; + userLanguageValue?: T; + workspaceLanguageValue?: T; + workspaceFolderLanguageValue?: T; + + } | undefined; + + update(section: string, value: any): Thenable; + } + /** * Represents a location inside a resource, such as a line * inside a text file. @@ -8691,7 +8714,33 @@ declare module 'vscode' { * @param scope A scope for which the configuration is asked for. * @return The full configuration or a subset. */ - export function getConfiguration(section?: string, scope?: ConfigurationScope | null): WorkspaceConfiguration; + export function getConfiguration(section?: string, resource?: Uri | null): WorkspaceConfiguration; + + /** + * Get a workspace folder configuration object for the given workspace folder. + * + * When a section-identifier is provided only that part of the configuration + * is returned. Dots in the section-identifier are interpreted as child-access, + * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. + * + * @param section A dot-separated identifier. + * @param workspaceFolder A workspace folder for which the configuration is asked for. + * @return The full configuration or a subset. + */ + export function getConfiguration(section: string | undefined, workspaceFolder: WorkspaceFolder): WorkspaceFolderConfiguration; + + /** + * Get a text document configuration object for the given text document. + * + * When a section-identifier is provided only that part of the configuration + * is returned. Dots in the section-identifier are interpreted as child-access, + * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. + * + * @param section A dot-separated identifier. + * @param textDocument A text document for which the configuration is asked for. + * @return The full configuration or a subset for the given text document + */ + export function getConfiguration(section: string | undefined, textDocument: TextDocument): TextDocumentConfiguration; /** * An event that is emitted when the [configuration](#WorkspaceConfiguration) changed. diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 414b3f8a38d..68f4a614f97 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -40,6 +40,19 @@ type ConfigurationInspect = { workspaceFolderValue?: T; }; +function isTextDocument(thing: any): thing is vscode.TextDocument { + return thing + && thing.uri instanceof vscode.Uri + && (!thing.languageId || typeof thing.languageId === 'string'); +} + +function isWorkspaceFolder(thing: any): thing is vscode.WorkspaceFolder { + return thing + && thing.uri instanceof vscode.Uri + && (!thing.name || typeof thing.name === 'string') + && (!thing.index || typeof thing.index === 'number'); +} + export class ExtHostConfiguration implements ExtHostConfigurationShape { readonly _serviceBrand: undefined; @@ -104,8 +117,13 @@ export class ExtHostConfigProvider { this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous)); } - getConfiguration(section?: string, scope?: vscode.ConfigurationScope, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { - const overrides: IConfigurationOverrides = scope ? scope instanceof vscode.Uri ? { resource: scope } : { resource: scope.resource, overrideIdentifier: scope.language } : {}; + getConfiguration(section: string | undefined, scope: vscode.Uri | vscode.WorkspaceFolder | vscode.TextDocument | null | undefined, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { + const overrides: IConfigurationOverrides = scope ? + scope instanceof vscode.Uri ? { resource: scope } + : isWorkspaceFolder(scope) ? { resource: scope.uri } + : isTextDocument(scope) ? { resource: scope.uri, overrideIdentifier: scope.languageId } + : scope + : {}; const config = this._toReadonlyValue(section ? lookUp(this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace), section) : this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace)); From e25b9c9f0b552d8c179ade042623c8f7f72c4bf2 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 16 Dec 2019 16:36:06 +0100 Subject: [PATCH 027/241] allow prepareCallHierarchy to also return an array --- src/vs/vscode.d.ts | 2 +- .../api/browser/mainThreadLanguageFeatures.ts | 12 ++++++++---- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- .../api/common/extHostLanguageFeatures.ts | 17 +++++++++++------ .../callHierarchy/browser/callHierarchy.ts | 14 +++++++++----- .../callHierarchy/browser/callHierarchyTree.ts | 2 +- 6 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 90b446a8075..63534a1cefc 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -4015,7 +4015,7 @@ declare module 'vscode' { * @returns A call hierarchy item or a thenable that resolves to such. The lack of a result can be * signaled by returning `undefined` or `null`. */ - prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; /** * Provide all incoming calls for an item, e.g all callers for a method. In graph terms this describes directed diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 05447ed8a4b..0ae9f987450 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -505,13 +505,17 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha this._registrations.set(handle, callh.CallHierarchyProviderRegistry.register(selector, { prepareCallHierarchy: async (document, position, token) => { - const item = await this._proxy.$prepareCallHierarchy(handle, document.uri, position, token); - if (!item) { + const items = await this._proxy.$prepareCallHierarchy(handle, document.uri, position, token); + if (!items) { return undefined; } return { - dispose: () => this._proxy.$releaseCallHierarchy(handle, item._sessionId), - root: MainThreadLanguageFeatures._reviveCallHierarchyItemDto(item) + dispose: () => { + for (const item of items) { + this._proxy.$releaseCallHierarchy(handle, item._sessionId); + } + }, + roots: items.map(MainThreadLanguageFeatures._reviveCallHierarchyItemDto) }; }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b79e49ac355..5bb0383ff42 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1188,7 +1188,7 @@ export interface ExtHostLanguageFeaturesShape { $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise; $provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise; $provideSelectionRanges(handle: number, resource: UriComponents, positions: IPosition[], token: CancellationToken): Promise; - $prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideCallHierarchyIncomingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $provideCallHierarchyOutgoingCalls(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise; $releaseCallHierarchy(handle: number, sessionId: string): void; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index a9482c1616c..ba0f638fefd 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1200,18 +1200,23 @@ class CallHierarchyAdapter { private readonly _provider: vscode.CallHierarchyProvider ) { } - async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise { + async prepareSession(uri: URI, position: IPosition, token: CancellationToken): Promise { const doc = this._documents.getDocument(uri); const pos = typeConvert.Position.to(position); - const item = await this._provider.prepareCallHierarchy(doc, pos, token); - if (!item) { + const items = await this._provider.prepareCallHierarchy(doc, pos, token); + if (!items) { return undefined; } - const sessionId = this._idPool.nextId(); + const sessionId = this._idPool.nextId(); this._cache.set(sessionId, new Map()); - return this._cacheAndConvertItem(sessionId, item); + + if (Array.isArray(items)) { + return items.map(item => this._cacheAndConvertItem(sessionId, item)); + } else { + return [this._cacheAndConvertItem(sessionId, items)]; + } } async provideCallsTo(sessionId: string, itemId: string, token: CancellationToken): Promise { @@ -1727,7 +1732,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._createDisposable(handle); } - $prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + $prepareCallHierarchy(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { return this._withAdapter(handle, CallHierarchyAdapter, adapter => Promise.resolve(adapter.prepareSession(URI.revive(resource), position, token)), undefined); } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts index ab12627fb50..c82d0a7739d 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.ts @@ -45,7 +45,7 @@ export interface OutgoingCall { } export interface CallHierarchySession { - root: CallHierarchyItem; + roots: CallHierarchyItem[]; dispose(): void; } @@ -92,15 +92,19 @@ export class CallHierarchyModel { if (!session) { return undefined; } - return new CallHierarchyModel(session.root._sessionId, provider, session.root, new RefCountedDisposabled(session)); + return new CallHierarchyModel(session.roots.reduce((p, c) => p + c._sessionId, ''), provider, session.roots, new RefCountedDisposabled(session)); } + readonly root: CallHierarchyItem; + private constructor( readonly id: string, readonly provider: CallHierarchyProvider, - readonly root: CallHierarchyItem, + readonly roots: CallHierarchyItem[], readonly ref: RefCountedDisposabled, - ) { } + ) { + this.root = roots[0]; + } dispose(): void { this.ref.release(); @@ -110,7 +114,7 @@ export class CallHierarchyModel { const that = this; return new class extends CallHierarchyModel { constructor() { - super(that.id, that.provider, item, that.ref.acquire()); + super(that.id, that.provider, [item], that.ref.acquire()); } }; } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index c412f7b992a..2e9cec145d3 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -43,7 +43,7 @@ export class DataSource implements IAsyncDataSource { async getChildren(element: CallHierarchyModel | Call): Promise { if (element instanceof CallHierarchyModel) { - return [new Call(element.root, undefined, element, undefined)]; + return element.roots.map(root => new Call(root, undefined, element, undefined)); } const { model, item } = element; From b489bc1cb0360a6892115ba4b87daadc1943f6f3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Dec 2019 16:37:21 +0100 Subject: [PATCH 028/241] deprecate using resource --- src/vs/vscode.d.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 4146d2e8cde..585191c23a0 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8702,6 +8702,20 @@ declare module 'vscode' { export const onDidRenameFiles: Event; /** + * Get a workspace configuration object. + * + * When a section-identifier is provided only that part of the configuration + * is returned. Dots in the section-identifier are interpreted as child-access, + * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. + * + * @param section A dot-separated identifier. + * @return The full configuration or a subset. + */ + export function getConfiguration(section?: string): WorkspaceConfiguration; + + /** + * + * * Get a workspace configuration object. * * When a section-identifier is provided only that part of the configuration @@ -8710,11 +8724,12 @@ declare module 'vscode' { * * When a scope is provided, configuration confined to that scope is returned. * + * @deprecated Pass [workspaceFolder](#WorkspaceFolder) instead of [resource](#Uri) * @param section A dot-separated identifier. * @param scope A scope for which the configuration is asked for. * @return The full configuration or a subset. */ - export function getConfiguration(section?: string, resource?: Uri | null): WorkspaceConfiguration; + export function getConfiguration(section: string | undefined, resource: Uri | null): WorkspaceConfiguration; /** * Get a workspace folder configuration object for the given workspace folder. From 77c83114d7f51b0f723f110b87468a495071e352 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Dec 2019 16:49:03 +0100 Subject: [PATCH 029/241] one more version to read configuration --- src/vs/vscode.d.ts | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 585191c23a0..537912236bc 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8712,24 +8712,7 @@ declare module 'vscode' { * @return The full configuration or a subset. */ export function getConfiguration(section?: string): WorkspaceConfiguration; - - /** - * - * - * Get a workspace configuration object. - * - * When a section-identifier is provided only that part of the configuration - * is returned. Dots in the section-identifier are interpreted as child-access, - * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. - * - * When a scope is provided, configuration confined to that scope is returned. - * - * @deprecated Pass [workspaceFolder](#WorkspaceFolder) instead of [resource](#Uri) - * @param section A dot-separated identifier. - * @param scope A scope for which the configuration is asked for. - * @return The full configuration or a subset. - */ - export function getConfiguration(section: string | undefined, resource: Uri | null): WorkspaceConfiguration; + export function getConfiguration(section: string | undefined, scope: { resource: Uri, language?: string }): WorkspaceConfiguration; /** * Get a workspace folder configuration object for the given workspace folder. @@ -8757,6 +8740,24 @@ declare module 'vscode' { */ export function getConfiguration(section: string | undefined, textDocument: TextDocument): TextDocumentConfiguration; + /** + * Get a workspace configuration object for the given scope. + * + * When a section-identifier is provided only that part of the configuration + * is returned. Dots in the section-identifier are interpreted as child-access, + * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. + * + * @param section A dot-separated identifier. + * @param scope A scope for which the configuration is confied to. + * @return The full configuration or a subset. + */ + export function getConfiguration(section: string | undefined, scope: { resource: Uri, language?: string }): WorkspaceConfiguration; + + /** + * @deprecated Pass [workspaceFolder](#WorkspaceFolder) instead of [resource](#Uri) + */ + export function getConfiguration(section: string | undefined, resource: Uri | null): WorkspaceConfiguration; + /** * An event that is emitted when the [configuration](#WorkspaceConfiguration) changed. */ From 2d9d8dc144c6b274e27fd2e2f909be40e28c64c3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Dec 2019 16:57:09 +0100 Subject: [PATCH 030/241] apiExample --- .../api/common/extHostConfiguration.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 68f4a614f97..8091f52196e 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -284,6 +284,42 @@ export class ExtHostConfigProvider { return scopes.reduce((result, scope) => { result.set(scope[0], scope[1]); return result; }, new Map()); } + apiExample() { + /** + * Most often scenario: Read window configuration effectively + */ + const windowConfiguration = vscode.workspace.getConfiguration('window'); + windowConfiguration.get('size'); + windowConfiguration.update('size', 2); // Updates in workspace + + /** + * Most often scenario: Read and Write workspace folder configuration effectively + */ + const workspaceFolderConfiguration = vscode.workspace.getConfiguration('git', vscode.workspace.workspaceFolders![0]); + workspaceFolderConfiguration.get('enabled'); + workspaceFolderConfiguration.update('enabled', false); // Effective update + + /** + * Most often scenario: Read and Write language configuration effectively + */ + const textDocumentConfiguration = vscode.workspace.getConfiguration('editor', vscode.window.activeTextEditor?.document!); + textDocumentConfiguration.get('formatOnSave'); + textDocumentConfiguration.update('formatOnSave', false); //Effective update. + + /** + * Special scenario: Write a configuration under a specific language + */ + const configuration = vscode.workspace.getConfiguration('editor', { resource: vscode.Uri.file('abc'), language: 'javascript' }); + configuration.update('formatOnSave', false); // Writes under languge overrides + + /** + * deprecated + */ + vscode.workspace.getConfiguration('editor', vscode.Uri.file('abc')); + vscode.workspace.getConfiguration('editor', null); + + } + } export const IExtHostConfiguration = createDecorator('IExtHostConfiguration'); From 73928a98565bdab777ba0bf22ee00849c1fb3c9a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Dec 2019 17:03:44 +0100 Subject: [PATCH 031/241] :lipstick: --- src/vs/vscode.d.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 537912236bc..7eb42594fc9 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8712,7 +8712,6 @@ declare module 'vscode' { * @return The full configuration or a subset. */ export function getConfiguration(section?: string): WorkspaceConfiguration; - export function getConfiguration(section: string | undefined, scope: { resource: Uri, language?: string }): WorkspaceConfiguration; /** * Get a workspace folder configuration object for the given workspace folder. @@ -8754,7 +8753,7 @@ declare module 'vscode' { export function getConfiguration(section: string | undefined, scope: { resource: Uri, language?: string }): WorkspaceConfiguration; /** - * @deprecated Pass [workspaceFolder](#WorkspaceFolder) instead of [resource](#Uri) + * @deprecated Instead use other variants of `getConfiguration` */ export function getConfiguration(section: string | undefined, resource: Uri | null): WorkspaceConfiguration; From c01fc29b8d5b794f7007e4c8409cbda60c12506e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 16 Dec 2019 17:23:13 +0100 Subject: [PATCH 032/241] remove redundant api --- src/vs/vscode.d.ts | 13 ------------- src/vs/workbench/api/common/extHost.api.impl.ts | 2 +- src/vs/workbench/api/common/extHostConfiguration.ts | 8 +++++--- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 7eb42594fc9..aa0dd703cc7 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8739,19 +8739,6 @@ declare module 'vscode' { */ export function getConfiguration(section: string | undefined, textDocument: TextDocument): TextDocumentConfiguration; - /** - * Get a workspace configuration object for the given scope. - * - * When a section-identifier is provided only that part of the configuration - * is returned. Dots in the section-identifier are interpreted as child-access, - * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. - * - * @param section A dot-separated identifier. - * @param scope A scope for which the configuration is confied to. - * @return The full configuration or a subset. - */ - export function getConfiguration(section: string | undefined, scope: { resource: Uri, language?: string }): WorkspaceConfiguration; - /** * @deprecated Instead use other variants of `getConfiguration` */ diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 82a186dd882..f8724e75305 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -664,7 +664,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onDidChangeConfiguration: (listener: (_: any) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) => { return configProvider.onDidChangeConfiguration(listener, thisArgs, disposables); }, - getConfiguration(section?: string, scope?: vscode.ConfigurationScope): vscode.WorkspaceConfiguration { + getConfiguration(section?: string, scope?: vscode.Uri | vscode.WorkspaceFolder | vscode.TextDocument | null): vscode.WorkspaceConfiguration { scope = arguments.length === 1 ? undefined : scope; return configProvider.getConfiguration(section, scope, extension.identifier); }, diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 8091f52196e..055d72584fa 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -117,7 +117,7 @@ export class ExtHostConfigProvider { this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous)); } - getConfiguration(section: string | undefined, scope: vscode.Uri | vscode.WorkspaceFolder | vscode.TextDocument | null | undefined, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { + getConfiguration(section?: string, scope?: vscode.Uri | vscode.WorkspaceFolder | vscode.TextDocument | null, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { const overrides: IConfigurationOverrides = scope ? scope instanceof vscode.Uri ? { resource: scope } : isWorkspaceFolder(scope) ? { resource: scope.uri } @@ -309,8 +309,10 @@ export class ExtHostConfigProvider { /** * Special scenario: Write a configuration under a specific language */ - const configuration = vscode.workspace.getConfiguration('editor', { resource: vscode.Uri.file('abc'), language: 'javascript' }); - configuration.update('formatOnSave', false); // Writes under languge overrides + const languageConfiguration = vscode.workspace.getConfiguration(); + const value = languageConfiguration.get<{ 'editor.formatOnSave': boolean }>('[javascript]')!; + value['editor.formatOnSave'] = false; + languageConfiguration.update('[javascript]', value); /** * deprecated From 2eccf30de6edc59b275f1acfb7eb2037c83a10c7 Mon Sep 17 00:00:00 2001 From: Remy Suen Date: Mon, 16 Dec 2019 16:41:29 -0500 Subject: [PATCH 033/241] Add missing documentation to CompletionItem's insertText If we look at the code in vscode.d.ts, we can see that this modes.ts file is not up-to-date. As a result, monaco.d.ts also needs an update. Signed-off-by: Remy Suen --- src/vs/editor/common/modes.ts | 2 +- src/vs/monaco.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 1b0a5dd0fea..9db95948d24 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -436,7 +436,7 @@ export interface CompletionItem { preselect?: boolean; /** * A string or snippet that should be inserted in a document when selecting - * this completion. + * this completion. When `falsy` the [label](#CompletionItem.label) * is used. */ insertText: string; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 1f40e30e414..58e9b0fa73d 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4752,7 +4752,7 @@ declare namespace monaco.languages { preselect?: boolean; /** * A string or snippet that should be inserted in a document when selecting - * this completion. + * this completion. When `falsy` the [label](#CompletionItem.label) * is used. */ insertText: string; From e2dbfd4b974487474f65ab54189531f97d8281e2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Dec 2019 07:33:34 +0100 Subject: [PATCH 034/241] electron - remove backgroundThrottling to fix blank windows --- src/vs/code/electron-main/window.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index b0a839d5b9d..b7e3cff475c 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -130,11 +130,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { show: !isFullscreenOrMaximized, title: product.nameLong, webPreferences: { - // By default if Code is in the background, intervals and timeouts get throttled, so we - // want to enforce that Code stays in the foreground. This triggers a disable_hidden_ - // flag that Electron provides via patch: - // https://github.com/electron/libchromiumcontent/blob/master/patches/common/chromium/disable_hidden.patch - backgroundThrottling: false, nodeIntegration: true, nodeIntegrationInWorker: RUN_TEXTMATE_IN_WORKER, webviewTag: true From ed020953b304b2fbe85150e1721c0e8abdd95c84 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Dec 2019 07:53:17 +0100 Subject: [PATCH 035/241] window - more validation of bounds --- src/vs/code/electron-main/window.ts | 40 +++++++++++++++++++---------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index b7e3cff475c..d0ea8752aae 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -800,15 +800,24 @@ export class CodeWindow extends Disposable implements ICodeWindow { if (displayWorkingArea) { this.logService.trace('window#validateWindowState: 1 monitor working area', displayWorkingArea); - if (state.x < displayWorkingArea.x) { - // prevent window from falling out of the screen to the left - state.x = displayWorkingArea.x; + function ensureStateInDisplayWorkingArea(): void { + if (!state || typeof state.x !== 'number' || typeof state.y !== 'number' || !displayWorkingArea) { + return; + } + + if (state.x < displayWorkingArea.x) { + // prevent window from falling out of the screen to the left + state.x = displayWorkingArea.x; + } + + if (state.y < displayWorkingArea.y) { + // prevent window from falling out of the screen to the top + state.y = displayWorkingArea.y; + } } - if (state.y < displayWorkingArea.y) { - // prevent window from falling out of the screen to the top - state.y = displayWorkingArea.y; - } + // ensure state is not outside display working area (top, left) + ensureStateInDisplayWorkingArea(); if (state.width > displayWorkingArea.width) { // prevent window from exceeding display bounds width @@ -833,6 +842,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { // the screen state.y = displayWorkingArea.y + displayWorkingArea.height - state.height; } + + // again ensure state is not outside display working area + // (it may have changed from the previous validation step) + ensureStateInDisplayWorkingArea(); } return state; @@ -852,17 +865,16 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - // Multi Monitor (non-fullscreen): be less strict because metrics can be crazy - const bounds = { x: state.x, y: state.y, width: state.width, height: state.height }; - const display = screen.getDisplayMatching(bounds); + // Multi Monitor (non-fullscreen): ensure window is within display bounds + const display = screen.getDisplayMatching({ x: state.x, y: state.y, width: state.width, height: state.height }); const displayWorkingArea = this.getWorkingArea(display); if ( display && // we have a display matching the desired bounds displayWorkingArea && // we have valid working area bounds - bounds.x < displayWorkingArea.x + displayWorkingArea.width && // prevent window from falling out of the screen to the right - bounds.y < displayWorkingArea.y + displayWorkingArea.height && // prevent window from falling out of the screen to the bottom - bounds.x + bounds.width > displayWorkingArea.x && // prevent window from falling out of the screen to the left - bounds.y + bounds.height > displayWorkingArea.y // prevent window from falling out of the scree nto the top + state.x + state.width > displayWorkingArea.x && // prevent window from falling out of the screen to the left + state.y + state.height > displayWorkingArea.y && // prevent window from falling out of the screen to the top + state.x < displayWorkingArea.x + displayWorkingArea.width && // prevent window from falling out of the screen to the right + state.y < displayWorkingArea.y + displayWorkingArea.height // prevent window from falling out of the screen to the bottom ) { this.logService.trace('window#validateWindowState: multi-monitor working area', displayWorkingArea); From 9db2c9564cc7652991662b596bdf81657f199dcd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Dec 2019 08:01:59 +0100 Subject: [PATCH 036/241] debt - try to fix flaky tests --- .../test/browser/editorGroupsService.test.ts | 4 +- .../editor/test/browser/editorService.test.ts | 54 ++- .../services/history/test/history.test.ts | 24 +- .../workbench/test/workbenchTestServices.ts | 308 ++++++++---------- 4 files changed, 203 insertions(+), 187 deletions(-) diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index b22215f7f2c..ccf554c339e 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -20,7 +20,7 @@ import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { CancellationToken } from 'vs/base/common/cancellation'; -export class TestEditorControl extends BaseEditor { +class TestEditorControl extends BaseEditor { constructor(@ITelemetryService telemetryService: ITelemetryService) { super('MyFileEditorForEditorGroupService', NullTelemetryService, new TestThemeService(), new TestStorageService()); } @@ -35,7 +35,7 @@ export class TestEditorControl extends BaseEditor { createEditor(): any { } } -export class TestEditorInput extends EditorInput implements IFileEditorInput { +class TestEditorInput extends EditorInput implements IFileEditorInput { constructor(private resource: URI) { super(); } diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index bfcc32372e1..079d1ce5ded 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorActivation } from 'vs/platform/editor/common/editor'; +import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; -import { workbenchInstantiationService, TestStorageService, TestEditorInput } from 'vs/workbench/test/workbenchTestServices'; +import { EditorInput, EditorOptions, IFileEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; +import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { EditorService, DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService'; @@ -48,6 +48,54 @@ class TestEditorControl extends BaseEditor { createEditor(): any { } } +class TestEditorInput extends EditorInput implements IFileEditorInput { + gotDisposed = false; + gotSaved = false; + gotSavedAs = false; + gotReverted = false; + dirty = false; + private fails = false; + constructor(public resource: URI) { super(); } + + getTypeId() { return 'testEditorInputForEditorService'; } + resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } + matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } + setEncoding(encoding: string) { } + getEncoding() { return undefined; } + setPreferredEncoding(encoding: string) { } + setMode(mode: string) { } + setPreferredMode(mode: string) { } + getResource(): URI { return this.resource; } + setForceOpenAsBinary(): void { } + setFailToOpen(): void { + this.fails = true; + } + save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.gotSaved = true; + return Promise.resolve(true); + } + saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.gotSavedAs = true; + return Promise.resolve(true); + } + revert(options?: IRevertOptions): Promise { + this.gotReverted = true; + this.gotSaved = false; + this.gotSavedAs = false; + return Promise.resolve(true); + } + isDirty(): boolean { + return this.dirty; + } + isReadonly(): boolean { + return false; + } + dispose(): void { + super.dispose(); + this.gotDisposed = true; + } +} + class FileServiceProvider extends Disposable { constructor(scheme: string, @IFileService fileService: IFileService) { super(); diff --git a/src/vs/workbench/services/history/test/history.test.ts b/src/vs/workbench/services/history/test/history.test.ts index 6c3384cfe8f..5a0d860f314 100644 --- a/src/vs/workbench/services/history/test/history.test.ts +++ b/src/vs/workbench/services/history/test/history.test.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorOptions, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IEditorInputFactory } from 'vs/workbench/common/editor'; +import { EditorOptions, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IEditorInputFactory, IFileEditorInput } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { workbenchInstantiationService, TestStorageService, TestEditorInput } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { EditorActivation } from 'vs/platform/editor/common/editor'; +import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; @@ -25,7 +25,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { timeout } from 'vs/base/common/async'; -export class TestEditorControl extends BaseEditor { +class TestEditorControl extends BaseEditor { constructor() { super('MyTestEditorForEditorHistory', NullTelemetryService, new TestThemeService(), new TestStorageService()); } @@ -40,6 +40,22 @@ export class TestEditorControl extends BaseEditor { createEditor(): any { } } +class TestEditorInput extends EditorInput implements IFileEditorInput { + + constructor(public resource: URI) { super(); } + + getTypeId() { return 'testEditorInputForEditorGroupService'; } + resolve(): Promise { return Promise.resolve(null); } + matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } + setEncoding(encoding: string) { } + getEncoding() { return undefined; } + setPreferredEncoding(encoding: string) { } + setMode(mode: string) { } + setPreferredMode(mode: string) { } + getResource(): URI { return this.resource; } + setForceOpenAsBinary(): void { } +} + class HistoryTestEditorInput extends TestEditorInput { getTypeId() { return 'testEditorInputForEditorsHistory'; } } diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index f0ca3eed438..1e96afa4b1d 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -11,7 +11,7 @@ import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, EditorInput, IFileEditorInput, GroupIdentifier, ISaveOptions } from 'vs/workbench/common/editor'; +import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier } from 'vs/workbench/common/editor'; import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView, IEditorGroupsAccessor } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; @@ -20,7 +20,7 @@ import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configur import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IEditorOptions, IResourceInput, IEditorModel } from 'vs/platform/editor/common/editor'; +import { IEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor'; import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkspaceContextService, IWorkspace as IWorkbenchWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, Workspace } from 'vs/platform/workspace/common/workspace'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; @@ -102,7 +102,7 @@ export function createFileInput(instantiationService: IInstantiationService, res export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); export class TestContextService implements IWorkspaceContextService { - public _serviceBrand: undefined; + _serviceBrand: undefined; private workspace: Workspace; private options: any; @@ -119,23 +119,23 @@ export class TestContextService implements IWorkspaceContextService { this._onDidChangeWorkbenchState = new Emitter(); } - public get onDidChangeWorkspaceName(): Event { + get onDidChangeWorkspaceName(): Event { return this._onDidChangeWorkspaceName.event; } - public get onDidChangeWorkspaceFolders(): Event { + get onDidChangeWorkspaceFolders(): Event { return this._onDidChangeWorkspaceFolders.event; } - public get onDidChangeWorkbenchState(): Event { + get onDidChangeWorkbenchState(): Event { return this._onDidChangeWorkbenchState.event; } - public getFolders(): IWorkspaceFolder[] { + getFolders(): IWorkspaceFolder[] { return this.workspace ? this.workspace.folders : []; } - public getWorkbenchState(): WorkbenchState { + getWorkbenchState(): WorkbenchState { if (this.workspace.configuration) { return WorkbenchState.WORKSPACE; } @@ -151,27 +151,27 @@ export class TestContextService implements IWorkspaceContextService { return Promise.resolve(this.getWorkspace()); } - public getWorkspace(): IWorkbenchWorkspace { + getWorkspace(): IWorkbenchWorkspace { return this.workspace; } - public getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { + getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { return this.workspace.getFolder(resource); } - public setWorkspace(workspace: any): void { + setWorkspace(workspace: any): void { this.workspace = workspace; } - public getOptions() { + getOptions() { return this.options; } - public updateOptions() { + updateOptions() { } - public isInsideWorkspace(resource: URI): boolean { + isInsideWorkspace(resource: URI): boolean { if (resource && this.workspace) { return resources.isEqualOrParent(resource, this.workspace.folders[0].uri); } @@ -179,17 +179,17 @@ export class TestContextService implements IWorkspaceContextService { return false; } - public toResource(workspaceRelativePath: string): URI { + toResource(workspaceRelativePath: string): URI { return URI.file(join('C:\\', workspaceRelativePath)); } - public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean { + isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean { return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && resources.isEqual(this.workspace.folders[0].uri, workspaceIdentifier); } } export class TestTextFileService extends NativeTextFileService { - public cleanupBackupsBeforeShutdownCalled!: boolean; + cleanupBackupsBeforeShutdownCalled!: boolean; private promptPath!: URI; private resolveTextContentError!: FileOperationError | null; @@ -236,15 +236,15 @@ export class TestTextFileService extends NativeTextFileService { ); } - public setPromptPath(path: URI): void { + setPromptPath(path: URI): void { this.promptPath = path; } - public setResolveTextContentErrorOnce(error: FileOperationError): void { + setResolveTextContentErrorOnce(error: FileOperationError): void { this.resolveTextContentError = error; } - public readStream(resource: URI, options?: IReadTextFileOptions): Promise { + readStream(resource: URI, options?: IReadTextFileOptions): Promise { if (this.resolveTextContentError) { const error = this.resolveTextContentError; this.resolveTextContentError = null; @@ -266,7 +266,7 @@ export class TestTextFileService extends NativeTextFileService { }); } - public promptForPath(_resource: URI, _defaultPath: URI): Promise { + promptForPath(_resource: URI, _defaultPath: URI): Promise { return Promise.resolve(this.promptPath); } @@ -333,7 +333,7 @@ export class TestExtensionService extends NullExtensionService { } export class TestMenuService implements IMenuService { - public _serviceBrand: undefined; + _serviceBrand: undefined; createMenu(_id: MenuId, _scopedKeybindingService: IContextKeyService): IMenu { return { @@ -368,68 +368,68 @@ export class TestHistoryService implements IHistoryService { export class TestDialogService implements IDialogService { - public _serviceBrand: undefined; + _serviceBrand: undefined; - public confirm(_confirmation: IConfirmation): Promise { + confirm(_confirmation: IConfirmation): Promise { return Promise.resolve({ confirmed: false }); } - public show(_severity: Severity, _message: string, _buttons: string[], _options?: IDialogOptions): Promise { + show(_severity: Severity, _message: string, _buttons: string[], _options?: IDialogOptions): Promise { return Promise.resolve({ choice: 0 }); } - public about(): Promise { + about(): Promise { return Promise.resolve(); } } export class TestFileDialogService implements IFileDialogService { - public _serviceBrand: undefined; + _serviceBrand: undefined; private confirmResult!: ConfirmResult; - public defaultFilePath(_schemeFilter?: string): URI | undefined { + defaultFilePath(_schemeFilter?: string): URI | undefined { return undefined; } - public defaultFolderPath(_schemeFilter?: string): URI | undefined { + defaultFolderPath(_schemeFilter?: string): URI | undefined { return undefined; } - public defaultWorkspacePath(_schemeFilter?: string): URI | undefined { + defaultWorkspacePath(_schemeFilter?: string): URI | undefined { return undefined; } - public pickFileFolderAndOpen(_options: IPickAndOpenOptions): Promise { + pickFileFolderAndOpen(_options: IPickAndOpenOptions): Promise { return Promise.resolve(0); } - public pickFileAndOpen(_options: IPickAndOpenOptions): Promise { + pickFileAndOpen(_options: IPickAndOpenOptions): Promise { return Promise.resolve(0); } - public pickFolderAndOpen(_options: IPickAndOpenOptions): Promise { + pickFolderAndOpen(_options: IPickAndOpenOptions): Promise { return Promise.resolve(0); } - public pickWorkspaceAndOpen(_options: IPickAndOpenOptions): Promise { + pickWorkspaceAndOpen(_options: IPickAndOpenOptions): Promise { return Promise.resolve(0); } - public pickFileToSave(_options: ISaveDialogOptions): Promise { + pickFileToSave(_options: ISaveDialogOptions): Promise { return Promise.resolve(undefined); } - public showSaveDialog(_options: ISaveDialogOptions): Promise { + showSaveDialog(_options: ISaveDialogOptions): Promise { return Promise.resolve(undefined); } - public showOpenDialog(_options: IOpenDialogOptions): Promise { + showOpenDialog(_options: IOpenDialogOptions): Promise { return Promise.resolve(undefined); } - public setConfirmResult(result: ConfirmResult): void { + setConfirmResult(result: ConfirmResult): void { this.confirmResult = result; } - public showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { + showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise { return Promise.resolve(this.confirmResult); } } export class TestLayoutService implements IWorkbenchLayoutService { - public _serviceBrand: undefined; + _serviceBrand: undefined; dimension: IDimension = { width: 800, height: 600 }; @@ -445,27 +445,27 @@ export class TestLayoutService implements IWorkbenchLayoutService { private readonly _onMenubarVisibilityChange = new Emitter(); - public get onMenubarVisibilityChange(): Event { + get onMenubarVisibilityChange(): Event { return this._onMenubarVisibilityChange.event; } - public isRestored(): boolean { + isRestored(): boolean { return true; } - public hasFocus(_part: Parts): boolean { + hasFocus(_part: Parts): boolean { return false; } - public hasWindowBorder(): boolean { + hasWindowBorder(): boolean { return false; } - public getWindowBorderRadius(): string | undefined { + getWindowBorderRadius(): string | undefined { return undefined; } - public isVisible(_part: Parts): boolean { + isVisible(_part: Parts): boolean { return true; } @@ -473,93 +473,93 @@ export class TestLayoutService implements IWorkbenchLayoutService { return new Dimension(0, 0); } - public getContainer(_part: Parts): HTMLElement { + getContainer(_part: Parts): HTMLElement { return null!; } - public isTitleBarHidden(): boolean { + isTitleBarHidden(): boolean { return false; } - public getTitleBarOffset(): number { + getTitleBarOffset(): number { return 0; } - public isStatusBarHidden(): boolean { + isStatusBarHidden(): boolean { return false; } - public isActivityBarHidden(): boolean { + isActivityBarHidden(): boolean { return false; } - public setActivityBarHidden(_hidden: boolean): void { } + setActivityBarHidden(_hidden: boolean): void { } - public isSideBarHidden(): boolean { + isSideBarHidden(): boolean { return false; } - public setEditorHidden(_hidden: boolean): Promise { return Promise.resolve(); } + setEditorHidden(_hidden: boolean): Promise { return Promise.resolve(); } - public setSideBarHidden(_hidden: boolean): Promise { return Promise.resolve(); } + setSideBarHidden(_hidden: boolean): Promise { return Promise.resolve(); } - public isPanelHidden(): boolean { + isPanelHidden(): boolean { return false; } - public setPanelHidden(_hidden: boolean): Promise { return Promise.resolve(); } + setPanelHidden(_hidden: boolean): Promise { return Promise.resolve(); } - public toggleMaximizedPanel(): void { } + toggleMaximizedPanel(): void { } - public isPanelMaximized(): boolean { + isPanelMaximized(): boolean { return false; } - public getMenubarVisibility(): MenuBarVisibility { + getMenubarVisibility(): MenuBarVisibility { throw new Error('not implemented'); } - public getSideBarPosition() { + getSideBarPosition() { return 0; } - public getPanelPosition() { + getPanelPosition() { return 0; } - public setPanelPosition(_position: PartPosition): Promise { + setPanelPosition(_position: PartPosition): Promise { return Promise.resolve(); } - public addClass(_clazz: string): void { } - public removeClass(_clazz: string): void { } + addClass(_clazz: string): void { } + removeClass(_clazz: string): void { } - public getMaximumEditorDimensions(): Dimension { throw new Error('not implemented'); } + getMaximumEditorDimensions(): Dimension { throw new Error('not implemented'); } - public getWorkbenchContainer(): HTMLElement { throw new Error('not implemented'); } - public getWorkbenchElement(): HTMLElement { throw new Error('not implemented'); } + getWorkbenchContainer(): HTMLElement { throw new Error('not implemented'); } + getWorkbenchElement(): HTMLElement { throw new Error('not implemented'); } - public toggleZenMode(): void { } + toggleZenMode(): void { } - public isEditorLayoutCentered(): boolean { return false; } - public centerEditorLayout(_active: boolean): void { } + isEditorLayoutCentered(): boolean { return false; } + centerEditorLayout(_active: boolean): void { } - public resizePart(_part: Parts, _sizeChange: number): void { } + resizePart(_part: Parts, _sizeChange: number): void { } - public registerPart(part: Part): void { } + registerPart(part: Part): void { } isWindowMaximized() { return false; } - public updateWindowMaximizedState(maximized: boolean): void { } + updateWindowMaximizedState(maximized: boolean): void { } } let activeViewlet: Viewlet = {} as any; export class TestViewletService implements IViewletService { - public _serviceBrand: undefined; + _serviceBrand: undefined; onDidViewletRegisterEmitter = new Emitter(); onDidViewletDeregisterEmitter = new Emitter(); @@ -571,93 +571,93 @@ export class TestViewletService implements IViewletService { onDidViewletOpen = this.onDidViewletOpenEmitter.event; onDidViewletClose = this.onDidViewletCloseEmitter.event; - public openViewlet(id: string, focus?: boolean): Promise { + openViewlet(id: string, focus?: boolean): Promise { return Promise.resolve(undefined); } - public getViewlets(): ViewletDescriptor[] { + getViewlets(): ViewletDescriptor[] { return []; } - public getAllViewlets(): ViewletDescriptor[] { + getAllViewlets(): ViewletDescriptor[] { return []; } - public getActiveViewlet(): IViewlet { + getActiveViewlet(): IViewlet { return activeViewlet; } - public dispose() { + dispose() { } - public getDefaultViewletId(): string { + getDefaultViewletId(): string { return 'workbench.view.explorer'; } - public getViewlet(id: string): ViewletDescriptor | undefined { + getViewlet(id: string): ViewletDescriptor | undefined { return undefined; } - public getProgressIndicator(id: string) { + getProgressIndicator(id: string) { return undefined; } - public hideActiveViewlet(): void { } + hideActiveViewlet(): void { } - public getLastActiveViewletId(): string { + getLastActiveViewletId(): string { return undefined!; } } export class TestPanelService implements IPanelService { - public _serviceBrand: undefined; + _serviceBrand: undefined; onDidPanelOpen = new Emitter<{ panel: IPanel, focus: boolean }>().event; onDidPanelClose = new Emitter().event; - public openPanel(id: string, focus?: boolean): undefined { + openPanel(id: string, focus?: boolean): undefined { return undefined; } - public getPanel(id: string): any { + getPanel(id: string): any { return activeViewlet; } - public getPanels() { + getPanels() { return []; } - public getPinnedPanels() { + getPinnedPanels() { return []; } - public getActivePanel(): IViewlet { + getActivePanel(): IViewlet { return activeViewlet; } - public setPanelEnablement(id: string, enabled: boolean): void { } + setPanelEnablement(id: string, enabled: boolean): void { } - public dispose() { + dispose() { } - public showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { + showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { throw new Error('Method not implemented.'); } - public getProgressIndicator(id: string) { + getProgressIndicator(id: string) { return null!; } - public hideActivePanel(): void { } + hideActivePanel(): void { } - public getLastActivePanelId(): string { + getLastActivePanelId(): string { return undefined!; } } export class TestStorageService extends InMemoryStorageService { - readonly _onWillSaveState: Emitter = this._register(new Emitter()); - readonly onWillSaveState: Event = this._onWillSaveState.event; + readonly _onWillSaveState = this._register(new Emitter()); + readonly onWillSaveState = this._onWillSaveState.event; } export class TestEditorGroupsService implements IEditorGroupsService { @@ -876,54 +876,6 @@ export class TestEditorGroupAccessor implements IEditorGroupsAccessor { arrangeGroups(arrangement: GroupsArrangement, target?: number | IEditorGroupView | undefined): void { throw new Error('Method not implemented.'); } } -export class TestEditorInput extends EditorInput implements IFileEditorInput { - public gotDisposed = false; - public gotSaved = false; - public gotSavedAs = false; - public gotReverted = false; - public dirty = false; - private fails = false; - constructor(public resource: URI) { super(); } - - getTypeId() { return 'testEditorInputForEditorService'; } - resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } - matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } - setEncoding(encoding: string) { } - getEncoding() { return undefined; } - setPreferredEncoding(encoding: string) { } - setMode(mode: string) { } - setPreferredMode(mode: string) { } - getResource(): URI { return this.resource; } - setForceOpenAsBinary(): void { } - setFailToOpen(): void { - this.fails = true; - } - save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - this.gotSaved = true; - return Promise.resolve(true); - } - saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - this.gotSavedAs = true; - return Promise.resolve(true); - } - revert(options?: IRevertOptions): Promise { - this.gotReverted = true; - this.gotSaved = false; - this.gotSavedAs = false; - return Promise.resolve(true); - } - isDirty(): boolean { - return this.dirty; - } - isReadonly(): boolean { - return false; - } - dispose(): void { - super.dispose(); - this.gotDisposed = true; - } -} - export class TestEditorService implements EditorServiceImpl { _serviceBrand: undefined; @@ -992,7 +944,7 @@ export class TestEditorService implements EditorServiceImpl { export class TestFileService implements IFileService { - public _serviceBrand: undefined; + _serviceBrand: undefined; private readonly _onFileChanges: Emitter; private readonly _onAfterOperation: Emitter; @@ -1008,31 +960,31 @@ export class TestFileService implements IFileService { this._onAfterOperation = new Emitter(); } - public setContent(content: string): void { + setContent(content: string): void { this.content = content; } - public getContent(): string { + getContent(): string { return this.content; } - public getLastReadFileUri(): URI { + getLastReadFileUri(): URI { return this.lastReadFileUri; } - public get onFileChanges(): Event { + get onFileChanges(): Event { return this._onFileChanges.event; } - public fireFileChanges(event: FileChangesEvent): void { + fireFileChanges(event: FileChangesEvent): void { this._onFileChanges.fire(event); } - public get onAfterOperation(): Event { + get onAfterOperation(): Event { return this._onAfterOperation.event; } - public fireAfterOperation(event: FileOperationEvent): void { + fireAfterOperation(event: FileOperationEvent): void { this._onAfterOperation.fire(event); } @@ -1170,21 +1122,21 @@ export class TestFileService implements IFileService { } export class TestBackupFileService implements IBackupFileService { - public _serviceBrand: undefined; + _serviceBrand: undefined; - public hasBackups(): Promise { + hasBackups(): Promise { return Promise.resolve(false); } - public hasBackup(_resource: URI): Promise { + hasBackup(_resource: URI): Promise { return Promise.resolve(false); } - public hasBackupSync(resource: URI, versionId?: number): boolean { + hasBackupSync(resource: URI, versionId?: number): boolean { return false; } - public loadBackupResource(resource: URI): Promise { + loadBackupResource(resource: URI): Promise { return this.hasBackup(resource).then(hasBackup => { if (hasBackup) { return this.toBackupResource(resource); @@ -1194,42 +1146,42 @@ export class TestBackupFileService implements IBackupFileService { }); } - public registerResourceForBackup(_resource: URI): Promise { + registerResourceForBackup(_resource: URI): Promise { return Promise.resolve(); } - public deregisterResourceForBackup(_resource: URI): Promise { + deregisterResourceForBackup(_resource: URI): Promise { return Promise.resolve(); } - public toBackupResource(_resource: URI): URI { + toBackupResource(_resource: URI): URI { throw new Error('not implemented'); } - public backupResource(_resource: URI, _content: ITextSnapshot, versionId?: number, meta?: T): Promise { + backupResource(_resource: URI, _content: ITextSnapshot, versionId?: number, meta?: T): Promise { return Promise.resolve(); } - public getWorkspaceFileBackups(): Promise { + getWorkspaceFileBackups(): Promise { return Promise.resolve([]); } - public parseBackupContent(textBufferFactory: ITextBufferFactory): string { + parseBackupContent(textBufferFactory: ITextBufferFactory): string { const textBuffer = textBufferFactory.create(DefaultEndOfLine.LF); const lineCount = textBuffer.getLineCount(); const range = new Range(1, 1, lineCount, textBuffer.getLineLength(lineCount) + 1); return textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined); } - public resolveBackupContent(_backup: URI): Promise> { + resolveBackupContent(_backup: URI): Promise> { throw new Error('not implemented'); } - public discardResourceBackup(_resource: URI): Promise { + discardResourceBackup(_resource: URI): Promise { return Promise.resolve(); } - public discardAllWorkspaceBackups(): Promise { + discardAllWorkspaceBackups(): Promise { return Promise.resolve(); } } @@ -1261,10 +1213,10 @@ export class TestCodeEditorService implements ICodeEditorService { export class TestLifecycleService implements ILifecycleService { - public _serviceBrand: undefined; + _serviceBrand: undefined; - public phase!: LifecyclePhase; - public startupKind!: StartupKind; + phase!: LifecyclePhase; + startupKind!: StartupKind; private readonly _onBeforeShutdown = new Emitter(); private readonly _onWillShutdown = new Emitter(); @@ -1274,26 +1226,26 @@ export class TestLifecycleService implements ILifecycleService { return Promise.resolve(); } - public fireShutdown(reason = ShutdownReason.QUIT): void { + fireShutdown(reason = ShutdownReason.QUIT): void { this._onWillShutdown.fire({ join: () => { }, reason }); } - public fireWillShutdown(event: BeforeShutdownEvent): void { + fireWillShutdown(event: BeforeShutdownEvent): void { this._onBeforeShutdown.fire(event); } - public get onBeforeShutdown(): Event { + get onBeforeShutdown(): Event { return this._onBeforeShutdown.event; } - public get onWillShutdown(): Event { + get onWillShutdown(): Event { return this._onWillShutdown.event; } - public get onShutdown(): Event { + get onShutdown(): Event { return this._onShutdown.event; } } @@ -1305,7 +1257,7 @@ export class TestTextResourceConfigurationService implements ITextResourceConfig constructor(private configurationService = new TestConfigurationService()) { } - public onDidChangeConfiguration() { + onDidChangeConfiguration() { return { dispose() { } }; } From 9031cea99d5c63f7bdfcceeb61e2bc081d605b32 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 Dec 2019 08:21:20 +0100 Subject: [PATCH 037/241] remove the api example --- .../api/common/extHostConfiguration.ts | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 055d72584fa..e1052f6dbc8 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -284,44 +284,6 @@ export class ExtHostConfigProvider { return scopes.reduce((result, scope) => { result.set(scope[0], scope[1]); return result; }, new Map()); } - apiExample() { - /** - * Most often scenario: Read window configuration effectively - */ - const windowConfiguration = vscode.workspace.getConfiguration('window'); - windowConfiguration.get('size'); - windowConfiguration.update('size', 2); // Updates in workspace - - /** - * Most often scenario: Read and Write workspace folder configuration effectively - */ - const workspaceFolderConfiguration = vscode.workspace.getConfiguration('git', vscode.workspace.workspaceFolders![0]); - workspaceFolderConfiguration.get('enabled'); - workspaceFolderConfiguration.update('enabled', false); // Effective update - - /** - * Most often scenario: Read and Write language configuration effectively - */ - const textDocumentConfiguration = vscode.workspace.getConfiguration('editor', vscode.window.activeTextEditor?.document!); - textDocumentConfiguration.get('formatOnSave'); - textDocumentConfiguration.update('formatOnSave', false); //Effective update. - - /** - * Special scenario: Write a configuration under a specific language - */ - const languageConfiguration = vscode.workspace.getConfiguration(); - const value = languageConfiguration.get<{ 'editor.formatOnSave': boolean }>('[javascript]')!; - value['editor.formatOnSave'] = false; - languageConfiguration.update('[javascript]', value); - - /** - * deprecated - */ - vscode.workspace.getConfiguration('editor', vscode.Uri.file('abc')); - vscode.workspace.getConfiguration('editor', null); - - } - } export const IExtHostConfiguration = createDecorator('IExtHostConfiguration'); From 1efd373c13f2487c851c72e3c898b07612015ba0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 Dec 2019 08:24:07 +0100 Subject: [PATCH 038/241] remove resource language scope from contrib point --- src/vs/workbench/api/common/configurationExtensionPoint.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 1f8513f02bf..70854858361 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -39,17 +39,16 @@ const configurationEntrySchema: IJSONSchema = { }, scope: { type: 'string', - enum: ['application', 'machine', 'window', 'resource', 'resource-language', 'machine-overridable'], + enum: ['application', 'machine', 'window', 'resource', 'machine-overridable'], default: 'window', enumDescriptions: [ nls.localize('scope.application.description', "Configuration that can be configured only in the user settings."), nls.localize('scope.machine.description', "Configuration that can be configured only in the user settings when the extension is running locally, or only in the remote settings when the extension is running remotely."), nls.localize('scope.window.description', "Configuration that can be configured in the user, remote or workspace settings."), nls.localize('scope.resource.description', "Configuration that can be configured in the user, remote, workspace or folder settings."), - nls.localize('scope.resource-language.description', "Resource configuration that can be configured also in language specific settings."), nls.localize('scope.machine-overridable.description', "Machine configuration that can be configured also in workspace or folder settings.") ], - description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `application`, `machine`, `window`, `resource`, `resource-language` and `machine-overridable`.") + description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `application`, `machine`, `window`, `resource`, and `machine-overridable`.") }, enumDescriptions: { type: 'array', @@ -219,8 +218,6 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten propertyConfiguration.scope = ConfigurationScope.RESOURCE; } else if (propertyConfiguration.scope.toString() === 'machine-overridable') { propertyConfiguration.scope = ConfigurationScope.MACHINE_OVERRIDABLE; - } else if (propertyConfiguration.scope.toString() === 'resource-language') { - propertyConfiguration.scope = ConfigurationScope.RESOURCE_LANGUAGE; } else { propertyConfiguration.scope = ConfigurationScope.WINDOW; } From 3d667a08607e19816a03a7a0ee6caf9f99c5e82f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 Dec 2019 08:25:55 +0100 Subject: [PATCH 039/241] Revert "remove resource language scope from contrib point" This reverts commit 1efd373c13f2487c851c72e3c898b07612015ba0. --- src/vs/workbench/api/common/configurationExtensionPoint.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 70854858361..1f8513f02bf 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -39,16 +39,17 @@ const configurationEntrySchema: IJSONSchema = { }, scope: { type: 'string', - enum: ['application', 'machine', 'window', 'resource', 'machine-overridable'], + enum: ['application', 'machine', 'window', 'resource', 'resource-language', 'machine-overridable'], default: 'window', enumDescriptions: [ nls.localize('scope.application.description', "Configuration that can be configured only in the user settings."), nls.localize('scope.machine.description', "Configuration that can be configured only in the user settings when the extension is running locally, or only in the remote settings when the extension is running remotely."), nls.localize('scope.window.description', "Configuration that can be configured in the user, remote or workspace settings."), nls.localize('scope.resource.description', "Configuration that can be configured in the user, remote, workspace or folder settings."), + nls.localize('scope.resource-language.description', "Resource configuration that can be configured also in language specific settings."), nls.localize('scope.machine-overridable.description', "Machine configuration that can be configured also in workspace or folder settings.") ], - description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `application`, `machine`, `window`, `resource`, and `machine-overridable`.") + description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `application`, `machine`, `window`, `resource`, `resource-language` and `machine-overridable`.") }, enumDescriptions: { type: 'array', @@ -218,6 +219,8 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten propertyConfiguration.scope = ConfigurationScope.RESOURCE; } else if (propertyConfiguration.scope.toString() === 'machine-overridable') { propertyConfiguration.scope = ConfigurationScope.MACHINE_OVERRIDABLE; + } else if (propertyConfiguration.scope.toString() === 'resource-language') { + propertyConfiguration.scope = ConfigurationScope.RESOURCE_LANGUAGE; } else { propertyConfiguration.scope = ConfigurationScope.WINDOW; } From 1efbb135833065943ab1693025c578d9ded488e1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Dec 2019 08:45:15 +0100 Subject: [PATCH 040/241] editors - fix various issues around preserving language mode --- src/vs/workbench/browser/dnd.ts | 2 +- src/vs/workbench/common/editor.ts | 6 +++--- .../files/browser/editors/fileEditorTracker.ts | 4 ---- .../textfile/browser/textFileService.ts | 17 ++++++++--------- .../textfile/common/textFileEditorModel.ts | 8 ++++++++ .../services/textfile/common/textfiles.ts | 5 ++--- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index d4884d5c2a7..10d6cf85ef7 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -383,7 +383,7 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: const model = textFileService.models.get(file.resource); if (model) { encoding = model.getEncoding(); - mode = model.textEditorModel?.getModeId(); + mode = model.getMode(); } } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index b15a2202780..7d4b9690214 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -618,12 +618,12 @@ export const enum EncodingMode { export interface IEncodingSupport { /** - * Gets the encoding of the input if known. + * Gets the encoding of the type if known. */ getEncoding(): string | undefined; /** - * Sets the encoding for the input for saving. + * Sets the encoding for the type for saving. */ setEncoding(encoding: string, mode: EncodingMode): void; } @@ -631,7 +631,7 @@ export interface IEncodingSupport { export interface IModeSupport { /** - * Sets the language mode of the input. + * Sets the language mode of the type. */ setMode(mode: string): void; } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index 92699a26fa3..a2fd13708d6 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -111,12 +111,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut } let encoding: string | undefined = undefined; - let mode: string | undefined = undefined; - const model = this.textFileService.models.get(resource); if (model) { encoding = model.getEncoding(); - mode = model.textEditorModel?.getModeId(); } this.editorService.replaceEditors([{ @@ -124,7 +121,6 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut replacement: { resource: reopenFileResource, encoding, - mode, options: { preserveFocus: true, pinned: group.isPinned(editor), diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 3a00df0a6e7..5f4ec3ee4b8 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -433,12 +433,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex modelToRestoreResource = joinPath(target, sourceModelResource.path.substr(source.path.length + 1)); } - let mode: string | undefined = sourceModel.textEditorModel?.getModeId(); - if (mode === PLAINTEXT_MODE_ID) { - mode = undefined; // never enforce plain text mode when moving as it is unspecific - } - - const modelToRestore: ModelToRestore = { resource: modelToRestoreResource, encoding: sourceModel.getEncoding(), mode }; + const modelToRestore: ModelToRestore = { resource: modelToRestoreResource, encoding: sourceModel.getEncoding() }; if (sourceModel.isDirty()) { modelToRestore.snapshot = sourceModel.createSnapshot(); } @@ -771,9 +766,13 @@ export abstract class AbstractTextFileService extends Disposable implements ITex await this.create(target, ''); } - let mode: string | undefined = sourceModel.textEditorModel?.getModeId(); - if (mode === PLAINTEXT_MODE_ID) { - mode = undefined; // never enforce plain text mode when moving as it is unspecific + // Carry over the mode if this is an untitled file and the mode was picked by the user + let mode: string | undefined; + if (sourceModel instanceof UntitledTextEditorModel) { + mode = sourceModel.getMode(); + if (mode === PLAINTEXT_MODE_ID) { + mode = undefined; // never enforce plain text mode when moving as it is unspecific + } } targetModel = await this.models.loadOrCreate(target, { encoding: sourceModel.getEncoding(), mode }); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index fb8822c7f3c..13c8397c189 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -960,6 +960,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } + getMode(): string | undefined { + if (this.textEditorModel) { + return this.textEditorModel.getModeId(); + } + + return this.preferredMode; + } + getEncoding(): string | undefined { return this.preferredEncoding || this.contentEncoding; } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 8b6750df496..b7abec08cba 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -456,6 +456,8 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport makeDirty(): void; + getMode(): string | undefined; + isResolved(): this is IResolvedTextFileEditorModel; isDisposed(): boolean; @@ -468,9 +470,6 @@ export interface IResolvedTextFileEditorModel extends ITextFileEditorModel { createSnapshot(): ITextSnapshot; } -/** - * Helper method to convert a snapshot into its full string form. - */ export function snapshotToString(snapshot: ITextSnapshot): string { const chunks: string[] = []; From 3f925972d399c86ccd3b7262b102491e651ca05e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 17 Dec 2019 09:46:34 +0100 Subject: [PATCH 041/241] add unit test for #86984 --- .../suggest/test/suggestController.test.ts | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/vs/editor/contrib/suggest/test/suggestController.test.ts diff --git a/src/vs/editor/contrib/suggest/test/suggestController.test.ts b/src/vs/editor/contrib/suggest/test/suggestController.test.ts new file mode 100644 index 00000000000..e3c00dbb01f --- /dev/null +++ b/src/vs/editor/contrib/suggest/test/suggestController.test.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; +import { createTestCodeEditor, TestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { ISuggestMemoryService } from 'vs/editor/contrib/suggest/suggestMemory'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { mock } from 'vs/editor/contrib/suggest/test/suggestModel.test'; +import { Selection } from 'vs/editor/common/core/selection'; +import { CompletionProviderRegistry, CompletionItemKind, CompletionItemInsertTextRule } from 'vs/editor/common/modes'; +import { Event } from 'vs/base/common/event'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; + +suite('SuggestController', function () { + + const disposables = new DisposableStore(); + + let controller: SuggestController; + let editor: TestCodeEditor; + let model: TextModel; + + setup(function () { + disposables.clear(); + + const serviceCollection = new ServiceCollection( + [ITelemetryService, NullTelemetryService], + [IStorageService, new InMemoryStorageService()], + [IKeybindingService, new MockKeybindingService()], + [IEditorWorkerService, new class extends mock() { + computeWordRanges() { + return Promise.resolve({}); + } + }], + [ISuggestMemoryService, new class extends mock() { + memorize(): void { } + select(): number { return 0; } + }] + ); + + model = TextModel.createFromString('', undefined, undefined, URI.from({ scheme: 'test-ctrl', path: '/path.tst' })); + editor = createTestCodeEditor({ + model, + serviceCollection, + }); + + editor.registerAndInstantiateContribution(SnippetController2.ID, SnippetController2); + controller = editor.registerAndInstantiateContribution(SuggestController.ID, SuggestController); + }); + + test('postfix completion reports incorrect position #86984', async function () { + disposables.add(CompletionProviderRegistry.register({ scheme: 'test-ctrl' }, { + provideCompletionItems(doc, pos) { + return { + suggestions: [{ + kind: CompletionItemKind.Snippet, + label: 'let', + insertText: 'let ${1:name} = foo$0', + insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet, + range: { startLineNumber: 1, startColumn: 9, endLineNumber: 1, endColumn: 11 }, + additionalTextEdits: [{ + text: '', + range: { startLineNumber: 1, startColumn: 5, endLineNumber: 1, endColumn: 9 } + }] + }] + }; + } + })); + + editor.setValue(' foo.le'); + editor.setSelection(new Selection(1, 11, 1, 11)); + + // trigger + let p1 = Event.toPromise(controller.model.onDidSuggest); + controller.triggerSuggest(); + await p1; + + // + let p2 = Event.toPromise(controller.model.onDidCancel); + controller.acceptSelectedSuggestion(false, false); + await p2; + + assert.equal(editor.getValue(), ' let name = foo'); + }); +}); From 54ab792f0fac6510a7c2aaa230983dc1dc18cf6a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Dec 2019 10:25:40 +0100 Subject: [PATCH 042/241] state - periodically save state to prevent loss on crash or shutdown (#87158) --- .../multicursor/test/multicursor.test.ts | 3 +- .../storage/browser/storageService.ts | 91 +++++++++++-------- src/vs/platform/storage/common/storage.ts | 18 +++- .../platform/storage/node/storageService.ts | 35 +++++++ .../browser/parts/editor/textEditor.ts | 7 +- src/vs/workbench/browser/workbench.ts | 14 ++- src/vs/workbench/electron-browser/window.ts | 1 + 7 files changed, 123 insertions(+), 46 deletions(-) diff --git a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts index 7724699eded..157a018ac0d 100644 --- a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts +++ b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts @@ -69,7 +69,8 @@ suite('Multicursor selection', () => { store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); }, remove: (key) => undefined, logStorage: () => undefined, - migrate: (toWorkspace) => Promise.resolve(undefined) + migrate: (toWorkspace) => Promise.resolve(undefined), + flush: () => undefined } as IStorageService); test('issue #8817: Cursor position changes when you cancel multicursor', () => { diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index e1da1af90f8..c1fa72a27c6 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -21,11 +21,11 @@ export class BrowserStorageService extends Disposable implements IStorageService _serviceBrand: undefined; - private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); - readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; + private readonly _onDidChangeStorage = this._register(new Emitter()); + readonly onDidChangeStorage = this._onDidChangeStorage.event; - private readonly _onWillSaveState: Emitter = this._register(new Emitter()); - readonly onWillSaveState: Event = this._onWillSaveState.event; + private readonly _onWillSaveState = this._register(new Emitter()); + readonly onWillSaveState = this._onWillSaveState.event; private globalStorage: IStorage | undefined; private workspaceStorage: IStorage | undefined; @@ -37,45 +37,15 @@ export class BrowserStorageService extends Disposable implements IStorageService private workspaceStorageFile: URI | undefined; private initializePromise: Promise | undefined; - private periodicSaveScheduler = this._register(new RunOnceScheduler(() => this.collectState(), 5000)); - get hasPendingUpdate(): boolean { - return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate); - } + private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 5000 /* every 5s */)); + private runWhenIdleDisposable: IDisposable | undefined = undefined; constructor( @IEnvironmentService private readonly environmentService: IEnvironmentService, @IFileService private readonly fileService: IFileService ) { super(); - - // In the browser we do not have support for long running unload sequences. As such, - // we cannot ask for saving state in that moment, because that would result in a - // long running operation. - // Instead, periodically ask customers to save save. The library will be clever enough - // to only save state that has actually changed. - this.periodicSaveScheduler.schedule(); - } - - private collectState(): void { - runWhenIdle(() => { - - // this event will potentially cause new state to be stored - // since new state will only be created while the document - // has focus, one optimization is to not run this when the - // document has no focus, assuming that state has not changed - // - // another optimization is to not collect more state if we - // have a pending update already running which indicates - // that the connection is either slow or disconnected and - // thus unhealthy. - if (document.hasFocus() && !this.hasPendingUpdate) { - this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); - } - - // repeat - this.periodicSaveScheduler.schedule(); - }); } initialize(payload: IWorkspaceInitializationPayload): Promise { @@ -109,6 +79,13 @@ export class BrowserStorageService extends Disposable implements IStorageService this.workspaceStorage.init(), this.globalStorage.init() ]); + + // In the browser we do not have support for long running unload sequences. As such, + // we cannot ask for saving state in that moment, because that would result in a + // long running operation. + // Instead, periodically ask customers to save save. The library will be clever enough + // to only save state that has actually changed. + this.periodicFlushScheduler.schedule(); } get(key: string, scope: StorageScope, fallbackValue: string): string; @@ -156,6 +133,40 @@ export class BrowserStorageService extends Disposable implements IStorageService throw new Error('Migrating storage is currently unsupported in Web'); } + private doFlushWhenIdle(): void { + + // Dispose any previous idle runner + this.runWhenIdleDisposable = dispose(this.runWhenIdleDisposable); + + // Run when idle + this.runWhenIdleDisposable = runWhenIdle(() => { + + // this event will potentially cause new state to be stored + // since new state will only be created while the document + // has focus, one optimization is to not run this when the + // document has no focus, assuming that state has not changed + // + // another optimization is to not collect more state if we + // have a pending update already running which indicates + // that the connection is either slow or disconnected and + // thus unhealthy. + if (document.hasFocus() && !this.hasPendingUpdate) { + this.flush(); + } + + // repeat + this.periodicFlushScheduler.schedule(); + }); + } + + get hasPendingUpdate(): boolean { + return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate); + } + + flush(): void { + this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); + } + close(): void { // We explicitly do not close our DBs because writing data onBeforeUnload() // can result in unexpected results. Namely, it seems that - even though this @@ -167,6 +178,12 @@ export class BrowserStorageService extends Disposable implements IStorageService // get triggered in this phase. this.dispose(); } + + dispose(): void { + this.runWhenIdleDisposable = dispose(this.runWhenIdleDisposable); + + super.dispose(); + } } export class FileStorageDatabase extends Disposable implements IStorageDatabase { diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 0094786e95b..8a97b4be30c 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -102,6 +102,13 @@ export interface IStorageService { * Migrate the storage contents to another workspace. */ migrate(toWorkspace: IWorkspaceInitializationPayload): Promise; + + /** + * Allows to flush state, e.g. in cases where a shutdown is + * imminent. This will send out the onWillSaveState to ask + * everyone for latest state. + */ + flush(): void; } export const enum StorageScope { @@ -126,10 +133,11 @@ export class InMemoryStorageService extends Disposable implements IStorageServic _serviceBrand: undefined; - private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); - readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; + private readonly _onDidChangeStorage = this._register(new Emitter()); + readonly onDidChangeStorage = this._onDidChangeStorage.event; - readonly onWillSaveState = Event.None; + protected readonly _onWillSaveState = this._register(new Emitter()); + readonly onWillSaveState = this._onWillSaveState.event; private globalCache: Map = new Map(); private workspaceCache: Map = new Map(); @@ -215,6 +223,10 @@ export class InMemoryStorageService extends Disposable implements IStorageServic async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise { // not supported } + + flush(): void { + this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); + } } export async function logStorage(global: Map, workspace: Map, globalPath: string, workspacePath: string): Promise { diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index bdd7b454af1..db8c578e75b 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -16,6 +16,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { onUnexpectedError } from 'vs/base/common/errors'; import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; +import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; export class NativeStorageService extends Disposable implements IStorageService { @@ -38,6 +39,9 @@ export class NativeStorageService extends Disposable implements IStorageService private initializePromise: Promise | undefined; + private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 60000 /* every minute */)); + private runWhenIdleDisposable: IDisposable | undefined = undefined; + constructor( globalStorageDatabase: IStorageDatabase, @ILogService private readonly logService: ILogService, @@ -63,10 +67,17 @@ export class NativeStorageService extends Disposable implements IStorageService } private async doInitialize(payload: IWorkspaceInitializationPayload): Promise { + + // Init all storage locations await Promise.all([ this.initializeGlobalStorage(), this.initializeWorkspaceStorage(payload) ]); + + // On some OS we do not get enough time to persist state on shutdown (e.g. when + // Windows restarts after applying updates). In other cases, VSCode might crash, + // so we periodically save state to reduce the chance of loosing any state. + this.periodicFlushScheduler.schedule(); } private initializeGlobalStorage(): Promise { @@ -185,8 +196,32 @@ export class NativeStorageService extends Disposable implements IStorageService this.getStorage(scope).delete(key); } + private doFlushWhenIdle(): void { + + // Dispose any previous idle runner + this.runWhenIdleDisposable = dispose(this.runWhenIdleDisposable); + + // Run when idle + this.runWhenIdleDisposable = runWhenIdle(() => { + + // send event to collect state + this.flush(); + + // repeat + this.periodicFlushScheduler.schedule(); + }); + } + + flush(): void { + this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); + } + async close(): Promise { + // Stop periodic scheduler and idle runner as we now collect state normally + this.periodicFlushScheduler.dispose(); + this.runWhenIdleDisposable = dispose(this.runWhenIdleDisposable); + // Signal as event so that clients can still store data this._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index d3f45474cda..1996880cf6b 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -23,8 +23,6 @@ import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -const TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState'; - export interface IEditorConfiguration { editor: object; diffEditor: object; @@ -35,6 +33,9 @@ export interface IEditorConfiguration { * be subclassed and not instantiated. */ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { + + static readonly TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState'; + private editorControl: IEditor | undefined; private editorContainer: HTMLElement | undefined; private hasPendingConfigurationChange: boolean | undefined; @@ -53,7 +54,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { ) { super(id, telemetryService, themeService, storageService); - this.editorMemento = this.getEditorMemento(editorGroupService, TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100); + this.editorMemento = this.getEditorMemento(editorGroupService, BaseTextEditor.TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100); this._register(this.configurationService.onDidChangeConfiguration(e => { const resource = this.getResource(); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 5f1cbf50e79..f989b18197c 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -44,6 +44,7 @@ import { WorkbenchContextKeysHandler } from 'vs/workbench/browser/contextkeys'; import { coalesce } from 'vs/base/common/arrays'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { Layout } from 'vs/workbench/browser/layout'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class Workbench extends Layout { @@ -140,6 +141,7 @@ export class Workbench extends Layout { const lifecycleService = accessor.get(ILifecycleService); const storageService = accessor.get(IStorageService); const configurationService = accessor.get(IConfigurationService); + const hostService = accessor.get(IHostService); // Layout this.initLayout(accessor); @@ -151,7 +153,7 @@ export class Workbench extends Layout { this._register(instantiationService.createInstance(WorkbenchContextKeysHandler)); // Register Listeners - this.registerListeners(lifecycleService, storageService, configurationService); + this.registerListeners(lifecycleService, storageService, configurationService, hostService); // Render Workbench this.renderWorkbench(instantiationService, accessor.get(INotificationService) as NotificationService, storageService, configurationService); @@ -224,7 +226,8 @@ export class Workbench extends Layout { private registerListeners( lifecycleService: ILifecycleService, storageService: IStorageService, - configurationService: IConfigurationService + configurationService: IConfigurationService, + hostService: IHostService ): void { // Configuration changes @@ -248,6 +251,13 @@ export class Workbench extends Layout { this._onShutdown.fire(); this.dispose(); })); + + // In some environments we do not get enough time to persist state on shutdown. + // In other cases, VSCode might crash, so we periodically save state to reduce + // the chance of loosing any state. + // The window loosing focus is a good indication that the user has stopped working + // in that window so we pick that at a time to collect state. + this._register(hostService.onDidChangeFocus(focus => { if (!focus) { storageService.flush(); } })); } private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto' | undefined; diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 389d3a6c14d..5e7ff1184b5 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -283,6 +283,7 @@ export class ElectronWindow extends Disposable { })); } + // Detect minimize / maximize this._register(Event.any( Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), () => true), Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), () => false) From a6e81fb6863470e7b35f598cab1670ff6c080f15 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Tue, 17 Dec 2019 10:24:33 +0100 Subject: [PATCH 043/241] finished debug API for inline DAs; fixes #85544 --- src/vs/vscode.d.ts | 40 ++++++++++++++++++++++++++++++++++++- src/vs/vscode.proposed.d.ts | 40 +------------------------------------ 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index e7a7adcd9b0..9628b478c76 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -9578,7 +9578,45 @@ declare module 'vscode' { constructor(port: number, host?: string); } - export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer; + /** + * A debug adapter that implements the Debug Adapter Protocol can be registered with VS Code if it implements the DebugAdapter interface. + */ + export interface DebugAdapter extends Disposable { + + /** + * An event which fires after the debug adapter has sent a Debug Adapter Protocol message to VS Code. + * Messages can be requests, responses, or events. + */ + readonly onDidSendMessage: Event; + + /** + * Handle a Debug Adapter Protocol message. + * Messages can be requests, responses, or events. + * Results or errors are returned via onSendMessage events. + * @param message A Debug Adapter Protocol message + */ + handleMessage(message: DebugProtocolMessage): void; + } + + /** + * A DebugProtocolMessage is an opaque stand-in type for the [ProtocolMessage](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage) type defined in the Debug Adapter Protocol. + */ + export interface DebugProtocolMessage { + // Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage). + } + + /** + * A debug adapter descriptor for an inline implementation. + */ + export class DebugAdapterInlineImplementation { + + /** + * Create a descriptor for an inline implementation of a debug adapter. + */ + constructor(implementation: DebugAdapter); + } + + export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterInlineImplementation; export interface DebugAdapterDescriptorFactory { /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index b04aeb64807..0a84bf4642d 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -784,45 +784,7 @@ declare module 'vscode' { //#endregion - //#region André: debug API for inline debug adapters https://github.com/microsoft/vscode/issues/85544 - - /** - * A DebugProtocolMessage is an opaque stand-in type for the [ProtocolMessage](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage) type defined in the Debug Adapter Protocol. - */ - export interface DebugProtocolMessage { - // Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage). - } - - /** - * A debug adapter that implements the Debug Adapter Protocol can be registered with VS Code if it implements the DebugAdapter interface. - */ - export interface DebugAdapter extends Disposable { - - /** - * An event which fires after the debug adapter has sent a Debug Adapter Protocol message to VS Code. - * Messages can be requests, responses, or events. - */ - readonly onDidSendMessage: Event; - - /** - * Handle a Debug Adapter Protocol message. - * Messages can be requests, responses, or events. - * Results or errors are returned via onSendMessage events. - * @param message A Debug Adapter Protocol message - */ - handleMessage(message: DebugProtocolMessage): void; - } - - /** - * A debug adapter descriptor for an inline implementation. - */ - export class DebugAdapterInlineImplementation { - - /** - * Create a descriptor for an inline implementation of a debug adapter. - */ - constructor(implementation: DebugAdapter); - } + //#region Debug // deprecated From ba3cc35f51f8144b9b20a30f75d060173f4dfa87 Mon Sep 17 00:00:00 2001 From: Przemek Dziewa Date: Tue, 17 Dec 2019 10:48:40 +0100 Subject: [PATCH 044/241] add max-width to statusbar-item --- src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 3102b114c52..72a43706546 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -44,6 +44,7 @@ line-height: 22px; height: 100%; vertical-align: top; + max-width: 40vw; } .monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak { From b7df9960324abae4c8f992ea47e8af1c9a5b4634 Mon Sep 17 00:00:00 2001 From: Przemek Dziewa Date: Tue, 17 Dec 2019 10:50:03 +0100 Subject: [PATCH 045/241] handle long texts in statusbar-item anchor --- .../browser/parts/statusbar/media/statusbarpart.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 72a43706546..21e81d94e82 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -95,7 +95,10 @@ height: 100%; padding: 0 5px 0 5px; white-space: pre; /* gives some degree of styling */ - align-items: center + align-items: center; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } .monaco-workbench .part.statusbar > .items-container > .statusbar-item > a:hover { From 21ca27da48867b54e0ec64a9f6dc9a5352c0bf5d Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 17 Dec 2019 09:51:55 +0000 Subject: [PATCH 046/241] strictFunctionTypes for mainThreadTask and extHostTreeViews Part of #81574 --- .../workbench/api/browser/mainThreadTask.ts | 22 +++++++++++-------- .../api/extHostTreeViews.test.ts | 2 +- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 5e9120d8360..79ae97c0082 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -514,15 +514,19 @@ export class MainThreadTask implements MainThreadTaskShape { if (TaskHandleDTO.is(value)) { const workspaceFolder = this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder)); if (workspaceFolder) { - this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task) => { - this._taskService.run(task).then(undefined, reason => { - // eat the error, it has already been surfaced to the user and we don't care about it here - }); - const result: TaskExecutionDTO = { - id: value.id, - task: TaskDTO.from(task) - }; - resolve(result); + this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task | undefined) => { + if (!task) { + reject(new Error('Task not found')); + } else { + this._taskService.run(task).then(undefined, reason => { + // eat the error, it has already been surfaced to the user and we don't care about it here + }); + const result: TaskExecutionDTO = { + id: value.id, + task: TaskDTO.from(task) + }; + resolve(result); + } }, (_error) => { reject(new Error('Task not found')); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts index bc50a3dd00a..fb419d8e700 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts @@ -75,7 +75,7 @@ suite('ExtHostTreeView', function () { rpcProtocol, new NullLogService() ), new NullLogService()); - onDidChangeTreeNode = new Emitter<{ key: string }>(); + onDidChangeTreeNode = new Emitter<{ key: string } | undefined>(); onDidChangeTreeNodeWithId = new Emitter<{ key: string }>(); testObject.createTreeView('testNodeTreeProvider', { treeDataProvider: aNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription); testObject.createTreeView('testNodeWithIdTreeProvider', { treeDataProvider: aNodeWithIdTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription); From f908b410553eacc1bd2bd17945ac50eea0d1b36f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 17 Dec 2019 11:02:10 +0100 Subject: [PATCH 047/241] make ThemeIcon#ctor public, #84695 --- src/vs/vscode.d.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 9628b478c76..8fc48e1022d 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -790,7 +790,8 @@ declare module 'vscode' { } /** - * A reference to a named icon. Currently only [File](#ThemeIcon.File) and [Folder](#ThemeIcon.Folder) are supported. + * A reference to a named icon. Currently, [File](#ThemeIcon.File), [Folder](#ThemeIcon.Folder), + * and [codicons](https://microsoft.github.io/vscode-codicons/dist/codicon.html) are supported. * Using a theme icon is preferred over a custom icon as it gives theme authors the possibility to change the icons. */ export class ThemeIcon { @@ -804,7 +805,11 @@ declare module 'vscode' { */ static readonly Folder: ThemeIcon; - private constructor(id: string); + /** + * Creates a reference to a theme icon. + * @param id id of the icon. The avaiable icons are listed in https://microsoft.github.io/vscode-codicons/dist/codicon.html. + */ + constructor(id: string); } /** From 241d4048aabc31db0baed66bb3d58cf3210d981d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Dec 2019 11:08:17 +0100 Subject: [PATCH 048/241] Losing content of unsaved files in workspace (fix #87156) --- .../workbench/contrib/backup/common/backupModelTracker.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts index bbc393aff32..584291fb088 100644 --- a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts @@ -37,7 +37,7 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu this._register(this.textFileService.models.onModelDisposed(e => this.discardBackup(e))); // Listen for untitled model changes - this._register(this.untitledTextEditorService.onDidCreate(e => this.onUntitledModelChanged(e))); + this._register(this.untitledTextEditorService.onDidCreate(e => this.onUntitledModelCreated(e))); this._register(this.untitledTextEditorService.onDidChangeContent(e => this.onUntitledModelChanged(e))); this._register(this.untitledTextEditorService.onDidDisposeModel(e => this.discardBackup(e))); @@ -65,6 +65,12 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu } } + private onUntitledModelCreated(resource: Uri): void { + if (this.untitledTextEditorService.isDirty(resource)) { + this.untitledTextEditorService.loadOrCreate({ resource }).then(model => model.backup()); + } + } + private onUntitledModelChanged(resource: Uri): void { if (this.untitledTextEditorService.isDirty(resource)) { this.untitledTextEditorService.loadOrCreate({ resource }).then(model => model.backup()); From fd44b940efdbdefd87db30d8d919a746ebd77f58 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 17 Dec 2019 11:13:09 +0100 Subject: [PATCH 049/241] 'human readable' becomes 'human-readable' --- src/vs/vscode.d.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 8fc48e1022d..48b083a3337 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1579,17 +1579,17 @@ declare module 'vscode' { export interface QuickPickItem { /** - * A human readable string which is rendered prominent. + * A human-readable string which is rendered prominent. */ label: string; /** - * A human readable string which is rendered less prominent. + * A human-readable string which is rendered less prominent. */ description?: string; /** - * A human readable string which is rendered less prominent. + * A human-readable string which is rendered less prominent. */ detail?: string; @@ -1693,7 +1693,7 @@ declare module 'vscode' { canSelectMany?: boolean; /** - * A set of file filters that are used by the dialog. Each entry is a human readable label, + * A set of file filters that are used by the dialog. Each entry is a human-readable label, * like "TypeScript", and an array of extensions, e.g. * ```ts * { @@ -1720,7 +1720,7 @@ declare module 'vscode' { saveLabel?: string; /** - * A set of file filters that are used by the dialog. Each entry is a human readable label, + * A set of file filters that are used by the dialog. Each entry is a human-readable label, * like "TypeScript", and an array of extensions, e.g. * ```ts * { @@ -1815,7 +1815,7 @@ declare module 'vscode' { * to the user. * * @param value The current value of the input box. - * @return A human readable string which is presented as diagnostic message. + * @return A human-readable string which is presented as diagnostic message. * Return `undefined`, `null`, or the empty string when 'value' is valid. */ validateInput?(value: string): string | undefined | null | Thenable; @@ -2341,7 +2341,7 @@ declare module 'vscode' { } /** - * The MarkdownString represents human readable text that supports formatting via the + * The MarkdownString represents human-readable text that supports formatting via the * markdown syntax. Standard markdown is supported, also tables, but no embedded html. */ export class MarkdownString { @@ -2386,7 +2386,7 @@ declare module 'vscode' { } /** - * ~~MarkedString can be used to render human readable text. It is either a markdown string + * ~~MarkedString can be used to render human-readable text. It is either a markdown string * or a code-block that provides a language and a code snippet. Note that * markdown strings will be sanitized - that means html will be escaped.~~ * @@ -2683,7 +2683,7 @@ declare module 'vscode' { */ export interface DocumentSymbolProviderMetadata { /** - * A human readable string that is shown when multiple outlines trees show for one document. + * A human-readable string that is shown when multiple outlines trees show for one document. */ label?: string; } @@ -7374,7 +7374,7 @@ declare module 'vscode' { iconPath?: string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon; /** - * A human readable string which is rendered less prominent. + * A human-readable string which is rendered less prominent. * When `true`, it is derived from [resourceUri](#TreeItem.resourceUri) and when `falsy`, it is not shown. */ description?: string | boolean; From 9e0cffd85cfc385323e4702a981fa6f14a3a25cb Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 17 Dec 2019 11:45:59 +0100 Subject: [PATCH 050/241] no more proposed check for theme icons for menus, #84695 --- src/vs/workbench/api/common/menusExtensionPoint.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index df971f54fad..4801b91d288 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -351,11 +351,8 @@ commandsExtensionPoint.setHandler(extensions => { let absoluteIcon: { dark: URI; light?: URI; } | ThemeIcon | undefined; if (icon) { if (typeof icon === 'string') { - if (extension.description.enableProposedApi) { - absoluteIcon = ThemeIcon.fromString(icon) || { dark: resources.joinPath(extension.description.extensionLocation, icon) }; - } else { - absoluteIcon = { dark: resources.joinPath(extension.description.extensionLocation, icon) }; - } + absoluteIcon = ThemeIcon.fromString(icon) || { dark: resources.joinPath(extension.description.extensionLocation, icon) }; + } else { absoluteIcon = { dark: resources.joinPath(extension.description.extensionLocation, icon.dark), From 7a374c3d13676739c81586c64e3e37f2a338b68d Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 17 Dec 2019 11:58:28 +0000 Subject: [PATCH 051/241] Handle stat.type correctly in typescript features extension #85753 --- extensions/typescript-language-features/src/features/task.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/features/task.ts b/extensions/typescript-language-features/src/features/task.ts index 59044029b69..48f9ca27d4a 100644 --- a/extensions/typescript-language-features/src/features/task.ts +++ b/extensions/typescript-language-features/src/features/task.ts @@ -20,7 +20,8 @@ type AutoDetect = 'on' | 'off' | 'build' | 'watch'; const exists = async (resource: vscode.Uri): Promise => { try { const stat = await vscode.workspace.fs.stat(resource); - return stat.type === vscode.FileType.File; + // stat.type is an enum flag + return !!(stat.type & vscode.FileType.File); } catch { return false; } From d8974485a13a5990b51d02908f6ddc7fd8cf79a3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 Dec 2019 13:05:45 +0100 Subject: [PATCH 052/241] simplify api --- src/vs/vscode.d.ts | 193 ++++++++---------- .../api/common/extHostConfiguration.ts | 21 +- 2 files changed, 102 insertions(+), 112 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 968cc6c511f..a41f433703f 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -4234,16 +4234,52 @@ declare module 'vscode' { /** * Represents the configuration. It is a merged view of * - * - Default configuration - * - Global configuration - * - Workspace configuration (if available) - * - Workspace folder configuration of the requested resource (if available) + * - *Default Settings* + * - *Global (User) Settings* + * - *Workspace settings* + * - *Workspace Folder settings* - From one of the [Workspace Folders](#workspace.workspaceFolders) under which requested resource belongs to. + * - *Language settings* - Settings defined under requested language. * - * *Global configuration* comes from User Settings and overrides Defaults. + * The *effective* value (returned by [`get`](#WorkspaceConfiguration.get)) is computed by overriding or merging the values in the following order. * - * *Workspace configuration* comes from Workspace Settings and overrides Global configuration. + * ``` + * `defaultValue` + * `globalValue` (if defined) + * `workspaceValue` (if defined) + * `workspaceFolderValue` (if defined) + * `defaultLanguageValue` (if defined) + * `globalLanguageValue` (if defined) + * `workspaceLanguageValue` (if defined) + * `workspaceLanguageValue` (if defined) + * ``` + * **Note:** Only `object` value types are merged and all other value types are overridden. * - * *Workspace Folder configuration* comes from `.vscode` folder under one of the [workspace folders](#workspace.workspaceFolders) and overrides Workspace configuration. + * Example 1: Overriding + * + * ```ts + * defaultValue = 'on'; + * globalValue = 'relative' + * workspaceFolderValue = 'off' + * value = 'off' + * ``` + * + * Example 2: Language Values + * + * ```ts + * defaultValue = 'on'; + * globalValue = 'relative' + * workspaceFolderValue = 'off' + * globalLanguageValue = 'on' + * value = 'on' + * ``` + * + * Example 3: Object Values + * + * ```ts + * defaultValue = { "a": 1, "b": 2 }; + * globalValue = { "b": 3, "c": 4 }; + * value = { "a": 1, "b": 3, "c": 4 }; + * ``` * * *Note:* Workspace and Workspace Folder configurations contains `launch` and `tasks` settings. Their basename will be * part of the section identifier. The following snippets shows how to retrieve all configurations @@ -4251,7 +4287,7 @@ declare module 'vscode' { * * ```ts * // launch.json configuration - * const config = workspace.getConfiguration('launch', vscode.window.activeTextEditor.document.uri); + * const config = workspace.getConfiguration('launch', vscode.workspace.workspaceFolders[0].uri); * * // retrieve values * const values = config.get('configurations'); @@ -4289,13 +4325,8 @@ declare module 'vscode' { /** * Retrieve all information about a configuration setting. A configuration value * often consists of a *default* value, a global or installation-wide value, - * a workspace-specific value and a folder-specific value. - * - * The *effective* value (returned by [`get`](#WorkspaceConfiguration.get)) - * is computed like this: `defaultValue` overridden by `globalValue`, - * `globalValue` overridden by `workspaceValue`. `workspaceValue` overwridden by `workspaceFolderValue`. - * Refer to [Settings](https://code.visualstudio.com/docs/getstarted/settings) - * for more information. + * a workspace-specific value, folder-specific value + * and language-specific values (if [WorkspaceConfiguration](#WorkspaceConfiguration) is scoped to a language). * * *Note:* The configuration name must denote a leaf in the configuration tree * (`editor.fontSize` vs `editor`) otherwise no result is returned. @@ -4303,57 +4334,6 @@ declare module 'vscode' { * @param section Configuration name, supports _dotted_ names. * @return Information about a configuration setting or `undefined`. */ - inspect(section: string): { key: string; defaultValue?: T; globalValue?: T; workspaceValue?: T, workspaceFolderValue?: T } | undefined; - - /** - * Update a configuration value. The updated configuration values are persisted. - * - * A value can be changed in - * - * - [Global configuration](#ConfigurationTarget.Global): Changes the value for all instances of the editor. - * - [Workspace configuration](#ConfigurationTarget.Workspace): Changes the value for current workspace, if available. - * - [Workspace folder configuration](#ConfigurationTarget.WorkspaceFolder): Changes the value for the - * [Workspace folder](#workspace.workspaceFolders) to which the current [configuration](#WorkspaceConfiguration) is scoped to. - * - * *Note 1:* Setting a global value in the presence of a more specific workspace value - * has no observable effect in that workspace, but in others. Setting a workspace value - * in the presence of a more specific folder value has no observable effect for the resources - * under respective [folder](#workspace.workspaceFolders), but in others. Refer to - * [Settings Inheritance](https://code.visualstudio.com/docs/getstarted/settings) for more information. - * - * *Note 2:* To remove a configuration value use `undefined`, like so: `config.update('somekey', undefined)` - * - * Will throw error when - * - Writing a configuration which is not registered. - * - Writing a configuration to workspace or folder target when no workspace is opened - * - Writing a configuration to folder target when there is no folder settings - * - Writing to folder target without passing a resource when getting the configuration (`workspace.getConfiguration(section, resource)`) - * - Writing a window configuration to folder target - * - * @param section Configuration name, supports _dotted_ names. - * @param value The new value. - * @param configurationTarget The [configuration target](#ConfigurationTarget) or a boolean value. - * - If `true` configuration target is `ConfigurationTarget.Global`. - * - If `false` configuration target is `ConfigurationTarget.Workspace`. - * - If `undefined` or `null` configuration target is - * `ConfigurationTarget.WorkspaceFolder` when configuration is resource specific - * `ConfigurationTarget.Workspace` otherwise. - */ - update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean): Thenable; - - /** - * Readable dictionary that backs this configuration. - */ - readonly [key: string]: any; - } - - export interface WorkspaceFolderConfiguration extends WorkspaceConfiguration { - - update(section: string, value: any): Thenable; - } - - export interface TextDocumentConfiguration extends WorkspaceConfiguration { - inspect(section: string): { key: string; @@ -4369,7 +4349,41 @@ declare module 'vscode' { } | undefined; - update(section: string, value: any): Thenable; + /** + * Update a configuration value. The updated configuration values are persisted. + * + * A value can be changed in + * + * - [Global settings](#ConfigurationTarget.Global): Changes the value for all instances of the editor. + * - [Workspace settings](#ConfigurationTarget.Workspace): Changes the value for current workspace, if available. + * - [Workspace folder settings](#ConfigurationTarget.WorkspaceFolder): Changes the value for settings from one of the [Workspace Folders](#workspace.workspaceFolders) under which the requested resource belongs to. + * - Language settings: Changes the value for the requested languageId. + * + * *Note:* To remove a configuration value use `undefined`, like so: `config.update('somekey', undefined)` + * + * @param section Configuration name, supports _dotted_ names. + * @param value The new value. + * @param configurationTarget The [configuration target](#ConfigurationTarget) or a boolean value. + * - If `true` updates [Global settings](#ConfigurationTarget.Global). + * - If `false` updates [Workspace settings](#ConfigurationTarget.Workspace). + * - If `undefined` or `null` updates to [Workspace folder settings](#ConfigurationTarget.WorkspaceFolder) if configuration is resource specific, + * otherwise to [Workspace settings](#ConfigurationTarget.Workspace). + * @param scopeToLanguage Whether to update the value in the scope of requested languageId or not. + * - If `true` updates the value under the requested languageId. + * - If `undefined` updates the value under the requested languageId only if the configuration is defined for the language. + * @throws error while updating + * - configuration which is not registered. + * - window configuration to workspace folder + * - configuration to workspace or workspace folder when no workspace is opened. + * - configuration to workspace folder when there is no workspace folder settings. + * - configuration to workspace folder when [WorkspaceConfiguration](#WorkspaceConfiguration) is not scoped to a resource. + */ + update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean, scopeToLanguage?: boolean): Thenable; + + /** + * Readable dictionary that backs this configuration. + */ + readonly [key: string]: any; } /** @@ -8702,41 +8716,12 @@ declare module 'vscode' { * is returned. Dots in the section-identifier are interpreted as child-access, * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. * + * When a scope is provided configuraiton confined to that scope is returned. Scope can be a resource or a language identifier or both. + * * @param section A dot-separated identifier. * @return The full configuration or a subset. */ - export function getConfiguration(section?: string): WorkspaceConfiguration; - - /** - * Get a workspace folder configuration object for the given workspace folder. - * - * When a section-identifier is provided only that part of the configuration - * is returned. Dots in the section-identifier are interpreted as child-access, - * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. - * - * @param section A dot-separated identifier. - * @param workspaceFolder A workspace folder for which the configuration is asked for. - * @return The full configuration or a subset. - */ - export function getConfiguration(section: string | undefined, workspaceFolder: WorkspaceFolder): WorkspaceFolderConfiguration; - - /** - * Get a text document configuration object for the given text document. - * - * When a section-identifier is provided only that part of the configuration - * is returned. Dots in the section-identifier are interpreted as child-access, - * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. - * - * @param section A dot-separated identifier. - * @param textDocument A text document for which the configuration is asked for. - * @return The full configuration or a subset for the given text document - */ - export function getConfiguration(section: string | undefined, textDocument: TextDocument): TextDocumentConfiguration; - - /** - * @deprecated Instead use other variants of `getConfiguration` - */ - export function getConfiguration(section: string | undefined, resource: Uri | null): WorkspaceConfiguration; + export function getConfiguration(section?: string | undefined, scope?: ConfigurationScope | null): WorkspaceConfiguration; /** * An event that is emitted when the [configuration](#WorkspaceConfiguration) changed. @@ -8768,19 +8753,21 @@ declare module 'vscode' { export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider, options?: { readonly isCaseSensitive?: boolean, readonly isReadonly?: boolean }): Disposable; } + export type ConfigurationScope = Uri | { resource: Uri, languageId?: string } | { resource?: Uri, languageId: string }; + /** * An event describing the change in Configuration */ export interface ConfigurationChangeEvent { /** - * Returns `true` if the given section for the given resource (if provided) is affected. + * Returns `true` if the given section is affected in the provided scope. * * @param section Configuration name, supports _dotted_ names. - * @param resource A resource Uri. - * @return `true` if the given section for the given resource (if provided) is affected. + * @param scope A scope in which to check. + * @return `true` if the given section is affected in the provided scope. */ - affectsConfiguration(section: string, resource?: Uri): boolean; + affectsConfiguration(section: string, scope?: ConfigurationScope): boolean; } /** diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index e1052f6dbc8..d034cae44c7 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { mixin, deepClone } from 'vs/base/common/objects'; -import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import * as vscode from 'vscode'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; @@ -53,6 +52,15 @@ function isWorkspaceFolder(thing: any): thing is vscode.WorkspaceFolder { && (!thing.index || typeof thing.index === 'number'); } +function scopeToOverrides(scope: vscode.ConfigurationScope | undefined): IConfigurationOverrides | undefined { + return scope ? + scope instanceof vscode.Uri ? { resource: scope } + : isWorkspaceFolder(scope) ? { resource: scope.uri } + : isTextDocument(scope) ? { resource: scope.uri, overrideIdentifier: scope.languageId } + : scope + : undefined; +} + export class ExtHostConfiguration implements ExtHostConfigurationShape { readonly _serviceBrand: undefined; @@ -117,13 +125,8 @@ export class ExtHostConfigProvider { this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous)); } - getConfiguration(section?: string, scope?: vscode.Uri | vscode.WorkspaceFolder | vscode.TextDocument | null, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { - const overrides: IConfigurationOverrides = scope ? - scope instanceof vscode.Uri ? { resource: scope } - : isWorkspaceFolder(scope) ? { resource: scope.uri } - : isTextDocument(scope) ? { resource: scope.uri, overrideIdentifier: scope.languageId } - : scope - : {}; + getConfiguration(section?: string, scope?: vscode.ConfigurationScope, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { + const overrides = scopeToOverrides(scope) || {}; const config = this._toReadonlyValue(section ? lookUp(this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace), section) : this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace)); @@ -276,7 +279,7 @@ export class ExtHostConfigProvider { private _toConfigurationChangeEvent(change: IConfigurationChange, previous: { data: IConfigurationData, workspace: Workspace | undefined }): vscode.ConfigurationChangeEvent { const event = new ConfigurationChangeEvent(change, previous, this._configuration, this._extHostWorkspace.workspace); return Object.freeze({ - affectsConfiguration: (section: string, resource?: URI) => event.affectsConfiguration(section, resource ? { resource } : undefined) + affectsConfiguration: (section: string, scope?: vscode.ConfigurationScope) => event.affectsConfiguration(section, scopeToOverrides(scope)) }); } From 7d5cd0d249b04f9773f9aebba89c24711963044d Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 17 Dec 2019 13:05:46 +0100 Subject: [PATCH 053/241] Case-sensitivity and file system providers fixes #48258 --- .../browser/editors/fileEditorTracker.ts | 16 ++--- .../contrib/files/browser/fileActions.ts | 8 +-- .../files/browser/views/explorerViewer.ts | 10 ++-- .../contrib/files/common/explorerModel.ts | 41 +++++++------ .../contrib/files/common/explorerService.ts | 59 +++++++++++-------- .../workbench/contrib/files/common/files.ts | 1 + .../electron-browser/explorerModel.test.ts | 19 ++++-- 7 files changed, 92 insertions(+), 62 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index a2fd13708d6..a253fbaea92 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -5,7 +5,6 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { URI } from 'vs/base/common/uri'; -import * as resources from 'vs/base/common/resources'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor'; import { ITextFileService, TextFileModelChangeEvent, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; @@ -25,6 +24,8 @@ import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor import { timeout } from 'vs/base/common/async'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; export class FileEditorTracker extends Disposable implements IWorkbenchContribution { @@ -42,7 +43,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IHostService private readonly hostService: IHostService, - @ICodeEditorService private readonly codeEditorService: ICodeEditorService + @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @IExplorerService private readonly explorerService: IExplorerService ) { super(); @@ -101,13 +103,13 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // Update Editor if file (or any parent of the input) got renamed or moved const resource = editor.getResource(); - if (resources.isEqualOrParent(resource, oldResource)) { + if (isEqualOrParent(resource, oldResource)) { let reopenFileResource: URI; if (oldResource.toString() === resource.toString()) { reopenFileResource = newResource; // file got moved } else { - const index = this.getIndexOfPath(resource.path, oldResource.path, resources.hasToIgnoreCase(resource)); - reopenFileResource = resources.joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved + const index = this.getIndexOfPath(resource.path, oldResource.path, this.explorerService.shouldIgnoreCase(resource)); + reopenFileResource = joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved } let encoding: string | undefined = undefined; @@ -195,7 +197,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // Do NOT close any opened editor that matches the resource path (either equal or being parent) of the // resource we move to (movedTo). Otherwise we would close a resource that has been renamed to the same // path but different casing. - if (movedTo && resources.isEqualOrParent(resource, movedTo)) { + if (movedTo && isEqualOrParent(resource, movedTo)) { return; } @@ -203,7 +205,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut if (arg1 instanceof FileChangesEvent) { matches = arg1.contains(resource, FileChangeType.DELETED); } else { - matches = resources.isEqualOrParent(resource, arg1); + matches = isEqualOrParent(resource, arg1); } if (!matches) { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index d682f3b8d69..a2ae86d7408 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -328,12 +328,12 @@ function containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean } -export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean, allowOverwrite: boolean }, incrementalNaming: 'simple' | 'smart'): URI { +export function findValidPasteFileTarget(explorerService: IExplorerService, targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean, allowOverwrite: boolean }, incrementalNaming: 'simple' | 'smart'): URI { let name = resources.basenameOrAuthority(fileToPaste.resource); let candidate = resources.joinPath(targetFolder.resource, name); while (true && !fileToPaste.allowOverwrite) { - if (!targetFolder.root.find(candidate)) { + if (!explorerService.findClosest(candidate)) { break; } @@ -870,7 +870,7 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole throw new Error('Parent folder is readonly.'); } - const newStat = new NewExplorerItem(folder, isFolder); + const newStat = new NewExplorerItem(explorerService, folder, isFolder); await folder.fetchChildren(fileService, explorerService); folder.addChild(newStat); @@ -1049,7 +1049,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { } const incrementalNaming = configurationService.getValue().explorer.incrementalNaming; - const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming); + const targetFile = findValidPasteFileTarget(explorerService, target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming); // Move/Copy File if (pasteShouldMove) { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 96ad2636400..c28b54aaaba 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -20,7 +20,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IFilesConfiguration, IExplorerService } from 'vs/workbench/contrib/files/common/files'; -import { dirname, joinPath, isEqualOrParent, basename, hasToIgnoreCase, distinctParents } from 'vs/base/common/resources'; +import { dirname, joinPath, isEqualOrParent, basename, distinctParents } from 'vs/base/common/resources'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { localize } from 'vs/nls'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; @@ -95,7 +95,7 @@ export class ExplorerDataSource implements IAsyncDataSource { // Check for name collisions const targetNames = new Set(); if (targetStat.children) { - const ignoreCase = hasToIgnoreCase(target.resource); + const ignoreCase = this.explorerService.shouldIgnoreCase(target.resource); targetStat.children.forEach(child => { targetNames.add(ignoreCase ? child.name.toLowerCase() : child.name); }); @@ -929,7 +929,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Run add in sequence const addPromisesFactory: ITask>[] = []; await Promise.all(resources.map(async resource => { - if (targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase())) { + if (targetNames.has(this.explorerService.shouldIgnoreCase(resource) ? basename(resource).toLowerCase() : basename(resource))) { const confirmationResult = await this.dialogService.confirm(getFileOverwriteConfirm(basename(resource))); if (!confirmationResult.confirmed) { return; @@ -1031,7 +1031,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Reuse duplicate action if user copies if (isCopy) { const incrementalNaming = this.configurationService.getValue().explorer.incrementalNaming; - const stat = await this.textFileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)); + const stat = await this.textFileService.copy(source.resource, findValidPasteFileTarget(this.explorerService, target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)); if (!stat.isDirectory) { await this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); } diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index dd17f19dfb6..e7b364edb86 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -6,7 +6,6 @@ import { URI } from 'vs/base/common/uri'; import { isEqual } from 'vs/base/common/extpath'; import { posix } from 'vs/base/common/path'; -import * as resources from 'vs/base/common/resources'; import { ResourceMap } from 'vs/base/common/map'; import { IFileStat, IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { rtrim, startsWithIgnoreCase, startsWith, equalsIgnoreCase } from 'vs/base/common/strings'; @@ -16,6 +15,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { joinPath, isEqualOrParent, basenameOrAuthority } from 'vs/base/common/resources'; export class ExplorerModel implements IDisposable { @@ -23,9 +23,12 @@ export class ExplorerModel implements IDisposable { private _listener: IDisposable; private readonly _onDidChangeRoots = new Emitter(); - constructor(private readonly contextService: IWorkspaceContextService) { + constructor( + private readonly contextService: IWorkspaceContextService, + explorerService: IExplorerService + ) { const setRoots = () => this._roots = this.contextService.getWorkspace().folders - .map(folder => new ExplorerItem(folder.uri, undefined, true, false, false, folder.name)); + .map(folder => new ExplorerItem(folder.uri, explorerService, undefined, true, false, false, folder.name)); setRoots(); this._listener = this.contextService.onDidChangeWorkspaceFolders(() => { @@ -80,11 +83,12 @@ export class ExplorerItem { constructor( public resource: URI, + private readonly explorerService: IExplorerService, private _parent: ExplorerItem | undefined, private _isDirectory?: boolean, private _isSymbolicLink?: boolean, private _isReadonly?: boolean, - private _name: string = resources.basenameOrAuthority(resource), + private _name: string = basenameOrAuthority(resource), private _mtime?: number, ) { this._isDirectoryResolved = false; @@ -154,8 +158,8 @@ export class ExplorerItem { return this === this.root; } - static create(service: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { - const stat = new ExplorerItem(raw.resource, parent, raw.isDirectory, raw.isSymbolicLink, service.hasCapability(raw.resource, FileSystemProviderCapabilities.Readonly), raw.name, raw.mtime); + static create(explorerService: IExplorerService, fileService: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { + const stat = new ExplorerItem(raw.resource, explorerService, parent, raw.isDirectory, raw.isSymbolicLink, fileService.hasCapability(raw.resource, FileSystemProviderCapabilities.Readonly), raw.name, raw.mtime); // Recursively add children if present if (stat.isDirectory) { @@ -164,13 +168,13 @@ export class ExplorerItem { // the folder is fully resolved if either it has a list of children or the client requested this by using the resolveTo // array of resource path to resolve. stat._isDirectoryResolved = !!raw.children || (!!resolveTo && resolveTo.some((r) => { - return resources.isEqualOrParent(r, stat.resource); + return isEqualOrParent(r, stat.resource); })); // Recurse into children if (raw.children) { for (let i = 0, len = raw.children.length; i < len; i++) { - const child = ExplorerItem.create(service, raw.children[i], stat, resolveTo); + const child = ExplorerItem.create(explorerService, fileService, raw.children[i], stat, resolveTo); stat.addChild(child); } } @@ -262,7 +266,7 @@ export class ExplorerItem { const resolveMetadata = explorerService.sortOrder === 'modified'; try { const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); - const resolved = ExplorerItem.create(fileService, stat, this); + const resolved = ExplorerItem.create(explorerService, fileService, stat, this); ExplorerItem.mergeLocalWithDisk(resolved, this); } catch (e) { this.isError = true; @@ -302,7 +306,7 @@ export class ExplorerItem { } private getPlatformAwareName(name: string): string { - return (!name || !resources.hasToIgnoreCase(this.resource)) ? name : name.toLowerCase(); + return this.explorerService.shouldIgnoreCase(this.resource) ? name.toLowerCase() : name; } /** @@ -319,7 +323,7 @@ export class ExplorerItem { private updateResource(recursive: boolean): void { if (this._parent) { - this.resource = resources.joinPath(this._parent.resource, this.name); + this.resource = joinPath(this._parent.resource, this.name); } if (recursive) { @@ -352,16 +356,17 @@ export class ExplorerItem { find(resource: URI): ExplorerItem | null { // Return if path found // For performance reasons try to do the comparison as fast as possible + const ignoreCase = this.explorerService.shouldIgnoreCase(resource); if (resource && this.resource.scheme === resource.scheme && equalsIgnoreCase(this.resource.authority, resource.authority) && - (resources.hasToIgnoreCase(resource) ? startsWithIgnoreCase(resource.path, this.resource.path) : startsWith(resource.path, this.resource.path))) { - return this.findByPath(rtrim(resource.path, posix.sep), this.resource.path.length); + (ignoreCase ? startsWithIgnoreCase(resource.path, this.resource.path) : startsWith(resource.path, this.resource.path))) { + return this.findByPath(rtrim(resource.path, posix.sep), this.resource.path.length, ignoreCase); } return null; //Unable to find } - private findByPath(path: string, index: number): ExplorerItem | null { - if (isEqual(rtrim(this.resource.path, posix.sep), path, resources.hasToIgnoreCase(this.resource))) { + private findByPath(path: string, index: number, ignoreCase: boolean): ExplorerItem | null { + if (isEqual(rtrim(this.resource.path, posix.sep), path, ignoreCase)) { return this; } @@ -383,7 +388,7 @@ export class ExplorerItem { if (child) { // We found a child with the given name, search inside it - return child.findByPath(path, indexOfNextSep); + return child.findByPath(path, indexOfNextSep, ignoreCase); } } @@ -392,7 +397,7 @@ export class ExplorerItem { } export class NewExplorerItem extends ExplorerItem { - constructor(parent: ExplorerItem, isDirectory: boolean) { - super(URI.file(''), parent, isDirectory); + constructor(explorerService: IExplorerService, parent: ExplorerItem, isDirectory: boolean) { + super(URI.file(''), explorerService, parent, isDirectory); } } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 23311249367..d2eed899780 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -9,8 +9,8 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IExplorerService, IFilesConfiguration, SortOrder, SortOrderConfiguration, IContextProvider } from 'vs/workbench/contrib/files/common/files'; import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; -import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files'; -import { dirname } from 'vs/base/common/resources'; +import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; +import { dirname, hasToIgnoreCase } from 'vs/base/common/resources'; import { memoize } from 'vs/base/common/decorators'; import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -41,8 +41,9 @@ export class ExplorerService implements IExplorerService { private editable: { stat: ExplorerItem, data: IEditableData } | undefined; private _sortOrder: SortOrder; private cutItems: ExplorerItem[] | undefined; - private fileSystemProviderSchemes = new Set(); private contextProvider: IContextProvider | undefined; + private fileSystemProviderCaseSensitivity = new Map(); + private model: ExplorerModel; constructor( @IFileService private fileService: IFileService, @@ -53,6 +54,29 @@ export class ExplorerService implements IExplorerService { @IEditorService private editorService: IEditorService, ) { this._sortOrder = this.configurationService.getValue('explorer.sortOrder'); + + this.model = new ExplorerModel(this.contextService, this); + this.disposables.add(this.model); + this.disposables.add(this.fileService.onAfterOperation(e => this.onFileOperation(e))); + this.disposables.add(this.fileService.onFileChanges(e => this.onFileChanges(e))); + this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); + this.disposables.add(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { + const provider = e.provider; + if (e.added && provider) { + const alreadyRegistered = this.fileSystemProviderCaseSensitivity.has(e.scheme); + const readCapability = () => this.fileSystemProviderCaseSensitivity.set(e.scheme, !!(provider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive)); + readCapability(); + + if (alreadyRegistered) { + // A file system provider got re-registered, we should update all file stats since they might change (got read-only) + this.model.roots.forEach(r => r.forgetChildren()); + this._onDidChangeItem.fire({ recursive: true }); + } else { + this.disposables.add(provider.onDidChangeCapabilities(() => readCapability())); + } + } + })); + this.disposables.add(this.model.onDidChangeRoots(() => this._onDidChangeRoots.fire())); } get roots(): ExplorerItem[] { @@ -107,24 +131,13 @@ export class ExplorerService implements IExplorerService { return fileEventsFilter; } - @memoize get model(): ExplorerModel { - const model = new ExplorerModel(this.contextService); - this.disposables.add(model); - this.disposables.add(this.fileService.onAfterOperation(e => this.onFileOperation(e))); - this.disposables.add(this.fileService.onFileChanges(e => this.onFileChanges(e))); - this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); - this.disposables.add(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { - if (e.added && this.fileSystemProviderSchemes.has(e.scheme)) { - // A file system provider got re-registered, we should update all file stats since they might change (got read-only) - this.model.roots.forEach(r => r.forgetChildren()); - this._onDidChangeItem.fire({ recursive: true }); - } else { - this.fileSystemProviderSchemes.add(e.scheme); - } - })); - this.disposables.add(model.onDidChangeRoots(() => this._onDidChangeRoots.fire())); + shouldIgnoreCase(resource: URI): boolean { + const caseSensitive = this.fileSystemProviderCaseSensitivity.get(resource.scheme); + if (typeof caseSensitive === 'undefined') { + return hasToIgnoreCase(resource); + } - return model; + return !caseSensitive; } // IExplorerService methods @@ -187,7 +200,7 @@ export class ExplorerService implements IExplorerService { const stat = await this.fileService.resolve(rootUri, options); // Convert to model - const modelStat = ExplorerItem.create(this.fileService, stat, undefined, options.resolveTo); + const modelStat = ExplorerItem.create(this, this.fileService, stat, undefined, options.resolveTo); // Update Input with disk Stat ExplorerItem.mergeLocalWithDisk(modelStat, root); const item = root.find(resource); @@ -231,11 +244,11 @@ export class ExplorerService implements IExplorerService { const thenable: Promise = p.isDirectoryResolved ? Promise.resolve(undefined) : this.fileService.resolve(p.resource, { resolveMetadata }); thenable.then(stat => { if (stat) { - const modelStat = ExplorerItem.create(this.fileService, stat, p.parent); + const modelStat = ExplorerItem.create(this, this.fileService, stat, p.parent); ExplorerItem.mergeLocalWithDisk(modelStat, p); } - const childElement = ExplorerItem.create(this.fileService, addedElement, p.parent); + const childElement = ExplorerItem.create(this, this.fileService, addedElement, p.parent); // Make sure to remove any previous version of the file if any p.removeChild(childElement); p.addChild(childElement); diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index dea52047854..86a60a95d14 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -55,6 +55,7 @@ export interface IExplorerService { refresh(): void; setToCopy(stats: ExplorerItem[], cut: boolean): void; isCut(stat: ExplorerItem): boolean; + shouldIgnoreCase(resource: URI): boolean; /** * Selects and reveal the file element provided by the given resource if its found in the explorer. diff --git a/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts index b269b6eb84c..af77f1c87cd 100644 --- a/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts +++ b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts @@ -10,9 +10,18 @@ import { join } from 'vs/base/common/path'; import { validateFileName } from 'vs/workbench/contrib/files/browser/fileActions'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { toResource } from 'vs/base/test/common/utils'; +import { hasToIgnoreCase } from 'vs/base/common/resources'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; + +class MockExplorerService { + shouldIgnoreCase(resource: URI) { + return hasToIgnoreCase(resource); + } +} +const mockExplorerService = new MockExplorerService() as IExplorerService; function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { - return new ExplorerItem(toResource.call(this, path), undefined, isFolder, false, false, name, mtime); + return new ExplorerItem(toResource.call(this, path), mockExplorerService, undefined, isFolder, false, false, name, mtime); } suite('Files - View Model', function () { @@ -243,19 +252,19 @@ suite('Files - View Model', function () { }); test('Merge Local with Disk', function () { - const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), undefined, true, false, false, 'to', Date.now()); - const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), undefined, true, false, false, 'to', Date.now()); + const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), mockExplorerService, undefined, true, false, false, 'to', Date.now()); + const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), mockExplorerService, undefined, true, false, false, 'to', Date.now()); // Merge Properties ExplorerItem.mergeLocalWithDisk(merge2, merge1); assert.strictEqual(merge1.mtime, merge2.mtime); // Merge Child when isDirectoryResolved=false is a no-op - merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), undefined, true, false, false, 'foo.html', Date.now())); + merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), mockExplorerService, undefined, true, false, false, 'foo.html', Date.now())); ExplorerItem.mergeLocalWithDisk(merge2, merge1); // Merge Child with isDirectoryResolved=true - const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), undefined, true, false, false, 'foo.html', Date.now()); + const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), mockExplorerService, undefined, true, false, false, 'foo.html', Date.now()); merge2.removeChild(child); merge2.addChild(child); (merge2)._isDirectoryResolved = true; From f4fc810feaed71661d60b5e41e1ce0d39f3e6747 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 17 Dec 2019 13:48:28 +0100 Subject: [PATCH 054/241] more jsdoc, #85753 --- src/vs/vscode.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 48b083a3337..bd8edc7b91c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5716,7 +5716,7 @@ declare module 'vscode' { /** * Enumeration of file types. The types `File` and `Directory` can also be - * a symbolic links, in that use `FileType.File | FileType.SymbolicLink` and + * a symbolic links, in that case use `FileType.File | FileType.SymbolicLink` and * `FileType.Directory | FileType.SymbolicLink`. */ export enum FileType { @@ -5745,6 +5745,8 @@ declare module 'vscode' { /** * The type of the file, e.g. is a regular file, a directory, or symbolic link * to a file. + * + * *Note:* This value might be a bitmask, e.g. `FileType.File | FileType.SymbolicLink`. */ type: FileType; /** From 9146c53f3fb784bb67a32e36fa9369c3f392f7a5 Mon Sep 17 00:00:00 2001 From: Marvin Heilemann Date: Tue, 17 Dec 2019 15:00:27 +0100 Subject: [PATCH 055/241] Resolved some requested changes --- .../themes/browser/workbenchThemeService.ts | 99 ++++++++++--------- .../themes/common/workbenchThemeService.ts | 8 -- 2 files changed, 52 insertions(+), 55 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 59d2b9df791..40adde676cf 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations, DETECT_AS_SETTING, COLOR_THEME_DARK_SETTING, WINDOW_MATCH_PREFERS_COLOR_SCHEME, COLOR_THEME_LIGHT_SETTING, ColorScheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations, DETECT_AS_SETTING, COLOR_THEME_DARK_SETTING, COLOR_THEME_LIGHT_SETTING } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -14,7 +14,7 @@ import * as errors from 'vs/base/common/errors'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; -import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService'; +import { ITheme, Extensions as ThemingExtensions, IThemingRegistry, DARK, LIGHT } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -43,6 +43,12 @@ const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark+'; const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light+'; const DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE = true; +enum ColorScheme { + LIGHT = 'light', + DARK = 'dark', + NO_PREFERENCE = 'no-preference' +} + const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; const PERSISTED_ICON_THEME_STORAGE_KEY = 'iconThemeData'; @@ -75,8 +81,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private colorThemeStore: ColorThemeStore; private currentColorTheme: ColorThemeData; - private autoSwitchColorTheme = DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE; - private autoSwitchColorThemeListener: MediaQueryList | undefined = undefined; + private autoSwitchColorTheme: MediaQueryList | undefined; private container: HTMLElement; private readonly onColorThemeChange: Emitter; private watchedColorThemeLocation: URI | undefined; @@ -259,7 +264,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); let detectThemeAutoSwitch = this.configurationService.getValue(DETECT_AS_SETTING); - this.autoSwitchColorTheme = detectThemeAutoSwitch; if (detectThemeAutoSwitch) { colorThemeSetting = this.getPreferredTheme(); } @@ -277,7 +281,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } return Promise.all([ - this.getColorThemeData(colorThemeSetting).then(theme => { + this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { return this.colorThemeStore.findThemeDataByParentLocation(uri).then(devThemes => { if (devThemes.length) { return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); @@ -300,28 +304,32 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private installColorThemeSwitch() { console.log('INSTALL'); - this.autoSwitchColorThemeListener = window.matchMedia(WINDOW_MATCH_PREFERS_COLOR_SCHEME); - this.autoSwitchColorThemeListener.addListener(this.onPreferColorSchemeChange); + this.autoSwitchColorTheme = window.matchMedia('(prefers-color-scheme: dark)'); + this.autoSwitchColorTheme.addListener(this.onPreferColorSchemeChange); console.log('INSTALLED'); } private deinstallColorThemeSwitch() { console.log('DEINSTALL'); - if (this.autoSwitchColorThemeListener) { - this.autoSwitchColorThemeListener.removeListener(this.onPreferColorSchemeChange); + if (this.autoSwitchColorTheme) { + this.autoSwitchColorTheme.removeListener(this.onPreferColorSchemeChange); this.configurationService.updateValue(DETECT_AS_SETTING, false); console.log('DEINSTALLED'); } } - private onPreferColorSchemeChange({ matches }: MediaQueryListEvent) { + private onPreferColorSchemeChange({ matches }: MediaQueryListEvent) { console.log('onPreferColorSchemeChange', matches); - let themeName = this.configurationService.getValue(COLOR_THEME_LIGHT_SETTING); + let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_LIGHT_SETTING); if (matches) { // prefers dark mode - themeName = this.configurationService.getValue(COLOR_THEME_DARK_SETTING); + colorThemeSetting = this.configurationService.getValue(COLOR_THEME_DARK_SETTING); } - this.setTheme(themeName); + this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { + if (theme) { + this.setColorTheme(theme.id, undefined); + } + }); } private installConfigurationListener() { @@ -329,23 +337,33 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (e.affectsConfiguration(COLOR_THEME_SETTING)) { let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.setTheme(colorThemeSetting); - this.deinstallColorThemeSwitch(); + this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { + if (theme) { + if (this.autoSwitchColorTheme && theme.type === DARK) { + this.configurationService.updateValue(COLOR_THEME_DARK_SETTING, theme.settingsId); + } + if (this.autoSwitchColorTheme && theme.type === LIGHT) { + this.configurationService.updateValue(COLOR_THEME_LIGHT_SETTING, theme.settingsId); + } + this.setColorTheme(theme.id, undefined); + } + }); } } if (e.affectsConfiguration(DETECT_AS_SETTING)) { let autoSwitchColorTheme = this.configurationService.getValue(DETECT_AS_SETTING); - console.log(autoSwitchColorTheme); - if (this.autoSwitchColorTheme !== autoSwitchColorTheme) { - this.autoSwitchColorTheme = autoSwitchColorTheme; - console.log('HAS CHANGED'); - if (autoSwitchColorTheme) { - this.installColorThemeSwitch(); - let themeName = this.getPreferredTheme(); - this.setTheme(themeName); - } else { - this.deinstallColorThemeSwitch(); + if (autoSwitchColorTheme) { + this.installColorThemeSwitch(); + let colorThemeSetting = this.getPreferredTheme(); + if (colorThemeSetting !== this.currentColorTheme.settingsId) { + this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { + if (theme) { + this.setColorTheme(theme.id, undefined); + } + }); } + } else { + this.deinstallColorThemeSwitch(); } } if (e.affectsConfiguration(ICON_THEME_SETTING)) { @@ -398,14 +416,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return ColorScheme.LIGHT; } - public getColorThemeData(colorThemeSetting: string): Promise { - return new Promise(resolve => { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { - resolve(theme); - }); - }); - } - public getColorTheme(): IColorTheme { return this.currentColorTheme; } @@ -418,15 +428,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.getColorTheme(); } - public setTheme(themeName: string) { - this.getColorThemeData(themeName).then(theme => { - if (theme) { - this.setColorTheme(theme.id, undefined); - this.configurationService.updateValue(COLOR_THEME_SETTING, themeName); - } - }); - } - public setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { if (!themeId) { return Promise.resolve(null); @@ -437,11 +438,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { themeId = validateThemeId(themeId); // migrate theme ids - return this.colorThemeStore.findThemeData(themeId, DEFAULT_THEME_ID).then(data => { - if (!data) { + return this.colorThemeStore.findThemeData(themeId, DEFAULT_THEME_ID).then(themeData => { + if (!themeData) { return null; } - const themeData = data; + this.configurationService.updateValue(COLOR_THEME_SETTING, themeData.settingsId); return themeData.ensureLoaded(this.extensionResourceLoaderService).then(_ => { if (themeId === this.currentColorTheme.id && !this.currentColorTheme.isLoaded && this.currentColorTheme.hasEqualData(themeData)) { this.currentColorTheme.clearCaches(); @@ -475,7 +476,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { public restoreColorTheme() { let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.setTheme(colorThemeSetting); + this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { + if (theme) { + this.setColorTheme(theme.id, undefined); + } + }); } } diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index faa27f7966d..c498b7d1ac2 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -21,19 +21,12 @@ export const COLOR_THEME_SETTING = 'workbench.colorTheme'; export const COLOR_THEME_DARK_SETTING = 'workbench.colorThemeDark'; export const COLOR_THEME_LIGHT_SETTING = 'workbench.colorThemeLight'; export const DETECT_AS_SETTING = 'workbench.colorThemeAutoSwitch'; -export const WINDOW_MATCH_PREFERS_COLOR_SCHEME = '(prefers-color-scheme: dark)'; export const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; export const ICON_THEME_SETTING = 'workbench.iconTheme'; export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations'; export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations'; export const CUSTOM_EDITOR_TOKENSTYLES_SETTING = 'editor.tokenColorCustomizationsExperimental'; -export enum ColorScheme { - LIGHT = 'light', - DARK = 'dark', - NO_PREFERENCE = 'no-preference' -} - export interface IColorTheme extends ITheme { readonly id: string; readonly label: string; @@ -66,7 +59,6 @@ export interface IWorkbenchThemeService extends IThemeService { setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined): Promise; getColorTheme(): IColorTheme; getColorThemes(): Promise; - getColorThemeData(colorThemeSetting: string): Promise; onDidColorThemeChange: Event; restoreColorTheme(): void; From 68f3322743d98850a5c3c8d2967246bb60a5b616 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 Dec 2019 15:10:48 +0100 Subject: [PATCH 056/241] strict resource language scope --- src/vs/vscode.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index a41f433703f..4e27d6be93b 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8753,7 +8753,7 @@ declare module 'vscode' { export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider, options?: { readonly isCaseSensitive?: boolean, readonly isReadonly?: boolean }): Disposable; } - export type ConfigurationScope = Uri | { resource: Uri, languageId?: string } | { resource?: Uri, languageId: string }; + export type ConfigurationScope = Uri | TextDocument | WorkspaceFolder | { resource: Uri, languageId: string }; /** * An event describing the change in Configuration From 48f20cbdaa497a4b91212418a7703c0dd0b405cb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 Dec 2019 15:35:28 +0100 Subject: [PATCH 057/241] move to proposed api --- src/vs/vscode.proposed.d.ts | 194 ++++++++++++++++++ .../api/browser/mainThreadConfiguration.ts | 55 ++--- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 4 +- .../api/common/extHostConfiguration.ts | 12 +- .../api/mainThreadConfiguration.test.ts | 38 ++-- 6 files changed, 241 insertions(+), 64 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index b04aeb64807..70a44149556 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1344,4 +1344,198 @@ declare module 'vscode' { } //#endregion + + //#region Language specific settings: https://github.com/microsoft/vscode/issues/26707 + + export type ConfigurationScope = Uri | TextDocument | WorkspaceFolder | { resource: Uri, languageId: string }; + + /** + * An event describing the change in Configuration + */ + export interface ConfigurationChangeEvent { + + /** + * Returns `true` if the given section is affected in the provided scope. + * + * @param section Configuration name, supports _dotted_ names. + * @param scope A scope in which to check. + * @return `true` if the given section is affected in the provided scope. + */ + affectsConfiguration(section: string, scope?: ConfigurationScope): boolean; + } + + export namespace workspace { + + /** + * Get a workspace configuration object. + * + * When a section-identifier is provided only that part of the configuration + * is returned. Dots in the section-identifier are interpreted as child-access, + * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. + * + * When a scope is provided configuraiton confined to that scope is returned. Scope can be a resource or a language identifier or both. + * + * @param section A dot-separated identifier. + * @return The full configuration or a subset. + */ + export function getConfiguration(section?: string | undefined, scope?: ConfigurationScope | null): WorkspaceConfiguration; + + } + + /** + * Represents the configuration. It is a merged view of + * + * - *Default Settings* + * - *Global (User) Settings* + * - *Workspace settings* + * - *Workspace Folder settings* - From one of the [Workspace Folders](#workspace.workspaceFolders) under which requested resource belongs to. + * - *Language settings* - Settings defined under requested language. + * + * The *effective* value (returned by [`get`](#WorkspaceConfiguration.get)) is computed by overriding or merging the values in the following order. + * + * ``` + * `defaultValue` + * `globalValue` (if defined) + * `workspaceValue` (if defined) + * `workspaceFolderValue` (if defined) + * `defaultLanguageValue` (if defined) + * `globalLanguageValue` (if defined) + * `workspaceLanguageValue` (if defined) + * `workspaceLanguageValue` (if defined) + * ``` + * **Note:** Only `object` value types are merged and all other value types are overridden. + * + * Example 1: Overriding + * + * ```ts + * defaultValue = 'on'; + * globalValue = 'relative' + * workspaceFolderValue = 'off' + * value = 'off' + * ``` + * + * Example 2: Language Values + * + * ```ts + * defaultValue = 'on'; + * globalValue = 'relative' + * workspaceFolderValue = 'off' + * globalLanguageValue = 'on' + * value = 'on' + * ``` + * + * Example 3: Object Values + * + * ```ts + * defaultValue = { "a": 1, "b": 2 }; + * globalValue = { "b": 3, "c": 4 }; + * value = { "a": 1, "b": 3, "c": 4 }; + * ``` + * + * *Note:* Workspace and Workspace Folder configurations contains `launch` and `tasks` settings. Their basename will be + * part of the section identifier. The following snippets shows how to retrieve all configurations + * from `launch.json`: + * + * ```ts + * // launch.json configuration + * const config = workspace.getConfiguration('launch', vscode.workspace.workspaceFolders[0].uri); + * + * // retrieve values + * const values = config.get('configurations'); + * ``` + * + * Refer to [Settings](https://code.visualstudio.com/docs/getstarted/settings) for more information. + */ + export interface WorkspaceConfiguration { + + /** + * Return a value from this configuration. + * + * @param section Configuration name, supports _dotted_ names. + * @return The value `section` denotes or `undefined`. + */ + get(section: string): T | undefined; + + /** + * Return a value from this configuration. + * + * @param section Configuration name, supports _dotted_ names. + * @param defaultValue A value should be returned when no value could be found, is `undefined`. + * @return The value `section` denotes or the default. + */ + get(section: string, defaultValue: T): T; + + /** + * Check if this configuration has a certain value. + * + * @param section Configuration name, supports _dotted_ names. + * @return `true` if the section doesn't resolve to `undefined`. + */ + has(section: string): boolean; + + /** + * Retrieve all information about a configuration setting. A configuration value + * often consists of a *default* value, a global or installation-wide value, + * a workspace-specific value, folder-specific value + * and language-specific values (if [WorkspaceConfiguration](#WorkspaceConfiguration) is scoped to a language). + * + * *Note:* The configuration name must denote a leaf in the configuration tree + * (`editor.fontSize` vs `editor`) otherwise no result is returned. + * + * @param section Configuration name, supports _dotted_ names. + * @return Information about a configuration setting or `undefined`. + */ + inspect(section: string): { + key: string; + + defaultValue?: T; + globalValue?: T; + workspaceValue?: T, + workspaceFolderValue?: T, + + defaultLanguageValue?: T; + userLanguageValue?: T; + workspaceLanguageValue?: T; + workspaceFolderLanguageValue?: T; + + } | undefined; + + /** + * Update a configuration value. The updated configuration values are persisted. + * + * A value can be changed in + * + * - [Global settings](#ConfigurationTarget.Global): Changes the value for all instances of the editor. + * - [Workspace settings](#ConfigurationTarget.Workspace): Changes the value for current workspace, if available. + * - [Workspace folder settings](#ConfigurationTarget.WorkspaceFolder): Changes the value for settings from one of the [Workspace Folders](#workspace.workspaceFolders) under which the requested resource belongs to. + * - Language settings: Changes the value for the requested languageId. + * + * *Note:* To remove a configuration value use `undefined`, like so: `config.update('somekey', undefined)` + * + * @param section Configuration name, supports _dotted_ names. + * @param value The new value. + * @param configurationTarget The [configuration target](#ConfigurationTarget) or a boolean value. + * - If `true` updates [Global settings](#ConfigurationTarget.Global). + * - If `false` updates [Workspace settings](#ConfigurationTarget.Workspace). + * - If `undefined` or `null` updates to [Workspace folder settings](#ConfigurationTarget.WorkspaceFolder) if configuration is resource specific, + * otherwise to [Workspace settings](#ConfigurationTarget.Workspace). + * @param scopeToLanguage Whether to update the value in the scope of requested languageId or not. + * - If `true` updates the value under the requested languageId. + * - If `undefined` updates the value under the requested languageId only if the configuration is defined for the language. + * @throws error while updating + * - configuration which is not registered. + * - window configuration to workspace folder + * - configuration to workspace or workspace folder when no workspace is opened. + * - configuration to workspace folder when there is no workspace folder settings. + * - configuration to workspace folder when [WorkspaceConfiguration](#WorkspaceConfiguration) is not scoped to a resource. + */ + update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean, scopeToLanguage?: boolean): Thenable; + + /** + * Readable dictionary that backs this configuration. + */ + readonly [key: string]: any; + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadConfiguration.ts b/src/vs/workbench/api/browser/mainThreadConfiguration.ts index e86e7c09263..21397e56218 100644 --- a/src/vs/workbench/api/browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/browser/mainThreadConfiguration.ts @@ -10,7 +10,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext, IConfigurationInitData } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ConfigurationTarget, IConfigurationService, IConfigurationOverrides, IConfigurationValue } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @extHostNamedCustomer(MainContext.MainThreadConfiguration) @@ -45,59 +45,42 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { this._configurationListener.dispose(); } - $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined): Promise { + $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise { overrides = { resource: overrides?.resource ? URI.revive(overrides.resource) : undefined, overrideIdentifier: overrides?.overrideIdentifier }; - return this.writeConfiguration(target, key, value, overrides); + return this.writeConfiguration(target, key, value, overrides, scopeToLanguage); } - $removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined): Promise { + $removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise { overrides = { resource: overrides?.resource ? URI.revive(overrides.resource) : undefined, overrideIdentifier: overrides?.overrideIdentifier }; - return this.writeConfiguration(target, key, undefined, overrides); + return this.writeConfiguration(target, key, undefined, overrides, scopeToLanguage); } - private writeConfiguration(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides): Promise { + private writeConfiguration(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides, scopeToLanguage: boolean | undefined): Promise { + target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, overrides); const configurationValue = this.configurationService.inspect(key, overrides); - target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, configurationValue, overrides); switch (target) { case ConfigurationTarget.MEMORY: - return this._updateValue(key, value, target, configurationValue.memory?.override, overrides); + return this._updateValue(key, value, target, configurationValue.memory?.override, overrides, scopeToLanguage); case ConfigurationTarget.WORKSPACE_FOLDER: - return this._updateValue(key, value, target, configurationValue.workspaceFolder?.override, overrides); + return this._updateValue(key, value, target, configurationValue.workspaceFolder?.override, overrides, scopeToLanguage); case ConfigurationTarget.WORKSPACE: - return this._updateValue(key, value, target, configurationValue.workspace?.override, overrides); + return this._updateValue(key, value, target, configurationValue.workspace?.override, overrides, scopeToLanguage); case ConfigurationTarget.USER_REMOTE: - return this._updateValue(key, value, target, configurationValue.userRemote?.override, overrides); + return this._updateValue(key, value, target, configurationValue.userRemote?.override, overrides, scopeToLanguage); default: - return this._updateValue(key, value, target, configurationValue.userLocal?.override, overrides); + return this._updateValue(key, value, target, configurationValue.userLocal?.override, overrides, scopeToLanguage); } } - private _updateValue(key: string, value: any, configurationTarget: ConfigurationTarget, overriddenValue: any | undefined, overrides: IConfigurationOverrides): Promise { - if (overrides.overrideIdentifier && overriddenValue !== undefined) { - return this.configurationService.updateValue(key, value, overrides, configurationTarget); - } else { - return this.configurationService.updateValue(key, value, { resource: overrides.resource }, configurationTarget); - } + private _updateValue(key: string, value: any, configurationTarget: ConfigurationTarget, overriddenValue: any | undefined, overrides: IConfigurationOverrides, scopeToLanguage: boolean | undefined): Promise { + overrides = scopeToLanguage === true ? overrides + : scopeToLanguage === false ? { resource: overrides.resource } + : overrides.overrideIdentifier && overriddenValue !== undefined ? overrides + : { resource: overrides.resource }; + return this.configurationService.updateValue(key, value, overrides, configurationTarget); } - private deriveConfigurationTarget(key: string, configurationValue: IConfigurationValue, overrides: IConfigurationOverrides): ConfigurationTarget { - if (overrides.overrideIdentifier) { - if (configurationValue.memory?.override !== undefined) { - return ConfigurationTarget.MEMORY; - } - if (configurationValue.workspaceFolder?.override !== undefined) { - return ConfigurationTarget.WORKSPACE_FOLDER; - } - if (configurationValue.workspace?.override !== undefined) { - return ConfigurationTarget.WORKSPACE; - } - if (configurationValue.userRemote?.override !== undefined) { - return ConfigurationTarget.USER_REMOTE; - } - if (configurationValue.userLocal?.override !== undefined) { - return ConfigurationTarget.USER_LOCAL; - } - } + private deriveConfigurationTarget(key: string, overrides: IConfigurationOverrides): ConfigurationTarget { if (overrides.resource && this._workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (configurationProperties[key] && (configurationProperties[key].scope === ConfigurationScope.RESOURCE || configurationProperties[key].scope === ConfigurationScope.RESOURCE_LANGUAGE)) { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b9f7256bbb6..b0466c7c142 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -664,7 +664,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onDidChangeConfiguration: (listener: (_: any) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) => { return configProvider.onDidChangeConfiguration(listener, thisArgs, disposables); }, - getConfiguration(section?: string, scope?: vscode.Uri | vscode.WorkspaceFolder | vscode.TextDocument | null): vscode.WorkspaceConfiguration { + getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null): vscode.WorkspaceConfiguration { scope = arguments.length === 1 ? undefined : scope; return configProvider.getConfiguration(section, scope, extension.identifier); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 23ced452855..c2d49e8fa42 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -147,8 +147,8 @@ export interface MainThreadCommentsShape extends IDisposable { } export interface MainThreadConfigurationShape extends IDisposable { - $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined): Promise; - $removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined): Promise; + $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise; + $removeConfigurationOption(target: ConfigurationTarget | null, key: string, overrides: IConfigurationOverrides | undefined, scopeToLanguage: boolean | undefined): Promise; } export interface MainThreadDiagnosticsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index d034cae44c7..8206164e947 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -52,7 +52,7 @@ function isWorkspaceFolder(thing: any): thing is vscode.WorkspaceFolder { && (!thing.index || typeof thing.index === 'number'); } -function scopeToOverrides(scope: vscode.ConfigurationScope | undefined): IConfigurationOverrides | undefined { +function scopeToOverrides(scope: vscode.ConfigurationScope | undefined | null): IConfigurationOverrides | undefined { return scope ? scope instanceof vscode.Uri ? { resource: scope } : isWorkspaceFolder(scope) ? { resource: scope.uri } @@ -125,7 +125,7 @@ export class ExtHostConfigProvider { this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous)); } - getConfiguration(section?: string, scope?: vscode.ConfigurationScope, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { + getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { const overrides = scopeToOverrides(scope) || {}; const config = this._toReadonlyValue(section ? lookUp(this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace), section) @@ -211,13 +211,13 @@ export class ExtHostConfigProvider { } return result; }, - update: (key: string, value: any, arg: ExtHostConfigurationTarget | boolean) => { + update: (key: string, value: any, extHostConfigurationTarget: ExtHostConfigurationTarget | boolean, scopeToLanguage?: boolean) => { key = section ? `${section}.${key}` : key; - const target = parseConfigurationTarget(arg); + const target = parseConfigurationTarget(extHostConfigurationTarget); if (value !== undefined) { - return this._proxy.$updateConfigurationOption(target, key, value, overrides); + return this._proxy.$updateConfigurationOption(target, key, value, overrides, scopeToLanguage); } else { - return this._proxy.$removeConfigurationOption(target, key, overrides); + return this._proxy.$removeConfigurationOption(target, key, overrides, scopeToLanguage); } }, inspect: (key: string): ConfigurationInspect | undefined => { diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts index db3c46a30a4..db13edd9507 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts @@ -63,7 +63,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', undefined); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -72,7 +72,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', { resource: URI.file('abc') }); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -81,7 +81,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', undefined); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -90,7 +90,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', undefined); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -99,7 +99,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -108,7 +108,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -117,7 +117,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', undefined); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -126,7 +126,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', { resource: URI.file('abc') }); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]); }); @@ -135,7 +135,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(ConfigurationTarget.USER, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }); + testObject.$updateConfigurationOption(ConfigurationTarget.USER, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.USER, target.args[0][3]); }); @@ -144,7 +144,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }); + testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -153,7 +153,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE_FOLDER, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }); + testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE_FOLDER, 'extHostConfiguration.window', 'value', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]); }); @@ -162,7 +162,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', undefined); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -171,7 +171,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', { resource: URI.file('abc') }); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -180,7 +180,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', undefined); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -189,7 +189,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', undefined); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -198,7 +198,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', { resource: URI.file('abc') }); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -207,7 +207,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', { resource: URI.file('abc') }); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -216,7 +216,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', undefined); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', undefined, undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -225,7 +225,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', { resource: URI.file('abc') }); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', { resource: URI.file('abc') }, undefined); assert.equal(ConfigurationTarget.WORKSPACE_FOLDER, target.args[0][3]); }); From 2ae386ecd536956c8a17507ef7b10eae113ab914 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 Dec 2019 15:36:49 +0100 Subject: [PATCH 058/241] reset vscode.d.ts --- src/vs/vscode.d.ts | 209 +++++++++++++++++++++++---------------------- 1 file changed, 107 insertions(+), 102 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 4e27d6be93b..bd8edc7b91c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -790,7 +790,8 @@ declare module 'vscode' { } /** - * A reference to a named icon. Currently only [File](#ThemeIcon.File) and [Folder](#ThemeIcon.Folder) are supported. + * A reference to a named icon. Currently, [File](#ThemeIcon.File), [Folder](#ThemeIcon.Folder), + * and [codicons](https://microsoft.github.io/vscode-codicons/dist/codicon.html) are supported. * Using a theme icon is preferred over a custom icon as it gives theme authors the possibility to change the icons. */ export class ThemeIcon { @@ -804,7 +805,11 @@ declare module 'vscode' { */ static readonly Folder: ThemeIcon; - private constructor(id: string); + /** + * Creates a reference to a theme icon. + * @param id id of the icon. The avaiable icons are listed in https://microsoft.github.io/vscode-codicons/dist/codicon.html. + */ + constructor(id: string); } /** @@ -1574,17 +1579,17 @@ declare module 'vscode' { export interface QuickPickItem { /** - * A human readable string which is rendered prominent. + * A human-readable string which is rendered prominent. */ label: string; /** - * A human readable string which is rendered less prominent. + * A human-readable string which is rendered less prominent. */ description?: string; /** - * A human readable string which is rendered less prominent. + * A human-readable string which is rendered less prominent. */ detail?: string; @@ -1688,7 +1693,7 @@ declare module 'vscode' { canSelectMany?: boolean; /** - * A set of file filters that are used by the dialog. Each entry is a human readable label, + * A set of file filters that are used by the dialog. Each entry is a human-readable label, * like "TypeScript", and an array of extensions, e.g. * ```ts * { @@ -1715,7 +1720,7 @@ declare module 'vscode' { saveLabel?: string; /** - * A set of file filters that are used by the dialog. Each entry is a human readable label, + * A set of file filters that are used by the dialog. Each entry is a human-readable label, * like "TypeScript", and an array of extensions, e.g. * ```ts * { @@ -1810,7 +1815,7 @@ declare module 'vscode' { * to the user. * * @param value The current value of the input box. - * @return A human readable string which is presented as diagnostic message. + * @return A human-readable string which is presented as diagnostic message. * Return `undefined`, `null`, or the empty string when 'value' is valid. */ validateInput?(value: string): string | undefined | null | Thenable; @@ -2336,7 +2341,7 @@ declare module 'vscode' { } /** - * The MarkdownString represents human readable text that supports formatting via the + * The MarkdownString represents human-readable text that supports formatting via the * markdown syntax. Standard markdown is supported, also tables, but no embedded html. */ export class MarkdownString { @@ -2381,7 +2386,7 @@ declare module 'vscode' { } /** - * ~~MarkedString can be used to render human readable text. It is either a markdown string + * ~~MarkedString can be used to render human-readable text. It is either a markdown string * or a code-block that provides a language and a code snippet. Note that * markdown strings will be sanitized - that means html will be escaped.~~ * @@ -2678,7 +2683,7 @@ declare module 'vscode' { */ export interface DocumentSymbolProviderMetadata { /** - * A human readable string that is shown when multiple outlines trees show for one document. + * A human-readable string that is shown when multiple outlines trees show for one document. */ label?: string; } @@ -4009,7 +4014,7 @@ declare module 'vscode' { * @returns A call hierarchy item or a thenable that resolves to such. The lack of a result can be * signaled by returning `undefined` or `null`. */ - prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + prepareCallHierarchy(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; /** * Provide all incoming calls for an item, e.g all callers for a method. In graph terms this describes directed @@ -4234,52 +4239,16 @@ declare module 'vscode' { /** * Represents the configuration. It is a merged view of * - * - *Default Settings* - * - *Global (User) Settings* - * - *Workspace settings* - * - *Workspace Folder settings* - From one of the [Workspace Folders](#workspace.workspaceFolders) under which requested resource belongs to. - * - *Language settings* - Settings defined under requested language. + * - Default configuration + * - Global configuration + * - Workspace configuration (if available) + * - Workspace folder configuration of the requested resource (if available) * - * The *effective* value (returned by [`get`](#WorkspaceConfiguration.get)) is computed by overriding or merging the values in the following order. + * *Global configuration* comes from User Settings and overrides Defaults. * - * ``` - * `defaultValue` - * `globalValue` (if defined) - * `workspaceValue` (if defined) - * `workspaceFolderValue` (if defined) - * `defaultLanguageValue` (if defined) - * `globalLanguageValue` (if defined) - * `workspaceLanguageValue` (if defined) - * `workspaceLanguageValue` (if defined) - * ``` - * **Note:** Only `object` value types are merged and all other value types are overridden. + * *Workspace configuration* comes from Workspace Settings and overrides Global configuration. * - * Example 1: Overriding - * - * ```ts - * defaultValue = 'on'; - * globalValue = 'relative' - * workspaceFolderValue = 'off' - * value = 'off' - * ``` - * - * Example 2: Language Values - * - * ```ts - * defaultValue = 'on'; - * globalValue = 'relative' - * workspaceFolderValue = 'off' - * globalLanguageValue = 'on' - * value = 'on' - * ``` - * - * Example 3: Object Values - * - * ```ts - * defaultValue = { "a": 1, "b": 2 }; - * globalValue = { "b": 3, "c": 4 }; - * value = { "a": 1, "b": 3, "c": 4 }; - * ``` + * *Workspace Folder configuration* comes from `.vscode` folder under one of the [workspace folders](#workspace.workspaceFolders) and overrides Workspace configuration. * * *Note:* Workspace and Workspace Folder configurations contains `launch` and `tasks` settings. Their basename will be * part of the section identifier. The following snippets shows how to retrieve all configurations @@ -4287,7 +4256,7 @@ declare module 'vscode' { * * ```ts * // launch.json configuration - * const config = workspace.getConfiguration('launch', vscode.workspace.workspaceFolders[0].uri); + * const config = workspace.getConfiguration('launch', vscode.window.activeTextEditor.document.uri); * * // retrieve values * const values = config.get('configurations'); @@ -4325,8 +4294,13 @@ declare module 'vscode' { /** * Retrieve all information about a configuration setting. A configuration value * often consists of a *default* value, a global or installation-wide value, - * a workspace-specific value, folder-specific value - * and language-specific values (if [WorkspaceConfiguration](#WorkspaceConfiguration) is scoped to a language). + * a workspace-specific value and a folder-specific value. + * + * The *effective* value (returned by [`get`](#WorkspaceConfiguration.get)) + * is computed like this: `defaultValue` overridden by `globalValue`, + * `globalValue` overridden by `workspaceValue`. `workspaceValue` overwridden by `workspaceFolderValue`. + * Refer to [Settings](https://code.visualstudio.com/docs/getstarted/settings) + * for more information. * * *Note:* The configuration name must denote a leaf in the configuration tree * (`editor.fontSize` vs `editor`) otherwise no result is returned. @@ -4334,51 +4308,43 @@ declare module 'vscode' { * @param section Configuration name, supports _dotted_ names. * @return Information about a configuration setting or `undefined`. */ - inspect(section: string): { - key: string; - - defaultValue?: T; - globalValue?: T; - workspaceValue?: T, - workspaceFolderValue?: T, - - defaultLanguageValue?: T; - userLanguageValue?: T; - workspaceLanguageValue?: T; - workspaceFolderLanguageValue?: T; - - } | undefined; + inspect(section: string): { key: string; defaultValue?: T; globalValue?: T; workspaceValue?: T, workspaceFolderValue?: T } | undefined; /** * Update a configuration value. The updated configuration values are persisted. * * A value can be changed in * - * - [Global settings](#ConfigurationTarget.Global): Changes the value for all instances of the editor. - * - [Workspace settings](#ConfigurationTarget.Workspace): Changes the value for current workspace, if available. - * - [Workspace folder settings](#ConfigurationTarget.WorkspaceFolder): Changes the value for settings from one of the [Workspace Folders](#workspace.workspaceFolders) under which the requested resource belongs to. - * - Language settings: Changes the value for the requested languageId. + * - [Global configuration](#ConfigurationTarget.Global): Changes the value for all instances of the editor. + * - [Workspace configuration](#ConfigurationTarget.Workspace): Changes the value for current workspace, if available. + * - [Workspace folder configuration](#ConfigurationTarget.WorkspaceFolder): Changes the value for the + * [Workspace folder](#workspace.workspaceFolders) to which the current [configuration](#WorkspaceConfiguration) is scoped to. * - * *Note:* To remove a configuration value use `undefined`, like so: `config.update('somekey', undefined)` + * *Note 1:* Setting a global value in the presence of a more specific workspace value + * has no observable effect in that workspace, but in others. Setting a workspace value + * in the presence of a more specific folder value has no observable effect for the resources + * under respective [folder](#workspace.workspaceFolders), but in others. Refer to + * [Settings Inheritance](https://code.visualstudio.com/docs/getstarted/settings) for more information. + * + * *Note 2:* To remove a configuration value use `undefined`, like so: `config.update('somekey', undefined)` + * + * Will throw error when + * - Writing a configuration which is not registered. + * - Writing a configuration to workspace or folder target when no workspace is opened + * - Writing a configuration to folder target when there is no folder settings + * - Writing to folder target without passing a resource when getting the configuration (`workspace.getConfiguration(section, resource)`) + * - Writing a window configuration to folder target * * @param section Configuration name, supports _dotted_ names. * @param value The new value. * @param configurationTarget The [configuration target](#ConfigurationTarget) or a boolean value. - * - If `true` updates [Global settings](#ConfigurationTarget.Global). - * - If `false` updates [Workspace settings](#ConfigurationTarget.Workspace). - * - If `undefined` or `null` updates to [Workspace folder settings](#ConfigurationTarget.WorkspaceFolder) if configuration is resource specific, - * otherwise to [Workspace settings](#ConfigurationTarget.Workspace). - * @param scopeToLanguage Whether to update the value in the scope of requested languageId or not. - * - If `true` updates the value under the requested languageId. - * - If `undefined` updates the value under the requested languageId only if the configuration is defined for the language. - * @throws error while updating - * - configuration which is not registered. - * - window configuration to workspace folder - * - configuration to workspace or workspace folder when no workspace is opened. - * - configuration to workspace folder when there is no workspace folder settings. - * - configuration to workspace folder when [WorkspaceConfiguration](#WorkspaceConfiguration) is not scoped to a resource. + * - If `true` configuration target is `ConfigurationTarget.Global`. + * - If `false` configuration target is `ConfigurationTarget.Workspace`. + * - If `undefined` or `null` configuration target is + * `ConfigurationTarget.WorkspaceFolder` when configuration is resource specific + * `ConfigurationTarget.Workspace` otherwise. */ - update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean, scopeToLanguage?: boolean): Thenable; + update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean): Thenable; /** * Readable dictionary that backs this configuration. @@ -5750,7 +5716,7 @@ declare module 'vscode' { /** * Enumeration of file types. The types `File` and `Directory` can also be - * a symbolic links, in that use `FileType.File | FileType.SymbolicLink` and + * a symbolic links, in that case use `FileType.File | FileType.SymbolicLink` and * `FileType.Directory | FileType.SymbolicLink`. */ export enum FileType { @@ -5779,6 +5745,8 @@ declare module 'vscode' { /** * The type of the file, e.g. is a regular file, a directory, or symbolic link * to a file. + * + * *Note:* This value might be a bitmask, e.g. `FileType.File | FileType.SymbolicLink`. */ type: FileType; /** @@ -7408,7 +7376,7 @@ declare module 'vscode' { iconPath?: string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon; /** - * A human readable string which is rendered less prominent. + * A human-readable string which is rendered less prominent. * When `true`, it is derived from [resourceUri](#TreeItem.resourceUri) and when `falsy`, it is not shown. */ description?: string | boolean; @@ -8716,12 +8684,13 @@ declare module 'vscode' { * is returned. Dots in the section-identifier are interpreted as child-access, * like `{ myExt: { setting: { doIt: true }}}` and `getConfiguration('myExt.setting').get('doIt') === true`. * - * When a scope is provided configuraiton confined to that scope is returned. Scope can be a resource or a language identifier or both. + * When a resource is provided, configuration scoped to that resource is returned. * * @param section A dot-separated identifier. + * @param resource A resource for which the configuration is asked for * @return The full configuration or a subset. */ - export function getConfiguration(section?: string | undefined, scope?: ConfigurationScope | null): WorkspaceConfiguration; + export function getConfiguration(section?: string, resource?: Uri | null): WorkspaceConfiguration; /** * An event that is emitted when the [configuration](#WorkspaceConfiguration) changed. @@ -8753,21 +8722,19 @@ declare module 'vscode' { export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider, options?: { readonly isCaseSensitive?: boolean, readonly isReadonly?: boolean }): Disposable; } - export type ConfigurationScope = Uri | TextDocument | WorkspaceFolder | { resource: Uri, languageId: string }; - /** * An event describing the change in Configuration */ export interface ConfigurationChangeEvent { /** - * Returns `true` if the given section is affected in the provided scope. + * Returns `true` if the given section for the given resource (if provided) is affected. * * @param section Configuration name, supports _dotted_ names. - * @param scope A scope in which to check. - * @return `true` if the given section is affected in the provided scope. + * @param resource A resource Uri. + * @return `true` if the given section for the given resource (if provided) is affected. */ - affectsConfiguration(section: string, scope?: ConfigurationScope): boolean; + affectsConfiguration(section: string, resource?: Uri): boolean; } /** @@ -9618,7 +9585,45 @@ declare module 'vscode' { constructor(port: number, host?: string); } - export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer; + /** + * A debug adapter that implements the Debug Adapter Protocol can be registered with VS Code if it implements the DebugAdapter interface. + */ + export interface DebugAdapter extends Disposable { + + /** + * An event which fires after the debug adapter has sent a Debug Adapter Protocol message to VS Code. + * Messages can be requests, responses, or events. + */ + readonly onDidSendMessage: Event; + + /** + * Handle a Debug Adapter Protocol message. + * Messages can be requests, responses, or events. + * Results or errors are returned via onSendMessage events. + * @param message A Debug Adapter Protocol message + */ + handleMessage(message: DebugProtocolMessage): void; + } + + /** + * A DebugProtocolMessage is an opaque stand-in type for the [ProtocolMessage](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage) type defined in the Debug Adapter Protocol. + */ + export interface DebugProtocolMessage { + // Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage). + } + + /** + * A debug adapter descriptor for an inline implementation. + */ + export class DebugAdapterInlineImplementation { + + /** + * Create a descriptor for an inline implementation of a debug adapter. + */ + constructor(implementation: DebugAdapter); + } + + export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterInlineImplementation; export interface DebugAdapterDescriptorFactory { /** From 16125bcbe3ad883a7a3f0b3abbfae280e1018b54 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Dec 2019 15:38:52 +0100 Subject: [PATCH 059/241] debt - add a test for restoring dirty files from hot exit --- src/vs/workbench/browser/editor.ts | 23 +- src/vs/workbench/common/editor.ts | 13 +- .../contrib/backup/common/backupRestorer.ts | 2 +- .../electron-browser/backupRestorer.test.ts | 153 ++++ .../backupFileService.test.ts | 15 +- .../test/browser/editorGroupsService.test.ts | 24 +- .../editor/test/browser/editorService.test.ts | 24 +- .../services/history/test/history.test.ts | 683 +++++++++--------- .../browser/parts/editor/baseEditor.test.ts | 38 +- .../test/common/editor/editorGroups.test.ts | 12 +- .../workbench/test/workbenchTestServices.ts | 21 +- 11 files changed, 602 insertions(+), 406 deletions(-) create mode 100644 src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index e03a20900a5..af3e39d295e 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -9,6 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { find } from 'vs/base/common/arrays'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export interface IEditorDescriptor { instantiate(instantiationService: IInstantiationService): BaseEditor; @@ -30,7 +31,7 @@ export interface IEditorRegistry { * @param inputDescriptors A set of constructor functions that return an instance of EditorInput for which the * registered editor should be used for. */ - registerEditor(descriptor: IEditorDescriptor, inputDescriptors: readonly SyncDescriptor[]): void; + registerEditor(descriptor: IEditorDescriptor, inputDescriptors: readonly SyncDescriptor[]): IDisposable; /** * Returns the editor descriptor for the given input or `undefined` if none. @@ -54,7 +55,7 @@ export interface IEditorRegistry { */ export class EditorDescriptor implements IEditorDescriptor { - public static create( + static create( ctor: { new(...services: Services): BaseEditor }, id: string, name: string @@ -87,14 +88,22 @@ export class EditorDescriptor implements IEditorDescriptor { class EditorRegistry implements IEditorRegistry { - private editors: EditorDescriptor[] = []; + private readonly editors: EditorDescriptor[] = []; private readonly mapEditorToInputs = new Map[]>(); - registerEditor(descriptor: EditorDescriptor, inputDescriptors: readonly SyncDescriptor[]): void { - // Register (Support multiple Editors per Input) + registerEditor(descriptor: EditorDescriptor, inputDescriptors: readonly SyncDescriptor[]): IDisposable { this.mapEditorToInputs.set(descriptor, inputDescriptors); this.editors.push(descriptor); + + return toDisposable(() => { + this.mapEditorToInputs.delete(descriptor); + + const index = this.editors.indexOf(descriptor); + if (index !== -1) { + this.editors.splice(index, 1); + } + }); } getEditor(input: EditorInput): EditorDescriptor | undefined { @@ -156,10 +165,6 @@ class EditorRegistry implements IEditorRegistry { return this.editors.slice(0); } - setEditors(editorsToSet: EditorDescriptor[]): void { - this.editors = editorsToSet; - } - getEditorInputs(): SyncDescriptor[] { const inputClasses: SyncDescriptor[] = []; for (const editor of this.editors) { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 7d4b9690214..630cfd443a2 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -7,7 +7,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; import { isUndefinedOrNull, withNullAsUndefined, assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IEditor as ICodeEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, IResourceInput, EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor'; import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; @@ -181,7 +181,7 @@ export interface IEditorInputFactoryRegistry { * @param editorInputId the identifier of the editor input * @param factory the editor input factory for serialization/deserialization */ - registerEditorInputFactory(editorInputId: string, ctor: { new(...Services: Services): IEditorInputFactory }): void; + registerEditorInputFactory(editorInputId: string, ctor: { new(...Services: Services): IEditorInputFactory }): IDisposable; /** * Returns the editor input factory for the given editor input. @@ -1232,6 +1232,7 @@ export interface IEditorMemento { class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { private instantiationService: IInstantiationService | undefined; private fileInputFactory: IFileInputFactory | undefined; + private readonly editorInputFactoryConstructors: Map> = new Map(); private readonly editorInputFactoryInstances: Map = new Map(); @@ -1258,12 +1259,18 @@ class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { return assertIsDefined(this.fileInputFactory); } - registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): void { + registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): IDisposable { if (!this.instantiationService) { this.editorInputFactoryConstructors.set(editorInputId, ctor); } else { this.createEditorInputFactory(editorInputId, ctor, this.instantiationService); + } + + return toDisposable(() => { + this.editorInputFactoryConstructors.delete(editorInputId); + this.editorInputFactoryInstances.delete(editorInputId); + }); } getEditorInputFactory(editorInputId: string): IEditorInputFactory | undefined { diff --git a/src/vs/workbench/contrib/backup/common/backupRestorer.ts b/src/vs/workbench/contrib/backup/common/backupRestorer.ts index cfe126edd4b..a205249e0dc 100644 --- a/src/vs/workbench/contrib/backup/common/backupRestorer.ts +++ b/src/vs/workbench/contrib/backup/common/backupRestorer.ts @@ -31,7 +31,7 @@ export class BackupRestorer implements IWorkbenchContribution { this.lifecycleService.when(LifecyclePhase.Restored).then(() => this.doRestoreBackups()); } - private async doRestoreBackups(): Promise { + protected async doRestoreBackups(): Promise { // Find all files and untitled with backups const backups = await this.backupFileService.getWorkspaceFileBackups(); diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts new file mode 100644 index 00000000000..1f55662a66d --- /dev/null +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as platform from 'vs/base/common/platform'; +import * as os from 'os'; +import * as path from 'vs/base/common/path'; +import * as pfs from 'vs/base/node/pfs'; +import { URI } from 'vs/base/common/uri'; +import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { DefaultEndOfLine } from 'vs/editor/common/model'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { hashPath } from 'vs/workbench/services/backup/node/backupFileService'; +import { BackupModelTracker } from 'vs/workbench/contrib/backup/common/backupModelTracker'; +import { TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/electron-browser/backupFileService.test'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { isEqual } from 'vs/base/common/resources'; + +const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer'); +const backupHome = path.join(userdataDir, 'Backups'); +const workspacesJsonPath = path.join(backupHome, 'workspaces.json'); + +const workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace'); +const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource)); +const fooFile = URI.file(platform.isWindows ? 'c:\\Foo' : '/Foo'); +const barFile = URI.file(platform.isWindows ? 'c:\\Bar' : '/Bar'); +const untitledFile1 = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); +const untitledFile2 = URI.from({ scheme: Schemas.untitled, path: 'Untitled-2' }); + +class TestBackupRestorer extends BackupRestorer { + async doRestoreBackups(): Promise { + return super.doRestoreBackups(); + } +} + +class ServiceAccessor { + constructor( + @ITextFileService public textFileService: TestTextFileService, + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService + ) { + } +} + +suite('BackupModelRestorer', () => { + let accessor: ServiceAccessor; + + let disposables: IDisposable[] = []; + + setup(async () => { + disposables.push(Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + TextFileEditor, + TextFileEditor.ID, + 'Text File Editor' + ), + [new SyncDescriptor(FileEditorInput)] + )); + + // Delete any existing backups completely and then re-create it. + await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + await pfs.mkdirp(backupHome); + + return pfs.writeFile(workspacesJsonPath, ''); + }); + + teardown(async () => { + dispose(disposables); + disposables = []; + + (accessor.textFileService.models).clear(); + (accessor.textFileService.models).dispose(); + accessor.untitledTextEditorService.revertAll(); + + return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + }); + + test('Restore backups', async () => { + const backupFileService = new NodeTestBackupFileService(workspaceBackupPath); + const instantiationService = workbenchInstantiationService(); + instantiationService.stub(IBackupFileService, backupFileService); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + accessor = instantiationService.createInstance(ServiceAccessor); + + const tracker = instantiationService.createInstance(BackupModelTracker); + const restorer = instantiationService.createInstance(TestBackupRestorer); + + // Backup 2 normal files and 2 untitled file + await backupFileService.backupResource(untitledFile1, createTextBufferFactory('untitled-1').create(DefaultEndOfLine.LF).createSnapshot(false)); + await backupFileService.backupResource(untitledFile2, createTextBufferFactory('untitled-2').create(DefaultEndOfLine.LF).createSnapshot(false)); + await backupFileService.backupResource(fooFile, createTextBufferFactory('fooFile').create(DefaultEndOfLine.LF).createSnapshot(false)); + await backupFileService.backupResource(barFile, createTextBufferFactory('barFile').create(DefaultEndOfLine.LF).createSnapshot(false)); + + // Verify backups restored and opened as dirty + await restorer.doRestoreBackups(); + assert.equal(editorService.editors.length, 4); + assert.ok(editorService.editors.every(editor => editor.isDirty())); + + let counter = 0; + for (const editor of editorService.editors) { + const resource = editor.getResource(); + if (isEqual(resource, untitledFile1)) { + const model = await accessor.untitledTextEditorService.createOrGet(resource).resolve(); + assert.equal(model.textEditorModel.getValue(), 'untitled-1'); + counter++; + } else if (isEqual(resource, untitledFile2)) { + const model = await accessor.untitledTextEditorService.createOrGet(resource).resolve(); + assert.equal(model.textEditorModel.getValue(), 'untitled-2'); + counter++; + } else if (isEqual(resource, fooFile)) { + const model = await accessor.textFileService.models.get(resource!)?.load(); + assert.equal(model?.textEditorModel?.getValue(), 'fooFile'); + counter++; + } else { + const model = await accessor.textFileService.models.get(resource!)?.load(); + assert.equal(model?.textEditorModel?.getValue(), 'barFile'); + counter++; + } + } + + assert.equal(counter, 4); + + part.dispose(); + tracker.dispose(); + }); +}); diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index 02fae436721..f49293fd2ab 100644 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -51,14 +51,13 @@ class TestBackupEnvironmentService extends NativeWorkbenchEnvironmentService { constructor(backupPath: string) { super({ ...parseArgs(process.argv, OPTIONS), ...{ backupPath, 'user-data-dir': userdataDir } } as IWindowConfiguration, process.execPath, 0); } - } -class TestBackupFileService extends BackupFileService { +export class NodeTestBackupFileService extends BackupFileService { readonly fileService: IFileService; - constructor(workspace: URI, backupHome: string, workspacesJsonPath: string) { + constructor(workspaceBackupPath: string) { const environmentService = new TestBackupEnvironmentService(workspaceBackupPath); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); @@ -76,10 +75,10 @@ class TestBackupFileService extends BackupFileService { } suite('BackupFileService', () => { - let service: TestBackupFileService; + let service: NodeTestBackupFileService; setup(async () => { - service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath); + service = new NodeTestBackupFileService(workspaceBackupPath); // Delete any existing backups completely and then re-create it. await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); @@ -141,7 +140,7 @@ suite('BackupFileService', () => { test('should return whether a backup resource exists', async () => { await pfs.mkdirp(path.dirname(fooBackupPath)); fs.writeFileSync(fooBackupPath, 'foo'); - service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath); + service = new NodeTestBackupFileService(workspaceBackupPath); const resource = await service.loadBackupResource(fooFile); assert.ok(resource); assert.equal(path.basename(resource!.fsPath), path.basename(fooBackupPath)); @@ -528,10 +527,10 @@ suite('BackupFileService', () => { suite('BackupFilesModel', () => { - let service: TestBackupFileService; + let service: NodeTestBackupFileService; setup(async () => { - service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath); + service = new NodeTestBackupFileService(workspaceBackupPath); // Delete any existing backups completely and then re-create it. await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index ccf554c339e..abf37eb4fd6 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -19,10 +19,14 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; + +const TEST_EDITOR_ID = 'MyFileEditorForEditorGroupService'; +const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorGroupService'; class TestEditorControl extends BaseEditor { - constructor(@ITelemetryService telemetryService: ITelemetryService) { super('MyFileEditorForEditorGroupService', NullTelemetryService, new TestThemeService(), new TestStorageService()); } + constructor(@ITelemetryService telemetryService: ITelemetryService) { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { super.setInput(input, options, token); @@ -30,7 +34,7 @@ class TestEditorControl extends BaseEditor { await input.resolve(); } - getId(): string { return 'MyFileEditorForEditorGroupService'; } + getId(): string { return TEST_EDITOR_ID; } layout(): void { } createEditor(): any { } } @@ -39,7 +43,7 @@ class TestEditorInput extends EditorInput implements IFileEditorInput { constructor(private resource: URI) { super(); } - getTypeId() { return 'testEditorInputForEditorGroupService'; } + getTypeId() { return TEST_EDITOR_INPUT_ID; } resolve(): Promise { return Promise.resolve(null); } matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } setEncoding(encoding: string) { } @@ -53,8 +57,9 @@ class TestEditorInput extends EditorInput implements IFileEditorInput { suite('EditorGroupsService', () => { - function registerTestEditorInput(): void { + let disposables: IDisposable[] = []; + setup(() => { interface ISerializedTestEditorInput { resource: string; } @@ -81,11 +86,14 @@ suite('EditorGroupsService', () => { } } - (Registry.as(EditorExtensions.EditorInputFactories)).registerEditorInputFactory('testEditorInputForGroupsService', TestEditorInputFactory); - (Registry.as(Extensions.Editors)).registerEditor(EditorDescriptor.create(TestEditorControl, 'MyTestEditorForGroupsService', 'My Test File Editor'), [new SyncDescriptor(TestEditorInput)]); - } + disposables.push((Registry.as(EditorExtensions.EditorInputFactories)).registerEditorInputFactory(TEST_EDITOR_INPUT_ID, TestEditorInputFactory)); + disposables.push((Registry.as(Extensions.Editors)).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test File Editor'), [new SyncDescriptor(TestEditorInput)])); + }); - registerTestEditorInput(); + teardown(() => { + dispose(disposables); + disposables = []; + }); function createPart(): EditorPart { const instantiationService = workbenchInstantiationService(); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 079d1ce5ded..2296fed1da8 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -25,7 +25,7 @@ import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { timeout } from 'vs/base/common/async'; import { toResource } from 'vs/base/test/common/utils'; import { IFileService } from 'vs/platform/files/common/files'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider'; @@ -33,9 +33,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { CancellationToken } from 'vscode'; +const TEST_EDITOR_ID = 'MyTestEditorForEditorService'; +const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorService'; + class TestEditorControl extends BaseEditor { - constructor(@ITelemetryService telemetryService: ITelemetryService) { super('MyTestEditorForEditorService', NullTelemetryService, new TestThemeService(), new TestStorageService()); } + constructor(@ITelemetryService telemetryService: ITelemetryService) { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { super.setInput(input, options, token); @@ -43,7 +46,7 @@ class TestEditorControl extends BaseEditor { await input.resolve(); } - getId(): string { return 'MyTestEditorForEditorService'; } + getId(): string { return TEST_EDITOR_ID; } layout(): void { } createEditor(): any { } } @@ -57,7 +60,7 @@ class TestEditorInput extends EditorInput implements IFileEditorInput { private fails = false; constructor(public resource: URI) { super(); } - getTypeId() { return 'testEditorInputForEditorService'; } + getTypeId() { return TEST_EDITOR_INPUT_ID; } resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } setEncoding(encoding: string) { } @@ -106,11 +109,16 @@ class FileServiceProvider extends Disposable { suite('EditorService', () => { - function registerTestEditorInput(): void { - Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, 'MyTestEditorForEditorService', 'My Test Editor For Next Editor Service'), [new SyncDescriptor(TestEditorInput)]); - } + let disposables: IDisposable[] = []; - registerTestEditorInput(); + setup(() => { + disposables.push(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test Editor For Next Editor Service'), [new SyncDescriptor(TestEditorInput)])); + }); + + teardown(() => { + dispose(disposables); + disposables = []; + }); test('basics', async () => { const partInstantiator = workbenchInstantiationService(); diff --git a/src/vs/workbench/services/history/test/history.test.ts b/src/vs/workbench/services/history/test/history.test.ts index 5a0d860f314..e032184cb5a 100644 --- a/src/vs/workbench/services/history/test/history.test.ts +++ b/src/vs/workbench/services/history/test/history.test.ts @@ -20,14 +20,19 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { EditorsHistory, HistoryService } from 'vs/workbench/services/history/browser/history'; import { WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { timeout } from 'vs/base/common/async'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; + +const TEST_EDITOR_ID = 'MyTestEditorForEditorHistory'; +const TEST_EDITOR_INPUT_ID = 'testEditorInputForHistoyService'; +const TEST_SERIALIZABLE_EDITOR_INPUT_ID = 'testSerializableEditorInputForHistoyService'; class TestEditorControl extends BaseEditor { - constructor() { super('MyTestEditorForEditorHistory', NullTelemetryService, new TestThemeService(), new TestStorageService()); } + constructor() { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { super.setInput(input, options, token); @@ -35,7 +40,7 @@ class TestEditorControl extends BaseEditor { await input.resolve(); } - getId(): string { return 'MyTestEditorForEditorHistory'; } + getId(): string { return TEST_EDITOR_ID; } layout(): void { } createEditor(): any { } } @@ -44,7 +49,7 @@ class TestEditorInput extends EditorInput implements IFileEditorInput { constructor(public resource: URI) { super(); } - getTypeId() { return 'testEditorInputForEditorGroupService'; } + getTypeId() { return TEST_EDITOR_INPUT_ID; } resolve(): Promise { return Promise.resolve(null); } matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } setEncoding(encoding: string) { } @@ -57,7 +62,7 @@ class TestEditorInput extends EditorInput implements IFileEditorInput { } class HistoryTestEditorInput extends TestEditorInput { - getTypeId() { return 'testEditorInputForEditorsHistory'; } + getTypeId() { return TEST_SERIALIZABLE_EDITOR_INPUT_ID; } } interface ISerializedTestInput { @@ -95,30 +100,41 @@ async function createServices(): Promise<[EditorPart, HistoryService, EditorServ await part.whenRestored; - const collection = new ServiceCollection(); - collection.set(IEditorGroupsService, part); + instantiationService.stub(IEditorGroupsService, part); - const childInstantiator = instantiationService.createChild(collection); + const editorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); - const childCollection = new ServiceCollection(); - const editorService = childInstantiator.createInstance(EditorService); - collection.set(IEditorService, editorService); - - const historyService = childInstantiator.createChild(childCollection).createInstance(HistoryService); + const historyService = instantiationService.createInstance(HistoryService); + instantiationService.stub(IHistoryService, historyService); return [part, historyService, editorService]; } suite('HistoryService', function () { + let disposables: IDisposable[] = []; + + setup(() => { + disposables.push(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(TEST_SERIALIZABLE_EDITOR_INPUT_ID, HistoryTestEditorInputFactory)); + disposables.push(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test Editor For History Editor Service'), [new SyncDescriptor(TestEditorInput), new SyncDescriptor(HistoryTestEditorInput)])); + }); + + teardown(() => { + dispose(disposables); + disposables = []; + }); + test('back / forward', async () => { const [part, historyService] = await createServices(); const input1 = new TestEditorInput(URI.parse('foo://bar1')); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + assert.equal(part.activeGroup.activeEditor, input1); const input2 = new TestEditorInput(URI.parse('foo://bar2')); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + assert.equal(part.activeGroup.activeEditor, input2); historyService.back(); assert.equal(part.activeGroup.activeEditor, input1); @@ -164,429 +180,418 @@ suite('HistoryService', function () { part.dispose(); }); -}); + suite('EditorHistory', function () { -suite('EditorHistory', function () { + test('basics (single group)', async () => { + const instantiationService = workbenchInstantiationService(); - function registerTestEditorInput(): void { - Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, 'MyTestEditorForEditorHistory', 'My Test Editor For History Editor Service'), [new SyncDescriptor(TestEditorInput)]); - } + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); - function registerEditorInputFactory() { - Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory('testEditorInputForEditorsHistory', HistoryTestEditorInputFactory); - } + await part.whenRestored; - registerEditorInputFactory(); - registerTestEditorInput(); + const history = new EditorsHistory(part, new TestStorageService()); - test('basics (single group)', async () => { - const instantiationService = workbenchInstantiationService(); + let historyChangeListenerCalled = false; + const listener = history.onDidChange(() => { + historyChangeListenerCalled = true; + }); - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); + let currentHistory = history.editors; + assert.equal(currentHistory.length, 0); + assert.equal(historyChangeListenerCalled, false); - await part.whenRestored; + const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); - const history = new EditorsHistory(part, new TestStorageService()); + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - let historyChangeListenerCalled = false; - const listener = history.onDidChange(() => { - historyChangeListenerCalled = true; + currentHistory = history.editors; + assert.equal(currentHistory.length, 1); + assert.equal(currentHistory[0].groupId, part.activeGroup.id); + assert.equal(currentHistory[0].editor, input1); + assert.equal(historyChangeListenerCalled, true); + + const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); + const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); + + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, part.activeGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, part.activeGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, part.activeGroup.id); + assert.equal(currentHistory[2].editor, input1); + + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, part.activeGroup.id); + assert.equal(currentHistory[0].editor, input2); + assert.equal(currentHistory[1].groupId, part.activeGroup.id); + assert.equal(currentHistory[1].editor, input3); + assert.equal(currentHistory[2].groupId, part.activeGroup.id); + assert.equal(currentHistory[2].editor, input1); + + historyChangeListenerCalled = false; + await part.activeGroup.closeEditor(input1); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 2); + assert.equal(currentHistory[0].groupId, part.activeGroup.id); + assert.equal(currentHistory[0].editor, input2); + assert.equal(currentHistory[1].groupId, part.activeGroup.id); + assert.equal(currentHistory[1].editor, input3); + assert.equal(historyChangeListenerCalled, true); + + await part.activeGroup.closeAllEditors(); + currentHistory = history.editors; + assert.equal(currentHistory.length, 0); + + part.dispose(); + listener.dispose(); }); - let currentHistory = history.editors; - assert.equal(currentHistory.length, 0); - assert.equal(historyChangeListenerCalled, false); + test('basics (multi group)', async () => { + const instantiationService = workbenchInstantiationService(); - const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); - await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await part.whenRestored; - currentHistory = history.editors; - assert.equal(currentHistory.length, 1); - assert.equal(currentHistory[0].groupId, part.activeGroup.id); - assert.equal(currentHistory[0].editor, input1); - assert.equal(historyChangeListenerCalled, true); + const rootGroup = part.activeGroup; - const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); - const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); + const history = new EditorsHistory(part, new TestStorageService()); - await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + let currentHistory = history.editors; + assert.equal(currentHistory.length, 0); - currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, part.activeGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, part.activeGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, part.activeGroup.id); - assert.equal(currentHistory[2].editor, input1); + const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); - currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, part.activeGroup.id); - assert.equal(currentHistory[0].editor, input2); - assert.equal(currentHistory[1].groupId, part.activeGroup.id); - assert.equal(currentHistory[1].editor, input3); - assert.equal(currentHistory[2].groupId, part.activeGroup.id); - assert.equal(currentHistory[2].editor, input1); + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); + await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); - historyChangeListenerCalled = false; - await part.activeGroup.closeEditor(input1); + currentHistory = history.editors; + assert.equal(currentHistory.length, 2); + assert.equal(currentHistory[0].groupId, sideGroup.id); + assert.equal(currentHistory[0].editor, input1); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input1); - currentHistory = history.editors; - assert.equal(currentHistory.length, 2); - assert.equal(currentHistory[0].groupId, part.activeGroup.id); - assert.equal(currentHistory[0].editor, input2); - assert.equal(currentHistory[1].groupId, part.activeGroup.id); - assert.equal(currentHistory[1].editor, input3); - assert.equal(historyChangeListenerCalled, true); + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); - await part.activeGroup.closeAllEditors(); - currentHistory = history.editors; - assert.equal(currentHistory.length, 0); + currentHistory = history.editors; + assert.equal(currentHistory.length, 2); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input1); + assert.equal(currentHistory[1].groupId, sideGroup.id); + assert.equal(currentHistory[1].editor, input1); - part.dispose(); - listener.dispose(); - }); + // Opening an editor inactive should not change + // the most recent editor, but rather put it behind + const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); - test('basics (multi group)', async () => { - const instantiationService = workbenchInstantiationService(); + await rootGroup.openEditor(input2, EditorOptions.create({ inactive: true })); - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); + currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input1); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, sideGroup.id); + assert.equal(currentHistory[2].editor, input1); - await part.whenRestored; + await rootGroup.closeAllEditors(); - const rootGroup = part.activeGroup; + currentHistory = history.editors; + assert.equal(currentHistory.length, 1); + assert.equal(currentHistory[0].groupId, sideGroup.id); + assert.equal(currentHistory[0].editor, input1); - const history = new EditorsHistory(part, new TestStorageService()); - - let currentHistory = history.editors; - assert.equal(currentHistory.length, 0); - - const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - - const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); + await sideGroup.closeAllEditors(); - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); - await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 2); - assert.equal(currentHistory[0].groupId, sideGroup.id); - assert.equal(currentHistory[0].editor, input1); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input1); + currentHistory = history.editors; + assert.equal(currentHistory.length, 0); - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); + part.dispose(); + }); - currentHistory = history.editors; - assert.equal(currentHistory.length, 2); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input1); - assert.equal(currentHistory[1].groupId, sideGroup.id); - assert.equal(currentHistory[1].editor, input1); + test('copy group', async () => { + const instantiationService = workbenchInstantiationService(); - // Opening an editor inactive should not change - // the most recent editor, but rather put it behind - const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); - await rootGroup.openEditor(input2, EditorOptions.create({ inactive: true })); + await part.whenRestored; - currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input1); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, sideGroup.id); - assert.equal(currentHistory[2].editor, input1); - - await rootGroup.closeAllEditors(); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 1); - assert.equal(currentHistory[0].groupId, sideGroup.id); - assert.equal(currentHistory[0].editor, input1); - - await sideGroup.closeAllEditors(); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 0); - - part.dispose(); - }); - - test('copy group', async () => { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; + const history = new EditorsHistory(part, new TestStorageService()); - const history = new EditorsHistory(part, new TestStorageService()); + const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); + const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); + const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); - const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); - const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); - const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); + const rootGroup = part.activeGroup; - const rootGroup = part.activeGroup; + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + let currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); - let currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); + const copiedGroup = part.copyGroup(rootGroup, rootGroup, GroupDirection.RIGHT); + copiedGroup.setActive(true); - const copiedGroup = part.copyGroup(rootGroup, rootGroup, GroupDirection.RIGHT); - copiedGroup.setActive(true); + currentHistory = history.editors; + assert.equal(currentHistory.length, 6); + assert.equal(currentHistory[0].groupId, copiedGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input3); + assert.equal(currentHistory[2].groupId, copiedGroup.id); + assert.equal(currentHistory[2].editor, input2); + assert.equal(currentHistory[3].groupId, copiedGroup.id); + assert.equal(currentHistory[3].editor, input1); + assert.equal(currentHistory[4].groupId, rootGroup.id); + assert.equal(currentHistory[4].editor, input2); + assert.equal(currentHistory[5].groupId, rootGroup.id); + assert.equal(currentHistory[5].editor, input1); + + part.dispose(); + }); + + test('initial editors are part of history and state is persisted & restored (single group)', async () => { + const instantiationService = workbenchInstantiationService(); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const rootGroup = part.activeGroup; + + const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); + const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); + const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); - currentHistory = history.editors; - assert.equal(currentHistory.length, 6); - assert.equal(currentHistory[0].groupId, copiedGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input3); - assert.equal(currentHistory[2].groupId, copiedGroup.id); - assert.equal(currentHistory[2].editor, input2); - assert.equal(currentHistory[3].groupId, copiedGroup.id); - assert.equal(currentHistory[3].editor, input1); - assert.equal(currentHistory[4].groupId, rootGroup.id); - assert.equal(currentHistory[4].editor, input2); - assert.equal(currentHistory[5].groupId, rootGroup.id); - assert.equal(currentHistory[5].editor, input1); - - part.dispose(); - }); - - test('initial editors are part of history and state is persisted & restored (single group)', async () => { - const instantiationService = workbenchInstantiationService(); - instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; - - const rootGroup = part.activeGroup; - - const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); - const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); - const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + const storage = new TestStorageService(); + const history = new EditorsHistory(part, storage); + await part.whenRestored; - const storage = new TestStorageService(); - const history = new EditorsHistory(part, storage); - await part.whenRestored; + let currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); - let currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); + storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); - storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + const restoredHistory = new EditorsHistory(part, storage); + await part.whenRestored; - const restoredHistory = new EditorsHistory(part, storage); - await part.whenRestored; + currentHistory = restoredHistory.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); - currentHistory = restoredHistory.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); + part.dispose(); + }); - part.dispose(); - }); + test('initial editors are part of history (multi group)', async () => { + const instantiationService = workbenchInstantiationService(); - test('initial editors are part of history (multi group)', async () => { - const instantiationService = workbenchInstantiationService(); + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); + await part.whenRestored; - await part.whenRestored; + const rootGroup = part.activeGroup; - const rootGroup = part.activeGroup; + const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); + const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); + const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); - const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); - const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); - const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + await sideGroup.openEditor(input3, EditorOptions.create({ pinned: true })); - const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - await sideGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + const storage = new TestStorageService(); + const history = new EditorsHistory(part, storage); + await part.whenRestored; - const storage = new TestStorageService(); - const history = new EditorsHistory(part, storage); - await part.whenRestored; + let currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, sideGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); - let currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, sideGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); + storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); - storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + const restoredHistory = new EditorsHistory(part, storage); + await part.whenRestored; - const restoredHistory = new EditorsHistory(part, storage); - await part.whenRestored; + currentHistory = restoredHistory.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, sideGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); - currentHistory = restoredHistory.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, sideGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); + part.dispose(); + }); - part.dispose(); - }); + test('history does not restore editors that cannot be serialized', async () => { + const instantiationService = workbenchInstantiationService(); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - test('history does not restore editors that cannot be serialized', async () => { - const instantiationService = workbenchInstantiationService(); - instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); + await part.whenRestored; - await part.whenRestored; + const rootGroup = part.activeGroup; - const rootGroup = part.activeGroup; + const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + const storage = new TestStorageService(); + const history = new EditorsHistory(part, storage); + await part.whenRestored; - const storage = new TestStorageService(); - const history = new EditorsHistory(part, storage); - await part.whenRestored; + let currentHistory = history.editors; + assert.equal(currentHistory.length, 1); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input1); - let currentHistory = history.editors; - assert.equal(currentHistory.length, 1); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input1); + storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); - storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + const restoredHistory = new EditorsHistory(part, storage); + await part.whenRestored; - const restoredHistory = new EditorsHistory(part, storage); - await part.whenRestored; + currentHistory = restoredHistory.editors; + assert.equal(currentHistory.length, 0); - currentHistory = restoredHistory.editors; - assert.equal(currentHistory.length, 0); + part.dispose(); + }); - part.dispose(); - }); + test('open next/previous recently used editor (single group)', async () => { + const [part, historyService] = await createServices(); - test('open next/previous recently used editor (single group)', async () => { - const [part, historyService] = await createServices(); + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + assert.equal(part.activeGroup.activeEditor, input1); - await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - assert.equal(part.activeGroup.activeEditor, input1); + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + assert.equal(part.activeGroup.activeEditor, input2); - await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - assert.equal(part.activeGroup.activeEditor, input2); + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input1); - historyService.openPreviouslyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input1); + historyService.openNextRecentlyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input2); - historyService.openNextRecentlyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input2); + historyService.openPreviouslyUsedEditor(part.activeGroup.id); + assert.equal(part.activeGroup.activeEditor, input1); - historyService.openPreviouslyUsedEditor(part.activeGroup.id); - assert.equal(part.activeGroup.activeEditor, input1); + historyService.openNextRecentlyUsedEditor(part.activeGroup.id); + assert.equal(part.activeGroup.activeEditor, input2); - historyService.openNextRecentlyUsedEditor(part.activeGroup.id); - assert.equal(part.activeGroup.activeEditor, input2); + part.dispose(); + }); - part.dispose(); - }); + test('open next/previous recently used editor (multi group)', async () => { + const [part, historyService] = await createServices(); + const rootGroup = part.activeGroup; - test('open next/previous recently used editor (multi group)', async () => { - const [part, historyService] = await createServices(); - const rootGroup = part.activeGroup; + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup, rootGroup); + assert.equal(rootGroup.activeEditor, input1); - historyService.openPreviouslyUsedEditor(); - assert.equal(part.activeGroup, rootGroup); - assert.equal(rootGroup.activeEditor, input1); + historyService.openNextRecentlyUsedEditor(); + assert.equal(part.activeGroup, sideGroup); + assert.equal(sideGroup.activeEditor, input2); - historyService.openNextRecentlyUsedEditor(); - assert.equal(part.activeGroup, sideGroup); - assert.equal(sideGroup.activeEditor, input2); + part.dispose(); + }); - part.dispose(); - }); + test('open next/previous recently is reset when other input opens', async () => { + const [part, historyService] = await createServices(); - test('open next/previous recently is reset when other input opens', async () => { - const [part, historyService] = await createServices(); + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input3 = new TestEditorInput(URI.parse('foo://bar3')); + const input4 = new TestEditorInput(URI.parse('foo://bar4')); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); - const input3 = new TestEditorInput(URI.parse('foo://bar3')); - const input4 = new TestEditorInput(URI.parse('foo://bar4')); + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); - await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input2); - historyService.openPreviouslyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input2); + await timeout(0); + await part.activeGroup.openEditor(input4, EditorOptions.create({ pinned: true })); - await timeout(0); - await part.activeGroup.openEditor(input4, EditorOptions.create({ pinned: true })); + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input2); - historyService.openPreviouslyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input2); + historyService.openNextRecentlyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input4); - historyService.openNextRecentlyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input4); - - part.dispose(); + part.dispose(); + }); }); }); + diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index bd26f256b6d..b6abedc45d5 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import { BaseEditor, EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorInput, EditorOptions, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as Platform from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -19,6 +18,7 @@ import { URI } from 'vs/base/common/uri'; import { IEditorRegistry, Extensions, EditorDescriptor } from 'vs/workbench/browser/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { dispose } from 'vs/base/common/lifecycle'; const NullThemeService = new TestThemeService(); @@ -131,8 +131,8 @@ suite('Workbench base editor', () => { let oldEditorsCnt = EditorRegistry.getEditors().length; let oldInputCnt = (EditorRegistry).getEditorInputs().length; - EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyInput)]); - EditorRegistry.registerEditor(d2, [new SyncDescriptor(MyInput), new SyncDescriptor(MyOtherInput)]); + const dispose1 = EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyInput)]); + const dispose2 = EditorRegistry.registerEditor(d2, [new SyncDescriptor(MyInput), new SyncDescriptor(MyOtherInput)]); assert.equal(EditorRegistry.getEditors().length, oldEditorsCnt + 2); assert.equal((EditorRegistry).getEditorInputs().length, oldInputCnt + 3); @@ -143,51 +143,41 @@ suite('Workbench base editor', () => { assert.strictEqual(EditorRegistry.getEditorById('id1'), d1); assert.strictEqual(EditorRegistry.getEditorById('id2'), d2); assert(!EditorRegistry.getEditorById('id3')); + + dispose([dispose1, dispose2]); }); test('Editor Lookup favors specific class over superclass (match on specific class)', function () { let d1 = EditorDescriptor.create(MyEditor, 'id1', 'name'); - let d2 = EditorDescriptor.create(MyOtherEditor, 'id2', 'name'); - let oldEditors = EditorRegistry.getEditors(); - (EditorRegistry).setEditors([]); + const disposable = EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyResourceInput)]); - EditorRegistry.registerEditor(d2, [new SyncDescriptor(ResourceEditorInput)]); - EditorRegistry.registerEditor(d1, [new SyncDescriptor(MyResourceInput)]); - - let inst = new TestInstantiationService(); + let inst = workbenchInstantiationService(); const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); assert.strictEqual(editor.getId(), 'myEditor'); const otherEditor = EditorRegistry.getEditor(inst.createInstance(ResourceEditorInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); - assert.strictEqual(otherEditor.getId(), 'myOtherEditor'); + assert.strictEqual(otherEditor.getId(), 'workbench.editors.textResourceEditor'); - (EditorRegistry).setEditors(oldEditors); + disposable.dispose(); }); test('Editor Lookup favors specific class over superclass (match on super class)', function () { - let d1 = EditorDescriptor.create(MyOtherEditor, 'id1', 'name'); - - let oldEditors = EditorRegistry.getEditors(); - (EditorRegistry).setEditors([]); - - EditorRegistry.registerEditor(d1, [new SyncDescriptor(ResourceEditorInput)]); - - let inst = new TestInstantiationService(); + let inst = workbenchInstantiationService(); const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); - assert.strictEqual('myOtherEditor', editor.getId()); - - (EditorRegistry).setEditors(oldEditors); + assert.strictEqual('workbench.editors.textResourceEditor', editor.getId()); }); test('Editor Input Factory', function () { workbenchInstantiationService().invokeFunction(accessor => EditorInputRegistry.start(accessor)); - EditorInputRegistry.registerEditorInputFactory('myInputId', MyInputFactory); + const disposable = EditorInputRegistry.registerEditorInputFactory('myInputId', MyInputFactory); let factory = EditorInputRegistry.getEditorInputFactory('myInputId'); assert(factory); + + disposable.dispose(); }); test('EditorMemento - basics', function () { diff --git a/src/vs/workbench/test/common/editor/editorGroups.test.ts b/src/vs/workbench/test/common/editor/editorGroups.test.ts index 9aa743a2b34..990b1fadbe3 100644 --- a/src/vs/workbench/test/common/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/common/editor/editorGroups.test.ts @@ -20,6 +20,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; function inst(): IInstantiationService { let inst = new TestInstantiationService(); @@ -160,13 +161,16 @@ class TestEditorInputFactory implements IEditorInputFactory { suite('Workbench editor groups', () => { - function registerEditorInputFactory() { - Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory('testEditorInputForGroups', TestEditorInputFactory); - } + let disposables: IDisposable[] = []; - registerEditorInputFactory(); + setup(() => { + disposables.push(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory('testEditorInputForGroups', TestEditorInputFactory)); + }); teardown(() => { + dispose(disposables); + disposables = []; + index = 1; }); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 1e96afa4b1d..b088fe4928a 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -33,7 +33,7 @@ import { ITextFileStreamContent, ITextFileService, IResourceEncoding, IReadTextF import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { MenuBarVisibility, IWindowConfiguration, IWindowOpenable, IOpenWindowOptions, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; @@ -94,6 +94,7 @@ import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { find } from 'vs/base/common/arrays'; import { WorkingCopyService, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -276,7 +277,11 @@ export class TestTextFileService extends NativeTextFileService { } } -export function workbenchInstantiationService(): IInstantiationService { +export interface ITestInstantiationService extends IInstantiationService { + stub(service: ServiceIdentifier, ctor: any): T; +} + +export function workbenchInstantiationService(): ITestInstantiationService { let instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()])); instantiationService.stub(IEnvironmentService, TestEnvironmentService); const contextKeyService = instantiationService.createInstance(MockContextKeyService); @@ -291,6 +296,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(IStorageService, new TestStorageService()); instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService()); instantiationService.stub(IDialogService, new TestDialogService()); + instantiationService.stub(IAccessibilityService, new TestAccessibilityService()); instantiationService.stub(IFileDialogService, new TestFileDialogService()); instantiationService.stub(IElectronService, new TestElectronService()); instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl)); @@ -322,6 +328,17 @@ export function workbenchInstantiationService(): IInstantiationService { return instantiationService; } +export class TestAccessibilityService implements IAccessibilityService { + + _serviceBrand: undefined; + + onDidChangeAccessibilitySupport = Event.None; + + alwaysUnderlineAccessKeys(): Promise { return Promise.resolve(false); } + getAccessibilitySupport(): AccessibilitySupport { return AccessibilitySupport.Unknown; } + setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { } +} + export class TestDecorationsService implements IDecorationsService { _serviceBrand: undefined; onDidChangeDecorations: Event = Event.None; From f837c3e54f7a6eced108589d5de01beb98e1c923 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 17 Dec 2019 15:39:57 +0100 Subject: [PATCH 060/241] Add tasks to user configuration (#81496) Part of #1435 --- extensions/configuration-editing/package.json | 4 + .../configuration/browser/configuration.ts | 293 ++++++++++-------- .../configuration/common/configuration.ts | 2 + .../common/configurationEditingService.ts | 18 +- .../configurationEditingService.test.ts | 124 +++++--- .../configurationService.test.ts | 27 +- 6 files changed, 292 insertions(+), 176 deletions(-) diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 5defc86cf5d..be2d2e8ebae 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -88,6 +88,10 @@ "fileMatch": "/.vscode/tasks.json", "url": "vscode://schemas/tasks" }, + { + "fileMatch": "%APP_SETTINGS_HOME%/tasks.json", + "url": "vscode://schemas/tasks" + }, { "fileMatch": "%APP_SETTINGS_HOME%/snippets/*.json", "url": "vscode://schemas/snippets" diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index d2450a267f7..455ea3af671 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -7,12 +7,12 @@ import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { Event, Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; -import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { Disposable, IDisposable, dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; -import { FOLDER_SETTINGS_PATH, 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 { 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, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -26,28 +26,61 @@ import { hash } from 'vs/base/common/hash'; export class UserConfiguration extends Disposable { - private readonly parser: ConfigurationModelParser; - private readonly reloadConfigurationScheduler: RunOnceScheduler; - protected readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); + private readonly _onDidInitializeCompleteConfiguration: Emitter = this._register(new Emitter()); + private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; + private readonly userConfiguration: MutableDisposable = this._register(new MutableDisposable()); + private readonly reloadConfigurationScheduler: RunOnceScheduler; + constructor( private readonly userSettingsResource: URI, private readonly scopes: ConfigurationScope[] | undefined, private readonly fileService: IFileService ) { super(); - - this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes); + this.userConfiguration.value = new UserSettings(this.userSettingsResource, this.scopes, this.fileService); + this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule())); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this.reload().then(configurationModel => this._onDidChangeConfiguration.fire(configurationModel)), 50)); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.userSettingsResource))(() => this.reloadConfigurationScheduler.schedule())); + + runWhenIdle(() => this._onDidInitializeCompleteConfiguration.fire(), 5000); + this._register(Event.once(this._onDidInitializeCompleteConfiguration.event)(() => this.reloadConfigurationScheduler.schedule())); } async initialize(): Promise { - return this.reload(); + return this.userConfiguration.value!.loadConfiguration(); } async reload(): Promise { + if (!(this.userConfiguration.value instanceof FileServiceBasedConfigurationWithNames)) { + this.userConfiguration.value = new FileServiceBasedConfigurationWithNames(resources.dirname(this.userSettingsResource), [FOLDER_SETTINGS_NAME, TASKS_CONFIGURATION_KEY], this.scopes, this.fileService); + this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule())); + } + return this.userConfiguration.value!.loadConfiguration(); + } + + reprocess(): ConfigurationModel { + return this.userConfiguration.value!.reprocess(); + } +} + +class UserSettings extends Disposable { + + private readonly parser: ConfigurationModelParser; + protected readonly _onDidChange: Emitter = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + constructor( + private readonly userSettingsResource: URI, + private readonly scopes: ConfigurationScope[] | undefined, + private readonly fileService: IFileService + ) { + super(); + this.parser = new ConfigurationModelParser(this.userSettingsResource.toString(), this.scopes); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.userSettingsResource))(() => this._onDidChange.fire())); + } + + async loadConfiguration(): Promise { try { const content = await this.fileService.readFile(this.userSettingsResource); this.parser.parseContent(content.value.toString() || '{}'); @@ -63,6 +96,127 @@ export class UserConfiguration extends Disposable { } } +class FileServiceBasedConfigurationWithNames extends Disposable { + + private _folderSettingsModelParser: ConfigurationModelParser; + private _standAloneConfigurations: ConfigurationModel[]; + private _cache: ConfigurationModel; + + protected readonly configurationResources: URI[]; + protected changeEventTriggerScheduler: RunOnceScheduler; + protected readonly _onDidChange: Emitter = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + constructor(protected readonly configurationFolder: URI, + private readonly configurationNames: string[], + private readonly scopes: ConfigurationScope[] | undefined, + private fileService: IFileService) { + super(); + this.configurationResources = this.configurationNames.map(name => resources.joinPath(this.configurationFolder, `${name}.json`)); + this._folderSettingsModelParser = new ConfigurationModelParser(this.configurationFolder.toString(), this.scopes); + this._standAloneConfigurations = []; + this._cache = new ConfigurationModel(); + + this.changeEventTriggerScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); + this._register(this.fileService.onFileChanges((e) => this.handleFileEvents(e))); + } + + async loadConfiguration(): Promise { + const configurationContents = await Promise.all(this.configurationResources.map(async resource => { + try { + const content = await this.fileService.readFile(resource); + return content.value.toString(); + } catch (error) { + const exists = await this.fileService.exists(resource); + if (exists) { + errors.onUnexpectedError(error); + } + } + return undefined; + })); + + // reset + this._standAloneConfigurations = []; + this._folderSettingsModelParser.parseContent(''); + + // parse + if (configurationContents[0]) { + this._folderSettingsModelParser.parseContent(configurationContents[0]); + } + for (let index = 1; index < configurationContents.length; index++) { + const contents = configurationContents[index]; + if (contents) { + const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.configurationResources[index].toString(), this.configurationNames[index]); + standAloneConfigurationModelParser.parseContent(contents); + this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel); + } + } + + // Consolidate (support *.json files in the workspace settings folder) + this.consolidate(); + + return this._cache; + } + + reprocess(): ConfigurationModel { + const oldContents = this._folderSettingsModelParser.configurationModel.contents; + this._folderSettingsModelParser.parse(); + if (!equals(oldContents, this._folderSettingsModelParser.configurationModel.contents)) { + this.consolidate(); + } + return this._cache; + } + + private consolidate(): void { + this._cache = this._folderSettingsModelParser.configurationModel.merge(...this._standAloneConfigurations); + } + + protected async handleFileEvents(event: FileChangesEvent): Promise { + const events = event.changes; + let affectedByChanges = false; + + // Find changes that affect workspace configuration files + for (let i = 0, len = events.length; i < len; i++) { + const resource = events[i].resource; + const basename = resources.basename(resource); + const isJson = extname(basename) === '.json'; + const isConfigurationFolderDeleted = (events[i].type === FileChangeType.DELETED && resources.isEqual(resource, this.configurationFolder)); + + if (!isJson && !isConfigurationFolderDeleted) { + continue; // only JSON files or the actual settings folder + } + + const folderRelativePath = this.toFolderRelativePath(resource); + if (!folderRelativePath) { + continue; // event is not inside folder + } + + // Handle case where ".vscode" got deleted + if (isConfigurationFolderDeleted) { + affectedByChanges = true; + break; + } + + // only valid workspace config files + if (this.configurationResources.some(configurationResource => resources.isEqual(configurationResource, resource))) { + affectedByChanges = true; + break; + } + } + + if (affectedByChanges) { + this.changeEventTriggerScheduler.schedule(); + } + } + + private toFolderRelativePath(resource: URI): string | undefined { + if (resources.isEqualOrParent(resource, this.configurationFolder)) { + return resources.relativePath(this.configurationFolder, resource); + } + return undefined; + } +} + export class RemoteUserConfiguration extends Disposable { private readonly _cachedConfiguration: CachedRemoteUserConfiguration; @@ -546,125 +700,12 @@ export interface IFolderConfiguration extends IDisposable { reprocess(): ConfigurationModel; } -class FileServiceBasedFolderConfiguration extends Disposable implements IFolderConfiguration { +class FileServiceBasedFolderConfiguration extends FileServiceBasedConfigurationWithNames implements IFolderConfiguration { - private _folderSettingsModelParser: ConfigurationModelParser; - private _standAloneConfigurations: ConfigurationModel[]; - private _cache: ConfigurationModel; - - private readonly configurationNames: string[]; - protected readonly configurationResources: URI[]; - private changeEventTriggerScheduler: RunOnceScheduler; - protected readonly _onDidChange: Emitter = this._register(new Emitter()); - readonly onDidChange: Event = this._onDidChange.event; - - constructor(protected readonly configurationFolder: URI, workbenchState: WorkbenchState, private fileService: IFileService) { - super(); - - this.configurationNames = [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY]; - this.configurationResources = this.configurationNames.map(name => resources.joinPath(this.configurationFolder, `${name}.json`)); - this._folderSettingsModelParser = new ConfigurationModelParser(FOLDER_SETTINGS_PATH, WorkbenchState.WORKSPACE === workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES); - this._standAloneConfigurations = []; - this._cache = new ConfigurationModel(); - - this.changeEventTriggerScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); - this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); + constructor(configurationFolder: URI, workbenchState: WorkbenchState, fileService: IFileService) { + super(configurationFolder, [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY], WorkbenchState.WORKSPACE === workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService); } - async loadConfiguration(): Promise { - const configurationContents = await Promise.all(this.configurationResources.map(async resource => { - try { - const content = await this.fileService.readFile(resource); - return content.value.toString(); - } catch (error) { - const exists = await this.fileService.exists(resource); - if (exists) { - errors.onUnexpectedError(error); - } - } - return undefined; - })); - - // reset - this._standAloneConfigurations = []; - this._folderSettingsModelParser.parseContent(''); - - // parse - if (configurationContents[0]) { - this._folderSettingsModelParser.parseContent(configurationContents[0]); - } - for (let index = 1; index < configurationContents.length; index++) { - const contents = configurationContents[index]; - if (contents) { - const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.configurationResources[index].toString(), this.configurationNames[index]); - standAloneConfigurationModelParser.parseContent(contents); - this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel); - } - } - - // Consolidate (support *.json files in the workspace settings folder) - this.consolidate(); - - return this._cache; - } - - reprocess(): ConfigurationModel { - const oldContents = this._folderSettingsModelParser.configurationModel.contents; - this._folderSettingsModelParser.parse(); - if (!equals(oldContents, this._folderSettingsModelParser.configurationModel.contents)) { - this.consolidate(); - } - return this._cache; - } - - private consolidate(): void { - this._cache = this._folderSettingsModelParser.configurationModel.merge(...this._standAloneConfigurations); - } - - private handleWorkspaceFileEvents(event: FileChangesEvent): void { - const events = event.changes; - let affectedByChanges = false; - - // Find changes that affect workspace configuration files - for (let i = 0, len = events.length; i < len; i++) { - const resource = events[i].resource; - const basename = resources.basename(resource); - const isJson = extname(basename) === '.json'; - const isConfigurationFolderDeleted = (events[i].type === FileChangeType.DELETED && resources.isEqual(resource, this.configurationFolder)); - - if (!isJson && !isConfigurationFolderDeleted) { - continue; // only JSON files or the actual settings folder - } - - const folderRelativePath = this.toFolderRelativePath(resource); - if (!folderRelativePath) { - continue; // event is not inside folder - } - - // Handle case where ".vscode" got deleted - if (isConfigurationFolderDeleted) { - affectedByChanges = true; - break; - } - - // only valid workspace config files - if (this.configurationResources.some(configurationResource => resources.isEqual(configurationResource, resource))) { - affectedByChanges = true; - break; - } - } - - if (affectedByChanges) { - this.changeEventTriggerScheduler.schedule(); - } - } - - private toFolderRelativePath(resource: URI): string | undefined { - if (resources.isEqualOrParent(resource, this.configurationFolder)) { - return resources.relativePath(this.configurationFolder, resource); - } - return undefined; - } } class CachedFolderConfiguration extends Disposable implements IFolderConfiguration { diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 069871e52db..7cec0a25421 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -28,6 +28,8 @@ export const LAUNCH_CONFIGURATION_KEY = 'launch'; export const WORKSPACE_STANDALONE_CONFIGURATIONS = Object.create(null); WORKSPACE_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/${TASKS_CONFIGURATION_KEY}.json`; WORKSPACE_STANDALONE_CONFIGURATIONS[LAUNCH_CONFIGURATION_KEY] = `${FOLDER_CONFIG_FOLDER_NAME}/${LAUNCH_CONFIGURATION_KEY}.json`; +export const USER_STANDALONE_CONFIGURATIONS = Object.create(null); +USER_STANDALONE_CONFIGURATIONS[TASKS_CONFIGURATION_KEY] = `${TASKS_CONFIGURATION_KEY}.json`; export type ConfigurationKey = { type: 'user' | 'workspaces' | 'folder', key: string }; diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 68bb46913a1..9253110fde1 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -5,6 +5,7 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; +import * as resources from 'vs/base/common/resources'; import * as json from 'vs/base/common/json'; import * as strings from 'vs/base/common/strings'; import { setProperty } from 'vs/base/common/jsonEdit'; @@ -19,7 +20,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService, IConfigurationOverrides, keyFromOverrideIdentifier } from 'vs/platform/configuration/common/configuration'; -import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY, USER_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; @@ -430,8 +431,8 @@ export class ConfigurationEditingService { } if (operation.workspaceStandAloneConfigurationKey) { - // Global tasks and launches are not supported - if (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE) { + // Global launches are not supported + if ((operation.workspaceStandAloneConfigurationKey !== TASKS_CONFIGURATION_KEY) && (target === EditableConfigurationTarget.USER_LOCAL || target === EditableConfigurationTarget.USER_REMOTE)) { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET, target, operation); } } @@ -497,9 +498,10 @@ export class ConfigurationEditingService { // Check for standalone workspace configurations if (config.key) { - const standaloneConfigurationKeys = Object.keys(WORKSPACE_STANDALONE_CONFIGURATIONS); + const standaloneConfigurationMap = target === EditableConfigurationTarget.USER_LOCAL ? USER_STANDALONE_CONFIGURATIONS : WORKSPACE_STANDALONE_CONFIGURATIONS; + const standaloneConfigurationKeys = Object.keys(standaloneConfigurationMap); for (const key of standaloneConfigurationKeys) { - const resource = this.getConfigurationFileResource(target, config, WORKSPACE_STANDALONE_CONFIGURATIONS[key], overrides.resource); + const resource = this.getConfigurationFileResource(target, config, standaloneConfigurationMap[key], overrides.resource); // Check for prefix if (config.key === key) { @@ -536,7 +538,11 @@ export class ConfigurationEditingService { private getConfigurationFileResource(target: EditableConfigurationTarget, config: IConfigurationValue, relativePath: string, resource: URI | null | undefined): URI | null { if (target === EditableConfigurationTarget.USER_LOCAL) { - return this.environmentService.settingsResource; + if (relativePath) { + return resources.joinPath(resources.dirname(this.environmentService.settingsResource), relativePath); + } else { + return this.environmentService.settingsResource; + } } if (target === EditableConfigurationTarget.USER_REMOTE) { return this.remoteSettingsResource; diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index d315118d91c..401d5a63768 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -18,7 +18,7 @@ import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; -import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH } from 'vs/workbench/services/configuration/common/configuration'; +import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH, USER_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -60,6 +60,7 @@ suite('ConfigurationEditingService', () => { let parentDir: string; let workspaceDir: string; let globalSettingsFile: string; + let globalTasksFile: string; let workspaceSettingsDir; suiteSetup(() => { @@ -94,6 +95,7 @@ suite('ConfigurationEditingService', () => { parentDir = path.join(os.tmpdir(), 'vsctests', id); workspaceDir = path.join(parentDir, 'workspaceconfig', id); globalSettingsFile = path.join(workspaceDir, 'settings.json'); + globalTasksFile = path.join(workspaceDir, 'tasks.json'); workspaceSettingsDir = path.join(workspaceDir, '.vscode'); return await mkdirp(workspaceSettingsDir, 493); @@ -149,12 +151,6 @@ suite('ConfigurationEditingService', () => { (error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY)); }); - test('errors cases - invalid target', () => { - return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'tasks.something', value: 'value' }) - .then(() => assert.fail('Should fail with ERROR_INVALID_TARGET'), - (error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_USER_TARGET)); - }); - test('errors cases - no workspace', () => { return setUpServices(true) .then(() => testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'configurationEditing.service.testSetting', value: 'value' })) @@ -162,11 +158,19 @@ suite('ConfigurationEditingService', () => { (error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED)); }); - test('errors cases - invalid configuration', () => { - fs.writeFileSync(globalSettingsFile, ',,,,,,,,,,,,,,'); - return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }) + function errorCasesInvalidConfig(file: string, key: string) { + fs.writeFileSync(file, ',,,,,,,,,,,,,,'); + return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key, value: 'value' }) .then(() => assert.fail('Should fail with ERROR_INVALID_CONFIGURATION'), (error: ConfigurationEditingError) => assert.equal(error.code, ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION)); + } + + test('errors cases - invalid configuration', () => { + return errorCasesInvalidConfig(globalSettingsFile, 'configurationEditing.service.testSetting'); + }); + + test('errors cases - invalid global tasks configuration', () => { + return errorCasesInvalidConfig(globalTasksFile, 'tasks.configurationEditing.service.testSetting'); }); test('errors cases - dirty', () => { @@ -271,44 +275,89 @@ suite('ConfigurationEditingService', () => { }); }); - test('write workspace standalone setting - empty file', () => { - return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks.service.testSetting', value: 'value' }) + function writeStandaloneSettingEmptyFile(configTarget: EditableConfigurationTarget, pathMap: any) { + return testObject.writeConfiguration(configTarget, { key: 'tasks.service.testSetting', value: 'value' }) .then(() => { - const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']); + const target = path.join(workspaceDir, pathMap['tasks']); const contents = fs.readFileSync(target).toString('utf8'); const parsed = json.parse(contents); assert.equal(parsed['service.testSetting'], 'value'); }); + } + + test('write workspace standalone setting - empty file', () => { + return writeStandaloneSettingEmptyFile(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS); }); - test('write workspace standalone setting - existing file', () => { - const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['launch']); + test('write user standalone setting - empty file', () => { + return writeStandaloneSettingEmptyFile(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS); + }); + + function writeStandaloneSettingExitingFile(configTarget: EditableConfigurationTarget, pathMap: any) { + const target = path.join(workspaceDir, pathMap['tasks']); fs.writeFileSync(target, '{ "my.super.setting": "my.super.value" }'); - return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'launch.service.testSetting', value: 'value' }) + return testObject.writeConfiguration(configTarget, { key: 'tasks.service.testSetting', value: 'value' }) .then(() => { const contents = fs.readFileSync(target).toString('utf8'); const parsed = json.parse(contents); assert.equal(parsed['service.testSetting'], 'value'); assert.equal(parsed['my.super.setting'], 'my.super.value'); }); + } + + test('write workspace standalone setting - existing file', () => { + return writeStandaloneSettingExitingFile(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS); }); + test('write user standalone setting - existing file', () => { + return writeStandaloneSettingExitingFile(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS); + }); + + function writeStandaloneSettingEmptyFileFullJson(configTarget: EditableConfigurationTarget, pathMap: any) { + return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } }) + .then(() => { + const target = path.join(workspaceDir, pathMap['tasks']); + const contents = fs.readFileSync(target).toString('utf8'); + const parsed = json.parse(contents); + + assert.equal(parsed['version'], '1.0.0'); + assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); + }); + } + test('write workspace standalone setting - empty file - full JSON', () => { - return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } }) + return writeStandaloneSettingEmptyFileFullJson(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS); + }); + + test('write user standalone setting - empty file - full JSON', () => { + return writeStandaloneSettingEmptyFileFullJson(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS); + }); + + function writeStandaloneSettingExistingFileFullJson(configTarget: EditableConfigurationTarget, pathMap: any) { + const target = path.join(workspaceDir, pathMap['tasks']); + fs.writeFileSync(target, '{ "my.super.setting": "my.super.value" }'); + return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } }) .then(() => { - const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']); const contents = fs.readFileSync(target).toString('utf8'); const parsed = json.parse(contents); assert.equal(parsed['version'], '1.0.0'); assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); }); - }); + } test('write workspace standalone setting - existing file - full JSON', () => { - const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']); - fs.writeFileSync(target, '{ "my.super.setting": "my.super.value" }'); - return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } }) + return writeStandaloneSettingExistingFileFullJson(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS); + }); + + test('write user standalone setting - existing file - full JSON', () => { + return writeStandaloneSettingExistingFileFullJson(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS); + }); + + function writeStandaloneSettingExistingFileWithJsonErrorFullJson(configTarget: EditableConfigurationTarget, pathMap: any) { + const target = path.join(workspaceDir, pathMap['tasks']); + fs.writeFileSync(target, '{ "my.super.setting": '); // invalid JSON + return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } }) .then(() => { const contents = fs.readFileSync(target).toString('utf8'); const parsed = json.parse(contents); @@ -316,23 +365,18 @@ suite('ConfigurationEditingService', () => { assert.equal(parsed['version'], '1.0.0'); assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); }); - }); + } test('write workspace standalone setting - existing file with JSON errors - full JSON', () => { - const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']); - fs.writeFileSync(target, '{ "my.super.setting": '); // invalid JSON - return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask' }] } }) - .then(() => { - const contents = fs.readFileSync(target).toString('utf8'); - const parsed = json.parse(contents); - - assert.equal(parsed['version'], '1.0.0'); - assert.equal(parsed['tasks'][0]['taskName'], 'myTask'); - }); + return writeStandaloneSettingExistingFileWithJsonErrorFullJson(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS); }); - test('write workspace standalone setting should replace complete file', () => { - const target = path.join(workspaceDir, WORKSPACE_STANDALONE_CONFIGURATIONS['tasks']); + test('write user standalone setting - existing file with JSON errors - full JSON', () => { + return writeStandaloneSettingExistingFileWithJsonErrorFullJson(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS); + }); + + function writeStandaloneSettingShouldReplace(configTarget: EditableConfigurationTarget, pathMap: any) { + const target = path.join(workspaceDir, pathMap['tasks']); fs.writeFileSync(target, `{ "version": "1.0.0", "tasks": [ @@ -344,11 +388,19 @@ suite('ConfigurationEditingService', () => { } ] }`); - return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] } }) + return testObject.writeConfiguration(configTarget, { key: 'tasks', value: { 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] } }) .then(() => { const actual = fs.readFileSync(target).toString('utf8'); const expected = JSON.stringify({ 'version': '1.0.0', tasks: [{ 'taskName': 'myTask1' }] }, null, '\t'); assert.equal(actual, expected); }); + } + + test('write workspace standalone setting should replace complete file', () => { + return writeStandaloneSettingShouldReplace(EditableConfigurationTarget.WORKSPACE, WORKSPACE_STANDALONE_CONFIGURATIONS); + }); + + test('write user standalone setting should replace complete file', () => { + return writeStandaloneSettingShouldReplace(EditableConfigurationTarget.USER_LOCAL, USER_STANDALONE_CONFIGURATIONS); }); }); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index e0d180d5470..f78eacb60af 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -47,6 +47,7 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { timeout } from 'vs/base/common/async'; class TestEnvironmentService extends NativeWorkbenchEnvironmentService { @@ -717,7 +718,7 @@ suite('WorkspaceService - Initialization', () => { suite('WorkspaceConfigurationService - Folder', () => { - let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: IConfigurationService, globalSettingsFile: string; + let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: IConfigurationService, globalSettingsFile: string, globalTasksFile: string; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); suiteSetup(() => { @@ -756,6 +757,7 @@ suite('WorkspaceConfigurationService - Folder', () => { parentResource = parentDir; workspaceDir = folderDir; globalSettingsFile = path.join(parentDir, 'settings.json'); + globalTasksFile = path.join(parentDir, 'tasks.json'); const instantiationService = workbenchInstantiationService(); const environmentService = new TestEnvironmentService(URI.file(parentDir)); @@ -1074,6 +1076,17 @@ suite('WorkspaceConfigurationService - Folder', () => { .then(() => assert.ok(target.called)); }); + test('no change event when there are no global tasks', async () => { + const target = sinon.spy(); + testObject.onDidChangeConfiguration(target); + await timeout(500); + assert.ok(target.notCalled); + }); + + test('change event when there are global tasks', () => { + fs.writeFileSync(globalTasksFile, '{ "version": "1.0.0", "tasks": [{ "taskName": "myTask" }'); + return new Promise((c) => testObject.onDidChangeConfiguration(() => c())); + }); }); suite('WorkspaceConfigurationService-Multiroot', () => { @@ -1430,7 +1443,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { }); }); - test('inspect tasks configuration', () => { + test('inspect tasks configuration', async () => { const expectedTasksConfiguration = { 'version': '2.0.0', 'tasks': [ @@ -1445,12 +1458,10 @@ suite('WorkspaceConfigurationService-Multiroot', () => { } ] }; - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'tasks', value: expectedTasksConfiguration }], true) - .then(() => testObject.reloadConfiguration()) - .then(() => { - const actual = testObject.inspect('tasks').workspaceValue; - assert.deepEqual(actual, expectedTasksConfiguration); - }); + await jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'tasks', value: expectedTasksConfiguration }], true); + await testObject.reloadConfiguration(); + const actual = testObject.inspect('tasks').workspaceValue; + assert.deepEqual(actual, expectedTasksConfiguration); }); test('update user configuration', () => { From 8b91600b08f5d88ba9d79785a7014c67f492629a Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 17 Dec 2019 15:49:27 +0100 Subject: [PATCH 061/241] respect onDidOpenResource passed element #87082 --- .../workbench/contrib/files/browser/views/explorerView.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index bd1e27a2150..a08b8ef9524 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -414,18 +414,18 @@ export class ExplorerView extends ViewPane { this._register(explorerNavigator); // Open when selecting via keyboard this._register(explorerNavigator.onDidOpenResource(async e => { - const selection = this.tree.getSelection(); + const element = e.element; // Do not react if the user is expanding selection via keyboard. // Check if the item was previously also selected, if yes the user is simply expanding / collapsing current selection #66589. const shiftDown = e.browserEvent instanceof KeyboardEvent && e.browserEvent.shiftKey; - if (selection.length === 1 && !shiftDown) { - if (selection[0].isDirectory || this.explorerService.isEditable(undefined)) { + if (element && !shiftDown) { + if (element.isDirectory || this.explorerService.isEditable(undefined)) { // Do not react if user is clicking on explorer items while some are being edited #70276 // Do not react if clicking on directories return; } this.telemetryService.publicLog2('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'explorer' }); - await this.editorService.openEditor({ resource: selection[0].resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + await this.editorService.openEditor({ resource: element.resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } })); From d6bfb1ff8e53c12d77595a8794409de70c70be25 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 17 Dec 2019 16:26:29 +0100 Subject: [PATCH 062/241] files - carry resource for each error to track down issues (for #86843) --- src/vs/platform/files/common/fileService.ts | 64 ++++++++++----------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 6edd9ff882a..870af29a60a 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -113,7 +113,7 @@ export class FileService extends Disposable implements IFileService { if (!provider) { const error = new Error(); error.name = 'ENOPRO'; - error.message = localize('noProviderFound', "No file system provider found for {0}", resource.toString()); + error.message = localize('noProviderFound', "No file system provider found for resource '{0}'", resource.toString()); throw error; } @@ -128,7 +128,7 @@ export class FileService extends Disposable implements IFileService { return provider; } - throw new Error('Provider neither has FileReadWrite, FileReadStream nor FileOpenReadWriteClose capability which is needed for the read operation.'); + throw new Error(`Provider for scheme '${resource.scheme}' neither has FileReadWrite, FileReadStream nor FileOpenReadWriteClose capability which is needed for the read operation.`); } private async withWriteProvider(resource: URI): Promise { @@ -138,7 +138,7 @@ export class FileService extends Disposable implements IFileService { return provider; } - throw new Error('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed for the write operation.'); + throw new Error(`Provider for scheme '${resource.scheme}' neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed for the write operation.`); } //#endregion @@ -160,10 +160,7 @@ export class FileService extends Disposable implements IFileService { // Specially handle file not found case as file operation result if (toFileSystemProviderErrorCode(error) === FileSystemProviderErrorCode.FileNotFound) { - throw new FileOperationError( - localize('fileNotFoundError', "File not found ({0})", this.resourceForError(resource)), - FileOperationResult.FILE_NOT_FOUND - ); + throw new FileOperationError(localize('fileNotFoundError', "File not found ({0})", this.resourceForError(resource)), FileOperationResult.FILE_NOT_FOUND); } // Bubble up any other error as is @@ -304,7 +301,7 @@ export class FileService extends Disposable implements IFileService { } async writeFile(resource: URI, bufferOrReadableOrStream: VSBuffer | VSBufferReadable | VSBufferReadableStream, options?: IWriteFileOptions): Promise { - const provider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(resource)); + const provider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(resource), resource); try { @@ -338,7 +335,7 @@ export class FileService extends Disposable implements IFileService { await this.doWriteBuffered(provider, resource, bufferOrReadableOrStream instanceof VSBuffer ? bufferToReadable(bufferOrReadableOrStream) : bufferOrReadableOrStream); } } catch (error) { - throw new FileOperationError(localize('err.write', "Unable to write file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); + throw new FileOperationError(localize('err.write', "Unable to write file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); } return this.resolve(resource, { resolveMetadata: true }); @@ -354,7 +351,7 @@ export class FileService extends Disposable implements IFileService { // file cannot be directory if ((stat.type & FileType.Directory) !== 0) { - throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options); + throw new FileOperationError(localize('fileIsDirectoryError', "Expected file '{0}' is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options); } // Dirty write prevention: if the file on disk has been changed and does not match our expected @@ -453,14 +450,14 @@ export class FileService extends Disposable implements IFileService { value: fileStream }; } catch (error) { - throw new FileOperationError(localize('err.read', "Unable to read file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); + throw new FileOperationError(localize('err.read', "Unable to read file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options); } } private readFileStreamed(provider: IFileSystemProviderWithFileReadStreamCapability, resource: URI, token: CancellationToken, options: IReadFileOptions = Object.create(null)): VSBufferReadableStream { const fileStream = provider.readFileStream(resource, options, token); - return this.transformFileReadStream(fileStream, options); + return this.transformFileReadStream(resource, fileStream, options); } private readFileBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, token: CancellationToken, options: IReadFileOptions = Object.create(null)): VSBufferReadableStream { @@ -469,13 +466,13 @@ export class FileService extends Disposable implements IFileService { bufferSize: this.BUFFER_SIZE }, token); - return this.transformFileReadStream(fileStream, options); + return this.transformFileReadStream(resource, fileStream, options); } - private transformFileReadStream(stream: ReadableStreamEvents, options: IReadFileOptions): VSBufferReadableStream { + private transformFileReadStream(resource: URI, stream: ReadableStreamEvents, options: IReadFileOptions): VSBufferReadableStream { return transform(stream, { data: data => data instanceof VSBuffer ? data : VSBuffer.wrap(data), - error: error => new FileOperationError(localize('err.read', "Unable to read file ({0})", ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options) + error: error => new FileOperationError(localize('err.read', "Unable to read file '{0}' ({1})", this.resourceForError(resource), ensureFileSystemProviderError(error).toString()), toFileOperationResult(error), options) }, data => VSBuffer.concat(data)); } @@ -493,7 +490,7 @@ export class FileService extends Disposable implements IFileService { } // Throw if file is too large to load - this.validateReadFileLimits(buffer.byteLength, options); + this.validateReadFileLimits(resource, buffer.byteLength, options); return bufferToStream(VSBuffer.wrap(buffer)); } @@ -503,7 +500,7 @@ export class FileService extends Disposable implements IFileService { // Throw if resource is a directory if (stat.isDirectory) { - throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options); + throw new FileOperationError(localize('fileIsDirectoryError', "Expected file '{0}' is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options); } // Throw if file not modified since (unless disabled) @@ -512,12 +509,12 @@ export class FileService extends Disposable implements IFileService { } // Throw if file is too large to load - this.validateReadFileLimits(stat.size, options); + this.validateReadFileLimits(resource, stat.size, options); return stat; } - private validateReadFileLimits(size: number, options?: IReadFileOptions): void { + private validateReadFileLimits(resource: URI, size: number, options?: IReadFileOptions): void { if (options?.limits) { let tooLargeErrorResult: FileOperationResult | undefined = undefined; @@ -530,7 +527,7 @@ export class FileService extends Disposable implements IFileService { } if (typeof tooLargeErrorResult === 'number') { - throw new FileOperationError(localize('fileTooLargeError', "File is too large to open"), tooLargeErrorResult); + throw new FileOperationError(localize('fileTooLargeError', "File '{0}' is too large to open", this.resourceForError(resource)), tooLargeErrorResult); } } } @@ -540,8 +537,8 @@ export class FileService extends Disposable implements IFileService { //#region Move/Copy/Delete/Create Folder async move(source: URI, target: URI, overwrite?: boolean): Promise { - const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(source)); - const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target)); + const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(source), source); + const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target), target); // move const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'move', !!overwrite); @@ -555,7 +552,7 @@ export class FileService extends Disposable implements IFileService { async copy(source: URI, target: URI, overwrite?: boolean): Promise { const sourceProvider = await this.withReadProvider(source); - const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target)); + const targetProvider = this.throwIfFileSystemIsReadonly(await this.withWriteProvider(target), target); // copy const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', !!overwrite); @@ -678,11 +675,11 @@ export class FileService extends Disposable implements IFileService { } if (isSameResourceWithDifferentPathCase && mode === 'copy') { - throw new Error(localize('unableToMoveCopyError1', "Unable to copy when source is same as target with different path case on a case insensitive file system")); + throw new Error(localize('unableToMoveCopyError1', "Unable to copy when source '{0}' is same as target '{1}' with different path case on a case insensitive file system", this.resourceForError(source), this.resourceForError(target))); } if (!isSameResourceWithDifferentPathCase && isEqualOrParent(target, source, !isPathCaseSensitive)) { - throw new Error(localize('unableToMoveCopyError2', "Unable to move/copy when source is parent of target.")); + throw new Error(localize('unableToMoveCopyError2', "Unable to move/copy when source '{0}' is parent of target '{1}'.", this.resourceForError(source), this.resourceForError(target))); } } @@ -709,7 +706,7 @@ export class FileService extends Disposable implements IFileService { } async createFolder(resource: URI): Promise { - const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource)); + const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource), resource); // mkdir recursively await this.mkdirp(provider, resource); @@ -729,7 +726,7 @@ export class FileService extends Disposable implements IFileService { try { const stat = await provider.stat(directory); if ((stat.type & FileType.Directory) === 0) { - throw new Error(localize('mkdirExistsError', "{0} exists, but is not a directory", this.resourceForError(directory))); + throw new Error(localize('mkdirExistsError', "Path '{0}' already exists, but is not a directory", this.resourceForError(directory))); } break; // we have hit a directory that exists -> good @@ -756,21 +753,18 @@ export class FileService extends Disposable implements IFileService { } async del(resource: URI, options?: { useTrash?: boolean; recursive?: boolean; }): Promise { - const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource)); + const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource), resource); // Validate trash support const useTrash = !!options?.useTrash; if (useTrash && !(provider.capabilities & FileSystemProviderCapabilities.Trash)) { - throw new Error(localize('err.trash', "Provider does not support trash.")); + throw new Error(localize('err.trash', "Provider for scheme '{0}' does not support trash.", resource.scheme)); } // Validate delete const exists = await this.exists(resource); if (!exists) { - throw new FileOperationError( - localize('fileNotFoundError', "File not found ({0})", this.resourceForError(resource)), - FileOperationResult.FILE_NOT_FOUND - ); + throw new FileOperationError(localize('fileNotFoundError', "File not found ({0})", this.resourceForError(resource)), FileOperationResult.FILE_NOT_FOUND); } // Validate recursive @@ -1059,9 +1053,9 @@ export class FileService extends Disposable implements IFileService { await this.doWriteUnbuffered(targetProvider, target, buffer); } - protected throwIfFileSystemIsReadonly(provider: T): T { + protected throwIfFileSystemIsReadonly(provider: T, resource: URI): T { if (provider.capabilities & FileSystemProviderCapabilities.Readonly) { - throw new FileOperationError(localize('err.readonly', "Resource can not be modified."), FileOperationResult.FILE_PERMISSION_DENIED); + throw new FileOperationError(localize('err.readonly', "Resource '{0}' can not be modified.", this.resourceForError(resource)), FileOperationResult.FILE_PERMISSION_DENIED); } return provider; From 85f587ab646c2d7bcbe902aee0cb48ccfcfdb4fe Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 17 Dec 2019 17:45:21 +0100 Subject: [PATCH 063/241] Windows ssh remote: Cannot use `\` in explorer new file dialog to create directories fixes microsoft/vscode-remote-release#1584 --- src/vs/workbench/contrib/files/browser/fileActions.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index a2ae86d7408..5c0bb70b565 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/fileactions'; import * as nls from 'vs/nls'; import { isWindows, isWeb } from 'vs/base/common/platform'; import * as extpath from 'vs/base/common/extpath'; -import { extname, basename } from 'vs/base/common/path'; +import { extname, basename, posix, win32 } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -45,6 +45,7 @@ import { asDomUri, triggerDownload } from 'vs/base/browser/dom'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { ILabelService } from 'vs/platform/label/common/label'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -854,6 +855,7 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole const editorService = accessor.get(IEditorService); const viewletService = accessor.get(IViewletService); const notificationService = accessor.get(INotificationService); + const labelService = accessor.get(ILabelService); await viewletService.openViewlet(VIEWLET_ID, true); @@ -876,7 +878,10 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole folder.addChild(newStat); const onSuccess = (value: string): Promise => { - const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value)); + const separator = labelService.getSeparator(folder.resource.scheme); + const resource = folder.resource.with({ path: separator === '/' ? posix.join(folder.resource.path, value) : win32.join(folder.resource.path, value) }); + const createPromise = isFolder ? fileService.createFolder(resource) : textFileService.create(resource); + return createPromise.then(created => { refreshIfSeparator(value, explorerService); return isFolder ? explorerService.select(created.resource, true) From 6219b5966c01237a087fbce087f5d4b6d07d1bce Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 Dec 2019 16:03:00 +0100 Subject: [PATCH 064/241] fix scope check --- .../api/common/extHostConfiguration.ts | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 8206164e947..6d875cca104 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -19,6 +19,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ILogService } from 'vs/platform/log/common/log'; import { Workspace } from 'vs/platform/workspace/common/workspace'; +import { URI } from 'vs/base/common/uri'; function lookUp(tree: any, key: string) { if (key) { @@ -41,24 +42,42 @@ type ConfigurationInspect = { function isTextDocument(thing: any): thing is vscode.TextDocument { return thing - && thing.uri instanceof vscode.Uri + && thing.uri instanceof URI && (!thing.languageId || typeof thing.languageId === 'string'); } function isWorkspaceFolder(thing: any): thing is vscode.WorkspaceFolder { return thing - && thing.uri instanceof vscode.Uri + && thing.uri instanceof URI && (!thing.name || typeof thing.name === 'string') && (!thing.index || typeof thing.index === 'number'); } +function isUri(thing: any): thing is vscode.Uri { + return thing + && thing.uri instanceof URI; +} + +function isResourceLanguage(thing: any): thing is { resource: URI, languageId: string } { + return thing + && thing.resource instanceof URI + && (!thing.languageId || typeof thing.languageId === 'string'); +} + function scopeToOverrides(scope: vscode.ConfigurationScope | undefined | null): IConfigurationOverrides | undefined { - return scope ? - scope instanceof vscode.Uri ? { resource: scope } - : isWorkspaceFolder(scope) ? { resource: scope.uri } - : isTextDocument(scope) ? { resource: scope.uri, overrideIdentifier: scope.languageId } - : scope - : undefined; + if (isUri(scope)) { + return { resource: scope }; + } + if (isWorkspaceFolder(scope)) { + return { resource: scope.uri }; + } + if (isTextDocument(scope)) { + return { resource: scope.uri, overrideIdentifier: scope.languageId }; + } + if (isResourceLanguage(scope)) { + return scope; + } + return undefined; } export class ExtHostConfiguration implements ExtHostConfigurationShape { From e289d2ac97e3f4f0fb3a0f73ea9e1e3fa60ddcad Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 Dec 2019 17:52:08 +0100 Subject: [PATCH 065/241] fix tests --- .../api/browser/mainThreadConfiguration.ts | 10 ++++----- .../api/common/extHostConfiguration.ts | 21 ++++++++++++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadConfiguration.ts b/src/vs/workbench/api/browser/mainThreadConfiguration.ts index 21397e56218..5c1dd842f6d 100644 --- a/src/vs/workbench/api/browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/browser/mainThreadConfiguration.ts @@ -60,15 +60,15 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { const configurationValue = this.configurationService.inspect(key, overrides); switch (target) { case ConfigurationTarget.MEMORY: - return this._updateValue(key, value, target, configurationValue.memory?.override, overrides, scopeToLanguage); + return this._updateValue(key, value, target, configurationValue?.memory?.override, overrides, scopeToLanguage); case ConfigurationTarget.WORKSPACE_FOLDER: - return this._updateValue(key, value, target, configurationValue.workspaceFolder?.override, overrides, scopeToLanguage); + return this._updateValue(key, value, target, configurationValue?.workspaceFolder?.override, overrides, scopeToLanguage); case ConfigurationTarget.WORKSPACE: - return this._updateValue(key, value, target, configurationValue.workspace?.override, overrides, scopeToLanguage); + return this._updateValue(key, value, target, configurationValue?.workspace?.override, overrides, scopeToLanguage); case ConfigurationTarget.USER_REMOTE: - return this._updateValue(key, value, target, configurationValue.userRemote?.override, overrides, scopeToLanguage); + return this._updateValue(key, value, target, configurationValue?.userRemote?.override, overrides, scopeToLanguage); default: - return this._updateValue(key, value, target, configurationValue.userLocal?.override, overrides, scopeToLanguage); + return this._updateValue(key, value, target, configurationValue?.userLocal?.override, overrides, scopeToLanguage); } } diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 6d875cca104..e03262a0b51 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -34,10 +34,16 @@ function lookUp(tree: any, key: string) { type ConfigurationInspect = { key: string; + defaultValue?: T; globalValue?: T; - workspaceValue?: T; - workspaceFolderValue?: T; + workspaceValue?: T, + workspaceFolderValue?: T, + + defaultLanguageValue?: T; + userLanguageValue?: T; + workspaceLanguageValue?: T; + workspaceFolderLanguageValue?: T; }; function isTextDocument(thing: any): thing is vscode.TextDocument { @@ -54,8 +60,7 @@ function isWorkspaceFolder(thing: any): thing is vscode.WorkspaceFolder { } function isUri(thing: any): thing is vscode.Uri { - return thing - && thing.uri instanceof URI; + return thing instanceof URI; } function isResourceLanguage(thing: any): thing is { resource: URI, languageId: string } { @@ -245,10 +250,16 @@ export class ExtHostConfigProvider { if (config) { return { key, + defaultValue: config.defaultValue, globalValue: config.userValue, workspaceValue: config.workspaceValue, - workspaceFolderValue: config.workspaceFolderValue + workspaceFolderValue: config.workspaceFolderValue, + + defaultLanguageValue: config.default?.override, + userLanguageValue: config.user?.override, + workspaceLanguageValue: config.workspace?.override, + workspaceFolderLanguageValue: config.workspaceFolder?.override, }; } return undefined; From 4f80a383d6710599df20ab09f84da4e3c183e932 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 17 Dec 2019 17:55:37 +0100 Subject: [PATCH 066/241] fixes #87185 --- .../workbench/contrib/files/browser/views/explorerViewer.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index c28b54aaaba..0328d0f5fc2 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -160,11 +160,13 @@ export class CompressedNavigationController implements ICompressedNavigationCont private updateLabels(templateData: IFileTemplateData): void { this._labels = Array.from(templateData.container.querySelectorAll('.label-name')) as HTMLElement[]; - for (let i = 0; i < this.items.length; i++) { + for (let i = 0; i < this.labels.length; i++) { this.labels[i].setAttribute('aria-label', this.items[i].name); } - DOM.addClass(this.labels[this._index], 'active'); + if (this._index < this.labels.length) { + DOM.addClass(this.labels[this._index], 'active'); + } } previous(): void { From acc5f32ee26443d63977d3d2f8619a7ba3262a02 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 17 Dec 2019 17:13:20 +0100 Subject: [PATCH 067/241] Add IWebWorkerOptions.keepIdleModels (microsoft/monaco-editor#1401) --- src/vs/editor/common/services/editorWorkerServiceImpl.ts | 8 +++++--- src/vs/editor/common/services/webWorker.ts | 7 ++++++- src/vs/monaco.d.ts | 5 +++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index a4a25cf7865..e1e5804fdc0 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -236,7 +236,7 @@ class WorkerManager extends Disposable { public withWorker(): Promise { this._lastWorkerUsedTime = (new Date()).getTime(); if (!this._editorWorkerClient) { - this._editorWorkerClient = new EditorWorkerClient(this._modelService, 'editorWorkerService'); + this._editorWorkerClient = new EditorWorkerClient(this._modelService, false, 'editorWorkerService'); } return Promise.resolve(this._editorWorkerClient); } @@ -374,13 +374,15 @@ export class EditorWorkerHost { export class EditorWorkerClient extends Disposable { private readonly _modelService: IModelService; + private readonly _keepIdleModels: boolean; private _worker: IWorkerClient | null; private readonly _workerFactory: DefaultWorkerFactory; private _modelManager: EditorModelManager | null; - constructor(modelService: IModelService, label: string | undefined) { + constructor(modelService: IModelService, keepIdleModels: boolean, label: string | undefined) { super(); this._modelService = modelService; + this._keepIdleModels = keepIdleModels; this._workerFactory = new DefaultWorkerFactory(label); this._worker = null; this._modelManager = null; @@ -417,7 +419,7 @@ export class EditorWorkerClient extends Disposable { private _getOrCreateModelManager(proxy: EditorSimpleWorker): EditorModelManager { if (!this._modelManager) { - this._modelManager = this._register(new EditorModelManager(proxy, this._modelService, false)); + this._modelManager = this._register(new EditorModelManager(proxy, this._modelService, this._keepIdleModels)); } return this._modelManager; } diff --git a/src/vs/editor/common/services/webWorker.ts b/src/vs/editor/common/services/webWorker.ts index 0ae55dffaa0..55dfe84af39 100644 --- a/src/vs/editor/common/services/webWorker.ts +++ b/src/vs/editor/common/services/webWorker.ts @@ -53,6 +53,11 @@ export interface IWebWorkerOptions { * An object that can be used by the web worker to make calls back to the main thread. */ host?: any; + /** + * Keep idle models. + * Defaults to false, which means that idle models will stop syncing after a while. + */ + keepIdleModels?: boolean; } class MonacoWebWorkerImpl extends EditorWorkerClient implements MonacoWebWorker { @@ -63,7 +68,7 @@ class MonacoWebWorkerImpl extends EditorWorkerClient implements MonacoWebWork private _foreignProxy: Promise | null; constructor(modelService: IModelService, opts: IWebWorkerOptions) { - super(modelService, opts.label); + super(modelService, opts.keepIdleModels || false, opts.label); this._foreignModuleId = opts.moduleId; this._foreignModuleCreateData = opts.createData || null; this._foreignModuleHost = opts.host || null; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 58e9b0fa73d..27cece82f20 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -996,6 +996,11 @@ declare namespace monaco.editor { * An object that can be used by the web worker to make calls back to the main thread. */ host?: any; + /** + * Keep idle models. + * Defaults to false, which means that idle models will stop syncing after a while. + */ + keepIdleModels?: boolean; } /** From 53eff05b4fa24afac93898b9affbe3b7df25da32 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 17 Dec 2019 17:57:10 +0100 Subject: [PATCH 068/241] do not expose resource-language scope --- src/vs/workbench/api/common/configurationExtensionPoint.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 88b4782cca3..ebd5a6b3d01 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -39,17 +39,16 @@ const configurationEntrySchema: IJSONSchema = { }, scope: { type: 'string', - enum: ['application', 'machine', 'window', 'resource', 'resource-language', 'machine-overridable'], + enum: ['application', 'machine', 'window', 'resource', 'machine-overridable'], default: 'window', enumDescriptions: [ nls.localize('scope.application.description', "Configuration that can be configured only in the user settings."), nls.localize('scope.machine.description', "Configuration that can be configured only in the user settings when the extension is running locally, or only in the remote settings when the extension is running remotely."), nls.localize('scope.window.description', "Configuration that can be configured in the user, remote or workspace settings."), nls.localize('scope.resource.description', "Configuration that can be configured in the user, remote, workspace or folder settings."), - nls.localize('scope.resource-language.description', "Resource configuration that can be configured also in language specific settings."), nls.localize('scope.machine-overridable.description', "Machine configuration that can be configured also in workspace or folder settings.") ], - description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `application`, `machine`, `window`, `resource`, `resource-language` and `machine-overridable`.") + description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `application`, `machine`, `window`, `resource`, and `machine-overridable`.") }, enumDescriptions: { type: 'array', From 8eb835c46399c28301d1c6019c9391ccc822d83f Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Tue, 17 Dec 2019 08:59:50 -0800 Subject: [PATCH 069/241] Support ViewPaneContainer in Panel (#87191) supporting viewpanecontainer in panels with PaneCompositePanel and ViewContainerLocation --- src/vs/workbench/api/browser/viewsExtensionPoint.ts | 4 ++-- src/vs/workbench/browser/panel.ts | 3 +++ .../browser/parts/views/viewPaneContainer.ts | 6 ++++-- src/vs/workbench/common/views.ts | 13 +++++++++---- src/vs/workbench/contrib/debug/common/debug.ts | 4 ++-- .../contrib/extensions/common/extensions.ts | 4 ++-- src/vs/workbench/contrib/files/common/files.ts | 4 ++-- .../contrib/remote/common/remote.contribution.ts | 3 ++- src/vs/workbench/contrib/scm/common/scm.ts | 4 ++-- src/vs/workbench/services/search/common/search.ts | 4 ++-- .../test/browser/parts/views/views.test.ts | 4 ++-- 11 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 98a6468b92c..16b29f60591 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -8,7 +8,7 @@ import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor } from 'vs/workbench/common/views'; +import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation } from 'vs/workbench/common/views'; import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; @@ -313,7 +313,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { if (!viewContainer) { - viewContainer = this.viewContainersRegistry.registerViewContainer(id, true, extensionId); + viewContainer = this.viewContainersRegistry.registerViewContainer(id, ViewContainerLocation.Sidebar, true, extensionId); class CustomViewPaneContainer extends ViewPaneContainer { constructor( diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index 666029c78ae..1f83af0e2db 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -12,9 +12,12 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro import { IConstructorSignature0, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { isAncestor } from 'vs/base/browser/dom'; import { assertIsDefined } from 'vs/base/common/types'; +import { PaneComposite } from 'vs/workbench/browser/panecomposite'; export abstract class Panel extends Composite implements IPanel { } +export abstract class PaneCompositePanel extends PaneComposite implements IPanel { } + /** * A panel descriptor is a leightweight descriptor of a panel in the workbench. */ diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 0dd8c50cffb..5d2b3bbefa6 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -36,7 +36,8 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; import { Component } from 'vs/workbench/common/component'; -import { Extensions, ViewletRegistry } from 'vs/workbench/browser/viewlet'; +import { Extensions as ViewletExtensions, ViewletRegistry } from 'vs/workbench/browser/viewlet'; +import { Extensions as PanelExtensions, PanelRegistry } from 'vs/workbench/browser/panel'; export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -344,7 +345,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getTitle(): string { - let title = Registry.as(Extensions.Viewlets).getViewlet(this.getId()).name; + const composite = Registry.as(ViewletExtensions.Viewlets).getViewlet(this.getId()) || Registry.as(PanelExtensions.Panels).getPanel(this.getId()); + let title = composite.name; if (this.isSingleView()) { const paneItemTitle = this.paneItems[0].pane.title; diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 119e583c046..3bc269ceaa9 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -26,6 +26,11 @@ export namespace Extensions { export const ViewsRegistry = 'workbench.registry.view'; } +export enum ViewContainerLocation { + Sidebar, + Panel +} + export interface IViewContainersRegistry { /** * An event that is triggerred when a view container is registered. @@ -50,7 +55,7 @@ export interface IViewContainersRegistry { * * @returns the registered ViewContainer. */ - registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer; + registerViewContainer(id: string, location: ViewContainerLocation, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer; /** * Deregisters the given view container @@ -71,7 +76,7 @@ interface ViewOrderDelegate { } export class ViewContainer { - protected constructor(readonly id: string, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier, readonly orderDelegate?: ViewOrderDelegate) { } + protected constructor(readonly id: string, readonly location: ViewContainerLocation, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier, readonly orderDelegate?: ViewOrderDelegate) { } } class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry { @@ -88,7 +93,7 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe return values(this.viewContainers); } - registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer { + registerViewContainer(id: string, location: ViewContainerLocation, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer { const existing = this.viewContainers.get(id); if (existing) { return existing; @@ -96,7 +101,7 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe const viewContainer = new class extends ViewContainer { constructor() { - super(id, !!hideIfEmpty, extensionId, viewOrderDelegate); + super(id, location, !!hideIfEmpty, extensionId, viewOrderDelegate); } }; this.viewContainers.set(id, viewContainer); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 10118761779..c3901bcaa0c 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -24,11 +24,11 @@ import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainer } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.debug'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export const VARIABLES_VIEW_ID = 'workbench.debug.variablesView'; export const WATCH_VIEW_ID = 'workbench.debug.watchExpressionsView'; diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 373ee7b0430..ada5d746c56 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -14,11 +14,11 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; -import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.extensions'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export const EXTENSIONS_CONFIG = '.vscode/extensions.json'; diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 86a60a95d14..82c461f53ac 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -17,7 +17,7 @@ import { IModeService, ILanguageSelection } from 'vs/editor/common/services/mode import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, IEditableData } from 'vs/workbench/common/views'; +import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, IEditableData, ViewContainerLocation } from 'vs/workbench/common/views'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; @@ -33,7 +33,7 @@ export const VIEWLET_ID = 'workbench.view.explorer'; /** * Explorer viewlet container. */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export interface IExplorerService { _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 3135630c1b4..e464b2f79a9 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -16,11 +16,12 @@ import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/s import { localize } from 'vs/nls'; import { joinPath } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation } from 'vs/workbench/common/views'; export const VIEWLET_ID = 'workbench.view.remote'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( VIEWLET_ID, + ViewContainerLocation.Sidebar, true, undefined, { diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 2a6b8d33430..9f73e62e8d7 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -9,11 +9,11 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Command } from 'vs/editor/common/modes'; import { ISequence } from 'vs/base/common/sequence'; -import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.scm'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); export interface IBaselineResourceProvider { getBaselineResource(resource: URI): Promise; diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 4ef600d4629..a19c66244f9 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -16,7 +16,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { Event } from 'vs/base/common/event'; import { relative } from 'vs/base/common/path'; -import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.search'; @@ -25,7 +25,7 @@ export const VIEW_ID = 'workbench.view.search'; /** * Search viewlet container. */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, true); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar, true); export const ISearchService = createDecorator('searchService'); diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index dbfe6851bae..d82935021ff 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { ContributableViewsModel, ViewsService, IViewState } from 'vs/workbench/browser/parts/views/views'; -import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { move } from 'vs/base/common/arrays'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -15,7 +15,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import sinon = require('sinon'); -const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer('test'); +const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer('test', ViewContainerLocation.Sidebar); const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); class ViewDescriptorSequence { From 043da28ddaddfda383bc5e4cce7ee972ce482b91 Mon Sep 17 00:00:00 2001 From: Simon Siefke Date: Tue, 17 Dec 2019 20:03:00 +0100 Subject: [PATCH 070/241] fix: typescript completion item kind for local function (#87182) --- .../typescript-language-features/src/features/completions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index da13f97f3db..5fdefe1b2ad 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -161,6 +161,7 @@ class MyCompletionItem extends vscode.CompletionItem { case PConst.Kind.memberSetAccessor: return vscode.CompletionItemKind.Field; case PConst.Kind.function: + case PConst.Kind.localFunction: return vscode.CompletionItemKind.Function; case PConst.Kind.memberFunction: case PConst.Kind.constructSignature: From 1b293495e76d71ef4d38d2da57a88eac339ae468 Mon Sep 17 00:00:00 2001 From: rzj17 Date: Mon, 16 Dec 2019 21:38:32 +0000 Subject: [PATCH 071/241] Refactor SortOrder & SortOrderConfiguration into one enum --- .../contrib/files/browser/files.contribution.ts | 6 +++--- .../contrib/files/common/explorerModel.ts | 4 ++-- .../contrib/files/common/explorerService.ts | 8 ++++---- src/vs/workbench/contrib/files/common/files.ts | 16 +++++++--------- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 615c14812d8..7d0ec14feb3 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; -import { VIEWLET_ID, SortOrderConfiguration, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, SortOrder, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; @@ -396,8 +396,8 @@ configurationRegistry.registerConfiguration({ }, 'explorer.sortOrder': { 'type': 'string', - 'enum': [SortOrderConfiguration.DEFAULT, SortOrderConfiguration.MIXED, SortOrderConfiguration.FILES_FIRST, SortOrderConfiguration.TYPE, SortOrderConfiguration.MODIFIED], - 'default': SortOrderConfiguration.DEFAULT, + 'enum': [SortOrder.Default, SortOrder.Mixed, SortOrder.FilesFirst, SortOrder.Type, SortOrder.Modified], + 'default': SortOrder.Default, 'enumDescriptions': [ nls.localize('sortOrder.default', 'Files and folders are sorted by their names, in alphabetical order. Folders are displayed before files.'), nls.localize('sortOrder.mixed', 'Files and folders are sorted by their names, in alphabetical order. Files are interwoven with folders.'), diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index e7b364edb86..d6bb9594120 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -14,7 +14,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { IExplorerService, SortOrder } from 'vs/workbench/contrib/files/common/files'; import { joinPath, isEqualOrParent, basenameOrAuthority } from 'vs/base/common/resources'; export class ExplorerModel implements IDisposable { @@ -263,7 +263,7 @@ export class ExplorerItem { if (!this._isDirectoryResolved) { // Resolve metadata only when the mtime is needed since this can be expensive // Mtime is only used when the sort order is 'modified' - const resolveMetadata = explorerService.sortOrder === 'modified'; + const resolveMetadata = explorerService.sortOrder === SortOrder.Modified; try { const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); const resolved = ExplorerItem.create(explorerService, fileService, stat, this); diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index d2eed899780..60d9ed77e97 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -6,7 +6,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IExplorerService, IFilesConfiguration, SortOrder, SortOrderConfiguration, IContextProvider } from 'vs/workbench/contrib/files/common/files'; +import { IExplorerService, IFilesConfiguration, SortOrder, IContextProvider } from 'vs/workbench/contrib/files/common/files'; import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; @@ -187,7 +187,7 @@ export class ExplorerService implements IExplorerService { } // Stat needs to be resolved first and then revealed - const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.sortOrder === 'modified' }; + const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.sortOrder === SortOrder.Modified }; const workspaceFolder = this.contextService.getWorkspaceFolder(resource); if (workspaceFolder === null) { return Promise.resolve(undefined); @@ -361,7 +361,7 @@ export class ExplorerService implements IExplorerService { } // Handle updated files/folders if we sort by modified - if (this._sortOrder === SortOrderConfiguration.MODIFIED) { + if (this._sortOrder === SortOrder.Modified) { const updated = e.getUpdated(); // Check updated: Refresh if updated file/folder part of resolved root @@ -387,7 +387,7 @@ export class ExplorerService implements IExplorerService { private filterToViewRelevantEvents(e: FileChangesEvent): FileChangesEvent { return new FileChangesEvent(e.changes.filter(change => { - if (change.type === FileChangeType.UPDATED && this._sortOrder !== SortOrderConfiguration.MODIFIED) { + if (change.type === FileChangeType.UPDATED && this._sortOrder !== SortOrder.Modified) { return false; // we only are about updated if we sort by modified time } diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 82c461f53ac..b9900702c0d 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -133,15 +133,13 @@ export interface IFileResource { isDirectory?: boolean; } -export const SortOrderConfiguration = { - DEFAULT: 'default', - MIXED: 'mixed', - FILES_FIRST: 'filesFirst', - TYPE: 'type', - MODIFIED: 'modified' -}; - -export type SortOrder = 'default' | 'mixed' | 'filesFirst' | 'type' | 'modified'; +export const enum SortOrder { + Default = 'default', + Mixed = 'mixed', + FilesFirst = 'filesFirst', + Type = 'type', + Modified = 'modified' +} export class TextFileContentProvider extends Disposable implements ITextModelContentProvider { private readonly fileWatcherDisposable = this._register(new MutableDisposable()); From 6debd159dfc987ce396fb1468f1750c7be084a48 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Tue, 17 Dec 2019 11:43:19 -0800 Subject: [PATCH 072/241] Add comment and improve mirror cursor exit logic --- .../client/src/mirrorCursor.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/extensions/html-language-features/client/src/mirrorCursor.ts b/extensions/html-language-features/client/src/mirrorCursor.ts index 4edf06bdc2b..3c7cca06ff4 100644 --- a/extensions/html-language-features/client/src/mirrorCursor.ts +++ b/extensions/html-language-features/client/src/mirrorCursor.ts @@ -93,6 +93,9 @@ export function activateMirrorCursor( }; if (cursors.length === 2 && inMirrorMode) { + /** + * Both cursors are positions + */ if (event.selections[0].isEmpty && event.selections[1].isEmpty) { if ( prevCursors.length === 2 && @@ -128,14 +131,24 @@ export function activateMirrorCursor( workspace.applyEdit(cleanupEdit); } } - } else { - const charBeforeAndAfterPositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual( + } + /** + * Both cursors are selections + */ + else { + const charBeforeAndAfterAnchorPositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual( + event.textEditor.document, + event.selections[0].anchor, + event.selections[1].anchor + ); + + const charBeforeAndAfterActivePositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual( event.textEditor.document, event.selections[0].active, event.selections[1].active ); - if (!charBeforeAndAfterPositionsRoughlyEqual) { + if (!charBeforeAndAfterAnchorPositionsRoughlyEqual || !charBeforeAndAfterActivePositionsRoughlyEqual) { exitMirrorMode(); } } From ee0960b25bb95d129c638e2f2781282ab9e60793 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2019 13:40:04 -0800 Subject: [PATCH 073/241] Update to use node 12 typings (#85578) * Update to use node 12 typings Fixes #82514 * Revert es6 promise changes This gets us back to having the duplicate indentifier errors * exclude es6-promise from vscode compile --- package.json | 2 +- src/tsconfig.json | 1 + src/typings/lib.ie11_safe_es6.d.ts | 4 ++-- src/vs/base/common/async.ts | 2 +- src/vs/base/node/decoder.ts | 2 +- .../workbench/services/extensions/common/lazyPromise.ts | 4 ++++ src/vs/workbench/services/search/node/fileSearch.ts | 4 ++-- src/vs/workbench/services/search/node/rawSearchService.ts | 1 + .../services/search/node/ripgrepTextSearchEngine.ts | 4 ++-- yarn.lock | 8 ++++---- 10 files changed, 19 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index d55272e9772..8b660bca986 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@types/iconv-lite": "0.0.1", "@types/keytar": "^4.4.0", "@types/mocha": "2.2.39", - "@types/node": "^10.12.12", + "@types/node": "^12.11.7", "@types/sinon": "^1.16.36", "@types/webpack": "^4.4.10", "@types/windows-foreground-love": "^0.3.0", diff --git a/src/tsconfig.json b/src/tsconfig.json index 8917d57336d..7e1e6c6d108 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -24,6 +24,7 @@ "./vs" ], "exclude": [ + "./typings/es6-promise.d.ts", "./typings/require-monaco.d.ts", "./typings/xterm.d.ts", "./typings/xterm-addon-search.d.ts", diff --git a/src/typings/lib.ie11_safe_es6.d.ts b/src/typings/lib.ie11_safe_es6.d.ts index 43bde6f7c64..547ba246917 100644 --- a/src/typings/lib.ie11_safe_es6.d.ts +++ b/src/typings/lib.ie11_safe_es6.d.ts @@ -25,7 +25,7 @@ interface Map { interface MapConstructor { new (): Map; - prototype: Map; + readonly prototype: Map; // not supported on IE11: // new (iterable: Iterable<[K, V]>): Map; @@ -51,7 +51,7 @@ interface Set { interface SetConstructor { new (): Set; - prototype: Set; + readonly prototype: Set; // not supported on IE11: // new (iterable: Iterable): Set; diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index cb0a39611e7..a2b6766b209 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -34,7 +34,7 @@ export function createCancelablePromise(callback: (token: CancellationToken) }); }); - return new class implements CancelablePromise { + return >new class { cancel() { source.cancel(); } diff --git a/src/vs/base/node/decoder.ts b/src/vs/base/node/decoder.ts index 0e313a5715a..767cf6d89c1 100644 --- a/src/vs/base/node/decoder.ts +++ b/src/vs/base/node/decoder.ts @@ -15,7 +15,7 @@ import { CharCode } from 'vs/base/common/charCode'; * - forEach() over the result to get the lines */ export class LineDecoder { - private stringDecoder: sd.NodeStringDecoder; + private stringDecoder: sd.StringDecoder; private remaining: string | null; constructor(encoding: string = 'utf8') { diff --git a/src/vs/workbench/services/extensions/common/lazyPromise.ts b/src/vs/workbench/services/extensions/common/lazyPromise.ts index 6cd7ce76aa6..0f6c07064a3 100644 --- a/src/vs/workbench/services/extensions/common/lazyPromise.ts +++ b/src/vs/workbench/services/extensions/common/lazyPromise.ts @@ -27,6 +27,10 @@ export class LazyPromise implements Promise { this._err = null; } + get [Symbol.toStringTag](): string { + return this.toString(); + } + private _ensureActual(): Promise { if (!this._actual) { this._actual = new Promise((c, e) => { diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index fc838b95e80..8a4da30f57d 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -7,7 +7,7 @@ import * as childProcess from 'child_process'; import * as fs from 'fs'; import * as path from 'vs/base/common/path'; import { Readable } from 'stream'; -import { NodeStringDecoder, StringDecoder } from 'string_decoder'; +import { StringDecoder } from 'string_decoder'; import * as arrays from 'vs/base/common/arrays'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as glob from 'vs/base/common/glob'; @@ -360,7 +360,7 @@ export class FileWalker { }); } - private forwardData(stream: Readable, encoding: string, cb: (err: Error | null, stdout?: string) => void): NodeStringDecoder { + private forwardData(stream: Readable, encoding: string, cb: (err: Error | null, stdout?: string) => void): StringDecoder { const decoder = new StringDecoder(encoding); stream.on('data', (data: Buffer) => { cb(null, decoder.write(data)); diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index ac159a60c92..336d4cd0350 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -380,6 +380,7 @@ export class SearchService implements IRawSearchService { */ private preventCancellation(promise: CancelablePromise): CancelablePromise { return new class implements CancelablePromise { + get [Symbol.toStringTag]() { return this.toString(); } cancel() { // Do nothing } diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index 835b0dc041b..99dd5602e4d 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -6,7 +6,7 @@ import * as cp from 'child_process'; import { EventEmitter } from 'events'; import * as path from 'vs/base/common/path'; -import { NodeStringDecoder, StringDecoder } from 'string_decoder'; +import { StringDecoder } from 'string_decoder'; import { createRegExp, startsWith, startsWithUTF8BOM, stripUTF8BOM, escapeRegExpCharacters, endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IExtendedExtensionSearchOptions, SearchError, SearchErrorCode, serializeSearchError } from 'vs/workbench/services/search/common/search'; @@ -169,7 +169,7 @@ export class RipgrepParser extends EventEmitter { private remainder = ''; private isDone = false; private hitLimit = false; - private stringDecoder: NodeStringDecoder; + private stringDecoder: StringDecoder; private numResults = 0; diff --git a/yarn.lock b/yarn.lock index 8c2e276bbec..687f0af9b44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -167,10 +167,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== -"@types/node@^10.12.12": - version "10.12.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47" - integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A== +"@types/node@^12.11.7": + version "12.12.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.14.tgz#1c1d6e3c75dba466e0326948d56e8bd72a1903d2" + integrity sha512-u/SJDyXwuihpwjXy7hOOghagLEV1KdAST6syfnOk6QZAMzZuWZqXy5aYYZbh8Jdpd4escVFP0MvftHNDb9pruA== "@types/node@^10.12.18": version "10.17.9" From 215e8524dc3c9e5e73244719e897c4df003936ad Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2019 13:39:46 -0800 Subject: [PATCH 074/241] Allow langauge sttings to override codeActionsOnSave Fixes #87159 --- src/vs/workbench/contrib/codeActions/common/configuration.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/codeActions/common/configuration.ts b/src/vs/workbench/contrib/codeActions/common/configuration.ts index d8878cd0181..bdbcc19eb6a 100644 --- a/src/vs/workbench/contrib/codeActions/common/configuration.ts +++ b/src/vs/workbench/contrib/codeActions/common/configuration.ts @@ -34,7 +34,7 @@ const codeActionsOnSaveSchema: IConfigurationPropertySchema = { }, default: {}, description: nls.localize('codeActionsOnSave', "Code action kinds to be run on save."), - scope: ConfigurationScope.RESOURCE + scope: ConfigurationScope.RESOURCE_LANGUAGE, }; export const editorConfiguration = Object.freeze({ @@ -45,7 +45,7 @@ export const editorConfiguration = Object.freeze({ type: 'number', default: 750, description: nls.localize('codeActionsOnSaveTimeout', "Timeout in milliseconds after which the code actions that are run on save are cancelled."), - scope: ConfigurationScope.RESOURCE + scope: ConfigurationScope.RESOURCE_LANGUAGE, }, } }); From b435511d7bd7ca38e3e551011e0a2d2e945e1106 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2019 15:02:36 -0800 Subject: [PATCH 075/241] Fixing undo/redo for new custom editor proposal --- .../api/browser/mainThreadWebview.ts | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index c98600d897b..d0fe331c5f5 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { onUnexpectedError } from 'vs/base/common/errors'; -import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import { startsWith } from 'vs/base/common/strings'; @@ -20,7 +20,7 @@ import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } fr import { IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; -import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; @@ -95,6 +95,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private readonly _webviewInputs = new WebviewInputStore(); private readonly _revivers = new Map(); private readonly _editorProviders = new Map(); + private readonly _customEditorModels = new Map(); constructor( context: extHostProtocol.IExtHostContext, @@ -272,10 +273,9 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma webviewInput.webview.extension = extension; const resource = webviewInput.getResource(); - const model = await this.loadOrCreateModel(webviewInput, resource, viewType, capabilities); + const model = await this.retainCustomEditorModel(webviewInput, resource, viewType, capabilities); webviewInput.onDisposeWebview(() => { - // TODO: This should be reference counted - this._customEditorService.models.disposeModel(model); + this.releaseCustomEditorModel(model); }); try { @@ -306,36 +306,53 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._editorProviders.delete(viewType); } - private async loadOrCreateModel(webviewInput: WebviewInput, resource: URI, viewType: string, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]) { - const existingModel = this._customEditorService.models.get(webviewInput.getResource(), webviewInput.viewType); - if (existingModel) { - return existingModel; + private async retainCustomEditorModel(webviewInput: WebviewInput, resource: URI, viewType: string, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]) { + const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); + + const existingEntry = this._customEditorModels.get(model); + if (existingEntry) { + ++existingEntry.referenceCount; + // no need to hook up listeners again + return model; } - const newModel = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); + this._customEditorModels.set(model, { referenceCount: 1 }); const capabilitiesSet = new Set(capabilities); if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Editable)) { - newModel.onUndo(edits => { + model.onUndo(edits => { this._proxy.$undoEdits(resource, viewType, edits.map(x => x.data)); }); - newModel.onApplyEdit(edits => { - const editsToApply = edits.filter(x => x.source !== newModel).map(x => x.data); + model.onApplyEdit(edits => { + const editsToApply = edits.filter(x => x.source !== model).map(x => x.data); if (editsToApply.length) { this._proxy.$applyEdits(resource, viewType, editsToApply); } }); - newModel.onWillSave(e => { + model.onWillSave(e => { e.waitUntil(this._proxy.$onSave(resource.toJSON(), viewType)); }); - newModel.onWillSaveAs(e => { + model.onWillSaveAs(e => { e.waitUntil(this._proxy.$onSaveAs(e.resource.toJSON(), viewType, e.targetResource.toJSON())); }); } - return newModel; + return model; + } + + private async releaseCustomEditorModel(model: ICustomEditorModel) { + const entry = this._customEditorModels.get(model); + if (!entry) { + return; + } + + --entry.referenceCount; + if (entry.referenceCount <= 0) { + this._customEditorService.models.disposeModel(model); + this._customEditorModels.delete(model); + } } public $onEdit(resource: UriComponents, viewType: string, editData: any): void { From 609a88d3e3642b293b49b0055282d485a579d01b Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2019 15:04:11 -0800 Subject: [PATCH 076/241] Update js/ts grammar --- .../javascript/syntaxes/JavaScript.tmLanguage.json | 13 ++++++++++++- .../syntaxes/JavaScriptReact.tmLanguage.json | 13 ++++++++++++- extensions/typescript-basics/cgmanifest.json | 2 +- .../syntaxes/TypeScript.tmLanguage.json | 13 ++++++++++++- .../syntaxes/TypeScriptReact.tmLanguage.json | 13 ++++++++++++- 5 files changed, 49 insertions(+), 5 deletions(-) diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index a8dff6ef901..db94d219f4f 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/2e0fded599d7b85b78692dee3d96e8a4710a5c57", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/c9c955af17ed0c86ebce32e0f26c72f0a2925937", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -3161,6 +3161,17 @@ "name": "keyword.operator.relational.js", "match": "<=|>=|<>|<|>" }, + { + "match": "(\\!)\\s*(/)(?![/*])", + "captures": { + "1": { + "name": "keyword.operator.logical.js" + }, + "2": { + "name": "keyword.operator.arithmetic.js" + } + } + }, { "name": "keyword.operator.logical.js", "match": "\\!|&&|\\|\\||\\?\\?" diff --git a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json b/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json index 178660a674b..8703a62331d 100644 --- a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/2e0fded599d7b85b78692dee3d96e8a4710a5c57", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/c9c955af17ed0c86ebce32e0f26c72f0a2925937", "name": "JavaScript (with React support)", "scopeName": "source.js.jsx", "patterns": [ @@ -3161,6 +3161,17 @@ "name": "keyword.operator.relational.js.jsx", "match": "<=|>=|<>|<|>" }, + { + "match": "(\\!)\\s*(/)(?![/*])", + "captures": { + "1": { + "name": "keyword.operator.logical.js.jsx" + }, + "2": { + "name": "keyword.operator.arithmetic.js.jsx" + } + } + }, { "name": "keyword.operator.logical.js.jsx", "match": "\\!|&&|\\|\\||\\?\\?" diff --git a/extensions/typescript-basics/cgmanifest.json b/extensions/typescript-basics/cgmanifest.json index 5aed2047051..f75427c65b2 100644 --- a/extensions/typescript-basics/cgmanifest.json +++ b/extensions/typescript-basics/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "TypeScript-TmLanguage", "repositoryUrl": "https://github.com/Microsoft/TypeScript-TmLanguage", - "commitHash": "2e0fded599d7b85b78692dee3d96e8a4710a5c57" + "commitHash": "c9c955af17ed0c86ebce32e0f26c72f0a2925937" } }, "license": "MIT", diff --git a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json index 2edc66ac5ed..b858d4758e9 100644 --- a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/2e0fded599d7b85b78692dee3d96e8a4710a5c57", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/c9c955af17ed0c86ebce32e0f26c72f0a2925937", "name": "TypeScript", "scopeName": "source.ts", "patterns": [ @@ -3210,6 +3210,17 @@ "name": "keyword.operator.relational.ts", "match": "<=|>=|<>|<|>" }, + { + "match": "(\\!)\\s*(/)(?![/*])", + "captures": { + "1": { + "name": "keyword.operator.logical.ts" + }, + "2": { + "name": "keyword.operator.arithmetic.ts" + } + } + }, { "name": "keyword.operator.logical.ts", "match": "\\!|&&|\\|\\||\\?\\?" diff --git a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json index 86dfac819a7..32071749fc1 100644 --- a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/2e0fded599d7b85b78692dee3d96e8a4710a5c57", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/c9c955af17ed0c86ebce32e0f26c72f0a2925937", "name": "TypeScriptReact", "scopeName": "source.tsx", "patterns": [ @@ -3161,6 +3161,17 @@ "name": "keyword.operator.relational.tsx", "match": "<=|>=|<>|<|>" }, + { + "match": "(\\!)\\s*(/)(?![/*])", + "captures": { + "1": { + "name": "keyword.operator.logical.tsx" + }, + "2": { + "name": "keyword.operator.arithmetic.tsx" + } + } + }, { "name": "keyword.operator.logical.tsx", "match": "\\!|&&|\\|\\||\\?\\?" From eb0ca2626beba3de0d2e6c0220e2c55d34cefc61 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Tue, 17 Dec 2019 15:16:35 -0800 Subject: [PATCH 077/241] Fix #86844 --- .../client/src/mirrorCursor.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/extensions/html-language-features/client/src/mirrorCursor.ts b/extensions/html-language-features/client/src/mirrorCursor.ts index 3c7cca06ff4..5c9ae446087 100644 --- a/extensions/html-language-features/client/src/mirrorCursor.ts +++ b/extensions/html-language-features/client/src/mirrorCursor.ts @@ -54,6 +54,10 @@ export function activateMirrorCursor( return; } + if (event.textEditor.document?.languageId !== 'html' && event.textEditor.document?.languageId !== 'handlebars') { + return; + } + prevCursors = cursors; cursors = event.selections; @@ -131,11 +135,10 @@ export function activateMirrorCursor( workspace.applyEdit(cleanupEdit); } } - } - /** - * Both cursors are selections - */ - else { + } else { + /** + * Both cursors are selections + */ const charBeforeAndAfterAnchorPositionsRoughlyEqual = isCharBeforeAndAfterPositionsRoughlyEqual( event.textEditor.document, event.selections[0].anchor, From 2e2c4f262e76f731a01d9d1ca64ebbbbe599c2cb Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2019 15:09:36 -0800 Subject: [PATCH 078/241] Fix toggle custom editor command not working --- src/vs/workbench/contrib/customEditor/browser/commands.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index f2b3fc113f2..431d0a7b2e5 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -230,6 +230,9 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { editorService.replaceEditors([{ editor: activeEditor, replacement: replInput, + options: { + ignoreOverrides: true, + } }], activeGroup); } }).register(); From a5d773c92d5c9e63fb0741516ce897e7028f08a4 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2019 15:14:00 -0800 Subject: [PATCH 079/241] Reuse createInput --- .../contrib/customEditor/browser/commands.ts | 23 ++++++------------- .../customEditor/browser/customEditors.ts | 6 ++++- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index 431d0a7b2e5..91668cb95ce 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -12,17 +12,16 @@ import * as nls from 'vs/nls'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; -import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService } from 'vs/platform/list/browser/listService'; -import { IEditorCommandsContext, IEditorInput } from 'vs/workbench/common/editor'; +import { IEditorCommandsContext } from 'vs/workbench/common/editor'; +import { defaultEditorId } from 'vs/workbench/contrib/customEditor/browser/customEditors'; import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; -import { defaultEditorId } from 'vs/workbench/contrib/customEditor/browser/customEditors'; -import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; const viewCategory = nls.localize('viewCategory', "View"); @@ -212,24 +211,16 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { const viewIDs = customEditorService.getContributedCustomEditors(targetResource); if (viewIDs && viewIDs.length) { toggleView = viewIDs[0].id; - } - else { + } else { return; } } - let replInput: IEditorInput; - if (toggleView === defaultEditorId) { - const instantiationService = accessor.get(IInstantiationService); - replInput = instantiationService.createInstance(FileEditorInput, targetResource, undefined, undefined); - } - else { - replInput = customEditorService.createInput(targetResource, toggleView, activeGroup); - } + const newEditorInput = customEditorService.createInput(targetResource, toggleView, activeGroup); editorService.replaceEditors([{ editor: activeEditor, - replacement: replInput, + replacement: newEditorInput, options: { ignoreOverrides: true, } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index b2c75386617..3310e9f474c 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -201,7 +201,11 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string; }, - ): CustomFileEditorInput { + ): EditorInput { + if (viewType === defaultEditorId) { + return this.instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); + } + const id = generateUuid(); const webview = new Lazy(() => { return this.webviewService.createWebviewEditorOverlay(id, { customClasses: options?.customClasses }, {}); From 9202b7d30c939c39712ca8fd9d4a89d308aa1aba Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2019 15:16:59 -0800 Subject: [PATCH 080/241] Don't reply on activeCustomEditor --- .../workbench/contrib/customEditor/browser/commands.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index 91668cb95ce..12c0a206604 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -22,6 +22,7 @@ import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/fi import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; const viewCategory = nls.localize('viewCategory', "View"); @@ -193,21 +194,16 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { const activeGroup = activeControl.group; const activeEditor = activeControl.input; - - if (!activeEditor) { - return; - } - const targetResource = activeEditor.getResource(); + if (!targetResource) { return; } const customEditorService = accessor.get(ICustomEditorService); - const activeCustomEditor = customEditorService.activeCustomEditor; let toggleView = defaultEditorId; - if (!activeCustomEditor) { + if (!(activeEditor instanceof CustomFileEditorInput)) { const viewIDs = customEditorService.getContributedCustomEditors(targetResource); if (viewIDs && viewIDs.length) { toggleView = viewIDs[0].id; From 5d9bcb294220839a92e3a02ffa643f86b871ecc4 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2019 15:47:00 -0800 Subject: [PATCH 081/241] Introduce CustomEditorInfoCollection --- .../contrib/customEditor/browser/commands.ts | 6 +- .../customEditor/browser/customEditors.ts | 78 +++++++------------ .../customEditor/common/customEditor.ts | 62 ++++++++++++++- 3 files changed, 91 insertions(+), 55 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index 12c0a206604..1022aa8e3f8 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -204,9 +204,9 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { let toggleView = defaultEditorId; if (!(activeEditor instanceof CustomFileEditorInput)) { - const viewIDs = customEditorService.getContributedCustomEditors(targetResource); - if (viewIDs && viewIDs.length) { - toggleView = viewIDs[0].id; + const bestAvailableEditor = customEditorService.getContributedCustomEditors(targetResource).bestAvailableEditor; + if (bestAvailableEditor) { + toggleView = bestAvailableEditor.id; } else { return; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 3310e9f474c..52d8f474f43 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { coalesce, distinct, find, mergeSort } from 'vs/base/common/arrays'; +import { coalesce } from 'vs/base/common/arrays'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { basename, isEqual } from 'vs/base/common/resources'; @@ -21,7 +21,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint'; -import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, CustomEditorInfo, CustomEditorPriority, CustomEditorSelector, ICustomEditor, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditor, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview'; @@ -130,15 +130,16 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return this._editorInfoStore.get(viewType); } - public getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[] { - return this._editorInfoStore.getContributedEditors(resource); + public getContributedCustomEditors(resource: URI): CustomEditorInfoCollection { + return new CustomEditorInfoCollection(this._editorInfoStore.getContributedEditors(resource)); } - public getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[] { + public getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection { const rawAssociations = this.configurationService.getValue(customEditorsAssociationsKey) || []; - return coalesce(rawAssociations - .filter(association => CustomEditorInfo.selectorMatches(association, resource)) - .map(association => this._editorInfoStore.get(association.viewType))); + return new CustomEditorInfoCollection( + coalesce(rawAssociations + .filter(association => CustomEditorInfo.selectorMatches(association, resource)) + .map(association => this._editorInfoStore.get(association.viewType)))); } public async promptOpenWith( @@ -146,21 +147,21 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ options?: ITextEditorOptions, group?: IEditorGroup, ): Promise { - const customEditors = distinct([ + const customEditors = new CustomEditorInfoCollection([ defaultEditorInfo, - ...this.getUserConfiguredCustomEditors(resource), - ...this.getContributedCustomEditors(resource), - ], editor => editor.id); + ...this.getUserConfiguredCustomEditors(resource).allEditors, + ...this.getContributedCustomEditors(resource).allEditors, + ]); let currentlyOpenedEditorType: undefined | string; for (const editor of group ? group.editors : []) { - if (editor.getResource() && isEqual(editor.getResource()!, resource)) { + if (editor.getResource() && isEqual(editor.getResource(), resource)) { currentlyOpenedEditorType = editor instanceof CustomFileEditorInput ? editor.viewType : defaultEditorId; break; } } - const items = customEditors.map((editorDescriptor): IQuickPickItem => ({ + const items = customEditors.allEditors.map((editorDescriptor): IQuickPickItem => ({ label: editorDescriptor.displayName, id: editorDescriptor.id, description: editorDescriptor.id === currentlyOpenedEditorType @@ -224,7 +225,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ group?: IEditorGroup ): Promise { if (group) { - const existingEditors = group.editors.filter(editor => editor.getResource() && isEqual(editor.getResource()!, resource)); + const existingEditors = group.editors.filter(editor => editor.getResource() && isEqual(editor.getResource(), resource)); if (existingEditors.length) { const existing = existingEditors[0]; if (!input.matches(existing)) { @@ -254,8 +255,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ } const possibleEditors = [ - ...this.getContributedCustomEditors(resource), - ...this.getUserConfiguredCustomEditors(resource), + ...this.getContributedCustomEditors(resource).allEditors, + ...this.getUserConfiguredCustomEditors(resource).allEditors, ]; this._hasCustomEditor.set(possibleEditors.length > 0); this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomFileEditorInput); @@ -306,7 +307,7 @@ export class CustomEditorContribution implements IWorkbenchContribution { const userConfiguredEditors = this.customEditorService.getUserConfiguredCustomEditors(resource); if (userConfiguredEditors.length) { return { - override: this.customEditorService.openWith(resource, userConfiguredEditors[0].id, options, group), + override: this.customEditorService.openWith(resource, userConfiguredEditors.allEditors[0].id, options, group), }; } @@ -315,15 +316,7 @@ export class CustomEditorContribution implements IWorkbenchContribution { return; } - // Find the single default editor to use (if any) by looking at the editor's priority and the - // other contributed editors. - const defaultEditor = find(contributedEditors, editor => { - if (editor.priority !== CustomEditorPriority.default && editor.priority !== CustomEditorPriority.builtin) { - return false; - } - return contributedEditors.every(otherEditor => - otherEditor === editor || isLowerPriority(otherEditor, editor)); - }); + const defaultEditor = contributedEditors.defaultEditor; if (defaultEditor) { return { override: this.customEditorService.openWith(resource, defaultEditor.id, options, group), @@ -331,7 +324,7 @@ export class CustomEditorContribution implements IWorkbenchContribution { } // If we have all optional editors, then open VS Code's standard editor - if (contributedEditors.every(editor => editor.priority === CustomEditorPriority.option)) { + if (contributedEditors.allEditors.every(editor => editor.priority === CustomEditorPriority.option)) { return; } @@ -371,20 +364,17 @@ export class CustomEditorContribution implements IWorkbenchContribution { } // Prefer default editors in the diff editor case but ultimatly always take the first editor - const editors = mergeSort( - distinct([ - ...this.customEditorService.getUserConfiguredCustomEditors(resource), - ...this.customEditorService.getContributedCustomEditors(resource).filter(x => x.priority !== CustomEditorPriority.option), - ], editor => editor.id), - (a, b) => { - return priorityToRank(a.priority) - priorityToRank(b.priority); - }); + const allEditors = new CustomEditorInfoCollection([ + ...this.customEditorService.getUserConfiguredCustomEditors(resource).allEditors, + ...this.customEditorService.getContributedCustomEditors(resource).allEditors.filter(x => x.priority !== CustomEditorPriority.option), + ]); - if (!editors.length) { + const bestAvailableEditor = allEditors.bestAvailableEditor; + if (!bestAvailableEditor) { return undefined; } - return this.customEditorService.createInput(resource, editors[0].id, group, { customClasses }); + return this.customEditorService.createInput(resource, bestAvailableEditor.id, group, { customClasses }); }; const modifiedOverride = getCustomEditorOverrideForSubInput(editor.modifiedInput, 'modified'); @@ -403,18 +393,6 @@ export class CustomEditorContribution implements IWorkbenchContribution { } } -function isLowerPriority(otherEditor: CustomEditorInfo, editor: CustomEditorInfo): unknown { - return priorityToRank(otherEditor.priority) < priorityToRank(editor.priority); -} - -function priorityToRank(priority: CustomEditorPriority): number { - switch (priority) { - case CustomEditorPriority.default: return 3; - case CustomEditorPriority.builtin: return 2; - case CustomEditorPriority.option: return 1; - } -} - registerThemingParticipant((theme, collector) => { const shadow = theme.getColor(colorRegistry.scrollbarShadow); if (shadow) { diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 8f37a7b6747..79fa6b1874d 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { distinct, find, mergeSort } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; import { basename } from 'vs/base/common/resources'; @@ -32,8 +33,8 @@ export interface ICustomEditorService { readonly activeCustomEditor: ICustomEditor | undefined; getCustomEditor(viewType: string): CustomEditorInfo | undefined; - getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[]; - getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[]; + getContributedCustomEditors(resource: URI): CustomEditorInfoCollection; + getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection; createInput(resource: URI, viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string }): EditorInput; @@ -122,3 +123,60 @@ export class CustomEditorInfo { return false; } } + +export class CustomEditorInfoCollection { + + public readonly allEditors: readonly CustomEditorInfo[]; + + constructor( + editors: readonly CustomEditorInfo[], + ) { + this.allEditors = distinct(editors, editor => editor.id); + } + + public get length(): number { return this.allEditors.length; } + + /** + * Find the single default editor to use (if any) by looking at the editor's priority and the + * other contributed editors. + */ + public get defaultEditor(): CustomEditorInfo | undefined { + return find(this.allEditors, editor => { + switch (editor.priority) { + case CustomEditorPriority.default: + case CustomEditorPriority.builtin: + // A default editor must have higher priority than all other contributed editors. + return this.allEditors.every(otherEditor => + otherEditor === editor || isLowerPriority(otherEditor, editor)); + + default: + return false; + } + }); + } + + /** + * Find the best available editor to use. + * + * Unlike the `defaultEditor`, a bestAvailableEditor can exist even if there are other editors with + * the same priority. + */ + public get bestAvailableEditor(): CustomEditorInfo | undefined { + const editors = mergeSort(Array.from(this.allEditors), (a, b) => { + return priorityToRank(a.priority) - priorityToRank(b.priority); + }); + return editors[0]; + } +} + +function isLowerPriority(otherEditor: CustomEditorInfo, editor: CustomEditorInfo): unknown { + return priorityToRank(otherEditor.priority) < priorityToRank(editor.priority); +} + +function priorityToRank(priority: CustomEditorPriority): number { + switch (priority) { + case CustomEditorPriority.default: return 3; + case CustomEditorPriority.builtin: return 2; + case CustomEditorPriority.option: return 1; + } +} From 906b111e3fc7bf2bdffa13ba31214715d37d908a Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2019 16:46:19 -0800 Subject: [PATCH 082/241] Only fire is dirty event for custom editor when the editor model is actually dirty Fixes #86551 Firing isDirty causes the editor to become pinned. We don't want this to happen by if no edits have happened --- .../contrib/customEditor/browser/customEditorInput.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 316fffe4b4f..483d0490e38 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -143,7 +143,9 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { public async resolve(): Promise { this._model = await this.customEditorService.models.loadOrCreate(this.getResource(), this.viewType); this._register(this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); - this._onDidChangeDirty.fire(); + if (this.isDirty()) { + this._onDidChangeDirty.fire(); + } return await super.resolve(); } From 4636be2b71c87bfb0bfe3c94278b447a5efcc1f1 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 17 Dec 2019 16:50:36 -0800 Subject: [PATCH 083/241] Remove grep for ts tests --- extensions/typescript-language-features/src/test/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/typescript-language-features/src/test/index.ts b/extensions/typescript-language-features/src/test/index.ts index ba0fd6d7663..1a5f7419059 100644 --- a/extensions/typescript-language-features/src/test/index.ts +++ b/extensions/typescript-language-features/src/test/index.ts @@ -23,7 +23,6 @@ testRunner.configure({ ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) useColors: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), // colored output from test results (only windows cannot handle) timeout: 60000, - grep: 'References' }); export = testRunner; From 4a2fe8a20d328d6b51104ce50983897687ac1fa0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 07:21:40 +0100 Subject: [PATCH 084/241] Fix #87198 remove the duplicate and fix compilation errors --- src/vs/vscode.proposed.d.ts | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index f9ee79736bd..4d0c545db31 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1410,31 +1410,6 @@ declare module 'vscode' { */ export interface WorkspaceConfiguration { - /** - * Return a value from this configuration. - * - * @param section Configuration name, supports _dotted_ names. - * @return The value `section` denotes or `undefined`. - */ - get(section: string): T | undefined; - - /** - * Return a value from this configuration. - * - * @param section Configuration name, supports _dotted_ names. - * @param defaultValue A value should be returned when no value could be found, is `undefined`. - * @return The value `section` denotes or the default. - */ - get(section: string, defaultValue: T): T; - - /** - * Check if this configuration has a certain value. - * - * @param section Configuration name, supports _dotted_ names. - * @return `true` if the section doesn't resolve to `undefined`. - */ - has(section: string): boolean; - /** * Retrieve all information about a configuration setting. A configuration value * often consists of a *default* value, a global or installation-wide value, @@ -1492,11 +1467,6 @@ declare module 'vscode' { * - configuration to workspace folder when [WorkspaceConfiguration](#WorkspaceConfiguration) is not scoped to a resource. */ update(section: string, value: any, configurationTarget?: ConfigurationTarget | boolean, scopeToLanguage?: boolean): Thenable; - - /** - * Readable dictionary that backs this configuration. - */ - readonly [key: string]: any; } //#endregion From c2d2f89e2383ca2a053b534e4797406846ad08ef Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 09:39:22 +0100 Subject: [PATCH 085/241] move problems to view container --- .../contrib/markers/browser/constants.ts | 2 + .../markers/browser/markers.contribution.ts | 56 +++++++++------ .../contrib/markers/browser/markersPanel.ts | 70 ++++++++++++------- 3 files changed, 82 insertions(+), 46 deletions(-) diff --git a/src/vs/workbench/contrib/markers/browser/constants.ts b/src/vs/workbench/contrib/markers/browser/constants.ts index 8d4348b87a7..470a762569e 100644 --- a/src/vs/workbench/contrib/markers/browser/constants.ts +++ b/src/vs/workbench/contrib/markers/browser/constants.ts @@ -7,6 +7,8 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export default { MARKERS_PANEL_ID: 'workbench.panel.markers', + MARKERS_PANEL_STORAGE_ID: 'workbench.panel.markers', + MARKERS_VIEW_ID: 'workbench.panel.markers.view', MARKER_COPY_ACTION_ID: 'problems.action.copy', MARKER_COPY_MESSAGE_ACTION_ID: 'problems.action.copyMessage', RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID: 'problems.action.copyRelatedInformationMessage', diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 42fd074794f..2bda0504826 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -12,7 +12,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 } from 'vs/workbench/contrib/markers/browser/markersModel'; -import { MarkersPanel } from 'vs/workbench/contrib/markers/browser/markersPanel'; +import { MarkersPanel, MarkersPane } from 'vs/workbench/contrib/markers/browser/markersPanel'; import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction } from 'vs/platform/actions/common/actions'; import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -29,6 +29,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; registerSingleton(IMarkersWorkbenchService, MarkersWorkbenchService, false); @@ -41,8 +42,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.WinCtrl | KeyCode.Enter }, handler: (accessor, args: any) => { - const markersPanel = (accessor.get(IPanelService).getActivePanel()); - markersPanel.openFileAtElement(markersPanel.getFocusElement(), false, true, true); + const markersPane = (accessor.get(IPanelService).getActivePanel()).getMarkersPane(); + markersPane.openFileAtElement(markersPane.getFocusElement(), false, true, true); } }); @@ -62,10 +63,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: Constants.MarkerFocusContextKey, primary: KeyMod.CtrlCmd | KeyCode.US_DOT, handler: (accessor, args: any) => { - const markersPanel = (accessor.get(IPanelService).getActivePanel()); - const focusedElement = markersPanel.getFocusElement(); + const markersPane = (accessor.get(IPanelService).getActivePanel()).getMarkersPane(); + const focusedElement = markersPane.getFocusElement(); if (focusedElement instanceof Marker) { - markersPanel.showQuickFixes(focusedElement); + markersPane.showQuickFixes(focusedElement); } } }); @@ -101,6 +102,14 @@ Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor ToggleMarkersPanelAction.ID )); +const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(Constants.MARKERS_PANEL_ID, ViewContainerLocation.Panel); +Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ + id: Constants.MARKERS_VIEW_ID, + name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, + canToggleVisibility: false, + ctorDescriptor: { ctor: MarkersPane }, +}], VIEW_CONTAINER); + // workbench const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(ActivityUpdater, LifecyclePhase.Restored); @@ -114,8 +123,8 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowProblemsPanelAc registerAction({ id: Constants.MARKER_COPY_ACTION_ID, title: { value: localize('copyMarker', "Copy"), original: 'Copy' }, - async handler(accessor) { - await copyMarker(accessor.get(IPanelService), accessor.get(IClipboardService)); + handler(accessor) { + copyMarker(accessor.get(IPanelService), accessor.get(IClipboardService)); }, menu: { menuId: MenuId.ProblemsPanelContext, @@ -133,8 +142,8 @@ registerAction({ registerAction({ id: Constants.MARKER_COPY_MESSAGE_ACTION_ID, title: { value: localize('copyMessage', "Copy Message"), original: 'Copy Message' }, - async handler(accessor) { - await copyMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); + handler(accessor) { + copyMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); }, menu: { menuId: MenuId.ProblemsPanelContext, @@ -145,8 +154,8 @@ registerAction({ registerAction({ id: Constants.RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID, title: { value: localize('copyMessage', "Copy Message"), original: 'Copy Message' }, - async handler(accessor) { - await copyRelatedInformationMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); + handler(accessor) { + copyRelatedInformationMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); }, menu: { menuId: MenuId.ProblemsPanelContext, @@ -186,7 +195,7 @@ registerAction({ const panelService = accessor.get(IPanelService); const panel = panelService.getActivePanel(); if (panel instanceof MarkersPanel) { - panel.markersViewModel.multiline = true; + panel.getMarkersPane().markersViewModel.multiline = true; } }, title: { value: localize('show multiline', "Show message in multiple lines"), original: 'Problems: Show message in multiple lines' }, @@ -202,7 +211,7 @@ registerAction({ const panelService = accessor.get(IPanelService); const panel = panelService.getActivePanel(); if (panel instanceof MarkersPanel) { - panel.markersViewModel.multiline = false; + panel.getMarkersPane().markersViewModel.multiline = false; } }, title: { value: localize('show singleline', "Show message in single line"), original: 'Problems: Show message in single line' }, @@ -213,30 +222,33 @@ registerAction({ } }); -async function copyMarker(panelService: IPanelService, clipboardService: IClipboardService) { +async function copyMarker(panelService: IPanelService, clipboardService: IClipboardService): Promise { const activePanel = panelService.getActivePanel(); if (activePanel instanceof MarkersPanel) { - const element = (activePanel).getFocusElement(); + const markersPane = activePanel.getMarkersPane(); + const element = markersPane.getFocusElement(); if (element instanceof Marker) { await clipboardService.writeText(`${element}`); } } } -async function copyMessage(panelService: IPanelService, clipboardService: IClipboardService) { +async function copyMessage(panelService: IPanelService, clipboardService: IClipboardService): Promise { const activePanel = panelService.getActivePanel(); if (activePanel instanceof MarkersPanel) { - const element = (activePanel).getFocusElement(); + const markersPane = activePanel.getMarkersPane(); + const element = markersPane.getFocusElement(); if (element instanceof Marker) { await clipboardService.writeText(element.marker.message); } } } -async function copyRelatedInformationMessage(panelService: IPanelService, clipboardService: IClipboardService) { +async function copyRelatedInformationMessage(panelService: IPanelService, clipboardService: IClipboardService): Promise { const activePanel = panelService.getActivePanel(); if (activePanel instanceof MarkersPanel) { - const element = (activePanel).getFocusElement(); + const markersPane = activePanel.getMarkersPane(); + const element = markersPane.getFocusElement(); if (element instanceof RelatedInformation) { await clipboardService.writeText(element.raw.message); } @@ -250,10 +262,10 @@ function focusProblemsView(panelService: IPanelService) { } } -function focusProblemsFilter(panelService: IPanelService) { +function focusProblemsFilter(panelService: IPanelService): void { const activePanel = panelService.getActivePanel(); if (activePanel instanceof MarkersPanel) { - activePanel.focusFilter(); + activePanel.getMarkersPane().focusFilter(); } } diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index 92455b67c18..79f2b34a7be 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import * as dom from 'vs/base/browser/dom'; import { IAction, IActionViewItem, Action } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { Panel } from 'vs/workbench/browser/panel'; +import { PaneCompositePanel } from 'vs/workbench/browser/panel'; 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 } from 'vs/workbench/contrib/markers/browser/markersModel'; @@ -42,12 +42,14 @@ import { domEvent } from 'vs/base/browser/event'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IMarker } from 'vs/platform/markers/common/markers'; import { withUndefinedAsNull } from 'vs/base/common/types'; -import { MementoObject } from 'vs/workbench/common/memento'; +import { MementoObject, Memento } from 'vs/workbench/common/memento'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { KeyCode } from 'vs/base/common/keyCodes'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ViewPaneContainer, ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterator> { const markersIt = Iterator.fromArray(resourceMarkers.markers); @@ -61,7 +63,27 @@ function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterat } -export class MarkersPanel extends Panel implements IMarkerFilterController { +export class MarkersPanel extends PaneCompositePanel { + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService contextService: IWorkspaceContextService) { + super(Constants.MARKERS_PANEL_ID, instantiationService.createInstance(ViewPaneContainer, Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_STORAGE_ID, { showHeaderInTitleWhenSingleView: true }), + telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + } + + getMarkersPane(): MarkersPane { + return this.viewPaneContainer.getView(Constants.MARKERS_VIEW_ID); + } + +} + +export class MarkersPane extends ViewPane implements IMarkerFilterController { private lastSelectedRelativeTop: number = 0; private currentActiveResource: URI | null = null; @@ -88,23 +110,25 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { readonly markersViewModel: MarkersViewModel; private isSmallLayout: boolean = false; + readonly onDidChangeVisibility = this.onDidChangeBodyVisibility; + constructor( + options: IViewPaneOptions, @IInstantiationService private readonly instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @ITelemetryService telemetryService: ITelemetryService, - @IThemeService themeService: IThemeService, + @IConfigurationService configurationService: IConfigurationService, + @ITelemetryService private readonly telemetryService: ITelemetryService, @IMarkersWorkbenchService private readonly markersWorkbenchService: IMarkersWorkbenchService, - @IStorageService storageService: IStorageService, @IContextKeyService contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IContextMenuService contextMenuService: IContextMenuService, @IMenuService private readonly menuService: IMenuService, - @IKeybindingService private readonly keybindingService: IKeybindingService, + @IKeybindingService keybindingService: IKeybindingService, + @IStorageService storageService: IStorageService, ) { - super(Constants.MARKERS_PANEL_ID, telemetryService, themeService, storageService); + super({ ...(options as IViewPaneOptions), id: Constants.MARKERS_VIEW_ID, ariaHeaderLabel: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, keybindingService, contextMenuService, configurationService, contextKeyService); this.panelFoucusContextKey = Constants.MarkerPanelFocusContextKey.bindTo(contextKeyService); - this.panelState = this.getMemento(StorageScope.WORKSPACE); + this.panelState = new Memento(Constants.MARKERS_PANEL_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE); this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'])); this._register(this.markersViewModel.onDidChange(marker => this.onDidChangeViewState(marker))); this.setCurrentActiveEditor(); @@ -125,9 +149,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { })); } - public create(parent: HTMLElement): void { - super.create(parent); - + public renderBody(parent: HTMLElement): void { dom.addClass(parent, 'markers-panel'); @@ -153,24 +175,24 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { })); this.filterActionBar.push(this.filterAction); - this.render(); + this.renderContent(); } public getTitle(): string { return Messages.MARKERS_PANEL_TITLE_PROBLEMS; } - public layout(dimension: dom.Dimension): void { + public layoutBody(height: number, width: number): void { const wasSmallLayout = this.isSmallLayout; - this.isSmallLayout = dimension.width < 600; + this.isSmallLayout = width < 600; if (this.isSmallLayout !== wasSmallLayout) { - this.updateTitleArea(); + this.updateActions(); dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); } - const height = this.isSmallLayout ? dimension.height - 44 : dimension.height; - this.tree.layout(height, dimension.width); - this.messageBoxContainer.style.height = `${height}px`; - this.filterAction.layout(this.isSmallLayout ? dimension.width : dimension.width - 200); + const contentHeight = this.isSmallLayout ? height - 44 : height; + this.tree.layout(contentHeight, width); + this.messageBoxContainer.style.height = `${contentHeight}px`; + this.filterAction.layout(this.isSmallLayout ? width : width - 200); } public focus(): void { @@ -510,7 +532,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { return total === 0 || filtered === 0; } - private render(): void { + private renderContent(): void { this.cachedFilterStats = undefined; this.resetTree(); this.tree.toggleVisibility(this.isEmpty()); @@ -776,7 +798,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.telemetryService.publicLog('problems.filter', data); } - protected saveState(): void { + saveState(): void { this.panelState['filter'] = this.filterAction.filterText; this.panelState['filterHistory'] = this.filterAction.filterHistory; this.panelState['showErrors'] = this.filterAction.showErrors; From 7659a39d676022553759d118d8ca34fafce1c5c5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 09:53:45 +0100 Subject: [PATCH 086/241] :lipstick: tests --- .../quickopen/test/browser/quickopen.test.ts | 10 +- .../test/common/quickOpenScorer.test.ts | 2 +- src/vs/base/test/browser/progressBar.test.ts | 3 +- src/vs/base/test/common/assert.test.ts | 1 + src/vs/base/test/common/collections.test.ts | 1 - src/vs/base/test/common/extpath.test.ts | 2 +- src/vs/base/test/common/mime.test.ts | 1 + src/vs/base/test/common/types.test.ts | 2 + .../electron-main/windowsStateStorage.test.ts | 1 + .../electron-main/backupMainService.test.ts | 101 +++++++++--------- .../storage/test/node/storageService.test.ts | 50 ++++----- .../test/browser/fileEditorInput.test.ts | 1 - .../textfile/test/textFileService.test.ts | 4 +- .../browser/parts/editor/baseEditor.test.ts | 10 +- .../workbench/test/browser/quickopen.test.ts | 7 +- src/vs/workbench/test/browser/viewlet.test.ts | 2 +- .../test/common/editor/editorModel.test.ts | 2 +- .../workbench/test/workbenchTestServices.ts | 59 +++++----- 18 files changed, 132 insertions(+), 127 deletions(-) diff --git a/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts b/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts index f53d0b4e1ed..f857c0b64a7 100644 --- a/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts +++ b/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts @@ -2,6 +2,7 @@ * 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 { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { DataSource } from 'vs/base/parts/quickopen/browser/quickOpenViewer'; @@ -28,7 +29,7 @@ suite('QuickOpen', () => { assert.equal(entry2, model.getEntries(true)[0]); }); - test('QuickOpenDataSource', () => { + test('QuickOpenDataSource', async () => { const model = new QuickOpenModel(); const entry1 = new QuickOpenEntry(); @@ -42,8 +43,7 @@ suite('QuickOpen', () => { assert.equal(true, ds.hasChildren(null!, model)); assert.equal(false, ds.hasChildren(null!, entry1)); - ds.getChildren(null!, model).then((children: any[]) => { - assert.equal(3, children.length); - }); + const children = await ds.getChildren(null!, model); + assert.equal(3, children.length); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts b/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts index 548cc9489f0..a7bbe6fb2a0 100644 --- a/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts +++ b/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts @@ -833,4 +833,4 @@ suite('Quick Open Scorer', () => { assert.equal(scorer.prepareQuery('ModelTester.ts').containsPathSeparator, false); assert.equal(scorer.prepareQuery('Model' + sep + 'Tester.ts').containsPathSeparator, true); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/browser/progressBar.test.ts b/src/vs/base/test/browser/progressBar.test.ts index 03e0a061419..f43082a0bc6 100644 --- a/src/vs/base/test/browser/progressBar.test.ts +++ b/src/vs/base/test/browser/progressBar.test.ts @@ -2,6 +2,7 @@ * 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 { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; @@ -28,4 +29,4 @@ suite('ProgressBar', () => { bar.dispose(); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/common/assert.test.ts b/src/vs/base/test/common/assert.test.ts index c7d3343ba29..a925cd0437a 100644 --- a/src/vs/base/test/common/assert.test.ts +++ b/src/vs/base/test/common/assert.test.ts @@ -2,6 +2,7 @@ * 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 { ok } from 'vs/base/common/assert'; diff --git a/src/vs/base/test/common/collections.test.ts b/src/vs/base/test/common/collections.test.ts index 353b5d0147d..e5449f46333 100644 --- a/src/vs/base/test/common/collections.test.ts +++ b/src/vs/base/test/common/collections.test.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import * as collections from 'vs/base/common/collections'; - suite('Collections', () => { test('forEach', () => { diff --git a/src/vs/base/test/common/extpath.test.ts b/src/vs/base/test/common/extpath.test.ts index da6da32873f..eb3d8da7a46 100644 --- a/src/vs/base/test/common/extpath.test.ts +++ b/src/vs/base/test/common/extpath.test.ts @@ -2,6 +2,7 @@ * 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 * as extpath from 'vs/base/common/extpath'; import * as platform from 'vs/base/common/platform'; @@ -28,7 +29,6 @@ suite('Paths', () => { assert.equal(extpath.getRoot('http://www/'), 'http://www/'); assert.equal(extpath.getRoot('file:///foo'), 'file:///'); assert.equal(extpath.getRoot('file://foo'), ''); - }); test('isUNC', () => { diff --git a/src/vs/base/test/common/mime.test.ts b/src/vs/base/test/common/mime.test.ts index 7c99c964c75..3d163580a6d 100644 --- a/src/vs/base/test/common/mime.test.ts +++ b/src/vs/base/test/common/mime.test.ts @@ -2,6 +2,7 @@ * 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 { guessMimeTypes, registerTextMime, suggestFilename } from 'vs/base/common/mime'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/base/test/common/types.test.ts b/src/vs/base/test/common/types.test.ts index 90e174dd000..0bec27fcd84 100644 --- a/src/vs/base/test/common/types.test.ts +++ b/src/vs/base/test/common/types.test.ts @@ -2,10 +2,12 @@ * 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 * as types from 'vs/base/common/types'; suite('Types', () => { + test('isFunction', () => { assert(!types.isFunction(undefined)); assert(!types.isFunction(null)); diff --git a/src/vs/code/test/electron-main/windowsStateStorage.test.ts b/src/vs/code/test/electron-main/windowsStateStorage.test.ts index 6094456c6f5..419caee0d71 100644 --- a/src/vs/code/test/electron-main/windowsStateStorage.test.ts +++ b/src/vs/code/test/electron-main/windowsStateStorage.test.ts @@ -2,6 +2,7 @@ * 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 * as os from 'os'; import * as path from 'vs/base/common/path'; diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index b4dc3452beb..0bffaa82992 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -44,16 +44,16 @@ suite('BackupMainService', () => { this.workspacesJsonPath = backupWorkspacesPath; } - public toBackupPath(arg: URI | string): string { + toBackupPath(arg: URI | string): string { const id = arg instanceof URI ? super.getFolderHash(arg) : arg; return path.join(this.backupHome, id); } - public getFolderHash(folderUri: URI): string { + getFolderHash(folderUri: URI): string { return super.getFolderHash(folderUri); } - public toLegacyBackupPath(folderPath: string): string { + toLegacyBackupPath(folderPath: string): string { return path.join(this.backupHome, super.getLegacyFolderHash(folderPath)); } } @@ -119,17 +119,16 @@ suite('BackupMainService', () => { let service: TestBackupMainService; let configService: TestConfigurationService; - setup(() => { + setup(async () => { // Delete any existing backups completely and then re-create it. - return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE).then(() => { - return pfs.mkdirp(backupHome); - }).then(() => { - configService = new TestConfigurationService(); - service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService); + await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + await pfs.mkdirp(backupHome); - return service.initialize(); - }); + configService = new TestConfigurationService(); + service = new TestBackupMainService(backupHome, backupWorkspacesPath, configService); + + return service.initialize(); }); teardown(() => { @@ -591,71 +590,71 @@ suite('BackupMainService', () => { }); }); - test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (folder workspace)', () => { + test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (folder workspace)', async () => { service.registerFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase())); assertEqualUris(service.getFolderBackupPaths(), [URI.file(fooFile.fsPath.toUpperCase())]); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { - const json = JSON.parse(buffer); - assert.deepEqual(json.folderURIWorkspaces, [URI.file(fooFile.fsPath.toUpperCase()).toString()]); - }); + + const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json = JSON.parse(buffer); + assert.deepEqual(json.folderURIWorkspaces, [URI.file(fooFile.fsPath.toUpperCase()).toString()]); }); - test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (root workspace)', () => { + test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (root workspace)', async () => { const upperFooPath = fooFile.fsPath.toUpperCase(); service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(upperFooPath)); assertEqualUris(service.getWorkspaceBackups().map(b => b.workspace.configPath), [URI.file(upperFooPath)]); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { - const json = JSON.parse(buffer); - assert.deepEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [URI.file(upperFooPath).toString()]); - }); + + const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json = (JSON.parse(buffer)); + assert.deepEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [URI.file(upperFooPath).toString()]); }); suite('removeBackupPathSync', () => { - test('should remove folder workspaces from workspaces.json (folder workspace)', () => { + test('should remove folder workspaces from workspaces.json (folder workspace)', async () => { service.registerFolderBackupSync(fooFile); service.registerFolderBackupSync(barFile); service.unregisterFolderBackupSync(fooFile); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { - const json = JSON.parse(buffer); - assert.deepEqual(json.folderURIWorkspaces, [barFile.toString()]); - service.unregisterFolderBackupSync(barFile); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => { - const json2 = JSON.parse(content); - assert.deepEqual(json2.folderURIWorkspaces, []); - }); - }); + + const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json = (JSON.parse(buffer)); + assert.deepEqual(json.folderURIWorkspaces, [barFile.toString()]); + service.unregisterFolderBackupSync(barFile); + + const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json2 = (JSON.parse(content)); + assert.deepEqual(json2.folderURIWorkspaces, []); }); - test('should remove folder workspaces from workspaces.json (root workspace)', () => { + test('should remove folder workspaces from workspaces.json (root workspace)', async () => { const ws1 = toWorkspaceBackupInfo(fooFile.fsPath); service.registerWorkspaceBackupSync(ws1); const ws2 = toWorkspaceBackupInfo(barFile.fsPath); service.registerWorkspaceBackupSync(ws2); service.unregisterWorkspaceBackupSync(ws1.workspace); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { - const json = JSON.parse(buffer); - assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [barFile.toString()]); - service.unregisterWorkspaceBackupSync(ws2.workspace); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => { - const json2 = JSON.parse(content); - assert.deepEqual(json2.rootURIWorkspaces, []); - }); - }); + + const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json = (JSON.parse(buffer)); + assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [barFile.toString()]); + service.unregisterWorkspaceBackupSync(ws2.workspace); + + const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json2 = (JSON.parse(content)); + assert.deepEqual(json2.rootURIWorkspaces, []); }); - test('should remove empty workspaces from workspaces.json', () => { + test('should remove empty workspaces from workspaces.json', async () => { service.registerEmptyWindowBackupSync('foo'); service.registerEmptyWindowBackupSync('bar'); service.unregisterEmptyWindowBackupSync('foo'); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { - const json = JSON.parse(buffer); - assert.deepEqual(json.emptyWorkspaces, ['bar']); - service.unregisterEmptyWindowBackupSync('bar'); - return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => { - const json2 = JSON.parse(content); - assert.deepEqual(json2.emptyWorkspaces, []); - }); - }); + + const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json = (JSON.parse(buffer)); + assert.deepEqual(json.emptyWorkspaces, ['bar']); + service.unregisterEmptyWindowBackupSync('bar'); + + const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json2 = (JSON.parse(content)); + assert.deepEqual(json2.emptyWorkspaces, []); }); test('should fail gracefully when removing a path that doesn\'t exist', async () => { diff --git a/src/vs/platform/storage/test/node/storageService.test.ts b/src/vs/platform/storage/test/node/storageService.test.ts index 5f9271b43c5..67eabfb6b66 100644 --- a/src/vs/platform/storage/test/node/storageService.test.ts +++ b/src/vs/platform/storage/test/node/storageService.test.ts @@ -28,11 +28,11 @@ suite('StorageService', () => { function removeData(scope: StorageScope): void { const storage = new InMemoryStorageService(); - storage.store('Monaco.IDE.Core.Storage.Test.remove', 'foobar', scope); - strictEqual('foobar', storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, (undefined)!)); + storage.store('test.remove', 'foobar', scope); + strictEqual('foobar', storage.get('test.remove', scope, (undefined)!)); - storage.remove('Monaco.IDE.Core.Storage.Test.remove', scope); - ok(!storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, (undefined)!)); + storage.remove('test.remove', scope); + ok(!storage.get('test.remove', scope, (undefined)!)); } test('Get Data, Integer, Boolean (global, in-memory)', () => { @@ -46,34 +46,34 @@ suite('StorageService', () => { function storeData(scope: StorageScope): void { const storage = new InMemoryStorageService(); - strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, 'foobar'), 'foobar'); - strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, ''), ''); - strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, 5), 5); - strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, 0), 0); - strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, true), true); - strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, false), false); + strictEqual(storage.get('test.get', scope, 'foobar'), 'foobar'); + strictEqual(storage.get('test.get', scope, ''), ''); + strictEqual(storage.getNumber('test.getNumber', scope, 5), 5); + strictEqual(storage.getNumber('test.getNumber', scope, 0), 0); + strictEqual(storage.getBoolean('test.getBoolean', scope, true), true); + strictEqual(storage.getBoolean('test.getBoolean', scope, false), false); - storage.store('Monaco.IDE.Core.Storage.Test.get', 'foobar', scope); - strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, (undefined)!), 'foobar'); + storage.store('test.get', 'foobar', scope); + strictEqual(storage.get('test.get', scope, (undefined)!), 'foobar'); - storage.store('Monaco.IDE.Core.Storage.Test.get', '', scope); - strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, (undefined)!), ''); + storage.store('test.get', '', scope); + strictEqual(storage.get('test.get', scope, (undefined)!), ''); - storage.store('Monaco.IDE.Core.Storage.Test.getNumber', 5, scope); - strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, (undefined)!), 5); + storage.store('test.getNumber', 5, scope); + strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 5); - storage.store('Monaco.IDE.Core.Storage.Test.getNumber', 0, scope); - strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, (undefined)!), 0); + storage.store('test.getNumber', 0, scope); + strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 0); - storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', true, scope); - strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, (undefined)!), true); + storage.store('test.getBoolean', true, scope); + strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), true); - storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', false, scope); - strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, (undefined)!), false); + storage.store('test.getBoolean', false, scope); + strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), false); - strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.getDefault', scope, 'getDefault'), 'getDefault'); - strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumberDefault', scope, 5), 5); - strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBooleanDefault', scope, true), true); + strictEqual(storage.get('test.getDefault', scope, 'getDefault'), 'getDefault'); + strictEqual(storage.getNumber('test.getNumberDefault', scope, 5), 5); + strictEqual(storage.getBoolean('test.getBooleanDefault', scope, true), true); } function uniqueStorageDir(): string { diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 932c54fa018..d1a86fceb9a 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -28,7 +28,6 @@ class ServiceAccessor { } suite('Files - FileEditorInput', () => { - let instantiationService: IInstantiationService; let accessor: ServiceAccessor; diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 79c3a8052dc..1c04252cdd7 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -40,8 +40,8 @@ class ServiceAccessor { class BeforeShutdownEventImpl implements BeforeShutdownEvent { - public value: boolean | Promise | undefined; - public reason = ShutdownReason.CLOSE; + value: boolean | Promise | undefined; + reason = ShutdownReason.CLOSE; veto(value: boolean | Promise): void { this.value = value; diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index b6abedc45d5..a2d778aeb88 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -256,17 +256,17 @@ suite('Workbench base editor', () => { } class TestEditorInput extends EditorInput { - constructor(private resource: URI, private id = 'testEditorInput') { + constructor(private resource: URI, private id = 'testEditorInputForMementoTest') { super(); } - public getTypeId() { return 'testEditorInput'; } - public resolve(): Promise { return Promise.resolve(null!); } + getTypeId() { return 'testEditorInputForMementoTest'; } + resolve(): Promise { return Promise.resolve(null!); } - public matches(other: TestEditorInput): boolean { + matches(other: TestEditorInput): boolean { return other && this.id === other.id && other instanceof TestEditorInput; } - public getResource(): URI { + getResource(): URI { return this.resource; } } diff --git a/src/vs/workbench/test/browser/quickopen.test.ts b/src/vs/workbench/test/browser/quickopen.test.ts index cbd8854f8eb..8303a042e22 100644 --- a/src/vs/workbench/test/browser/quickopen.test.ts +++ b/src/vs/workbench/test/browser/quickopen.test.ts @@ -11,7 +11,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenAction, QuickOpenHandler } from 'vs/workbench/browser/quickopen'; export class TestQuickOpenService implements IQuickOpenService { - public _serviceBrand: undefined; + + _serviceBrand: undefined; private callback?: (prefix?: string) => void; @@ -44,8 +45,8 @@ export class TestQuickOpenService implements IQuickOpenService { return null!; } - public dispose() { } - public navigate(): void { } + dispose() { } + navigate(): void { } } suite('QuickOpen', () => { diff --git a/src/vs/workbench/test/browser/viewlet.test.ts b/src/vs/workbench/test/browser/viewlet.test.ts index 7ca46667704..15a1cb526eb 100644 --- a/src/vs/workbench/test/browser/viewlet.test.ts +++ b/src/vs/workbench/test/browser/viewlet.test.ts @@ -16,7 +16,7 @@ suite('Viewlets', () => { super('id', null!, null!, null!, null!, null!, null!, null!, null!, null!, null!); } - public layout(dimension: any): void { + layout(dimension: any): void { throw new Error('Method not implemented.'); } } diff --git a/src/vs/workbench/test/common/editor/editorModel.test.ts b/src/vs/workbench/test/common/editor/editorModel.test.ts index 5b368da847a..c442c63736b 100644 --- a/src/vs/workbench/test/common/editor/editorModel.test.ts +++ b/src/vs/workbench/test/common/editor/editorModel.test.ts @@ -21,7 +21,7 @@ import { TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTe class MyEditorModel extends EditorModel { } class MyTextEditorModel extends BaseTextEditorModel { - public createTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredMode?: string) { + createTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredMode?: string) { return super.createTextEditorModel(value, resource, preferredMode); } diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index b088fe4928a..2ff6ff19bc2 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -245,26 +245,25 @@ export class TestTextFileService extends NativeTextFileService { this.resolveTextContentError = error; } - readStream(resource: URI, options?: IReadTextFileOptions): Promise { + async readStream(resource: URI, options?: IReadTextFileOptions): Promise { if (this.resolveTextContentError) { const error = this.resolveTextContentError; this.resolveTextContentError = null; - return Promise.reject(error); + throw error; } - return this.fileService.readFileStream(resource, options).then(async (content): Promise => { - return { - resource: content.resource, - name: content.name, - mtime: content.mtime, - ctime: content.ctime, - etag: content.etag, - encoding: 'utf8', - value: await createTextBufferFactoryFromStream(content.value), - size: 10 - }; - }); + const content = await this.fileService.readFileStream(resource, options); + return { + resource: content.resource, + name: content.name, + mtime: content.mtime, + ctime: content.ctime, + etag: content.etag, + encoding: 'utf8', + value: await createTextBufferFactoryFromStream(content.value), + size: 10 + }; } promptForPath(_resource: URI, _defaultPath: URI): Promise { @@ -1021,12 +1020,14 @@ export class TestFileService implements IFileService { }); } - resolveAll(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise { - return Promise.all(toResolve.map(resourceAndOption => this.resolve(resourceAndOption.resource, resourceAndOption.options))).then(stats => stats.map(stat => ({ stat, success: true }))); + async resolveAll(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise { + const stats = await Promise.all(toResolve.map(resourceAndOption => this.resolve(resourceAndOption.resource, resourceAndOption.options))); + + return stats.map(stat => ({ stat, success: true })); } - exists(_resource: URI): Promise { - return Promise.resolve(true); + async exists(_resource: URI): Promise { + return true; } readFile(resource: URI, options?: IReadFileOptions | undefined): Promise { @@ -1071,11 +1072,12 @@ export class TestFileService implements IFileService { }); } - writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise { - return timeout(0).then(() => ({ + async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise { + await timeout(0); + + return ({ resource, etag: 'index.txt', - encoding: 'utf8', mtime: Date.now(), ctime: Date.now(), size: 42, @@ -1083,7 +1085,7 @@ export class TestFileService implements IFileService { isDirectory: false, isSymbolicLink: false, name: resources.basename(resource) - })); + }); } move(_source: URI, _target: URI, _overwrite?: boolean): Promise { @@ -1153,14 +1155,13 @@ export class TestBackupFileService implements IBackupFileService { return false; } - loadBackupResource(resource: URI): Promise { - return this.hasBackup(resource).then(hasBackup => { - if (hasBackup) { - return this.toBackupResource(resource); - } + async loadBackupResource(resource: URI): Promise { + const hasBackup = await this.hasBackup(resource); + if (hasBackup) { + return this.toBackupResource(resource); + } - return undefined; - }); + return undefined; } registerResourceForBackup(_resource: URI): Promise { From c7ab68cc80fad42df1a9deeb9371204086e32269 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 10:09:53 +0100 Subject: [PATCH 087/241] state :lipstick: --- src/vs/platform/storage/node/storageMainService.ts | 8 ++++---- .../workbench/contrib/files/browser/views/explorerView.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/platform/storage/node/storageMainService.ts b/src/vs/platform/storage/node/storageMainService.ts index 81c9511a239..3c34616f373 100644 --- a/src/vs/platform/storage/node/storageMainService.ts +++ b/src/vs/platform/storage/node/storageMainService.ts @@ -89,11 +89,11 @@ export class StorageMainService extends Disposable implements IStorageMainServic private static readonly STORAGE_NAME = 'state.vscdb'; - private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); - readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; + private readonly _onDidChangeStorage = this._register(new Emitter()); + readonly onDidChangeStorage = this._onDidChangeStorage.event; - private readonly _onWillSaveState: Emitter = this._register(new Emitter()); - readonly onWillSaveState: Event = this._onWillSaveState.event; + private readonly _onWillSaveState = this._register(new Emitter()); + readonly onWillSaveState = this._onWillSaveState.event; get items(): Map { return this.storage.items; } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 2f6c9edc6f4..7f05dc279a9 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -436,7 +436,7 @@ export class ExplorerView extends ViewPane { } })); - // save view state on shutdown + // save view state this._register(this.storageService.onWillSaveState(() => { this.storageService.store(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE); })); From 8e2b3047ebdfc9b0fa79023da0fcfc81b8532aff Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 18 Dec 2019 10:14:36 +0100 Subject: [PATCH 088/241] Enable more than just localhost for port forwarding providers Part of #81388 --- src/vs/platform/remote/common/tunnel.ts | 6 +- .../platform/remote/common/tunnelService.ts | 8 +- .../api/browser/mainThreadTunnelService.ts | 6 +- .../workbench/api/common/extHost.protocol.ts | 4 +- .../api/common/extHostTunnelService.ts | 2 +- .../api/node/extHostTunnelService.ts | 10 +- .../contrib/remote/browser/tunnelView.ts | 68 +++++---- .../contrib/webview/common/portMapping.ts | 2 +- src/vs/workbench/electron-browser/window.ts | 2 +- .../remote/common/remoteExplorerService.ts | 138 ++++++++++-------- .../services/remote/node/tunnelService.ts | 74 ++++++---- 11 files changed, 187 insertions(+), 133 deletions(-) diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index 7693ad596c6..b5fdfa8bff0 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -33,10 +33,10 @@ export interface ITunnelService { readonly tunnels: Promise; readonly onTunnelOpened: Event; - readonly onTunnelClosed: Event; + readonly onTunnelClosed: Event<{ host: string, port: number }>; - openTunnel(remotePort: number, localPort?: number): Promise | undefined; - closeTunnel(remotePort: number): Promise; + openTunnel(remoteHost: string | undefined, remotePort: number, localPort?: number): Promise | undefined; + closeTunnel(remoteHost: string, remotePort: number): Promise; setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable; } diff --git a/src/vs/platform/remote/common/tunnelService.ts b/src/vs/platform/remote/common/tunnelService.ts index 2501ebc90f1..a5fa07ac174 100644 --- a/src/vs/platform/remote/common/tunnelService.ts +++ b/src/vs/platform/remote/common/tunnelService.ts @@ -13,12 +13,12 @@ export class NoOpTunnelService implements ITunnelService { public readonly tunnels: Promise = Promise.resolve([]); private _onTunnelOpened: Emitter = new Emitter(); public onTunnelOpened: Event = this._onTunnelOpened.event; - private _onTunnelClosed: Emitter = new Emitter(); - public onTunnelClosed: Event = this._onTunnelClosed.event; - openTunnel(_remotePort: number): Promise | undefined { + private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter(); + public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event; + openTunnel(_remoteHost: string, _remotePort: number): Promise | undefined { return undefined; } - async closeTunnel(_remotePort: number): Promise { + async closeTunnel(_remoteHost: string, _remotePort: number): Promise { } setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { throw new Error('Method not implemented.'); diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 482fe0f8808..3ab437aeef4 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -22,15 +22,15 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { } async $openTunnel(tunnelOptions: TunnelOptions): Promise { - const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote.port, tunnelOptions.localPort, tunnelOptions.name); + const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote, tunnelOptions.localPort, tunnelOptions.name); if (tunnel) { return TunnelDto.fromServiceTunnel(tunnel); } return undefined; } - async $closeTunnel(remotePort: number): Promise { - return this.remoteExplorerService.close(remotePort); + async $closeTunnel(remote: { host: string, port: number }): Promise { + return this.remoteExplorerService.close(remote); } async $registerCandidateFinder(): Promise { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 2e879ed1eda..b4c6ebbb625 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -774,7 +774,7 @@ export interface MainThreadWindowShape extends IDisposable { export interface MainThreadTunnelServiceShape extends IDisposable { $openTunnel(tunnelOptions: TunnelOptions): Promise; - $closeTunnel(remotePort: number): Promise; + $closeTunnel(remote: { host: string, port: number }): Promise; $registerCandidateFinder(): Promise; $setTunnelProvider(): Promise; } @@ -1395,7 +1395,7 @@ export interface ExtHostStorageShape { export interface ExtHostTunnelServiceShape { - $findCandidatePorts(): Promise<{ port: number, detail: string }[]>; + $findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]>; $forwardPort(tunnelOptions: TunnelOptions): Promise | undefined; $closeTunnel(remote: { host: string, port: number }): Promise; } diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 40c37a6c00c..a606b3820f0 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -48,7 +48,7 @@ export class ExtHostTunnelService implements IExtHostTunnelService { async makeTunnel(forward: TunnelOptions): Promise { return undefined; } - async $findCandidatePorts(): Promise<{ port: number; detail: string; }[]> { + async $findCandidatePorts(): Promise<{ host: string, port: number; detail: string; }[]> { return []; } async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise { return { dispose: () => { } }; } diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index 9dbde0ddabb..93b333980a5 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -52,7 +52,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe const tunnel = await this._proxy.$openTunnel(forward); if (tunnel) { const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remote, tunnel.localAddress, () => { - return this._proxy.$closeTunnel(tunnel.remote.port); + return this._proxy.$closeTunnel(tunnel.remote); }); this._register(disposableTunnel); return disposableTunnel; @@ -95,7 +95,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe this._extensionTunnels.set(tunnelOptions.remote.host, new Map()); } this._extensionTunnels.get(tunnelOptions.remote.host)!.set(tunnelOptions.remote.port, tunnel); - this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote.port))); + this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote))); return Promise.resolve(TunnelDto.fromApiTunnel(tunnel)); }); } @@ -104,12 +104,12 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } - async $findCandidatePorts(): Promise<{ port: number, detail: string }[]> { + async $findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]> { if (!isLinux) { return []; } - const ports: { port: number, detail: string }[] = []; + const ports: { host: string, port: number, detail: string }[] = []; const tcp: string = fs.readFileSync('/proc/net/tcp', 'utf8'); const tcp6: string = fs.readFileSync('/proc/net/tcp6', 'utf8'); const procSockets: string = await (new Promise(resolve => { @@ -150,7 +150,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => { const command = processMap[socketMap[socket].pid].cmd; if (!command.match('.*\.vscode\-server\-[a-zA-Z]+\/bin.*') && (command.indexOf('out/vs/server/main.js') === -1)) { - ports.push({ port, detail: processMap[socketMap[socket].pid].cmd }); + ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd }); } }); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 1e881877649..4f6ebb31e5e 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -26,7 +26,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions'; import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IRemoteExplorerService, TunnelModel } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService, TunnelModel, MakeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -105,13 +105,13 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { get forwarded(): TunnelItem[] { return Array.from(this.model.forwarded.values()).map(tunnel => { - return new TunnelItem(TunnelType.Forwarded, tunnel.remote, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description); + return new TunnelItem(TunnelType.Forwarded, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description); }); } get detected(): TunnelItem[] { return Array.from(this.model.detected.values()).map(tunnel => { - return new TunnelItem(TunnelType.Detected, tunnel.remote, tunnel.localAddress, false, tunnel.name, tunnel.description); + return new TunnelItem(TunnelType.Detected, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, false, tunnel.name, tunnel.description); }); } @@ -119,8 +119,9 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { return this.model.candidates.then(values => { const candidates: TunnelItem[] = []; values.forEach(value => { - if (!this.model.forwarded.has(value.port) && !this.model.detected.has(value.port)) { - candidates.push(new TunnelItem(TunnelType.Candidate, value.port, undefined, false, undefined, value.detail)); + const key = MakeAddress(value.host, value.port); + if (!this.model.forwarded.has(key) && !this.model.detected.has(key)) { + candidates.push(new TunnelItem(TunnelType.Candidate, value.host, value.port, undefined, false, undefined, value.detail)); } }); return candidates; @@ -185,7 +186,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRendereritem).remote); + return !!((item).remotePort); } renderElement(element: ITreeNode, index: number, templateData: ITunnelTemplateData): void { @@ -196,7 +197,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer { - const isEditing = !!this.remoteExplorerService.getEditableData(e); + const isEditing = !!this.remoteExplorerService.getEditableData(e.host, e.port); if (!isEditing) { dom.removeClass(treeContainer, 'highlight'); @@ -575,12 +578,12 @@ namespace LabelTunnelAction { return async (accessor, arg) => { if (arg instanceof TunnelItem) { const remoteExplorerService = accessor.get(IRemoteExplorerService); - remoteExplorerService.setEditable(arg.remote, { + remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, { onFinish: (value, success) => { if (success) { - remoteExplorerService.tunnelModel.name(arg.remote, value); + remoteExplorerService.tunnelModel.name(arg.remoteHost, arg.remotePort, value); } - remoteExplorerService.setEditable(arg.remote, null); + remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, null); }, validationMessage: () => null, placeholder: nls.localize('remote.tunnelsView.labelPlaceholder', "Port label"), @@ -596,24 +599,32 @@ namespace ForwardPortAction { export const ID = 'remote.tunnel.forward'; export const LABEL = nls.localize('remote.tunnel.forward', "Forward a Port"); + function parseInput(value: string): { host: string, port: number } | undefined { + const matches = value.match(/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\:|localhost:)?([0-9]+)$/); + if (!matches) { + return undefined; + } + return { host: matches[1]?.substring(0, matches[1].length - 1) || 'localhost', port: Number(matches[2]) }; + } + export function handler(): ICommandHandler { return async (accessor, arg) => { const remoteExplorerService = accessor.get(IRemoteExplorerService); if (arg instanceof TunnelItem) { - remoteExplorerService.tunnelModel.forward(arg.remote); + remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort }); } else { const viewsService = accessor.get(IViewsService); await viewsService.openView(TunnelPanel.ID, true); - remoteExplorerService.setEditable(undefined, { + remoteExplorerService.setEditable(undefined, undefined, { onFinish: (value, success) => { - if (success) { - remoteExplorerService.tunnelModel.forward(Number(value)); + let parsed: { host: string, port: number } | undefined; + if (success && (parsed = parseInput(value))) { + remoteExplorerService.forward({ host: parsed.host, port: parsed.port }); } - remoteExplorerService.setEditable(undefined, null); + remoteExplorerService.setEditable(undefined, undefined, null); }, validationMessage: (value) => { - const asNumber = Number(value); - if ((value === '') || isNaN(asNumber) || (asNumber < 0) || (asNumber > 65535)) { + if (!parseInput(value)) { return nls.localize('remote.tunnelsView.portNumberValid', "Port number is invalid"); } return null; @@ -633,7 +644,7 @@ namespace ClosePortAction { return async (accessor, arg) => { if (arg instanceof TunnelItem) { const remoteExplorerService = accessor.get(IRemoteExplorerService); - await remoteExplorerService.tunnelModel.close(arg.remote); + await remoteExplorerService.close({ host: arg.remoteHost, port: arg.remotePort }); } }; } @@ -648,9 +659,10 @@ namespace OpenPortInBrowserAction { if (arg instanceof TunnelItem) { const model = accessor.get(IRemoteExplorerService).tunnelModel; const openerService = accessor.get(IOpenerService); - const tunnel = model.forwarded.has(arg.remote) ? model.forwarded.get(arg.remote) : model.detected.get(arg.remote); + const key = MakeAddress(arg.remoteHost, arg.remotePort); + const tunnel = model.forwarded.get(key) || model.detected.get(key); let address: string | undefined; - if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remote))) { + if (tunnel && tunnel.localAddress && (address = model.address(tunnel.remoteHost, tunnel.remotePort))) { return openerService.open(URI.parse('http://' + address)); } return Promise.resolve(); @@ -668,7 +680,7 @@ namespace CopyAddressAction { if (arg instanceof TunnelItem) { const model = accessor.get(IRemoteExplorerService).tunnelModel; const clipboard = accessor.get(IClipboardService); - const address = model.address(arg.remote); + const address = model.address(arg.remoteHost, arg.remotePort); if (address) { await clipboard.writeText(address.toString()); } diff --git a/src/vs/workbench/contrib/webview/common/portMapping.ts b/src/vs/workbench/contrib/webview/common/portMapping.ts index 69c216631ba..fe5a6d21962 100644 --- a/src/vs/workbench/contrib/webview/common/portMapping.ts +++ b/src/vs/workbench/contrib/webview/common/portMapping.ts @@ -68,7 +68,7 @@ export class WebviewPortMappingManager extends Disposable { if (existing) { return existing; } - const tunnel = this.tunnelService.openTunnel(remotePort); + const tunnel = this.tunnelService.openTunnel(undefined, remotePort); if (tunnel) { this._tunnels.set(remotePort, tunnel); } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 5e7ff1184b5..02735f4a472 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -453,7 +453,7 @@ export class ElectronWindow extends Disposable { if (options?.allowTunneling) { const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri); if (portMappingRequest) { - const tunnel = await this.tunnelService.openTunnel(portMappingRequest.port); + const tunnel = await this.tunnelService.openTunnel(undefined, portMappingRequest.port); if (tunnel) { return { resolved: uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}` }), diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 01f41b09a1c..f83efc87dc3 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -18,24 +18,32 @@ export const IRemoteExplorerService = createDecorator('r export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; export interface Tunnel { - remote: number; + remoteHost: string; + remotePort: number; localAddress: string; - local?: number; + localPort?: number; name?: string; description?: string; closeable?: boolean; } +export function MakeAddress(host: string, port: number): string { + if (host = '127.0.0.1') { + host = 'localhost'; + } + return host + ':' + port; +} + export class TunnelModel extends Disposable { - readonly forwarded: Map; - readonly detected: Map; + readonly forwarded: Map; + readonly detected: Map; private _onForwardPort: Emitter = new Emitter(); public onForwardPort: Event = this._onForwardPort.event; - private _onClosePort: Emitter = new Emitter(); - public onClosePort: Event = this._onClosePort.event; - private _onPortName: Emitter = new Emitter(); - public onPortName: Event = this._onPortName.event; - private _candidateFinder: (() => Promise<{ port: number, detail: string }[]>) | undefined; + private _onClosePort: Emitter<{ host: string, port: number }> = new Emitter(); + public onClosePort: Event<{ host: string, port: number }> = this._onClosePort.event; + private _onPortName: Emitter<{ host: string, port: number }> = new Emitter(); + public onPortName: Event<{ host: string, port: number }> = this._onPortName.event; + private _candidateFinder: (() => Promise<{ host: string, port: number, detail: string }[]>) | undefined; constructor( @ITunnelService private readonly tunnelService: ITunnelService @@ -45,10 +53,11 @@ export class TunnelModel extends Disposable { this.tunnelService.tunnels.then(tunnels => { tunnels.forEach(tunnel => { if (tunnel.localAddress) { - this.forwarded.set(tunnel.tunnelRemotePort, { - remote: tunnel.tunnelRemotePort, + this.forwarded.set(MakeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort), { + remotePort: tunnel.tunnelRemotePort, + remoteHost: tunnel.tunnelRemoteHost, localAddress: tunnel.localAddress, - local: tunnel.tunnelLocalPort + localPort: tunnel.tunnelLocalPort }); } }); @@ -56,75 +65,83 @@ export class TunnelModel extends Disposable { this.detected = new Map(); this._register(this.tunnelService.onTunnelOpened(tunnel => { - if (!this.forwarded.has(tunnel.tunnelRemotePort) && tunnel.localAddress) { - this.forwarded.set(tunnel.tunnelRemotePort, { - remote: tunnel.tunnelRemotePort, + const key = MakeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort); + if ((!this.forwarded.has(key)) && tunnel.localAddress) { + this.forwarded.set(key, { + remoteHost: tunnel.tunnelRemoteHost, + remotePort: tunnel.tunnelRemotePort, localAddress: tunnel.localAddress, - local: tunnel.tunnelLocalPort, + localPort: tunnel.tunnelLocalPort, closeable: true }); } - this._onForwardPort.fire(this.forwarded.get(tunnel.tunnelRemotePort)!); + this._onForwardPort.fire(this.forwarded.get(key)!); })); - this._register(this.tunnelService.onTunnelClosed(remotePort => { - if (this.forwarded.has(remotePort)) { - this.forwarded.delete(remotePort); - this._onClosePort.fire(remotePort); + this._register(this.tunnelService.onTunnelClosed(address => { + const key = MakeAddress(address.host, address.port); + if (this.forwarded.has(key)) { + this.forwarded.delete(key); + this._onClosePort.fire(address); } })); } - async forward(remote: number, local?: number, name?: string): Promise { - if (!this.forwarded.has(remote)) { - const tunnel = await this.tunnelService.openTunnel(remote, local); + async forward(remote: { host: string, port: number }, local?: number, name?: string): Promise { + const key = MakeAddress(remote.host, remote.port); + if (!this.forwarded.has(key)) { + const tunnel = await this.tunnelService.openTunnel(remote.host, remote.port, local); if (tunnel && tunnel.localAddress) { const newForward: Tunnel = { - remote: tunnel.tunnelRemotePort, - local: tunnel.tunnelLocalPort, + remoteHost: tunnel.tunnelRemoteHost, + remotePort: tunnel.tunnelRemotePort, + localPort: tunnel.tunnelLocalPort, name: name, closeable: true, localAddress: tunnel.localAddress }; - this.forwarded.set(remote, newForward); + this.forwarded.set(key, newForward); this._onForwardPort.fire(newForward); return tunnel; } } } - name(remote: number, name: string) { - if (this.forwarded.has(remote)) { - this.forwarded.get(remote)!.name = name; - this._onPortName.fire(remote); - } else if (this.detected.has(remote)) { - this.detected.get(remote)!.name = name; - this._onPortName.fire(remote); + name(host: string, port: number, name: string) { + const key = MakeAddress(host, port); + if (this.forwarded.has(key)) { + this.forwarded.get(key)!.name = name; + this._onPortName.fire({ host, port }); + } else if (this.detected.has(key)) { + this.detected.get(key)!.name = name; + this._onPortName.fire({ host, port }); } } - async close(remote: number): Promise { - return this.tunnelService.closeTunnel(remote); + async close(host: string, port: number): Promise { + return this.tunnelService.closeTunnel(host, port); } - address(remote: number): string | undefined { - return (this.forwarded.get(remote) || this.detected.get(remote))?.localAddress; + address(host: string, port: number): string | undefined { + const key = MakeAddress(host, port); + return (this.forwarded.get(key) || this.detected.get(key))?.localAddress; } addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[]): void { tunnels.forEach(tunnel => { - this.detected.set(tunnel.remote.port, { - remote: tunnel.remote.port, + this.detected.set(MakeAddress(tunnel.remote.host, tunnel.remote.port), { + remoteHost: tunnel.remote.host, + remotePort: tunnel.remote.port, localAddress: tunnel.localAddress, closeable: false }); }); } - registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void { + registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void { this._candidateFinder = finder; } - get candidates(): Promise<{ port: number, detail: string }[]> { + get candidates(): Promise<{ host: string, port: number, detail: string }[]> { if (this._candidateFinder) { return this._candidateFinder(); } @@ -138,13 +155,13 @@ export interface IRemoteExplorerService { targetType: string; readonly helpInformation: HelpInformation[]; readonly tunnelModel: TunnelModel; - onDidChangeEditable: Event; - setEditable(remote: number | undefined, data: IEditableData | null): void; - getEditableData(remote: number | undefined): IEditableData | undefined; - forward(remote: number, local?: number, name?: string): Promise; - close(remote: number): Promise; + onDidChangeEditable: Event<{ host: string, port: number | undefined }>; + setEditable(remoteHost: string | undefined, remotePort: number | undefined, data: IEditableData | null): void; + getEditableData(remoteHost: string | undefined, remotePort: number | undefined): IEditableData | undefined; + forward(remote: { host: string, port: number }, localPort?: number, name?: string): Promise; + close(remote: { host: string, port: number }): Promise; addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): void; - registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void; + registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void; } export interface HelpInformation { @@ -189,9 +206,9 @@ class RemoteExplorerService implements IRemoteExplorerService { public readonly onDidChangeTargetType: Event = this._onDidChangeTargetType.event; private _helpInformation: HelpInformation[] = []; private _tunnelModel: TunnelModel; - private _editable: { remote: number | undefined, data: IEditableData } | undefined; - private readonly _onDidChangeEditable: Emitter = new Emitter(); - public readonly onDidChangeEditable: Event = this._onDidChangeEditable.event; + private _editable: { remoteHost: string, remotePort: number | undefined, data: IEditableData } | undefined; + private readonly _onDidChangeEditable: Emitter<{ host: string, port: number | undefined }> = new Emitter(); + public readonly onDidChangeEditable: Event<{ host: string, port: number | undefined }> = this._onDidChangeEditable.event; constructor( @IStorageService private readonly storageService: IStorageService, @@ -246,12 +263,12 @@ class RemoteExplorerService implements IRemoteExplorerService { return this._tunnelModel; } - forward(remote: number, local?: number, name?: string): Promise { + forward(remote: { host: string, port: number }, local?: number, name?: string): Promise { return this.tunnelModel.forward(remote, local, name); } - close(remote: number): Promise { - return this.tunnelModel.close(remote); + close(remote: { host: string, port: number }): Promise { + return this.tunnelModel.close(remote.host, remote.port); } addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): void { @@ -260,20 +277,21 @@ class RemoteExplorerService implements IRemoteExplorerService { } } - setEditable(remote: number | undefined, data: IEditableData | null): void { + setEditable(remoteHost: string, remotePort: number | undefined, data: IEditableData | null): void { if (!data) { this._editable = undefined; } else { - this._editable = { remote, data }; + this._editable = { remoteHost, remotePort, data }; } - this._onDidChangeEditable.fire(remote); + this._onDidChangeEditable.fire({ host: remoteHost, port: remotePort }); } - getEditableData(remote: number | undefined): IEditableData | undefined { - return this._editable && this._editable.remote === remote ? this._editable.data : undefined; + getEditableData(remoteHost: string | undefined, remotePort: number | undefined): IEditableData | undefined { + return (this._editable && (this._editable.remotePort === remotePort) && this._editable.remoteHost === remoteHost) ? + this._editable.data : undefined; } - registerCandidateFinder(finder: () => Promise<{ port: number, detail: string }[]>): void { + registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void { this.tunnelModel.registerCandidateFinder(finder); } diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 4e60ad01e15..79f473813dc 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -103,9 +103,9 @@ export class TunnelService implements ITunnelService { private _onTunnelOpened: Emitter = new Emitter(); public onTunnelOpened: Event = this._onTunnelOpened.event; - private _onTunnelClosed: Emitter = new Emitter(); - public onTunnelClosed: Event = this._onTunnelClosed.event; - private readonly _tunnels = new Map }>(); + private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter(); + public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event; + private readonly _tunnels = new Map }>>(); private _tunnelProvider: ITunnelProvider | undefined; public constructor( @@ -130,23 +130,32 @@ export class TunnelService implements ITunnelService { } public get tunnels(): Promise { - return Promise.all(Array.from(this._tunnels.values()).map(x => x.value)); + const promises: Promise[] = []; + Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value))); + return Promise.all(promises); } dispose(): void { - for (const { value } of this._tunnels.values()) { - value.then(tunnel => tunnel.dispose()); + for (const portMap of this._tunnels.values()) { + for (const { value } of portMap.values()) { + value.then(tunnel => tunnel.dispose()); + } + portMap.clear(); } this._tunnels.clear(); } - openTunnel(remotePort: number, localPort: number): Promise | undefined { + openTunnel(remoteHost: string | undefined, remotePort: number, localPort: number): Promise | undefined { const remoteAuthority = this.environmentService.configuration.remoteAuthority; if (!remoteAuthority) { return undefined; } - const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remotePort, localPort); + if (!remoteHost || (remoteHost === '127.0.0.1')) { + remoteHost = 'localhost'; + } + + const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remoteHost, remotePort, localPort); if (!resolvedTunnel) { return resolvedTunnel; } @@ -165,48 +174,62 @@ export class TunnelService implements ITunnelService { tunnelLocalPort: tunnel.tunnelLocalPort, localAddress: tunnel.localAddress, dispose: () => { - const existing = this._tunnels.get(tunnel.tunnelRemotePort); - if (existing) { - existing.refcount--; - this.tryDisposeTunnel(tunnel.tunnelRemotePort, existing); + const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost); + if (existingHost) { + const existing = existingHost.get(tunnel.tunnelRemotePort); + if (existing) { + existing.refcount--; + this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing); + } } } }; } - private async tryDisposeTunnel(remotePort: number, tunnel: { refcount: number, readonly value: Promise }): Promise { + private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise }): Promise { if (tunnel.refcount <= 0) { const disposePromise: Promise = tunnel.value.then(tunnel => { tunnel.dispose(); - this._onTunnelClosed.fire(tunnel.tunnelRemotePort); + this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }); }); - this._tunnels.delete(remotePort); + if (this._tunnels.has(remoteHost)) { + this._tunnels.get(remoteHost)!.delete(remotePort); + } return disposePromise; } } - async closeTunnel(remotePort: number): Promise { - if (this._tunnels.has(remotePort)) { - const value = this._tunnels.get(remotePort)!; + async closeTunnel(remoteHost: string, remotePort: number): Promise { + const portMap = this._tunnels.get(remoteHost); + if (portMap && portMap.has(remotePort)) { + const value = portMap.get(remotePort)!; value.refcount = 0; - await this.tryDisposeTunnel(remotePort, value); + await this.tryDisposeTunnel(remoteHost, remotePort, value); } } - private retainOrCreateTunnel(remoteAuthority: string, remotePort: number, localPort?: number): Promise | undefined { - const existing = this._tunnels.get(remotePort); + private addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise) { + if (!this._tunnels.has(remoteHost)) { + this._tunnels.set(remoteHost, new Map()); + } + this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel }); + } + + private retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise | undefined { + const portMap = this._tunnels.get(remoteHost); + const existing = portMap ? portMap.get(remotePort) : undefined; if (existing) { ++existing.refcount; return existing.value; } if (this._tunnelProvider) { - const tunnel = this._tunnelProvider.forwardPort({ remote: { host: 'localhost', port: remotePort } }); + const tunnel = this._tunnelProvider.forwardPort({ remote: { host: remoteHost, port: remotePort } }); if (tunnel) { - this._tunnels.set(remotePort, { refcount: 1, value: tunnel }); + this.addTunnelToMap(remoteHost, remotePort, tunnel); } return tunnel; - } else { + } else if (remoteHost === 'localhost') { const options: IConnectionOptions = { commit: product.commit, socketFactory: nodeSocketFactory, @@ -221,9 +244,10 @@ export class TunnelService implements ITunnelService { }; const tunnel = createRemoteTunnel(options, remotePort, localPort); - this._tunnels.set(remotePort, { refcount: 1, value: tunnel }); + this.addTunnelToMap(remoteHost, remotePort, tunnel); return tunnel; } + return undefined; } } From a6652eec0719f01f2f57299a3a855db0e4e0bbc8 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 18 Dec 2019 10:18:26 +0100 Subject: [PATCH 089/241] Update vscode.proposed.d.ts to reflect new state of forwardPort and makeTunnel --- src/vs/vscode.proposed.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 4d0c545db31..ad7d468f6fb 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -79,7 +79,8 @@ declare module 'vscode' { export namespace workspace { /** - * Forwards a port. Currently only works for a remote host of localhost. + * Forwards a port. If the current resolver implements RemoteAuthorityResolver:forwardPort then that will be used to make the tunnel. + * By default, makeTunnel only support localhost; however, RemoteAuthorityResolver:forwardPort can be used to support other ips. * @param forward The `localPort` is a suggestion only. If that port is not available another will be chosen. */ export function makeTunnel(forward: TunnelOptions): Thenable; From d79953fa273a80b118041eef93342d1ff088378c Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 18 Dec 2019 10:22:59 +0100 Subject: [PATCH 090/241] Fix alignment of action icons in Forwarded Ports view --- src/vs/workbench/contrib/remote/browser/media/tunnelView.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/media/tunnelView.css b/src/vs/workbench/contrib/remote/browser/media/tunnelView.css index 0b34a1d1259..659c875492f 100644 --- a/src/vs/workbench/contrib/remote/browser/media/tunnelView.css +++ b/src/vs/workbench/contrib/remote/browser/media/tunnelView.css @@ -6,7 +6,3 @@ .customview-tree .tunnel-view-label { flex: 1; } - -.customview-tree .tunnel-view-label .action-label.codicon { - margin-top: 4px; -} From a62ea85438935c69d4f49f7c990bb829064a6d53 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 18 Dec 2019 10:55:18 +0100 Subject: [PATCH 091/241] Polish forward port command --- .../contrib/remote/browser/tunnelView.ts | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 4f6ebb31e5e..daedf64538d 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -472,7 +472,7 @@ export class TunnelPanel extends ViewPane { this._register(Event.debounce(navigator.onDidOpenResource, (last, event) => event, 75, true)(e => { if (e.element && (e.element.tunnelType === TunnelType.Add)) { - this.commandService.executeCommand(ForwardPortAction.ID); + this.commandService.executeCommand(ForwardPortAction.ID, 'inline add'); } })); @@ -598,6 +598,7 @@ namespace LabelTunnelAction { namespace ForwardPortAction { export const ID = 'remote.tunnel.forward'; export const LABEL = nls.localize('remote.tunnel.forward', "Forward a Port"); + const forwardPrompt = nls.localize('remote.tunnel.forwardPrompt', "Port number or address (eg. 3000 or 10.10.10.10:2000)."); function parseInput(value: string): { host: string, port: number } | undefined { const matches = value.match(/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\:|localhost:)?([0-9]+)$/); @@ -607,14 +608,19 @@ namespace ForwardPortAction { return { host: matches[1]?.substring(0, matches[1].length - 1) || 'localhost', port: Number(matches[2]) }; } + function validateInput(value: string): string | null { + if (!parseInput(value)) { + return nls.localize('remote.tunnelsView.portNumberValid', "Port number is invalid"); + } + return null; + } + export function handler(): ICommandHandler { return async (accessor, arg) => { const remoteExplorerService = accessor.get(IRemoteExplorerService); if (arg instanceof TunnelItem) { remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort }); - } else { - const viewsService = accessor.get(IViewsService); - await viewsService.openView(TunnelPanel.ID, true); + } else if (arg) { remoteExplorerService.setEditable(undefined, undefined, { onFinish: (value, success) => { let parsed: { host: string, port: number } | undefined; @@ -623,14 +629,21 @@ namespace ForwardPortAction { } remoteExplorerService.setEditable(undefined, undefined, null); }, - validationMessage: (value) => { - if (!parseInput(value)) { - return nls.localize('remote.tunnelsView.portNumberValid', "Port number is invalid"); - } - return null; - }, - placeholder: nls.localize('remote.tunnelsView.forwardPortPlaceholder', "Port number") + validationMessage: validateInput, + placeholder: forwardPrompt }); + } else { + const viewsService = accessor.get(IViewsService); + const quickInputService = accessor.get(IQuickInputService); + await viewsService.openView(TunnelPanel.ID, true); + const value = await quickInputService.input({ + prompt: forwardPrompt, + validateInput: (value) => Promise.resolve(validateInput(value)) + }); + let parsed: { host: string, port: number } | undefined; + if (value && (parsed = parseInput(value))) { + remoteExplorerService.forward({ host: parsed.host, port: parsed.port }); + } } }; } From 26f5dfcd76c9a02f116a706844611aeb09ae8c0b Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Dec 2019 10:57:54 +0100 Subject: [PATCH 092/241] Revert "Windows ssh remote: Cannot use `\` in explorer new file dialog to create directories" This reverts commit 85f587ab646c2d7bcbe902aee0cb48ccfcfdb4fe. --- src/vs/workbench/contrib/files/browser/fileActions.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 5c0bb70b565..a2ae86d7408 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/fileactions'; import * as nls from 'vs/nls'; import { isWindows, isWeb } from 'vs/base/common/platform'; import * as extpath from 'vs/base/common/extpath'; -import { extname, basename, posix, win32 } from 'vs/base/common/path'; +import { extname, basename } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -45,7 +45,6 @@ import { asDomUri, triggerDownload } from 'vs/base/browser/dom'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ILabelService } from 'vs/platform/label/common/label'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -855,7 +854,6 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole const editorService = accessor.get(IEditorService); const viewletService = accessor.get(IViewletService); const notificationService = accessor.get(INotificationService); - const labelService = accessor.get(ILabelService); await viewletService.openViewlet(VIEWLET_ID, true); @@ -878,10 +876,7 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole folder.addChild(newStat); const onSuccess = (value: string): Promise => { - const separator = labelService.getSeparator(folder.resource.scheme); - const resource = folder.resource.with({ path: separator === '/' ? posix.join(folder.resource.path, value) : win32.join(folder.resource.path, value) }); - const createPromise = isFolder ? fileService.createFolder(resource) : textFileService.create(resource); - + const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value)); return createPromise.then(created => { refreshIfSeparator(value, explorerService); return isFolder ? explorerService.select(created.resource, true) From 728416a0dbbdc88f30f803f8a5a9f22b55b9c6e2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 11:26:57 +0100 Subject: [PATCH 093/241] window - save window state on focus lost (fix #87171) --- .../electron-main/windowsMainService.ts | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 101dd334ad3..6515a6325f1 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -13,14 +13,14 @@ import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { IStateService } from 'vs/platform/state/node/state'; import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; -import { ipcMain as ipc, screen, BrowserWindow, systemPreferences, MessageBoxOptions, Display } from 'electron'; +import { ipcMain as ipc, screen, BrowserWindow, systemPreferences, MessageBoxOptions, Display, app } from 'electron'; import { parseLineAndColumnAware } from 'vs/code/node/paths'; import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest } from 'vs/platform/windows/common/windows'; import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/platform/windows/node/window'; -import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import product from 'vs/platform/product/common/product'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; @@ -160,14 +160,16 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private readonly windowsState: IWindowsState; private lastClosedWindowState?: IWindowState; + private shuttingDown = false; + private readonly _onWindowReady = this._register(new Emitter()); - readonly onWindowReady: CommonEvent = this._onWindowReady.event; + readonly onWindowReady = this._onWindowReady.event; private readonly _onWindowClose = this._register(new Emitter()); - readonly onWindowClose: CommonEvent = this._onWindowClose.event; + readonly onWindowClose = this._onWindowClose.event; private readonly _onWindowsCountChanged = this._register(new Emitter()); - readonly onWindowsCountChanged: CommonEvent = this._onWindowsCountChanged.event; + readonly onWindowsCountChanged = this._onWindowsCountChanged.event; constructor( private readonly machineId: string, @@ -236,6 +238,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic systemPreferences.on('high-contrast-color-scheme-changed', () => onHighContrastChange()); } + // When a window looses focus, save all windows state. This allows to + // prevent loss of window-state data when OS is restarted without properly + // shutting down the application (https://github.com/microsoft/vscode/issues/87171) + app.on('browser-window-blur', () => { + if (!this.shuttingDown) { + this.saveWindowsState(); + } + }); + // Handle various lifecycle events around windows this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window)); this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown()); @@ -292,6 +303,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0) // private onBeforeShutdown(): void { + this.shuttingDown = true; + + this.saveWindowsState(); + } + + private saveWindowsState(): void { const currentWindowsState: IWindowsState = { openedWindows: [], lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow, @@ -327,8 +344,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Persist const state = getWindowsStateStoreData(currentWindowsState); - this.logService.trace('onBeforeShutdown', state); this.stateService.setItem(WindowsMainService.windowsStateStorageKey, state); + + if (this.shuttingDown) { + this.logService.trace('onBeforeShutdown', state); + } } // See note on #onBeforeShutdown() for details how these events are flowing From 3cac79829b5b8d5f221c92d72428e7317d955ba5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 11:28:51 +0100 Subject: [PATCH 094/241] adopt to new view containers --- .../markers/browser/markers.contribution.ts | 73 +++++----- .../contrib/markers/browser/markersPanel.ts | 134 +++++++++++------- .../markers/browser/markersPanelActions.ts | 1 + 3 files changed, 118 insertions(+), 90 deletions(-) diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 2bda0504826..e6a39eb883f 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -12,7 +12,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 } from 'vs/workbench/contrib/markers/browser/markersModel'; -import { MarkersPanel, MarkersPane } from 'vs/workbench/contrib/markers/browser/markersPanel'; +import { MarkersPanel, MarkersView, getMarkersView } from 'vs/workbench/contrib/markers/browser/markersPanel'; import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction } from 'vs/platform/actions/common/actions'; import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -42,8 +42,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.WinCtrl | KeyCode.Enter }, handler: (accessor, args: any) => { - const markersPane = (accessor.get(IPanelService).getActivePanel()).getMarkersPane(); - markersPane.openFileAtElement(markersPane.getFocusElement(), false, true, true); + const markersView = getMarkersView(accessor.get(IPanelService))!; + markersView.openFileAtElement(markersView.getFocusElement(), false, true, true); } }); @@ -63,10 +63,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: Constants.MarkerFocusContextKey, primary: KeyMod.CtrlCmd | KeyCode.US_DOT, handler: (accessor, args: any) => { - const markersPane = (accessor.get(IPanelService).getActivePanel()).getMarkersPane(); - const focusedElement = markersPane.getFocusElement(); + const markersView = getMarkersView(accessor.get(IPanelService))!; + const focusedElement = markersView.getFocusElement(); if (focusedElement instanceof Marker) { - markersPane.showQuickFixes(focusedElement); + markersView.showQuickFixes(focusedElement); } } }); @@ -107,7 +107,7 @@ Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews id: Constants.MARKERS_VIEW_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, canToggleVisibility: false, - ctorDescriptor: { ctor: MarkersPane }, + ctorDescriptor: { ctor: MarkersView }, }], VIEW_CONTAINER); // workbench @@ -123,8 +123,8 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowProblemsPanelAc registerAction({ id: Constants.MARKER_COPY_ACTION_ID, title: { value: localize('copyMarker', "Copy"), original: 'Copy' }, - handler(accessor) { - copyMarker(accessor.get(IPanelService), accessor.get(IClipboardService)); + async handler(accessor) { + await copyMarker(accessor.get(IPanelService), accessor.get(IClipboardService)); }, menu: { menuId: MenuId.ProblemsPanelContext, @@ -142,8 +142,8 @@ registerAction({ registerAction({ id: Constants.MARKER_COPY_MESSAGE_ACTION_ID, title: { value: localize('copyMessage', "Copy Message"), original: 'Copy Message' }, - handler(accessor) { - copyMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); + async handler(accessor) { + await copyMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); }, menu: { menuId: MenuId.ProblemsPanelContext, @@ -154,8 +154,8 @@ registerAction({ registerAction({ id: Constants.RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID, title: { value: localize('copyMessage', "Copy Message"), original: 'Copy Message' }, - handler(accessor) { - copyRelatedInformationMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); + async handler(accessor) { + await copyRelatedInformationMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); }, menu: { menuId: MenuId.ProblemsPanelContext, @@ -192,10 +192,9 @@ registerAction({ registerAction({ id: Constants.MARKERS_PANEL_SHOW_MULTILINE_MESSAGE, handler(accessor) { - const panelService = accessor.get(IPanelService); - const panel = panelService.getActivePanel(); - if (panel instanceof MarkersPanel) { - panel.getMarkersPane().markersViewModel.multiline = true; + const markersView = getMarkersView(accessor.get(IPanelService)); + if (markersView) { + markersView.markersViewModel.multiline = true; } }, title: { value: localize('show multiline', "Show message in multiple lines"), original: 'Problems: Show message in multiple lines' }, @@ -208,10 +207,9 @@ registerAction({ registerAction({ id: Constants.MARKERS_PANEL_SHOW_SINGLELINE_MESSAGE, handler(accessor) { - const panelService = accessor.get(IPanelService); - const panel = panelService.getActivePanel(); - if (panel instanceof MarkersPanel) { - panel.getMarkersPane().markersViewModel.multiline = false; + const markersView = getMarkersView(accessor.get(IPanelService)); + if (markersView) { + markersView.markersViewModel.multiline = false; } }, title: { value: localize('show singleline', "Show message in single line"), original: 'Problems: Show message in single line' }, @@ -222,33 +220,30 @@ registerAction({ } }); -async function copyMarker(panelService: IPanelService, clipboardService: IClipboardService): Promise { - const activePanel = panelService.getActivePanel(); - if (activePanel instanceof MarkersPanel) { - const markersPane = activePanel.getMarkersPane(); - const element = markersPane.getFocusElement(); +async function copyMarker(panelService: IPanelService, clipboardService: IClipboardService) { + const markersView = getMarkersView(panelService); + if (markersView) { + const element = markersView.getFocusElement(); if (element instanceof Marker) { await clipboardService.writeText(`${element}`); } } } -async function copyMessage(panelService: IPanelService, clipboardService: IClipboardService): Promise { - const activePanel = panelService.getActivePanel(); - if (activePanel instanceof MarkersPanel) { - const markersPane = activePanel.getMarkersPane(); - const element = markersPane.getFocusElement(); +async function copyMessage(panelService: IPanelService, clipboardService: IClipboardService) { + const markersView = getMarkersView(panelService); + if (markersView) { + const element = markersView.getFocusElement(); if (element instanceof Marker) { await clipboardService.writeText(element.marker.message); } } } -async function copyRelatedInformationMessage(panelService: IPanelService, clipboardService: IClipboardService): Promise { - const activePanel = panelService.getActivePanel(); - if (activePanel instanceof MarkersPanel) { - const markersPane = activePanel.getMarkersPane(); - const element = markersPane.getFocusElement(); +async function copyRelatedInformationMessage(panelService: IPanelService, clipboardService: IClipboardService) { + const markersView = getMarkersView(panelService); + if (markersView) { + const element = markersView.getFocusElement(); if (element instanceof RelatedInformation) { await clipboardService.writeText(element.raw.message); } @@ -263,9 +258,9 @@ function focusProblemsView(panelService: IPanelService) { } function focusProblemsFilter(panelService: IPanelService): void { - const activePanel = panelService.getActivePanel(); - if (activePanel instanceof MarkersPanel) { - activePanel.getMarkersPane().focusFilter(); + const markersView = getMarkersView(panelService); + if (markersView) { + markersView.focusFilter(); } } diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index 79f2b34a7be..0d73676150a 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -50,6 +50,15 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ViewPaneContainer, ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; + +export function getMarkersView(panelService: IPanelService): MarkersView | undefined { + const activePanel = panelService.getActivePanel(); + if (activePanel instanceof PaneCompositePanel) { + return activePanel.getViewPaneContainer().getView(Constants.MARKERS_VIEW_ID); + } + return undefined; +} function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterator> { const markersIt = Iterator.fromArray(resourceMarkers.markers); @@ -73,17 +82,13 @@ export class MarkersPanel extends PaneCompositePanel { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, @IWorkspaceContextService contextService: IWorkspaceContextService) { - super(Constants.MARKERS_PANEL_ID, instantiationService.createInstance(ViewPaneContainer, Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_STORAGE_ID, { showHeaderInTitleWhenSingleView: true }), + super(Constants.MARKERS_PANEL_ID, instantiationService.createInstance(ViewPaneContainer, Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_STORAGE_ID, { showHeaderInTitleWhenSingleView: true, donotShowViewTitleWhenSingleView: true }), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); } - getMarkersPane(): MarkersPane { - return this.viewPaneContainer.getView(Constants.MARKERS_VIEW_ID); - } - } -export class MarkersPane extends ViewPane implements IMarkerFilterController { +export class MarkersView extends ViewPane implements IMarkerFilterController { private lastSelectedRelativeTop: number = 0; private currentActiveResource: URI | null = null; @@ -91,11 +96,10 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { private readonly rangeHighlightDecorations: RangeHighlightDecorations; private readonly filter: Filter; - private tree!: MarkersTree; - private filterActionBar!: ActionBar; - private messageBoxContainer!: HTMLElement; - private ariaLabelElement!: HTMLElement; - + private tree: MarkersTree | undefined; + private filterActionBar: ActionBar | undefined; + private messageBoxContainer: HTMLElement | undefined; + private ariaLabelElement: HTMLElement | undefined; private readonly collapseAllAction: IAction; private readonly filterAction: MarkersFilterAction; @@ -174,7 +178,7 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { } })); - this.filterActionBar.push(this.filterAction); + this.filterActionBar!.push(this.filterAction); this.renderContent(); } @@ -187,22 +191,28 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { this.isSmallLayout = width < 600; if (this.isSmallLayout !== wasSmallLayout) { this.updateActions(); - dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); + if (this.filterActionBar) { + dom.toggleClass(this.filterActionBar.getContainer(), 'hide', !this.isSmallLayout); + } } const contentHeight = this.isSmallLayout ? height - 44 : height; - this.tree.layout(contentHeight, width); - this.messageBoxContainer.style.height = `${contentHeight}px`; + if (this.tree) { + this.tree.layout(contentHeight, width); + } + if (this.messageBoxContainer) { + this.messageBoxContainer.style.height = `${contentHeight}px`; + } this.filterAction.layout(this.isSmallLayout ? width : width - 200); } public focus(): void { - if (this.tree.getHTMLElement() === document.activeElement) { + if (this.tree && this.tree.getHTMLElement() === document.activeElement) { return; } - if (this.isEmpty()) { + if (this.isEmpty() && this.messageBoxContainer) { this.messageBoxContainer.focus(); - } else { + } else if (this.tree) { this.tree.getHTMLElement().focus(); } } @@ -265,7 +275,7 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { } private refreshPanel(markerOrChange?: Marker | MarkerChangesEvent): void { - if (this.isVisible()) { + if (this.isVisible() && this.tree) { this.cachedFilterStats = undefined; if (markerOrChange) { @@ -299,6 +309,9 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { } private resetTree(): void { + if (!this.tree) { + return; + } let resourceMarkers: ResourceMarkers[] = []; if (this.filterAction.activeFile) { if (this.currentActiveResource) { @@ -316,11 +329,15 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { private updateFilter() { this.cachedFilterStats = undefined; this.filter.options = new FilterOptions(this.filterAction.filterText, this.getFilesExcludeExpressions(), this.filterAction.showWarnings, this.filterAction.showErrors, this.filterAction.showInfos); - this.tree.refilter(); + if (this.tree) { + this.tree.refilter(); + } this._onDidFilter.fire(); const { total, filtered } = this.getFilterStats(); - this.tree.toggleVisibility(total === 0 || filtered === 0); + if (this.tree) { + this.tree.toggleVisibility(total === 0 || filtered === 0); + } this.renderMessage(); } @@ -434,7 +451,7 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { })); this._register(Event.any(this.tree.onDidChangeSelection, this.tree.onDidChangeFocus)(() => { - const elements = [...this.tree.getSelection(), ...this.tree.getFocus()]; + const elements = [...this.tree!.getSelection(), ...this.tree!.getFocus()]; for (const element of elements) { if (element instanceof Marker) { const viewModel = this.markersViewModel.getViewModel(element); @@ -447,11 +464,13 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { } private collapseAll(): void { - this.tree.collapseAll(); - this.tree.setSelection([]); - this.tree.setFocus([]); - this.tree.getHTMLElement().focus(); - this.tree.focusFirst(); + if (this.tree) { + this.tree.collapseAll(); + this.tree.setSelection([]); + this.tree.setFocus([]); + this.tree.getHTMLElement().focus(); + this.tree.focusFirst(); + } } private createListeners(): void { @@ -462,7 +481,9 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { this.onActiveEditorChanged(); } })); - this._register(this.tree.onDidChangeSelection(() => this.onSelected())); + if (this.tree) { + this._register(this.tree.onDidChangeSelection(() => this.onSelected())); + } this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { this.reportFilteringUsed(); if (event.activeFile) { @@ -521,9 +542,11 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { } private onSelected(): void { - let selection = this.tree.getSelection(); - if (selection && selection.length > 0) { - this.lastSelectedRelativeTop = this.tree.getRelativeTop(selection[0]) || 0; + if (this.tree) { + let selection = this.tree.getSelection(); + if (selection && selection.length > 0) { + this.lastSelectedRelativeTop = this.tree!.getRelativeTop(selection[0]) || 0; + } } } @@ -535,11 +558,16 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { private renderContent(): void { this.cachedFilterStats = undefined; this.resetTree(); - this.tree.toggleVisibility(this.isEmpty()); + if (this.tree) { + this.tree.toggleVisibility(this.isEmpty()); + } this.renderMessage(); } private renderMessage(): void { + if (!this.messageBoxContainer || !this.ariaLabelElement) { + return; + } dom.clearNode(this.messageBoxContainer); const { total, filtered } = this.getFilterStats(); @@ -589,19 +617,19 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { e.stopPropagation(); } }); - this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); + this.ariaLabelElement!.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); } private renderNoProblemsMessageForActiveFile(container: HTMLElement) { const span = dom.append(container, dom.$('span')); span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT; - this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT); + this.ariaLabelElement!.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT); } private renderNoProblemsMessage(container: HTMLElement) { const span = dom.append(container, dom.$('span')); span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT; - this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT); + this.ariaLabelElement!.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT); } private clearFilters(): void { @@ -614,7 +642,7 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { private autoReveal(focus: boolean = false): void { // No need to auto reveal if active file filter is on - if (this.filterAction.activeFile) { + if (this.filterAction.activeFile || !this.tree) { return; } let autoReveal = this.configurationService.getValue('problems.autoReveal'); @@ -647,11 +675,13 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { } private hasSelectedMarkerFor(resource: ResourceMarkers): boolean { - let selectedElement = this.tree.getSelection(); - if (selectedElement && selectedElement.length > 0) { - if (selectedElement[0] instanceof Marker) { - if (resource.resource.toString() === (selectedElement[0]).marker.resource.toString()) { - return true; + if (this.tree) { + let selectedElement = this.tree.getSelection(); + if (selectedElement && selectedElement.length > 0) { + if (selectedElement[0] instanceof Marker) { + if (resource.resource.toString() === (selectedElement[0]).marker.resource.toString()) { + return true; + } } } } @@ -660,13 +690,13 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { private updateRangeHighlights() { this.rangeHighlightDecorations.removeHighlightRange(); - if (this.tree.getHTMLElement() === document.activeElement) { + if (this.tree && this.tree.getHTMLElement() === document.activeElement) { this.highlightCurrentSelectedMarkerRange(); } } private highlightCurrentSelectedMarkerRange() { - const selections = this.tree.getSelection(); + const selections = this.tree ? this.tree.getSelection() : []; if (selections.length !== 1) { return; @@ -702,7 +732,7 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { }, onHide: (wasCancelled?: boolean) => { if (wasCancelled) { - this.tree.domFocus(); + this.tree!.domFocus(); } } }); @@ -722,7 +752,7 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { } } - const menu = this.menuService.createMenu(MenuId.ProblemsPanelContext, this.tree.contextKeyService); + const menu = this.menuService.createMenu(MenuId.ProblemsPanelContext, this.tree!.contextKeyService); const groups = menu.getActions(); menu.dispose(); @@ -737,7 +767,7 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { } public getFocusElement() { - return this.tree.getFocus()[0]; + return this.tree ? this.tree.getFocus()[0] : undefined; } public getActionViewItem(action: IAction): IActionViewItem | undefined { @@ -760,13 +790,15 @@ export class MarkersPane extends ViewPane implements IMarkerFilterController { } private computeFilterStats(): { total: number; filtered: number; } { - const root = this.tree.getNode(); 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++; + for (const resourceMarkerNode of root.children) { + for (const markerNode of resourceMarkerNode.children) { + if (resourceMarkerNode.visible && markerNode.visible) { + filtered++; + } } } } diff --git a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts index 39719499d5d..e786f650dcf 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts @@ -304,6 +304,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { this.element.className = this.action.class || ''; this.createInput(this.element); this.createControls(this.element); + this.updateClass(); this.adjustInputBox(); } From c7b1f298fd76c1236a87912ac0a50670745b0bcd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 11:37:43 +0100 Subject: [PATCH 095/241] move markers panel --- .../markers/browser/markers.contribution.ts | 48 ++++++++++++++----- .../contrib/markers/browser/markersPanel.ts | 19 +------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index e6a39eb883f..f24e6759dd6 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -12,9 +12,9 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { localize } from 'vs/nls'; import { Marker, RelatedInformation } from 'vs/workbench/contrib/markers/browser/markersModel'; -import { MarkersPanel, MarkersView, getMarkersView } from 'vs/workbench/contrib/markers/browser/markersPanel'; +import { MarkersView, getMarkersView } from 'vs/workbench/contrib/markers/browser/markersPanel'; import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction } from 'vs/platform/actions/common/actions'; -import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; +import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor, PaneCompositePanel } from 'vs/workbench/browser/panel'; import { Registry } from 'vs/platform/registry/common/platform'; import { ToggleMarkersPanelAction, ShowProblemsPanelAction } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; @@ -30,6 +30,14 @@ import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatus import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; registerSingleton(IMarkersWorkbenchService, MarkersWorkbenchService, false); @@ -92,7 +100,29 @@ Registry.as(Extensions.Configuration).registerConfigurat }); +// markers view container +const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(Constants.MARKERS_PANEL_ID, ViewContainerLocation.Panel); +Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ + id: Constants.MARKERS_VIEW_ID, + name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, + canToggleVisibility: false, + ctorDescriptor: { ctor: MarkersView }, +}], VIEW_CONTAINER); + // markers panel +class MarkersPanel extends PaneCompositePanel { + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService contextService: IWorkspaceContextService) { + super(Constants.MARKERS_PANEL_ID, instantiationService.createInstance(ViewPaneContainer, Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_STORAGE_ID, { showHeaderInTitleWhenSingleView: true, donotShowViewTitleWhenSingleView: true }), + telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + } +} Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( MarkersPanel, Constants.MARKERS_PANEL_ID, @@ -102,14 +132,6 @@ Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor ToggleMarkersPanelAction.ID )); -const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(Constants.MARKERS_PANEL_ID, ViewContainerLocation.Panel); -Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ - id: Constants.MARKERS_VIEW_ID, - name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, - canToggleVisibility: false, - ctorDescriptor: { ctor: MarkersView }, -}], VIEW_CONTAINER); - // workbench const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(ActivityUpdater, LifecyclePhase.Restored); @@ -251,9 +273,9 @@ async function copyRelatedInformationMessage(panelService: IPanelService, clipbo } function focusProblemsView(panelService: IPanelService) { - const activePanel = panelService.getActivePanel(); - if (activePanel instanceof MarkersPanel) { - activePanel.focus(); + const markersView = getMarkersView(panelService); + if (markersView) { + markersView.focus(); } } diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index 0d73676150a..08142832622 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -48,8 +48,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { KeyCode } from 'vs/base/common/keyCodes'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ViewPaneContainer, ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; export function getMarkersView(panelService: IPanelService): MarkersView | undefined { @@ -72,22 +71,6 @@ function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterat } -export class MarkersPanel extends PaneCompositePanel { - - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IStorageService storageService: IStorageService, - @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - @IWorkspaceContextService contextService: IWorkspaceContextService) { - super(Constants.MARKERS_PANEL_ID, instantiationService.createInstance(ViewPaneContainer, Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_STORAGE_ID, { showHeaderInTitleWhenSingleView: true, donotShowViewTitleWhenSingleView: true }), - telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); - } - -} - export class MarkersView extends ViewPane implements IMarkerFilterController { private lastSelectedRelativeTop: number = 0; From c55adf61c032decc0722ccb978f5e063bfdfe35f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 11:42:35 +0100 Subject: [PATCH 096/241] rename to markers view --- .../markers/browser/markers.contribution.ts | 19 ++++++++++++++++--- .../markers/browser/markersTreeViewer.ts | 2 +- .../{markersPanel.ts => markersView.ts} | 4 ++-- ...sPanelActions.ts => markersViewActions.ts} | 15 --------------- 4 files changed, 19 insertions(+), 21 deletions(-) rename src/vs/workbench/contrib/markers/browser/{markersPanel.ts => markersView.ts} (99%) rename src/vs/workbench/contrib/markers/browser/{markersPanelActions.ts => markersViewActions.ts} (96%) diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index f24e6759dd6..53101af8b25 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -12,11 +12,11 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { localize } from 'vs/nls'; import { Marker, RelatedInformation } from 'vs/workbench/contrib/markers/browser/markersModel'; -import { MarkersView, getMarkersView } from 'vs/workbench/contrib/markers/browser/markersPanel'; +import { MarkersView, getMarkersView } from 'vs/workbench/contrib/markers/browser/markersView'; import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction } from 'vs/platform/actions/common/actions'; -import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor, PaneCompositePanel } from 'vs/workbench/browser/panel'; +import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor, PaneCompositePanel, TogglePanelAction } from 'vs/workbench/browser/panel'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ToggleMarkersPanelAction, ShowProblemsPanelAction } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; +import { ShowProblemsPanelAction } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -38,6 +38,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; registerSingleton(IMarkersWorkbenchService, MarkersWorkbenchService, false); @@ -123,6 +124,18 @@ class MarkersPanel extends PaneCompositePanel { telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); } } +class ToggleMarkersPanelAction extends TogglePanelAction { + + public static readonly ID = 'workbench.actions.view.problems'; + public static readonly LABEL = Messages.MARKERS_PANEL_TOGGLE_LABEL; + + constructor(id: string, label: string, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IPanelService panelService: IPanelService + ) { + super(id, label, Constants.MARKERS_PANEL_ID, panelService, layoutService); + } +} Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( MarkersPanel, Constants.MARKERS_PANEL_ID, diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 19fac10efdc..4b63f2f07b4 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -17,7 +17,7 @@ import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { QuickFixAction, QuickFixActionViewItem } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; +import { QuickFixAction, QuickFixActionViewItem } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { ILabelService } from 'vs/platform/label/common/label'; import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts similarity index 99% rename from src/vs/workbench/contrib/markers/browser/markersPanel.ts rename to src/vs/workbench/contrib/markers/browser/markersView.ts index 08142832622..1b586f7cb21 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -14,7 +14,7 @@ import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/ import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { Marker, ResourceMarkers, RelatedInformation, MarkerChangesEvent } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersFilterActionViewItem, MarkersFilterAction, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; +import { MarkersFilterActionViewItem, MarkersFilterAction, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; @@ -376,7 +376,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { }; this.tree = this._register(this.instantiationService.createInstance(MarkersTree, - 'MarkersPanel', + 'MarkersView', dom.append(parent, dom.$('.tree-container.show-file-icons')), virtualDelegate, renderers, diff --git a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts similarity index 96% rename from src/vs/workbench/contrib/markers/browser/markersPanelActions.ts rename to src/vs/workbench/contrib/markers/browser/markersViewActions.ts index e786f650dcf..c21572f70bf 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersViewActions.ts @@ -10,10 +10,8 @@ import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { TogglePanelAction } from 'vs/workbench/browser/panel'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IThemeService, registerThemingParticipant, ICssStyleCollector, ITheme } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; @@ -30,19 +28,6 @@ import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilte import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -export class ToggleMarkersPanelAction extends TogglePanelAction { - - public static readonly ID = 'workbench.actions.view.problems'; - public static readonly LABEL = Messages.MARKERS_PANEL_TOGGLE_LABEL; - - constructor(id: string, label: string, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IPanelService panelService: IPanelService - ) { - super(id, label, Constants.MARKERS_PANEL_ID, panelService, layoutService); - } -} - export class ShowProblemsPanelAction extends Action { public static readonly ID = 'workbench.action.problems.focus'; From 19062a297616055203708da0f86381d7d4a35572 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 11:42:43 +0100 Subject: [PATCH 097/241] debt - move workbench.enableExperiments to correct place --- build/lib/i18n.resources.json | 4 ++++ .../browser/workbench.contribution.ts | 6 ------ .../browser/experiments.contribution.ts | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index e9a4f279631..e82324735d7 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -58,6 +58,10 @@ "name": "vs/workbench/contrib/emmet", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/experiments", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/extensions", "project": "vscode-workbench" diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 854db17c376..2cd6fba7098 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -235,12 +235,6 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio 'description': nls.localize('settings.editor.desc', "Determines which settings editor to use by default."), 'default': 'ui', 'scope': ConfigurationScope.WINDOW - }, - 'workbench.enableExperiments': { - 'type': 'boolean', - 'description': nls.localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."), - 'default': true, - 'tags': ['usesOnlineServices'] } } }); diff --git a/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts b/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts index 67b6159734f..cc24a8695e8 100644 --- a/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts +++ b/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts @@ -3,13 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; registerSingleton(IExperimentService, ExperimentService, true); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExperimentalPrompts, LifecyclePhase.Eventually); + +const registry = Registry.as(ConfigurationExtensions.Configuration); + +// Configuration +registry.registerConfiguration({ + ...workbenchConfigurationNodeBase, + 'properties': { + 'workbench.enableExperiments': { + 'type': 'boolean', + 'description': localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."), + 'default': true, + 'tags': ['usesOnlineServices'] + } + } +}); From b6ae21e6e2e7255978993df45c1317a869170d21 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 11:46:26 +0100 Subject: [PATCH 098/241] tests - increase timeout for backup test --- .../backup/test/electron-browser/backupRestorer.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts index 1f55662a66d..19054f498f3 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts @@ -93,7 +93,9 @@ suite('BackupModelRestorer', () => { return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); }); - test('Restore backups', async () => { + test('Restore backups', async function () { + this.timeout(20000); + const backupFileService = new NodeTestBackupFileService(workspaceBackupPath); const instantiationService = workbenchInstantiationService(); instantiationService.stub(IBackupFileService, backupFileService); From 67a6aac599cdabff3c5aa31b150ef2b05c9305ee Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 12:12:53 +0100 Subject: [PATCH 099/241] #87243 Support hiding view container title when merged --- .../api/browser/viewsExtensionPoint.ts | 2 +- .../browser/parts/views/viewPaneContainer.ts | 37 ++++++++++--------- .../browser/parts/views/viewsViewlet.ts | 2 +- .../contrib/debug/browser/debugViewlet.ts | 2 +- .../extensions/browser/extensionsViewlet.ts | 2 +- .../contrib/files/browser/explorerViewlet.ts | 2 +- .../contrib/scm/browser/scmViewlet.ts | 2 +- .../contrib/search/browser/searchViewlet.ts | 2 +- 8 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 16b29f60591..ff322f7237f 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -327,7 +327,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, ) { - super(id, `${id}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); } } diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 5d2b3bbefa6..bd80bbfd037 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -234,7 +234,8 @@ export abstract class ViewPane extends Pane implements IView { } export interface IViewPaneContainerOptions extends IPaneViewOptions { - showHeaderInTitleWhenSingleView: boolean; + mergeViewWithContainerWhenSingleView: boolean; + donotShowContainerTitleWhenMergedWithContainer?: boolean; } interface IViewPaneItem { @@ -346,14 +347,16 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { getTitle(): string { const composite = Registry.as(ViewletExtensions.Viewlets).getViewlet(this.getId()) || Registry.as(PanelExtensions.Panels).getPanel(this.getId()); - let title = composite.name; - if (this.isSingleView()) { + if (this.isViewMergedWithContainer()) { const paneItemTitle = this.paneItems[0].pane.title; - title = paneItemTitle ? `${title}: ${paneItemTitle}` : title; + if (this.options.donotShowContainerTitleWhenMergedWithContainer) { + return this.paneItems[0].pane.title; + } + return paneItemTitle ? `${composite.name}: ${paneItemTitle}` : composite.name; } - return title; + return composite.name; } private showContextMenu(event: StandardMouseEvent): void { @@ -402,7 +405,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getActions(): IAction[] { - if (this.isSingleView()) { + if (this.isViewMergedWithContainer()) { return this.paneItems[0].pane.getActions(); } @@ -410,7 +413,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getSecondaryActions(): IAction[] { - if (this.isSingleView()) { + if (this.isViewMergedWithContainer()) { return this.paneItems[0].pane.getSecondaryActions(); } @@ -418,7 +421,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getActionViewItem(action: IAction): IActionViewItem | undefined { - if (this.isSingleView()) { + if (this.isViewMergedWithContainer()) { return this.paneItems[0].pane.getActionViewItem(action); } @@ -459,14 +462,14 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } addPanes(panes: { pane: ViewPane, size: number, index?: number; }[]): void { - const wasSingleView = this.isSingleView(); + const wasMerged = this.isViewMergedWithContainer(); for (const { pane: pane, size, index } of panes) { this.addPane(pane, size, index); } this.updateViewHeaders(); - if (this.isSingleView() !== wasSingleView) { + if (this.isViewMergedWithContainer() !== wasMerged) { this.updateTitleArea(); } } @@ -643,7 +646,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void { const onDidFocus = pane.onDidFocus(() => this.lastFocusedPane = pane); const onDidChangeTitleArea = pane.onDidChangeTitleArea(() => { - if (this.isSingleView()) { + if (this.isViewMergedWithContainer()) { this.updateTitleArea(); } }); @@ -668,12 +671,12 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } removePanes(panes: ViewPane[]): void { - const wasSingleView = this.isSingleView(); + const wasMerged = this.isViewMergedWithContainer(); panes.forEach(pane => this.removePane(pane)); this.updateViewHeaders(); - if (wasSingleView !== this.isSingleView()) { + if (wasMerged !== this.isViewMergedWithContainer()) { this.updateTitleArea(); } } @@ -726,8 +729,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { return assertIsDefined(this.paneview).getPaneSize(pane); } - protected updateViewHeaders(): void { - if (this.isSingleView()) { + private updateViewHeaders(): void { + if (this.isViewMergedWithContainer()) { this.paneItems[0].pane.setExpanded(true); this.paneItems[0].pane.headerVisible = false; } else { @@ -735,8 +738,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } } - protected isSingleView(): boolean { - if (!(this.options.showHeaderInTitleWhenSingleView && this.paneItems.length === 1)) { + private isViewMergedWithContainer(): boolean { + if (!(this.options.mergeViewWithContainerWhenSingleView && this.paneItems.length === 1)) { return false; } if (!this.areExtensionsReady) { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index fa22f5d0b97..078939500ef 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -46,7 +46,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { @IWorkspaceContextService contextService: IWorkspaceContextService ) { - super(viewletId, `${viewletId}.state`, { showHeaderInTitleWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(viewletId, `${viewletId}.state`, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); this._register(onDidChangeFilterValue(newFilterValue => { this.filterValue = newFilterValue; this.onFilterChanged(newFilterValue); diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 86fb545750b..562559bbd05 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -79,7 +79,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer { @IContextKeyService private readonly contextKeyService: IContextKeyService, @INotificationService private readonly notificationService: INotificationService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state))); this._register(this.debugService.onDidNewSession(() => this.updateToolBar())); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 32944476223..72c39d72b82 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -370,7 +370,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); this.searchDelayer = new Delayer(500); this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 601ed0673d3..083cd8bd13d 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -183,7 +183,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { @IExtensionService extensionService: IExtensionService ) { - super(VIEWLET_ID, ExplorerViewPaneContainer.EXPLORER_VIEWS_STATE, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, ExplorerViewPaneContainer.EXPLORER_VIEWS_STATE, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 145596f7591..95fbff130af 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -116,7 +116,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super(VIEWLET_ID, SCMViewPaneContainer.STATE_KEY, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, SCMViewPaneContainer.STATE_KEY, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); this.menus = instantiationService.createInstance(SCMMenus, undefined); this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this)); diff --git a/src/vs/workbench/contrib/search/browser/searchViewlet.ts b/src/vs/workbench/contrib/search/browser/searchViewlet.ts index a216091af0c..4b153f60e47 100644 --- a/src/vs/workbench/contrib/search/browser/searchViewlet.ts +++ b/src/vs/workbench/contrib/search/browser/searchViewlet.ts @@ -48,7 +48,7 @@ export class SearchViewPaneContainer extends ViewPaneContainer { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); } getTitle(): string { From 3be2aeddcb318817738c2e5d061e86d03857f0e9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 12:14:38 +0100 Subject: [PATCH 100/241] adopt to new options --- .../workbench/contrib/markers/browser/markers.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 53101af8b25..627505bc2eb 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -120,7 +120,7 @@ class MarkersPanel extends PaneCompositePanel { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, @IWorkspaceContextService contextService: IWorkspaceContextService) { - super(Constants.MARKERS_PANEL_ID, instantiationService.createInstance(ViewPaneContainer, Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_STORAGE_ID, { showHeaderInTitleWhenSingleView: true, donotShowViewTitleWhenSingleView: true }), + super(Constants.MARKERS_PANEL_ID, instantiationService.createInstance(ViewPaneContainer, Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_STORAGE_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); } } From e7235916ba8843e11be004159382c4e2c542a18b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 12:24:49 +0100 Subject: [PATCH 101/241] file service - add event for listening to capability changes --- src/vs/platform/files/common/fileService.ts | 14 +++++++++----- src/vs/platform/files/common/files.ts | 9 +++++++++ .../files/test/browser/fileService.test.ts | 19 ++++++++++++++++--- .../test/common/nullFileSystemProvider.ts | 14 +++++++++++--- .../workbench/test/workbenchTestServices.ts | 1 + 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 870af29a60a..2f2613c0948 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, toDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; -import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, hasFileReadStreamCapability, IFileSystemProviderWithFileReadStreamCapability, ensureFileSystemProviderError } from 'vs/platform/files/common/files'; +import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED, hasFileReadStreamCapability, IFileSystemProviderWithFileReadStreamCapability, ensureFileSystemProviderError, IFileSystemProviderCapabilitiesChangeEvent } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isAbsolutePath, dirname, basename, joinPath, isEqual, isEqualOrParent } from 'vs/base/common/resources'; @@ -33,11 +33,14 @@ export class FileService extends Disposable implements IFileService { //#region File System Provider - private _onDidChangeFileSystemProviderRegistrations: Emitter = this._register(new Emitter()); - readonly onDidChangeFileSystemProviderRegistrations: Event = this._onDidChangeFileSystemProviderRegistrations.event; + private _onDidChangeFileSystemProviderRegistrations = this._register(new Emitter()); + readonly onDidChangeFileSystemProviderRegistrations = this._onDidChangeFileSystemProviderRegistrations.event; - private _onWillActivateFileSystemProvider: Emitter = this._register(new Emitter()); - readonly onWillActivateFileSystemProvider: Event = this._onWillActivateFileSystemProvider.event; + private _onWillActivateFileSystemProvider = this._register(new Emitter()); + readonly onWillActivateFileSystemProvider = this._onWillActivateFileSystemProvider.event; + + private _onDidChangeFileSystemProviderCapabilities = this._register(new Emitter()); + readonly onDidChangeFileSystemProviderCapabilities = this._onDidChangeFileSystemProviderCapabilities.event; private readonly provider = new Map(); @@ -53,6 +56,7 @@ export class FileService extends Disposable implements IFileService { // Forward events from provider const providerDisposables = new DisposableStore(); providerDisposables.add(provider.onDidChangeFile(changes => this._onFileChanges.fire(new FileChangesEvent(changes)))); + providerDisposables.add(provider.onDidChangeCapabilities(() => this._onDidChangeFileSystemProviderCapabilities.fire({ provider }))); if (typeof provider.onDidErrorOccur === 'function') { providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(new Error(error)))); } diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index c00e9b17fef..c567ecc1b67 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -28,6 +28,11 @@ export interface IFileService { */ readonly onDidChangeFileSystemProviderRegistrations: Event; + /** + * An even that is fired when a registered file system provider changes it's capabilities. + */ + readonly onDidChangeFileSystemProviderCapabilities: Event; + /** * An event that is fired when a file system provider is about to be activated. Listeners * can join this event with a long running promise to help in the activation process. @@ -409,6 +414,10 @@ export interface IFileSystemProviderRegistrationEvent { provider?: IFileSystemProvider; } +export interface IFileSystemProviderCapabilitiesChangeEvent { + provider: IFileSystemProvider; +} + export interface IFileSystemProviderActivationEvent { scheme: string; join(promise: Promise): void; diff --git a/src/vs/platform/files/test/browser/fileService.test.ts b/src/vs/platform/files/test/browser/fileService.test.ts index 2eb366a65f0..edc32f0a3b4 100644 --- a/src/vs/platform/files/test/browser/fileService.test.ts +++ b/src/vs/platform/files/test/browser/fileService.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { FileService } from 'vs/platform/files/common/fileService'; import { URI } from 'vs/base/common/uri'; -import { IFileSystemProviderRegistrationEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; +import { IFileSystemProviderRegistrationEvent, FileSystemProviderCapabilities, IFileSystemProviderCapabilitiesChangeEvent } from 'vs/platform/files/common/files'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { NullLogService } from 'vs/platform/log/common/log'; import { timeout } from 'vs/base/common/async'; @@ -17,6 +17,7 @@ suite('File Service', () => { test('provider registration', async () => { const service = new FileService(new NullLogService()); const resource = URI.parse('test://foo/bar'); + const provider = new NullFileSystemProvider(); assert.equal(service.canHandleResource(resource), false); @@ -25,6 +26,11 @@ suite('File Service', () => { registrations.push(e); }); + const capabilityChanges: IFileSystemProviderCapabilitiesChangeEvent[] = []; + service.onDidChangeFileSystemProviderCapabilities(e => { + capabilityChanges.push(e); + }); + let registrationDisposable: IDisposable | undefined = undefined; let callCount = 0; service.onWillActivateFileSystemProvider(e => { @@ -32,7 +38,7 @@ suite('File Service', () => { if (e.scheme === 'test' && callCount === 1) { e.join(new Promise(resolve => { - registrationDisposable = service.registerProvider('test', new NullFileSystemProvider()); + registrationDisposable = service.registerProvider('test', provider); resolve(); })); @@ -48,6 +54,13 @@ suite('File Service', () => { assert.equal(registrations[0].added, true); assert.ok(registrationDisposable); + assert.equal(capabilityChanges.length, 0); + + provider.setCapabilities(FileSystemProviderCapabilities.FileFolderCopy); + assert.equal(capabilityChanges.length, 1); + provider.setCapabilities(FileSystemProviderCapabilities.Readonly); + assert.equal(capabilityChanges.length, 2); + await service.activateProvider('test'); assert.equal(callCount, 2); // activation is called again @@ -109,4 +122,4 @@ suite('File Service', () => { watcher3Disposable2.dispose(); assert.equal(disposeCounter, 2); }); -}); \ No newline at end of file +}); diff --git a/src/vs/platform/files/test/common/nullFileSystemProvider.ts b/src/vs/platform/files/test/common/nullFileSystemProvider.ts index 59ba8e873cd..27250d0a7d1 100644 --- a/src/vs/platform/files/test/common/nullFileSystemProvider.ts +++ b/src/vs/platform/files/test/common/nullFileSystemProvider.ts @@ -6,14 +6,22 @@ import { URI } from 'vs/base/common/uri'; import { FileSystemProviderCapabilities, IFileSystemProvider, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, IFileChange } from 'vs/platform/files/common/files'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; export class NullFileSystemProvider implements IFileSystemProvider { capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.Readonly; - onDidChangeCapabilities: Event = Event.None; - onDidChangeFile: Event = Event.None; + private readonly _onDidChangeCapabilities = new Emitter(); + readonly onDidChangeCapabilities: Event = this._onDidChangeCapabilities.event; + + setCapabilities(capabilities: FileSystemProviderCapabilities): void { + this.capabilities = capabilities; + + this._onDidChangeCapabilities.fire(); + } + + readonly onDidChangeFile: Event = Event.None; constructor(private disposableFactory: () => IDisposable = () => Disposable.None) { } diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 2ff6ff19bc2..10055eaaf1a 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -966,6 +966,7 @@ export class TestFileService implements IFileService { private readonly _onAfterOperation: Emitter; readonly onWillActivateFileSystemProvider = Event.None; + readonly onDidChangeFileSystemProviderCapabilities = Event.None; readonly onError: Event = Event.None; private content = 'Hello Html'; From 7846353fec1b3c56c0d30ca8bcff11abc5b52b46 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 12:29:29 +0100 Subject: [PATCH 102/241] use donotShowContainerTitleWhenMergedWithContainer option --- src/vs/workbench/contrib/search/browser/searchViewlet.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchViewlet.ts b/src/vs/workbench/contrib/search/browser/searchViewlet.ts index 4b153f60e47..827ab6cc96b 100644 --- a/src/vs/workbench/contrib/search/browser/searchViewlet.ts +++ b/src/vs/workbench/contrib/search/browser/searchViewlet.ts @@ -14,8 +14,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { ViewletRegistry, Extensions, Viewlet } from 'vs/workbench/browser/viewlet'; +import { Viewlet } from 'vs/workbench/browser/viewlet'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; @@ -48,11 +47,7 @@ export class SearchViewPaneContainer extends ViewPaneContainer { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); - } - - getTitle(): string { - return Registry.as(Extensions.Viewlets).getViewlet(this.getId()).name; + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); } getSearchView(): SearchView | undefined { From 94b843562ef29ac9e0afcfc88221a1453f4f5600 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Dec 2019 12:39:27 +0100 Subject: [PATCH 103/241] fileService: add scheme to change capaiblity event --- src/vs/platform/files/common/fileService.ts | 2 +- src/vs/platform/files/common/files.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 2f2613c0948..268f00245ac 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -56,7 +56,7 @@ export class FileService extends Disposable implements IFileService { // Forward events from provider const providerDisposables = new DisposableStore(); providerDisposables.add(provider.onDidChangeFile(changes => this._onFileChanges.fire(new FileChangesEvent(changes)))); - providerDisposables.add(provider.onDidChangeCapabilities(() => this._onDidChangeFileSystemProviderCapabilities.fire({ provider }))); + providerDisposables.add(provider.onDidChangeCapabilities(() => this._onDidChangeFileSystemProviderCapabilities.fire({ provider, scheme }))); if (typeof provider.onDidErrorOccur === 'function') { providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(new Error(error)))); } diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index c567ecc1b67..a45eb2016cf 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -416,6 +416,7 @@ export interface IFileSystemProviderRegistrationEvent { export interface IFileSystemProviderCapabilitiesChangeEvent { provider: IFileSystemProvider; + scheme: string; } export interface IFileSystemProviderActivationEvent { From 8bc277fa7bc70e547b75136f23f9047b8e05ec3c Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Dec 2019 12:42:17 +0100 Subject: [PATCH 104/241] explorer: read all capabilites like a proper gentlemen #48258 --- .../browser/editors/fileEditorTracker.ts | 7 ++- .../contrib/files/browser/fileActions.ts | 5 ++- .../files/browser/views/explorerViewer.ts | 13 +++--- .../contrib/files/common/explorerModel.ts | 34 +++++++------- .../contrib/files/common/explorerService.ts | 44 +++++++------------ .../workbench/contrib/files/common/files.ts | 1 - .../electron-browser/explorerModel.test.ts | 21 +++------ .../workbench/test/workbenchTestServices.ts | 8 +++- 8 files changed, 58 insertions(+), 75 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index a253fbaea92..a1bf560bef7 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor'; import { ITextFileService, TextFileModelChangeEvent, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; -import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; +import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -25,7 +25,6 @@ import { timeout } from 'vs/base/common/async'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { isEqualOrParent, joinPath } from 'vs/base/common/resources'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; export class FileEditorTracker extends Disposable implements IWorkbenchContribution { @@ -44,7 +43,6 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IHostService private readonly hostService: IHostService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, - @IExplorerService private readonly explorerService: IExplorerService ) { super(); @@ -108,7 +106,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut if (oldResource.toString() === resource.toString()) { reopenFileResource = newResource; // file got moved } else { - const index = this.getIndexOfPath(resource.path, oldResource.path, this.explorerService.shouldIgnoreCase(resource)); + const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive); + const index = this.getIndexOfPath(resource.path, oldResource.path, ignoreCase); reopenFileResource = joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved } diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index a2ae86d7408..3fe0e232847 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -870,8 +870,9 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole throw new Error('Parent folder is readonly.'); } - const newStat = new NewExplorerItem(explorerService, folder, isFolder); - await folder.fetchChildren(fileService, explorerService); + const newStat = new NewExplorerItem(fileService, folder, isFolder); + const sortOrder = explorerService.sortOrder; + await folder.fetchChildren(sortOrder); folder.addChild(newStat); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 0328d0f5fc2..7ca8ee5af60 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, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDisposable, Disposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -90,12 +90,13 @@ export class ExplorerDataSource implements IAsyncDataSource { + const sortOrder = this.explorerService.sortOrder; + const promise = element.fetchChildren(sortOrder).then(undefined, e => { if (element instanceof ExplorerItem && element.isRoot) { if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { // Single folder create a dummy explorer item to show error - const placeholder = new ExplorerItem(element.resource, this.explorerService, undefined, false); + const placeholder = new ExplorerItem(element.resource, this.fileService, undefined, false); placeholder.isError = true; return [placeholder]; } else { @@ -921,17 +922,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Check for name collisions const targetNames = new Set(); + const caseSensitive = this.fileService.hasCapability(target.resource, FileSystemProviderCapabilities.PathCaseSensitive); if (targetStat.children) { - const ignoreCase = this.explorerService.shouldIgnoreCase(target.resource); targetStat.children.forEach(child => { - targetNames.add(ignoreCase ? child.name.toLowerCase() : child.name); + targetNames.add(caseSensitive ? child.name : child.name.toLowerCase()); }); } // Run add in sequence const addPromisesFactory: ITask>[] = []; await Promise.all(resources.map(async resource => { - if (targetNames.has(this.explorerService.shouldIgnoreCase(resource) ? basename(resource).toLowerCase() : basename(resource))) { + if (targetNames.has(caseSensitive ? basename(resource) : basename(resource).toLowerCase())) { const confirmationResult = await this.dialogService.confirm(getFileOverwriteConfirm(basename(resource))); if (!confirmationResult.confirmed) { return; diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index d6bb9594120..fd01f455765 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -14,8 +14,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; -import { IExplorerService, SortOrder } from 'vs/workbench/contrib/files/common/files'; import { joinPath, isEqualOrParent, basenameOrAuthority } from 'vs/base/common/resources'; +import { SortOrder } from 'vs/workbench/contrib/files/common/files'; export class ExplorerModel implements IDisposable { @@ -25,10 +25,10 @@ export class ExplorerModel implements IDisposable { constructor( private readonly contextService: IWorkspaceContextService, - explorerService: IExplorerService + fileService: IFileService ) { const setRoots = () => this._roots = this.contextService.getWorkspace().folders - .map(folder => new ExplorerItem(folder.uri, explorerService, undefined, true, false, false, folder.name)); + .map(folder => new ExplorerItem(folder.uri, fileService, undefined, true, false, folder.name)); setRoots(); this._listener = this.contextService.onDidChangeWorkspaceFolders(() => { @@ -83,11 +83,10 @@ export class ExplorerItem { constructor( public resource: URI, - private readonly explorerService: IExplorerService, + private readonly fileService: IFileService, private _parent: ExplorerItem | undefined, private _isDirectory?: boolean, private _isSymbolicLink?: boolean, - private _isReadonly?: boolean, private _name: string = basenameOrAuthority(resource), private _mtime?: number, ) { @@ -112,7 +111,7 @@ export class ExplorerItem { } get isReadonly(): boolean { - return !!this._isReadonly; + return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); } get mtime(): number | undefined { @@ -158,8 +157,8 @@ export class ExplorerItem { return this === this.root; } - static create(explorerService: IExplorerService, fileService: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { - const stat = new ExplorerItem(raw.resource, explorerService, parent, raw.isDirectory, raw.isSymbolicLink, fileService.hasCapability(raw.resource, FileSystemProviderCapabilities.Readonly), raw.name, raw.mtime); + static create(fileService: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { + const stat = new ExplorerItem(raw.resource, fileService, parent, raw.isDirectory, raw.isSymbolicLink, raw.name, raw.mtime); // Recursively add children if present if (stat.isDirectory) { @@ -174,7 +173,7 @@ export class ExplorerItem { // Recurse into children if (raw.children) { for (let i = 0, len = raw.children.length; i < len; i++) { - const child = ExplorerItem.create(explorerService, fileService, raw.children[i], stat, resolveTo); + const child = ExplorerItem.create(fileService, raw.children[i], stat, resolveTo); stat.addChild(child); } } @@ -208,7 +207,6 @@ export class ExplorerItem { local._mtime = disk.mtime; local._isDirectoryResolved = disk._isDirectoryResolved; local._isSymbolicLink = disk.isSymbolicLink; - local._isReadonly = disk.isReadonly; local.isError = disk.isError; // Merge Children if resolved @@ -259,14 +257,14 @@ export class ExplorerItem { return this.children.get(this.getPlatformAwareName(name)); } - async fetchChildren(fileService: IFileService, explorerService: IExplorerService): Promise { + async fetchChildren(sortOrder: SortOrder): Promise { if (!this._isDirectoryResolved) { // Resolve metadata only when the mtime is needed since this can be expensive // Mtime is only used when the sort order is 'modified' - const resolveMetadata = explorerService.sortOrder === SortOrder.Modified; + const resolveMetadata = sortOrder === SortOrder.Modified; try { - const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); - const resolved = ExplorerItem.create(explorerService, fileService, stat, this); + const stat = await this.fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); + const resolved = ExplorerItem.create(this.fileService, stat, this); ExplorerItem.mergeLocalWithDisk(resolved, this); } catch (e) { this.isError = true; @@ -306,7 +304,7 @@ export class ExplorerItem { } private getPlatformAwareName(name: string): string { - return this.explorerService.shouldIgnoreCase(this.resource) ? name.toLowerCase() : name; + return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.PathCaseSensitive) ? name : name.toLowerCase(); } /** @@ -356,7 +354,7 @@ export class ExplorerItem { find(resource: URI): ExplorerItem | null { // Return if path found // For performance reasons try to do the comparison as fast as possible - const ignoreCase = this.explorerService.shouldIgnoreCase(resource); + const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive); if (resource && this.resource.scheme === resource.scheme && equalsIgnoreCase(this.resource.authority, resource.authority) && (ignoreCase ? startsWithIgnoreCase(resource.path, this.resource.path) : startsWith(resource.path, this.resource.path))) { return this.findByPath(rtrim(resource.path, posix.sep), this.resource.path.length, ignoreCase); @@ -397,7 +395,7 @@ export class ExplorerItem { } export class NewExplorerItem extends ExplorerItem { - constructor(explorerService: IExplorerService, parent: ExplorerItem, isDirectory: boolean) { - super(URI.file(''), explorerService, parent, isDirectory); + constructor(fileService: IFileService, parent: ExplorerItem, isDirectory: boolean) { + super(URI.file(''), fileService, parent, isDirectory); } } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 60d9ed77e97..077aa3ddc26 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -9,8 +9,8 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { IExplorerService, IFilesConfiguration, SortOrder, IContextProvider } from 'vs/workbench/contrib/files/common/files'; import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; -import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; -import { dirname, hasToIgnoreCase } from 'vs/base/common/resources'; +import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files'; +import { dirname } from 'vs/base/common/resources'; import { memoize } from 'vs/base/common/decorators'; import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -42,7 +42,6 @@ export class ExplorerService implements IExplorerService { private _sortOrder: SortOrder; private cutItems: ExplorerItem[] | undefined; private contextProvider: IContextProvider | undefined; - private fileSystemProviderCaseSensitivity = new Map(); private model: ExplorerModel; constructor( @@ -55,25 +54,21 @@ export class ExplorerService implements IExplorerService { ) { this._sortOrder = this.configurationService.getValue('explorer.sortOrder'); - this.model = new ExplorerModel(this.contextService, this); + this.model = new ExplorerModel(this.contextService, this.fileService); this.disposables.add(this.model); this.disposables.add(this.fileService.onAfterOperation(e => this.onFileOperation(e))); this.disposables.add(this.fileService.onFileChanges(e => this.onFileChanges(e))); this.disposables.add(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue()))); - this.disposables.add(this.fileService.onDidChangeFileSystemProviderRegistrations(e => { - const provider = e.provider; - if (e.added && provider) { - const alreadyRegistered = this.fileSystemProviderCaseSensitivity.has(e.scheme); - const readCapability = () => this.fileSystemProviderCaseSensitivity.set(e.scheme, !!(provider.capabilities & FileSystemProviderCapabilities.PathCaseSensitive)); - readCapability(); - - if (alreadyRegistered) { - // A file system provider got re-registered, we should update all file stats since they might change (got read-only) - this.model.roots.forEach(r => r.forgetChildren()); - this._onDidChangeItem.fire({ recursive: true }); - } else { - this.disposables.add(provider.onDidChangeCapabilities(() => readCapability())); + this.disposables.add(Event.any<{ scheme: string }>(this.fileService.onDidChangeFileSystemProviderRegistrations, this.fileService.onDidChangeFileSystemProviderCapabilities)(e => { + let affected = false; + this.model.roots.forEach(r => { + if (r.resource.scheme === e.scheme) { + affected = true; + r.forgetChildren(); } + }); + if (affected) { + this._onDidChangeItem.fire({ recursive: true }); } })); this.disposables.add(this.model.onDidChangeRoots(() => this._onDidChangeRoots.fire())); @@ -131,15 +126,6 @@ export class ExplorerService implements IExplorerService { return fileEventsFilter; } - shouldIgnoreCase(resource: URI): boolean { - const caseSensitive = this.fileSystemProviderCaseSensitivity.get(resource.scheme); - if (typeof caseSensitive === 'undefined') { - return hasToIgnoreCase(resource); - } - - return !caseSensitive; - } - // IExplorerService methods findClosest(resource: URI): ExplorerItem | null { @@ -200,7 +186,7 @@ export class ExplorerService implements IExplorerService { const stat = await this.fileService.resolve(rootUri, options); // Convert to model - const modelStat = ExplorerItem.create(this, this.fileService, stat, undefined, options.resolveTo); + const modelStat = ExplorerItem.create(this.fileService, stat, undefined, options.resolveTo); // Update Input with disk Stat ExplorerItem.mergeLocalWithDisk(modelStat, root); const item = root.find(resource); @@ -244,11 +230,11 @@ export class ExplorerService implements IExplorerService { const thenable: Promise = p.isDirectoryResolved ? Promise.resolve(undefined) : this.fileService.resolve(p.resource, { resolveMetadata }); thenable.then(stat => { if (stat) { - const modelStat = ExplorerItem.create(this, this.fileService, stat, p.parent); + const modelStat = ExplorerItem.create(this.fileService, stat, p.parent); ExplorerItem.mergeLocalWithDisk(modelStat, p); } - const childElement = ExplorerItem.create(this, this.fileService, addedElement, p.parent); + const childElement = ExplorerItem.create(this.fileService, addedElement, p.parent); // Make sure to remove any previous version of the file if any p.removeChild(childElement); p.addChild(childElement); diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index b9900702c0d..283cbbfe0d4 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -55,7 +55,6 @@ export interface IExplorerService { refresh(): void; setToCopy(stats: ExplorerItem[], cut: boolean): void; isCut(stat: ExplorerItem): boolean; - shouldIgnoreCase(resource: URI): boolean; /** * Selects and reveal the file element provided by the given resource if its found in the explorer. diff --git a/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts index af77f1c87cd..6966534a25b 100644 --- a/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts +++ b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts @@ -10,18 +10,11 @@ import { join } from 'vs/base/common/path'; import { validateFileName } from 'vs/workbench/contrib/files/browser/fileActions'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { toResource } from 'vs/base/test/common/utils'; -import { hasToIgnoreCase } from 'vs/base/common/resources'; -import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; - -class MockExplorerService { - shouldIgnoreCase(resource: URI) { - return hasToIgnoreCase(resource); - } -} -const mockExplorerService = new MockExplorerService() as IExplorerService; +import { TestFileService } from 'vs/workbench/test/workbenchTestServices'; +const fileService = new TestFileService(); function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { - return new ExplorerItem(toResource.call(this, path), mockExplorerService, undefined, isFolder, false, false, name, mtime); + return new ExplorerItem(toResource.call(this, path), fileService, undefined, isFolder, false, name, mtime); } suite('Files - View Model', function () { @@ -252,19 +245,19 @@ suite('Files - View Model', function () { }); test('Merge Local with Disk', function () { - const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), mockExplorerService, undefined, true, false, false, 'to', Date.now()); - const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), mockExplorerService, undefined, true, false, false, 'to', Date.now()); + const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, 'to', Date.now()); + const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, 'to', Date.now()); // Merge Properties ExplorerItem.mergeLocalWithDisk(merge2, merge1); assert.strictEqual(merge1.mtime, merge2.mtime); // Merge Child when isDirectoryResolved=false is a no-op - merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), mockExplorerService, undefined, true, false, false, 'foo.html', Date.now())); + merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, undefined, true, false, 'foo.html', Date.now())); ExplorerItem.mergeLocalWithDisk(merge2, merge1); // Merge Child with isDirectoryResolved=true - const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), mockExplorerService, undefined, true, false, false, 'foo.html', Date.now()); + const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, undefined, true, false, 'foo.html', Date.now()); merge2.removeChild(child); merge2.addChild(child); (merge2)._isDirectoryResolved = true; diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 10055eaaf1a..81358078d5f 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -1123,7 +1123,13 @@ export class TestFileService implements IFileService { return resource.scheme === 'file' || this.providers.has(resource.scheme); } - hasCapability(resource: URI, capability: FileSystemProviderCapabilities): boolean { return false; } + hasCapability(resource: URI, capability: FileSystemProviderCapabilities): boolean { + if (capability === FileSystemProviderCapabilities.PathCaseSensitive && isLinux) { + return true; + } + + return false; + } del(_resource: URI, _options?: { useTrash?: boolean, recursive?: boolean }): Promise { return Promise.resolve(); From 833250951b5807dc50592e3cea9058c9517e93fa Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 12:50:39 +0100 Subject: [PATCH 105/241] #87246 use container descriptor to register view container --- .../api/browser/viewsExtensionPoint.ts | 2 +- src/vs/workbench/common/views.ts | 37 +++++++++++++---- .../workbench/contrib/debug/common/debug.ts | 2 +- .../contrib/extensions/common/extensions.ts | 2 +- .../workbench/contrib/files/common/files.ts | 2 +- .../markers/browser/markers.contribution.ts | 2 +- .../remote/common/remote.contribution.ts | 41 +++++++++---------- src/vs/workbench/contrib/scm/common/scm.ts | 2 +- .../services/search/common/search.ts | 2 +- .../test/browser/parts/views/views.test.ts | 2 +- 10 files changed, 56 insertions(+), 38 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index ff322f7237f..4357c15c830 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -313,7 +313,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { if (!viewContainer) { - viewContainer = this.viewContainersRegistry.registerViewContainer(id, ViewContainerLocation.Sidebar, true, extensionId); + viewContainer = this.viewContainersRegistry.registerViewContainer({ id, hideIfEmpty: true, extensionId }, ViewContainerLocation.Sidebar); class CustomViewPaneContainer extends ViewPaneContainer { constructor( diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 3bc269ceaa9..2c729271e21 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -31,6 +31,18 @@ export enum ViewContainerLocation { Panel } +export interface IViewContainerDescriptor { + + readonly id: string; + + readonly viewOrderDelegate?: ViewOrderDelegate; + + readonly hideIfEmpty?: boolean; + + readonly extensionId?: ExtensionIdentifier; + +} + export interface IViewContainersRegistry { /** * An event that is triggerred when a view container is registered. @@ -48,14 +60,15 @@ export interface IViewContainersRegistry { readonly all: ViewContainer[]; /** - * Registers a view container with given id - * No op if a view container is already registered with the given id. + * Registers a view container to given location. + * No op if a view container is already registered. * - * @param id of the view container. + * @param viewContainerDescriptor descriptor of view container + * @param location location of the view container * * @returns the registered ViewContainer. */ - registerViewContainer(id: string, location: ViewContainerLocation, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer; + registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, location: ViewContainerLocation): ViewContainer; /** * Deregisters the given view container @@ -76,7 +89,13 @@ interface ViewOrderDelegate { } export class ViewContainer { - protected constructor(readonly id: string, readonly location: ViewContainerLocation, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier, readonly orderDelegate?: ViewOrderDelegate) { } + + protected constructor(private readonly descriptor: IViewContainerDescriptor) { } + + readonly id: string = this.descriptor.id; + readonly hideIfEmpty: boolean = !!this.descriptor.hideIfEmpty; + readonly extensionId: ExtensionIdentifier | undefined = this.descriptor.extensionId; + readonly orderDelegate: ViewOrderDelegate | undefined = this.descriptor.viewOrderDelegate; } class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry { @@ -93,18 +112,18 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe return values(this.viewContainers); } - registerViewContainer(id: string, location: ViewContainerLocation, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer { - const existing = this.viewContainers.get(id); + registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, location: ViewContainerLocation): ViewContainer { + const existing = this.viewContainers.get(viewContainerDescriptor.id); if (existing) { return existing; } const viewContainer = new class extends ViewContainer { constructor() { - super(id, location, !!hideIfEmpty, extensionId, viewOrderDelegate); + super(viewContainerDescriptor); } }; - this.viewContainers.set(id, viewContainer); + this.viewContainers.set(viewContainerDescriptor.id, viewContainer); this._onDidRegister.fire(viewContainer); return viewContainer; } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index c3901bcaa0c..777f24123e6 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -28,7 +28,7 @@ import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewCon import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.debug'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID }, ViewContainerLocation.Sidebar); export const VARIABLES_VIEW_ID = 'workbench.debug.variablesView'; export const WATCH_VIEW_ID = 'workbench.debug.watchExpressionsView'; diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index ada5d746c56..2425b0aeb4e 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -18,7 +18,7 @@ import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRe import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.extensions'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID }, ViewContainerLocation.Sidebar); export const EXTENSIONS_CONFIG = '.vscode/extensions.json'; diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 283cbbfe0d4..2bc88cf3cca 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -33,7 +33,7 @@ export const VIEWLET_ID = 'workbench.view.explorer'; /** * Explorer viewlet container. */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID }, ViewContainerLocation.Sidebar); export interface IExplorerService { _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 627505bc2eb..0723ead7463 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -102,7 +102,7 @@ Registry.as(Extensions.Configuration).registerConfigurat // markers view container -const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(Constants.MARKERS_PANEL_ID, ViewContainerLocation.Panel); +const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Constants.MARKERS_PANEL_ID }, ViewContainerLocation.Panel); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: Constants.MARKERS_VIEW_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index e464b2f79a9..cfdff3ca468 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -20,31 +20,30 @@ import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExte export const VIEWLET_ID = 'workbench.view.remote'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( - VIEWLET_ID, - ViewContainerLocation.Sidebar, - true, - undefined, { - getOrder: (group?: string) => { - if (!group) { + id: VIEWLET_ID, + hideIfEmpty: true, + viewOrderDelegate: { + getOrder: (group?: string) => { + if (!group) { + return; + } + + let matches = /^targets@(\d+)$/.exec(group); + if (matches) { + return -1000; + } + + matches = /^details(@(\d+))?$/.exec(group); + + if (matches) { + return -500; + } + return; } - - let matches = /^targets@(\d+)$/.exec(group); - if (matches) { - return -1000; - } - - matches = /^details(@(\d+))?$/.exec(group); - - if (matches) { - return -500; - } - - return; } - } -); + }, ViewContainerLocation.Sidebar); export class LabelContribution implements IWorkbenchContribution { constructor( diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 9f73e62e8d7..17918a2a7de 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -13,7 +13,7 @@ import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRe import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.scm'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID }, ViewContainerLocation.Sidebar); export interface IBaselineResourceProvider { getBaselineResource(resource: URI): Promise; diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index a19c66244f9..140d4be9183 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -25,7 +25,7 @@ export const VIEW_ID = 'workbench.view.search'; /** * Search viewlet container. */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, ViewContainerLocation.Sidebar, true); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, hideIfEmpty: true }, ViewContainerLocation.Sidebar); export const ISearchService = createDecorator('searchService'); diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index d82935021ff..9a32a551881 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -15,7 +15,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import sinon = require('sinon'); -const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer('test', ViewContainerLocation.Sidebar); +const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'test' }, ViewContainerLocation.Sidebar); const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); class ViewDescriptorSequence { From 65bdd32da188856d7298fd106c3e55175509302f Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Dec 2019 13:04:14 +0100 Subject: [PATCH 106/241] Revert "respect onDidOpenResource passed element" This reverts commit 8b91600b08f5d88ba9d79785a7014c67f492629a. --- .../workbench/contrib/files/browser/views/explorerView.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 7f05dc279a9..67bad15f747 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -412,18 +412,18 @@ export class ExplorerView extends ViewPane { this._register(explorerNavigator); // Open when selecting via keyboard this._register(explorerNavigator.onDidOpenResource(async e => { - const element = e.element; + const selection = this.tree.getSelection(); // Do not react if the user is expanding selection via keyboard. // Check if the item was previously also selected, if yes the user is simply expanding / collapsing current selection #66589. const shiftDown = e.browserEvent instanceof KeyboardEvent && e.browserEvent.shiftKey; - if (element && !shiftDown) { - if (element.isDirectory || this.explorerService.isEditable(undefined)) { + if (selection.length === 1 && !shiftDown) { + if (selection[0].isDirectory || this.explorerService.isEditable(undefined)) { // Do not react if user is clicking on explorer items while some are being edited #70276 // Do not react if clicking on directories return; } this.telemetryService.publicLog2('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'explorer' }); - await this.editorService.openEditor({ resource: element.resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + await this.editorService.openEditor({ resource: selection[0].resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } })); From 369fa90ac45cc69460d9d439c967d93c1145a513 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 18 Dec 2019 15:09:47 +0100 Subject: [PATCH 107/241] Setting for restoring forwarded ports Part of #81388 --- .../electron-browser/remote.contribution.ts | 5 +++ .../remote/common/remoteExplorerService.ts | 34 +++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 52105cb7f24..8de660c78fe 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -401,6 +401,11 @@ Registry.as(ConfigurationExtensions.Configuration) type: 'boolean', markdownDescription: nls.localize('remote.downloadExtensionsLocally', "When enabled extensions are downloaded locally and installed on remote."), default: false + }, + 'remote.restoreForwardedPorts': { + type: 'boolean', + markdownDescription: nls.localize('remote.restoreForwardedPorts', "Restores the ports you forwarded in a workspace."), + default: false } } }); diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index f83efc87dc3..6dc3baf25dc 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -13,9 +13,11 @@ import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/e import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEditableData } from 'vs/workbench/common/views'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const IRemoteExplorerService = createDecorator('remoteExplorerService'); export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; +const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore'; export interface Tunnel { remoteHost: string; @@ -46,7 +48,9 @@ export class TunnelModel extends Disposable { private _candidateFinder: (() => Promise<{ host: string, port: number, detail: string }[]>) | undefined; constructor( - @ITunnelService private readonly tunnelService: ITunnelService + @ITunnelService private readonly tunnelService: ITunnelService, + @IStorageService private readonly storageService: IStorageService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); this.forwarded = new Map(); @@ -74,6 +78,7 @@ export class TunnelModel extends Disposable { localPort: tunnel.tunnelLocalPort, closeable: true }); + this.storeForwarded(); } this._onForwardPort.fire(this.forwarded.get(key)!); })); @@ -81,9 +86,29 @@ export class TunnelModel extends Disposable { const key = MakeAddress(address.host, address.port); if (this.forwarded.has(key)) { this.forwarded.delete(key); + this.storeForwarded(); this._onClosePort.fire(address); } })); + + this.restoreForwarded(); + } + + private async restoreForwarded() { + if (this.configurationService.getValue('remote.restoreForwardedPorts')) { + const tunnelsString = this.storageService.get(TUNNELS_TO_RESTORE, StorageScope.WORKSPACE); + if (tunnelsString) { + (JSON.parse(tunnelsString))?.forEach(tunnel => { + this.forward({ host: tunnel.remoteHost, port: tunnel.remotePort }, tunnel.localPort, tunnel.name); + }); + } + } + } + + private storeForwarded() { + if (this.configurationService.getValue('remote.restoreForwardedPorts')) { + this.storageService.store(TUNNELS_TO_RESTORE, JSON.stringify(Array.from(this.forwarded.values())), StorageScope.WORKSPACE); + } } async forward(remote: { host: string, port: number }, local?: number, name?: string): Promise { @@ -110,6 +135,7 @@ export class TunnelModel extends Disposable { const key = MakeAddress(host, port); if (this.forwarded.has(key)) { this.forwarded.get(key)!.name = name; + this.storeForwarded(); this._onPortName.fire({ host, port }); } else if (this.detected.has(key)) { this.detected.get(key)!.name = name; @@ -212,8 +238,10 @@ class RemoteExplorerService implements IRemoteExplorerService { constructor( @IStorageService private readonly storageService: IStorageService, - @ITunnelService tunnelService: ITunnelService) { - this._tunnelModel = new TunnelModel(tunnelService); + @ITunnelService tunnelService: ITunnelService, + @IConfigurationService configurationService: IConfigurationService + ) { + this._tunnelModel = new TunnelModel(tunnelService, storageService, configurationService); remoteHelpExtPoint.setHandler((extensions) => { let helpInformation: HelpInformation[] = []; for (let extension of extensions) { From 25810007508ba9bac40c1d9640c0acd99cd9c615 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 15:12:03 +0100 Subject: [PATCH 108/241] add types for windows-registry (fix #83421) --- package.json | 1 + src/typings/vscode-windows-registry.d.ts | 9 --------- yarn.lock | 15 ++++++++++----- 3 files changed, 11 insertions(+), 14 deletions(-) delete mode 100644 src/typings/vscode-windows-registry.d.ts diff --git a/package.json b/package.json index 8b660bca986..6d7aefa2974 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "@types/mocha": "2.2.39", "@types/node": "^12.11.7", "@types/sinon": "^1.16.36", + "@types/vscode-windows-registry": "^1.0.0", "@types/webpack": "^4.4.10", "@types/windows-foreground-love": "^0.3.0", "@types/windows-mutex": "^0.4.0", diff --git a/src/typings/vscode-windows-registry.d.ts b/src/typings/vscode-windows-registry.d.ts deleted file mode 100644 index 9be14daa4b2..00000000000 --- a/src/typings/vscode-windows-registry.d.ts +++ /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. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode-windows-registry' { - export type HKEY = "HKEY_CURRENT_USER" | "HKEY_LOCAL_MACHINE" | "HKEY_CLASSES_ROOT" | "HKEY_USERS" | "HKEY_CURRENT_CONFIG"; - export function GetStringRegKey(hive: HKEY, path: string, name: string): string | undefined; -} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 687f0af9b44..22634955751 100644 --- a/yarn.lock +++ b/yarn.lock @@ -167,16 +167,16 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== -"@types/node@^12.11.7": - version "12.12.14" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.14.tgz#1c1d6e3c75dba466e0326948d56e8bd72a1903d2" - integrity sha512-u/SJDyXwuihpwjXy7hOOghagLEV1KdAST6syfnOk6QZAMzZuWZqXy5aYYZbh8Jdpd4escVFP0MvftHNDb9pruA== - "@types/node@^10.12.18": version "10.17.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.9.tgz#4f251a1ed77ac7ef09d456247d67fc8173f6b9da" integrity sha512-+6VygF9LbG7Gaqeog2G7u1+RUcmo0q1rI+2ZxdIg2fAUngk5Vz9fOCHXdloNUOHEPd1EuuOpL5O0CdgN9Fx5UQ== +"@types/node@^12.11.7": + version "12.12.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.14.tgz#1c1d6e3c75dba466e0326948d56e8bd72a1903d2" + integrity sha512-u/SJDyXwuihpwjXy7hOOghagLEV1KdAST6syfnOk6QZAMzZuWZqXy5aYYZbh8Jdpd4escVFP0MvftHNDb9pruA== + "@types/semver@^5.4.0", "@types/semver@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" @@ -199,6 +199,11 @@ dependencies: source-map "^0.6.1" +"@types/vscode-windows-registry@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/vscode-windows-registry/-/vscode-windows-registry-1.0.0.tgz#333eea7fd5743fa4c99dff13af16de2c08a586d0" + integrity sha512-gyq9tIMbxry5GL2gY7J30E6R3EUx0cAin/k3wfsQez4C5uDWVJmJw142x6KFXtYX7xYQL/IXmm4cRqi4ghg05A== + "@types/webpack@^4.4.10": version "4.4.10" resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.4.10.tgz#2ecf12589142bc531549140612815b7d8b076358" From 895f7b2651ccc49658f429905bf9f5df0282beed Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 15:15:08 +0100 Subject: [PATCH 109/241] Set window.restoreWindows default to restore all windows (fix #87258) --- src/vs/workbench/electron-browser/desktop.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 821219da530..6eaa0687e48 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -239,7 +239,7 @@ import product from 'vs/platform/product/common/product'; nls.localize('window.reopenFolders.one', "Reopen the last active window."), nls.localize('window.reopenFolders.none', "Never reopen a window. Always start with an empty one.") ], - 'default': 'one', + 'default': 'all', 'scope': ConfigurationScope.APPLICATION, 'description': nls.localize('restoreWindows', "Controls how windows are being reopened after a restart.") }, From 88f1e2d26d575ac93aa2a11355dde835216a7e02 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 15:30:39 +0100 Subject: [PATCH 110/241] change view container registry to use location --- src/vs/workbench/browser/parts/views/views.ts | 4 +- src/vs/workbench/common/views.ts | 54 ++++++++++++------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 2b35bf2b70c..2c5cf72cdfb 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -655,8 +655,8 @@ export class ViewsService extends Disposable implements IViewsService { this.viewDisposable.forEach(disposable => disposable.dispose()); this.viewDisposable.clear(); })); - this._register(viewContainersRegistry.onDidRegister(viewContainer => this.onDidRegisterViewContainer(viewContainer))); - this._register(viewContainersRegistry.onDidDeregister(viewContainer => this.onDidDeregisterViewContainer(viewContainer))); + this._register(viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer))); + this._register(viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer))); this._register(toDisposable(() => { this.viewDescriptorCollections.forEach(({ disposable }) => disposable.dispose()); this.viewDescriptorCollections.clear(); diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 2c729271e21..13473dadd3b 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -12,11 +12,12 @@ import { IViewlet } from 'vs/workbench/common/viewlet'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { values, keys } from 'vs/base/common/map'; +import { values, keys, getOrSet } from 'vs/base/common/map'; import { Registry } from 'vs/platform/registry/common/platform'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IAction } from 'vs/base/common/actions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { flatten } from 'vs/base/common/arrays'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; export const FocusedViewContext = new RawContextKey('focusedView', ''); @@ -47,12 +48,12 @@ export interface IViewContainersRegistry { /** * An event that is triggerred when a view container is registered. */ - readonly onDidRegister: Event; + readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>; /** * An event that is triggerred when a view container is deregistered. */ - readonly onDidDeregister: Event; + readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>; /** * All registered view containers @@ -82,6 +83,11 @@ export interface IViewContainersRegistry { * @returns the view container with given id. */ get(id: string): ViewContainer | undefined; + + /** + * Returns all view containers in the given location + */ + getViewContainers(location: ViewContainerLocation): ViewContainer[]; } interface ViewOrderDelegate { @@ -100,20 +106,20 @@ export class ViewContainer { class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry { - private readonly _onDidRegister = this._register(new Emitter()); - readonly onDidRegister: Event = this._onDidRegister.event; + private readonly _onDidRegister = this._register(new Emitter<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>()); + readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }> = this._onDidRegister.event; - private readonly _onDidDeregister = this._register(new Emitter()); - readonly onDidDeregister: Event = this._onDidDeregister.event; + private readonly _onDidDeregister = this._register(new Emitter<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>()); + readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }> = this._onDidDeregister.event; - private viewContainers: Map = new Map(); + private viewContainers: Map = new Map(); get all(): ViewContainer[] { - return values(this.viewContainers); + return flatten(values(this.viewContainers)); } - registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, location: ViewContainerLocation): ViewContainer { - const existing = this.viewContainers.get(viewContainerDescriptor.id); + registerViewContainer(viewContainerDescriptor: IViewContainerDescriptor, viewContainerLocation: ViewContainerLocation): ViewContainer { + const existing = this.get(viewContainerDescriptor.id); if (existing) { return existing; } @@ -123,21 +129,33 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe super(viewContainerDescriptor); } }; - this.viewContainers.set(viewContainerDescriptor.id, viewContainer); - this._onDidRegister.fire(viewContainer); + const viewContainers = getOrSet(this.viewContainers, viewContainerLocation, []); + viewContainers.push(viewContainer); + this._onDidRegister.fire({ viewContainer, viewContainerLocation }); return viewContainer; } deregisterViewContainer(viewContainer: ViewContainer): void { - const existing = this.viewContainers.get(viewContainer.id); - if (existing) { - this.viewContainers.delete(viewContainer.id); - this._onDidDeregister.fire(viewContainer); + for (const viewContainerLocation of keys(this.viewContainers)) { + const viewContainers = this.viewContainers.get(viewContainerLocation)!; + const index = viewContainers?.indexOf(viewContainer); + if (index !== -1) { + viewContainers?.splice(index, 1); + if (viewContainers.length === 0) { + this.viewContainers.delete(viewContainerLocation); + } + this._onDidDeregister.fire({ viewContainer, viewContainerLocation }); + return; + } } } get(id: string): ViewContainer | undefined { - return this.viewContainers.get(id); + return this.all.filter(viewContainer => viewContainer.id === id)[0]; + } + + getViewContainers(location: ViewContainerLocation): ViewContainer[] { + return [...(this.viewContainers.get(location) || [])]; } } From d42882c2260f511339b4e8aede225d2d3097f1f3 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Dec 2019 15:59:05 +0100 Subject: [PATCH 111/241] fixes #87235 --- src/vs/workbench/contrib/debug/common/debugModel.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index a7530a41686..0eef4b21d26 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -118,9 +118,12 @@ export class ExpressionContainer implements IExpressionContainer { try { const response = await this.session!.variables(this.reference || 0, this.threadId, filter, start, count); return response && response.body && response.body.variables - ? distinct(response.body.variables.filter(v => !!v && isString(v.name)), (v: DebugProtocol.Variable) => v.name).map((v: DebugProtocol.Variable) => - new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type)) - : []; + ? distinct(response.body.variables.filter(v => !!v), v => v.name).map(v => { + if (isString(v.value) && isString(v.name) && typeof v.variablesReference === 'number') { + return new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type); + } + return new Variable(this.session, this.threadId, this, 0, '', undefined, nls.localize('invalidVariableAttributes', "Invalid variable attributes"), 0, 0, { kind: 'virtual' }, undefined, false); + }) : []; } catch (e) { return [new Variable(this.session, this.threadId, this, 0, '', undefined, e.message, 0, 0, { kind: 'virtual' }, undefined, false)]; } From d548002ed16d842b74f3c2dfb571335da937b231 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Dec 2019 16:18:58 +0100 Subject: [PATCH 112/241] Debug Console: Backspace does not remove extra lines at the end fixes #87126 --- .../workbench/contrib/debug/browser/repl.ts | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index a23b9f0f60c..46d6f21846c 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -89,8 +89,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati _serviceBrand: undefined; private static readonly REFRESH_DELAY = 100; // delay in ms to refresh the repl for new elements to show - private static readonly REPL_INPUT_INITIAL_HEIGHT = 19; - private static readonly REPL_INPUT_MAX_HEIGHT = 170; + private static readonly REPL_INPUT_LINE_HEIGHT = 19; private history: HistoryNavigator; private tree!: WorkbenchAsyncDataTree; @@ -99,7 +98,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private replInput!: CodeEditorWidget; private replInputContainer!: HTMLElement; private dimension!: dom.Dimension; - private replInputHeight: number; + private replInputLineCount = 1; private model!: ITextModel; private historyNavigationEnablement!: IContextKey; private scopedInstantiationService!: IInstantiationService; @@ -123,7 +122,6 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati ) { super(REPL_ID, telemetryService, themeService, storageService); - this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT; this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50); codeEditorService.registerDecorationType(DECORATION_KEY, {}); this.registerListeners(); @@ -303,8 +301,8 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati revealLastElement(this.tree); this.history.add(this.replInput.getValue()); this.replInput.setValue(''); - const shouldRelayout = this.replInputHeight > Repl.REPL_INPUT_INITIAL_HEIGHT; - this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT; + const shouldRelayout = this.replInputLineCount > 1; + this.replInputLineCount = 1; if (shouldRelayout) { // Trigger a layout to shrink a potential multi line input this.layout(this.dimension); @@ -330,18 +328,19 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati layout(dimension: dom.Dimension): void { this.dimension = dimension; + const replInputHeight = Repl.REPL_INPUT_LINE_HEIGHT * this.replInputLineCount; if (this.tree) { const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight; - const treeHeight = dimension.height - this.replInputHeight; + const treeHeight = dimension.height - replInputHeight; this.tree.getHTMLElement().style.height = `${treeHeight}px`; this.tree.layout(treeHeight, dimension.width); if (lastElementVisible) { revealLastElement(this.tree); } } - this.replInputContainer.style.height = `${this.replInputHeight}px`; + this.replInputContainer.style.height = `${replInputHeight}px`; - this.replInput.layout({ width: dimension.width - 20, height: this.replInputHeight }); + this.replInput.layout({ width: dimension.width - 20, height: replInputHeight }); } focus(): void { @@ -466,16 +465,14 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.replInput = this.scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions()); - this._register(this.replInput.onDidScrollChange(e => { - if (!e.scrollHeightChanged) { - return; - } - this.replInputHeight = Math.max(Repl.REPL_INPUT_INITIAL_HEIGHT, Math.min(Repl.REPL_INPUT_MAX_HEIGHT, e.scrollHeight, this.dimension.height)); - this.layout(this.dimension); - })); this._register(this.replInput.onDidChangeModelContent(() => { const model = this.replInput.getModel(); this.historyNavigationEnablement.set(!!model && model.getValue() === ''); + const lineCount = model ? Math.min(10, model.getLineCount()) : 1; + if (lineCount !== this.replInputLineCount) { + this.replInputLineCount = lineCount; + this.layout(this.dimension); + } })); // We add the input decoration only when the focus is in the input #61126 this._register(this.replInput.onDidFocusEditorText(() => this.updateInputDecoration())); From db1c20eae77ea1025b4ea7b4e6277d56ce59c5af Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 18 Dec 2019 16:23:52 +0100 Subject: [PATCH 113/241] Tunnel API renames --- .../remote/common/remoteAuthorityResolver.ts | 2 +- src/vs/platform/remote/common/tunnel.ts | 4 +-- src/vs/vscode.proposed.d.ts | 25 +++++++++++-------- .../api/browser/mainThreadTunnelService.ts | 12 ++++----- .../workbench/api/common/extHost.api.impl.ts | 4 +-- .../workbench/api/common/extHost.protocol.ts | 3 ++- .../api/common/extHostExtensionService.ts | 2 +- .../api/common/extHostTunnelService.ts | 19 +++++--------- .../api/node/extHostTunnelService.ts | 25 ++++++++++--------- .../electron-browser/extensionService.ts | 2 +- .../remote/common/remoteExplorerService.ts | 14 +++++------ .../services/remote/node/tunnelService.ts | 2 +- 12 files changed, 56 insertions(+), 58 deletions(-) diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 8d0e2617a34..7cd49599314 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -18,7 +18,7 @@ export interface ResolvedOptions { } export interface TunnelInformation { - detectedTunnels?: { remote: { port: number, host: string }, localAddress: string }[]; + environmentTunnels?: { remoteAddress: { port: number, host: string }, localAddress: string }[]; } export interface ResolverResult { diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index b5fdfa8bff0..154df6e884e 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -19,9 +19,9 @@ export interface RemoteTunnel { } export interface TunnelOptions { - remote: { port: number, host: string }; + remoteAddress: { port: number, host: string }; localPort?: number; - name?: string; + label?: string; } export interface ITunnelProvider { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index ad7d468f6fb..5102240dd8a 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -34,15 +34,18 @@ declare module 'vscode' { } export interface TunnelOptions { - remote: { port: number, host: string }; - localPort?: number; - name?: string; + remoteAddress: { port: number, host: string }; + // The desired local port. If this port can't be used, then another will be chosen. + localAddressPort?: number; + label?: string; } export interface Tunnel { - remote: { port: number, host: string }; + remoteAddress: { port: number, host: string }; + //The complete local address(ex. localhost:1234) localAddress: string; - onDispose: Event; + // Implementers of Tunnel should fire onDidDispose when dispose is called. + onDidDispose: Event; dispose(): void; } @@ -52,10 +55,10 @@ declare module 'vscode' { export interface TunnelInformation { /** * Tunnels that are detected by the extension. The remotePort is used for display purposes. - * The localAddress should be the complete local address(ex. localhost:1234) for connecting to the port. Tunnels provided through + * The localAddress should be the complete local address (ex. localhost:1234) for connecting to the port. Tunnels provided through * detected are read-only from the forwarded ports UI. */ - detectedTunnels?: { remote: { port: number, host: string }, localAddress: string }[]; + environmentTunnels?: { remoteAddress: { port: number, host: string }, localAddress: string }[]; } export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation; @@ -74,16 +77,16 @@ declare module 'vscode' { * When not implemented, the core will use its default forwarding logic. * When implemented, the core will use this to forward ports. */ - forwardPort?(tunnelOptions: TunnelOptions): Thenable | undefined; + tunnelFactory?: (tunnelOptions: TunnelOptions) => Thenable | undefined; } export namespace workspace { /** * Forwards a port. If the current resolver implements RemoteAuthorityResolver:forwardPort then that will be used to make the tunnel. - * By default, makeTunnel only support localhost; however, RemoteAuthorityResolver:forwardPort can be used to support other ips. - * @param forward The `localPort` is a suggestion only. If that port is not available another will be chosen. + * By default, openTunnel only support localhost; however, RemoteAuthorityResolver:tunnelFactory can be used to support other ips. + * @param tunnelOptions The `localPort` is a suggestion only. If that port is not available another will be chosen. */ - export function makeTunnel(forward: TunnelOptions): Thenable; + export function openTunnel(tunnelOptions: TunnelOptions): Thenable; } export interface ResourceLabelFormatter { diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 3ab437aeef4..2a1b63a6f32 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; -import { TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; +import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; -import { ITunnelProvider, ITunnelService } from 'vs/platform/remote/common/tunnel'; +import { ITunnelProvider, ITunnelService, TunnelOptions } from 'vs/platform/remote/common/tunnel'; @extHostNamedCustomer(MainContext.MainThreadTunnelService) export class MainThreadTunnelService implements MainThreadTunnelServiceShape { @@ -22,7 +22,7 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { } async $openTunnel(tunnelOptions: TunnelOptions): Promise { - const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote, tunnelOptions.localPort, tunnelOptions.name); + const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localPort, tunnelOptions.label); if (tunnel) { return TunnelDto.fromServiceTunnel(tunnel); } @@ -44,11 +44,11 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape { if (forward) { return forward.then(tunnel => { return { - tunnelRemotePort: tunnel.remote.port, - tunnelRemoteHost: tunnel.remote.host, + tunnelRemotePort: tunnel.remoteAddress.port, + tunnelRemoteHost: tunnel.remoteAddress.host, localAddress: tunnel.localAddress, dispose: () => { - this._proxy.$closeTunnel({ host: tunnel.remote.host, port: tunnel.remote.port }); + this._proxy.$closeTunnel({ host: tunnel.remoteAddress.host, port: tunnel.remoteAddress.port }); } }; }); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index b0466c7c142..95470c9d94b 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -714,9 +714,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I onWillRenameFiles: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables); }, - makeTunnel: (forward: vscode.TunnelOptions) => { + openTunnel: (forward: vscode.TunnelOptions) => { checkProposedApiEnabled(extension); - return extHostTunnelService.makeTunnel(forward); + return extHostTunnelService.openTunnel(forward); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b4c6ebbb625..2aac272c570 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -47,7 +47,8 @@ import { createExtHostContextProxyIdentifier as createExtId, createMainContextPr import * as search from 'vs/workbench/services/search/common/search'; import { SaveReason } from 'vs/workbench/common/editor'; import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; -import { TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; +import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; +import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index caa8fee5d81..944546d61d9 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -662,7 +662,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio value: { authority, options, - tunnelInformation: { detectedTunnels: result.detectedTunnels } + tunnelInformation: { environmentTunnels: result.environmentTunnels } } }; } catch (err) { diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index a606b3820f0..f3e81eab7bd 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -6,27 +6,20 @@ import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import * as vscode from 'vscode'; -import { RemoteTunnel } from 'vs/platform/remote/common/tunnel'; +import { RemoteTunnel, TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { IDisposable } from 'vs/base/common/lifecycle'; -export interface TunnelOptions { - remote: { port: number, host: string }; - localPort?: number; - name?: string; - closeable?: boolean; -} - export interface TunnelDto { - remote: { port: number, host: string }; + remoteAddress: { port: number, host: string }; localAddress: string; } export namespace TunnelDto { export function fromApiTunnel(tunnel: vscode.Tunnel): TunnelDto { - return { remote: tunnel.remote, localAddress: tunnel.localAddress }; + return { remoteAddress: tunnel.remoteAddress, localAddress: tunnel.localAddress }; } export function fromServiceTunnel(tunnel: RemoteTunnel): TunnelDto { - return { remote: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress }; + return { remoteAddress: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress }; } } @@ -37,7 +30,7 @@ export interface Tunnel extends vscode.Disposable { export interface IExtHostTunnelService extends ExtHostTunnelServiceShape { readonly _serviceBrand: undefined; - makeTunnel(forward: TunnelOptions): Promise; + openTunnel(forward: TunnelOptions): Promise; setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise; } @@ -45,7 +38,7 @@ export const IExtHostTunnelService = createDecorator('IEx export class ExtHostTunnelService implements IExtHostTunnelService { _serviceBrand: undefined; - async makeTunnel(forward: TunnelOptions): Promise { + async openTunnel(forward: TunnelOptions): Promise { return undefined; } async $findCandidatePorts(): Promise<{ host: string, port: number; detail: string; }[]> { diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index 93b333980a5..ee35a7d80b0 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -13,16 +13,17 @@ import { exec } from 'child_process'; import * as resources from 'vs/base/common/resources'; import * as fs from 'fs'; import { isLinux } from 'vs/base/common/platform'; -import { IExtHostTunnelService, TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; +import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { asPromise } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; +import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; class ExtensionTunnel implements vscode.Tunnel { private _onDispose: Emitter = new Emitter(); - onDispose: Event = this._onDispose.event; + onDidDispose: Event = this._onDispose.event; constructor( - public readonly remote: { port: number; host: string; }, + public readonly remoteAddress: { port: number; host: string; }, public readonly localAddress: string, private readonly _dispose: () => void) { } @@ -48,11 +49,11 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe this.registerCandidateFinder(); } } - async makeTunnel(forward: TunnelOptions): Promise { + async openTunnel(forward: TunnelOptions): Promise { const tunnel = await this._proxy.$openTunnel(forward); if (tunnel) { - const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remote, tunnel.localAddress, () => { - return this._proxy.$closeTunnel(tunnel.remote); + const disposableTunnel: vscode.Tunnel = new ExtensionTunnel(tunnel.remoteAddress, tunnel.localAddress, () => { + return this._proxy.$closeTunnel(tunnel.remoteAddress); }); this._register(disposableTunnel); return disposableTunnel; @@ -65,8 +66,8 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe } async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise { - if (provider && provider.forwardPort) { - this._forwardPortProvider = provider.forwardPort; + if (provider && provider.tunnelFactory) { + this._forwardPortProvider = provider.tunnelFactory; await this._proxy.$setTunnelProvider(); } else { this._forwardPortProvider = undefined; @@ -91,11 +92,11 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe const providedPort = this._forwardPortProvider!(tunnelOptions); if (providedPort !== undefined) { return asPromise(() => providedPort).then(tunnel => { - if (!this._extensionTunnels.has(tunnelOptions.remote.host)) { - this._extensionTunnels.set(tunnelOptions.remote.host, new Map()); + if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) { + this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map()); } - this._extensionTunnels.get(tunnelOptions.remote.host)!.set(tunnelOptions.remote.port, tunnel); - this._register(tunnel.onDispose(() => this._proxy.$closeTunnel(tunnel.remote))); + this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, tunnel); + this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress))); return Promise.resolve(TunnelDto.fromApiTunnel(tunnel)); }); } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 0ca576029c4..04b014914bb 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -473,7 +473,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten // set the resolved authority this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority.authority, resolvedAuthority.options); - this._remoteExplorerService.addDetected(resolvedAuthority.tunnelInformation?.detectedTunnels); + this._remoteExplorerService.addEnvironmentTunnels(resolvedAuthority.tunnelInformation?.environmentTunnels); // monitor for breakage const connection = this._remoteAgentService.getConnection(); diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 6dc3baf25dc..7453c0297d4 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -152,11 +152,11 @@ export class TunnelModel extends Disposable { return (this.forwarded.get(key) || this.detected.get(key))?.localAddress; } - addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[]): void { + addEnvironmentTunnels(tunnels: { remoteAddress: { port: number, host: string }, localAddress: string }[]): void { tunnels.forEach(tunnel => { - this.detected.set(MakeAddress(tunnel.remote.host, tunnel.remote.port), { - remoteHost: tunnel.remote.host, - remotePort: tunnel.remote.port, + this.detected.set(MakeAddress(tunnel.remoteAddress.host, tunnel.remoteAddress.port), { + remoteHost: tunnel.remoteAddress.host, + remotePort: tunnel.remoteAddress.port, localAddress: tunnel.localAddress, closeable: false }); @@ -186,7 +186,7 @@ export interface IRemoteExplorerService { getEditableData(remoteHost: string | undefined, remotePort: number | undefined): IEditableData | undefined; forward(remote: { host: string, port: number }, localPort?: number, name?: string): Promise; close(remote: { host: string, port: number }): Promise; - addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): void; + addEnvironmentTunnels(tunnels: { remoteAddress: { port: number, host: string }, localAddress: string }[] | undefined): void; registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void; } @@ -299,9 +299,9 @@ class RemoteExplorerService implements IRemoteExplorerService { return this.tunnelModel.close(remote.host, remote.port); } - addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[] | undefined): void { + addEnvironmentTunnels(tunnels: { remoteAddress: { port: number, host: string }, localAddress: string }[] | undefined): void { if (tunnels) { - this.tunnelModel.addDetected(tunnels); + this.tunnelModel.addEnvironmentTunnels(tunnels); } } diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 79f473813dc..66e50c7e997 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -224,7 +224,7 @@ export class TunnelService implements ITunnelService { } if (this._tunnelProvider) { - const tunnel = this._tunnelProvider.forwardPort({ remote: { host: remoteHost, port: remotePort } }); + const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort } }); if (tunnel) { this.addTunnelToMap(remoteHost, remotePort, tunnel); } From 72dfc13b730a8398b9aef746664d5697a975ad50 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 18 Dec 2019 07:27:58 -0800 Subject: [PATCH 114/241] Revert "fixes #85451" This reverts commit 8b7742fc25469fb69cb5e040d3ac185719c99f3e. --- src/vs/workbench/browser/layout.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 02f948b1d7c..6423aeaaf75 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -178,8 +178,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi position: Position.BOTTOM, lastNonMaximizedWidth: 300, lastNonMaximizedHeight: 300, - panelToRestore: undefined as string | undefined, - restored: false + panelToRestore: undefined as string | undefined }, statusBar: { @@ -571,10 +570,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private updatePanelPosition() { const defaultPanelPosition = this.configurationService.getValue(Settings.PANEL_POSITION); - const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, undefined); + const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, defaultPanelPosition); - this.state.panel.restored = panelPosition !== undefined; - this.state.panel.position = ((panelPosition || defaultPanelPosition) === 'right') ? Position.RIGHT : Position.BOTTOM; + this.state.panel.position = (panelPosition === 'right') ? Position.RIGHT : Position.BOTTOM; } registerPart(part: Part): void { @@ -1281,7 +1279,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height); // At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300))); - const panelSize = this.state.panel.restored ? this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, workbenchDimensions.height / 3)) : workbenchDimensions.height / 3; + const panelSize = this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, workbenchDimensions.height / 3)); const titleBarHeight = this.titleBarPartView.minimumHeight; const statusBarHeight = this.statusBarPartView.minimumHeight; From 66e1177de374338ee0e0cb23138cf01877cc0488 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 16:37:15 +0100 Subject: [PATCH 115/241] fix writing resource scope settings into workspace folder --- .../common/configurationEditingService.ts | 2 +- .../configurationService.test.ts | 52 +++++++++++++++++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 9253110fde1..e313353286b 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -461,7 +461,7 @@ export class ConfigurationEditingService { if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); - if (configurationProperties[operation.key].scope !== ConfigurationScope.RESOURCE) { + if (!(configurationProperties[operation.key].scope === ConfigurationScope.RESOURCE || configurationProperties[operation.key].scope === ConfigurationScope.RESOURCE_LANGUAGE)) { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation); } } diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index f78eacb60af..bd9070dd0b5 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -718,7 +718,7 @@ suite('WorkspaceService - Initialization', () => { suite('WorkspaceConfigurationService - Folder', () => { - let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: IConfigurationService, globalSettingsFile: string, globalTasksFile: string; + let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: IConfigurationService, globalSettingsFile: string, globalTasksFile: string, workspaceService: WorkspaceService; const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); suiteSetup(() => { @@ -745,6 +745,11 @@ suite('WorkspaceConfigurationService - Folder', () => { 'type': 'string', 'default': 'isSet', scope: ConfigurationScope.RESOURCE + }, + 'configurationService.folder.languageSetting': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.RESOURCE_LANGUAGE } } }); @@ -767,7 +772,7 @@ suite('WorkspaceConfigurationService - Folder', () => { const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, diskFileSystemProvider, environmentService)); - const workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); + workspaceService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); instantiationService.stub(IEnvironmentService, environmentService); @@ -794,7 +799,7 @@ suite('WorkspaceConfigurationService - Folder', () => { }); test('defaults', () => { - assert.deepEqual(testObject.getValue('configurationService'), { 'folder': { 'applicationSetting': 'isSet', 'machineSetting': 'isSet', 'machineOverridableSetting': 'isSet', 'testSetting': 'isSet' } }); + assert.deepEqual(testObject.getValue('configurationService'), { 'folder': { 'applicationSetting': 'isSet', 'machineSetting': 'isSet', 'machineOverridableSetting': 'isSet', 'testSetting': 'isSet', 'languageSetting': 'isSet' } }); }); test('globals override defaults', () => { @@ -1028,6 +1033,16 @@ suite('WorkspaceConfigurationService - Folder', () => { .then(() => assert.equal(testObject.getValue('tasks.service.testSetting'), 'value')); }); + test('update resource configuration', () => { + return testObject.updateValue('configurationService.folder.testSetting', 'value', { resource: workspaceService.getWorkspace().folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER) + .then(() => assert.equal(testObject.getValue('configurationService.folder.testSetting'), 'value')); + }); + + test('update resource language configuration', () => { + return testObject.updateValue('configurationService.folder.languageSetting', 'value', { resource: workspaceService.getWorkspace().folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER) + .then(() => assert.equal(testObject.getValue('configurationService.folder.languageSetting'), 'value')); + }); + test('update application setting into workspace configuration in a workspace is not supported', () => { return testObject.updateValue('configurationService.folder.applicationSetting', 'workspaceValue', {}, ConfigurationTarget.WORKSPACE, true) .then(() => assert.fail('Should not be supported'), (e) => assert.equal(e.code, ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION)); @@ -1122,6 +1137,11 @@ suite('WorkspaceConfigurationService-Multiroot', () => { 'type': 'string', 'default': 'isSet', scope: ConfigurationScope.RESOURCE + }, + 'configurationService.workspace.testLanguageSetting': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.RESOURCE_LANGUAGE } } }); @@ -1299,6 +1319,26 @@ suite('WorkspaceConfigurationService-Multiroot', () => { }); }); + test('resource language setting in folder is read after it is registered later', () => { + fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.testNewResourceLanguageSetting2": "workspaceFolderValue" }'); + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'settings', value: { 'configurationService.workspace.testNewResourceLanguageSetting2': 'workspaceValue' } }], true) + .then(() => testObject.reloadConfiguration()) + .then(() => { + configurationRegistry.registerConfiguration({ + 'id': '_test', + 'type': 'object', + 'properties': { + 'configurationService.workspace.testNewResourceLanguageSetting2': { + 'type': 'string', + 'default': 'isSet', + scope: ConfigurationScope.RESOURCE_LANGUAGE + } + } + }); + assert.equal(testObject.getValue('configurationService.workspace.testNewResourceLanguageSetting2', { resource: workspaceContextService.getWorkspace().folders[0].uri }), 'workspaceFolderValue'); + }); + }); + test('machine overridable setting in folder is read after it is registered later', () => { fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.testNewMachineOverridableSetting2": "workspaceFolderValue" }'); return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'settings', value: { 'configurationService.workspace.testNewMachineOverridableSetting2': 'workspaceValue' } }], true) @@ -1504,6 +1544,12 @@ suite('WorkspaceConfigurationService-Multiroot', () => { .then(() => assert.equal(testObject.getValue('configurationService.workspace.testResourceSetting', { resource: workspace.folders[0].uri }), 'workspaceFolderValue')); }); + test('update resource language configuration in workspace folder', () => { + const workspace = workspaceContextService.getWorkspace(); + return testObject.updateValue('configurationService.workspace.testLanguageSetting', 'workspaceFolderValue', { resource: workspace.folders[0].uri }, ConfigurationTarget.WORKSPACE_FOLDER) + .then(() => assert.equal(testObject.getValue('configurationService.workspace.testLanguageSetting', { resource: workspace.folders[0].uri }), 'workspaceFolderValue')); + }); + test('update workspace folder configuration should trigger change event before promise is resolve', () => { const workspace = workspaceContextService.getWorkspace(); const target = sinon.spy(); From 5c0d431b601f9d3aff1cb54f7dbe00f727e18231 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 16:37:41 +0100 Subject: [PATCH 116/241] enable proposed api check for language settings --- src/vs/workbench/api/common/extHost.api.impl.ts | 2 +- src/vs/workbench/api/common/extHostConfiguration.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 95470c9d94b..e3fd440c160 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -666,7 +666,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null): vscode.WorkspaceConfiguration { scope = arguments.length === 1 ? undefined : scope; - return configProvider.getConfiguration(section, scope, extension.identifier); + return configProvider.getConfiguration(section, scope, extension); }, registerTextDocumentContentProvider(scheme: string, provider: vscode.TextDocumentContentProvider) { return extHostDocumentContentProviders.registerTextDocumentContentProvider(scheme, provider); diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index e03262a0b51..40bc2a63312 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -13,13 +13,14 @@ import { ConfigurationTarget, IConfigurationChange, IConfigurationData, IConfigu import { Configuration, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; import { isObject } from 'vs/base/common/types'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Barrier } from 'vs/base/common/async'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ILogService } from 'vs/platform/log/common/log'; import { Workspace } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; function lookUp(tree: any, key: string) { if (key) { @@ -149,14 +150,17 @@ export class ExtHostConfigProvider { this._onDidChangeConfiguration.fire(this._toConfigurationChangeEvent(change, previous)); } - getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { + getConfiguration(section?: string, scope?: vscode.ConfigurationScope | null, extensionDescription?: IExtensionDescription): vscode.WorkspaceConfiguration { const overrides = scopeToOverrides(scope) || {}; + if (overrides.overrideIdentifier && extensionDescription) { + checkProposedApiEnabled(extensionDescription); + } const config = this._toReadonlyValue(section ? lookUp(this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace), section) : this._configuration.getValue(undefined, overrides, this._extHostWorkspace.workspace)); if (section) { - this._validateConfigurationAccess(section, overrides, extensionId); + this._validateConfigurationAccess(section, overrides, extensionDescription?.identifier); } function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget | null { @@ -179,7 +183,7 @@ export class ExtHostConfigProvider { return typeof lookUp(config, key) !== 'undefined'; }, get: (key: string, defaultValue?: T) => { - this._validateConfigurationAccess(section ? `${section}.${key}` : key, overrides, extensionId); + this._validateConfigurationAccess(section ? `${section}.${key}` : key, overrides, extensionDescription?.identifier); let result = lookUp(config, key); if (typeof result === 'undefined') { result = defaultValue; From 24a6aef5eecb8b1c1b122b77a6c9e0d737f35138 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Dec 2019 16:45:38 +0100 Subject: [PATCH 117/241] New debug/run experience always writes launch.json into first workspace folder fixes #87237 --- .../contrib/debug/browser/debugActions.ts | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 6a01b97f2cf..b44e946b8ca 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -7,12 +7,13 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession, ILaunch } from 'vs/workbench/contrib/debug/common/debug'; import { Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export abstract class AbstractDebugAction extends Action { @@ -60,7 +61,8 @@ export class ConfigureAction extends AbstractDebugAction { @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(id, label, 'debug-action codicon codicon-gear', debugService, keybindingService); this._register(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass())); @@ -87,10 +89,26 @@ export class ConfigureAction extends AbstractDebugAction { return; } - const sideBySide = !!(event && (event.ctrlKey || event.metaKey)); const configurationManager = this.debugService.getConfigurationManager(); - if (configurationManager.selectedConfiguration.launch) { - return configurationManager.selectedConfiguration.launch.openConfigFile(sideBySide, false); + let launch: ILaunch | undefined; + if (configurationManager.selectedConfiguration.name) { + launch = configurationManager.selectedConfiguration.launch; + } else { + const launches = configurationManager.getLaunches().filter(l => !!l.workspace); + if (launches.length === 1) { + launch = launches[0]; + } else { + const picks = launches.map(l => ({ label: l.name, launch: l })); + const picked = await this.quickInputService.pick<{ label: string, launch: ILaunch }>(picks, { activeItem: picks[0], placeHolder: nls.localize('selectWorkspaceFolder', "Select a workspace folder to create a launch.json file in") }); + if (picked) { + launch = picked.launch; + } + } + } + + if (launch) { + const sideBySide = !!(event && (event.ctrlKey || event.metaKey)); + return launch.openConfigFile(sideBySide, false); } } } From f1083a76e35c8bcc28a41212afd9d3d6eaf990e2 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 18 Dec 2019 07:47:28 -0800 Subject: [PATCH 118/241] fixes #85451 --- src/vs/workbench/browser/layout.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 6423aeaaf75..f07e5d72cb1 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -58,6 +58,7 @@ enum Storage { PANEL_HIDDEN = 'workbench.panel.hidden', PANEL_POSITION = 'workbench.panel.location', PANEL_SIZE = 'workbench.panel.size', + PANEL_DIMENSION = 'workbench.panel.dimension', PANEL_LAST_NON_MAXIMIZED_WIDTH = 'workbench.panel.lastNonMaximizedWidth', PANEL_LAST_NON_MAXIMIZED_HEIGHT = 'workbench.panel.lastNonMaximizedHeight', @@ -897,6 +898,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi : (this.state.panel.position === Position.BOTTOM ? grid.getViewSize(this.panelPartView).height : grid.getViewSize(this.panelPartView).width); this.storageService.store(Storage.PANEL_SIZE, panelSize, StorageScope.GLOBAL); + this.storageService.store(Storage.PANEL_DIMENSION, this.state.panel.position === Position.RIGHT ? 'right' : 'bottom', StorageScope.GLOBAL); const gridSize = grid.getViewSize(); this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL); @@ -1279,7 +1281,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height); // At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300))); - const panelSize = this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, workbenchDimensions.height / 3)); + const panelDimension = this.storageService.get(Storage.PANEL_DIMENSION, StorageScope.GLOBAL, 'bottom') === 'bottom' ? Position.BOTTOM : Position.RIGHT; + const fallbackPanelSize = this.state.panel.position === Position.BOTTOM ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4; + const panelSize = panelDimension === this.state.panel.position ? this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, fallbackPanelSize)) : fallbackPanelSize; const titleBarHeight = this.titleBarPartView.minimumHeight; const statusBarHeight = this.statusBarPartView.minimumHeight; From 0eebe2856f2b16450e2a84240c8ac489e74e22c9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Dec 2019 16:54:56 +0100 Subject: [PATCH 119/241] editors - allow to listen to editor part options changes --- .../workbench/browser/parts/editor/editor.ts | 7 +---- .../browser/parts/editor/editorGroupView.ts | 4 +-- .../browser/parts/editor/editorPart.ts | 28 ++++++++----------- src/vs/workbench/common/editor.ts | 5 ++++ .../editor/common/editorGroupsService.ts | 7 ++++- .../workbench/test/workbenchTestServices.ts | 1 + 6 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 39bf1a94e44..bf44520a5dd 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditor, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditor, IEditorPartOptions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -63,11 +63,6 @@ export function getEditorPartOptions(config: IWorkbenchEditorConfiguration): IEd return options; } -export interface IEditorPartOptionsChangeEvent { - oldPartOptions: IEditorPartOptions; - newPartOptions: IEditorPartOptions; -} - export interface IEditorOpeningEvent extends IEditorIdentifier { options?: IEditorOptions; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 51be3d78e6d..df3eb524e89 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, toResource, SideBySideEditor, SaveReason, SaveContext } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, toResource, SideBySideEditor, SaveReason, SaveContext, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom'; @@ -31,7 +31,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; -import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index f08c37e4ae1..3c75a60d829 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -12,11 +12,11 @@ import { contrastBorder, editorBackground } from 'vs/platform/theme/common/color import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, ICopyEditorOptions, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IView, orthogonal, LayoutPriority, IViewSize, Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid'; -import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorPartOptions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; import { values } from 'vs/base/common/map'; import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme'; import { distinct, coalesce } from 'vs/base/common/arrays'; -import { IEditorGroupsAccessor, IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartOptionsChangeEvent, IEditorPartCreationOptions } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartCreationOptions } from 'vs/workbench/browser/parts/editor/editor'; import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -110,9 +110,12 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro private readonly _onDidMoveGroup = this._register(new Emitter()); readonly onDidMoveGroup = this._onDidMoveGroup.event; - private onDidSetGridWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>()); - private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); - get onDidSizeConstraintsChange(): Event<{ width: number; height: number; } | undefined> { return Event.any(this.onDidSetGridWidget.event, this._onDidSizeConstraintsChange.event); } + private readonly onDidSetGridWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>()); + private readonly _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); + readonly onDidSizeConstraintsChange = Event.any(this.onDidSetGridWidget.event, this._onDidSizeConstraintsChange.event); + + private readonly _onDidEditorPartOptionsChange = this._register(new Emitter()); + readonly onDidEditorPartOptionsChange = this._onDidEditorPartOptionsChange.event; //#endregion @@ -155,13 +158,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.registerListeners(); } - //#region IEditorGroupsAccessor - - private enforcedPartOptions: IEditorPartOptions[] = []; - - private readonly _onDidEditorPartOptionsChange: Emitter = this._register(new Emitter()); - readonly onDidEditorPartOptionsChange: Event = this._onDidEditorPartOptionsChange.event; - private registerListeners(): void { this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); } @@ -185,6 +181,10 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this._onDidEditorPartOptionsChange.fire({ oldPartOptions, newPartOptions }); } + //#region IEditorGroupsService + + private enforcedPartOptions: IEditorPartOptions[] = []; + get partOptions(): IEditorPartOptions { return this._partOptions; } @@ -199,10 +199,6 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro }); } - //#endregion - - //#region IEditorGroupsService - private _contentDimension!: Dimension; get contentDimension(): Dimension { return this._contentDimension; } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 630cfd443a2..f67d99b5082 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1177,6 +1177,11 @@ export interface IEditorPartOptions extends IEditorPartConfiguration { iconTheme?: string; } +export interface IEditorPartOptionsChangeEvent { + oldPartOptions: IEditorPartOptions; + newPartOptions: IEditorPartOptions; +} + export enum SideBySideEditor { MASTER = 1, DETAILS = 2 diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 805ecedb425..a86ff3eca5a 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; import { IEditorOptions, ITextEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; @@ -344,6 +344,11 @@ export interface IEditorGroupsService { */ readonly partOptions: IEditorPartOptions; + /** + * An event that notifies when editor part options change. + */ + readonly onDidEditorPartOptionsChange: Event; + /** * Enforce editor part options temporarily. */ diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 81358078d5f..f71d7d3d306 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -689,6 +689,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { onDidMoveGroup: Event = Event.None; onDidGroupIndexChange: Event = Event.None; onDidLayout: Event = Event.None; + onDidEditorPartOptionsChange = Event.None; orientation: any; whenRestored: Promise = Promise.resolve(undefined); From 71eb6ad17eaf49a46fd176ca74a083001e17f7de Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Dec 2019 17:54:09 +0100 Subject: [PATCH 120/241] vscode do not be smart and just pass the whole repl input content to debug adapter fixes vscode-js-debug#192 --- src/vs/workbench/contrib/debug/browser/repl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 46d6f21846c..2a1c1dc06b7 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -145,7 +145,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati if (model) { const word = model.getWordAtPosition(position); const overwriteBefore = word ? word.word.length : 0; - const text = model.getLineContent(position.lineNumber); + const text = model.getValue(); const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined; const suggestions = await session.completions(frameId, text, position, overwriteBefore, token); From ad0eae3d68d4897f9f4da7d659f81741830cc909 Mon Sep 17 00:00:00 2001 From: Alexander Fell <39180662+AlexFell-Velo@users.noreply.github.com> Date: Wed, 18 Dec 2019 17:18:25 +0000 Subject: [PATCH 121/241] add position panel left (#84477) - enable layout code to position panel to the left --- src/vs/workbench/browser/contextkeys.ts | 4 +- src/vs/workbench/browser/layout.ts | 46 ++++--- .../browser/parts/panel/media/panelpart.css | 16 +++ .../browser/parts/panel/panelActions.ts | 123 ++++++++++-------- .../browser/parts/panel/panelPart.ts | 11 +- .../browser/workbench.contribution.ts | 2 +- src/vs/workbench/browser/workbench.ts | 4 +- .../services/layout/browser/layoutService.ts | 20 +++ 8 files changed, 141 insertions(+), 85 deletions(-) diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 2726a5c7d68..6f1eebf4841 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -16,7 +16,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; -import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { isMacintosh, isLinux, isWindows, isWeb } from 'vs/base/common/platform'; import { PanelPositionContext } from 'vs/workbench/common/panel'; @@ -151,7 +151,7 @@ export class WorkbenchContextKeysHandler extends Disposable { // Panel Position this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService); - this.panelPositionContext.set(this.layoutService.getPanelPosition() === Position.RIGHT ? 'right' : 'bottom'); + this.panelPositionContext.set(positionToString(this.layoutService.getPanelPosition())); this.registerListeners(); } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index f07e5d72cb1..4cdc4bd8f51 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -14,7 +14,7 @@ import { pathsToEditors } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; -import { Position, Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { Position, Parts, IWorkbenchLayoutService, positionFromString, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -573,7 +573,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const defaultPanelPosition = this.configurationService.getValue(Settings.PANEL_POSITION); const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, defaultPanelPosition); - this.state.panel.position = (panelPosition === 'right') ? Position.RIGHT : Position.BOTTOM; + this.state.panel.position = positionFromString(panelPosition || defaultPanelPosition); } registerPart(part: Part): void { @@ -666,15 +666,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } getMaximumEditorDimensions(): Dimension { + const isColumn = this.state.panel.position === Position.RIGHT || this.state.panel.position === Position.LEFT; const takenWidth = (this.isVisible(Parts.ACTIVITYBAR_PART) ? this.activityBarPartView.minimumWidth : 0) + (this.isVisible(Parts.SIDEBAR_PART) ? this.sideBarPartView.minimumWidth : 0) + - (this.isVisible(Parts.PANEL_PART) && this.state.panel.position === Position.RIGHT ? this.panelPartView.minimumWidth : 0); + (this.isVisible(Parts.PANEL_PART) && isColumn ? this.panelPartView.minimumWidth : 0); const takenHeight = (this.isVisible(Parts.TITLEBAR_PART) ? this.titleBarPartView.minimumHeight : 0) + (this.isVisible(Parts.STATUSBAR_PART) ? this.statusBarPartView.minimumHeight : 0) + - (this.isVisible(Parts.PANEL_PART) && this.state.panel.position === Position.BOTTOM ? this.panelPartView.minimumHeight : 0); + (this.isVisible(Parts.PANEL_PART) && !isColumn ? this.panelPartView.minimumHeight : 0); const availableWidth = this.dimension.width - takenWidth; const availableHeight = this.dimension.height - takenHeight; @@ -1202,26 +1203,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return this.state.panel.position; } - setPanelPosition(position: Position.BOTTOM | Position.RIGHT): void { + setPanelPosition(position: Position): void { if (this.state.panel.hidden) { this.setPanelHidden(false); } const panelPart = this.getPart(Parts.PANEL_PART); - const newPositionValue = (position === Position.BOTTOM) ? 'bottom' : 'right'; - const oldPositionValue = (this.state.panel.position === Position.BOTTOM) ? 'bottom' : 'right'; + const oldPositionValue = positionToString(this.state.panel.position); + const newPositionValue = positionToString(position); this.state.panel.position = position; - function positionToString(position: Position): string { - switch (position) { - case Position.LEFT: return 'left'; - case Position.RIGHT: return 'right'; - case Position.BOTTOM: return 'bottom'; - } - } - // Save panel position - this.storageService.store(Storage.PANEL_POSITION, positionToString(this.state.panel.position), StorageScope.WORKSPACE); + this.storageService.store(Storage.PANEL_POSITION, newPositionValue, StorageScope.WORKSPACE); // Adjust CSS const panelContainer = assertIsDefined(panelPart.getContainer()); @@ -1250,14 +1243,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (position === Position.BOTTOM) { this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.height : this.state.panel.lastNonMaximizedHeight, this.editorPartView, Direction.Down); - } else { + } else if (position === Position.RIGHT) { this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : this.state.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Right); + } else { + this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : this.state.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Left); } // Reset sidebar to original size before shifting the panel this.workbenchGrid.resizeView(this.sideBarPartView, sideBarSize); - this._onPanelPositionChange.fire(positionToString(this.state.panel.position)); + this._onPanelPositionChange.fire(newPositionValue); } isWindowMaximized() { @@ -1275,6 +1270,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._onMaximizeChange.fire(maximized); } + private arrangeEditorNodes(editorNode: ISerializedNode, panelNode: ISerializedNode, editorSectionWidth: number): ISerializedNode[] { + switch (this.state.panel.position) { + case Position.BOTTOM: + return [{ type: 'branch', data: [editorNode, panelNode], size: editorSectionWidth }]; + case Position.RIGHT: + return [editorNode, panelNode]; + case Position.LEFT: + return [panelNode, editorNode]; + } + } + private createGridDescriptor(): ISerializedGrid { const workbenchDimensions = this.getClientArea(); const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width); @@ -1321,9 +1327,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi visible: !this.state.panel.hidden }; - const editorSectionNode: ISerializedNode[] = this.state.panel.position === Position.BOTTOM - ? [{ type: 'branch', data: [editorNode, panelNode], size: editorSectionWidth }] - : [editorNode, panelNode]; + const editorSectionNode = this.arrangeEditorNodes(editorNode, panelNode, editorSectionWidth); const middleSection: ISerializedNode[] = this.state.sideBar.position === Position.LEFT ? [activityBarNode, sideBarNode, ...editorSectionNode] diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 4909d7e9738..abca35a41a7 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -37,6 +37,15 @@ border-left-width: 0; /* no border when editor area is hiden */ } +.monaco-workbench .part.panel.left { + border-right-width: 1px; + border-right-style: solid; +} + +.monaco-workbench.noeditorarea .part.panel.left { + border-right-width: 0; /* no border when editor area is hiden */ +} + .monaco-workbench .part.panel > .title > .title-actions .monaco-action-bar .action-item .action-label { outline-offset: -2px; } @@ -121,3 +130,10 @@ .monaco-workbench .part.panel.right .title-actions .codicon-chevron-down { transform: rotate(-90deg); } + +/* Rotate icons when panel is on left */ +.monaco-workbench .part.panel.left .title-actions .codicon-split-horizontal, +.monaco-workbench .part.panel.left .title-actions .codicon-chevron-up, +.monaco-workbench .part.panel.left .title-actions .codicon-chevron-down { + transform: rotate(90deg); +} diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 7c0ba38cc48..0bb5ac80bf5 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -12,11 +12,12 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Parts, Position, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { IActivity } from 'vs/workbench/common/activity'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; export class ClosePanelAction extends Action { @@ -88,42 +89,6 @@ class FocusPanelAction extends Action { } } -export class TogglePanelPositionAction extends Action { - - static readonly ID = 'workbench.action.togglePanelPosition'; - static readonly LABEL = nls.localize('toggledPanelPosition', "Toggle Panel Position"); - - static readonly MOVE_TO_RIGHT_LABEL = nls.localize('moveToRight', "Move Panel Right"); - static readonly MOVE_TO_BOTTOM_LABEL = nls.localize('moveToBottom', "Move Panel to Bottom"); - - private readonly toDispose = this._register(new DisposableStore()); - - constructor( - id: string, - label: string, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IEditorGroupsService editorGroupsService: IEditorGroupsService - ) { - super(id, label, layoutService.getPanelPosition() === Position.RIGHT ? 'move-panel-to-bottom' : 'move-panel-to-right'); - - const setClassAndLabel = () => { - const positionRight = this.layoutService.getPanelPosition() === Position.RIGHT; - this.class = positionRight ? 'move-panel-to-bottom' : 'move-panel-to-right'; - this.label = positionRight ? TogglePanelPositionAction.MOVE_TO_BOTTOM_LABEL : TogglePanelPositionAction.MOVE_TO_RIGHT_LABEL; - }; - - this.toDispose.add(editorGroupsService.onDidLayout(() => setClassAndLabel())); - - setClassAndLabel(); - } - - run(): Promise { - const position = this.layoutService.getPanelPosition(); - - this.layoutService.setPanelPosition(position === Position.BOTTOM ? Position.RIGHT : Position.BOTTOM); - return Promise.resolve(); - } -} export class ToggleMaximizedPanelAction extends Action { @@ -160,6 +125,54 @@ export class ToggleMaximizedPanelAction extends Action { } } +const PositionPanelActionId = { + LEFT: 'workbench.action.positionPanelLeft', + RIGHT: 'workbench.action.positionPanelRight', + BOTTOM: 'workbench.action.positionPanelBottom', +}; + +interface PanelActionConfig { + id: string; + when: ContextKeyExpr; + alias: string; + label: string; + value: T; +} + +function createPositionPanelActionConfig(id: string, alias: string, label: string, position: Position): PanelActionConfig { + return { + id, + alias, + label, + value: position, + when: PanelPositionContext.notEqualsTo(positionToString(position)) + }; +} + +export const PositionPanelActionConfigs = [ + createPositionPanelActionConfig(PositionPanelActionId.LEFT, 'View: Panel Position Left', nls.localize('positionPanelLeft', 'Move Panel Left'), Position.LEFT), + createPositionPanelActionConfig(PositionPanelActionId.RIGHT, 'View: Panel Position Right', nls.localize('positionPanelRight', 'Move Panel Right'), Position.RIGHT), + createPositionPanelActionConfig(PositionPanelActionId.BOTTOM, 'View: Panel Position Bottom', nls.localize('positionPanelBottom', 'Move Panel To Bottom'), Position.BOTTOM), +]; + +const positionByActionId = new Map(PositionPanelActionConfigs.map(config => [config.id, config.value])); + +export class SetPanelPositionAction extends Action { + constructor( + id: string, + label: string, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + ) { + super(id, label); + } + + run(): Promise { + const position = positionByActionId.get(this.id); + this.layoutService.setPanelPosition(position === undefined ? Position.BOTTOM : position); + return Promise.resolve(); + } +} + export class PanelActivityAction extends ActivityAction { constructor( @@ -247,7 +260,6 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TogglePanelAc actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(FocusPanelAction, FocusPanelAction.ID, FocusPanelAction.LABEL), 'View: Focus into Panel', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL), 'View: Toggle Maximized Panel', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View")); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), 'View: Toggle Panel Position', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, undefined), 'View: Toggle Panel Position', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(PreviousPanelViewAction, PreviousPanelViewAction.ID, PreviousPanelViewAction.LABEL), 'View: Previous Panel View', nls.localize('view', "View")); actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(NextPanelViewAction, NextPanelViewAction.ID, NextPanelViewAction.LABEL), 'View: Next Panel View', nls.localize('view', "View")); @@ -262,22 +274,21 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { order: 5 }); -MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '3_workbench_layout_move', - command: { - id: TogglePanelPositionAction.ID, - title: TogglePanelPositionAction.MOVE_TO_RIGHT_LABEL - }, - when: PanelPositionContext.isEqualTo('bottom'), - order: 5 -}); +function registerPositionPanelActionById(config: PanelActionConfig) { + const { id, label, alias, when } = config; + // register the workbench action + actionRegistry.registerWorkbenchAction(SyncActionDescriptor.create(SetPanelPositionAction, id, label), alias, nls.localize('view', "View"), when); + // register as a menu item + MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '3_workbench_layout_move', + command: { + id, + title: label + }, + when, + order: 5 + }); +} -MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '3_workbench_layout_move', - command: { - id: TogglePanelPositionAction.ID, - title: TogglePanelPositionAction.MOVE_TO_BOTTOM_LABEL - }, - when: PanelPositionContext.isEqualTo('right'), - order: 5 -}); +// register each position panel action +PositionPanelActionConfigs.forEach(registerPositionPanelActionById); diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index be69e4c61e5..bc4a89e974b 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -18,7 +18,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ClosePanelAction, TogglePanelPositionAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction } from 'vs/workbench/browser/parts/panel/panelActions'; +import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -122,7 +122,10 @@ export class PanelPart extends CompositePart implements IPanelService { getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))), getContextMenuActions: () => [ - this.instantiationService.createInstance(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), + ...PositionPanelActionConfigs + // show the contextual menu item if it is not in that position + .filter(({ when }) => contextKeyService.contextMatchesRules(when)) + .map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)), this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel")) ], getDefaultCompositeId: () => Registry.as(PanelExtensions.Panels).getDefaultPanelId(), @@ -207,7 +210,9 @@ export class PanelPart extends CompositePart implements IPanelService { const container = assertIsDefined(this.getContainer()); container.style.backgroundColor = this.getColor(PANEL_BACKGROUND) || ''; - container.style.borderLeftColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder) || ''; + const borderColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder) || ''; + container.style.borderLeftColor = borderColor; + container.style.borderRightColor = borderColor; const title = this.getTitleArea(); if (title) { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 2cd6fba7098..09f1a41da3d 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -174,7 +174,7 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio }, 'workbench.panel.defaultLocation': { 'type': 'string', - 'enum': ['bottom', 'right'], + 'enum': ['left', 'bottom', 'right'], 'default': 'bottom', 'description': nls.localize('panelDefaultLocation', "Controls the default location of the panel (terminal, debug console, output, problems). It can either show at the bottom or on the right of the workbench.") }, diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index f989b18197c..7430a4265d1 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -18,7 +18,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; -import { Position, Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { Position, Parts, IWorkbenchLayoutService, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IStorageService, WillSaveStateReason, StorageScope } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -349,7 +349,7 @@ export class Workbench extends Layout { { id: Parts.ACTIVITYBAR_PART, role: 'navigation', classes: ['activitybar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, { id: Parts.SIDEBAR_PART, role: 'complementary', classes: ['sidebar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, { id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.state.editor.restoreEditors } }, - { id: Parts.PANEL_PART, role: 'complementary', classes: ['panel', this.state.panel.position === Position.BOTTOM ? 'bottom' : 'right'] }, + { id: Parts.PANEL_PART, role: 'complementary', classes: ['panel', positionToString(this.state.panel.position)] }, { id: Parts.STATUSBAR_PART, role: 'contentinfo', classes: ['statusbar'] } ].forEach(({ id, role, classes, options }) => { const partContainer = this.createPart(id, role, classes); diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 94aa3ef9f55..828a900d396 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -27,6 +27,26 @@ export const enum Position { BOTTOM } +export function positionToString(position: Position): string { + switch (position) { + case Position.LEFT: return 'left'; + case Position.RIGHT: return 'right'; + case Position.BOTTOM: return 'bottom'; + } + + return 'bottom'; +} + +const positionsByString: { [key: string]: Position } = { + [positionToString(Position.LEFT)]: Position.LEFT, + [positionToString(Position.RIGHT)]: Position.RIGHT, + [positionToString(Position.BOTTOM)]: Position.BOTTOM +}; + +export function positionFromString(str: string): Position { + return positionsByString[str]; +} + export interface IWorkbenchLayoutService extends ILayoutService { _serviceBrand: undefined; From e1843386c0e7374bb48e0a607702faa641d38169 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 18 Dec 2019 09:20:08 -0800 Subject: [PATCH 122/241] use positionToString for panel dimension --- src/vs/workbench/browser/layout.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 4cdc4bd8f51..623dfa72b75 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -899,7 +899,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi : (this.state.panel.position === Position.BOTTOM ? grid.getViewSize(this.panelPartView).height : grid.getViewSize(this.panelPartView).width); this.storageService.store(Storage.PANEL_SIZE, panelSize, StorageScope.GLOBAL); - this.storageService.store(Storage.PANEL_DIMENSION, this.state.panel.position === Position.RIGHT ? 'right' : 'bottom', StorageScope.GLOBAL); + this.storageService.store(Storage.PANEL_DIMENSION, positionToString(this.state.panel.position), StorageScope.GLOBAL); const gridSize = grid.getViewSize(); this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL); @@ -1287,7 +1287,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height); // At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300))); - const panelDimension = this.storageService.get(Storage.PANEL_DIMENSION, StorageScope.GLOBAL, 'bottom') === 'bottom' ? Position.BOTTOM : Position.RIGHT; + const panelDimension = positionFromString(this.storageService.get(Storage.PANEL_DIMENSION, StorageScope.GLOBAL, 'bottom')); const fallbackPanelSize = this.state.panel.position === Position.BOTTOM ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4; const panelSize = panelDimension === this.state.panel.position ? this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, fallbackPanelSize)) : fallbackPanelSize; From d6c6f024c8124ca8bcce8f89737032414b31068a Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 18 Dec 2019 18:36:36 +0100 Subject: [PATCH 123/241] repl use language mode of active editor fixes #87124 --- .../workbench/contrib/debug/browser/repl.ts | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 2a1c1dc06b7..c6b56ddcc59 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -25,9 +25,9 @@ import { IInstantiationService, createDecorator } from 'vs/platform/instantiatio import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Panel } from 'vs/workbench/browser/panel'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { memoize } from 'vs/base/common/decorators'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IDebugService, REPL_ID, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IExpressionContainer, IExpression, IReplElementSource, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; @@ -105,6 +105,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private replElementsChangeListener: IDisposable | undefined; private styleElement: HTMLStyleElement | undefined; private completionItemProvider: IDisposable | undefined; + private modelChangeListener: IDisposable = Disposable.None; constructor( @IDebugService private readonly debugService: IDebugService, @@ -118,7 +119,8 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati @IContextMenuService private readonly contextMenuService: IContextMenuService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, - @IClipboardService private readonly clipboardService: IClipboardService + @IClipboardService private readonly clipboardService: IClipboardService, + @IEditorService private readonly editorService: IEditorService ) { super(REPL_ID, telemetryService, themeService, storageService); @@ -179,6 +181,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati dispose(this.model); } else { this.model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:replinput`), true); + this.setMode(); this.replInput.setModel(this.model); this.updateInputDecoration(); this.refreshReplElements(true); @@ -189,6 +192,9 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.onDidFontChange(); } })); + this._register(this.editorService.onDidActiveEditorChange(() => { + this.setMode(); + })); } get isReadonly(): boolean { @@ -213,6 +219,21 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.tree.domFocus(); } + private setMode(): void { + if (!this.isVisible()) { + return; + } + + const activeEditor = this.editorService.activeTextEditorWidget; + if (isCodeEditor(activeEditor)) { + this.modelChangeListener.dispose(); + this.modelChangeListener = activeEditor.onDidChangeModelLanguage(() => this.setMode()); + if (activeEditor.hasModel()) { + this.model.setMode(activeEditor.getModel().getLanguageIdentifier()); + } + } + } + private onDidFontChange(): void { if (this.styleElement) { const debugConsole = this.configurationService.getValue('debug').console; From 3f57a0f75f215c7eff95a1aa1e582ffd9decb41f Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 18 Dec 2019 09:35:32 -0800 Subject: [PATCH 124/241] update minimum width for panel --- 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 bc4a89e974b..9ded18492b0 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -52,7 +52,7 @@ export class PanelPart extends CompositePart implements IPanelService { //#region IView - readonly minimumWidth: number = 300; + readonly minimumWidth: number = 420; readonly maximumWidth: number = Number.POSITIVE_INFINITY; readonly minimumHeight: number = 77; readonly maximumHeight: number = Number.POSITIVE_INFINITY; From 870eb4165ff4464e2da86b74bd837eb16b9fde39 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 18 Dec 2019 11:07:37 -0800 Subject: [PATCH 125/241] Fix #87239 --- .../browser/workbench.contribution.ts | 18 ------------- .../common/preferencesContribution.ts | 26 +++++++++++++++++++ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 09f1a41da3d..be42ef48bee 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -207,24 +207,6 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio ], 'included': isMacintosh }, - 'workbench.settings.enableNaturalLanguageSearch': { - 'type': 'boolean', - 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings. The natural language search is provided by a Microsoft online service."), - 'default': true, - 'scope': ConfigurationScope.WINDOW, - 'tags': ['usesOnlineServices'] - }, - 'workbench.settings.settingsSearchTocBehavior': { - 'type': 'string', - 'enum': ['hide', 'filter'], - 'enumDescriptions': [ - nls.localize('settingsSearchTocBehavior.hide', "Hide the Table of Contents while searching."), - nls.localize('settingsSearchTocBehavior.filter', "Filter the Table of Contents to just categories that have matching settings. Clicking a category will filter the results to that category."), - ], - 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor Table of Contents while searching."), - 'default': 'filter', - 'scope': ConfigurationScope.WINDOW - }, 'workbench.settings.editor': { 'type': 'string', 'enum': ['ui', 'json'], diff --git a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index cde13f2ab32..fef8e28c6c8 100644 --- a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { endsWith } from 'vs/base/common/strings'; @@ -22,6 +23,7 @@ import { IEditorInput } from 'vs/workbench/common/editor'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { FOLDER_SETTINGS_PATH, IPreferencesService, USE_SPLIT_JSON_SETTING } from 'vs/workbench/services/preferences/common/preferences'; +import { Extensions, IConfigurationRegistry, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; const schemaRegistry = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); @@ -147,3 +149,27 @@ export class PreferencesContribution implements IWorkbenchContribution { dispose(this.settingsListener); } } + +const registry = Registry.as(Extensions.Configuration); +registry.registerConfiguration({ + 'properties': { + 'workbench.settings.enableNaturalLanguageSearch': { + 'type': 'boolean', + 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings. The natural language search is provided by a Microsoft online service."), + 'default': true, + 'scope': ConfigurationScope.WINDOW, + 'tags': ['usesOnlineServices'] + }, + 'workbench.settings.settingsSearchTocBehavior': { + 'type': 'string', + 'enum': ['hide', 'filter'], + 'enumDescriptions': [ + nls.localize('settingsSearchTocBehavior.hide', "Hide the Table of Contents while searching."), + nls.localize('settingsSearchTocBehavior.filter', "Filter the Table of Contents to just categories that have matching settings. Clicking a category will filter the results to that category."), + ], + 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor Table of Contents while searching."), + 'default': 'filter', + 'scope': ConfigurationScope.WINDOW + }, + } +}); From dbc7fa3c3e8952244720fdda5301788fbe72f478 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 21:42:56 +0100 Subject: [PATCH 126/241] #87246 Introduce name in view container --- build/lib/i18n.resources.json | 4 ++++ src/vs/workbench/api/browser/viewsExtensionPoint.ts | 2 +- .../browser/parts/views/viewPaneContainer.ts | 12 +++++------- src/vs/workbench/common/views.ts | 3 +++ .../contrib/debug/browser/debug.contribution.ts | 2 +- src/vs/workbench/contrib/debug/common/debug.ts | 2 +- .../extensions/browser/extensions.contribution.ts | 4 ++-- .../contrib/extensions/common/extensions.ts | 3 ++- .../contrib/files/browser/files.contribution.ts | 4 ++-- src/vs/workbench/contrib/files/common/files.ts | 3 ++- .../contrib/markers/browser/markers.contribution.ts | 4 ++-- src/vs/workbench/contrib/remote/browser/remote.ts | 2 +- .../contrib/remote/common/remote.contribution.ts | 1 + .../contrib/scm/browser/scm.contribution.ts | 4 ++-- src/vs/workbench/contrib/scm/common/scm.ts | 3 ++- .../contrib/search/browser/search.contribution.ts | 2 +- src/vs/workbench/services/search/common/search.ts | 3 ++- .../workbench/test/browser/parts/views/views.test.ts | 2 +- 18 files changed, 35 insertions(+), 25 deletions(-) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index e82324735d7..d5fd9c166b5 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -270,6 +270,10 @@ "name": "vs/workbench/services/remote", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/search", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/textfile", "project": "vscode-workbench" diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 4357c15c830..61b092a328f 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -313,7 +313,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { if (!viewContainer) { - viewContainer = this.viewContainersRegistry.registerViewContainer({ id, hideIfEmpty: true, extensionId }, ViewContainerLocation.Sidebar); + viewContainer = this.viewContainersRegistry.registerViewContainer({ id, hideIfEmpty: true, name: title, extensionId }, ViewContainerLocation.Sidebar); class CustomViewPaneContainer extends ViewPaneContainer { constructor( diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index bd80bbfd037..8685606b5e7 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -25,7 +25,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndControlle import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; @@ -36,8 +36,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; import { Component } from 'vs/workbench/common/component'; -import { Extensions as ViewletExtensions, ViewletRegistry } from 'vs/workbench/browser/viewlet'; -import { Extensions as PanelExtensions, PanelRegistry } from 'vs/workbench/browser/panel'; export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -245,6 +243,7 @@ interface IViewPaneItem { export class ViewPaneContainer extends Component implements IViewPaneContainer { + private readonly viewContainer: ViewContainer; private lastFocusedPane: ViewPane | undefined; private paneItems: IViewPaneItem[] = []; private paneview?: PaneView; @@ -309,6 +308,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.options.dnd = new DefaultPaneDndController(); } + this.viewContainer = container; this.visibleViewsStorageId = `${id}.numberOfVisibleViews`; this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, undefined); this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables))); @@ -346,17 +346,15 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getTitle(): string { - const composite = Registry.as(ViewletExtensions.Viewlets).getViewlet(this.getId()) || Registry.as(PanelExtensions.Panels).getPanel(this.getId()); - if (this.isViewMergedWithContainer()) { const paneItemTitle = this.paneItems[0].pane.title; if (this.options.donotShowContainerTitleWhenMergedWithContainer) { return this.paneItems[0].pane.title; } - return paneItemTitle ? `${composite.name}: ${paneItemTitle}` : composite.name; + return paneItemTitle ? `${this.viewContainer.name}: ${paneItemTitle}` : this.viewContainer.name; } - return composite.name; + return this.viewContainer.name; } private showContextMenu(event: StandardMouseEvent): void { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 13473dadd3b..5508b341928 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -36,6 +36,8 @@ export interface IViewContainerDescriptor { readonly id: string; + readonly name: string; + readonly viewOrderDelegate?: ViewOrderDelegate; readonly hideIfEmpty?: boolean; @@ -99,6 +101,7 @@ export class ViewContainer { protected constructor(private readonly descriptor: IViewContainerDescriptor) { } readonly id: string = this.descriptor.id; + readonly name: string = this.descriptor.name; readonly hideIfEmpty: boolean = !!this.descriptor.hideIfEmpty; readonly extensionId: ExtensionIdentifier | undefined = this.descriptor.extensionId; readonly orderDelegate: ViewOrderDelegate | undefined = this.descriptor.viewOrderDelegate; diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index fb50a8cdb98..96c6e477b6f 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -83,7 +83,7 @@ class OpenDebugPanelAction extends TogglePanelAction { Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( DebugViewlet, VIEWLET_ID, - nls.localize('debugAndRun', "Debug and Run"), + VIEW_CONTAINER.name, 'codicon-debug-alt', 3 )); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 777f24123e6..d3eb78bed69 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -28,7 +28,7 @@ import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewCon import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.debug'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID }, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: nls.localize('debugAndRun', "Debug and Run") }, ViewContainerLocation.Sidebar); export const VARIABLES_VIEW_ID = 'workbench.debug.variablesView'; export const WATCH_VIEW_ID = 'workbench.debug.watchExpressionsView'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index a083e174f82..49b9286c12b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { VIEWLET_ID, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { VIEWLET_ID, IExtensionsWorkbenchService, VIEW_CONTAINER } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, @@ -79,7 +79,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( const viewletDescriptor = ViewletDescriptor.create( ExtensionsViewlet, VIEWLET_ID, - localize('extensions', "Extensions"), + VIEW_CONTAINER.name, 'codicon-extensions', 4 ); diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 2425b0aeb4e..f7dd43dadca 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -16,9 +16,10 @@ import { URI } from 'vs/base/common/uri'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; +import { localize } from 'vs/nls'; export const VIEWLET_ID = 'workbench.view.extensions'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID }, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('extensions', "Extensions") }, ViewContainerLocation.Sidebar); export const EXTENSIONS_CONFIG = '.vscode/extensions.json'; diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 7d0ec14feb3..3912c5b7350 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; -import { VIEWLET_ID, SortOrder, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, VIEW_CONTAINER, SortOrder, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; @@ -77,7 +77,7 @@ class FileUriLabelContribution implements IWorkbenchContribution { Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( ExplorerViewlet, VIEWLET_ID, - nls.localize('explore', "Explorer"), + VIEW_CONTAINER.name, 'codicon-files', 0 )); diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 2bc88cf3cca..f445cadae83 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -24,6 +24,7 @@ import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { once } from 'vs/base/common/functional'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { localize } from 'vs/nls'; /** * Explorer viewlet id. @@ -33,7 +34,7 @@ export const VIEWLET_ID = 'workbench.view.explorer'; /** * Explorer viewlet container. */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID }, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('explore', "Explorer") }, ViewContainerLocation.Sidebar); export interface IExplorerService { _serviceBrand: undefined; diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 0723ead7463..5c42c1d979b 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -102,7 +102,7 @@ Registry.as(Extensions.Configuration).registerConfigurat // markers view container -const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Constants.MARKERS_PANEL_ID }, ViewContainerLocation.Panel); +const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Constants.MARKERS_PANEL_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, ViewContainerLocation.Panel); Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ id: Constants.MARKERS_VIEW_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, @@ -139,7 +139,7 @@ class ToggleMarkersPanelAction extends TogglePanelAction { Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( MarkersPanel, Constants.MARKERS_PANEL_ID, - Messages.MARKERS_PANEL_TITLE_PROBLEMS, + VIEW_CONTAINER.name, 'markersPanel', 10, ToggleMarkersPanelAction.ID diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index cbd0e4e7728..f14bd8e7db2 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -356,7 +356,7 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer { Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( RemoteViewlet, VIEWLET_ID, - nls.localize('remote.explorer', "Remote Explorer"), + VIEW_CONTAINER.name, 'codicon-remote-explorer', 4 )); diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index cfdff3ca468..18989f3dca1 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -22,6 +22,7 @@ export const VIEWLET_ID = 'workbench.view.remote'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( { id: VIEWLET_ID, + name: localize('remote.explorer', "Remote Explorer"), hideIfEmpty: true, viewOrderDelegate: { getOrder: (group?: string) => { diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 0021ac28e7d..8300e45cb3a 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet'; -import { VIEWLET_ID, ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; +import { VIEWLET_ID, VIEW_CONTAINER, ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; @@ -41,7 +41,7 @@ Registry.as(WorkbenchExtensions.Workbench) Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( SCMViewlet, VIEWLET_ID, - localize('source control', "Source Control"), + VIEW_CONTAINER.name, 'codicon-source-control', 2 )); diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 17918a2a7de..861a297c815 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -11,9 +11,10 @@ import { Command } from 'vs/editor/common/modes'; import { ISequence } from 'vs/base/common/sequence'; import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; +import { localize } from 'vs/nls'; export const VIEWLET_ID = 'workbench.view.scm'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID }, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('source control', "Source Control"), }, ViewContainerLocation.Sidebar); export interface IBaselineResourceProvider { getBaselineResource(resource: URI): Promise; diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 6986f568acd..418fd0257d0 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -508,7 +508,7 @@ class ShowAllSymbolsAction extends Action { Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( SearchViewlet, VIEWLET_ID, - nls.localize('name', "Search"), + VIEW_CONTAINER.name, 'codicon-search', 1 )); diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 140d4be9183..5e7d4a890c6 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -18,6 +18,7 @@ import { Event } from 'vs/base/common/event'; import { relative } from 'vs/base/common/path'; import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; +import { localize } from 'vs/nls'; export const VIEWLET_ID = 'workbench.view.search'; export const PANEL_ID = 'workbench.view.search'; @@ -25,7 +26,7 @@ export const VIEW_ID = 'workbench.view.search'; /** * Search viewlet container. */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, hideIfEmpty: true }, ViewContainerLocation.Sidebar); +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('name', "Search"), hideIfEmpty: true }, ViewContainerLocation.Sidebar); export const ISearchService = createDecorator('searchService'); diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index 9a32a551881..5b4b006e769 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -15,7 +15,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import sinon = require('sinon'); -const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'test' }, ViewContainerLocation.Sidebar); +const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'test', name: 'test' }, ViewContainerLocation.Sidebar); const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); class ViewDescriptorSequence { From b58c6fafad3ac21bf771d9804e2d86766ad11748 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 2 Dec 2019 23:40:22 +0100 Subject: [PATCH 127/241] [html] add semantic highlight --- .../client/src/htmlMain.ts | 26 ++++++++- .../server/src/htmlServerMain.ts | 56 ++++++++++++++++++- .../server/src/modes/javascriptMode.ts | 52 +++++++++++++++++ .../server/src/modes/languageModes.ts | 2 + 4 files changed, 133 insertions(+), 3 deletions(-) diff --git a/extensions/html-language-features/client/src/htmlMain.ts b/extensions/html-language-features/client/src/htmlMain.ts index e8a9a2fa22b..b60afd2faf5 100644 --- a/extensions/html-language-features/client/src/htmlMain.ts +++ b/extensions/html-language-features/client/src/htmlMain.ts @@ -9,7 +9,7 @@ import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); import { languages, ExtensionContext, IndentAction, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace, Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit, CompletionContext, CompletionList } from 'vscode'; -import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams, DocumentRangeFormattingRequest, ProvideCompletionItemsSignature } from 'vscode-languageclient'; +import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams, DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier } from 'vscode-languageclient'; import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared'; import { activateTagClosing } from './tagClosing'; import TelemetryReporter from 'vscode-extension-telemetry'; @@ -23,6 +23,18 @@ namespace MatchingTagPositionRequest { export const type: RequestType = new RequestType('html/matchingTagPosition'); } +// experimental: semantic tokens +interface SemanticTokenParams { + textDocument: TextDocumentIdentifier; + ranges?: Range[]; +} +namespace SemanticTokenRequest { + export const type: RequestType = new RequestType('html/semanticTokens'); +} +namespace SemanticTokenLegendRequest { + export const type: RequestType = new RequestType('html/semanticTokenLegend'); +} + interface IPackageInfo { name: string; version: string; @@ -132,6 +144,18 @@ export function activate(context: ExtensionContext) { updateFormatterRegistration(); toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); toDispose.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration('html.format.enable') && updateFormatterRegistration())); + + + + // toDispose.push(languages.registerSemanticTokensProvider(documentSelector, { + // provideSemanticTokens(doc, opts) { + // return null; + // } + // g\ + + // })) + + }); function updateFormatterRegistration() { diff --git a/extensions/html-language-features/server/src/htmlServerMain.ts b/extensions/html-language-features/server/src/htmlServerMain.ts index ba4d4b8e1aa..cbbd4c4b5a9 100644 --- a/extensions/html-language-features/server/src/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/htmlServerMain.ts @@ -6,10 +6,10 @@ import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType, DocumentRangeFormattingRequest, Disposable, DocumentSelector, TextDocumentPositionParams, ServerCapabilities, - Position, ConfigurationRequest, ConfigurationParams, DidChangeWorkspaceFoldersNotification, + Position, ConfigurationRequest, ConfigurationParams, DidChangeWorkspaceFoldersNotification, Range, WorkspaceFolder, DocumentColorRequest, ColorInformation, ColorPresentationRequest, TextDocumentSyncKind } from 'vscode-languageserver'; -import { TextDocument, Diagnostic, DocumentLink, SymbolInformation } from 'vscode-html-languageservice'; +import { TextDocument, Diagnostic, DocumentLink, SymbolInformation, TextDocumentIdentifier } from 'vscode-html-languageservice'; import { getLanguageModes, LanguageModes, Settings } from './modes/languageModes'; import { format } from './modes/formatting'; @@ -29,6 +29,18 @@ namespace MatchingTagPositionRequest { export const type: RequestType = new RequestType('html/matchingTagPosition'); } +// experimental: semantic tokens +interface SemanticTokenParams { + textDocument: TextDocumentIdentifier; + ranges?: Range[]; +} +namespace SemanticTokenRequest { + export const type: RequestType = new RequestType('html/semanticTokens'); +} +namespace SemanticTokenLegendRequest { + export const type: RequestType = new RequestType('html/semanticTokenLegend'); +} + // Create a connection for the server const connection: IConnection = createConnection(); @@ -500,5 +512,45 @@ connection.onRequest(MatchingTagPositionRequest.type, (params, token) => { }, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token); }); +connection.onRequest(MatchingTagPositionRequest.type, (params, token) => { + return runSafe(() => { + const document = documents.get(params.textDocument.uri); + if (document) { + const pos = params.position; + if (pos.character > 0) { + const mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1)); + if (mode && mode.findMatchingTagPosition) { + return mode.findMatchingTagPosition(document, pos); + } + } + } + return null; + }, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token); +}); + +connection.onRequest(SemanticTokenRequest.type, (params, token) => { + return runSafe(() => { + const document = documents.get(params.textDocument.uri); + if (document) { + const jsMode = languageModes.getMode('javascript'); + if (jsMode && jsMode.getSemanticTokens) { + return jsMode.getSemanticTokens(document, params.ranges); + } + } + return null; + }, null, `Error while computing semantic tokens for ${params.textDocument.uri}`, token); +}); + +connection.onRequest(SemanticTokenLegendRequest.type, (_params, token) => { + return runSafe(() => { + const jsMode = languageModes.getMode('javascript'); + if (jsMode && jsMode.getSemanticTokenLegend) { + return jsMode.getSemanticTokenLegend(); + } + return null; + }, null, `Error while computing semantic tokens legend`, token); +}); + + // Listen on the connection connection.listen(); diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index 0bd4259f883..69e65148035 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -314,6 +314,44 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache Position | null; getFoldingRanges?: (document: TextDocument) => FoldingRange[]; onDocumentRemoved(document: TextDocument): void; + getSemanticTokens?(document: TextDocument, ranges: Range[] | undefined): number[]; + getSemanticTokenLegend?(): { types: string[], modifiers: string[] }; dispose(): void; } From 42c54bd5593083c33fb23e00c7b49ff1ae5c3226 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 2 Dec 2019 23:44:09 +0100 Subject: [PATCH 128/241] get legend --- .../client/src/htmlMain.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/extensions/html-language-features/client/src/htmlMain.ts b/extensions/html-language-features/client/src/htmlMain.ts index b60afd2faf5..d5af25e826d 100644 --- a/extensions/html-language-features/client/src/htmlMain.ts +++ b/extensions/html-language-features/client/src/htmlMain.ts @@ -9,7 +9,7 @@ import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); import { languages, ExtensionContext, IndentAction, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace, Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit, CompletionContext, CompletionList } from 'vscode'; -import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams, DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier } from 'vscode-languageclient'; +import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams, DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0 } from 'vscode-languageclient'; import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared'; import { activateTagClosing } from './tagClosing'; import TelemetryReporter from 'vscode-extension-telemetry'; @@ -32,7 +32,7 @@ namespace SemanticTokenRequest { export const type: RequestType = new RequestType('html/semanticTokens'); } namespace SemanticTokenLegendRequest { - export const type: RequestType = new RequestType('html/semanticTokenLegend'); + export const type: RequestType0<{ types: string[]; modifiers: string[] } | null, any, any> = new RequestType0('html/semanticTokenLegend'); } interface IPackageInfo { @@ -145,16 +145,15 @@ export function activate(context: ExtensionContext) { toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); toDispose.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration('html.format.enable') && updateFormatterRegistration())); + client.sendRequest(SemanticTokenLegendRequest.type).then(legend => { + // toDispose.push(languages.registerSemanticTokensProvider(documentSelector, { + // provideSemanticTokens(doc, opts) { + // return null; + // } + // g\ - - // toDispose.push(languages.registerSemanticTokensProvider(documentSelector, { - // provideSemanticTokens(doc, opts) { - // return null; - // } - // g\ - - // })) - + // }), vsc); + }); }); From 28a0f24daab04a9b1db782b64e1722e805683906 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 12 Dec 2019 17:08:25 +0100 Subject: [PATCH 129/241] improve logging --- .../common/services/modelServiceImpl.ts | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index c0280a3c379..95b967ab215 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -592,30 +592,34 @@ class SemanticColoringProviderStyling { public getMetadata(tokenTypeIndex: number, tokenModifierSet: number): number { const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet); + let metadata: number | undefined; if (entry) { - return entry.metadata; - } - - const tokenType = this._legend.tokenTypes[tokenTypeIndex]; - const tokenModifiers: string[] = []; - for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { - if (tokenModifierSet & 1) { - tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]); + metadata = entry.metadata; + } else { + const tokenType = this._legend.tokenTypes[tokenTypeIndex]; + const tokenModifiers: string[] = []; + for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) { + if (tokenModifierSet & 1) { + tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]); + } + tokenModifierSet = tokenModifierSet >> 1; } - tokenModifierSet = tokenModifierSet >> 1; - } - let metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers); - if (typeof metadata === 'undefined') { - metadata = Constants.NO_STYLING; + metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers); + if (typeof metadata === 'undefined') { + metadata = Constants.NO_STYLING; + } + this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata); } if (this._logService.getLevel() === LogLevel.Trace) { - this._logService.trace(`getTokenStyleMetadata(${tokenType}${tokenModifiers.length ? ', ' + tokenModifiers.join(' ') : ''}): foreground: ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`); + const type = this._legend.tokenTypes[tokenTypeIndex]; + const modifiers = tokenModifierSet ? ' ' + this._legend.tokenModifiers.filter((_, i) => tokenModifierSet & (1 << i)).join(' ') : ''; + this._logService.trace(`tokenStyleMetadata ${entry ? '[CACHED] ' : ''}${type}${modifiers}: foreground ${TokenMetadata.getForeground(metadata)}, fontStyle ${TokenMetadata.getFontStyle(metadata).toString(2)}`); } - - this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata); return metadata; } + + } const enum SemanticColoringConstants { From 03e79666c7480881c70311c670331279c84643c4 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 12 Dec 2019 17:08:54 +0100 Subject: [PATCH 130/241] sem highlighting in HTML --- .../client/src/htmlMain.ts | 34 +++++++++++++------ .../server/src/modes/javascriptMode.ts | 8 +++-- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/extensions/html-language-features/client/src/htmlMain.ts b/extensions/html-language-features/client/src/htmlMain.ts index d5af25e826d..2df5e7f5c13 100644 --- a/extensions/html-language-features/client/src/htmlMain.ts +++ b/extensions/html-language-features/client/src/htmlMain.ts @@ -8,8 +8,15 @@ import * as fs from 'fs'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); -import { languages, ExtensionContext, IndentAction, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace, Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit, CompletionContext, CompletionList } from 'vscode'; -import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams, DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0 } from 'vscode-languageclient'; +import { + languages, ExtensionContext, IndentAction, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace, + Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit, CompletionContext, CompletionList, SemanticTokensLegend, + SemanticTokensProvider, SemanticTokens +} from 'vscode'; +import { + LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams, DocumentRangeFormattingParams, + DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange +} from 'vscode-languageclient'; import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared'; import { activateTagClosing } from './tagClosing'; import TelemetryReporter from 'vscode-extension-telemetry'; @@ -26,7 +33,7 @@ namespace MatchingTagPositionRequest { // experimental: semantic tokens interface SemanticTokenParams { textDocument: TextDocumentIdentifier; - ranges?: Range[]; + ranges?: LspRange[]; } namespace SemanticTokenRequest { export const type: RequestType = new RequestType('html/semanticTokens'); @@ -146,13 +153,20 @@ export function activate(context: ExtensionContext) { toDispose.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration('html.format.enable') && updateFormatterRegistration())); client.sendRequest(SemanticTokenLegendRequest.type).then(legend => { - // toDispose.push(languages.registerSemanticTokensProvider(documentSelector, { - // provideSemanticTokens(doc, opts) { - // return null; - // } - // g\ - - // }), vsc); + if (legend) { + const provider: SemanticTokensProvider = { + provideSemanticTokens(doc, opts) { + const params: SemanticTokenParams = { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(doc), + ranges: opts.ranges?.map(r => client.code2ProtocolConverter.asRange(r)) + }; + return client.sendRequest(SemanticTokenRequest.type, params).then(data => { + return data && new SemanticTokens(new Uint32Array(data)); + }); + } + }; + toDispose.push(languages.registerSemanticTokensProvider(documentSelector, provider, new SemanticTokensLegend(legend.types, legend.modifiers))); + } }); }); diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index 69e65148035..5430109bf9e 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -29,17 +29,19 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache compilerOptions, getScriptFileNames: () => [FILE_NAME, jquery_d_ts], - getScriptKind: () => ts.ScriptKind.JS, + getScriptKind: (fileName) => fileName === FILE_NAME ? currentScriptKind : ts.ScriptKind.JS, getScriptVersion: (fileName: string) => { if (fileName === FILE_NAME) { return String(scriptFileVersion); @@ -315,7 +317,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache Date: Thu, 12 Dec 2019 22:35:42 +0100 Subject: [PATCH 131/241] fixes --- .../server/src/modes/javascriptMode.ts | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index 5430109bf9e..337d47a65a4 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -17,6 +17,7 @@ import * as ts from 'typescript'; import { join } from 'path'; const FILE_NAME = 'vscode://javascript/1'; // the same 'file' is used for all contents +const TS_FILE_NAME = 'vscode://javascript/2.ts'; const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; let jquery_d_ts = join(__dirname, '../lib/jquery.d.ts'); // when packaged @@ -29,21 +30,19 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache compilerOptions, - getScriptFileNames: () => [FILE_NAME, jquery_d_ts], - getScriptKind: (fileName) => fileName === FILE_NAME ? currentScriptKind : ts.ScriptKind.JS, + getScriptFileNames: () => [FILE_NAME, TS_FILE_NAME, jquery_d_ts], + getScriptKind: (fileName) => fileName === TS_FILE_NAME ? ts.ScriptKind.TS : ts.ScriptKind.JS, getScriptVersion: (fileName: string) => { - if (fileName === FILE_NAME) { + if (fileName === FILE_NAME || fileName === TS_FILE_NAME) { return String(scriptFileVersion); } return '1'; // default lib an jquery.d.ts are static @@ -51,7 +50,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { let text = ''; if (startsWith(fileName, 'vscode:')) { - if (fileName === FILE_NAME) { + if (fileName === FILE_NAME || fileName === TS_FILE_NAME) { text = currentTextDocument.getText(); } } else { @@ -317,7 +316,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache Date: Fri, 13 Dec 2019 11:50:23 +0100 Subject: [PATCH 132/241] extract js semantic tokens --- .../server/src/modes/javascriptMode.ts | 50 ++--------------- .../src/modes/javascriptSemanticTokens.ts | 56 +++++++++++++++++++ .../server/src/modes/languageModes.ts | 3 +- 3 files changed, 64 insertions(+), 45 deletions(-) create mode 100644 extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index 337d47a65a4..258e471d7dc 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -7,14 +7,15 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache import { SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation, Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString, - DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions, FoldingRange, FoldingRangeKind, SelectionRange -} from 'vscode-html-languageservice'; -import { LanguageMode, Settings } from './languageModes'; + DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions, FoldingRange, FoldingRangeKind, SelectionRange, + LanguageMode, Settings +} from './languageModes'; import { getWordAtText, startsWith, isWhitespaceOnly, repeat } from '../utils/strings'; import { HTMLDocumentRegions } from './embeddedSupport'; import * as ts from 'typescript'; import { join } from 'path'; +import { getSemanticTokens, getSemanticTokenLegend } from './javascriptSemanticTokens'; const FILE_NAME = 'vscode://javascript/1'; // the same 'file' is used for all contents const TS_FILE_NAME = 'vscode://javascript/2.ts'; @@ -320,38 +321,10 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache Date: Fri, 13 Dec 2019 11:50:52 +0100 Subject: [PATCH 133/241] [html] use languageModes for language types --- .../server/src/htmlServerMain.ts | 10 ++++--- .../server/src/languageModelCache.ts | 22 ++++++++-------- .../server/src/modes/cssMode.ts | 6 ++--- .../server/src/modes/embeddedSupport.ts | 2 +- .../server/src/modes/formatting.ts | 3 +-- .../server/src/modes/htmlFolding.ts | 5 ++-- .../server/src/modes/htmlMode.ts | 6 ++--- .../server/src/modes/pathCompletion.ts | 3 +-- .../server/src/modes/selectionRanges.ts | 3 +-- .../server/src/test/completions.test.ts | 5 +--- .../server/src/test/embedded.test.ts | 21 ++++++++------- .../server/src/test/folding.test.ts | 26 +++++++++---------- .../server/src/test/formatting.test.ts | 7 +++-- .../server/src/test/selectionRanges.test.ts | 5 +--- .../server/src/utils/documentContext.ts | 3 +-- 15 files changed, 58 insertions(+), 69 deletions(-) diff --git a/extensions/html-language-features/server/src/htmlServerMain.ts b/extensions/html-language-features/server/src/htmlServerMain.ts index cbbd4c4b5a9..2289c38c950 100644 --- a/extensions/html-language-features/server/src/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/htmlServerMain.ts @@ -6,11 +6,13 @@ import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType, DocumentRangeFormattingRequest, Disposable, DocumentSelector, TextDocumentPositionParams, ServerCapabilities, - Position, ConfigurationRequest, ConfigurationParams, DidChangeWorkspaceFoldersNotification, Range, - WorkspaceFolder, DocumentColorRequest, ColorInformation, ColorPresentationRequest, TextDocumentSyncKind + ConfigurationRequest, ConfigurationParams, DidChangeWorkspaceFoldersNotification, + DocumentColorRequest, ColorPresentationRequest, TextDocumentSyncKind } from 'vscode-languageserver'; -import { TextDocument, Diagnostic, DocumentLink, SymbolInformation, TextDocumentIdentifier } from 'vscode-html-languageservice'; -import { getLanguageModes, LanguageModes, Settings } from './modes/languageModes'; +import { + getLanguageModes, LanguageModes, Settings, TextDocument, Position, Diagnostic, WorkspaceFolder, ColorInformation, + Range, DocumentLink, SymbolInformation, TextDocumentIdentifier +} from './modes/languageModes'; import { format } from './modes/formatting'; import { pushAll } from './utils/arrays'; diff --git a/extensions/html-language-features/server/src/languageModelCache.ts b/extensions/html-language-features/server/src/languageModelCache.ts index c022b8047a9..06d331bca8a 100644 --- a/extensions/html-language-features/server/src/languageModelCache.ts +++ b/extensions/html-language-features/server/src/languageModelCache.ts @@ -18,10 +18,10 @@ export function getLanguageModelCache(maxEntries: number, cleanupIntervalTime let cleanupInterval: NodeJS.Timer | undefined = undefined; if (cleanupIntervalTimeInSec > 0) { cleanupInterval = setInterval(() => { - let cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000; - let uris = Object.keys(languageModels); - for (let uri of uris) { - let languageModelInfo = languageModels[uri]; + const cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000; + const uris = Object.keys(languageModels); + for (const uri of uris) { + const languageModelInfo = languageModels[uri]; if (languageModelInfo.cTime < cutoffTime) { delete languageModels[uri]; nModels--; @@ -32,14 +32,14 @@ export function getLanguageModelCache(maxEntries: number, cleanupIntervalTime return { get(document: TextDocument): T { - let version = document.version; - let languageId = document.languageId; - let languageModelInfo = languageModels[document.uri]; + const version = document.version; + const languageId = document.languageId; + const languageModelInfo = languageModels[document.uri]; if (languageModelInfo && languageModelInfo.version === version && languageModelInfo.languageId === languageId) { languageModelInfo.cTime = Date.now(); return languageModelInfo.languageModel; } - let languageModel = parse(document); + const languageModel = parse(document); languageModels[document.uri] = { languageModel, version, languageId, cTime: Date.now() }; if (!languageModelInfo) { nModels++; @@ -48,8 +48,8 @@ export function getLanguageModelCache(maxEntries: number, cleanupIntervalTime if (nModels === maxEntries) { let oldestTime = Number.MAX_VALUE; let oldestUri = null; - for (let uri in languageModels) { - let languageModelInfo = languageModels[uri]; + for (const uri in languageModels) { + const languageModelInfo = languageModels[uri]; if (languageModelInfo.cTime < oldestTime) { oldestUri = uri; oldestTime = languageModelInfo.cTime; @@ -64,7 +64,7 @@ export function getLanguageModelCache(maxEntries: number, cleanupIntervalTime }, onDocumentRemoved(document: TextDocument) { - let uri = document.uri; + const uri = document.uri; if (languageModels[uri]) { delete languageModels[uri]; nModels--; diff --git a/extensions/html-language-features/server/src/modes/cssMode.ts b/extensions/html-language-features/server/src/modes/cssMode.ts index 490f7ea2fa4..e705f44bbab 100644 --- a/extensions/html-language-features/server/src/modes/cssMode.ts +++ b/extensions/html-language-features/server/src/modes/cssMode.ts @@ -4,11 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; -import { TextDocument, Position, Range, CompletionList } from 'vscode-html-languageservice'; -import { Stylesheet, FoldingRange, LanguageService as CSSLanguageService } from 'vscode-css-languageservice'; -import { LanguageMode, Workspace } from './languageModes'; +import { Stylesheet, LanguageService as CSSLanguageService } from 'vscode-css-languageservice'; +import { FoldingRange, LanguageMode, Workspace, Color, TextDocument, Position, Range, CompletionList } from './languageModes'; import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport'; -import { Color } from 'vscode-languageserver'; export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegions: LanguageModelCache, workspace: Workspace): LanguageMode { let embeddedCSSDocuments = getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument('css')); diff --git a/extensions/html-language-features/server/src/modes/embeddedSupport.ts b/extensions/html-language-features/server/src/modes/embeddedSupport.ts index df47be3ed97..837842c6405 100644 --- a/extensions/html-language-features/server/src/modes/embeddedSupport.ts +++ b/extensions/html-language-features/server/src/modes/embeddedSupport.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TextDocument, Position, LanguageService, TokenType, Range } from 'vscode-html-languageservice'; +import { TextDocument, Position, LanguageService, TokenType, Range } from './languageModes'; export interface LanguageRange extends Range { languageId: string | undefined; diff --git a/extensions/html-language-features/server/src/modes/formatting.ts b/extensions/html-language-features/server/src/modes/formatting.ts index a9df4b4a30b..4acb60a9b9a 100644 --- a/extensions/html-language-features/server/src/modes/formatting.ts +++ b/extensions/html-language-features/server/src/modes/formatting.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TextDocument, Range, TextEdit, FormattingOptions, Position } from 'vscode-html-languageservice'; -import { LanguageModes, Settings, LanguageModeRange } from './languageModes'; +import { LanguageModes, Settings, LanguageModeRange, TextDocument, Range, TextEdit, FormattingOptions, Position } from './languageModes'; import { pushAll } from '../utils/arrays'; import { isEOL } from '../utils/strings'; diff --git a/extensions/html-language-features/server/src/modes/htmlFolding.ts b/extensions/html-language-features/server/src/modes/htmlFolding.ts index aacdcc604fc..5eccc0ef876 100644 --- a/extensions/html-language-features/server/src/modes/htmlFolding.ts +++ b/extensions/html-language-features/server/src/modes/htmlFolding.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TextDocument, CancellationToken, Position, Range } from 'vscode-languageserver'; -import { FoldingRange } from 'vscode-html-languageservice'; -import { LanguageModes, LanguageMode } from './languageModes'; +import { TextDocument, FoldingRange, Position, Range, LanguageModes, LanguageMode } from './languageModes'; +import { CancellationToken } from 'vscode-languageserver'; export function getFoldingRanges(languageModes: LanguageModes, document: TextDocument, maxRanges: number | undefined, _cancellationToken: CancellationToken | null): FoldingRange[] { let htmlMode = languageModes.getMode('html'); diff --git a/extensions/html-language-features/server/src/modes/htmlMode.ts b/extensions/html-language-features/server/src/modes/htmlMode.ts index 6f2461d6c20..251821d1272 100644 --- a/extensions/html-language-features/server/src/modes/htmlMode.ts +++ b/extensions/html-language-features/server/src/modes/htmlMode.ts @@ -7,9 +7,9 @@ import { getLanguageModelCache } from '../languageModelCache'; import { LanguageService as HTMLLanguageService, HTMLDocument, DocumentContext, FormattingOptions, HTMLFormatConfiguration, SelectionRange, - TextDocument, Position, Range, CompletionItem, FoldingRange -} from 'vscode-html-languageservice'; -import { LanguageMode, Workspace } from './languageModes'; + TextDocument, Position, Range, CompletionItem, FoldingRange, + LanguageMode, Workspace +} from './languageModes'; import { getPathCompletionParticipant } from './pathCompletion'; export function getHTMLMode(htmlLanguageService: HTMLLanguageService, workspace: Workspace): LanguageMode { diff --git a/extensions/html-language-features/server/src/modes/pathCompletion.ts b/extensions/html-language-features/server/src/modes/pathCompletion.ts index 278d967bd95..d522efdc0df 100644 --- a/extensions/html-language-features/server/src/modes/pathCompletion.ts +++ b/extensions/html-language-features/server/src/modes/pathCompletion.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { WorkspaceFolder } from 'vscode-languageserver'; import * as path from 'path'; import * as fs from 'fs'; import { URI } from 'vscode-uri'; -import { ICompletionParticipant, TextDocument, CompletionItemKind, CompletionItem, TextEdit, Range, Position } from 'vscode-html-languageservice'; +import { ICompletionParticipant, TextDocument, CompletionItemKind, CompletionItem, TextEdit, Range, Position, WorkspaceFolder } from './languageModes'; import { startsWith } from '../utils/strings'; import { contains } from '../utils/arrays'; diff --git a/extensions/html-language-features/server/src/modes/selectionRanges.ts b/extensions/html-language-features/server/src/modes/selectionRanges.ts index ab6cbdc0f36..6c0627f6356 100644 --- a/extensions/html-language-features/server/src/modes/selectionRanges.ts +++ b/extensions/html-language-features/server/src/modes/selectionRanges.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LanguageModes } from './languageModes'; -import { TextDocument, Position, Range, SelectionRange } from 'vscode-html-languageservice'; +import { LanguageModes, TextDocument, Position, Range, SelectionRange } from './languageModes'; export function getSelectionRanges(languageModes: LanguageModes, document: TextDocument, positions: Position[]) { const htmlMode = languageModes.getMode('html'); diff --git a/extensions/html-language-features/server/src/test/completions.test.ts b/extensions/html-language-features/server/src/test/completions.test.ts index e0e3a7e22ca..9c3b8f0831b 100644 --- a/extensions/html-language-features/server/src/test/completions.test.ts +++ b/extensions/html-language-features/server/src/test/completions.test.ts @@ -6,10 +6,7 @@ import 'mocha'; import * as assert from 'assert'; import * as path from 'path'; import { URI } from 'vscode-uri'; -import { getLanguageModes } from '../modes/languageModes'; -import { WorkspaceFolder } from 'vscode-languageserver'; -import { TextDocument, CompletionList, CompletionItemKind, ClientCapabilities } from 'vscode-html-languageservice'; - +import { getLanguageModes, WorkspaceFolder, TextDocument, CompletionList, CompletionItemKind, ClientCapabilities} from '../modes/languageModes'; export interface ItemDescription { label: string; documentation?: string; diff --git a/extensions/html-language-features/server/src/test/embedded.test.ts b/extensions/html-language-features/server/src/test/embedded.test.ts index 4191cfcb9b5..525d5a59c11 100644 --- a/extensions/html-language-features/server/src/test/embedded.test.ts +++ b/extensions/html-language-features/server/src/test/embedded.test.ts @@ -5,31 +5,32 @@ import 'mocha'; import * as assert from 'assert'; import * as embeddedSupport from '../modes/embeddedSupport'; -import { getLanguageService, TextDocument } from 'vscode-html-languageservice'; +import { getLanguageService } from 'vscode-html-languageservice'; +import { TextDocument } from '../modes/languageModes'; suite('HTML Embedded Support', () => { - var htmlLanguageService = getLanguageService(); + const htmlLanguageService = getLanguageService(); function assertLanguageId(value: string, expectedLanguageId: string | undefined): void { - let offset = value.indexOf('|'); + const offset = value.indexOf('|'); value = value.substr(0, offset) + value.substr(offset + 1); - let document = TextDocument.create('test://test/test.html', 'html', 0, value); + const document = TextDocument.create('test://test/test.html', 'html', 0, value); - let position = document.positionAt(offset); + const position = document.positionAt(offset); - let docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document); - let languageId = docRegions.getLanguageAtPosition(position); + const docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document); + const languageId = docRegions.getLanguageAtPosition(position); assert.equal(languageId, expectedLanguageId); } function assertEmbeddedLanguageContent(value: string, languageId: string, expectedContent: string): void { - let document = TextDocument.create('test://test/test.html', 'html', 0, value); + const document = TextDocument.create('test://test/test.html', 'html', 0, value); - let docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document); - let content = docRegions.getEmbeddedDocument(languageId); + const docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document); + const content = docRegions.getEmbeddedDocument(languageId); assert.equal(content.getText(), expectedContent); } diff --git a/extensions/html-language-features/server/src/test/folding.test.ts b/extensions/html-language-features/server/src/test/folding.test.ts index f732a6cd72c..5384e48c6ab 100644 --- a/extensions/html-language-features/server/src/test/folding.test.ts +++ b/extensions/html-language-features/server/src/test/folding.test.ts @@ -17,13 +17,13 @@ interface ExpectedIndentRange { } function assertRanges(lines: string[], expected: ExpectedIndentRange[], message?: string, nRanges?: number): void { - let document = TextDocument.create('test://foo/bar.json', 'json', 1, lines.join('\n')); - let workspace = { + const document = TextDocument.create('test://foo/bar.json', 'json', 1, lines.join('\n')); + const workspace = { settings: {}, folders: [{ name: 'foo', uri: 'test://foo' }] }; - let languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST); - let actual = getFoldingRanges(languageModes, document, nRanges, null); + const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST); + const actual = getFoldingRanges(languageModes, document, nRanges, null); let actualRanges = []; for (let i = 0; i < actual.length; i++) { @@ -40,7 +40,7 @@ function r(startLine: number, endLine: number, kind?: string): ExpectedIndentRan suite('HTML Folding', () => { test('Embedded JavaScript', () => { - let input = [ + const input = [ /*0*/'', /*1*/'', /*2*/'', + /*6*/'', + /*7*/'', + ]; + assertTokens(input, [ + t(3, 6, 1, 'variable.declaration'), t(3, 13, 2, 'variable.declaration'), t(3, 18, 1, 'variable'), + t(4, 8, 2, 'variable') + ]); + }); + + test('function', () => { + const input = [ + /*0*/'', + /*1*/'', + /*2*/'', + /*7*/'', + /*8*/'', + ]; + assertTokens(input, [ + t(3, 11, 3, 'function.declaration'), t(3, 15, 2, 'parameter.declaration'), + t(4, 11, 3, 'function'), t(4, 15, 3, 'namespace'), t(4, 20, 3, 'member'), t(4, 24, 2, 'parameter') + ]); + }); + + +}); From 43394dee801ea2b5ea433ffffcaf20cb8da176ab Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 18 Dec 2019 17:07:09 +0100 Subject: [PATCH 137/241] more tests --- .../src/modes/javascriptSemanticTokens.ts | 11 +++++--- .../server/src/test/semanticTokens.test.ts | 27 ++++++++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index 382e6fe19eb..e3015f40e89 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -30,8 +30,11 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current if (symbol) { let typeIdx = tokenFromDeclarationMapping[symbol.valueDeclaration.kind]; let modifierSet = 0; - if (node.parent && (node.parent).name === node) { - modifierSet = TokenModifier.declaration; + if (node.parent) { + const parentTypeIdx = tokenFromDeclarationMapping[node.parent.kind]; + if (parentTypeIdx === typeIdx && (node.parent).name === node) { + modifierSet = TokenModifier.declaration; + } } if (typeIdx !== undefined) { resultTokens.push({ offset: node.getStart(), length: node.getWidth(), typeIdx, modifierSet }); @@ -130,5 +133,7 @@ const tokenFromDeclarationMapping: { [name: string]: TokenType } = { [ts.SyntaxKind.EnumDeclaration]: TokenType.enum, [ts.SyntaxKind.EnumMember]: TokenType.property, [ts.SyntaxKind.ClassDeclaration]: TokenType.property, - [ts.SyntaxKind.MethodDeclaration]: TokenType.function + [ts.SyntaxKind.MethodDeclaration]: TokenType.member, + [ts.SyntaxKind.FunctionDeclaration]: TokenType.function, + [ts.SyntaxKind.MethodSignature]: TokenType.member, }; diff --git a/extensions/html-language-features/server/src/test/semanticTokens.test.ts b/extensions/html-language-features/server/src/test/semanticTokens.test.ts index aeb9c672896..0bc0e934e9c 100644 --- a/extensions/html-language-features/server/src/test/semanticTokens.test.ts +++ b/extensions/html-language-features/server/src/test/semanticTokens.test.ts @@ -83,9 +83,34 @@ suite('JavaScript Semantic Tokens', () => { ]; assertTokens(input, [ t(3, 11, 3, 'function.declaration'), t(3, 15, 2, 'parameter.declaration'), - t(4, 11, 3, 'function'), t(4, 15, 3, 'namespace'), t(4, 20, 3, 'member'), t(4, 24, 2, 'parameter') + t(4, 11, 3, 'function'), t(4, 15, 4, 'variable'), t(4, 20, 3, 'member'), t(4, 24, 2, 'parameter') + ]); + }); + + test('members', () => { + const input = [ + /*0*/'', + /*1*/'', + /*2*/'', + /*10*/'', + /*11*/'', + ]; + assertTokens(input, [ + t(3, 8, 1, 'class.declaration'), + t(4, 11, 1, 'member.declaration'), + t(5, 4, 1, 'property.declaration'), + t(6, 4, 1, 'member.declaration'), t(6, 17, 1, 'class'), t(6, 19, 1, 'property'), + t(7, 8, 1, 'member.declaration'), ]); }); + }); From 32230fd7c3d1d59590bc056927b6404e2628b083 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 18 Dec 2019 22:15:55 +0100 Subject: [PATCH 138/241] more tests --- .../src/modes/javascriptSemanticTokens.ts | 38 ++++++++----- .../server/src/test/semanticTokens.test.ts | 53 ++++++++++++------- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index e3015f40e89..8d296801750 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -28,16 +28,26 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current if (node.kind === ts.SyntaxKind.Identifier) { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol) { - let typeIdx = tokenFromDeclarationMapping[symbol.valueDeclaration.kind]; - let modifierSet = 0; - if (node.parent) { - const parentTypeIdx = tokenFromDeclarationMapping[node.parent.kind]; - if (parentTypeIdx === typeIdx && (node.parent).name === node) { - modifierSet = TokenModifier.declaration; + const decl = symbol.valueDeclaration || symbol.declarations[0]; + if (decl) { + let typeIdx = tokenFromDeclarationMapping[decl.kind]; + let modifierSet = 0; + if (node.parent) { + const parentTypeIdx = tokenFromDeclarationMapping[node.parent.kind]; + if (parentTypeIdx === typeIdx && (node.parent).name === node) { + modifierSet = TokenModifier.declaration; + } + } + const modifiers = ts.getCombinedModifierFlags(decl); + if (modifiers & ts.ModifierFlags.Static) { + modifierSet |= TokenModifier.static; + } + if (modifiers & ts.ModifierFlags.Async) { + modifierSet |= TokenModifier.async; + } + if (typeIdx !== undefined) { + resultTokens.push({ offset: node.getStart(), length: node.getWidth(), typeIdx, modifierSet }); } - } - if (typeIdx !== undefined) { - resultTokens.push({ offset: node.getStart(), length: node.getWidth(), typeIdx, modifierSet }); } } } @@ -94,7 +104,7 @@ export function getSemanticTokenLegend() { const tokenTypes: string[] = ['class', 'enum', 'interface', 'namespace', 'parameterType', 'type', 'parameter', 'variable', 'property', 'constant', 'function', 'member']; -const tokenModifiers: string[] = ['declaration',]; +const tokenModifiers: string[] = ['declaration', 'static', 'async']; enum TokenType { 'class' = 0, @@ -112,7 +122,9 @@ enum TokenType { } enum TokenModifier { - 'declaration' = 0x01 + 'declaration' = 0x01, + 'static' = 0x02, + 'async' = 0x04, } // const tokenFromClassificationMapping: { [name: string]: TokenType } = { @@ -132,8 +144,10 @@ const tokenFromDeclarationMapping: { [name: string]: TokenType } = { [ts.SyntaxKind.ModuleDeclaration]: TokenType.namespace, [ts.SyntaxKind.EnumDeclaration]: TokenType.enum, [ts.SyntaxKind.EnumMember]: TokenType.property, - [ts.SyntaxKind.ClassDeclaration]: TokenType.property, + [ts.SyntaxKind.ClassDeclaration]: TokenType.class, [ts.SyntaxKind.MethodDeclaration]: TokenType.member, [ts.SyntaxKind.FunctionDeclaration]: TokenType.function, [ts.SyntaxKind.MethodSignature]: TokenType.member, + [ts.SyntaxKind.GetAccessor]: TokenType.property, + [ts.SyntaxKind.PropertySignature]: TokenType.property, }; diff --git a/extensions/html-language-features/server/src/test/semanticTokens.test.ts b/extensions/html-language-features/server/src/test/semanticTokens.test.ts index 0bc0e934e9c..cc5dfaad5c1 100644 --- a/extensions/html-language-features/server/src/test/semanticTokens.test.ts +++ b/extensions/html-language-features/server/src/test/semanticTokens.test.ts @@ -57,15 +57,21 @@ suite('JavaScript Semantic Tokens', () => { /*0*/'', /*1*/'', /*2*/'', - /*6*/'', - /*7*/'', + /*3*/' var x = 9, y1 = [x];', + /*4*/' try {', + /*5*/' for (const s of y1) { }', + /*6*/' } catch (e) {', + /*7*/' throw y1;', + /*8*/' }', + /*9*/'', + /*10*/'', + /*11*/'', ]; assertTokens(input, [ - t(3, 6, 1, 'variable.declaration'), t(3, 13, 2, 'variable.declaration'), t(3, 18, 1, 'variable'), - t(4, 8, 2, 'variable') + t(3, 6, 1, 'variable.declaration'), t(3, 13, 2, 'variable.declaration'), t(3, 19, 1, 'variable'), + t(5, 15, 1, 'variable.declaration'), t(5, 20, 2, 'variable'), + t(6, 11, 1, 'variable.declaration'), + t(7, 10, 2, 'variable') ]); }); @@ -77,13 +83,15 @@ suite('JavaScript Semantic Tokens', () => { /*3*/' function foo(p1) {', /*4*/' return foo(Math.abs(p1))', /*5*/' }', - /*6*/'', - /*7*/'', - /*8*/'', + /*6*/' `/${window.location}`.split("/").forEach(s => foo(s));', + /*7*/'', + /*8*/'', + /*9*/'', ]; assertTokens(input, [ t(3, 11, 3, 'function.declaration'), t(3, 15, 2, 'parameter.declaration'), - t(4, 11, 3, 'function'), t(4, 15, 4, 'variable'), t(4, 20, 3, 'member'), t(4, 24, 2, 'parameter') + t(4, 11, 3, 'function'), t(4, 15, 4, 'variable'), t(4, 20, 3, 'member'), t(4, 24, 2, 'parameter'), + t(6, 6, 6, 'variable'), t(6, 13, 8, 'property'), t(6, 24, 5, 'member'), t(6, 35, 7, 'member'), t(6, 43, 1, 'parameter.declaration'), t(6, 48, 3, 'function'), t(6, 52, 1, 'parameter') ]); }); @@ -95,19 +103,24 @@ suite('JavaScript Semantic Tokens', () => { /*3*/' class A {', /*4*/' static x = 9;', /*5*/' f = 9;', - /*6*/' m() { return A.x; };', - /*7*/' get s() { return this.f + this.m() }', - /*8*/' }', - /*9*/'', - /*10*/'', - /*11*/'', + /*6*/' async m() { return A.x + await this.m(); };', + /*7*/' get s() { return this.f; ', + /*8*/' static t() { return new A().f; };', + /*9*/' constructor() {}', + /*10*/' }', + /*11*/'', + /*12*/'', + /*13*/'', ]; + + assertTokens(input, [ t(3, 8, 1, 'class.declaration'), - t(4, 11, 1, 'member.declaration'), + t(4, 11, 1, 'property.declaration.static'), t(5, 4, 1, 'property.declaration'), - t(6, 4, 1, 'member.declaration'), t(6, 17, 1, 'class'), t(6, 19, 1, 'property'), - t(7, 8, 1, 'member.declaration'), + t(6, 10, 1, 'member.declaration.async'), t(6, 23, 1, 'class'), t(6, 25, 1, 'property.static'), t(6, 40, 1, 'member.async'), + t(7, 8, 1, 'property.declaration'), t(7, 26, 1, 'property'), + t(8, 11, 1, 'member.declaration.static'), t(8, 28, 1, 'class'), t(8, 32, 1, 'property'), ]); }); From 7d75eff9e28e3eef4dde1a9d180104105af744f3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Dec 2019 22:40:06 +0100 Subject: [PATCH 139/241] fix #87284 --- .../extensions/browser/extensionsActions.ts | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index b999f540f0b..01ea6b803b5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -709,7 +709,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); if (this.extension) { - const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction)]; + const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction), this.instantiationService.createInstance(CopyExtensionIdAction)]; if (this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); } @@ -794,8 +794,8 @@ export class InstallAnotherVersionAction extends ExtensionAction { export class ExtensionInfoAction extends ExtensionAction { - static readonly ID = 'extensions.extensionInfo'; - static readonly LABEL = localize('extensionInfoAction', "Copy Extension Information"); + static readonly ID = 'workbench.extensions.action.copyExtension'; + static readonly LABEL = localize('workbench.extensions.action.copyExtension', "Copy"); constructor( @IClipboardService private readonly clipboardService: IClipboardService @@ -826,6 +826,30 @@ export class ExtensionInfoAction extends ExtensionAction { } } +export class CopyExtensionIdAction extends ExtensionAction { + + static readonly ID = 'workbench.extensions.action.copyExtensionId'; + static readonly LABEL = localize('workbench.extensions.action.copyExtensionId', "Copy Extension Id"); + + constructor( + @IClipboardService private readonly clipboardService: IClipboardService + ) { + super(CopyExtensionIdAction.ID, CopyExtensionIdAction.LABEL); + this.update(); + } + + update(): void { + this.enabled = !!this.extension; + } + + async run(): Promise { + if (!this.extension) { + return; + } + return this.clipboardService.writeText(this.extension.identifier.id); + } +} + export class ExtensionSettingsAction extends ExtensionAction { static readonly ID = 'extensions.extensionSettings'; From 893369ff8fa8d3a39da12aa46c640dfb71bcd094 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 18 Dec 2019 14:11:57 -0800 Subject: [PATCH 140/241] update distro --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6d7aefa2974..04e101c7990 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.42.0", - "distro": "e49812b31f1c71911d0557284874d07c7b790f13", + "distro": "f2bd019844350f43b12eb22c3e68e9879e36b067", "author": { "name": "Microsoft Corporation" }, @@ -166,4 +166,4 @@ "windows-mutex": "0.3.0", "windows-process-tree": "0.2.4" } -} +} \ No newline at end of file From e74405d11443c5361c31e2bc341866d146eee206 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Wed, 18 Dec 2019 19:55:23 -0800 Subject: [PATCH 141/241] Fix #86417, align progress badge --- .../services/progress/browser/media/progressService.css | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/progress/browser/media/progressService.css b/src/vs/workbench/services/progress/browser/media/progressService.css index 4cf8a37994b..6b73a2f3a4e 100644 --- a/src/vs/workbench/services/progress/browser/media/progressService.css +++ b/src/vs/workbench/services/progress/browser/media/progressService.css @@ -17,8 +17,11 @@ width: 14px; height: 14px; position: absolute; - top: 1px; - left: 1px; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: auto; background-color: currentColor; content: ''; } From 641543b6ee8cee736d87be432e6f7254f9d7395c Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Wed, 18 Dec 2019 22:30:07 -0800 Subject: [PATCH 142/241] Consolidate codicon files to vs/base/parts/codicon --- build/gulpfile.editor.js | 2 +- build/gulpfile.vscode.js | 2 +- src/vs/base/browser/markdownRenderer.ts | 2 +- .../browser/ui/highlightedlabel/highlightedLabel.ts | 2 +- src/vs/base/common/htmlContent.ts | 2 +- .../codicon/browser}/codicon-animations.css | 0 .../codicon => parts/codicon/browser}/codicon.css | 0 .../codicon => parts/codicon/browser}/codicon.ttf | Bin .../codicon/browser}/codiconLabel.ts | 6 +++--- src/vs/base/{ => parts/codicon}/common/codicon.ts | 0 src/vs/base/{ => parts/codicon}/common/codicons.ts | 0 src/vs/base/test/common/codicon.test.ts | 2 +- .../electron-browser/issue/issueReporterMain.ts | 2 +- src/vs/editor/contrib/codelens/codelensWidget.ts | 2 +- src/vs/editor/contrib/suggest/suggestWidget.ts | 2 +- .../standalone/browser/quickOpen/quickOutline.ts | 2 +- src/vs/workbench/api/common/extHostTypes.ts | 2 +- .../browser/parts/quickinput/quickInputList.ts | 2 +- .../browser/parts/statusbar/statusbarPart.ts | 2 +- .../electron-browser/runtimeExtensionsEditor.ts | 2 +- src/vs/workbench/contrib/scm/browser/mainPane.ts | 2 +- 21 files changed, 18 insertions(+), 18 deletions(-) rename src/vs/base/{browser/ui/codiconLabel/codicon => parts/codicon/browser}/codicon-animations.css (100%) rename src/vs/base/{browser/ui/codiconLabel/codicon => parts/codicon/browser}/codicon.css (100%) rename src/vs/base/{browser/ui/codiconLabel/codicon => parts/codicon/browser}/codicon.ttf (100%) rename src/vs/base/{browser/ui/codiconLabel => parts/codicon/browser}/codiconLabel.ts (82%) rename src/vs/base/{ => parts/codicon}/common/codicon.ts (100%) rename src/vs/base/{ => parts/codicon}/common/codicons.ts (100%) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 938c82f57be..f4c32ea6c7a 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -41,7 +41,7 @@ var editorEntryPoints = [ ]; var editorResources = [ - 'out-editor-build/vs/base/browser/ui/codiconLabel/**/*.ttf' + 'out-editor-build/vs/base/parts/codicon/browser/codiconLabel/**/*.ttf' ]; var BUNDLED_FILE_HEADER = [ diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index bb504fc09e9..5465dbe692d 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -67,7 +67,7 @@ const vscodeResources = [ 'out-build/vs/base/common/performance.js', 'out-build/vs/base/node/languagePacks.js', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh,ps.sh}', - 'out-build/vs/base/browser/ui/codiconLabel/codicon/**', + 'out-build/vs/base/parts/codicon/browser/codiconLabel/**', '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/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 2918133f5e5..0f4cfe96ac5 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -15,7 +15,7 @@ import { cloneAndChange } from 'vs/base/common/objects'; import { escape } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { renderCodicons, markdownEscapeEscapedCodicons } from 'vs/base/common/codicons'; +import { renderCodicons, markdownEscapeEscapedCodicons } from 'vs/base/parts/codicon/common/codicons'; export interface MarkdownRenderOptions extends FormattedTextRenderOptions { codeBlockRenderer?: (modeId: string, value: string) => Promise; diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 163dffb2d5a..bcbd085dc95 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as objects from 'vs/base/common/objects'; -import { renderCodicons } from 'vs/base/common/codicons'; +import { renderCodicons } from 'vs/base/parts/codicon/common/codicons'; import { escape } from 'vs/base/common/strings'; export interface IHighlight { diff --git a/src/vs/base/common/htmlContent.ts b/src/vs/base/common/htmlContent.ts index be074865f2b..26c5e94dce3 100644 --- a/src/vs/base/common/htmlContent.ts +++ b/src/vs/base/common/htmlContent.ts @@ -5,7 +5,7 @@ import { equals } from 'vs/base/common/arrays'; import { UriComponents } from 'vs/base/common/uri'; -import { escapeCodicons } from 'vs/base/common/codicons'; +import { escapeCodicons } from 'vs/base/parts/codicon/common/codicons'; export interface IMarkdownString { readonly value: string; diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css b/src/vs/base/parts/codicon/browser/codicon-animations.css similarity index 100% rename from src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css rename to src/vs/base/parts/codicon/browser/codicon-animations.css diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/parts/codicon/browser/codicon.css similarity index 100% rename from src/vs/base/browser/ui/codiconLabel/codicon/codicon.css rename to src/vs/base/parts/codicon/browser/codicon.css diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/parts/codicon/browser/codicon.ttf similarity index 100% rename from src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf rename to src/vs/base/parts/codicon/browser/codicon.ttf diff --git a/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts b/src/vs/base/parts/codicon/browser/codiconLabel.ts similarity index 82% rename from src/vs/base/browser/ui/codiconLabel/codiconLabel.ts rename to src/vs/base/parts/codicon/browser/codiconLabel.ts index ccec1f655bb..f3f39b79e34 100644 --- a/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts +++ b/src/vs/base/parts/codicon/browser/codiconLabel.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./codicon/codicon'; -import 'vs/css!./codicon/codicon-animations'; +import 'vs/css!./codicon'; +import 'vs/css!./codicon-animations'; import { escape } from 'vs/base/common/strings'; -import { renderCodicons } from 'vs/base/common/codicons'; +import { renderCodicons } from 'vs/base/parts/codicon/common/codicons'; export class CodiconLabel { diff --git a/src/vs/base/common/codicon.ts b/src/vs/base/parts/codicon/common/codicon.ts similarity index 100% rename from src/vs/base/common/codicon.ts rename to src/vs/base/parts/codicon/common/codicon.ts diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/parts/codicon/common/codicons.ts similarity index 100% rename from src/vs/base/common/codicons.ts rename to src/vs/base/parts/codicon/common/codicons.ts diff --git a/src/vs/base/test/common/codicon.test.ts b/src/vs/base/test/common/codicon.test.ts index b3fdb5bde1b..5d6cc672e30 100644 --- a/src/vs/base/test/common/codicon.test.ts +++ b/src/vs/base/test/common/codicon.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { IMatch } from 'vs/base/common/filters'; -import { matchesFuzzyCodiconAware, parseCodicons, IParsedCodicons } from 'vs/base/common/codicon'; +import { matchesFuzzyCodiconAware, parseCodicons, IParsedCodicons } from 'vs/base/parts/codicon/common/codicon'; export interface ICodiconFilter { // Returns null if word doesn't match. diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 6b1bab2128b..4f3d5b9d306 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -8,7 +8,7 @@ import * as os from 'os'; import * as browser from 'vs/base/browser/browser'; import { $ } from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; -import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; +import { CodiconLabel } from 'vs/base/parts/codicon/browser/codiconLabel'; import * as collections from 'vs/base/common/collections'; import { debounce } from 'vs/base/common/decorators'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index a661af5def1..7e5e92b82a5 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -5,7 +5,7 @@ import 'vs/css!./codelensWidget'; import * as dom from 'vs/base/browser/dom'; -import { renderCodicons } from 'vs/base/common/codicons'; +import { renderCodicons } from 'vs/base/parts/codicon/common/codicons'; import { escape } from 'vs/base/common/strings'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index b3cf4a333ac..60b34bc7ade 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/suggest'; -import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded +import 'vs/base/parts/codicon/browser/codiconLabel'; // The codicon symbol styles are defined here and must be loaded import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded import * as nls from 'vs/nls'; import { createMatches } from 'vs/base/common/filters'; diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts index 772073939ef..4fea5b682b2 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./quickOutline'; -import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded +import 'vs/base/parts/codicon/browser/codiconLabel'; // The codicon symbol styles are defined here and must be loaded import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded import { CancellationToken } from 'vs/base/common/cancellation'; import { matchesFuzzy } from 'vs/base/common/filters'; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 51c2eff6d41..e5d719d625a 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -14,7 +14,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import * as vscode from 'vscode'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { escapeCodicons } from 'vs/base/common/codicons'; +import { escapeCodicons } from 'vs/base/parts/codicon/common/codicons'; function es5ClassCompat(target: Function): any { ///@ts-ignore diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index 1242effa0ff..9a5048360bf 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -11,7 +11,7 @@ import { WorkbenchList, IWorkbenchListOptions } from 'vs/platform/list/browser/l import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IMatch } from 'vs/base/common/filters'; -import { matchesFuzzyCodiconAware, parseCodicons } from 'vs/base/common/codicon'; +import { matchesFuzzyCodiconAware, parseCodicons } from 'vs/base/parts/codicon/common/codicon'; import { compareAnything } from 'vs/base/common/comparers'; import { Emitter, Event } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 425e82cf37b..6a2633dca00 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/statusbarpart'; import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { dispose, IDisposable, Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; +import { CodiconLabel } from 'vs/base/parts/codicon/browser/codiconLabel'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Part } from 'vs/workbench/browser/part'; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index d1ad6303939..0899641cc07 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -38,7 +38,7 @@ import { randomPort } from 'vs/base/node/ports'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ILabelService } from 'vs/platform/label/common/label'; -import { renderCodicons } from 'vs/base/common/codicons'; +import { renderCodicons } from 'vs/base/parts/codicon/common/codicons'; import { escape } from 'vs/base/common/strings'; import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index d5ec661f87b..bdffb1a3c68 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -25,7 +25,7 @@ import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionba import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { Command } from 'vs/editor/common/modes'; -import { renderCodicons } from 'vs/base/common/codicons'; +import { renderCodicons } from 'vs/base/parts/codicon/common/codicons'; import { escape } from 'vs/base/common/strings'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; From 5adef7f73d2388e0fced718f5bcc4f7030391c51 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 19 Dec 2019 07:58:31 +0100 Subject: [PATCH 143/241] clean up tslint.json layer rules --- tslint.json | 81 ++++++++++++++++++----------------------------------- 1 file changed, 28 insertions(+), 53 deletions(-) diff --git a/tslint.json b/tslint.json index 06adddd9c6e..33ff60030f4 100644 --- a/tslint.json +++ b/tslint.json @@ -68,7 +68,8 @@ "target": "**/vs/base/common/**", "restrictions": [ "vs/nls", - "**/vs/base/common/**" + "**/vs/base/common/**", + "**/vs/base/parts/*/common/**" ] }, { @@ -78,7 +79,8 @@ "sinon", "vs/nls", "**/vs/base/common/**", - "**/vs/base/test/common/**" + "**/vs/base/test/common/**", + "**/vs/base/parts/*/common/**" ] }, { @@ -86,14 +88,16 @@ "restrictions": [ "vs/nls", "vs/css!./**/*", - "**/vs/base/{common,browser}/**" + "**/vs/base/{common,browser}/**", + "**/vs/base/parts/*/{common,browser}/**" ] }, { "target": "**/vs/base/node/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,browser,node}/**", + "**/vs/base/{common,node}/**", + "**/vs/base/parts/*/{common,node}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -105,6 +109,7 @@ "sinon", "vs/nls", "**/vs/base/{common,browser}/**", + "**/vs/base/parts/*/{common,browser}/**", "**/vs/base/test/{common,browser}/**" ] }, @@ -129,8 +134,8 @@ "target": "**/vs/base/parts/*/node/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,browser,node}/**", - "**/vs/base/parts/*/{common,browser,node}/**", + "**/vs/base/{common,node}/**", + "**/vs/base/parts/*/{common,node}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -148,8 +153,8 @@ "target": "**/vs/base/parts/*/electron-main/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,browser,node,electron-main}/**", - "**/vs/base/parts/*/{common,browser,node,electron-main}/**", + "**/vs/base/{common,node,electron-main}/**", + "**/vs/base/parts/*/{common,node,electron-main}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -187,9 +192,9 @@ "target": "**/vs/platform/*/node/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,browser,node}/**", - "**/vs/base/parts/*/{common,browser,node}/**", - "**/vs/platform/*/{common,browser,node}/**", + "**/vs/base/{common,node}/**", + "**/vs/base/parts/*/{common,node}/**", + "**/vs/platform/*/{common,node}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -208,9 +213,9 @@ "target": "**/vs/platform/*/electron-main/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,browser,node}/**", - "**/vs/base/parts/*/{common,browser,node,electron-browser}/**", - "**/vs/platform/*/{common,browser,node,electron-main}/**", + "**/vs/base/{common,node,electron-main}/**", + "**/vs/base/parts/*/{common,node,electron-main}/**", + "**/vs/platform/*/{common,node,electron-main}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -396,6 +401,7 @@ "**/vs/editor/common/**", "**/vs/editor/contrib/*/common/**", "**/vs/workbench/api/common/**", + "**/vs/base/parts/*/common/**", "**/vs/workbench/common/**", "**/vs/workbench/services/*/common/**", "**/vs/workbench/contrib/*/common/**" @@ -508,37 +514,6 @@ "!path" // node modules (except path where we have our own impl) ] }, - { - "target": "**/vs/workbench/contrib/files/common/**", - "restrictions": [ - "vs/nls", - "**/vs/base/common/**", - "**/vs/base/parts/*/common/**", - "**/vs/platform/*/common/**", - "**/vs/editor/common/**", - "**/vs/editor/contrib/*/common/**", - "**/vs/workbench/common/**", - "**/vs/workbench/services/*/common/**", - "**/vs/workbench/contrib/files/common/**", - "assert" - ] - }, - { - "target": "**/vs/workbench/contrib/files/browser/**", - "restrictions": [ - "vs/nls", - "vs/css!./**/*", - "**/vs/base/{common,browser}/**", - "**/vs/base/parts/*/{common,browser}/**", - "**/vs/platform/*/{common,browser}/**", - "**/vs/editor/{common,browser}/**", - "**/vs/editor/contrib/**", // editor/contrib is equivalent to /browser/ by convention - "**/vs/workbench/{common,browser}/**", - "**/vs/workbench/services/*/{common,browser}/**", - "**/vs/workbench/contrib/files/{common,browser}/**", - "assert" - ] - }, { "target": "**/vs/workbench/contrib/terminal/browser/**", "restrictions": [ @@ -566,10 +541,10 @@ "target": "**/vs/code/node/**", "restrictions": [ "vs/nls", - "**/vs/base/**/{common,browser,node}/**", - "**/vs/base/parts/**/{common,browser,node}/**", - "**/vs/platform/**/{common,browser,node}/**", - "**/vs/code/**/{common,browser,node}/**", + "**/vs/base/**/{common,node}/**", + "**/vs/base/parts/**/{common,node}/**", + "**/vs/platform/**/{common,node}/**", + "**/vs/code/**/{common,node}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -589,10 +564,10 @@ "target": "**/vs/code/electron-main/**", "restrictions": [ "vs/nls", - "**/vs/base/**/{common,browser,node}/**", - "**/vs/base/parts/**/{common,browser,node,electron-main}/**", - "**/vs/platform/**/{common,browser,node,electron-main}/**", - "**/vs/code/**/{common,browser,node,electron-main}/**", + "**/vs/base/**/{common,node,electron-main}/**", + "**/vs/base/parts/**/{common,node,electron-main}/**", + "**/vs/platform/**/{common,node,electron-main}/**", + "**/vs/code/**/{common,node,electron-main}/**", "!path" // node modules (except path where we have our own impl) ] }, From 7d119bc4a0933ac8c65ed072f431fe2a7286a5fa Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 19 Dec 2019 08:06:19 +0100 Subject: [PATCH 144/241] actually fix #87258 --- src/vs/platform/windows/electron-main/windowsMainService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 6515a6325f1..ca87b9be80c 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1019,10 +1019,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic restoreWindows = 'all'; // always reopen all windows when an update was applied } else { const windowConfig = this.configurationService.getValue('window'); - restoreWindows = windowConfig?.restoreWindows || 'one'; + restoreWindows = windowConfig?.restoreWindows || 'all'; // by default restore all windows if (['all', 'folders', 'one', 'none'].indexOf(restoreWindows) === -1) { - restoreWindows = 'one'; + restoreWindows = 'all'; // by default restore all windows } } From c2e3756755028a0acee2a76f082d68a64dd69c4b Mon Sep 17 00:00:00 2001 From: Przemek Dziewa Date: Thu, 19 Dec 2019 08:36:35 +0100 Subject: [PATCH 145/241] remove unnecessary white-space: nowrap --- src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 21e81d94e82..a0c9f9e0eb8 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -97,7 +97,6 @@ white-space: pre; /* gives some degree of styling */ align-items: center; text-overflow: ellipsis; - white-space: nowrap; overflow: hidden; } From fb1fc020f4aebdf485ec7e988452e20d47840089 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 Dec 2019 10:40:46 +0100 Subject: [PATCH 146/241] :lipstick: --- .../contrib/extensions/browser/extensionsActions.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 01ea6b803b5..0945d46954e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -708,13 +708,11 @@ export class ManageExtensionAction extends ExtensionDropDownAction { groups.push([this.instantiationService.createInstance(UninstallAction)]); groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); - if (this.extension) { - const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction), this.instantiationService.createInstance(CopyExtensionIdAction)]; - if (this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { - extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); - } - groups.push(extensionActions); + const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction), this.instantiationService.createInstance(CopyExtensionIdAction)]; + if (this.extension && this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { + extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); } + groups.push(extensionActions); groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension)); From 046ae379deed45c7c1ba926755c41e1ab78c9f2c Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 19 Dec 2019 10:41:30 +0100 Subject: [PATCH 147/241] simple editor options: no indent guides. --- .../codeEditor/browser/simpleEditorOptions.ts | 3 ++- src/vs/workbench/contrib/debug/browser/repl.ts | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts index ac40c376e27..b13b0f1aa57 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts @@ -34,7 +34,8 @@ export function getSimpleEditorOptions(): IEditorOptions { acceptSuggestionOnEnter: 'smart', minimap: { enabled: false - } + }, + renderIndentGuides: false }; } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index c6b56ddcc59..68244da992c 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -6,7 +6,6 @@ import 'vs/css!vs/workbench/contrib/debug/browser/media/repl'; import * as nls from 'vs/nls'; import { URI as uri } from 'vs/base/common/uri'; -import * as errors from 'vs/base/common/errors'; import { IAction, IActionViewItem, Action } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; @@ -401,17 +400,17 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati @memoize private get refreshScheduler(): RunOnceScheduler { - return new RunOnceScheduler(() => { + return new RunOnceScheduler(async () => { if (!this.tree.getInput()) { return; } + const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight; - this.tree.updateChildren().then(() => { - if (lastElementVisible) { - // Only scroll if we were scrolled all the way down before tree refreshed #10486 - revealLastElement(this.tree); - } - }, errors.onUnexpectedError); + await this.tree.updateChildren(); + if (lastElementVisible) { + // Only scroll if we were scrolled all the way down before tree refreshed #10486 + revealLastElement(this.tree); + } }, Repl.REFRESH_DELAY); } @@ -588,6 +587,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.replElementsChangeListener.dispose(); } this.refreshScheduler.dispose(); + this.modelChangeListener.dispose(); super.dispose(); } } From 60d585662a926b6468a5a218cc84a59f9a9d0202 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 19 Dec 2019 10:47:27 +0100 Subject: [PATCH 148/241] move repl renderer related stuff to the replViewer --- .../workbench/contrib/debug/browser/repl.ts | 372 +----------------- .../contrib/debug/browser/replViewer.ts | 352 +++++++++++++++++ 2 files changed, 370 insertions(+), 354 deletions(-) create mode 100644 src/vs/workbench/contrib/debug/browser/replViewer.ts diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 68244da992c..e719772b78c 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -4,14 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!vs/workbench/contrib/debug/browser/media/repl'; -import * as nls from 'vs/nls'; import { URI as uri } from 'vs/base/common/uri'; import { IAction, IActionViewItem, Action } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import severity from 'vs/base/common/severity'; import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { ITextModel } from 'vs/editor/common/model'; import { Position } from 'vs/editor/common/core/position'; @@ -29,7 +27,7 @@ import { memoize } from 'vs/base/common/decorators'; import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { IDebugService, REPL_ID, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IExpressionContainer, IExpression, IReplElementSource, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, REPL_ID, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; import { HistoryNavigator } from 'vs/base/common/history'; import { IHistoryNavigationWidget } from 'vs/base/browser/history'; import { createAndBindHistoryNavigationWidgetScopedContextKeyService } from 'vs/platform/browser/contextScopedHistoryWidget'; @@ -42,27 +40,21 @@ import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/d import { CompletionContext, CompletionList, CompletionProviderRegistry } from 'vs/editor/common/modes'; import { first } from 'vs/base/common/arrays'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; -import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; -import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; +import { ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { renderExpressionValue, AbstractExpressionsRenderer, IExpressionTemplateData, renderVariable, IInputBoxOptions } from 'vs/workbench/contrib/debug/browser/baseDebugView'; -import { handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling'; -import { ILabelService } from 'vs/platform/label/common/label'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { removeAnsiEscapeCodes } from 'vs/base/common/strings'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { FuzzyScore } from 'vs/base/common/filters'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; +import { ReplDelegate, ReplVariablesRenderer, ReplSimpleElementsRenderer, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplRawObjectsRenderer, ReplDataSource, ReplAccessibilityProvider } from 'vs/workbench/contrib/debug/browser/replViewer'; +import { localize } from 'vs/nls'; const $ = dom.$; @@ -441,7 +433,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati // https://github.com/microsoft/TypeScript/issues/32526 new ReplDataSource() as IAsyncDataSource, { - ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel"), + ariaLabel: localize('replAriaLabel', "Read Eval Print Loop Panel"), accessibilityProvider: new ReplAccessibilityProvider(), identityProvider: { getId: (element: IReplElement) => element.getId() }, mouseSupport: false, @@ -481,7 +473,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati [IContextKeyService, scopedContextKeyService], [IPrivateReplService, this])); const options = getSimpleEditorOptions(); options.readOnly = true; - options.ariaLabel = nls.localize('debugConsole', "Debug Console"); + options.ariaLabel = localize('debugConsole', "Debug Console"); this.replInput = this.scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions()); @@ -504,18 +496,18 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private onContextMenu(e: ITreeContextMenuEvent): void { const actions: IAction[] = []; - actions.push(new Action('debug.replCopy', nls.localize('copy', "Copy"), undefined, true, async () => { + actions.push(new Action('debug.replCopy', localize('copy', "Copy"), undefined, true, async () => { const nativeSelection = window.getSelection(); if (nativeSelection) { await this.clipboardService.writeText(nativeSelection.toString()); } return Promise.resolve(); })); - actions.push(new Action('workbench.debug.action.copyAll', nls.localize('copyAll', "Copy All"), undefined, true, async () => { + actions.push(new Action('workbench.debug.action.copyAll', localize('copyAll', "Copy All"), undefined, true, async () => { await this.clipboardService.writeText(this.getVisibleContent()); return Promise.resolve(); })); - actions.push(new Action('debug.collapseRepl', nls.localize('collapse', "Collapse All"), undefined, true, () => { + actions.push(new Action('debug.collapseRepl', localize('collapse', "Collapse All"), undefined, true, () => { this.tree.collapseAll(); this.replInput.focus(); return Promise.resolve(); @@ -560,7 +552,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati }, renderOptions: { after: { - contentText: nls.localize('startDebugFirst', "Please start a debug session to evaluate expressions"), + contentText: localize('startDebugFirst', "Please start a debug session to evaluate expressions"), color: transparentForeground ? transparentForeground.toString() : undefined } } @@ -592,334 +584,6 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati } } -// Repl tree - -interface IReplEvaluationInputTemplateData { - label: HighlightedLabel; -} - -interface IReplEvaluationResultTemplateData { - value: HTMLElement; - annotation: HTMLElement; -} - -interface ISimpleReplElementTemplateData { - container: HTMLElement; - value: HTMLElement; - source: HTMLElement; - getReplElementSource(): IReplElementSource | undefined; - toDispose: IDisposable[]; -} - -interface IRawObjectReplTemplateData { - container: HTMLElement; - expression: HTMLElement; - name: HTMLElement; - value: HTMLElement; - annotation: HTMLElement; - label: HighlightedLabel; -} - -class ReplEvaluationInputsRenderer implements ITreeRenderer { - static readonly ID = 'replEvaluationInput'; - - get templateId(): string { - return ReplEvaluationInputsRenderer.ID; - } - - renderTemplate(container: HTMLElement): IReplEvaluationInputTemplateData { - dom.append(container, $('span.arrow.codicon.codicon-chevron-right')); - const input = dom.append(container, $('.expression')); - const label = new HighlightedLabel(input, false); - return { label }; - } - - renderElement(element: ITreeNode, index: number, templateData: IReplEvaluationInputTemplateData): void { - const evaluation = element.element; - templateData.label.set(evaluation.value, createMatches(element.filterData)); - } - - disposeTemplate(templateData: IReplEvaluationInputTemplateData): void { - // noop - } -} - -class ReplEvaluationResultsRenderer implements ITreeRenderer { - static readonly ID = 'replEvaluationResult'; - - get templateId(): string { - return ReplEvaluationResultsRenderer.ID; - } - - constructor(private readonly linkDetector: LinkDetector) { } - - renderTemplate(container: HTMLElement): IReplEvaluationResultTemplateData { - dom.append(container, $('span.arrow.codicon.codicon-chevron-left')); - const output = dom.append(container, $('.evaluation-result.expression')); - const value = dom.append(output, $('span.value')); - const annotation = dom.append(output, $('span')); - - return { value, annotation }; - } - - renderElement(element: ITreeNode, index: number, templateData: IReplEvaluationResultTemplateData): void { - const expression = element.element; - renderExpressionValue(expression, templateData.value, { - preserveWhitespace: !expression.hasChildren, - showHover: false, - colorize: true, - linkDetector: this.linkDetector - }); - if (expression.hasChildren) { - templateData.annotation.className = 'annotation codicon codicon-info'; - templateData.annotation.title = nls.localize('stateCapture', "Object state is captured from first evaluation"); - } - } - - disposeTemplate(templateData: IReplEvaluationResultTemplateData): void { - // noop - } -} - -class ReplSimpleElementsRenderer implements ITreeRenderer { - static readonly ID = 'simpleReplElement'; - - constructor( - private readonly linkDetector: LinkDetector, - @IEditorService private readonly editorService: IEditorService, - @ILabelService private readonly labelService: ILabelService, - @IThemeService private readonly themeService: IThemeService - ) { } - - get templateId(): string { - return ReplSimpleElementsRenderer.ID; - } - - renderTemplate(container: HTMLElement): ISimpleReplElementTemplateData { - const data: ISimpleReplElementTemplateData = Object.create(null); - dom.addClass(container, 'output'); - const expression = dom.append(container, $('.output.expression.value-and-source')); - - data.container = container; - data.value = dom.append(expression, $('span.value')); - data.source = dom.append(expression, $('.source')); - data.toDispose = []; - data.toDispose.push(dom.addDisposableListener(data.source, 'click', e => { - e.preventDefault(); - e.stopPropagation(); - const source = data.getReplElementSource(); - if (source) { - source.source.openInEditor(this.editorService, { - startLineNumber: source.lineNumber, - startColumn: source.column, - endLineNumber: source.lineNumber, - endColumn: source.column - }); - } - })); - - return data; - } - - renderElement({ element }: ITreeNode, index: number, templateData: ISimpleReplElementTemplateData): void { - // value - dom.clearNode(templateData.value); - // Reset classes to clear ansi decorations since templates are reused - templateData.value.className = 'value'; - const result = handleANSIOutput(element.value, this.linkDetector, this.themeService, element.session); - templateData.value.appendChild(result); - - dom.addClass(templateData.value, (element.severity === severity.Warning) ? 'warn' : (element.severity === severity.Error) ? 'error' : (element.severity === severity.Ignore) ? 'ignore' : 'info'); - templateData.source.textContent = element.sourceData ? `${element.sourceData.source.name}:${element.sourceData.lineNumber}` : ''; - templateData.source.title = element.sourceData ? this.labelService.getUriLabel(element.sourceData.source.uri) : ''; - templateData.getReplElementSource = () => element.sourceData; - } - - disposeTemplate(templateData: ISimpleReplElementTemplateData): void { - dispose(templateData.toDispose); - } -} - -export class ReplVariablesRenderer extends AbstractExpressionsRenderer { - - static readonly ID = 'replVariable'; - - get templateId(): string { - return ReplVariablesRenderer.ID; - } - - constructor( - private readonly linkDetector: LinkDetector, - @IDebugService debugService: IDebugService, - @IContextViewService contextViewService: IContextViewService, - @IThemeService themeService: IThemeService, - ) { - super(debugService, contextViewService, themeService); - } - - protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void { - renderVariable(expression as Variable, data, true, highlights, this.linkDetector); - } - - protected getInputBoxOptions(expression: IExpression): IInputBoxOptions | undefined { - return undefined; - } -} - -class ReplRawObjectsRenderer implements ITreeRenderer { - static readonly ID = 'rawObject'; - - constructor(private readonly linkDetector: LinkDetector) { } - - get templateId(): string { - return ReplRawObjectsRenderer.ID; - } - - renderTemplate(container: HTMLElement): IRawObjectReplTemplateData { - dom.addClass(container, 'output'); - - const expression = dom.append(container, $('.output.expression')); - const name = dom.append(expression, $('span.name')); - const label = new HighlightedLabel(name, false); - const value = dom.append(expression, $('span.value')); - const annotation = dom.append(expression, $('span')); - - return { container, expression, name, label, value, annotation }; - } - - renderElement(node: ITreeNode, index: number, templateData: IRawObjectReplTemplateData): void { - // key - const element = node.element; - templateData.label.set(element.name ? `${element.name}:` : '', createMatches(node.filterData)); - if (element.name) { - templateData.name.textContent = `${element.name}:`; - } else { - templateData.name.textContent = ''; - } - - // value - renderExpressionValue(element.value, templateData.value, { - preserveWhitespace: true, - showHover: false, - linkDetector: this.linkDetector - }); - - // annotation if any - if (element.annotation) { - templateData.annotation.className = 'annotation codicon codicon-info'; - templateData.annotation.title = element.annotation; - } else { - templateData.annotation.className = ''; - templateData.annotation.title = ''; - } - } - - disposeTemplate(templateData: IRawObjectReplTemplateData): void { - // noop - } -} - -class ReplDelegate extends CachedListVirtualDelegate { - - constructor(private configurationService: IConfigurationService) { - super(); - } - - getHeight(element: IReplElement): number { - const config = this.configurationService.getValue('debug'); - - if (!config.console.wordWrap) { - return this.estimateHeight(element, true); - } - - return super.getHeight(element); - } - - protected estimateHeight(element: IReplElement, ignoreValueLength = false): number { - const config = this.configurationService.getValue('debug'); - const rowHeight = Math.ceil(1.4 * config.console.fontSize); - const countNumberOfLines = (str: string) => Math.max(1, (str && str.match(/\r\n|\n/g) || []).length); - const hasValue = (e: any): e is { value: string } => typeof e.value === 'string'; - - // Calculate a rough overestimation for the height - // For every 30 characters increase the number of lines needed - if (hasValue(element)) { - let value = element.value; - let valueRows = countNumberOfLines(value) + (ignoreValueLength ? 0 : Math.floor(value.length / 30)); - - return valueRows * rowHeight; - } - - return rowHeight; - } - - getTemplateId(element: IReplElement): string { - if (element instanceof Variable && element.name) { - return ReplVariablesRenderer.ID; - } - if (element instanceof ReplEvaluationResult) { - return ReplEvaluationResultsRenderer.ID; - } - if (element instanceof ReplEvaluationInput) { - return ReplEvaluationInputsRenderer.ID; - } - if (element instanceof SimpleReplElement || (element instanceof Variable && !element.name)) { - // Variable with no name is a top level variable which should be rendered like a repl element #17404 - return ReplSimpleElementsRenderer.ID; - } - - return ReplRawObjectsRenderer.ID; - } - - hasDynamicHeight(element: IReplElement): boolean { - // Empty elements should not have dynamic height since they will be invisible - return element.toString().length > 0; - } -} - -function isDebugSession(obj: any): obj is IDebugSession { - return typeof obj.getReplElements === 'function'; -} - -class ReplDataSource implements IAsyncDataSource { - - hasChildren(element: IReplElement | IDebugSession): boolean { - if (isDebugSession(element)) { - return true; - } - - return !!(element).hasChildren; - } - - getChildren(element: IReplElement | IDebugSession): Promise { - if (isDebugSession(element)) { - return Promise.resolve(element.getReplElements()); - } - if (element instanceof RawObjectReplElement) { - return element.getChildren(); - } - - return (element).getChildren(); - } -} - -class ReplAccessibilityProvider implements IAccessibilityProvider { - getAriaLabel(element: IReplElement): string { - if (element instanceof Variable) { - return nls.localize('replVariableAriaLabel', "Variable {0} has value {1}, read eval print loop, debug", element.name, element.value); - } - if (element instanceof SimpleReplElement || element instanceof ReplEvaluationInput || element instanceof ReplEvaluationResult) { - return nls.localize('replValueOutputAriaLabel', "{0}, read eval print loop, debug", element.value); - } - if (element instanceof RawObjectReplElement) { - return nls.localize('replRawObjectAriaLabel', "Repl variable {0} has value {1}, read eval print loop, debug", element.name, element.value); - } - - return ''; - } -} - - // Repl actions and commands class AcceptReplInputAction extends EditorAction { @@ -927,7 +591,7 @@ class AcceptReplInputAction extends EditorAction { constructor() { super({ id: 'repl.action.acceptInput', - label: nls.localize({ key: 'actions.repl.acceptInput', comment: ['Apply input from the debug console input box'] }, "REPL Accept Input"), + label: localize({ key: 'actions.repl.acceptInput', comment: ['Apply input from the debug console input box'] }, "REPL Accept Input"), alias: 'REPL Accept Input', precondition: CONTEXT_IN_DEBUG_REPL, kbOpts: { @@ -949,7 +613,7 @@ class FilterReplAction extends EditorAction { constructor() { super({ id: 'repl.action.filter', - label: nls.localize('repl.action.filter', "REPL Focus Content to Filter"), + label: localize('repl.action.filter', "REPL Focus Content to Filter"), alias: 'REPL Filter', precondition: CONTEXT_IN_DEBUG_REPL, kbOpts: { @@ -971,7 +635,7 @@ class ReplCopyAllAction extends EditorAction { constructor() { super({ id: 'repl.action.copyAll', - label: nls.localize('actions.repl.copyAll', "Debug: Console Copy All"), + label: localize('actions.repl.copyAll', "Debug: Console Copy All"), alias: 'Debug Console Copy All', precondition: CONTEXT_IN_DEBUG_REPL, }); @@ -1004,7 +668,7 @@ class SelectReplActionViewItem extends FocusSessionActionViewItem { class SelectReplAction extends Action { static readonly ID = 'workbench.action.debug.selectRepl'; - static readonly LABEL = nls.localize('selectRepl', "Select Debug Console"); + static readonly LABEL = localize('selectRepl', "Select Debug Console"); constructor(id: string, label: string, @IDebugService private readonly debugService: IDebugService, @@ -1027,7 +691,7 @@ class SelectReplAction extends Action { export class ClearReplAction extends Action { static readonly ID = 'workbench.debug.panel.action.clearReplAction'; - static readonly LABEL = nls.localize('clearRepl', "Clear Console"); + static readonly LABEL = localize('clearRepl', "Clear Console"); constructor(id: string, label: string, @IPanelService private readonly panelService: IPanelService @@ -1038,6 +702,6 @@ export class ClearReplAction extends Action { async run(): Promise { const repl = this.panelService.openPanel(REPL_ID); await repl.clearRepl(); - aria.status(nls.localize('debugConsoleCleared', "Debug console was cleared")); + aria.status(localize('debugConsoleCleared', "Debug console was cleared")); } } diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts new file mode 100644 index 00000000000..11e4c1ddf55 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts @@ -0,0 +1,352 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import severity from 'vs/base/common/severity'; +import * as dom from 'vs/base/browser/dom'; +import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; +import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { renderExpressionValue, AbstractExpressionsRenderer, IExpressionTemplateData, renderVariable, IInputBoxOptions } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { FuzzyScore, createMatches } from 'vs/base/common/filters'; +import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { IReplElementSource, IDebugService, IExpression, IReplElement, IDebugConfiguration, IDebugSession, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { localize } from 'vs/nls'; + +const $ = dom.$; + +interface IReplEvaluationInputTemplateData { + label: HighlightedLabel; +} + +interface IReplEvaluationResultTemplateData { + value: HTMLElement; + annotation: HTMLElement; +} + +interface ISimpleReplElementTemplateData { + container: HTMLElement; + value: HTMLElement; + source: HTMLElement; + getReplElementSource(): IReplElementSource | undefined; + toDispose: IDisposable[]; +} + +interface IRawObjectReplTemplateData { + container: HTMLElement; + expression: HTMLElement; + name: HTMLElement; + value: HTMLElement; + annotation: HTMLElement; + label: HighlightedLabel; +} + +export class ReplEvaluationInputsRenderer implements ITreeRenderer { + static readonly ID = 'replEvaluationInput'; + + get templateId(): string { + return ReplEvaluationInputsRenderer.ID; + } + + renderTemplate(container: HTMLElement): IReplEvaluationInputTemplateData { + dom.append(container, $('span.arrow.codicon.codicon-chevron-right')); + const input = dom.append(container, $('.expression')); + const label = new HighlightedLabel(input, false); + return { label }; + } + + renderElement(element: ITreeNode, index: number, templateData: IReplEvaluationInputTemplateData): void { + const evaluation = element.element; + templateData.label.set(evaluation.value, createMatches(element.filterData)); + } + + disposeTemplate(templateData: IReplEvaluationInputTemplateData): void { + // noop + } +} + +export class ReplEvaluationResultsRenderer implements ITreeRenderer { + static readonly ID = 'replEvaluationResult'; + + get templateId(): string { + return ReplEvaluationResultsRenderer.ID; + } + + constructor(private readonly linkDetector: LinkDetector) { } + + renderTemplate(container: HTMLElement): IReplEvaluationResultTemplateData { + dom.append(container, $('span.arrow.codicon.codicon-chevron-left')); + const output = dom.append(container, $('.evaluation-result.expression')); + const value = dom.append(output, $('span.value')); + const annotation = dom.append(output, $('span')); + + return { value, annotation }; + } + + renderElement(element: ITreeNode, index: number, templateData: IReplEvaluationResultTemplateData): void { + const expression = element.element; + renderExpressionValue(expression, templateData.value, { + preserveWhitespace: !expression.hasChildren, + showHover: false, + colorize: true, + linkDetector: this.linkDetector + }); + if (expression.hasChildren) { + templateData.annotation.className = 'annotation codicon codicon-info'; + templateData.annotation.title = localize('stateCapture', "Object state is captured from first evaluation"); + } + } + + disposeTemplate(templateData: IReplEvaluationResultTemplateData): void { + // noop + } +} + +export class ReplSimpleElementsRenderer implements ITreeRenderer { + static readonly ID = 'simpleReplElement'; + + constructor( + private readonly linkDetector: LinkDetector, + @IEditorService private readonly editorService: IEditorService, + @ILabelService private readonly labelService: ILabelService, + @IThemeService private readonly themeService: IThemeService + ) { } + + get templateId(): string { + return ReplSimpleElementsRenderer.ID; + } + + renderTemplate(container: HTMLElement): ISimpleReplElementTemplateData { + const data: ISimpleReplElementTemplateData = Object.create(null); + dom.addClass(container, 'output'); + const expression = dom.append(container, $('.output.expression.value-and-source')); + + data.container = container; + data.value = dom.append(expression, $('span.value')); + data.source = dom.append(expression, $('.source')); + data.toDispose = []; + data.toDispose.push(dom.addDisposableListener(data.source, 'click', e => { + e.preventDefault(); + e.stopPropagation(); + const source = data.getReplElementSource(); + if (source) { + source.source.openInEditor(this.editorService, { + startLineNumber: source.lineNumber, + startColumn: source.column, + endLineNumber: source.lineNumber, + endColumn: source.column + }); + } + })); + + return data; + } + + renderElement({ element }: ITreeNode, index: number, templateData: ISimpleReplElementTemplateData): void { + // value + dom.clearNode(templateData.value); + // Reset classes to clear ansi decorations since templates are reused + templateData.value.className = 'value'; + const result = handleANSIOutput(element.value, this.linkDetector, this.themeService, element.session); + templateData.value.appendChild(result); + + dom.addClass(templateData.value, (element.severity === severity.Warning) ? 'warn' : (element.severity === severity.Error) ? 'error' : (element.severity === severity.Ignore) ? 'ignore' : 'info'); + templateData.source.textContent = element.sourceData ? `${element.sourceData.source.name}:${element.sourceData.lineNumber}` : ''; + templateData.source.title = element.sourceData ? this.labelService.getUriLabel(element.sourceData.source.uri) : ''; + templateData.getReplElementSource = () => element.sourceData; + } + + disposeTemplate(templateData: ISimpleReplElementTemplateData): void { + dispose(templateData.toDispose); + } +} + +export class ReplVariablesRenderer extends AbstractExpressionsRenderer { + + static readonly ID = 'replVariable'; + + get templateId(): string { + return ReplVariablesRenderer.ID; + } + + constructor( + private readonly linkDetector: LinkDetector, + @IDebugService debugService: IDebugService, + @IContextViewService contextViewService: IContextViewService, + @IThemeService themeService: IThemeService, + ) { + super(debugService, contextViewService, themeService); + } + + protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void { + renderVariable(expression as Variable, data, true, highlights, this.linkDetector); + } + + protected getInputBoxOptions(expression: IExpression): IInputBoxOptions | undefined { + return undefined; + } +} + +export class ReplRawObjectsRenderer implements ITreeRenderer { + static readonly ID = 'rawObject'; + + constructor(private readonly linkDetector: LinkDetector) { } + + get templateId(): string { + return ReplRawObjectsRenderer.ID; + } + + renderTemplate(container: HTMLElement): IRawObjectReplTemplateData { + dom.addClass(container, 'output'); + + const expression = dom.append(container, $('.output.expression')); + const name = dom.append(expression, $('span.name')); + const label = new HighlightedLabel(name, false); + const value = dom.append(expression, $('span.value')); + const annotation = dom.append(expression, $('span')); + + return { container, expression, name, label, value, annotation }; + } + + renderElement(node: ITreeNode, index: number, templateData: IRawObjectReplTemplateData): void { + // key + const element = node.element; + templateData.label.set(element.name ? `${element.name}:` : '', createMatches(node.filterData)); + if (element.name) { + templateData.name.textContent = `${element.name}:`; + } else { + templateData.name.textContent = ''; + } + + // value + renderExpressionValue(element.value, templateData.value, { + preserveWhitespace: true, + showHover: false, + linkDetector: this.linkDetector + }); + + // annotation if any + if (element.annotation) { + templateData.annotation.className = 'annotation codicon codicon-info'; + templateData.annotation.title = element.annotation; + } else { + templateData.annotation.className = ''; + templateData.annotation.title = ''; + } + } + + disposeTemplate(templateData: IRawObjectReplTemplateData): void { + // noop + } +} + +export class ReplDelegate extends CachedListVirtualDelegate { + + constructor(private configurationService: IConfigurationService) { + super(); + } + + getHeight(element: IReplElement): number { + const config = this.configurationService.getValue('debug'); + + if (!config.console.wordWrap) { + return this.estimateHeight(element, true); + } + + return super.getHeight(element); + } + + protected estimateHeight(element: IReplElement, ignoreValueLength = false): number { + const config = this.configurationService.getValue('debug'); + const rowHeight = Math.ceil(1.4 * config.console.fontSize); + const countNumberOfLines = (str: string) => Math.max(1, (str && str.match(/\r\n|\n/g) || []).length); + const hasValue = (e: any): e is { value: string } => typeof e.value === 'string'; + + // Calculate a rough overestimation for the height + // For every 30 characters increase the number of lines needed + if (hasValue(element)) { + let value = element.value; + let valueRows = countNumberOfLines(value) + (ignoreValueLength ? 0 : Math.floor(value.length / 30)); + + return valueRows * rowHeight; + } + + return rowHeight; + } + + getTemplateId(element: IReplElement): string { + if (element instanceof Variable && element.name) { + return ReplVariablesRenderer.ID; + } + if (element instanceof ReplEvaluationResult) { + return ReplEvaluationResultsRenderer.ID; + } + if (element instanceof ReplEvaluationInput) { + return ReplEvaluationInputsRenderer.ID; + } + if (element instanceof SimpleReplElement || (element instanceof Variable && !element.name)) { + // Variable with no name is a top level variable which should be rendered like a repl element #17404 + return ReplSimpleElementsRenderer.ID; + } + + return ReplRawObjectsRenderer.ID; + } + + hasDynamicHeight(element: IReplElement): boolean { + // Empty elements should not have dynamic height since they will be invisible + return element.toString().length > 0; + } +} + +function isDebugSession(obj: any): obj is IDebugSession { + return typeof obj.getReplElements === 'function'; +} + +export class ReplDataSource implements IAsyncDataSource { + + hasChildren(element: IReplElement | IDebugSession): boolean { + if (isDebugSession(element)) { + return true; + } + + return !!(element).hasChildren; + } + + getChildren(element: IReplElement | IDebugSession): Promise { + if (isDebugSession(element)) { + return Promise.resolve(element.getReplElements()); + } + if (element instanceof RawObjectReplElement) { + return element.getChildren(); + } + + return (element).getChildren(); + } +} + +export class ReplAccessibilityProvider implements IAccessibilityProvider { + getAriaLabel(element: IReplElement): string { + if (element instanceof Variable) { + return localize('replVariableAriaLabel', "Variable {0} has value {1}, read eval print loop, debug", element.name, element.value); + } + if (element instanceof SimpleReplElement || element instanceof ReplEvaluationInput || element instanceof ReplEvaluationResult) { + return localize('replValueOutputAriaLabel', "{0}, read eval print loop, debug", element.value); + } + if (element instanceof RawObjectReplElement) { + return localize('replRawObjectAriaLabel', "Repl variable {0} has value {1}, read eval print loop, debug", element.name, element.value); + } + + return ''; + } +} From 64723126f947e785c3fd5c1c348cbcd3dd02fd89 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 Dec 2019 10:51:18 +0100 Subject: [PATCH 149/241] :lipstick: --- .../contrib/extensions/browser/extensionsActions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 0945d46954e..e279bd9087e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -708,7 +708,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { groups.push([this.instantiationService.createInstance(UninstallAction)]); groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); - const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction), this.instantiationService.createInstance(CopyExtensionIdAction)]; + const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(CopyExtensionInfoAction), this.instantiationService.createInstance(CopyExtensionIdAction)]; if (this.extension && this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); } @@ -790,7 +790,7 @@ export class InstallAnotherVersionAction extends ExtensionAction { } } -export class ExtensionInfoAction extends ExtensionAction { +export class CopyExtensionInfoAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.copyExtension'; static readonly LABEL = localize('workbench.extensions.action.copyExtension', "Copy"); @@ -798,7 +798,7 @@ export class ExtensionInfoAction extends ExtensionAction { constructor( @IClipboardService private readonly clipboardService: IClipboardService ) { - super(ExtensionInfoAction.ID, ExtensionInfoAction.LABEL); + super(CopyExtensionInfoAction.ID, CopyExtensionInfoAction.LABEL); this.update(); } From f0f0000aa7ad7694342667635c82fa0e88f66022 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 19 Dec 2019 11:15:42 +0100 Subject: [PATCH 150/241] debug configurations: cleaner selection of configurations fixes #87324 --- .../browser/debugConfigurationManager.ts | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index c653b018cb3..763ad95dd10 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -68,8 +68,8 @@ export class ConfigurationManager implements IConfigurationManager { @ICommandService private readonly commandService: ICommandService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, - @IContextKeyService contextKeyService: IContextKeyService, - @IHistoryService historyService: IHistoryService + @IHistoryService private readonly historyService: IHistoryService, + @IContextKeyService contextKeyService: IContextKeyService ) { this.configProviders = []; this.adapterDescriptorFactories = []; @@ -83,13 +83,7 @@ export class ConfigurationManager implements IConfigurationManager { if (previousSelectedLaunch && previousSelectedLaunch.getConfigurationNames().length) { this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE)); } else if (this.launches.length > 0) { - const rootUri = historyService.getLastActiveWorkspaceRoot(); - let launch = this.getLaunch(rootUri); - if (!launch || launch.getConfigurationNames().length === 0) { - launch = first(this.launches, l => !!(l && l.getConfigurationNames().length), launch) || this.launches[0]; - } - - this.selectConfiguration(launch); + this.selectConfiguration(undefined); } } @@ -286,13 +280,13 @@ export class ConfigurationManager implements IConfigurationManager { this.toDispose.push(Event.any(this.contextService.onDidChangeWorkspaceFolders, this.contextService.onDidChangeWorkbenchState)(() => { this.initLaunches(); - const toSelect = this.selectedLaunch || (this.launches.length > 0 ? this.launches[0] : undefined); - this.selectConfiguration(toSelect); + this.selectConfiguration(undefined); this.setCompoundSchemaValues(); })); this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('launch')) { - this.selectConfiguration(this.selectedLaunch); + // A change happen in the launch.json. If there is already a launch configuration selected, do not change the selection. + this.selectConfiguration(undefined); this.setCompoundSchemaValues(); } })); @@ -306,7 +300,7 @@ export class ConfigurationManager implements IConfigurationManager { this.launches.push(this.instantiationService.createInstance(UserLaunch)); if (this.selectedLaunch && this.launches.indexOf(this.selectedLaunch) === -1) { - this.setSelectedLaunch(undefined); + this.selectConfiguration(undefined); } } @@ -355,10 +349,23 @@ export class ConfigurationManager implements IConfigurationManager { } selectConfiguration(launch: ILaunch | undefined, name?: string): void { + if (typeof launch === 'undefined') { + const rootUri = this.historyService.getLastActiveWorkspaceRoot(); + launch = this.getLaunch(rootUri); + if (!launch || launch.getConfigurationNames().length === 0) { + launch = first(this.launches, l => !!(l && l.getConfigurationNames().length), launch) || this.launches[0]; + } + } + const previousLaunch = this.selectedLaunch; const previousName = this.selectedName; + this.selectedLaunch = launch; - this.setSelectedLaunch(launch); + if (this.selectedLaunch) { + this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE); + } else { + this.storageService.remove(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); + } const names = launch ? launch.getConfigurationNames() : []; if (name && names.indexOf(name) >= 0) { this.setSelectedLaunchName(name); @@ -467,16 +474,6 @@ export class ConfigurationManager implements IConfigurationManager { } } - private setSelectedLaunch(selectedLaunch: ILaunch | undefined): void { - this.selectedLaunch = selectedLaunch; - - if (this.selectedLaunch) { - this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE); - } else { - this.storageService.remove(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); - } - } - dispose(): void { this.toDispose = dispose(this.toDispose); } From c53d7b78822066976cabce64bbc42a729cff9447 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 19 Dec 2019 11:39:00 +0100 Subject: [PATCH 151/241] debt - ensure there is only 1 editor service instantiated --- .../contrib/files/browser/explorerViewlet.ts | 11 +- .../services/editor/browser/editorService.ts | 129 ++++++++++++------ .../editor/test/browser/editorService.test.ts | 3 +- .../workbench/test/workbenchTestServices.ts | 15 +- 4 files changed, 101 insertions(+), 57 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 083cd8bd13d..a96695b8c01 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -33,7 +33,6 @@ import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/vi import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { withUndefinedAsNull } from 'vs/base/common/types'; import { Viewlet } from 'vs/workbench/browser/viewlet'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -202,8 +201,7 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { // without causing the animation in the opened editors view to kick in and change scroll position. // We try to be smart and only use the delay if we recognize that the user action is likely to cause // a new entry in the opened editors view. - const delegatingEditorService = this.instantiationService.createInstance(DelegatingEditorService); - delegatingEditorService.setEditorOpenHandler(async (delegate, group, editor, options): Promise => { + const delegatingEditorService = this.instantiationService.createInstance(DelegatingEditorService, async (delegate, group, editor, options): Promise => { let openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { let delay = 0; @@ -219,19 +217,16 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { openEditorsView.setStructuralRefreshDelay(delay); } - let openedEditor: IEditor | undefined; try { - openedEditor = await delegate(group, editor, options); + return await delegate(group, editor, options); } catch (error) { - // ignore + return null; // ignore } finally { const openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { openEditorsView.setStructuralRefreshDelay(0); } } - - return withUndefinedAsNull(openedEditor); }); const explorerInstantiator = this.instantiationService.createChild(new ServiceCollection([IEditorService, delegatingEditorService])); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index d64e64a3564..987883a3fb4 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -228,6 +228,17 @@ export class EditorService extends Disposable implements EditorServiceImpl { openEditor(editor: IResourceDiffInput, group?: OpenInEditorGroup): Promise; openEditor(editor: IResourceSideBySideInput, group?: OpenInEditorGroup): Promise; async openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise { + const result = this.doResolveEditorOpenRequest(editor, optionsOrGroup, group); + if (result) { + const [resolvedGroup, resolvedEditor, resolvedOptions] = result; + + return withNullAsUndefined(await resolvedGroup.openEditor(resolvedEditor, resolvedOptions)); + } + + return undefined; + } + + doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined { let resolvedGroup: IEditorGroup | undefined; let candidateGroup: OpenInEditorGroup | undefined; @@ -275,16 +286,12 @@ export class EditorService extends Disposable implements EditorServiceImpl { typedOptions.overwrite({ activation: EditorActivation.ACTIVATE }); } - return this.doOpenEditor(resolvedGroup, typedEditor, typedOptions); + return [resolvedGroup, typedEditor, typedOptions]; } return undefined; } - protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { - return withNullAsUndefined(await group.openEditor(editor, options)); - } - private findTargetGroup(input: IEditorInput, options?: IEditorOptions, group?: OpenInEditorGroup): IEditorGroup { let targetGroup: IEditorGroup | undefined; @@ -756,7 +763,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { export interface IEditorOpenHandler { ( - delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise, + delegate: (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => Promise, group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions @@ -765,51 +772,87 @@ export interface IEditorOpenHandler { /** * The delegating workbench editor service can be used to override the behaviour of the openEditor() - * method by providing a IEditorOpenHandler. + * method by providing a IEditorOpenHandler. All calls are being delegated to the existing editor + * service otherwise. */ -export class DelegatingEditorService extends EditorService { - private editorOpenHandler: IEditorOpenHandler | undefined; +export class DelegatingEditorService implements IEditorService { + + _serviceBrand: undefined; constructor( - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, - @IInstantiationService instantiationService: IInstantiationService, - @ILabelService labelService: ILabelService, - @IFileService fileService: IFileService, - @IConfigurationService configurationService: IConfigurationService - ) { - super( - editorGroupService, - untitledTextEditorService, - instantiationService, - labelService, - fileService, - configurationService - ); - } + private editorOpenHandler: IEditorOpenHandler, + @IEditorService private editorService: EditorService + ) { } - setEditorOpenHandler(handler: IEditorOpenHandler): void { - this.editorOpenHandler = handler; - } + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceInput | IUntitledTextResourceInput, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceDiffInput, group?: OpenInEditorGroup): Promise; + openEditor(editor: IResourceSideBySideInput, group?: OpenInEditorGroup): Promise; + async openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | OpenInEditorGroup, group?: OpenInEditorGroup): Promise { + const result = this.editorService.doResolveEditorOpenRequest(editor, optionsOrGroup, group); + if (result) { + const [resolvedGroup, resolvedEditor, resolvedOptions] = result; - protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { - if (!this.editorOpenHandler) { - return super.doOpenEditor(group, editor, options); + // Pass on to editor open handler + const control = await this.editorOpenHandler( + (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => group.openEditor(editor, options), + resolvedGroup, + resolvedEditor, + resolvedOptions + ); + + if (control) { + return control; // the opening was handled, so return early + } + + return withNullAsUndefined(await resolvedGroup.openEditor(resolvedEditor, resolvedOptions)); } - const control = await this.editorOpenHandler( - (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => super.doOpenEditor(group, editor, options), - group, - editor, - options - ); - - if (control) { - return control; // the opening was handled, so return early - } - - return super.doOpenEditor(group, editor, options); + return undefined; } + + //#region Delegate to IEditorService + + get onDidActiveEditorChange(): Event { return this.editorService.onDidActiveEditorChange; } + get onDidVisibleEditorsChange(): Event { return this.editorService.onDidVisibleEditorsChange; } + + get activeEditor(): IEditorInput | undefined { return this.editorService.activeEditor; } + get activeControl(): IVisibleEditor | undefined { return this.editorService.activeControl; } + get activeTextEditorWidget(): ICodeEditor | IDiffEditor | undefined { return this.editorService.activeTextEditorWidget; } + get visibleEditors(): ReadonlyArray { return this.editorService.visibleEditors; } + get visibleControls(): ReadonlyArray { return this.editorService.visibleControls; } + get visibleTextEditorWidgets(): ReadonlyArray { return this.editorService.visibleTextEditorWidgets; } + get editors(): ReadonlyArray { return this.editorService.editors; } + + openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise; + openEditors(editors: IResourceEditor[], group?: OpenInEditorGroup): Promise; + openEditors(editors: Array, group?: OpenInEditorGroup): Promise { + return this.editorService.openEditors(editors, group); + } + + replaceEditors(editors: IResourceEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise; + replaceEditors(editors: IEditorReplacement[], group: IEditorGroup | GroupIdentifier): Promise; + replaceEditors(editors: Array, group: IEditorGroup | GroupIdentifier): Promise { + return this.editorService.replaceEditors(editors as IResourceEditorReplacement[] /* TS fail */, group); + } + + isOpen(editor: IEditorInput | IResourceInput | IUntitledTextResourceInput): boolean { return this.editorService.isOpen(editor); } + + getOpened(editor: IResourceInput | IUntitledTextResourceInput): IEditorInput | undefined { return this.editorService.getOpened(editor); } + + overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { return this.editorService.overrideOpenEditor(handler); } + + invokeWithinEditorContext(fn: (accessor: ServicesAccessor) => T): T { return this.editorService.invokeWithinEditorContext(fn); } + + createInput(input: IResourceEditor): IEditorInput { return this.editorService.createInput(input); } + + save(editors: IEditorIdentifier | IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { return this.editorService.save(editors, options); } + saveAll(options?: ISaveAllEditorsOptions): Promise { return this.editorService.saveAll(options); } + + revert(editors: IEditorIdentifier | IEditorIdentifier[], options?: IRevertOptions): Promise { return this.editorService.revert(editors, options); } + revertAll(options?: IRevertAllEditorsOptions): Promise { return this.editorService.revertAll(options); } + + //#endregion } registerSingleton(IEditorService, EditorService); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 2296fed1da8..11aab22bc60 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -364,8 +364,7 @@ suite('EditorService', () => { const ed = instantiationService.createInstance(MyEditor, 'my.editor'); const inp = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.parse('my://resource-delegate'), undefined); - const delegate = instantiationService.createInstance(DelegatingEditorService); - delegate.setEditorOpenHandler((delegate, group, input) => { + const delegate = instantiationService.createInstance(DelegatingEditorService, (delegate, group, input) => { assert.strictEqual(input, inp); done(); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index f71d7d3d306..1f1fb6245e5 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -11,7 +11,7 @@ import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView, IEditorGroupsAccessor } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; @@ -57,7 +57,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/browser/decorations'; import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, EditorsOrder, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, IOpenEditorOverrideHandler, IVisibleEditor, ISaveEditorsOptions, IRevertAllEditorsOptions } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IOpenEditorOverrideHandler, IVisibleEditor, ISaveEditorsOptions, IRevertAllEditorsOptions, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; @@ -316,9 +316,10 @@ export function workbenchInstantiationService(): ITestInstantiationService { instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); instantiationService.stub(IThemeService, new TestThemeService()); instantiationService.stub(ILogService, new NullLogService()); - instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService([new TestEditorGroupView(0)])); + const editorGroupService = new TestEditorGroupsService([new TestEditorGroupView(0)]); + instantiationService.stub(IEditorGroupsService, editorGroupService); instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService)); - const editorService = new TestEditorService(); + const editorService = new TestEditorService(editorGroupService); instantiationService.stub(IEditorService, editorService); instantiationService.stub(ICodeEditorService, new TestCodeEditorService()); instantiationService.stub(IViewletService, new TestViewletService()); @@ -910,6 +911,8 @@ export class TestEditorService implements EditorServiceImpl { visibleTextEditorWidgets = []; visibleEditors: ReadonlyArray = []; + constructor(private editorGroupService: IEditorGroupsService) { } + overrideOpenEditor(_handler: IOpenEditorOverrideHandler): IDisposable { return toDisposable(() => undefined); } @@ -918,6 +921,10 @@ export class TestEditorService implements EditorServiceImpl { throw new Error('not implemented'); } + doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditor): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined { + return [this.editorGroupService.activeGroup, editor as EditorInput, undefined]; + } + openEditors(_editors: any, _group?: any): Promise { throw new Error('not implemented'); } From c7da14d06f4d05a44a6cf21252efc496db1f4ac1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 19 Dec 2019 11:49:45 +0100 Subject: [PATCH 152/241] fix compile --- src/vs/workbench/test/workbenchTestServices.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 1f1fb6245e5..3d43c156d2c 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -911,7 +911,7 @@ export class TestEditorService implements EditorServiceImpl { visibleTextEditorWidgets = []; visibleEditors: ReadonlyArray = []; - constructor(private editorGroupService: IEditorGroupsService) { } + constructor(private editorGroupService?: IEditorGroupsService) { } overrideOpenEditor(_handler: IOpenEditorOverrideHandler): IDisposable { return toDisposable(() => undefined); @@ -922,6 +922,10 @@ export class TestEditorService implements EditorServiceImpl { } doResolveEditorOpenRequest(editor: IEditorInput | IResourceEditor): [IEditorGroup, EditorInput, EditorOptions | undefined] | undefined { + if (!this.editorGroupService) { + return undefined; + } + return [this.editorGroupService.activeGroup, editor as EditorInput, undefined]; } From 494a6bcfb91e8f118813b84daa06c30bb9023e0c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 Dec 2019 11:50:44 +0100 Subject: [PATCH 153/241] polish --- .../themes/browser/workbenchThemeService.ts | 226 ++++++++---------- .../themes/common/workbenchThemeService.ts | 6 +- 2 files changed, 102 insertions(+), 130 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 40adde676cf..a40550eec3b 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations, DETECT_AS_SETTING, COLOR_THEME_DARK_SETTING, COLOR_THEME_LIGHT_SETTING } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -14,7 +14,7 @@ import * as errors from 'vs/base/common/errors'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; -import { ITheme, Extensions as ThemingExtensions, IThemingRegistry, DARK, LIGHT } from 'vs/platform/theme/common/themeService'; +import { ITheme, Extensions as ThemingExtensions, IThemingRegistry, ThemeType, LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -35,19 +35,21 @@ import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +// settings + +const PREFERRED_DARK_THEME_SETTING = 'workbench.preferredDarkColorTheme'; +const PREFERRED_LIGHT_THEME_SETTING = 'workbench.preferredLightColorTheme'; +const PREFERRED_HC_THEME_SETTING = 'workbench.preferredHighContrastColorTheme'; +const DETECT_COLOR_SCHEME_SETTING = 'workbench.autoDetectColorScheme'; +const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; + // implementation const DEFAULT_THEME_ID = 'vs-dark vscode-theme-defaults-themes-dark_plus-json'; const DEFAULT_THEME_SETTING_VALUE = 'Default Dark+'; const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark+'; const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light+'; -const DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE = true; - -enum ColorScheme { - LIGHT = 'light', - DARK = 'dark', - NO_PREFERENCE = 'no-preference' -} +const DEFAULT_THEME_HC_SETTING_VALUE = 'Default High Contrast'; const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; const PERSISTED_ICON_THEME_STORAGE_KEY = 'iconThemeData'; @@ -81,7 +83,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private colorThemeStore: ColorThemeStore; private currentColorTheme: ColorThemeData; - private autoSwitchColorTheme: MediaQueryList | undefined; private container: HTMLElement; private readonly onColorThemeChange: Emitter; private watchedColorThemeLocation: URI | undefined; @@ -124,7 +125,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.iconThemeStore = new FileIconThemeStore(extensionService); this.onColorThemeChange = new Emitter({ leakWarningThreshold: 400 }); - this.onPreferColorSchemeChange = this.onPreferColorSchemeChange.bind(this); this.currentColorTheme = ColorThemeData.createUnloadedTheme(''); this.currentIconTheme = FileIconThemeData.createUnloadedTheme(''); @@ -159,7 +159,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.initialize().then(undefined, errors.onUnexpectedError).then(_ => { this.installConfigurationListener(); - this.installColorThemeSwitch(); + this.installPreferredSchemeListener(); }); let prevColorId: string | undefined = undefined; @@ -260,76 +260,43 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } private initialize(): Promise<[IColorTheme | null, IFileIconTheme | null]> { - let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); - let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); - - let detectThemeAutoSwitch = this.configurationService.getValue(DETECT_AS_SETTING); - if (detectThemeAutoSwitch) { - colorThemeSetting = this.getPreferredTheme(); - } - - let detectHCThemeSetting = this.configurationService.getValue(DETECT_HC_SETTING); - if (this.environmentService.configuration.highContrast && detectHCThemeSetting) { - colorThemeSetting = HC_THEME_ID; - } + const colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); + const iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); const extDevLocs = this.environmentService.extensionDevelopmentLocationURI; - let uri: URI | undefined; - if (extDevLocs && extDevLocs.length > 0) { - // if there are more than one ext dev paths, use first - uri = extDevLocs[0]; - } - return Promise.all([ - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { - return this.colorThemeStore.findThemeDataByParentLocation(uri).then(devThemes => { - if (devThemes.length) { - return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); - } else { - return this.setColorTheme(theme && theme.id, undefined); - } - }); - }), - this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => { - return this.iconThemeStore.findThemeDataByParentLocation(uri).then(devThemes => { - if (devThemes.length) { - return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); - } else { - return this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined); - } - }); - }), - ]); - } - - private installColorThemeSwitch() { - console.log('INSTALL'); - this.autoSwitchColorTheme = window.matchMedia('(prefers-color-scheme: dark)'); - this.autoSwitchColorTheme.addListener(this.onPreferColorSchemeChange); - console.log('INSTALLED'); - } - - private deinstallColorThemeSwitch() { - console.log('DEINSTALL'); - if (this.autoSwitchColorTheme) { - this.autoSwitchColorTheme.removeListener(this.onPreferColorSchemeChange); - this.configurationService.updateValue(DETECT_AS_SETTING, false); - console.log('DEINSTALLED'); - } - } - - private onPreferColorSchemeChange({ matches }: MediaQueryListEvent) { - console.log('onPreferColorSchemeChange', matches); - let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_LIGHT_SETTING); - if (matches) { - // prefers dark mode - colorThemeSetting = this.configurationService.getValue(COLOR_THEME_DARK_SETTING); - } - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { - if (theme) { - this.setColorTheme(theme.id, undefined); + const initializeColorTheme = async () => { + if (extDevLocs && extDevLocs.length > 0) { // in dev mode, switch to a theme provided by the extension under dev. + const devThemes = await this.colorThemeStore.findThemeDataByParentLocation(extDevLocs[0]); + if (devThemes.length) { + return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); + } } - }); + let theme = await this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID); + const preferredType = this.getPreferredColorScheme(); + let settingsTarget: undefined | 'auto' = undefined; + if (preferredType && theme && theme.type !== preferredType) { + const preferedTheme = await this.getPreferredColorTheme(preferredType); + if (preferedTheme) { + theme = preferedTheme; + settingsTarget = 'auto'; + } + } + return this.setColorTheme(theme && theme.id, settingsTarget); + }; + + const initializeIconTheme = async () => { + if (extDevLocs && extDevLocs.length > 0) { // in dev mode, switch to a theme provided by the extension under dev. + const devThemes = await this.iconThemeStore.findThemeDataByParentLocation(extDevLocs[0]); + if (devThemes.length) { + return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); + } + } + const theme = await this.iconThemeStore.findThemeBySettingsId(iconThemeSetting); + return this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined); + }; + + return Promise.all([initializeColorTheme(), initializeIconTheme()]); } private installConfigurationListener() { @@ -339,31 +306,15 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (colorThemeSetting !== this.currentColorTheme.settingsId) { this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { if (theme) { - if (this.autoSwitchColorTheme && theme.type === DARK) { - this.configurationService.updateValue(COLOR_THEME_DARK_SETTING, theme.settingsId); - } - if (this.autoSwitchColorTheme && theme.type === LIGHT) { - this.configurationService.updateValue(COLOR_THEME_LIGHT_SETTING, theme.settingsId); - } this.setColorTheme(theme.id, undefined); } }); } } - if (e.affectsConfiguration(DETECT_AS_SETTING)) { - let autoSwitchColorTheme = this.configurationService.getValue(DETECT_AS_SETTING); + if (e.affectsConfiguration(DETECT_COLOR_SCHEME_SETTING)) { + let autoSwitchColorTheme = this.configurationService.getValue(DETECT_COLOR_SCHEME_SETTING); if (autoSwitchColorTheme) { - this.installColorThemeSwitch(); - let colorThemeSetting = this.getPreferredTheme(); - if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { - if (theme) { - this.setColorTheme(theme.id, undefined); - } - }); - } - } else { - this.deinstallColorThemeSwitch(); + this.preferredSchemeUpdated(); } } if (e.affectsConfiguration(ICON_THEME_SETTING)) { @@ -396,24 +347,45 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { }); } - private getPreferredTheme(): string { - let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_LIGHT_SETTING); - const darkMode = this.getPreferredColorScheme() === ColorScheme.DARK; - if (darkMode) { - colorThemeSetting = this.configurationService.getValue(COLOR_THEME_DARK_SETTING); - } - return colorThemeSetting; + // preferred scheme handling + + private installPreferredSchemeListener() { + window.matchMedia('(prefers-color-scheme: dark)').addListener(async () => this.preferredSchemeUpdated()); } - public getPreferredColorScheme(): ColorScheme { - const noPreference = window.matchMedia(`(prefers-color-scheme: ${ColorScheme.NO_PREFERENCE})`).matches; - const prefersDark = window.matchMedia(`(prefers-color-scheme: ${ColorScheme.DARK})`).matches; - if (noPreference) { - return ColorScheme.NO_PREFERENCE; - } else if (prefersDark) { - return ColorScheme.DARK; + private async preferredSchemeUpdated() { + const scheme = this.getPreferredColorScheme(); + if (scheme && this.currentColorTheme.type !== scheme) { + const preferedTheme = await this.getPreferredColorTheme(scheme); + if (preferedTheme) { + return this.setColorTheme(preferedTheme.id, 'auto'); + } } - return ColorScheme.LIGHT; + return undefined; + } + + private getPreferredColorScheme(): ThemeType | undefined { + let detectHCThemeSetting = this.configurationService.getValue(DETECT_HC_SETTING); + if (this.environmentService.configuration.highContrast && detectHCThemeSetting) { + return HIGH_CONTRAST; + } + if (this.configurationService.getValue(DETECT_COLOR_SCHEME_SETTING)) { + if (window.matchMedia(`(prefers-color-scheme: light)`).matches) { + return LIGHT; + } else if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) { + return DARK; + } + } + return undefined; + } + + private getPreferredColorTheme(type: ThemeType): Promise { + const settingId = type === DARK ? PREFERRED_DARK_THEME_SETTING : type === LIGHT ? PREFERRED_LIGHT_THEME_SETTING : PREFERRED_HC_THEME_SETTING; + const themeSettingId = this.configurationService.getValue(settingId); + if (themeSettingId) { + return this.colorThemeStore.findThemeDataBySettingsId(themeSettingId, undefined); + } + return Promise.resolve(undefined); } public getColorTheme(): IColorTheme { @@ -735,22 +707,25 @@ const colorThemeSettingSchema: IConfigurationPropertySchema = { enumDescriptions: [], errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), }; -const colorThemeDarkSettingSchema: IConfigurationPropertySchema = { +const preferredDarkThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', - description: nls.localize('colorThemeDark', 'Specifies the color theme used for a dark OS appearance.'), + description: nls.localize('preferredDarkColorTheme', 'Specifies the preferred color theme for dark OS appearance when \'{0}\' is enabled.', DETECT_COLOR_SCHEME_SETTING), default: DEFAULT_THEME_DARK_SETTING_VALUE, - errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), }; -const colorThemeLightSettingSchema: IConfigurationPropertySchema = { +const preferredLightThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', - description: nls.localize('colorThemeLight', 'Specifies the color theme used for a light OS appearance.'), + description: nls.localize('preferredLightColorTheme', 'Specifies the preferred color theme for light OS appearance when \'{0}\' is enabled.', DETECT_COLOR_SCHEME_SETTING), default: DEFAULT_THEME_LIGHT_SETTING_VALUE, - errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), }; -const colorThemeAutoSwitchSettingSchema: IConfigurationPropertySchema = { +const preferredHCThemeSettingSchema: IConfigurationPropertySchema = { + type: 'string', + description: nls.localize('preferredHCColorTheme', 'Specifies the preferred color theme used in high contrast mode when \'{0}\' is enabled.', DETECT_HC_SETTING), + default: DEFAULT_THEME_HC_SETTING_VALUE, +}; +const detectColorSchemeSettingSchema: IConfigurationPropertySchema = { type: 'boolean', - description: nls.localize('colorThemeAutoSwitch', 'Changes the color theme based on the OS appearance.'), - default: DEFAULT_THEME_AUTO_SWITCH_SETTING_VALUE + description: nls.localize('detectColorScheme', 'If set, use the prefered color theme based on the OS appearance.'), + default: true }; const iconThemeSettingSchema: IConfigurationPropertySchema = { @@ -778,9 +753,10 @@ const themeSettingsConfiguration: IConfigurationNode = { type: 'object', properties: { [COLOR_THEME_SETTING]: colorThemeSettingSchema, - [COLOR_THEME_DARK_SETTING]: colorThemeDarkSettingSchema, - [COLOR_THEME_LIGHT_SETTING]: colorThemeLightSettingSchema, - [DETECT_AS_SETTING]: colorThemeAutoSwitchSettingSchema, + [PREFERRED_DARK_THEME_SETTING]: preferredDarkThemeSettingSchema, + [PREFERRED_LIGHT_THEME_SETTING]: preferredLightThemeSettingSchema, + [PREFERRED_HC_THEME_SETTING]: preferredHCThemeSettingSchema, + [DETECT_COLOR_SCHEME_SETTING]: detectColorSchemeSettingSchema, [ICON_THEME_SETTING]: iconThemeSettingSchema, [CUSTOM_WORKBENCH_COLORS_SETTING]: colorCustomizationsSchema } @@ -839,4 +815,4 @@ const tokenColorCustomizationConfiguration: IConfigurationNode = { }; configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration); -registerSingleton(IWorkbenchThemeService, WorkbenchThemeService); \ No newline at end of file +registerSingleton(IWorkbenchThemeService, WorkbenchThemeService); diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index c498b7d1ac2..4756204b595 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -18,10 +18,6 @@ export const VS_HC_THEME = 'hc-black'; export const HC_THEME_ID = 'Default High Contrast'; export const COLOR_THEME_SETTING = 'workbench.colorTheme'; -export const COLOR_THEME_DARK_SETTING = 'workbench.colorThemeDark'; -export const COLOR_THEME_LIGHT_SETTING = 'workbench.colorThemeLight'; -export const DETECT_AS_SETTING = 'workbench.colorThemeAutoSwitch'; -export const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; export const ICON_THEME_SETTING = 'workbench.iconTheme'; export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations'; export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations'; @@ -114,4 +110,4 @@ export interface IThemeExtensionPoint { path: string; uiTheme?: typeof VS_LIGHT_THEME | typeof VS_DARK_THEME | typeof VS_HC_THEME; _watch: boolean; // unsupported options to watch location -} \ No newline at end of file +} From d92afa067c3b7dd2d8242b1fc15d1a588b7e98ba Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 19 Dec 2019 12:21:08 +0100 Subject: [PATCH 154/241] editors - introduce and use EditorsObserver and use for MRU list --- .../workbench/browser/parts/editor/editor.ts | 10 + .../browser/parts/editor/editorsObserver.ts | 296 ++++++++++ .../services/editor/browser/editorService.ts | 13 +- .../test/browser/editorsObserver.test.ts | 538 ++++++++++++++++++ .../services/history/browser/history.ts | 318 +---------- .../services/history/common/history.ts | 4 +- .../services/history/test/history.test.ts | 420 +------------- .../workbench/test/workbenchTestServices.ts | 4 +- 8 files changed, 876 insertions(+), 727 deletions(-) create mode 100644 src/vs/workbench/browser/parts/editor/editorsObserver.ts create mode 100644 src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index bf44520a5dd..7434fe1b1cf 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -151,4 +151,14 @@ export interface EditorServiceImpl extends IEditorService { * Emitted when an editor failed to open. */ readonly onDidOpenEditorFail: Event; + + /** + * Emitted when the list of most recently active editors change. + */ + readonly onDidMostRecentlyActiveEditorsChange: Event; + + /** + * Access to the list of most recently active editors. + */ + readonly mostRecentlyActiveEditors: ReadonlyArray; } diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts new file mode 100644 index 00000000000..9c6f9ce6d10 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -0,0 +1,296 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions } from 'vs/workbench/common/editor'; +import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { coalesce } from 'vs/base/common/arrays'; +import { LinkedMap, Touch } from 'vs/base/common/map'; + +interface ISerializedEditorsList { + entries: ISerializedEditorIdentifier[]; +} + +interface ISerializedEditorIdentifier { + groupId: GroupIdentifier; + index: number; +} + +/** + * A observer of opened editors across all editor groups by most recently used. + * Rules: + * - the last editor in the list is the one most recently activated + * - the first editor in the list is the one that was activated the longest time ago + * - an editor that opens inactive will be placed behind the currently active editor + */ +export class EditorsObserver extends Disposable { + + private static readonly STORAGE_KEY = 'editorsObserver.state'; + + private readonly keyMap = new Map>(); + private readonly mostRecentEditorsMap = new LinkedMap(); + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange = this._onDidChange.event; + + get editors(): IEditorIdentifier[] { + return this.mostRecentEditorsMap.values(); + } + + constructor( + @IEditorGroupsService private editorGroupsService: IEditorGroupsService, + @IStorageService private readonly storageService: IStorageService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.storageService.onWillSaveState(() => this.saveState())); + this._register(this.editorGroupsService.onDidAddGroup(group => this.onGroupAdded(group))); + + this.editorGroupsService.whenRestored.then(() => this.loadState()); + } + + private onGroupAdded(group: IEditorGroup): void { + + // Make sure to add any already existing editor + // of the new group into our list in LRU order + const groupEditorsMru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); + for (let i = groupEditorsMru.length - 1; i >= 0; i--) { + this.addMostRecentEditor(group, groupEditorsMru[i], false /* is not active */); + } + + // Make sure that active editor is put as first if group is active + if (this.editorGroupsService.activeGroup === group && group.activeEditor) { + this.addMostRecentEditor(group, group.activeEditor, true /* is active */); + } + + // Group Listeners + this.registerGroupListeners(group); + } + + private registerGroupListeners(group: IEditorGroup): void { + const groupDisposables = new DisposableStore(); + groupDisposables.add(group.onDidGroupChange(e => { + switch (e.kind) { + + // Group gets active: put active editor as most recent + case GroupChangeKind.GROUP_ACTIVE: { + if (this.editorGroupsService.activeGroup === group && group.activeEditor) { + this.addMostRecentEditor(group, group.activeEditor, true /* is active */); + } + + break; + } + + // Editor gets active: put active editor as most recent + // if group is active, otherwise second most recent + case GroupChangeKind.EDITOR_ACTIVE: { + if (e.editor) { + this.addMostRecentEditor(group, e.editor, this.editorGroupsService.activeGroup === group); + } + + break; + } + + // Editor opens: put it as second most recent + case GroupChangeKind.EDITOR_OPEN: { + if (e.editor) { + this.addMostRecentEditor(group, e.editor, false /* is not active */); + } + + break; + } + + // Editor closes: remove from recently opened + case GroupChangeKind.EDITOR_CLOSE: { + if (e.editor) { + this.removeMostRecentEditor(group, e.editor); + } + + break; + } + } + })); + + // Make sure to cleanup on dispose + Event.once(group.onWillDispose)(() => dispose(groupDisposables)); + } + + private addMostRecentEditor(group: IEditorGroup, editor: IEditorInput, isActive: boolean): void { + const key = this.ensureKey(group, editor); + const mostRecentEditor = this.mostRecentEditorsMap.first; + + // Active or first entry: add to end of map + if (isActive || !mostRecentEditor) { + this.mostRecentEditorsMap.set(key, key, mostRecentEditor ? Touch.AsOld /* make first */ : undefined); + } + + // Otherwise: insert before most recent + else { + // we have most recent editors. as such we + // put this newly opened editor right before + // the current most recent one because it cannot + // be the most recently active one unless + // it becomes active. but it is still more + // active then any other editor in the list. + this.mostRecentEditorsMap.set(key, key, Touch.AsOld /* make first */); + this.mostRecentEditorsMap.set(mostRecentEditor, mostRecentEditor, Touch.AsOld /* make first */); + } + + // Event + this._onDidChange.fire(); + } + + private removeMostRecentEditor(group: IEditorGroup, editor: IEditorInput): void { + const key = this.findKey(group, editor); + if (key) { + + // Remove from most recent editors + this.mostRecentEditorsMap.delete(key); + + // Remove from key map + const map = this.keyMap.get(group.id); + if (map && map.delete(key.editor) && map.size === 0) { + this.keyMap.delete(group.id); + } + + // Event + this._onDidChange.fire(); + } + } + + private findKey(group: IEditorGroup, editor: IEditorInput): IEditorIdentifier | undefined { + const groupMap = this.keyMap.get(group.id); + if (!groupMap) { + return undefined; + } + + return groupMap.get(editor); + } + + private ensureKey(group: IEditorGroup, editor: IEditorInput): IEditorIdentifier { + let groupMap = this.keyMap.get(group.id); + if (!groupMap) { + groupMap = new Map(); + + this.keyMap.set(group.id, groupMap); + } + + let key = groupMap.get(editor); + if (!key) { + key = { groupId: group.id, editor }; + groupMap.set(editor, key); + } + + return key; + } + + private saveState(): void { + if (this.mostRecentEditorsMap.isEmpty()) { + this.storageService.remove(EditorsObserver.STORAGE_KEY, StorageScope.WORKSPACE); + } else { + this.storageService.store(EditorsObserver.STORAGE_KEY, JSON.stringify(this.serialize()), StorageScope.WORKSPACE); + } + } + + private serialize(): ISerializedEditorsList { + const registry = Registry.as(Extensions.EditorInputFactories); + + const entries = this.mostRecentEditorsMap.values(); + const mapGroupToSerializableEditorsOfGroup = new Map(); + + return { + entries: coalesce(entries.map(({ editor, groupId }) => { + + // Find group for entry + const group = this.editorGroupsService.getGroup(groupId); + if (!group) { + return undefined; + } + + // Find serializable editors of group + let serializableEditorsOfGroup = mapGroupToSerializableEditorsOfGroup.get(group); + if (!serializableEditorsOfGroup) { + serializableEditorsOfGroup = group.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => { + const factory = registry.getEditorInputFactory(editor.getTypeId()); + + return factory?.canSerialize(editor); + }); + mapGroupToSerializableEditorsOfGroup.set(group, serializableEditorsOfGroup); + } + + // Only store the index of the editor of that group + // which can be undefined if the editor is not serializable + const index = serializableEditorsOfGroup.indexOf(editor); + if (index === -1) { + return undefined; + } + + return { groupId, index }; + })) + }; + } + + private loadState(): void { + const serialized = this.storageService.get(EditorsObserver.STORAGE_KEY, StorageScope.WORKSPACE); + + // Previous state: + if (serialized) { + + // Load editors map from persisted state + this.deserialize(JSON.parse(serialized)); + } + + // No previous state: best we can do is add each editor + // from oldest to most recently used editor group + else { + const groups = this.editorGroupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE); + for (let i = groups.length - 1; i >= 0; i--) { + const group = groups[i]; + const groupEditorsMru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); + for (let i = groupEditorsMru.length - 1; i >= 0; i--) { + this.addMostRecentEditor(group, groupEditorsMru[i], true /* enforce as active to preserve order */); + } + } + } + + // Ensure we listen on group changes for those that exist on startup + for (const group of this.editorGroupsService.groups) { + this.registerGroupListeners(group); + } + } + + private deserialize(serialized: ISerializedEditorsList): void { + const mapValues: [IEditorIdentifier, IEditorIdentifier][] = []; + + for (const { groupId, index } of serialized.entries) { + + // Find group for entry + const group = this.editorGroupsService.getGroup(groupId); + if (!group) { + continue; + } + + // Find editor for entry + const editor = group.getEditorByIndex(index); + if (!editor) { + continue; + } + + // Make sure key is registered as well + const editorIdentifier = this.ensureKey(group, editor); + mapValues.push([editorIdentifier, editorIdentifier]); + } + + // Fill map with deserialized values + this.mostRecentEditorsMap.fromJSON(mapValues); + } +} diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 987883a3fb4..a50defb71e3 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -27,6 +27,7 @@ import { IEditorGroupView, IEditorOpeningEvent, EditorServiceImpl } from 'vs/wor import { ILabelService } from 'vs/platform/label/common/label'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver'; type CachedEditorInput = ResourceEditorInput | IFileEditorInput; type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE; @@ -51,14 +52,19 @@ export class EditorService extends Disposable implements EditorServiceImpl { private readonly _onDidOpenEditorFail = this._register(new Emitter()); readonly onDidOpenEditorFail = this._onDidOpenEditorFail.event; + private readonly _onDidMostRecentlyActiveEditorsChange = this._register(new Emitter()); + readonly onDidMostRecentlyActiveEditorsChange = this._onDidMostRecentlyActiveEditorsChange.event; + //#endregion private fileInputFactory: IFileInputFactory; - private openEditorHandlers: IOpenEditorOverrideHandler[] = []; + private readonly openEditorHandlers: IOpenEditorOverrideHandler[] = []; private lastActiveEditor: IEditorInput | undefined = undefined; private lastActiveGroupId: GroupIdentifier | undefined = undefined; + private readonly editorsObserver = this._register(this.instantiationService.createInstance(EditorsObserver)); + constructor( @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @@ -80,6 +86,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { this.editorGroupService.whenRestored.then(() => this.onEditorsRestored()); this.editorGroupService.onDidActiveGroupChange(group => this.handleActiveEditorChange(group)); this.editorGroupService.onDidAddGroup(group => this.registerGroupListeners(group as IEditorGroupView)); + this.editorsObserver.onDidChange(() => this._onDidMostRecentlyActiveEditorsChange.fire()); } private onEditorsRestored(): void { @@ -188,6 +195,10 @@ export class EditorService extends Disposable implements EditorServiceImpl { return editors; } + get mostRecentlyActiveEditors(): IEditorIdentifier[] { + return this.editorsObserver.editors; + } + get activeEditor(): IEditorInput | undefined { const activeGroup = this.editorGroupService.activeGroup; diff --git a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts new file mode 100644 index 00000000000..f61b53c29e5 --- /dev/null +++ b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts @@ -0,0 +1,538 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { EditorOptions, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IEditorInputFactory, IFileEditorInput } from 'vs/workbench/common/editor'; +import { URI } from 'vs/base/common/uri'; +import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { HistoryService } from 'vs/workbench/services/history/browser/history'; +import { WillSaveStateReason } from 'vs/platform/storage/common/storage'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { timeout } from 'vs/base/common/async'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver'; + +const TEST_EDITOR_ID = 'MyTestEditorForEditorsObserver'; +const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorsObserver'; +const TEST_SERIALIZABLE_EDITOR_INPUT_ID = 'testSerializableEditorInputForEditorsObserver'; + +class TestEditorControl extends BaseEditor { + + constructor() { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } + + async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + super.setInput(input, options, token); + + await input.resolve(); + } + + getId(): string { return TEST_EDITOR_ID; } + layout(): void { } + createEditor(): any { } +} + +class TestEditorInput extends EditorInput implements IFileEditorInput { + + constructor(public resource: URI) { super(); } + + getTypeId() { return TEST_EDITOR_INPUT_ID; } + resolve(): Promise { return Promise.resolve(null); } + matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } + setEncoding(encoding: string) { } + getEncoding() { return undefined; } + setPreferredEncoding(encoding: string) { } + setMode(mode: string) { } + setPreferredMode(mode: string) { } + getResource(): URI { return this.resource; } + setForceOpenAsBinary(): void { } +} + +class EditorsObserverTestEditorInput extends TestEditorInput { + getTypeId() { return TEST_SERIALIZABLE_EDITOR_INPUT_ID; } +} + +interface ISerializedTestInput { + resource: string; +} + +class EditorsObserverTestEditorInputFactory implements IEditorInputFactory { + + canSerialize(editorInput: EditorInput): boolean { + return true; + } + + serialize(editorInput: EditorInput): string { + let testEditorInput = editorInput; + let testInput: ISerializedTestInput = { + resource: testEditorInput.resource.toString() + }; + + return JSON.stringify(testInput); + } + + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { + let testInput: ISerializedTestInput = JSON.parse(serializedEditorInput); + + return new EditorsObserverTestEditorInput(URI.parse(testInput.resource)); + } +} + +async function createServices(): Promise<[EditorPart, HistoryService, EditorService]> { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + instantiationService.stub(IEditorGroupsService, part); + + const editorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + const historyService = instantiationService.createInstance(HistoryService); + instantiationService.stub(IHistoryService, historyService); + + return [part, historyService, editorService]; +} + +suite('EditorsObserver', function () { + + let disposables: IDisposable[] = []; + + setup(() => { + disposables.push(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(TEST_SERIALIZABLE_EDITOR_INPUT_ID, EditorsObserverTestEditorInputFactory)); + disposables.push(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test Editor For Editors Observer'), [new SyncDescriptor(TestEditorInput), new SyncDescriptor(EditorsObserverTestEditorInput)])); + }); + + teardown(() => { + dispose(disposables); + disposables = []; + }); + + + test('basics (single group)', async () => { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const history = new EditorsObserver(part, new TestStorageService()); + + let historyChangeListenerCalled = false; + const listener = history.onDidChange(() => { + historyChangeListenerCalled = true; + }); + + let currentHistory = history.editors; + assert.equal(currentHistory.length, 0); + assert.equal(historyChangeListenerCalled, false); + + const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); + + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 1); + assert.equal(currentHistory[0].groupId, part.activeGroup.id); + assert.equal(currentHistory[0].editor, input1); + assert.equal(historyChangeListenerCalled, true); + + const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); + const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); + + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, part.activeGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, part.activeGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, part.activeGroup.id); + assert.equal(currentHistory[2].editor, input1); + + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, part.activeGroup.id); + assert.equal(currentHistory[0].editor, input2); + assert.equal(currentHistory[1].groupId, part.activeGroup.id); + assert.equal(currentHistory[1].editor, input3); + assert.equal(currentHistory[2].groupId, part.activeGroup.id); + assert.equal(currentHistory[2].editor, input1); + + historyChangeListenerCalled = false; + await part.activeGroup.closeEditor(input1); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 2); + assert.equal(currentHistory[0].groupId, part.activeGroup.id); + assert.equal(currentHistory[0].editor, input2); + assert.equal(currentHistory[1].groupId, part.activeGroup.id); + assert.equal(currentHistory[1].editor, input3); + assert.equal(historyChangeListenerCalled, true); + + await part.activeGroup.closeAllEditors(); + currentHistory = history.editors; + assert.equal(currentHistory.length, 0); + + part.dispose(); + listener.dispose(); + }); + + test('basics (multi group)', async () => { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const rootGroup = part.activeGroup; + + const history = new EditorsObserver(part, new TestStorageService()); + + let currentHistory = history.editors; + assert.equal(currentHistory.length, 0); + + const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + + const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); + await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 2); + assert.equal(currentHistory[0].groupId, sideGroup.id); + assert.equal(currentHistory[0].editor, input1); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input1); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 2); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input1); + assert.equal(currentHistory[1].groupId, sideGroup.id); + assert.equal(currentHistory[1].editor, input1); + + // Opening an editor inactive should not change + // the most recent editor, but rather put it behind + const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); + + await rootGroup.openEditor(input2, EditorOptions.create({ inactive: true })); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input1); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, sideGroup.id); + assert.equal(currentHistory[2].editor, input1); + + await rootGroup.closeAllEditors(); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 1); + assert.equal(currentHistory[0].groupId, sideGroup.id); + assert.equal(currentHistory[0].editor, input1); + + await sideGroup.closeAllEditors(); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 0); + + part.dispose(); + }); + + test('copy group', async () => { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const history = new EditorsObserver(part, new TestStorageService()); + + const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); + const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); + const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); + + const rootGroup = part.activeGroup; + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + + let currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); + + const copiedGroup = part.copyGroup(rootGroup, rootGroup, GroupDirection.RIGHT); + copiedGroup.setActive(true); + + currentHistory = history.editors; + assert.equal(currentHistory.length, 6); + assert.equal(currentHistory[0].groupId, copiedGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input3); + assert.equal(currentHistory[2].groupId, copiedGroup.id); + assert.equal(currentHistory[2].editor, input2); + assert.equal(currentHistory[3].groupId, copiedGroup.id); + assert.equal(currentHistory[3].editor, input1); + assert.equal(currentHistory[4].groupId, rootGroup.id); + assert.equal(currentHistory[4].editor, input2); + assert.equal(currentHistory[5].groupId, rootGroup.id); + assert.equal(currentHistory[5].editor, input1); + + part.dispose(); + }); + + test('initial editors are part of history and state is persisted & restored (single group)', async () => { + const instantiationService = workbenchInstantiationService(); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const rootGroup = part.activeGroup; + + const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); + const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); + const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + + const storage = new TestStorageService(); + const history = new EditorsObserver(part, storage); + await part.whenRestored; + + let currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); + + storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + + const restoredHistory = new EditorsObserver(part, storage); + await part.whenRestored; + + currentHistory = restoredHistory.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); + + part.dispose(); + }); + + test('initial editors are part of history (multi group)', async () => { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const rootGroup = part.activeGroup; + + const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); + const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); + const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + + const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + await sideGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + + const storage = new TestStorageService(); + const history = new EditorsObserver(part, storage); + await part.whenRestored; + + let currentHistory = history.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, sideGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); + + storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + + const restoredHistory = new EditorsObserver(part, storage); + await part.whenRestored; + + currentHistory = restoredHistory.editors; + assert.equal(currentHistory.length, 3); + assert.equal(currentHistory[0].groupId, sideGroup.id); + assert.equal(currentHistory[0].editor, input3); + assert.equal(currentHistory[1].groupId, rootGroup.id); + assert.equal(currentHistory[1].editor, input2); + assert.equal(currentHistory[2].groupId, rootGroup.id); + assert.equal(currentHistory[2].editor, input1); + + part.dispose(); + }); + + test('history does not restore editors that cannot be serialized', async () => { + const instantiationService = workbenchInstantiationService(); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + await part.whenRestored; + + const rootGroup = part.activeGroup; + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + + const storage = new TestStorageService(); + const history = new EditorsObserver(part, storage); + await part.whenRestored; + + let currentHistory = history.editors; + assert.equal(currentHistory.length, 1); + assert.equal(currentHistory[0].groupId, rootGroup.id); + assert.equal(currentHistory[0].editor, input1); + + storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + + const restoredHistory = new EditorsObserver(part, storage); + await part.whenRestored; + + currentHistory = restoredHistory.editors; + assert.equal(currentHistory.length, 0); + + part.dispose(); + }); + + test('open next/previous recently used editor (single group)', async () => { + const [part, historyService] = await createServices(); + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + assert.equal(part.activeGroup.activeEditor, input1); + + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + assert.equal(part.activeGroup.activeEditor, input2); + + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input1); + + historyService.openNextRecentlyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input2); + + historyService.openPreviouslyUsedEditor(part.activeGroup.id); + assert.equal(part.activeGroup.activeEditor, input1); + + historyService.openNextRecentlyUsedEditor(part.activeGroup.id); + assert.equal(part.activeGroup.activeEditor, input2); + + part.dispose(); + }); + + test('open next/previous recently used editor (multi group)', async () => { + const [part, historyService] = await createServices(); + const rootGroup = part.activeGroup; + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + + const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup, rootGroup); + assert.equal(rootGroup.activeEditor, input1); + + historyService.openNextRecentlyUsedEditor(); + assert.equal(part.activeGroup, sideGroup); + assert.equal(sideGroup.activeEditor, input2); + + part.dispose(); + }); + + test('open next/previous recently is reset when other input opens', async () => { + const [part, historyService] = await createServices(); + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input3 = new TestEditorInput(URI.parse('foo://bar3')); + const input4 = new TestEditorInput(URI.parse('foo://bar4')); + + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input2); + + await timeout(0); + await part.activeGroup.openEditor(input4, EditorOptions.create({ pinned: true })); + + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input2); + + historyService.openNextRecentlyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input4); + + part.dispose(); + }); +}); diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 009ad65946f..9d38b5e6747 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -7,7 +7,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IEditor } from 'vs/editor/common/editorCommon'; import { ITextEditorOptions, IResourceInput, ITextEditorSelection } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor as IBaseEditor, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, toResource, IEditorIdentifier, GroupIdentifier, Extensions } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor as IBaseEditor, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, toResource, IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; @@ -16,9 +16,9 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, EditorsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { getExcludes, ISearchConfiguration } from 'vs/workbench/services/search/common/search'; import { IExpression } from 'vs/base/common/glob'; @@ -34,9 +34,6 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { Schemas } from 'vs/base/common/network'; -import { LinkedMap, Touch } from 'vs/base/common/map'; - -//#region Text Editor State helper /** * Stores the selection & view state of an editor and allows to compare it to other selection states. @@ -86,295 +83,6 @@ export class TextEditorState { } } -//#endregion - -//#region Editors History - -interface ISerializedEditorHistory { - history: ISerializedEditorIdentifier[]; -} - -interface ISerializedEditorIdentifier { - groupId: GroupIdentifier; - index: number; -} - -/** - * A history of opened editors across all editor groups by most recently used. - * Rules: - * - the last editor in the history is the one most recently activated - * - the first editor in the history is the one that was activated the longest time ago - * - an editor that opens inactive will be placed behind the currently active editor - */ -export class EditorsHistory extends Disposable { - - private static readonly STORAGE_KEY = 'history.editors'; - - private readonly keyMap = new Map>(); - private readonly mostRecentEditorsMap = new LinkedMap(); - - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; - - get editors(): IEditorIdentifier[] { - return this.mostRecentEditorsMap.values(); - } - - constructor( - @IEditorGroupsService private editorGroupsService: IEditorGroupsService, - @IStorageService private readonly storageService: IStorageService - ) { - super(); - - this.registerListeners(); - } - - private registerListeners(): void { - this._register(this.storageService.onWillSaveState(() => this.saveState())); - this._register(this.editorGroupsService.onDidAddGroup(group => this.onGroupAdded(group))); - - this.editorGroupsService.whenRestored.then(() => this.loadState()); - } - - private onGroupAdded(group: IEditorGroup): void { - - // Make sure to add any already existing editor - // of the new group into our history in LRU order - const groupEditorsMru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); - for (let i = groupEditorsMru.length - 1; i >= 0; i--) { - this.addMostRecentEditor(group, groupEditorsMru[i], false /* is not active */); - } - - // Make sure that active editor is put as first if group is active - if (this.editorGroupsService.activeGroup === group && group.activeEditor) { - this.addMostRecentEditor(group, group.activeEditor, true /* is active */); - } - - // Group Listeners - this.registerGroupListeners(group); - } - - private registerGroupListeners(group: IEditorGroup): void { - const groupDisposables = new DisposableStore(); - groupDisposables.add(group.onDidGroupChange(e => { - switch (e.kind) { - - // Group gets active: put active editor as most recent - case GroupChangeKind.GROUP_ACTIVE: { - if (this.editorGroupsService.activeGroup === group && group.activeEditor) { - this.addMostRecentEditor(group, group.activeEditor, true /* is active */); - } - - break; - } - - // Editor gets active: put active editor as most recent - // if group is active, otherwise second most recent - case GroupChangeKind.EDITOR_ACTIVE: { - if (e.editor) { - this.addMostRecentEditor(group, e.editor, this.editorGroupsService.activeGroup === group); - } - - break; - } - - // Editor opens: put it as second most recent - case GroupChangeKind.EDITOR_OPEN: { - if (e.editor) { - this.addMostRecentEditor(group, e.editor, false /* is not active */); - } - - break; - } - - // Editor closes: remove from recently opened - case GroupChangeKind.EDITOR_CLOSE: { - if (e.editor) { - this.removeMostRecentEditor(group, e.editor); - } - - break; - } - } - })); - - // Make sure to cleanup on dispose - Event.once(group.onWillDispose)(() => dispose(groupDisposables)); - } - - private addMostRecentEditor(group: IEditorGroup, editor: IEditorInput, isActive: boolean): void { - const key = this.ensureKey(group, editor); - const mostRecentEditor = this.mostRecentEditorsMap.first; - - // Active or first entry: add to end of map - if (isActive || !mostRecentEditor) { - this.mostRecentEditorsMap.set(key, key, mostRecentEditor ? Touch.AsOld /* make first */ : undefined); - } - - // Otherwise: insert before most recent - else { - // we have most recent editors. as such we - // put this newly opened editor right before - // the current most recent one because it cannot - // be the most recently active one unless - // it becomes active. but it is still more - // active then any other editor in the list. - this.mostRecentEditorsMap.set(key, key, Touch.AsOld /* make first */); - this.mostRecentEditorsMap.set(mostRecentEditor, mostRecentEditor, Touch.AsOld /* make first */); - } - - // Event - this._onDidChange.fire(); - } - - private removeMostRecentEditor(group: IEditorGroup, editor: IEditorInput): void { - const key = this.findKey(group, editor); - if (key) { - - // Remove from most recent editors - this.mostRecentEditorsMap.delete(key); - - // Remove from key map - const map = this.keyMap.get(group.id); - if (map && map.delete(key.editor) && map.size === 0) { - this.keyMap.delete(group.id); - } - - // Event - this._onDidChange.fire(); - } - } - - private findKey(group: IEditorGroup, editor: IEditorInput): IEditorIdentifier | undefined { - const groupMap = this.keyMap.get(group.id); - if (!groupMap) { - return undefined; - } - - return groupMap.get(editor); - } - - private ensureKey(group: IEditorGroup, editor: IEditorInput): IEditorIdentifier { - let groupMap = this.keyMap.get(group.id); - if (!groupMap) { - groupMap = new Map(); - - this.keyMap.set(group.id, groupMap); - } - - let key = groupMap.get(editor); - if (!key) { - key = { groupId: group.id, editor }; - groupMap.set(editor, key); - } - - return key; - } - - private saveState(): void { - if (this.mostRecentEditorsMap.isEmpty()) { - this.storageService.remove(EditorsHistory.STORAGE_KEY, StorageScope.WORKSPACE); - } else { - this.storageService.store(EditorsHistory.STORAGE_KEY, JSON.stringify(this.serialize()), StorageScope.WORKSPACE); - } - } - - private serialize(): ISerializedEditorHistory { - const registry = Registry.as(Extensions.EditorInputFactories); - - const history = this.mostRecentEditorsMap.values(); - const mapGroupToSerializableEditorsOfGroup = new Map(); - - return { - history: coalesce(history.map(({ editor, groupId }) => { - - // Find group for entry - const group = this.editorGroupsService.getGroup(groupId); - if (!group) { - return undefined; - } - - // Find serializable editors of group - let serializableEditorsOfGroup = mapGroupToSerializableEditorsOfGroup.get(group); - if (!serializableEditorsOfGroup) { - serializableEditorsOfGroup = group.getEditors(EditorsOrder.SEQUENTIAL).filter(editor => { - const factory = registry.getEditorInputFactory(editor.getTypeId()); - - return factory?.canSerialize(editor); - }); - mapGroupToSerializableEditorsOfGroup.set(group, serializableEditorsOfGroup); - } - - // Only store the index of the editor of that group - // which can be undefined if the editor is not serializable - const index = serializableEditorsOfGroup.indexOf(editor); - if (index === -1) { - return undefined; - } - - return { groupId, index }; - })) - }; - } - - private loadState(): void { - const serialized = this.storageService.get(EditorsHistory.STORAGE_KEY, StorageScope.WORKSPACE); - - // Previous state: - if (serialized) { - - // Load history map from persisted state - this.deserialize(JSON.parse(serialized)); - } - - // No previous state: best we can do is add each editor - // from oldest to most recently used editor group - else { - const groups = this.editorGroupsService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE); - for (let i = groups.length - 1; i >= 0; i--) { - const group = groups[i]; - const groupEditorsMru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); - for (let i = groupEditorsMru.length - 1; i >= 0; i--) { - this.addMostRecentEditor(group, groupEditorsMru[i], true /* enforce as active to preserve order */); - } - } - } - - // Ensure we listen on group changes for those that exist on startup - for (const group of this.editorGroupsService.groups) { - this.registerGroupListeners(group); - } - } - - deserialize(serialized: ISerializedEditorHistory): void { - const mapValues: [IEditorIdentifier, IEditorIdentifier][] = []; - - for (const { groupId, index } of serialized.history) { - - // Find group for entry - const group = this.editorGroupsService.getGroup(groupId); - if (!group) { - continue; - } - - // Find editor for entry - const editor = group.getEditorByIndex(index); - if (!editor) { - continue; - } - - // Make sure key is registered as well - const editorIdentifier = this.ensureKey(group, editor); - mapValues.push([editorIdentifier, editorIdentifier]); - } - - // Fill map with deserialized values - this.mostRecentEditorsMap.fromJSON(mapValues); - } -} - -//#endregion - interface ISerializedEditorHistoryEntry { resourceJSON?: object; editorInputJSON?: { typeId: string; deserialized: string; }; @@ -424,7 +132,7 @@ export class HistoryService extends Disposable implements IHistoryService { this._register(this.storageService.onWillSaveState(() => this.saveState())); this._register(this.fileService.onFileChanges(event => this.onFileChanges(event))); this._register(this.resourceFilter.onExpressionChange(() => this.removeExcludedFromHistory())); - this._register(this.mostRecentlyUsedOpenEditors.onDidChange(() => this.handleEditorEventInRecentEditorsStack())); + this._register(this.editorService.onDidMostRecentlyActiveEditorsChange(() => this.handleEditorEventInRecentEditorsStack())); // if the service is created late enough that an editor is already opened // make sure to trigger the onActiveEditorChanged() to track the editor @@ -1102,7 +810,7 @@ export class HistoryService extends Disposable implements IHistoryService { this.editorHistoryListeners.clear(); } - getHistory(): Array { + getHistory(): ReadonlyArray { this.ensureHistoryLoaded(); return this.history.slice(0); @@ -1266,12 +974,10 @@ export class HistoryService extends Disposable implements IHistoryService { //#region Editor Most Recently Used History - private readonly mostRecentlyUsedOpenEditors = this._register(this.instantiationService.createInstance(EditorsHistory)); - - private recentlyUsedEditorsStack: IEditorIdentifier[] | undefined = undefined; + private recentlyUsedEditorsStack: ReadonlyArray | undefined = undefined; private recentlyUsedEditorsStackIndex = 0; - private recentlyUsedEditorsInGroupStack: IEditorIdentifier[] | undefined = undefined; + private recentlyUsedEditorsInGroupStack: ReadonlyArray | undefined = undefined; private recentlyUsedEditorsInGroupStackIndex = 0; private navigatingInRecentlyUsedEditorsStack = false; @@ -1309,15 +1015,15 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private ensureRecentlyUsedStack(indexModifier: (index: number) => number, groupId?: GroupIdentifier): [IEditorIdentifier[], number] { - let editors: IEditorIdentifier[]; + private ensureRecentlyUsedStack(indexModifier: (index: number) => number, groupId?: GroupIdentifier): [ReadonlyArray, number] { + let editors: ReadonlyArray; let index: number; const group = typeof groupId === 'number' ? this.editorGroupService.getGroup(groupId) : undefined; // Across groups if (!group) { - editors = this.recentlyUsedEditorsStack || this.mostRecentlyUsedOpenEditors.editors; + editors = this.recentlyUsedEditorsStack || this.editorService.mostRecentlyActiveEditors; index = this.recentlyUsedEditorsStackIndex; } @@ -1362,8 +1068,8 @@ export class HistoryService extends Disposable implements IHistoryService { } } - getMostRecentlyUsedOpenEditors(): Array { - return this.mostRecentlyUsedOpenEditors.editors; + getMostRecentlyUsedOpenEditors(): ReadonlyArray { + return this.editorService.mostRecentlyActiveEditors; } //#endregion diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index 76f695a27e1..efb224beb2f 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -57,7 +57,7 @@ export interface IHistoryService { /** * Get the entire history of editors that were opened. */ - getHistory(): Array; + getHistory(): ReadonlyArray; /** * Looking at the editor history, returns the workspace root of the last file that was @@ -91,5 +91,5 @@ export interface IHistoryService { /** * Get a list of most recently used editors that are open. */ - getMostRecentlyUsedOpenEditors(): Array; + getMostRecentlyUsedOpenEditors(): ReadonlyArray; } diff --git a/src/vs/workbench/services/history/test/history.test.ts b/src/vs/workbench/services/history/test/history.test.ts index e032184cb5a..67c5b2fca7e 100644 --- a/src/vs/workbench/services/history/test/history.test.ts +++ b/src/vs/workbench/services/history/test/history.test.ts @@ -11,18 +11,16 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorModel } from 'vs/platform/editor/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { EditorsHistory, HistoryService } from 'vs/workbench/services/history/browser/history'; -import { WillSaveStateReason } from 'vs/platform/storage/common/storage'; +import { HistoryService } from 'vs/workbench/services/history/browser/history'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; -import { timeout } from 'vs/base/common/async'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -180,418 +178,6 @@ suite('HistoryService', function () { part.dispose(); }); - - suite('EditorHistory', function () { - - test('basics (single group)', async () => { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; - - const history = new EditorsHistory(part, new TestStorageService()); - - let historyChangeListenerCalled = false; - const listener = history.onDidChange(() => { - historyChangeListenerCalled = true; - }); - - let currentHistory = history.editors; - assert.equal(currentHistory.length, 0); - assert.equal(historyChangeListenerCalled, false); - - const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); - - await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 1); - assert.equal(currentHistory[0].groupId, part.activeGroup.id); - assert.equal(currentHistory[0].editor, input1); - assert.equal(historyChangeListenerCalled, true); - - const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); - const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); - - await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, part.activeGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, part.activeGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, part.activeGroup.id); - assert.equal(currentHistory[2].editor, input1); - - await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, part.activeGroup.id); - assert.equal(currentHistory[0].editor, input2); - assert.equal(currentHistory[1].groupId, part.activeGroup.id); - assert.equal(currentHistory[1].editor, input3); - assert.equal(currentHistory[2].groupId, part.activeGroup.id); - assert.equal(currentHistory[2].editor, input1); - - historyChangeListenerCalled = false; - await part.activeGroup.closeEditor(input1); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 2); - assert.equal(currentHistory[0].groupId, part.activeGroup.id); - assert.equal(currentHistory[0].editor, input2); - assert.equal(currentHistory[1].groupId, part.activeGroup.id); - assert.equal(currentHistory[1].editor, input3); - assert.equal(historyChangeListenerCalled, true); - - await part.activeGroup.closeAllEditors(); - currentHistory = history.editors; - assert.equal(currentHistory.length, 0); - - part.dispose(); - listener.dispose(); - }); - - test('basics (multi group)', async () => { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; - - const rootGroup = part.activeGroup; - - const history = new EditorsHistory(part, new TestStorageService()); - - let currentHistory = history.editors; - assert.equal(currentHistory.length, 0); - - const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - - const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); - - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); - await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 2); - assert.equal(currentHistory[0].groupId, sideGroup.id); - assert.equal(currentHistory[0].editor, input1); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input1); - - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 2); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input1); - assert.equal(currentHistory[1].groupId, sideGroup.id); - assert.equal(currentHistory[1].editor, input1); - - // Opening an editor inactive should not change - // the most recent editor, but rather put it behind - const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); - - await rootGroup.openEditor(input2, EditorOptions.create({ inactive: true })); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input1); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, sideGroup.id); - assert.equal(currentHistory[2].editor, input1); - - await rootGroup.closeAllEditors(); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 1); - assert.equal(currentHistory[0].groupId, sideGroup.id); - assert.equal(currentHistory[0].editor, input1); - - await sideGroup.closeAllEditors(); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 0); - - part.dispose(); - }); - - test('copy group', async () => { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; - - const history = new EditorsHistory(part, new TestStorageService()); - - const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); - const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); - const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); - - const rootGroup = part.activeGroup; - - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); - - let currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); - - const copiedGroup = part.copyGroup(rootGroup, rootGroup, GroupDirection.RIGHT); - copiedGroup.setActive(true); - - currentHistory = history.editors; - assert.equal(currentHistory.length, 6); - assert.equal(currentHistory[0].groupId, copiedGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input3); - assert.equal(currentHistory[2].groupId, copiedGroup.id); - assert.equal(currentHistory[2].editor, input2); - assert.equal(currentHistory[3].groupId, copiedGroup.id); - assert.equal(currentHistory[3].editor, input1); - assert.equal(currentHistory[4].groupId, rootGroup.id); - assert.equal(currentHistory[4].editor, input2); - assert.equal(currentHistory[5].groupId, rootGroup.id); - assert.equal(currentHistory[5].editor, input1); - - part.dispose(); - }); - - test('initial editors are part of history and state is persisted & restored (single group)', async () => { - const instantiationService = workbenchInstantiationService(); - instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; - - const rootGroup = part.activeGroup; - - const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); - const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); - const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); - - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); - - const storage = new TestStorageService(); - const history = new EditorsHistory(part, storage); - await part.whenRestored; - - let currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); - - storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); - - const restoredHistory = new EditorsHistory(part, storage); - await part.whenRestored; - - currentHistory = restoredHistory.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); - - part.dispose(); - }); - - test('initial editors are part of history (multi group)', async () => { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; - - const rootGroup = part.activeGroup; - - const input1 = new HistoryTestEditorInput(URI.parse('foo://bar1')); - const input2 = new HistoryTestEditorInput(URI.parse('foo://bar2')); - const input3 = new HistoryTestEditorInput(URI.parse('foo://bar3')); - - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - - const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - await sideGroup.openEditor(input3, EditorOptions.create({ pinned: true })); - - const storage = new TestStorageService(); - const history = new EditorsHistory(part, storage); - await part.whenRestored; - - let currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, sideGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); - - storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); - - const restoredHistory = new EditorsHistory(part, storage); - await part.whenRestored; - - currentHistory = restoredHistory.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, sideGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); - - part.dispose(); - }); - - test('history does not restore editors that cannot be serialized', async () => { - const instantiationService = workbenchInstantiationService(); - instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; - - const rootGroup = part.activeGroup; - - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - - const storage = new TestStorageService(); - const history = new EditorsHistory(part, storage); - await part.whenRestored; - - let currentHistory = history.editors; - assert.equal(currentHistory.length, 1); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input1); - - storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); - - const restoredHistory = new EditorsHistory(part, storage); - await part.whenRestored; - - currentHistory = restoredHistory.editors; - assert.equal(currentHistory.length, 0); - - part.dispose(); - }); - - test('open next/previous recently used editor (single group)', async () => { - const [part, historyService] = await createServices(); - - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); - - await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - assert.equal(part.activeGroup.activeEditor, input1); - - await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - assert.equal(part.activeGroup.activeEditor, input2); - - historyService.openPreviouslyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input1); - - historyService.openNextRecentlyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input2); - - historyService.openPreviouslyUsedEditor(part.activeGroup.id); - assert.equal(part.activeGroup.activeEditor, input1); - - historyService.openNextRecentlyUsedEditor(part.activeGroup.id); - assert.equal(part.activeGroup.activeEditor, input2); - - part.dispose(); - }); - - test('open next/previous recently used editor (multi group)', async () => { - const [part, historyService] = await createServices(); - const rootGroup = part.activeGroup; - - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); - - const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - - historyService.openPreviouslyUsedEditor(); - assert.equal(part.activeGroup, rootGroup); - assert.equal(rootGroup.activeEditor, input1); - - historyService.openNextRecentlyUsedEditor(); - assert.equal(part.activeGroup, sideGroup); - assert.equal(sideGroup.activeEditor, input2); - - part.dispose(); - }); - - test('open next/previous recently is reset when other input opens', async () => { - const [part, historyService] = await createServices(); - - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); - const input3 = new TestEditorInput(URI.parse('foo://bar3')); - const input4 = new TestEditorInput(URI.parse('foo://bar4')); - - await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); - - historyService.openPreviouslyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input2); - - await timeout(0); - await part.activeGroup.openEditor(input4, EditorOptions.create({ pinned: true })); - - historyService.openPreviouslyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input2); - - historyService.openNextRecentlyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input4); - - part.dispose(); - }); - }); }); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 3d43c156d2c..bed812a691c 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -374,7 +374,7 @@ export class TestHistoryService implements IHistoryService { remove(_input: IEditorInput | IResourceInput): void { } clear(): void { } clearRecentlyOpened(): void { } - getHistory(): Array { return []; } + getHistory(): ReadonlyArray { return []; } openNextRecentlyUsedEditor(group?: GroupIdentifier): void { } openPreviouslyUsedEditor(group?: GroupIdentifier): void { } getMostRecentlyUsedOpenEditors(): Array { return []; } @@ -902,11 +902,13 @@ export class TestEditorService implements EditorServiceImpl { onDidVisibleEditorsChange: Event = Event.None; onDidCloseEditor: Event = Event.None; onDidOpenEditorFail: Event = Event.None; + onDidMostRecentlyActiveEditorsChange: Event = Event.None; activeControl!: IVisibleEditor; activeTextEditorWidget: any; activeEditor!: IEditorInput; editors: ReadonlyArray = []; + mostRecentlyActiveEditors: ReadonlyArray = []; visibleControls: ReadonlyArray = []; visibleTextEditorWidgets = []; visibleEditors: ReadonlyArray = []; From 5a94ca0bde2bf7ad7bee9310f81edbea90ace547 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 Dec 2019 12:31:44 +0100 Subject: [PATCH 155/241] enums for preferred theme settings --- .../themes/browser/workbenchThemeService.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 840c3706236..70bda875fac 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -167,8 +167,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { // update settings schema setting for theme specific settings this.colorThemeStore.onDidChange(async event => { // updates enum for the 'workbench.colorTheme` setting - colorThemeSettingSchema.enum = event.themes.map(t => t.settingsId); - colorThemeSettingSchema.enumDescriptions = event.themes.map(t => t.description || ''); + colorThemeSettingEnum.splice(0, colorThemeSettingEnum.length, ...event.themes.map(t => t.settingsId)); + colorThemeSettingEnumDescriptions.splice(0, colorThemeSettingEnumDescriptions.length, ...event.themes.map(t => t.description || '')); const themeSpecificWorkbenchColors: IJSONSchema = { properties: {} }; const themeSpecificTokenColors: IJSONSchema = { properties: {} }; @@ -699,28 +699,40 @@ registerFileIconThemeSchemas(); // Configuration: Themes const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); +const colorThemeSettingEnum: string[] = []; +const colorThemeSettingEnumDescriptions: string[] = []; + const colorThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."), default: DEFAULT_THEME_SETTING_VALUE, - enum: [], - enumDescriptions: [], + enum: colorThemeSettingEnum, + enumDescriptions: colorThemeSettingEnumDescriptions, errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), }; const preferredDarkThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', description: nls.localize('preferredDarkColorTheme', 'Specifies the preferred color theme for dark OS appearance when \'{0}\' is enabled.', DETECT_COLOR_SCHEME_SETTING), default: DEFAULT_THEME_DARK_SETTING_VALUE, + enum: colorThemeSettingEnum, + enumDescriptions: colorThemeSettingEnumDescriptions, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), }; const preferredLightThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', description: nls.localize('preferredLightColorTheme', 'Specifies the preferred color theme for light OS appearance when \'{0}\' is enabled.', DETECT_COLOR_SCHEME_SETTING), default: DEFAULT_THEME_LIGHT_SETTING_VALUE, + enum: colorThemeSettingEnum, + enumDescriptions: colorThemeSettingEnumDescriptions, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), }; const preferredHCThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', description: nls.localize('preferredHCColorTheme', 'Specifies the preferred color theme used in high contrast mode when \'{0}\' is enabled.', DETECT_HC_SETTING), default: DEFAULT_THEME_HC_SETTING_VALUE, + enum: colorThemeSettingEnum, + enumDescriptions: colorThemeSettingEnumDescriptions, + errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."), }; const detectColorSchemeSettingSchema: IConfigurationPropertySchema = { type: 'boolean', From 10950126e7bffad52d28892d6a15e14af967e893 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 19 Dec 2019 13:03:48 +0100 Subject: [PATCH 156/241] Have a setting for maximum number open files (editors) for #9872 --- .../browser/parts/editor/editorsObserver.ts | 101 +++- .../browser/workbench.contribution.ts | 16 + src/vs/workbench/common/editor.ts | 5 + .../test/browser/editorsObserver.test.ts | 464 ++++++++++-------- .../services/history/test/history.test.ts | 80 ++- 5 files changed, 447 insertions(+), 219 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index 9c6f9ce6d10..5dfd3b80e90 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -11,6 +11,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { coalesce } from 'vs/base/common/arrays'; import { LinkedMap, Touch } from 'vs/base/common/map'; +import { equals } from 'vs/base/common/objects'; interface ISerializedEditorsList { entries: ISerializedEditorIdentifier[]; @@ -27,6 +28,8 @@ interface ISerializedEditorIdentifier { * - the last editor in the list is the one most recently activated * - the first editor in the list is the one that was activated the longest time ago * - an editor that opens inactive will be placed behind the currently active editor + * + * The observer may start to close editors based on the workbench.editor.limit setting. */ export class EditorsObserver extends Disposable { @@ -54,6 +57,7 @@ export class EditorsObserver extends Disposable { private registerListeners(): void { this._register(this.storageService.onWillSaveState(() => this.saveState())); this._register(this.editorGroupsService.onDidAddGroup(group => this.onGroupAdded(group))); + this._register(this.editorGroupsService.onDidEditorPartOptionsChange(e => this.onDidEditorPartOptionsChange(e))); this.editorGroupsService.whenRestored.then(() => this.loadState()); } @@ -101,9 +105,13 @@ export class EditorsObserver extends Disposable { } // Editor opens: put it as second most recent + // + // Also check for maximum allowed number of editors and + // start to close oldest ones if needed. case GroupChangeKind.EDITOR_OPEN: { if (e.editor) { this.addMostRecentEditor(group, e.editor, false /* is not active */); + this.ensureOpenedEditorsLimit({ groupId: group.id, editor: e.editor }, group.id); } break; @@ -124,6 +132,18 @@ export class EditorsObserver extends Disposable { Event.once(group.onWillDispose)(() => dispose(groupDisposables)); } + private onDidEditorPartOptionsChange(event: IEditorPartOptionsChangeEvent): void { + if (!equals(event.newPartOptions.limit, event.oldPartOptions.limit)) { + const activeGroup = this.editorGroupsService.activeGroup; + let exclude: IEditorIdentifier | undefined = undefined; + if (activeGroup.activeEditor) { + exclude = { editor: activeGroup.activeEditor, groupId: activeGroup.id }; + } + + this.ensureOpenedEditorsLimit(exclude); + } + } + private addMostRecentEditor(group: IEditorGroup, editor: IEditorInput, isActive: boolean): void { const key = this.ensureKey(group, editor); const mostRecentEditor = this.mostRecentEditorsMap.first; @@ -193,6 +213,85 @@ export class EditorsObserver extends Disposable { return key; } + private async ensureOpenedEditorsLimit(exclude: IEditorIdentifier | undefined, groupId?: GroupIdentifier): Promise { + + // In editor group + if (this.editorGroupsService.partOptions.limit?.perEditorGroup) { + + // For specific editor groups + if (typeof groupId === 'number') { + const group = this.editorGroupsService.getGroup(groupId); + if (group) { + this.doEnsureOpenedEditorsLimit(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => ({ editor, groupId })), exclude); + } + } + + // For all editor groups + else { + for (const group of this.editorGroupsService.groups) { + await this.ensureOpenedEditorsLimit(exclude, group.id); + } + } + } + + // Across all editor groups + else { + this.doEnsureOpenedEditorsLimit(this.mostRecentEditorsMap.values(), exclude); + } + } + + private async doEnsureOpenedEditorsLimit(mostRecentEditors: IEditorIdentifier[], exclude?: IEditorIdentifier): Promise { + if (!this.editorGroupsService.partOptions.limit?.enabled) { + return; // only if enabled + } + + if ( + typeof this.editorGroupsService.partOptions.limit.value !== 'number' || + this.editorGroupsService.partOptions.limit.value <= 0 || + this.editorGroupsService.partOptions.limit.value >= mostRecentEditors.length + ) { + return; // only if opened editors exceed setting and is valid + } + + // Extract least recently used editors that can be closed + const leastRecentlyClosableEditors = mostRecentEditors.reverse().filter(({ editor, groupId }) => { + if (editor.isDirty()) { + return false; // not dirty editors + } + + if (exclude && editor === exclude.editor && groupId === exclude.groupId) { + return false; // never the editor that should be excluded + } + + return true; + }); + + // Close editors until we reached the limit again + let editorsToCloseCount = mostRecentEditors.length - this.editorGroupsService.partOptions.limit.value; + const mapGroupToEditorsToClose = new Map(); + for (const { groupId, editor } of leastRecentlyClosableEditors) { + let editorsInGroupToClose = mapGroupToEditorsToClose.get(groupId); + if (!editorsInGroupToClose) { + editorsInGroupToClose = []; + mapGroupToEditorsToClose.set(groupId, editorsInGroupToClose); + } + + editorsInGroupToClose.push(editor); + editorsToCloseCount--; + + if (editorsToCloseCount === 0) { + break; // limit reached + } + } + + for (const [groupId, editors] of mapGroupToEditorsToClose) { + const group = this.editorGroupsService.getGroup(groupId); + if (group) { + await group.closeEditors(editors, { preserveFocus: true }); + } + } + } + private saveState(): void { if (this.mostRecentEditorsMap.isEmpty()) { this.storageService.remove(EditorsObserver.STORAGE_KEY, StorageScope.WORKSPACE); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index be42ef48bee..fef0e3ac714 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -131,6 +131,22 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio 'default': true, 'description': nls.localize('centeredLayoutAutoResize', "Controls if the centered layout should automatically resize to maximum width when more than one group is open. Once only one group is open it will resize back to the original centered width.") }, + 'workbench.editor.limit.enabled': { + 'type': 'boolean', + 'default': false, + 'description': nls.localize('limitEditorsEnablement', "Controls if the number of opened editors should be limited or not. When enabled, less recently used editors that are not dirty will close to make space for newly opening editors.") + }, + 'workbench.editor.limit.value': { + 'type': 'number', + 'default': 10, + 'exclusiveMinimum': 0, + 'description': nls.localize('limitEditorsMaximum', "Controls the maximum number of opened editors. Use the `workbench.editor.limit.perEditorGroup` setting to control this limit per editor group or across all groups.") + }, + 'workbench.editor.limit.perEditorGroup': { + 'type': 'boolean', + 'default': false, + 'description': nls.localize('perEditorGroup', "Controls if the limit of maximum opened editors should apply per editor group or across all editor groups.") + }, 'workbench.commandPalette.history': { 'type': 'number', 'description': nls.localize('commandHistory', "Controls the number of recently used commands to keep in history for the command palette. Set to 0 to disable command history."), diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index f67d99b5082..d1b3ef8d803 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1171,6 +1171,11 @@ interface IEditorPartConfiguration { labelFormat?: 'default' | 'short' | 'medium' | 'long'; restoreViewState?: boolean; splitSizing?: 'split' | 'distribute'; + limit?: { + enabled?: boolean; + value?: number; + perEditorGroup?: boolean; + }; } export interface IEditorPartOptions extends IEditorPartConfiguration { diff --git a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts index f61b53c29e5..97bb769736a 100644 --- a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts @@ -11,21 +11,17 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { HistoryService } from 'vs/workbench/services/history/browser/history'; import { WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; -import { timeout } from 'vs/base/common/async'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver'; +import { timeout } from 'vs/base/common/async'; const TEST_EDITOR_ID = 'MyTestEditorForEditorsObserver'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorsObserver'; @@ -48,6 +44,8 @@ class TestEditorControl extends BaseEditor { class TestEditorInput extends EditorInput implements IFileEditorInput { + private dirty = false; + constructor(public resource: URI) { super(); } getTypeId() { return TEST_EDITOR_INPUT_ID; } @@ -60,6 +58,8 @@ class TestEditorInput extends EditorInput implements IFileEditorInput { setPreferredMode(mode: string) { } getResource(): URI { return this.resource; } setForceOpenAsBinary(): void { } + isDirty(): boolean { return this.dirty; } + setDirty(): void { this.dirty = true; } } class EditorsObserverTestEditorInput extends TestEditorInput { @@ -92,26 +92,6 @@ class EditorsObserverTestEditorInputFactory implements IEditorInputFactory { } } -async function createServices(): Promise<[EditorPart, HistoryService, EditorService]> { - const instantiationService = workbenchInstantiationService(); - - const part = instantiationService.createInstance(EditorPart); - part.create(document.createElement('div')); - part.layout(400, 300); - - await part.whenRestored; - - instantiationService.stub(IEditorGroupsService, part); - - const editorService = instantiationService.createInstance(EditorService); - instantiationService.stub(IEditorService, editorService); - - const historyService = instantiationService.createInstance(HistoryService); - instantiationService.stub(IHistoryService, historyService); - - return [part, historyService, editorService]; -} - suite('EditorsObserver', function () { let disposables: IDisposable[] = []; @@ -136,26 +116,26 @@ suite('EditorsObserver', function () { await part.whenRestored; - const history = new EditorsObserver(part, new TestStorageService()); + const observer = new EditorsObserver(part, new TestStorageService()); - let historyChangeListenerCalled = false; - const listener = history.onDidChange(() => { - historyChangeListenerCalled = true; + let observerChangeListenerCalled = false; + const listener = observer.onDidChange(() => { + observerChangeListenerCalled = true; }); - let currentHistory = history.editors; - assert.equal(currentHistory.length, 0); - assert.equal(historyChangeListenerCalled, false); + let currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 0); + assert.equal(observerChangeListenerCalled, false); const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - currentHistory = history.editors; - assert.equal(currentHistory.length, 1); - assert.equal(currentHistory[0].groupId, part.activeGroup.id); - assert.equal(currentHistory[0].editor, input1); - assert.equal(historyChangeListenerCalled, true); + currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 1); + assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id); + assert.equal(currentEditorsMRU[0].editor, input1); + assert.equal(observerChangeListenerCalled, true); const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); @@ -163,40 +143,40 @@ suite('EditorsObserver', function () { await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); - currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, part.activeGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, part.activeGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, part.activeGroup.id); - assert.equal(currentHistory[2].editor, input1); + currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 3); + assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id); + assert.equal(currentEditorsMRU[0].editor, input3); + assert.equal(currentEditorsMRU[1].groupId, part.activeGroup.id); + assert.equal(currentEditorsMRU[1].editor, input2); + assert.equal(currentEditorsMRU[2].groupId, part.activeGroup.id); + assert.equal(currentEditorsMRU[2].editor, input1); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, part.activeGroup.id); - assert.equal(currentHistory[0].editor, input2); - assert.equal(currentHistory[1].groupId, part.activeGroup.id); - assert.equal(currentHistory[1].editor, input3); - assert.equal(currentHistory[2].groupId, part.activeGroup.id); - assert.equal(currentHistory[2].editor, input1); + currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 3); + assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id); + assert.equal(currentEditorsMRU[0].editor, input2); + assert.equal(currentEditorsMRU[1].groupId, part.activeGroup.id); + assert.equal(currentEditorsMRU[1].editor, input3); + assert.equal(currentEditorsMRU[2].groupId, part.activeGroup.id); + assert.equal(currentEditorsMRU[2].editor, input1); - historyChangeListenerCalled = false; + observerChangeListenerCalled = false; await part.activeGroup.closeEditor(input1); - currentHistory = history.editors; - assert.equal(currentHistory.length, 2); - assert.equal(currentHistory[0].groupId, part.activeGroup.id); - assert.equal(currentHistory[0].editor, input2); - assert.equal(currentHistory[1].groupId, part.activeGroup.id); - assert.equal(currentHistory[1].editor, input3); - assert.equal(historyChangeListenerCalled, true); + currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 2); + assert.equal(currentEditorsMRU[0].groupId, part.activeGroup.id); + assert.equal(currentEditorsMRU[0].editor, input2); + assert.equal(currentEditorsMRU[1].groupId, part.activeGroup.id); + assert.equal(currentEditorsMRU[1].editor, input3); + assert.equal(observerChangeListenerCalled, true); await part.activeGroup.closeAllEditors(); - currentHistory = history.editors; - assert.equal(currentHistory.length, 0); + currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 0); part.dispose(); listener.dispose(); @@ -213,10 +193,10 @@ suite('EditorsObserver', function () { const rootGroup = part.activeGroup; - const history = new EditorsObserver(part, new TestStorageService()); + const observer = new EditorsObserver(part, new TestStorageService()); - let currentHistory = history.editors; - assert.equal(currentHistory.length, 0); + let currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 0); const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -225,21 +205,21 @@ suite('EditorsObserver', function () { await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); - currentHistory = history.editors; - assert.equal(currentHistory.length, 2); - assert.equal(currentHistory[0].groupId, sideGroup.id); - assert.equal(currentHistory[0].editor, input1); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input1); + currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 2); + assert.equal(currentEditorsMRU[0].groupId, sideGroup.id); + assert.equal(currentEditorsMRU[0].editor, input1); + assert.equal(currentEditorsMRU[1].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[1].editor, input1); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); - currentHistory = history.editors; - assert.equal(currentHistory.length, 2); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input1); - assert.equal(currentHistory[1].groupId, sideGroup.id); - assert.equal(currentHistory[1].editor, input1); + currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 2); + assert.equal(currentEditorsMRU[0].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[0].editor, input1); + assert.equal(currentEditorsMRU[1].groupId, sideGroup.id); + assert.equal(currentEditorsMRU[1].editor, input1); // Opening an editor inactive should not change // the most recent editor, but rather put it behind @@ -247,26 +227,26 @@ suite('EditorsObserver', function () { await rootGroup.openEditor(input2, EditorOptions.create({ inactive: true })); - currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input1); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, sideGroup.id); - assert.equal(currentHistory[2].editor, input1); + currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 3); + assert.equal(currentEditorsMRU[0].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[0].editor, input1); + assert.equal(currentEditorsMRU[1].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[1].editor, input2); + assert.equal(currentEditorsMRU[2].groupId, sideGroup.id); + assert.equal(currentEditorsMRU[2].editor, input1); await rootGroup.closeAllEditors(); - currentHistory = history.editors; - assert.equal(currentHistory.length, 1); - assert.equal(currentHistory[0].groupId, sideGroup.id); - assert.equal(currentHistory[0].editor, input1); + currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 1); + assert.equal(currentEditorsMRU[0].groupId, sideGroup.id); + assert.equal(currentEditorsMRU[0].editor, input1); await sideGroup.closeAllEditors(); - currentHistory = history.editors; - assert.equal(currentHistory.length, 0); + currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 0); part.dispose(); }); @@ -280,7 +260,7 @@ suite('EditorsObserver', function () { await part.whenRestored; - const history = new EditorsObserver(part, new TestStorageService()); + const observer = new EditorsObserver(part, new TestStorageService()); const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); @@ -292,37 +272,37 @@ suite('EditorsObserver', function () { await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); - let currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); + let currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 3); + assert.equal(currentEditorsMRU[0].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[0].editor, input3); + assert.equal(currentEditorsMRU[1].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[1].editor, input2); + assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[2].editor, input1); const copiedGroup = part.copyGroup(rootGroup, rootGroup, GroupDirection.RIGHT); copiedGroup.setActive(true); - currentHistory = history.editors; - assert.equal(currentHistory.length, 6); - assert.equal(currentHistory[0].groupId, copiedGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input3); - assert.equal(currentHistory[2].groupId, copiedGroup.id); - assert.equal(currentHistory[2].editor, input2); - assert.equal(currentHistory[3].groupId, copiedGroup.id); - assert.equal(currentHistory[3].editor, input1); - assert.equal(currentHistory[4].groupId, rootGroup.id); - assert.equal(currentHistory[4].editor, input2); - assert.equal(currentHistory[5].groupId, rootGroup.id); - assert.equal(currentHistory[5].editor, input1); + currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 6); + assert.equal(currentEditorsMRU[0].groupId, copiedGroup.id); + assert.equal(currentEditorsMRU[0].editor, input3); + assert.equal(currentEditorsMRU[1].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[1].editor, input3); + assert.equal(currentEditorsMRU[2].groupId, copiedGroup.id); + assert.equal(currentEditorsMRU[2].editor, input2); + assert.equal(currentEditorsMRU[3].groupId, copiedGroup.id); + assert.equal(currentEditorsMRU[3].editor, input1); + assert.equal(currentEditorsMRU[4].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[4].editor, input2); + assert.equal(currentEditorsMRU[5].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[5].editor, input1); part.dispose(); }); - test('initial editors are part of history and state is persisted & restored (single group)', async () => { + test('initial editors are part of observer and state is persisted & restored (single group)', async () => { const instantiationService = workbenchInstantiationService(); instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); @@ -343,36 +323,36 @@ suite('EditorsObserver', function () { await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); const storage = new TestStorageService(); - const history = new EditorsObserver(part, storage); + const observer = new EditorsObserver(part, storage); await part.whenRestored; - let currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); + let currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 3); + assert.equal(currentEditorsMRU[0].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[0].editor, input3); + assert.equal(currentEditorsMRU[1].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[1].editor, input2); + assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[2].editor, input1); storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); - const restoredHistory = new EditorsObserver(part, storage); + const restoredObserver = new EditorsObserver(part, storage); await part.whenRestored; - currentHistory = restoredHistory.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); + currentEditorsMRU = restoredObserver.editors; + assert.equal(currentEditorsMRU.length, 3); + assert.equal(currentEditorsMRU[0].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[0].editor, input3); + assert.equal(currentEditorsMRU[1].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[1].editor, input2); + assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[2].editor, input1); part.dispose(); }); - test('initial editors are part of history (multi group)', async () => { + test('initial editors are part of observer (multi group)', async () => { const instantiationService = workbenchInstantiationService(); const part = instantiationService.createInstance(EditorPart); @@ -394,36 +374,36 @@ suite('EditorsObserver', function () { await sideGroup.openEditor(input3, EditorOptions.create({ pinned: true })); const storage = new TestStorageService(); - const history = new EditorsObserver(part, storage); + const observer = new EditorsObserver(part, storage); await part.whenRestored; - let currentHistory = history.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, sideGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); + let currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 3); + assert.equal(currentEditorsMRU[0].groupId, sideGroup.id); + assert.equal(currentEditorsMRU[0].editor, input3); + assert.equal(currentEditorsMRU[1].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[1].editor, input2); + assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[2].editor, input1); storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); - const restoredHistory = new EditorsObserver(part, storage); + const restoredObserver = new EditorsObserver(part, storage); await part.whenRestored; - currentHistory = restoredHistory.editors; - assert.equal(currentHistory.length, 3); - assert.equal(currentHistory[0].groupId, sideGroup.id); - assert.equal(currentHistory[0].editor, input3); - assert.equal(currentHistory[1].groupId, rootGroup.id); - assert.equal(currentHistory[1].editor, input2); - assert.equal(currentHistory[2].groupId, rootGroup.id); - assert.equal(currentHistory[2].editor, input1); + currentEditorsMRU = restoredObserver.editors; + assert.equal(currentEditorsMRU.length, 3); + assert.equal(currentEditorsMRU[0].groupId, sideGroup.id); + assert.equal(currentEditorsMRU[0].editor, input3); + assert.equal(currentEditorsMRU[1].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[1].editor, input2); + assert.equal(currentEditorsMRU[2].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[2].editor, input1); part.dispose(); }); - test('history does not restore editors that cannot be serialized', async () => { + test('observer does not restore editors that cannot be serialized', async () => { const instantiationService = workbenchInstantiationService(); instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); @@ -440,99 +420,149 @@ suite('EditorsObserver', function () { await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); const storage = new TestStorageService(); - const history = new EditorsObserver(part, storage); + const observer = new EditorsObserver(part, storage); await part.whenRestored; - let currentHistory = history.editors; - assert.equal(currentHistory.length, 1); - assert.equal(currentHistory[0].groupId, rootGroup.id); - assert.equal(currentHistory[0].editor, input1); + let currentEditorsMRU = observer.editors; + assert.equal(currentEditorsMRU.length, 1); + assert.equal(currentEditorsMRU[0].groupId, rootGroup.id); + assert.equal(currentEditorsMRU[0].editor, input1); storage._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); - const restoredHistory = new EditorsObserver(part, storage); + const restoredObserver = new EditorsObserver(part, storage); await part.whenRestored; - currentHistory = restoredHistory.editors; - assert.equal(currentHistory.length, 0); + currentEditorsMRU = restoredObserver.editors; + assert.equal(currentEditorsMRU.length, 0); part.dispose(); }); - test('open next/previous recently used editor (single group)', async () => { - const [part, historyService] = await createServices(); + test('observer closes editors when limit reached (across all groups)', async () => { + const instantiationService = workbenchInstantiationService(); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); - await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - assert.equal(part.activeGroup.activeEditor, input1); + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); - await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - assert.equal(part.activeGroup.activeEditor, input2); + part.enforcePartOptions({ limit: { enabled: true, value: 3 } }); - historyService.openPreviouslyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input1); + await part.whenRestored; - historyService.openNextRecentlyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input2); + const storage = new TestStorageService(); + const observer = new EditorsObserver(part, storage); - historyService.openPreviouslyUsedEditor(part.activeGroup.id); - assert.equal(part.activeGroup.activeEditor, input1); - - historyService.openNextRecentlyUsedEditor(part.activeGroup.id); - assert.equal(part.activeGroup.activeEditor, input2); - - part.dispose(); - }); - - test('open next/previous recently used editor (multi group)', async () => { - const [part, historyService] = await createServices(); const rootGroup = part.activeGroup; - - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); - const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - - historyService.openPreviouslyUsedEditor(); - assert.equal(part.activeGroup, rootGroup); - assert.equal(rootGroup.activeEditor, input1); - - historyService.openNextRecentlyUsedEditor(); - assert.equal(part.activeGroup, sideGroup); - assert.equal(sideGroup.activeEditor, input2); - - part.dispose(); - }); - - test('open next/previous recently is reset when other input opens', async () => { - const [part, historyService] = await createServices(); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); const input2 = new TestEditorInput(URI.parse('foo://bar2')); const input3 = new TestEditorInput(URI.parse('foo://bar3')); const input4 = new TestEditorInput(URI.parse('foo://bar4')); - await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); - await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true })); - historyService.openPreviouslyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input2); + assert.equal(rootGroup.count, 3); + assert.equal(rootGroup.isOpened(input1), false); + assert.equal(rootGroup.isOpened(input2), true); + assert.equal(rootGroup.isOpened(input3), true); + assert.equal(rootGroup.isOpened(input4), true); + + input2.setDirty(); + part.enforcePartOptions({ limit: { enabled: true, value: 1 } }); await timeout(0); - await part.activeGroup.openEditor(input4, EditorOptions.create({ pinned: true })); - historyService.openPreviouslyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input2); + assert.equal(rootGroup.count, 2); + assert.equal(rootGroup.isOpened(input1), false); + assert.equal(rootGroup.isOpened(input2), true); // dirty + assert.equal(rootGroup.isOpened(input3), false); + assert.equal(rootGroup.isOpened(input4), true); - historyService.openNextRecentlyUsedEditor(); - assert.equal(part.activeGroup.activeEditor, input4); + const input5 = new TestEditorInput(URI.parse('foo://bar5')); + await sideGroup.openEditor(input5, EditorOptions.create({ pinned: true })); + assert.equal(rootGroup.count, 1); + assert.equal(rootGroup.isOpened(input1), false); + assert.equal(rootGroup.isOpened(input2), true); // dirty + assert.equal(rootGroup.isOpened(input3), false); + assert.equal(rootGroup.isOpened(input4), false); + + assert.equal(sideGroup.isOpened(input5), true); + + observer.dispose(); + part.dispose(); + }); + + test('observer closes editors when limit reached (in group)', async () => { + const instantiationService = workbenchInstantiationService(); + + instantiationService.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + part.enforcePartOptions({ limit: { enabled: true, value: 3, perEditorGroup: true } }); + + await part.whenRestored; + + const storage = new TestStorageService(); + const observer = new EditorsObserver(part, storage); + + const rootGroup = part.activeGroup; + const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input3 = new TestEditorInput(URI.parse('foo://bar3')); + const input4 = new TestEditorInput(URI.parse('foo://bar4')); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + await rootGroup.openEditor(input4, EditorOptions.create({ pinned: true })); + + assert.equal(rootGroup.count, 3); + assert.equal(rootGroup.isOpened(input1), false); + assert.equal(rootGroup.isOpened(input2), true); + assert.equal(rootGroup.isOpened(input3), true); + assert.equal(rootGroup.isOpened(input4), true); + + await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await sideGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + await sideGroup.openEditor(input4, EditorOptions.create({ pinned: true })); + + assert.equal(sideGroup.count, 3); + assert.equal(sideGroup.isOpened(input1), false); + assert.equal(sideGroup.isOpened(input2), true); + assert.equal(sideGroup.isOpened(input3), true); + assert.equal(sideGroup.isOpened(input4), true); + + part.enforcePartOptions({ limit: { enabled: true, value: 1, perEditorGroup: true } }); + + await timeout(10); + + assert.equal(rootGroup.count, 1); + assert.equal(rootGroup.isOpened(input1), false); + assert.equal(rootGroup.isOpened(input2), false); + assert.equal(rootGroup.isOpened(input3), false); + assert.equal(rootGroup.isOpened(input4), true); + + assert.equal(sideGroup.count, 1); + assert.equal(sideGroup.isOpened(input1), false); + assert.equal(sideGroup.isOpened(input2), false); + assert.equal(sideGroup.isOpened(input3), false); + assert.equal(sideGroup.isOpened(input4), true); + + observer.dispose(); part.dispose(); }); }); diff --git a/src/vs/workbench/services/history/test/history.test.ts b/src/vs/workbench/services/history/test/history.test.ts index 67c5b2fca7e..0ccce7b7c28 100644 --- a/src/vs/workbench/services/history/test/history.test.ts +++ b/src/vs/workbench/services/history/test/history.test.ts @@ -11,7 +11,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, GroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -23,6 +23,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { timeout } from 'vs/base/common/async'; const TEST_EDITOR_ID = 'MyTestEditorForEditorHistory'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForHistoyService'; @@ -178,6 +179,83 @@ suite('HistoryService', function () { part.dispose(); }); + + test('open next/previous recently used editor (single group)', async () => { + const [part, historyService] = await createServices(); + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + assert.equal(part.activeGroup.activeEditor, input1); + + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + assert.equal(part.activeGroup.activeEditor, input2); + + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input1); + + historyService.openNextRecentlyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input2); + + historyService.openPreviouslyUsedEditor(part.activeGroup.id); + assert.equal(part.activeGroup.activeEditor, input1); + + historyService.openNextRecentlyUsedEditor(part.activeGroup.id); + assert.equal(part.activeGroup.activeEditor, input2); + + part.dispose(); + }); + + test('open next/previous recently used editor (multi group)', async () => { + const [part, historyService] = await createServices(); + const rootGroup = part.activeGroup; + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + + const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); + + await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await sideGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup, rootGroup); + assert.equal(rootGroup.activeEditor, input1); + + historyService.openNextRecentlyUsedEditor(); + assert.equal(part.activeGroup, sideGroup); + assert.equal(sideGroup.activeEditor, input2); + + part.dispose(); + }); + + test('open next/previous recently is reset when other input opens', async () => { + const [part, historyService] = await createServices(); + + const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input3 = new TestEditorInput(URI.parse('foo://bar3')); + const input4 = new TestEditorInput(URI.parse('foo://bar4')); + + await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); + await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); + await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); + + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input2); + + await timeout(0); + await part.activeGroup.openEditor(input4, EditorOptions.create({ pinned: true })); + + historyService.openPreviouslyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input2); + + historyService.openNextRecentlyUsedEditor(); + assert.equal(part.activeGroup.activeEditor, input4); + + part.dispose(); + }); }); From cd976be1b0370be6b0447858f3f220c835c3e89f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 Dec 2019 13:12:11 +0100 Subject: [PATCH 157/241] Fix #87355 --- .../platform/userDataSync/common/userDataSyncService.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index db118026b58..b906b66aafa 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -47,7 +47,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (this.userDataSyncStoreService.userDataSyncStore) { this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus())); - this._register(authTokenService.onDidChangeStatus(() => this.onDidChangeAuthTokenStatus())); } this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal)); @@ -118,11 +117,4 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } return null; } - - private onDidChangeAuthTokenStatus(): void { - if (this.authTokenService.status === AuthTokenStatus.SignedOut) { - this.stop(); - } - } - } From c1c46d00d782cbf5c8838152abcc1a68112b917c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 19 Dec 2019 15:39:08 +0100 Subject: [PATCH 158/241] editors observer :lipstick: --- .../browser/parts/editor/editorsObserver.ts | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index 5dfd3b80e90..c15fa2c10bb 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -33,7 +33,7 @@ interface ISerializedEditorIdentifier { */ export class EditorsObserver extends Disposable { - private static readonly STORAGE_KEY = 'editorsObserver.state'; + private static readonly STORAGE_KEY = 'editors.mru'; private readonly keyMap = new Map>(); private readonly mostRecentEditorsMap = new LinkedMap(); @@ -214,6 +214,15 @@ export class EditorsObserver extends Disposable { } private async ensureOpenedEditorsLimit(exclude: IEditorIdentifier | undefined, groupId?: GroupIdentifier): Promise { + if ( + !this.editorGroupsService.partOptions.limit?.enabled || + typeof this.editorGroupsService.partOptions.limit.value !== 'number' || + this.editorGroupsService.partOptions.limit.value <= 0 + ) { + return; // return early if not enabled or invalid + } + + const limit = this.editorGroupsService.partOptions.limit.value; // In editor group if (this.editorGroupsService.partOptions.limit?.perEditorGroup) { @@ -222,7 +231,7 @@ export class EditorsObserver extends Disposable { if (typeof groupId === 'number') { const group = this.editorGroupsService.getGroup(groupId); if (group) { - this.doEnsureOpenedEditorsLimit(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => ({ editor, groupId })), exclude); + this.doEnsureOpenedEditorsLimit(limit, group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => ({ editor, groupId })), exclude); } } @@ -236,21 +245,13 @@ export class EditorsObserver extends Disposable { // Across all editor groups else { - this.doEnsureOpenedEditorsLimit(this.mostRecentEditorsMap.values(), exclude); + this.doEnsureOpenedEditorsLimit(limit, this.mostRecentEditorsMap.values(), exclude); } } - private async doEnsureOpenedEditorsLimit(mostRecentEditors: IEditorIdentifier[], exclude?: IEditorIdentifier): Promise { - if (!this.editorGroupsService.partOptions.limit?.enabled) { - return; // only if enabled - } - - if ( - typeof this.editorGroupsService.partOptions.limit.value !== 'number' || - this.editorGroupsService.partOptions.limit.value <= 0 || - this.editorGroupsService.partOptions.limit.value >= mostRecentEditors.length - ) { - return; // only if opened editors exceed setting and is valid + private async doEnsureOpenedEditorsLimit(limit: number, mostRecentEditors: IEditorIdentifier[], exclude?: IEditorIdentifier): Promise { + if (limit >= mostRecentEditors.length) { + return; // only if opened editors exceed setting and is valid and enabled } // Extract least recently used editors that can be closed @@ -267,7 +268,7 @@ export class EditorsObserver extends Disposable { }); // Close editors until we reached the limit again - let editorsToCloseCount = mostRecentEditors.length - this.editorGroupsService.partOptions.limit.value; + let editorsToCloseCount = mostRecentEditors.length - limit; const mapGroupToEditorsToClose = new Map(); for (const { groupId, editor } of leastRecentlyClosableEditors) { let editorsInGroupToClose = mapGroupToEditorsToClose.get(groupId); From a4f610ec282c421eb43688857af53bd2775acc1b Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 Dec 2019 15:47:01 +0100 Subject: [PATCH 159/241] update --- .../themes/browser/workbenchThemeService.ts | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 70bda875fac..b9af024b8ab 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -53,6 +53,7 @@ const DEFAULT_THEME_HC_SETTING_VALUE = 'Default High Contrast'; const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; const PERSISTED_ICON_THEME_STORAGE_KEY = 'iconThemeData'; +const PERSISTED_OS_COLOR_SCHEME = 'osColorScheme'; const defaultThemeExtensionId = 'vscode-theme-defaults'; const oldDefaultThemeExtensionId = 'vscode-theme-colorful-defaults'; @@ -273,16 +274,13 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } let theme = await this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID); - const preferredType = this.getPreferredColorScheme(); - let settingsTarget: undefined | 'auto' = undefined; - if (preferredType && theme && theme.type !== preferredType) { - const preferedTheme = await this.getPreferredColorTheme(preferredType); - if (preferedTheme) { - theme = preferedTheme; - settingsTarget = 'auto'; - } + + const persistedColorScheme = this.storageService.get(PERSISTED_OS_COLOR_SCHEME, StorageScope.GLOBAL); + const preferredColorScheme = this.getPreferredColorScheme(); + if (persistedColorScheme && preferredColorScheme && persistedColorScheme !== preferredColorScheme) { + return this.applyPreferredColorTheme(preferredColorScheme); } - return this.setColorTheme(theme && theme.id, settingsTarget); + return this.setColorTheme(theme && theme.id, undefined); }; const initializeIconTheme = async () => { @@ -312,10 +310,16 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } if (e.affectsConfiguration(DETECT_COLOR_SCHEME_SETTING)) { - let autoSwitchColorTheme = this.configurationService.getValue(DETECT_COLOR_SCHEME_SETTING); - if (autoSwitchColorTheme) { - this.preferredSchemeUpdated(); - } + this.preferredSchemeUpdated(); + } + if (e.affectsConfiguration(PREFERRED_DARK_THEME_SETTING) && this.getPreferredColorScheme() === DARK) { + this.applyPreferredColorTheme(DARK); + } + if (e.affectsConfiguration(PREFERRED_LIGHT_THEME_SETTING) && this.getPreferredColorScheme() === LIGHT) { + this.applyPreferredColorTheme(LIGHT); + } + if (e.affectsConfiguration(PREFERRED_HC_THEME_SETTING) && this.getPreferredColorScheme() === HIGH_CONTRAST) { + this.applyPreferredColorTheme(HIGH_CONTRAST); } if (e.affectsConfiguration(ICON_THEME_SETTING)) { let iconThemeSetting = this.configurationService.getValue(ICON_THEME_SETTING); @@ -355,11 +359,9 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private async preferredSchemeUpdated() { const scheme = this.getPreferredColorScheme(); - if (scheme && this.currentColorTheme.type !== scheme) { - const preferedTheme = await this.getPreferredColorTheme(scheme); - if (preferedTheme) { - return this.setColorTheme(preferedTheme.id, 'auto'); - } + this.storageService.store(PERSISTED_OS_COLOR_SCHEME, scheme, StorageScope.GLOBAL); + if (scheme) { + return this.applyPreferredColorTheme(scheme); } return undefined; } @@ -379,13 +381,16 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return undefined; } - private getPreferredColorTheme(type: ThemeType): Promise { + private async applyPreferredColorTheme(type: ThemeType): Promise { const settingId = type === DARK ? PREFERRED_DARK_THEME_SETTING : type === LIGHT ? PREFERRED_LIGHT_THEME_SETTING : PREFERRED_HC_THEME_SETTING; const themeSettingId = this.configurationService.getValue(settingId); if (themeSettingId) { - return this.colorThemeStore.findThemeDataBySettingsId(themeSettingId, undefined); + const theme = await this.colorThemeStore.findThemeDataBySettingsId(themeSettingId, undefined); + if (theme) { + return this.setColorTheme(theme.id, 'auto'); + } } - return Promise.resolve(undefined); + return Promise.resolve(null); } public getColorTheme(): IColorTheme { @@ -736,7 +741,7 @@ const preferredHCThemeSettingSchema: IConfigurationPropertySchema = { }; const detectColorSchemeSettingSchema: IConfigurationPropertySchema = { type: 'boolean', - description: nls.localize('detectColorScheme', 'If set, use the prefered color theme based on the OS appearance.'), + description: nls.localize('detectColorScheme', 'If set, automatically switch to the preferred color theme based on the OS appearance.'), default: true }; From 10091e7300581b075ba8c2773187c38ffeeac266 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 Dec 2019 16:22:21 +0100 Subject: [PATCH 160/241] polish --- .../themes/browser/workbenchThemeService.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index b9af024b8ab..ed8db767922 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -302,7 +302,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (e.affectsConfiguration(COLOR_THEME_SETTING)) { let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { + this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => { if (theme) { this.setColorTheme(theme.id, undefined); } @@ -310,7 +310,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } if (e.affectsConfiguration(DETECT_COLOR_SCHEME_SETTING)) { - this.preferredSchemeUpdated(); + this.handlePreferredSchemeUpdated(); } if (e.affectsConfiguration(PREFERRED_DARK_THEME_SETTING) && this.getPreferredColorScheme() === DARK) { this.applyPreferredColorTheme(DARK); @@ -354,10 +354,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { // preferred scheme handling private installPreferredSchemeListener() { - window.matchMedia('(prefers-color-scheme: dark)').addListener(async () => this.preferredSchemeUpdated()); + window.matchMedia('(prefers-color-scheme: dark)').addListener(async () => this.handlePreferredSchemeUpdated()); } - private async preferredSchemeUpdated() { + private async handlePreferredSchemeUpdated() { const scheme = this.getPreferredColorScheme(); this.storageService.store(PERSISTED_OS_COLOR_SCHEME, scheme, StorageScope.GLOBAL); if (scheme) { @@ -390,7 +390,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.setColorTheme(theme.id, 'auto'); } } - return Promise.resolve(null); + return null; } public getColorTheme(): IColorTheme { @@ -419,7 +419,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (!themeData) { return null; } - this.configurationService.updateValue(COLOR_THEME_SETTING, themeData.settingsId); return themeData.ensureLoaded(this.extensionResourceLoaderService).then(_ => { if (themeId === this.currentColorTheme.id && !this.currentColorTheme.isLoaded && this.currentColorTheme.hasEqualData(themeData)) { this.currentColorTheme.clearCaches(); @@ -453,7 +452,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { public restoreColorTheme() { let colorThemeSetting = this.configurationService.getValue(COLOR_THEME_SETTING); if (colorThemeSetting !== this.currentColorTheme.settingsId) { - this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { + this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, undefined).then(theme => { if (theme) { this.setColorTheme(theme.id, undefined); } From afde09fe31642a6e09825ffe15825722cfd2c681 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 Dec 2019 16:40:46 +0100 Subject: [PATCH 161/241] add checkProposedApiEnabled checks --- src/vs/workbench/api/common/extHost.api.impl.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index e958b105b24..87558e6d0ec 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -552,9 +552,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostQuickOpen.createInputBox(extension.identifier); }, get activeColorTheme(): vscode.ColorTheme { + checkProposedApiEnabled(extension); return extHostTheming.activeColorTheme; }, onDidChangeActiveColorTheme(listener, thisArg?, disposables?) { + checkProposedApiEnabled(extension); return extHostTheming.onDidChangeActiveColorTheme(listener, thisArg, disposables); } }; From a30f7ec06cd30fc017a5e02ce5a071c58e71981e Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 Dec 2019 16:52:49 +0100 Subject: [PATCH 162/241] separate color --- src/vs/editor/contrib/folding/foldingModel.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index 4102adab824..33ac4318dd9 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -7,7 +7,8 @@ import { ITextModel, IModelDecorationOptions, IModelDeltaDecoration, IModelDecor import { Event, Emitter } from 'vs/base/common/event'; import { FoldingRegions, ILineRange, FoldingRegion } from './foldingRanges'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { editorSelectionBackground } from 'vs/platform/theme/common/colorRegistry'; +import { registerColor, editorSelectionBackground, darken, lighten } from 'vs/platform/theme/common/colorRegistry'; +import * as nls from 'vs/nls'; export interface IDecorationProvider { getDecorationOption(isCollapsed: boolean): IModelDecorationOptions; @@ -91,12 +92,6 @@ export class FoldingModel { endColumn: maxColumn }; newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed) }); - }; - registerThemingParticipant((theme, collector) => { - const highlightCollapsedBackground = theme.getColor(editorSelectionBackground); - if (highlightCollapsedBackground) { - collector.addRule(`.monaco-editor .folded-background { background-color: ${highlightCollapsedBackground}; }`); - } }); let i = 0; let nextCollapsed = () => { @@ -362,3 +357,12 @@ export function setCollapseStateForType(foldingModel: FoldingModel, type: string } foldingModel.toggleCollapseState(toToggle); } + +export const foldBackgroundBackground = registerColor('editor.foldBackground', { light: darken(editorSelectionBackground, 0.6), dark: lighten(editorSelectionBackground, 0.6), hc: null }, nls.localize('editorSelectionBackground', "Color of the editor selection.")); + +registerThemingParticipant((theme, collector) => { + const foldBackground = theme.getColor(foldBackgroundBackground); + if (foldBackground) { + collector.addRule(`.monaco-editor .folded-background { background-color: ${foldBackground}; }`); + } +}); From e8d46aa75b1315bbad324de41eac986fca3205f6 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 19 Dec 2019 16:56:19 +0100 Subject: [PATCH 163/241] Add refresh button to ports view to refresh candidate Fix a bad leak in the ports view Fix the ip address of candidate ports --- .../api/node/extHostTunnelService.ts | 13 +- .../contrib/remote/browser/tunnelView.ts | 140 ++++++++++-------- .../remote/common/remoteExplorerService.ts | 59 ++++++-- 3 files changed, 138 insertions(+), 74 deletions(-) diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index ee35a7d80b0..794d8863dc9 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -178,7 +178,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe const address = row.local_address.split(':'); return { socket: parseInt(row.inode, 10), - ip: address[0], + ip: this.parseIpAddress(address[0]), port: parseInt(address[1], 16) }; }).map(port => [port.port, port]) @@ -186,6 +186,17 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe ]; } + private parseIpAddress(hex: string): string { + let result = ''; + for (let i = hex.length - 2; (i >= 0); i -= 2) { + result += parseInt(hex.substr(i, 2), 16); + if (i !== 0) { + result += '.'; + } + } + return result; + } + private loadConnectionTable(stdout: string): Record[] { const lines = stdout.trim().split('\n'); const names = lines.shift()!.trim().split(/\s+/) diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index daedf64538d..c3274afa90d 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -20,13 +20,13 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { Disposable, IDisposable, toDisposable, MutableDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, MutableDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { ActionBar, ActionViewItem, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IMenuService, MenuId, IMenu, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions'; import { createAndFillInContextMenuActions, createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IRemoteExplorerService, TunnelModel, MakeAddress } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService, TunnelModel, MakeAddress, TunnelType, ITunnelItem } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -55,6 +55,7 @@ export interface ITunnelViewModel { readonly forwarded: TunnelItem[]; readonly detected: TunnelItem[]; readonly candidates: Promise; + readonly input: ITunnelItem | ITunnelGroup | undefined; groups(): Promise; } @@ -62,6 +63,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { private _onForwardedPortsChanged: Emitter = new Emitter(); public onForwardedPortsChanged: Event = this._onForwardedPortsChanged.event; private model: TunnelModel; + private _input: ITunnelItem | ITunnelGroup | undefined; constructor( @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService) { @@ -70,6 +72,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { this._register(this.model.onForwardPort(() => this._onForwardedPortsChanged.fire())); this._register(this.model.onClosePort(() => this._onForwardedPortsChanged.fire())); this._register(this.model.onPortName(() => this._onForwardedPortsChanged.fire())); + this._register(this.model.onCandidatesChanged(() => this._onForwardedPortsChanged.fire())); } async groups(): Promise { @@ -96,10 +99,13 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { items: candidates }); } - groups.push({ - label: nls.localize('remote.tunnelsView.add', "Forward a Port..."), - tunnelType: TunnelType.Add, - }); + if (!this._input) { + this._input = { + label: nls.localize('remote.tunnelsView.add', "Forward a Port..."), + tunnelType: TunnelType.Add, + }; + } + groups.push(this._input); return groups; } @@ -128,6 +134,10 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { }); } + get input(): ITunnelItem | ITunnelGroup | undefined { + return this._input; + } + dispose() { super.dispose(); } @@ -197,7 +207,7 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer; } -interface ITunnelItem { - tunnelType: TunnelType; - remoteHost: string; - remotePort: number; - localAddress?: string; - name?: string; - closeable?: boolean; - readonly description?: string; - readonly label: string; -} - class TunnelItem implements ITunnelItem { constructor( public tunnelType: TunnelType, @@ -472,12 +465,12 @@ export class TunnelPanel extends ViewPane { this._register(Event.debounce(navigator.onDidOpenResource, (last, event) => event, 75, true)(e => { if (e.element && (e.element.tunnelType === TunnelType.Add)) { - this.commandService.executeCommand(ForwardPortAction.ID, 'inline add'); + this.commandService.executeCommand(ForwardPortAction.INLINE_ID); } })); this._register(this.remoteExplorerService.onDidChangeEditable(async e => { - const isEditing = !!this.remoteExplorerService.getEditableData(e.host, e.port); + const isEditing = !!this.remoteExplorerService.getEditableData(e); if (!isEditing) { dom.removeClass(treeContainer, 'highlight'); @@ -487,6 +480,7 @@ export class TunnelPanel extends ViewPane { if (isEditing) { dom.addClass(treeContainer, 'highlight'); + this.tree.reveal(e ? e : this.viewModel.input); } else { this.tree.domFocus(); } @@ -494,8 +488,7 @@ export class TunnelPanel extends ViewPane { } private get contributedContextMenu(): IMenu { - const contributedContextMenu = this.menuService.createMenu(MenuId.TunnelContext, this.tree.contextKeyService); - this._register(contributedContextMenu); + const contributedContextMenu = this._register(this.menuService.createMenu(MenuId.TunnelContext, this.tree.contextKeyService)); return contributedContextMenu; } @@ -578,12 +571,12 @@ namespace LabelTunnelAction { return async (accessor, arg) => { if (arg instanceof TunnelItem) { const remoteExplorerService = accessor.get(IRemoteExplorerService); - remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, { + remoteExplorerService.setEditable(arg, { onFinish: (value, success) => { if (success) { remoteExplorerService.tunnelModel.name(arg.remoteHost, arg.remotePort, value); } - remoteExplorerService.setEditable(arg.remoteHost, arg.remotePort, null); + remoteExplorerService.setEditable(arg, null); }, validationMessage: () => null, placeholder: nls.localize('remote.tunnelsView.labelPlaceholder', "Port label"), @@ -596,7 +589,8 @@ namespace LabelTunnelAction { } namespace ForwardPortAction { - export const ID = 'remote.tunnel.forward'; + export const INLINE_ID = 'remote.tunnel.forwardInline'; + export const COMMANDPALETTE_ID = 'remote.tunnel.forwardCommandPalette'; export const LABEL = nls.localize('remote.tunnel.forward', "Forward a Port"); const forwardPrompt = nls.localize('remote.tunnel.forwardPrompt', "Port number or address (eg. 3000 or 10.10.10.10:2000)."); @@ -615,35 +609,40 @@ namespace ForwardPortAction { return null; } - export function handler(): ICommandHandler { + export function inlineHandler(): ICommandHandler { return async (accessor, arg) => { const remoteExplorerService = accessor.get(IRemoteExplorerService); if (arg instanceof TunnelItem) { remoteExplorerService.forward({ host: arg.remoteHost, port: arg.remotePort }); - } else if (arg) { - remoteExplorerService.setEditable(undefined, undefined, { + } else { + remoteExplorerService.setEditable(undefined, { onFinish: (value, success) => { let parsed: { host: string, port: number } | undefined; if (success && (parsed = parseInput(value))) { remoteExplorerService.forward({ host: parsed.host, port: parsed.port }); } - remoteExplorerService.setEditable(undefined, undefined, null); + remoteExplorerService.setEditable(undefined, null); }, validationMessage: validateInput, placeholder: forwardPrompt }); - } else { - const viewsService = accessor.get(IViewsService); - const quickInputService = accessor.get(IQuickInputService); - await viewsService.openView(TunnelPanel.ID, true); - const value = await quickInputService.input({ - prompt: forwardPrompt, - validateInput: (value) => Promise.resolve(validateInput(value)) - }); - let parsed: { host: string, port: number } | undefined; - if (value && (parsed = parseInput(value))) { - remoteExplorerService.forward({ host: parsed.host, port: parsed.port }); - } + } + }; + } + + export function commandPaletteHandler(): ICommandHandler { + return async (accessor, arg) => { + const remoteExplorerService = accessor.get(IRemoteExplorerService); + const viewsService = accessor.get(IViewsService); + const quickInputService = accessor.get(IQuickInputService); + await viewsService.openView(TunnelPanel.ID, true); + const value = await quickInputService.input({ + prompt: forwardPrompt, + validateInput: (value) => Promise.resolve(validateInput(value)) + }); + let parsed: { host: string, port: number } | undefined; + if (value && (parsed = parseInput(value))) { + remoteExplorerService.forward({ host: parsed.host, port: parsed.port }); } }; } @@ -702,30 +701,51 @@ namespace CopyAddressAction { } } +namespace RefreshTunnelViewAction { + export const ID = 'remote.tunnel.refresh'; + export const LABEL = nls.localize('remote.tunnel.refreshView', "Refresh"); + + export function handler(): ICommandHandler { + return (accessor, arg) => { + const remoteExplorerService = accessor.get(IRemoteExplorerService); + return remoteExplorerService.refresh(); + }; + } +} + CommandsRegistry.registerCommand(LabelTunnelAction.ID, LabelTunnelAction.handler()); -CommandsRegistry.registerCommand(ForwardPortAction.ID, ForwardPortAction.handler()); +CommandsRegistry.registerCommand(ForwardPortAction.INLINE_ID, ForwardPortAction.inlineHandler()); +CommandsRegistry.registerCommand(ForwardPortAction.COMMANDPALETTE_ID, ForwardPortAction.commandPaletteHandler()); CommandsRegistry.registerCommand(ClosePortAction.ID, ClosePortAction.handler()); CommandsRegistry.registerCommand(OpenPortInBrowserAction.ID, OpenPortInBrowserAction.handler()); CommandsRegistry.registerCommand(CopyAddressAction.ID, CopyAddressAction.handler()); +CommandsRegistry.registerCommand(RefreshTunnelViewAction.ID, RefreshTunnelViewAction.handler()); MenuRegistry.appendMenuItem(MenuId.CommandPalette, ({ command: { - id: ForwardPortAction.ID, + id: ForwardPortAction.COMMANDPALETTE_ID, title: ForwardPortAction.LABEL }, when: forwardedPortsViewEnabled })); - - MenuRegistry.appendMenuItem(MenuId.TunnelTitle, ({ group: 'navigation', order: 0, command: { - id: ForwardPortAction.ID, + id: ForwardPortAction.COMMANDPALETTE_ID, title: ForwardPortAction.LABEL, icon: { id: 'codicon/plus' } } })); +MenuRegistry.appendMenuItem(MenuId.TunnelTitle, ({ + group: 'navigation', + order: 1, + command: { + id: RefreshTunnelViewAction.ID, + title: RefreshTunnelViewAction.LABEL, + icon: { id: 'codicon/refresh' } + } +})); MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ group: '0_manage', order: 0, @@ -757,7 +777,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ group: '0_manage', order: 1, command: { - id: ForwardPortAction.ID, + id: ForwardPortAction.INLINE_ID, title: ForwardPortAction.LABEL, }, when: TunnelTypeContextKey.isEqualTo(TunnelType.Candidate) @@ -784,7 +804,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ MenuRegistry.appendMenuItem(MenuId.TunnelInline, ({ order: 0, command: { - id: ForwardPortAction.ID, + id: ForwardPortAction.INLINE_ID, title: ForwardPortAction.LABEL, icon: { id: 'codicon/plus' } }, diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 7453c0297d4..7654610a629 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -19,6 +19,24 @@ export const IRemoteExplorerService = createDecorator('r export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; const TUNNELS_TO_RESTORE = 'remote.tunnels.toRestore'; +export enum TunnelType { + Candidate = 'Candidate', + Detected = 'Detected', + Forwarded = 'Forwarded', + Add = 'Add' +} + +export interface ITunnelItem { + tunnelType: TunnelType; + remoteHost: string; + remotePort: number; + localAddress?: string; + name?: string; + closeable?: boolean; + readonly description?: string; + readonly label: string; +} + export interface Tunnel { remoteHost: string; remotePort: number; @@ -45,7 +63,10 @@ export class TunnelModel extends Disposable { public onClosePort: Event<{ host: string, port: number }> = this._onClosePort.event; private _onPortName: Emitter<{ host: string, port: number }> = new Emitter(); public onPortName: Event<{ host: string, port: number }> = this._onPortName.event; + private _candidates: { host: string, port: number, detail: string }[] = []; private _candidateFinder: (() => Promise<{ host: string, port: number, detail: string }[]>) | undefined; + private _onCandidatesChanged: Emitter = new Emitter(); + public onCandidatesChanged: Event = this._onCandidatesChanged.event; constructor( @ITunnelService private readonly tunnelService: ITunnelService, @@ -168,10 +189,18 @@ export class TunnelModel extends Disposable { } get candidates(): Promise<{ host: string, port: number, detail: string }[]> { + return this.updateCandidates().then(() => this._candidates); + } + + private async updateCandidates(): Promise { if (this._candidateFinder) { - return this._candidateFinder(); + this._candidates = await this._candidateFinder(); } - return Promise.resolve([]); + } + + async refresh(): Promise { + await this.updateCandidates(); + this._onCandidatesChanged.fire(); } } @@ -181,13 +210,14 @@ export interface IRemoteExplorerService { targetType: string; readonly helpInformation: HelpInformation[]; readonly tunnelModel: TunnelModel; - onDidChangeEditable: Event<{ host: string, port: number | undefined }>; - setEditable(remoteHost: string | undefined, remotePort: number | undefined, data: IEditableData | null): void; - getEditableData(remoteHost: string | undefined, remotePort: number | undefined): IEditableData | undefined; + onDidChangeEditable: Event; + setEditable(tunnelItem: ITunnelItem | undefined, data: IEditableData | null): void; + getEditableData(tunnelItem: ITunnelItem | undefined): IEditableData | undefined; forward(remote: { host: string, port: number }, localPort?: number, name?: string): Promise; close(remote: { host: string, port: number }): Promise; addEnvironmentTunnels(tunnels: { remoteAddress: { port: number, host: string }, localAddress: string }[] | undefined): void; registerCandidateFinder(finder: () => Promise<{ host: string, port: number, detail: string }[]>): void; + refresh(): Promise; } export interface HelpInformation { @@ -232,9 +262,9 @@ class RemoteExplorerService implements IRemoteExplorerService { public readonly onDidChangeTargetType: Event = this._onDidChangeTargetType.event; private _helpInformation: HelpInformation[] = []; private _tunnelModel: TunnelModel; - private _editable: { remoteHost: string, remotePort: number | undefined, data: IEditableData } | undefined; - private readonly _onDidChangeEditable: Emitter<{ host: string, port: number | undefined }> = new Emitter(); - public readonly onDidChangeEditable: Event<{ host: string, port: number | undefined }> = this._onDidChangeEditable.event; + private _editable: { tunnelItem: ITunnelItem | undefined, data: IEditableData } | undefined; + private readonly _onDidChangeEditable: Emitter = new Emitter(); + public readonly onDidChangeEditable: Event = this._onDidChangeEditable.event; constructor( @IStorageService private readonly storageService: IStorageService, @@ -305,17 +335,17 @@ class RemoteExplorerService implements IRemoteExplorerService { } } - setEditable(remoteHost: string, remotePort: number | undefined, data: IEditableData | null): void { + setEditable(tunnelItem: ITunnelItem | undefined, data: IEditableData | null): void { if (!data) { this._editable = undefined; } else { - this._editable = { remoteHost, remotePort, data }; + this._editable = { tunnelItem, data }; } - this._onDidChangeEditable.fire({ host: remoteHost, port: remotePort }); + this._onDidChangeEditable.fire(tunnelItem); } - getEditableData(remoteHost: string | undefined, remotePort: number | undefined): IEditableData | undefined { - return (this._editable && (this._editable.remotePort === remotePort) && this._editable.remoteHost === remoteHost) ? + getEditableData(tunnelItem: ITunnelItem | undefined): IEditableData | undefined { + return (this._editable && (!tunnelItem || (this._editable.tunnelItem?.remotePort === tunnelItem.remotePort) && (this._editable.tunnelItem.remoteHost === tunnelItem.remoteHost))) ? this._editable.data : undefined; } @@ -323,6 +353,9 @@ class RemoteExplorerService implements IRemoteExplorerService { this.tunnelModel.registerCandidateFinder(finder); } + refresh(): Promise { + return this.tunnelModel.refresh(); + } } registerSingleton(IRemoteExplorerService, RemoteExplorerService, true); From bfb0275163ecff3dfa989afdfdd0c12ade615471 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 Dec 2019 17:02:52 +0100 Subject: [PATCH 164/241] improve color --- src/vs/editor/contrib/folding/foldingModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index 33ac4318dd9..399ecf46a0d 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -92,7 +92,7 @@ export class FoldingModel { endColumn: maxColumn }; newEditorDecorations.push({ range: decorationRange, options: this._decorationProvider.getDecorationOption(isCollapsed) }); - }); + }; let i = 0; let nextCollapsed = () => { while (i < this._regions.length) { @@ -358,7 +358,7 @@ export function setCollapseStateForType(foldingModel: FoldingModel, type: string foldingModel.toggleCollapseState(toToggle); } -export const foldBackgroundBackground = registerColor('editor.foldBackground', { light: darken(editorSelectionBackground, 0.6), dark: lighten(editorSelectionBackground, 0.6), hc: null }, nls.localize('editorSelectionBackground', "Color of the editor selection.")); +export const foldBackgroundBackground = registerColor('editor.foldBackground', { light: lighten(editorSelectionBackground, 0.5), dark: darken(editorSelectionBackground, 0.5), hc: null }, nls.localize('editorSelectionBackground', "Color of the editor selection.")); registerThemingParticipant((theme, collector) => { const foldBackground = theme.getColor(foldBackgroundBackground); From 43fec67203bd3f6f12e58d4878c03fbe44a5b620 Mon Sep 17 00:00:00 2001 From: Kai Maetzel Date: Thu, 19 Dec 2019 08:22:57 -0800 Subject: [PATCH 165/241] Add Rules of Conduct --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index addf36f4339..4b385f04183 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us improve --- - + From 085abd5f1a2c30abb8a10bd00be9b72291ed478d Mon Sep 17 00:00:00 2001 From: Kai Maetzel Date: Thu, 19 Dec 2019 08:23:27 -0800 Subject: [PATCH 166/241] Adding Rules of Conduct --- .github/ISSUE_TEMPLATE/feature_request.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index a820760fc48..2dc1460b16f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,6 +4,7 @@ about: Suggest an idea for this project --- + From 6009cf68bd547d8e8e7b06c335c28f82bba64829 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 19 Dec 2019 09:29:34 -0800 Subject: [PATCH 167/241] Fixx #87438, update git-pull-request icon --- src/vs/base/parts/codicon/browser/codicon.css | 2 +- src/vs/base/parts/codicon/browser/codicon.ttf | Bin 48356 -> 55808 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/parts/codicon/browser/codicon.css b/src/vs/base/parts/codicon/browser/codicon.css index b60241663e1..c166a90bfc2 100644 --- a/src/vs/base/parts/codicon/browser/codicon.css +++ b/src/vs/base/parts/codicon/browser/codicon.css @@ -5,7 +5,7 @@ @font-face { font-family: "codicon"; - src: url("./codicon.ttf?072cd8445a025297c265f9d008123381") format("truetype"); + src: url("./codicon.ttf?a5c81ab5ee50c711f20256281c796746") format("truetype"); } .codicon[class*='codicon-'] { diff --git a/src/vs/base/parts/codicon/browser/codicon.ttf b/src/vs/base/parts/codicon/browser/codicon.ttf index fd2bf2e192d44315ac693b657049b5c0bd16a26e..213b189cd914d26a61d666dd3c535e36d345bd34 100644 GIT binary patch literal 55808 zcmeFad3+nyy*GT$j23I(B}=w;OR_D=i)2aOB009RI*a2Zj>+Qe3rQde1QHg7J&>@3 z0!>*=p%f^jEn7=&OI%7REp#Ebx6nRq1Kr@>Qt6VmG=B9fL_ij-{Jvj5M~LcSLaeFH+ppLpT)%D~AvJA;eC4-WHm=>^T3EFX=l&MRZovWd|5Dnp zKMvGwxoG#^2cE$mq3;r+7~j5Q{n~#H*BvIL`b|QFH5aYjyHl7;zK`##aZ=#owHIv+ zJ~{n5LSD1ud+p90m+t<>k3W8ckRJvJG5v<1?&5o_Jof(aKgIXik#qlOvwQ?czdvJH zVzsIJZ$!th_*HQ2>+DRv{dhurU;HCJRuQ3cJ+8skFA$6TnH|;QAAaWS_xRjG@O>aA>z)IklAYJ0}bv*NA$!}=W%5<>GZH}wO+B=R)>1F^Q9ljPAPvzlt)mfIPa9|>jnf28(kAjC`8;`; zd;#pBK<1FSWPtQi9hpt$lbOUts>xR{&N@H^n3n$k`=6dQfN_vF18rgRl!I}x(pnD8 zL{h?>WWbyxrS%+`oussZ1M`%WHgb?3V7rL}bC;Ahb1)`a+QNbPOiCAWU}lrjRu0T@ zQrgBrQrK?iz`Q4=i#Q+wNa1qzhB~mJK zKthocm^%Yx6)9cA0jWhwtQ`UJb!@NWfFvWO>p39PNa+R+NHXNlQwsT!5U$7FGuXq%SFbj)Q5F(!CtA2DuOW0Mj}pHvRynjY8l*j zF{Jbq2eb_-J-mGIZ`^t0eweG;~da@r1UKg=s;3>kptS0l)lXY zy+}$gaX>?o(swwZD@o~P4#qM{-{pY*B&An4ph-#TdmPZIr1X6bXjf8tl>>T~lwRY2 z#wDd6a6tEx(vLZyg-Pjk4(MZ2I?e&jOiKUC0Ub?BKjDD3CZ&JlfZir0Hg5rFa8i1M z1G=1)-sFH*C#9cpK);jH&pDv!N$G!aKDZR%5c0x*j0=IXFs6oN zfQ^wdjg&j)ED2Jc#Q~dwlxK6mx*+8_9OSpy&gFo`LCOOhusuk59tW%t zQl8HNdxVr1aKJJl<^Ibh$A z@-hxsI;1?p0h@=EFW`XnL(0oJU;((Pz%A*{xmq>Xv2P`L2 zUcp7?b+YKDd3My~ppjFsz;-Fe=H*-)kwp%!;1=|Zbs1@6- z9F)m}Z5-5&?RF08!1f{z>csY94(4Sp@8Fq+)9JCJGt2k%`+p9TfJ+?&-+JNmo4%&$A zH5`=9^=mmOo9owc&?L6kbHFYo`%^a{~N%lcal<(ny1x?DI<$x_s{(^zVz^rQdZxGDjen`YO$w}Dp0h*=r=sJ2e{kmWj zdW13IMNtsP#1|FqiszKqs+Ox>R{cU9Rd=h0)LYd1)DNqVs$WsRSLLkguew8{*Ys+} zG;e7>)UMRNtbI>ct6QSGM>nqT)GycHroZ1%Z#Zn!8~cr0jW-$(8=o*9H6Azq+N3b~ zO!t_MnEq~VG0!z$Yrfz7qWPy5tEJnr#d5^*l6Ba6oAnzur>)(#&UTCKsO=Xv$)2@u zwcllb+5Uke;JDN2aV~M*@048gTwigOs{5;7bQ|64+($f4&yZ)o=ZTtZ&GMRkH4oQ( zSevTdQu}-Fo!)nSO}-cWL;eo}*}%!**F!TycZ41Z{b$%7-WPr;{6Sq$-Q#s{McN{F zM&6C4qMM>G#;%PWt*@^?T>oK1XTyeupEjIqG&a82_^Y@O?~Fg57)iX4_;a#0*_+&+ ze7MQp)YLTE^k`G5InlhSd93+(i=t&-%hj#g)^({(sYlY*^yTU2(%(;?ZkyS*zwME> zpSFFNxia%&b}0K|u0A)Gd#hd1zM*}8`#l|-I)2}&?Yyh=-LBEDA9shk_jNzleRA6H zw3qUY`3?Cy^2ZCcg_(tYh4G$f&(@w_O&^*5Xm6tT;@)TaYWueIz1OerZ<>*xarKOQ zXS_1wy_vq5TW0xY&7O5|)=y_m%x;=JJo}zG%jeuX=lERD+?jKKK42fXZQvUNAI`gS z-Z$n8^XJYVoB#a<&IPjNpPM0&4%jU9ev1lwR9ucPRo<;S!6&c#T=F*KlDqlb|c;(FYEeqpx^6B~0 zdm^z|q-Xkly@e?L3cbRevx*B<0iSBxnoHJoE7PMLI$_2o(WA4z9YIUj63hfGX^Xf`I*^wR&^>t}U0~Y+-6I`f_ASmog7Xcq1D(u06?WB$qi#CLZ8 zv9V%t@;E&u>G3mDbvh(`iCMKampYcoxzxc}&gD?Ya^w4x$BrfU3%hP++nedpn-k-S zV~PFy@iv}-2%5Tnow$uz)Ha%?F^b!w-=pmb+L4eZ3(_P#D!nT23%5<~W{!iYc8IZS zG;rbNC~ky);yf`Xy_%3-OHeTntu%E_h*|3qnt_`Z_e@MTew|1owyOd zgx{8b81j~1Cp;*eWWPn#VcZ!SJSRLjmKcj~E^MYh9ve$+qAR6GH&@R6CcM?`T;39n zpo<&B8P1pF35DTpF#6Hd50L)PWTy z46z@OFRI*Ur9CxpV!9~mOT~3ljZvWDC+?Z~L@-XaW1Ki(&j-*xVkVslXOxb#Lsa>M zbTE|{XwH(uxKZbVE^)(O3kB)5{9mgCORYuNWyYJ-r!HP0-FfXA`pnw<=r1Pnl;%H@ z>WqO}v$-}vj~n27l|Cx{bNm>7O8&hI#i!vVh%)a6jZUN4CLtCz3TlVlL|tjSPrz?x z(mACn6wRca>PRj-jkaZTdFt|sILS|gVt+MlSa4xiUvXWAR!4j%Y~iNX=8*k_PdaLC zixjC%`jV?Q5v||Y*0Q(|ptY{a$LZoZ^NL+NFKAb*8!xz`_u#zQ7dKWng>AUBx^dVR z@px?8W)2M>%&ZO91y!o#oC{j^En76BQfJ{$ViKMlC-Z1TEa{ZfVOObr6z^ftkxpeZ zZQ+=_iN-1I`019d>15lqotqEd)-_|_9oM$^BNATB8x%iDKbcVI2SR_=hTUxtYZHsbYRExsn=;7cmxA zTY{ot^0+?>=ev+Aywmg!Jq2lU`b`J*OTTl7iOI<6(<~^iJag{`_`zz>!VM@XoQXkT zp%f;tBDf``L!CjXju-|f2B^gaX_m|2rw;xqAid^4;rEM)z~t)_6ZF)C|5#vx9xY0* zPWZ?D6T-<+zkk#(>Cq0SMhp3t+x&rm|4?!~pP!)94>|pQ`yJBD6`9HALIZp;>zOZs z=4m>`#J$4~J&<7oF)uWUM$t!I4!hIkXcDqi6~-utg;U{HXoG1q1hi$lXv*oOcD2o} z#x^fRV@=d3I9-{xTr@9W;KYnLGmo-`5sL@;XeN;3luoLl2^F%a@YOTRKAsmQbzGv7QPFlT+=^HoXH_^Jb2!puQ zA4sQ`jBH$Z$2N~B)5TcdSgIit22mokH7Jq$u~6*8mv^I&=?-H z>i2mf5zjuohPI>{ePNY<@rotEL@+S2=kf++oiDL?HIp>)kvp&}=&8Q#hO^Y7`wTj)yVA4zuZt<95(CBVvAO5T~?*ie_uXVszUrc6Zgy zv4}yZp$4r(XVceOP1c5_yWZ=G)EH>6ebMS|7k6ZH0e?-r-d#%tt+U!#qt_W5sy!_b z+*_B%=hk}6CY{q%r!}eEHGY?wR+%+Un}M1Pe>B!bebri<#;Q}hYW>wFt<7Q#Jt+P9 zb?8rM=P#(KT5WY%je_R-XoJaQQ0dG%o6hC4)q5>AN2I3K6HT^7+IC#LZOx)ii&?O18iPxxGfMBNC^ecrol6s=Z7rGRR&PVV9M)Q#{@QAluF9shdi)l9*!V}I z$zantGzPQF?{%q-tRgPqL+NdF*Xzu5J-2T(@HfLc1AhR&3}*#PuR}|uM3SJsIPCE+HuM@}Xy;?(!vD&GlwALL<)c6CrY-hHkEi>8>^VPEE(R=Kc z8gG4dQ)9?ntG79=CTcZBVmDX0Z8n?RYxdO#eGYAvMr-hz^aj0JXVuuW)&5AG@s9=* zHQ1aQ5G#vLUu)KTR3>em(WO+G%$}No_|mPZ)+S*N4Plrcm)=f1T&vYp8*01(bG_Fc zsZpz`*;?zXuV0f%cV;@<0b($)J21C$bLT`m*UXN90t-=+0_MMe#!=|9!?QU+(w4_%jmUh;dplmtE ze*nBh4c3rmu>{a3F)XHYHVDjun5K?&rY(x81gk~ukSBxSz`O#*0$0h!FyWxsFxQ#{ z@T)9+v%UX^JwNErTqX`39TG%Qpnvb~>$TcLL7moJ2wA)i-L7ELZ!-Ck-e{NYMvHr; zsW#@ccx+m=R!5(^TQE+3a5oKTe)AiR#wuOZ`PcqA3YAf*p6005y6QY;g*^~&!tf3U zFH(tys4M2TXf3W9w+76-!e94`(^(WwLt|pjM#cS;e@gvZ`UU!tSUq`2q>XjbN5T-x z>SKfUN!Si5uzI6x<9HE~jY9)5qMidubjbgdcxOPX#MDqH^0%(ar&3~i0v7Q~*`{H3 z%{lleu>)TvF5;&=z&{B;>5VszB&>xZq+u~nFSs}{5>H6elkt&+^fO!#PgL%sQMiHM zhYNE(NB>IAG3l+C^mde9%68v|ZkxJi;Rf`fe9vlFC$6(6&<~|Q#b_OSojLbGer_ZN zng#o3L!7axIPJJI0kIcNfa|lS;Wp2R@NA_mVO^A5p@jyOl0 z!)!|pb)jIiz6KY3OL`{4@F3!*u-sCplVQsJ4&*M&5G=J`Ti8Yl?qsoxMz3V&jeOq!A<3J3uNOi0!nY`DXqpr=BCo>f+j z#xmLy=$Ch`=3qx{k8o0BUDe~C!N0|BpnrNzd&msmB(EdNK8YwV%h@3j=o-#g>^_Mp*|fg1tE+SUrjt`Ua0E2P1#|9}LpQF=2q&B3 zlYi#>;*sui&yz2Z26~HW!Q7lwPrLA@+1Q`_Gv7bM&l$(-fah<3MHRU@0`?uuq-ABx z8vu4l7%J9C(GqBt46He}WnZe);U=b3K#o&RJ8V0VZ`!iKsU7Zz5E&^se#qF>*BJ_R z_H_owi^bYd5PPAb{EmZN#z%}@K_Mj_2z7+;I#$cxL-ZcLmF)~hZ%+Sp>e3L(5l-HT zQsoQe%lbP);hLH-#?aK|;r39dJ6>mRfSLoZn1Z{ z-+uxIK-eKz19e`bK#%nm`jS^9_e$#$S0wuL{fT`-syG(N^R~{X~qTriRt!Rvy=1#BKOa*4SMkvdT*N^>Wq0s1`sQ{n=A&CjEs%VX9v z3W`=Mz_=Ti2Jrh_&M8GDlU=e+Q2BLw>fOYwMIGS~V=8%?%ewM7LSfG+Sn*L}o7YO8n z9qOvNn^!pOX^Yk0iRP#1A2rq1In z&``(pju05|Tw_~Hvrbv<46Yh!D@4PI-8&cPs&sv`?VF{&Lv)Gs)F3=*YDc{a0?k2H6ar6Jp z-DD2s>$4VqH?Iy!U!{u%6F0*n$nC&;W&OrH0r0uWPQ{=+nwVmY(dc83C5|1VzfZ93 zvz8Hp|8hL0nV%7!t4|+@M+@wWPZ@rd z_Gj|ij?qu(r#gr&XJz;&#B?{4!+T`gKb=~-?9=4(={JRw?7JT`2f;b|!3r-CSb;4E zZ)+xu>4llbD0|wabmibZv%q~~apiCbW}_oEQJ9^9!6%&b=O+h8{q(?Y_K~?nCVoYq zAN9|s(ZU6@oMZ5i1On0@FaZO<+U>-N?fCebKR+?yPa(L&$J-HkzT0uX>^IU57jsI9 z@J2yW!RsS?lgBb&qfZ11(pKmGz?g7S+~%Kr-9P4h&M%}V-`DQn8IZXEmmimkUxr+5 zzz7~hnU+=`OabW7Y|hdKcUr}>%%-VRbmkmzwlTN7GXkE+T+M7|TG-Tt>90~-?9QBs zIV(Ubw@z2@Efvx6`rYZ zHOwH|!Qu;GYgjVGDk1I?pTXFV!T#t*ly3xgfrgj)mQ1%~ww24;d^F7{7py__9sFPD zRm50e1+twy<1*+_HS^j)BEilNhl~PjK{fgnUbDZhylC5+byum>%`N|O@zyo#uT-m= zTCQnamtHlvAlW=?-$VCZk=HGx&n9m+SvqF4D(n0FojSKk>2#Y^)Y)wEH^nvKuSV(< zgZZGpDQpcbzU1l-apN>r%s0+2dh+#JA8k%97+kgJ&^mwPmctL-N`IWV+22>MYMs$; zHSJKNRHM@2xvM$W(4B7Txwn75DG2F@{mhknSGNja2$gxt{GuLc@_sZVh*}gr4UqJ( zMQ)xH_-6qTk6h%ecsNV(z*Nt0zGi9xuXJW}Vsh!sIY;KqTuLbwHN(9#K0l*(SR+y@ zy*4Y~7V`T;ZHTm0cIb3L;R~j_?&wMfeF~xDn%>@PIz&T!mc!|E%!;cgf98zjEmo@~ zA8`tC^-A~z93i++%x06z9&)&hmdQ)1;D|7qOh$*zQbm6j3^ynnRB{eUgl}6Ef!R%W3V)Ht~7KjoG~!z(-_`PwOySCcfx2uPs5IJ zMKTe!&EgW@yVsh%Hno?wT+!3EHrCxSE4QgW*-QhHSS$8voZC{Dw9=0CosA2-1L>Wq ztx-x3O%|!oQJ<-e^~R(J+UZj1%k8sH(E}Stu1#H$?WA>~c%#tXR=8)uilwvXw@jb5 zEwv-kxvn$Ny|6KgBGz4KwNr1jHdF6J36n1GUpqhWN^9tfuU6)m@K@olGS~OAyhR8a zP%iRHdW(-6^qiU*gMR*F1!-e zbnV0`Rb4x8+$+86QP(e6wK%1ypSNP@4fxOaxT)9{W|pbR{Gewzv<;5$9B{5x(J?Ur z2lrWXLVB%GA(XPL79`jyoE<@r26HTo1vP(>^qV{Hq`^DC{oQrzzPna9i9(*lUuC{e z%GW^uBAOQzCO;$nmYod0`osjrHZPaiKUluTq?V4RW6bM*>3>`b@$kHK-w!M0@N)>Y zv1=v0a8iPNujr6O#l|BxjgSGC{ua#Hob-dIo_dP@jm71p9~^k<>8Ejeh2tgV^9e@# zDcT{uD!nEq**k5owAly32k0Z-W{@XL$OzdO`XrV~JA_vvtljqxRCVpTaqo2t@W;Ap z?_3T0JWt&@GhdYNjdKvD-$ckaL}hGgOW0wxzM9Zf|f#AY8MC8rRksjY6-^*7==ITPl}|IHlKC#a8WJ6blHEX}_Zf}h`9P*jM-@R@WW7Fsaqp@bK^ud~%aA3xuTmI7FjHGhX(`#aD zFIyX3i(+O*{ax;*(WN#!t*u!*qIE>^OSZ0;J8i5lc;El)RNqmwjvTagkB-u#qZKnB zran8D$90~=xeBx-^U=U!uw@_-&>jdrM`L({?Zga3rl~Q5SYI)AZA82TEmEK}G~xnp zRLas(vqn0`G@@`{$B4b-qV2W4S9vn=gE!8$Zn*twZ>wz~V$%M=c(?}9a9U$5$xjR{e$B18Mvu57 z)9&ZG79vz8c)=W1GKDYfl5IWtjk=`|JEd1J#=ASIRvMRHq20)sIW=YThB|Dt-m_rx z=>;{k{%^t}=@*sxx(gkr|HSW{SMWJSOwD6fKi0dMhtWU9vv*bk={h3cX|z-Z{cdQv43YU7>P_9 zohEtDwrb_~wV3Bxmcs-+7UQde*~A5C^aYFbj1I&TO{Jn1ifraVP9JECqxU zm^6*T4np#R9Pg6}2lv4#A!4}mM2P{aa`6BI_=(I$@1)K98u#B2zvi0wO*b~u>)J1G z+(|(y?tZ}5Cfw0@i=ySS>-ID&_BY-pq-+n|jfG*CHty+eUwz5y4(Wc{Sl_>BMm@bx zm^>{6XmxeGVe`(-jg8fA`V%p5P5kB?8?U*h5%2V!MwBbA-|BFPUE=0NiaQ$*C^DVh zStSZqEZQJ+iw?)O)e4l|*uJJ6uSOJIU*CV1La}VslKHc9*$xDvX3t-;W~y(Qj&iVU zgj`0>MM4INYy-yNdWEJxGdNunoxbWSrSylXXdm6=AG_(n=+T!gQuk^`|GR-p>sVoh_hHMw{sviXq>h}w&z>k`t zmf`Z9ICe~U>GGRkW6t&ai7T%d@kvo?e2@k$6hnfBFXPo0s$swFP;mkaeah@18Sgjtb zmFmw9-cWitl^WhOoK6pm$*F2*G@8kK=_yS%tI6ANfyc(3XY%|nq;WdG26g9hnQ{9{ z%$rtX-pqbuaagP=WJa{!4_}oufTXE&+>vseb*c=O&&A4sG?|K43 zi=72p^YC&HBFk%h9IrDm^vRU)bn#f>*fExi!Ry8BNmeHFou5-LCE}jp%z4pxdI}j{ z=hv;sj-aSdl!YwKsj^sH|2&CtPLa}qG;373sm|OoDvx_UUTJWAS1Fs_yNG(4(C;h; za86x?UD9hO&b@D>wjr&pk~hHa`4jbnSAME~l|D$qVRuf=CauMBMk-m$u~awa7zP_1 z7@wFpRe}j9#!GwUz$v7rv>`Qxxi(lL%$c3Da`OmA9c*+qQ(;T7z62A4W11&bWaMt1 zkmp(c82m3R3lccY|ikqp4l^dv&9;BOG}}5&iq*eJ$>st)UMWj z-FI!vXWAR;Z4PBFz4qEd_w>}ZI=d*;bnI;3XE>ZVJoOTfD8qrw&EJ?it3Tfvtv8x< zy1Lko+wPw+Hq;P3{WDV_l+Jb(3iIwruhl3`=DG#f74p4ZS56Pw=;t3yj6E118;c{^ z5w};dalVc@n?a8EQnogXhM|33$SGy6{&W;h^JudGJ={z)1sFOm$Z$W#nHsq`e(FHx z6I8Sb`A#ksoH{HOif!TkIb%+{AO?ha&-kt@4u9@*!^P`-*9WVeRzXm=6x){A@43gm zq^;QEo$JNN#kPn}yS%RE-r?f4dt%cKK94fm6clS*R()gAJhPa*Y{xuneATY)b8XRD z+AbbpC;EBOHR0ttZN#?toWkz8NB*c51go<;c)kDH;_w%eb$Y9-MhrGZl^&mY!Sc?h zV~&QEm+hEiX;^;A_VtBAg|kfYn@av9^D;A5`2Xx_E*A3twXeCz>|5m2e44|V``q3G zPvUxm=M52BkyR{O%2EO$rDau0@tK(iX#geC4ix$-e!AoIaDle7S7CAy%NT|o&442? ztdC5c#hl^@+U{W~pX`M**$LQ%*6zhHPxaFY;c4lSC*5Gl4YNC<9m;kEr6`k)?J2#PgdVPAD?V*vV>h=~ zzE5`kM%;jWB5HJ&r?9&5JO`#LnT=HqjS)i)yGRe(+E3+7J0yUjmt#sh&q58UTx}_P zh*_^0wFBNFkx4k@E`mOve~R$g*OBEph@{AOq-n_hlpY^$Y+90N%J%lYdT;B6ZK);q zrqWwlrw_-QmL%Cx@e9XYlaEaCNFjY}@`~xxuIin(?1@O@j~sE`Vd?XYhjnqsj~48e zj}Jd_W{$A>>){{q$R4yaHCLyhLcsH484<>m!&N|t5F+H9niV%S}kI#4d9u`r?my4 zbRFqTvml2rGRTw;E4l4zL8t_BEv#lLG8{xkJG?S&QAR+EbdUc;LHoK>@*$o&PLCoE zwT$PK1`3-Jb8bpBP*r-(M(M}-&tGM*F%rpCbpC+rxTcZ;d2HM-9YA~U@lR}+dsD-W z(%({RRu#rhFox;@iGdL<*O~RV5w&(B#?y$I#XRdQG9o7dLsRkUt1~Vo!Y>HD#6;%w zXS6XRXC_14fIz0&AX}kj;+TNWEL1=HK(z2Q20b;xWEy=64gRI0bv9v!l~>nOEY-IPvi+}=A)cmsDQJxSZyog$U^ z%pF2Z(2nu^v4F4EYF*jcd5y8o-jqB>4bD9@TBU*Pt}JP7y|d;xQ_90eOgT*_~5La6F0bMvv5=3`Cf!uSO3 zKm~dIzKr^5;lrwx*N5R^h)3llK?B9mtw8|s0^QB#7$SkFG3K0D z8SE^P6gk!zOgWq)&$<)iCoo3QlJ8*VjYxm zOnNQA&y%*&J)_5VKAwDxnIm9X_>6O9b(duv(BT{wSTVmhm@tbRBHxrTVm0$s zMPo+B5~48}MXU%1zKY^9p(|Lc3wxvLrYf~n7YO%W*e+6~3MsMvgcTW>7Kc)4Nz54W z*jt5**;omsaxf(o?h3} z-h`EMEr!Kc^qK4qpD!A2h*(|Kpa8d|qXI`S<7UK3LT^uRcJ#E)2|_yZk~nCAe>HFWN?ho`e;}Qti?#p${8zZvxGD zAVF_uuc=+Jm}H|PDP8KE+L|JG{$nX7oy1FA{P({{nbevb{P^P-wMr+M zjQn^KPh8vvP3z+_oQZC@2s6eRc=DS3wX+gLC}?UJRWHzZx2Wx}yXWso5K zATVR+iOF;0Yv1~8>bjvAk_C~l;51#p1Z2?1G??Z&SxmllUa;-ttC6oxZyBEAdp|*4 z4RX#BLH2?TDONEiztt8S(>f0LoltXS&=!P}LE41xG2cxA=`dsf41UOKCmn%@NqW;; z5ROhRxsZmO8M?LTqDNz6fefQn!e_FYq9)^f=?k=71JW_|yJz(XX0QB%OoPh`9KRG} z&6Qy_fd70%{x!dt^!I?|xSd&g5DEjo$`l6HmWfIEm!KndNrq9nN0ZHKnSjG@P3hDl z@DR4cpSpmJGu#u}*+d=kIto99{)8K#Kr`~{i5Sc{H53S0!?s1KV#+<>$NM7dori5h>0-72hT^irhnE#^@IM{6p*V8*@KEta z_fyfcOt@Gp=h+EA!}UX{VgkkC`XL*xKc~3g9bQzt!F^5U^APBwCFyu={7Cy|_=H|9+c(WA-a+KXf?J!R(Yb%&XGZV%I(F~J3Q~zXRfQcp; z0cVfNdFps$Rz8-4h1td8ybUld^aicnsM5O)HjP=|5l^&ZYpAU?I5cXNLMaN04d}I# zerbT2SmU@rPznl_LZdV&G%B}Bs|i^3%Bp;oQg00)58SQNS=0)hLWNkrLQp=>EJWHN zoU}Of?kE;5CC??BQDIYBO=@G6MeEi(EG+Rx&bmR?hCy#oTSc>|606ks zJ>(Hkg|5b`vzW~mowY_MAJHiBQ%;pdZO|jLj;;8tv@PS(HpHSDIAlM0mC3CJmv1G%eO6Q6%&in`h1ON zTBO$CAB^p2j@b-iup?wP`MVN$Se#1`o^c116BAY4%P(Bjqnnse=-j#Z`ql5&>-?TX zaAv+yB?z=S(V6dX$fBOf#tHFR@yqaYC&+yE{G79?OHBgub66G(>cPek(wEYrGcUj{ zhK~eZB&a}^C&Oxfem0a(aEMdu%5WAvKG=BEO^Jn<`a2WV$v#_TK}(=3QB7y&aoXIulbo$KrVEm@U zP05ATi4H$qq3vBYlA*Y>u0Z>+PI>}%n!=`T?yIeK=<3_sq)nP&J({b|+P7+1jYUOQ z#BzfPT%QW>lqSFX)L<_v9Zv7OSeIuLg@w>f5!o zE$lWu{(zy4{@k2wuG8pr4Y?Wz^1}pWjPm#k9t2ZufvKTZzr-$`~rmhKt8T`I7 zj{MNDIq<4YXfX^KcurUYuzRPub4L?9gn=WshlqAWL!nihXym^!tlUOZZl>JnEej&H zzT`PWt8GhNMja2=%54%lXJ2UfzaL}5l}b^1RX^M=kJh%4RlQo8KWC`5EC{#G?o=yz zqs(oc-BHE&yZ@WRts=wN8h<^c17B|kOA{+Xc;F1F>Bvcng>x|fMVAdr>1ZwtUXWG; zFMgrnkFv-{`XpUGa*(cR+LUhlTF0S|Ltne>E$QB^Uk*P1)1}4xU?;ICSU~#u#Scy% z|0UhBZN-|1^x6pBGcMizXV}B}ivRy{@!Ra_Q1U`W668>##MAFyBFU zO%PZ}MF!`^6p+_P!J%qo3sB`NR7{k~E92M>`j@FGI9=H$mx<eRJJf$T`mFIi@54JQ0+!nRXD;oM& z9G>504Q3xOTC9c#s+%k+jm@3vS~I81U(;Eu&=`GUC?saQU2cEP%<$^Yl*g`VwZ>f! zA%JICrq^q0%puWUr5BY|I;~z0Vy|FxmAW>cPHUrvDse?$ug~Y}$Sq#_($dAb4(w0wUm?DH-tWp4IQ;_ES6yU} z85KrDYp}l2+wRc0^d_H?R_TpZwY617eHAtOOnR5j(e7=G1ycs2LR)LnQn#(jsH;LY zqM%Z!RR*I;i|k27pGHr;{e2@VUYtJ=PbA_4^Iu#s($^1zm2p$|@rru#dqmQ z-W5g{MKRjL&YX(bk%Dc<=*nz+dv@jM+pAY*bGhuw)%0QC`h27H32L}oVHnf=N>uy> z9LV^#y2{lq+OQh1-8tRbm|yR&HWdbN8nj#lvxb(&oCFHybOV9xGol8LEMG;PQg}VX}`dH!Ep4^zwb{B z9*aTg`~C4*aoRX5er!>4|F=7o2+4OPzh-+=&BBp%&-21ZNK?)&$QBxBFP#;~T7-Y= zP)e)m(Ft>t>V$jpeMq~11*4jpKSz)oBn!n`(=IVmi$Fml#ez~apDQE}esH}XY)5jg{+$hxoWAOoEr zDHagA{d)1-_`0x2c9ZR5i-v^Mp9sNdxi* z8VtFd!XTD6kOWSLQ!PGH<#PpwSH|lDVnc%%sE@B44m6pnsLAJ|yXY>L$xrD_eLy-U zyQ`mOvYno2ohER-p%${(ut;+>ff&;b{PV*ClL^2#y-zWf-{P^QzXb`nOTV3 zIC0*zczt*8_kMY-J?}qppz+{iU)#uf<+>?Cn{R!vUI@~MX4c{eVvv7sPFjCl0;oXAd zA4F(jP_P9=F+m5rs`K|hDb$ZVBPNDJ}d z1bz0MicOqEE{hJm$DXZ$aR7fKW)Sl(+v4u1c-GW-hym|Dl zboz^U(RCezdc8-hqFSrY?$PS*Z(HHlXt!)}m^50d(#&T*bmoHj;uoa@U;N^uDrDK1 z?bd)>V^N9Hfd?{*E2*){<<~o;FC%bv)_2QResv?h)6Z(hZ7J3W@*)prV!^7y3sq!! zk`D9|GAh{`41`-_Ds@H`MK|FA2_P{D_+VHI)(L;mkJop{vSa>$-yaB0n5C-2%27%dot?aq1%v!(%V-pd<4yK#)U~}Ao+d%-PoayUr_waTwmeh`fiKG-$o3S1lh|(Vx#bTic)c-j|yFR{D2;0bkF97z`8pY(Hn-6KO0V7l#u=o zzxH@CF>n6L?hT_Wd;3x@S7G}2hP6XW)={A`F_h??uPIz{|L2P{DScxo@$+TL?FE?N z(g2nx@_N4}ejPbrZbTswIAv}-mLKa%%_a=F2wX@kK|YqwV5K=)9{Q{J zdi7^KJMMYthM}swdgbT7@yyNPROb@9i#=X~KE0$f9X{~PH$S&Rm7jCnBlqvAy}kNJ zLV8JOeqcwNZFv9Ss)sJD&0e+a%IY6}8F8ij<3Fe^F1tG8-TH-9{-Hb9Lafa1>|Da+ z2(RZZaTmBAkM|LlZy}R4v@Qk&o80nx6r}EBS3c-+vUFUg4$fa_vYIb6**EWnFKyN@ z8C!Y5;Ls)&W*BW`)fQvZ&Mrt%VI>J$nzC;4c*Zz%{|k?AP%2^#_u-Po^cD7S3=B+J z(3RnL#GHtzgRI-}&&Jlo&@cisvO`LgL69RIzS&4&m)ub64M2%8mX2$uO+5S8!) zhA|(ZdvlN*Z0O00PmoIK!uo*sB&YuZUxS@$>s4xXl{wxNH&>~(Vm;Nm8hy(p2mOkF zwf9XCrkH#B?2xb8pso73ceQ^jd{_t(ksdLyfV-eT^l~#k#wW=81!wzIH!kMmCCwnq*ug`2aC!`n>A{QTiDu>fCP6M9{uGr% zB?}Lh^PP4Jt{A3aE&O6RYoiTSE;OKi(ap;!U2u8G3UjBU^V;=sw$J;Z>myJ z!RNME@m~OWbB)?hTxL+om%(qqFT>h^bFQn=2jQY#cRw)peHohDJwildfv=_Pi_Zsjl_prY=+)_=VY8kGrN3 z7rI02!iJc9p*4mJqcsL3;8kR{(1t$HVdj9SN7&jH*e-Ay*jN%4bE$~@fkn92e=~-B zHo;_cw$^HLSTS~9rqwN2Hdh<;X^gtY6b;DV;rkArpf&Yd@P|E%d5~vMn3N?pd$>pl z2|{xGkE#9~g)u8K!JTGqAbhQIsnM%k143RR-vLSxD|NxRXjb^%{M}B6$+&2DZ(}S~ z-5pp3c7P}bYMWbswhzOE=2m$e(f2Ts+aUYtSpDs(T{2?F)l?UH*b}ygnp-Zv#7}pfP#AYY4OT zeJ0i>oOjB(2hGSh=#c-f54J7@A~6V36U2jhAZ_Hy2qhGQ;6yfa5V=#3L+5;h#J6Y+ zi-*EF_ASyBTrl}@K)h>XUe3eGZ>-jshBgeDHQp`XEmjw+g*4uZ-`(QXm=|qaWY$cM zIhq>{V|pbXR&j8?G%%kY%oi_ov@{sTbV7Q9S)mh!md?(W0^)-D2UDpBS?(@BvRX&0 z?e@iorBVCGzS@Bd;B)K~wl0AQHm0I5*&xPjF?BA6Jnb~rUE+~>SliMCPXy+# z{8TJix3ONGdZ?CKg~hGIXAPwS@fjO_av0 zZH3vfdA)d`qSHW+%2)BMTNX_80?JIBhT$|y-B{mMU>PKM?2@!RK_8c%N9^Ya9^5p+v$_#?ACN9c(#H#D%1mN~ z#3J+zORK`&F+qmZ5X>c}YnV%e9fd4~lE%6`WrnTGlh1<35Pp!nyyD%6Y#d}c;ZbIJ zb}&k9-|1Z2(XkGmgnDGXH}p4rr(s6@tZdtGE1r3`R&ZpNwsx%T{0`2*%kRet6er3j ziOJ5jodz5()b}?S@J;=U219PKCa>yRkujjy(*~Nv7Y64PCpdc@1RglEBZ4a@jb<*J!I1~)6+TN&nd)Lsj7zgthO>@OA?9(!gOkutyo@j#i~?KyjDK*A9CF8# zE?DF86IEbj$lD%4p;c753_hc?N=%CgC#eKPnM6fTu_LAs5gZZ``7`NkMvbUU!(9X4 zodOOf!Kf9KOq0R8B&2mpqupt%GN_eQCyLAL#|~PRumz44s6|woEP8FVMy1laH26}b z5U7Y?yP(yml(R#L?}!SsO{;NflqxMAt)xep3MCZ;g<4Z3(t1HuL1W@UfJ*$D&7{`C zU8OSUbXJq%j69jL(XpZd3&U7*qGt*)>5t3nzMh_3bbh~1*-TT{G25Q7YuJj9DIgsp zEUz?qMt)|D{DdD+w`8G!#0Z!8Wmw{wLfl)x)~K;mm2>kt zSjV69lr*fSM)3Fl0Dh4_G6)OP_)dkZz6w8ZHSF#z{9wrAq{wW_grltM@sIg)Mes34 z4mmJUb_AmdbsiZ;O*)`6msbek1UN3ykFis__@xR zzP|phIjNOz)vq1Ns*;OFmu6MfJe{@cIFQp2V%@J>!?`UaS zeAo7@@XOuLy|_cs(J^;zA>h@hKdeR zOKD`0)#P+&)q1sB(^#)j(jI$FO?^$xhpwR06>xo)uO?Y*H6h@rHU`Wgb7e>Trb_R_ zBTt=rmBA}-#~opfUa!Ja<+4tl((W0LcnrE~tr8EsGI%1=H_WvvjYd(W3!3c~i(S{o zex|;rV0O8Ju4>xOa{_4(e#I1?`js&IUDn33og@53ycPZu=F8|p+cLo}vm{&ECTEws zkWvFfSjUKz;NnTwPIz}%V4<>vS$HhGA|>=+(g*GOO6Zk(A@)n9GPMNq-1qRq2r9NN zx!?@d^J*PiYWt=|6u$P}u85*m>2zV^QEi0DNDXXQD|SCeNMyaAAUMyR5x$A0qou@`HPS!6v~ke4}w zv%uq6SyIA;K9hh#y4ZJVKLxHvwi+h&p7GSO6kd3m4Bc}^#_Wd|P!EPnXc$irg<|FZ^WvdT9o|8X<{g zQ}@8vjTas-_9+H=A%Z?K%arPo1dk`lc@`z>2 zNOl&OO;`^Cg|ZT62M`Kn_qB0bT3*}GGFn>TUqW9W?R%wrytWh!zWx8cb0sG!yyYRC zdv)*C9p`-K8^7=OJ@?$)b(g^N$r&Q-A-G^eWStXG-woUXbmv4!X;n^%Vr{K-r8d57 zMcl5vyz~u$RkhY@q_>6d&bA4Q-w4jW8|ksUC7YU;3O0mr6VXsORvy!?bY%D4DS-QIsMb&JaCjiVjQF6(V8D@)AZ(A5_2VDk~NNX}36 zmX#}lW${yW!TR%?_GYRh*KKOpu_UNa)Wv?aW`6rYj|-Mc;eP%SK16wrm`5sFF>yrv zY>vCeFKaj(oRSr0Yrn-^=?lxkMr7V zoj^hvW~YQJfrOW#`BQ-zphnoUnyLCJ?aayUK!G!W+ZsA}XF@o>lAf^=Vagk_IhJOWPdEK!nHtxsEvAV&z`$OZw2`qgz&LxC_ z21p`IYd3I5fE)o^lTZU6+d=>vL;(;Ki^P}`u)-}aX>0Ok$R6AKrSI8T;}73{>ohBI zJ>b6IRl@s3v$isz+-L|+wDgB^^2O}F3H>|w8C zj^No_L_#dJH;Z7T$9J7fwyP;Ok1*B_Vh@BbwxE-p5T3y-LD>pOeu4+0Fj>08u$vI# zb&;YT2@&^q5Kd4hdJ%Yb7P3#(rgycgVq5xlw5w`=P`0EqXp8b!Ktha?G!VP*zE~O) zZJThjPZQgm-r25y_KB{p~orBHosm0(?jpKnF-#uu(Q&;cI2A%tNdQCf7SYH zMx>m^7^>ampI>5EBbu&IsMYr7)W*6HD6efD=XcEg74OaC+&;HJu0pw`I}fs{An$9G zYKmY&CFQaq7RwcJ@+gy!nMjMsWd>^?Sss*5Etg(}PC|KIH&j;&-muAFy)}BPfAxwM zr!f|+Dyi^sU{s66q;S^2wwInY0a!e{eR)cN-Gkhv>ja0TC6y|@HFld*>uFoD3So&4 z#R8_w?QDwq=3?uQa$IXI$3TvOY54(Eta=X5m0)D#u!mW3Mi)$A8m1Ff!>!HnLwJU5 z(85#>ijk7nh8zM(v6EjZyasWAuVqzJEMvf=a6n2NNEL{4#O1>x;C4_%X)maAZ3#}$ z;Ixxbiy=aMw1kQvCLjW^fX-h<-Uu1l6;{jno(`4J;a$2gtnhbqEr6!!t1NP7!MAqQ zx5RBV>B?NC?G7_;m#tjO(`RPi&*l7e-YSqK)wuE{`V9^r=v3G%mTK2AS53K92#}C%|@H5#2Tv)0T$*kSJ#I0{aWbo%w9VC<5bjtA&Ypg zb=3r{zDnkE-0-AQ&F-)u^zE;4ecKyhw}3iYRMhxx!Diof)kyFEK&ip`{oqB1j{yg! zlMbcS$|rBYQl-xtsBvBE4Pz`vg&+8l395HB`Ve6$W)T%*OfmEb;=@5am1_jb5NQOI zkeR|zf#z!sA>B#cDwMLpniA=9>2i0N4H#xB-k9#3eTvi##4p<~Nr|z&Wzx>_;o)-j z`LeyKq_Mr(F6Fke!A+Z_ySB1{iU%zBL39@8otuGktbku_8NM~r%3AA1br_xywTc8M zoE&XntW=eYs4Bs+dFWAUJ0t~%Ftxz^*!hPg!x1l2TnDpVBch(FyzM9Y(e;p{D@0yoRwZVg z5n>r>3J#vXup`o+1VqNTRl;ir1y-*2;e;RWtb!jV_oaw-qjbR;`7rhHn%Oj>?gO~CRnAD@7Qc^<2x@2)+KGRB}PHDtZnIL z_TxvJV(WhXwU!v>-#I@N<=+(N{6d~@aptF~hi3f<6dxYNhODN5KdG|tWx{c3OG4R! zPxA>fj#EU)adKuRIW>h>{D>pT*`s7e^G{DEQ=nW;PMzd1p2Bl7i6;L2=UB9-Wj)m3 zNA@xiH1PDt*GVTmb1(7S&YU-&F=1gNs{$#>uxKg3L7ZA=>ndQ=Msg*`*qp=9fOAA< z1Et@Cln8^3USQ|M;8&W*#7aFwZYw1L+T&gjpSKD>yD&MiYQd_fUi8GA3n&a$t+m|fHI$ZHYy2h;DD(s2+G^=LNWYWiz<%*^NHgfUbPt?R;&2zJ zlarZ_D_4HvS~q)se@na0TC>vW-nM*4&xZ4ku~}MUt6AZ6ZU}4Ph54`&iu#M6#Dxk=r_ct9RiAD8SsExspc$n?*x1Xh*TtiiogTH8*V#R5&i|L z1hg)P$_~T}R0%h&p$u2GT@AEJ#7^L6DDQx1LYncRT8|K#I_}2sKFL~|Vn~PYh&8oz zvAXJ5AQ<#Vt83Z6QY#RP)Ya9;np#`=MjwcFjrPrsM(OiS9dA1H&JxnMdb9?G%Az&e zbT*~6ZEAD-GYR2_rI$2CJ3jrur#qsJmn>}`jKs>$-+F#|G&1=2c~Socxj8<_o{u$N za_uFJeABlpd>WNh=E-C{?B|a5f(j@Ytw_i)>dfjZ4}t%hx_;3XI030Z`SuHenkQdE z!4^V3J_VZtejY+Ty^-COE+oWM-&))-&5+21}mz9(Gu@Ha$L9y*JYxMgeN$Ti8O1( z0OTxByhD&tV$E})!9hR~nWR{UT1P2705QhNQNb+2o`v@o<{~9!P7_jUt{xHvoOq^Y z5$F02>|zi6vmtoF+rV&?gm-K?)lp;|(a9ON2-7Je1j+kg=V@|o0%jof3^pzFW~6cg zCRMls9(^cWEwSZQvR98bY(3?z_u?-Uimt4yTNypIwc+S5q;c?}-yD?2**fXzU$L7j zDjqr_J=L}3RN z*Vb0AT=mS>&8bDc&XRpM-hOB);`x_fl@HGc@UZaxWB7iVoht4mT&$WhhGp*tSusLx z;r-;!zJKBU{s$iL-!CC)M;v*fx4oyQz4wK}yY%YJ+!*_ncr)~7@~gKHFV&pY7la@} zgqBlnokPq?c_*^38bMTYs;J!&vI!&Y;(5XH%8&lcmIVXSM=wZs2ZO>FRvcWu{NRoB zvb=R?YwON^J6cT^8n_eXb>4yLfR>xFIMVD(r(DrS{ex zBeWAl8!`f1n_q!+}tE|R+55@yM(F^Eu6H^+189r5oS*`1KmjTkBa6Wa91?R z%@k{?P(zQ7W4qSEmTj%>=0qzZLoG03Tf=}z;{c(v9Po16D!guLEG;i_>qCh3m`Y4W zw?@OH`m1YF@v2*IHCQ`qu3#&xJB?c{j&y`!ua6pI625+PxD(yyu68hFF(56t0fV0z!i_4;snr65Cz2RjhR5x*y7)%yv z=IWYccU2seMK(h>zVfk=wMtQ4YcEr2T0G^fywhNHG8BOkXQxZC=xNcY${n>T?b-@t z;h}gBb6VHwVn*g`EMvw7O>3;W3CZpVQ<7KIq#zfVX(o4IX?lO(qggQnVtxqzT*pk)-E0{iM4K1p& zcCz6@tOug|xh z_2n7qJ$NlNnkv7;WwGcqHZ>yjg4(9h2}{7c;3sw<`NwKKX|r00SsJb4LI$N*~fzkqb8})Shd;!YMy8mmm~>VOB`h|0oI&6*^R&yu=B|z zrnz=ZbJrz7gvXNH4O@^c#Cs4X+-h-pYUNWD&9W7+^D%;{MQM!)@4C_HkWpNqwB+vg zUkklNpE5l?`||Wl2(CI_ddXGow^;nuuKwuZ&&CcWFO5DrRrf>`rT}>FsK^db9dOJ9 z>^_!)Z^tQY#12*_{V;lX*JtYvLTvr?lDaQ!xtLhdW#30e%;I^?2ZkE4D1s$X>Kj+n z;p0j`iU6}OuL2|=1|)D;+~YvA7CsHh(PbcxiUed%VBkoT#HbKQx_Z}{+Zwf+#~#z@ z7}3Rd6J5NwT$^mxtlDa2npIvL4 zwxQl!QZk&zvt;62-xCYZ(qVT7tg|n$@0)oqWP#|x)TMa*(){j|C(psfQ+2#ns)_CCgpTcOoN? zWr;D5fg`UVCkiJ_Vgh)ov&2LQr%sv*ojup^3O62J`+K<*py44)0lN1Zt+1420PJJb zJt~L+|60fac0jNPilbdV^)tg=yf}qS2#h8;O!$a@gl=&$=7Ii0*iT+7gO6>PP!LC) z;}h{`XM)UjxL{<{UE2P1D~d(j&TgNz9S)RBz=dn zgFgN}(ES1Db9JL?qguR8Imu2JDi;o?X-rcaHR616gTKl<`udiQscx}J# z-un+-xMCV^8TM0WbA6du;tZ^*gRvybxYwZbmctHgflYyt9(RkB{|01I7>o`W?uaTo zksHi8lt_-j7={ZC>gk-Ss+RF)6w`uvEXIzMf7buo65EN}q&ra<(REsSn5ZVIyhOqQ zatmo6lEql!yu?&z|AF-U`RN1wou`pvR<~ly%G%nMwW9HOL|TnBP2ojW_zZ`R7YA@%I+zH!zy+11WAAi)64aDo&Arawd(9vIak zCZ-PYe{oISew+<)b=(fx3|AQj{I$~0Y$a^Sk9q=N*9iO%S2W`PwcGlcHf>7lGTE54 zI#}ihw=3HQhL73b{g2lq;}wawUWuMp2~FU8<#N%l2t}D6CIK>yD>~@|R7FKi1GZld7v(v1&88 zgYK~Q_s6YQ!dq?;bTP{XraRS59hlTTiFq4c}6SXP1m0JVw!^45ax@NYxN&NIs%n@iAo|%z7q{+WQ zEvl0q*0`13wI|iscDvM>EZ^J2xLO{bp!%J zP9?7J_iM)CaGetG-|d=hn&Z>^%o?!KLLL|8mCT!MLf`RG@8OkU$mc7QVG*oD7L+ts z1R*Wq4nV!V*`v6-(sk3Yj;AqkpM1-o3cLl9JTj?4!-^y%H1L@`BV$6C`##xbbil47 zt}b96p*6&vggSzARm4T$?gF@uMg-X_biYyl${9a>9P%xm^4~L^n!0DUc`E4_?0zDB zrtD3sO@Y;cseAmX*~hVEa_YFh8=s-9&~A9OWLfq_qQ!nh`nsAw(*&^k zoA6wLbODbZ@F3KK=Ke8^;F~FY#wh~%(3<-kMJ$Q=8&(zX&>Vh2eS-fVN5=+n;Azq- z@QZ}mlvF3Llj;g9=o9uj$OXYT8Q_~?1>)nV)s0LIC|~r08690`+K)e|lbFBh2S^vb z4fxy&KinTG_Z{Lwb%PCKDQ8H;brG=T9R4DA!Yw9bjw9q(d?E)@2zD>Ub||LQ=9bN3 zZZ(LVk3H6V^wx^+R@{2D_e}FMpZ~mj?}DRk6`LyBjxN~iK6g?2W>sBeab$7-&g$}w zHdlE~pgHC+3m5(4QTPF+yFd9Lg=2nDf+NM(I~w=&eA-Y0t`c2I1LTgD9x$(eN*vln z4m!DhG)As99;6H!Xl=^EyX-#%3m`^p2pr9Dfg>o7WD^LE(*PMwo$`22d89A$x3~Ch zhA)c!{yQGecRa`WV-QEAQx(aw0ODvVzaP(X3Xpr~&i+onzf-USW-?WfhmRs3GXmQS z9{f}Jhh=LwR&q`qI2P?fWiPO3!_b|vT5}er$G@o zwXieMzjam0Cw%EdOW(F|_0n>W+wCb|x^8=4%L%~hO(CrsC2mbRo4wR1O8*J+mfG5` zPUC0zBjPWQLsxS{iqp*53L<2hJFRXdMp6PCt(Y!B_Z30Obb^(m`?ePxD9@~WrwFNc=*Qs{`Qhx z;@&Rn(7*iVhgYiA@i;%m?7i)#9S?o?nQonz^;qZchs=90a@&;2$ zTbMRj&|?oLD`z)(qRn-l#Ja>u{Ns;pgA$FFa7%1WwYqiVwk~5J#5`^m(=>sDDHK|C z-pVG8bd{!Y`FUN{ion8ktJ@6z@bV62-I8}aQ4jvYiNx$7yx=HoOg`$FK2Lg?9E+aE zElu2g`0ZpcIU+%D*?ti*A5L1yDX=N2im#=>5dEK}-~GdJh&q*=D-==U$HAWRK4%?- z6Kc;L<9b9jpP2V$(g-S{tHAH1c9J-BQHCqr;~mftV8L-j88KW2U7!JznSCHRDUS}n zWTTRyAZG`WQUt~zo;waOBEhZT3!#KwJYjf7Fifx`;xQ?Paf2l{w>5|#h;N9a5de(; z0-gh}uY732gJB!2$2XvKuUvkE?E*_pb6{n^+1BQ(6qZ(c)C!<(n@cXUs2q`GB{Lb= z11)=21eIbG)38ntS{jW`?{rrzG->NY3&LA!7uHpnt%ya0*;=(q7xR@%p9+?2U<+cA zQr(MMivlUe7NfTP!Kv^_Yh<*RxWpgX=sUQ!BkvKu`?L0cA7#g>H)HYSAE z^u|}s*Or?cel?=yMuj>6e{0981z{%w0I^VGsZ$qfW3zWQmo=8^N~#2ZtV?WHBYvQA zmuhtIN-tOYE0*sD)KFAeR#XO53fDS&jUV9-t=SuO+ngoJ5}is7YCawCH$hi;jaOUK z)=(P@v4e7*kvaQ@YzQObDwGW|kLek1V~GBOb`j?mRPwY+cLZE@+xz>aFEZ-@iaO~A znC+uqG3$m6EgOY|^attpx7F9*cCh~7!Fr*Gt&+awjC+l9a-D8GKCq#x9=V%YnpBsENzymYFIWNPH;}n8I@FjrWi-!hasmjwF zV#C3?Bn=UitAD>T`sFW2uVVB5auqeM{4;2I3-vq%`&;-BzgM#)KE&^pZx!yvosczD z|G1hhjPfH#NehZ)5eAvZ8Ds3TzEgr^B}{kNhIZLZ5n-%}hmCpqkaD z*ylGzClJ+9eYJXu|4M-kFK=YmJrv!XTc3|j&BUfwO1Jxin?4`gG%-_tQ)DV~65Fwx zH@*y_@S;!nG4Ttqt5!ko?3&k)DBXTMB}jav&~rfoP7?%zTy7q)ZR73-AZh>(fCwRf zIMgoU1;9JPG9)R^pkN4oC1-=5nH-ADpmUQoW*UN&B8})T+a9=R2@`_;?ZL_mxWvj{cZH{GMVCikZU%=`94?RVkz<2=9|H?z?|Dn;r0iFh z)_AxxoV{8A!xcXlAL+X2fokdIj&|}qz>g~HHaHUqDknaHF^PZJ3_KZY z2Fl8HGw*l+tiVN5K?#C&ouacr<}TsU0L|l-DW;Pi&)JF%Hyf$kY++27VVvo?yn%TG zEDuIdWyCEq1lL6NYRAhh*JWeUlKv~#w*U4S_SJV^e)$dYn##U6_DSt4|G3kO45=5P zECGUX=DCgFctu_)WUJsxl<^QWg;1+9fy5NMvNa@LBc1Ay)>Kxqb`-d*oc$H@ay)+N zC+vP!!j+j_P~9fBMx_%T?e1l=PGD{dKx9|C1dq*FY%n_>(RO z*A2s?1Td~Z#m{9JDjpCcKd08`KBH~}95^Ux1-#wdeRvFYf2mG9jc6!zWUa!D3k3jB zYAAN+AQFUE+qxHOzJaJxDut-hH#Exz^B2Tb3wk^1$aTy8w}?q?X`72+b?m6uFYeFp zO-R>qdNA(f#djAmNR&+$g~i=H?+ToOi;B}Cb_P`hdI^LE7uha*o14&NfMk)dD1=5s z6+p{53wj4){`d@moP-N&(5d(lunPihDkO-K;-bXSku?M>fggj*rg*a-wWbkhhA>k> zzPu2q3e2~=oPuDt+uF)Yn;glcy3y*elmC6dyrX1z;eTGtph zgVwd$74~bjouF>8M9YOzgWu}17>!>4B0*CYXh4ozdB|RA@IgwLO-LB`dG#QiTU756 z1XnFvXlKk>MIn^59Yn~>D)%JXKz+eKaM#O9pwa1aOU7}p+a4~#qy*p;JSZtP63Jdeb zmsAE*@BtL_m1RF+u@B3cl$c|<$pM3sWD&tO2snku`HxEQk7Z@q6b{rq`LVV^zkO5rj|TTFz5J9={Z8 z_e=X|A4VdUgx^n3w@KHvo;-;x{@HgPW=rmR^QosE{OQAN<6SgPC- zzWmT)b^d=-;5pn)ByCtY?>D|9?kt*JsACi7u zq6#hFytZCZwS3d+IUVB!^fl-hh{4Emujn#sh30~P?*2vB7M>t!N4x^~p%*09%`H(4 zG0^H01ZnQMJ_)^wj!ayy%L;f%3@4amY87T_GviX~2f^i@K~}ND=JHfE11atfnzRO!(`8^L*GJORb!;88CGWW> zdHlF^cQl)gX2)XLY;5cWU0u9Rr&W}M>%t{sLS@i7G~gRhm>oJ5cq-YCae<#P1`s3; z8%)iWrFA;vMGC9S26QIKeDnOkQj!jvyf`){9}@>>lO;Z1iKDL4>8z~t8dM5}MxoL< z%nBS^%V+^k~9vl@qz#Ddf zIR`g)_=~O@bGfGU!0T4QW@)YLIFgJ!v;Lm}391e|J*}5?{(N5<`|hF^hqd12)tGkd z8E(@lOd6-oV(5L(WN*qu9oBNc;=%jwaV)Y~eVZz#e$dcYA4xgG`cmsZuYV@el<5mQ z&6Q@?FM7RVW2Mnje_5y9v~ACDhb3eLkU@9B`(~|6Q*N}BudQ`B?z!(l(re`Fm1`Zs zDj|KGTo_1HB&rhLzp0PYKLiAjCLC%m86a7}p@=SCP;vm{pu!pm$;ue>qU@4w9>VSs zs~90A={qQk4@7?qV+YzfM|T$A$xTga&T%OHUj!@4pHuJ=LxX|07_TJLQYnfEqevj6 z+AD#NK3((~%kQ`*881P44cYWmai$YBn|GbLI!wvd{|TAqmf>wpGTbdv5rM^yekTLn zK8{L@!*wmId_d*@`zK+|J{JnfC^d{b8KU;?IY2Y0Qc0pCK^ZR!`_I1o{C|ZXTL|14 z0np-XH)^`e@Uzi#kz*95n2YF&1%&t5qDdZ5OV zu*8!^kwrqpp2TBDv1WN~avC!Ttwh#k;eGDAF^0*`lew5ls-r8Ri{X`EQ?i0dH3^g; zFl$YO;tV=(0=O>t^VL&`B*79y*cq+B_sn+b-Lm6mJM)xD@9o}Qwev?icfR!&((foq z0X>uOQpjGoqVyi$)Nb$bvMK3b+c&khB)Z$Aw|lqk#r=@``hSQM=#6wP7?otA!7~QG z31!TOc;;{fRKU=<+>}Z;wYKTXhASIaDZ56mxhfNk*Ia%71DCB49$nXPMdK9>tHo7Q z_uhA;E?$|v`s(2%`}w-YE7(%VQ=%+ThE*bQ0<^)iI@=wVP1m};s2+PAwf?lO%LC6=x{Y{8~GO-SI1z^hrD?}sqo%W)04 zj*;7LM$}o&|4?6$N(?ge1nLb5djcL3dnLKC#ma>6^{T5(uY`0zt%BF#l9< z1(@Lmcg3=k%PJ<1j@@ng>Z<+8{mb@=C(P3Ks&<%5FVL5CnTJAMrB!=NtE8{Ab+oR% zYr~!ErN5`xyrgu6^vYLfSeNwSU+B3Mu9@cyQAH>Z`icl4<&KN$gc}Gk-VQ;y!-e+R zt(yWjR&2R-XJZrla{r4x4X-u!N>8m;ff;8#z&qFN+Qkn1K5 zGxzMza2CxYw>)_qIUbh^(4S3M^aPb{<&|6IK1{OBhx~wfp$be-Da1v=#XxgSJcw)v z5L1KJPDP!VPf6%8d}KqBMmROWYlXFhD}!u{*X^+w7d-BYl!&^VPaE|L^#HK4c-2j`ybcWNv;CEJa3JKftlo(fQ$V>7sh1(@9lE@8%`33mles+B>) z@DYxcwb_X!f_};Fphy>d+!bmxQ3VfS;ka-sU4miZS(h;T8ZM!^YAYN4#4AusM+#RU zj_?a$&Z?OGeSQU1r=cKib_0ApHcZG#a9Cp_0E+7kFoMR_fpU>wZBK)XSOiG!wq z`g{X33j{I;2TQ{3BCbO66{G2PVN=Kk@mS+kYc4m`)HQ4kRE2CWe9egxp$5OL+`a5@ zc57Ytwk1_;)+Fk*T2ZZ5EtuT5L+djBS!nTk$TJdl+Kut9N=t5kZGLw5o@iaI!t`{g zMUfBQA}rm_UakG6$765xSoGhjdqJVXv(zA$S~MB1TzgHEiG?aEcJxG_|8{9bRf!QK zhmOUEvpa2}rP~{$v-iShtW+ZDaPeejyTPrSVPC20DkaZPWM90Z#XDKI|H3O`wROY3 zNkwbuOKw&{CNHL+V z0-;DGkL3M#!<|S_Hu^{5krYh~sp+cHX?Q-!2|@e{nB6ETgcC){(C!rCKw!S5xwjd6 z6p0u}9>_jakR_0%LY_ueIaMm0^ho!|g&Qj?q(_U}fQNd7wn4x}&06Kp#F5h3WgfOS zUN}y18_Ly%u)hGqaBJ~s#p8%0@^>+!u8GebLEgA{J}1NtoI@C+Ts9fdLu4BodM8=Z z5pB#S#@J_<@N2gYdjXM zHQ+CAt#*c^A*Wsmsv~1X?dGADHc{|3Enc(Kv#?cHUDnnleB^Dg8w_p3P0QxDuLpg7 zb+slEt*&r;1yQRCGk1$oKhsBOpIOht3Cd@Q-3@Xl6PfAI^e zG;{JR)8BYrdXE;E?)j6`&z}^|pGih9%tuondj0``N9~lPIenTGGe(JRl|z-jNgH)RnZ<))(=_H?_B zXuG?$M_k=|Tvu!N>|qBq4xJ#Dm+0&kaE006*YSHjPQ6xNUSTP9yWJ%wy;f@k`-Jpj zqS$AP_oMCqem8LMFlVU$mzQdc{uV^f(f{404E>h)or}**j+5wtUklIn z%0-3ztR{e6C<;lWGqCbzk_gCjC#O^NmO2g452~N+hB7h(&3m;!brLnQLEjGtF2-=7 zMx1?KfFsO`Y#NkTMUonzG8q#9WxLJJFWyaw!sS8KKkEpT&rE|HNvQgy8gL_iXm}uy znteIGw>p^u0$IA`lgqyqWXUffi|IV-F`ho@JY-m`43QO8XQg+kKYm7ET$*3PsmVY; z*1Rnq-Dhw+xMg9#iVrS^ zcj}CO$>SVyR2?oSnB}k!trQiP;o5BbG*Zq1m4yR9_`OU&gDI6B%x2(}O>`*DKZ>HX+sFMz7Ygcqe3z`TB9metq8yzhl1MF%WAPtpNY zh*A*ZjvNw|PAItOS3K&*MI(jch~%`Q_%MNB>P~jA+Prxc%83=;*zIQ?5W*?67NL1yrNSL}O$ z9D)g9oIRU@eObiUo`Cl-j*PB7^RAxvY1~EJ5ZoUgok)V%1J#ybaNr)o#(?*u1wd_H zmJIYvpxJ z`SXH$3S2lOkh%cJ}TKcxu>9gydDsN{)h1CL<99O`k zGC1AaUmlgag9 zNodH7B12jfDbnarfn89rRnCx`nbg{2(nVIYQgl_9czvD{SHPiTTC-ChM3pI|4Edzz zLEEOWtWZE!N8;t)h6)Er(%V%!7{nTd!)QTTkAxZHI{)RdXQMcm;xm6#d$Kx*zs#S^3lqT)GD2`&WSM#94r{-~&_WBRD{-oiWXymX~HIeGiB zOV>(IwhvZ|HPLWrQP-VaiwG{bsOvv?+K4c;9V?N zp?au&T@j88tZgVVLpW8W=*xYxc%GPC|99spV&wfF&TkWEk@`QJ9_9xcIhJ|fmMs;$ zk_QwM4t@l*)rB@m6;q+DK-*bpE9Vino4maW>m!A>4qmeQLYpwCJ8%b>LW?z~d6`06 zK>Ki^EuwvGp{+ps_Ci|;z2mcmHdPM#N};WT<$S8p*3S#G^1f{Ua9?(8V|s8h(~~PK z72dX{bNS)ySfHuCv9M-EdMusmnMn5sdJhHi`v;pRCI$inx$J0Qd3J0foylYa}{Dy{s!utBY?C89{d0Aj4hWYy#5ESWogYzbFT|MBjJ@>P7m$qWd9PIsJ z>^}zmq6zn?@gMeBfo;b4c0KbZ@ZOK@dgmR&YaSSlLCpOV_#MFV0FIRfaw7oV06HFR zoxyJwYsUF!`>>`D>+9zY@x8{8_t=2nfq&S(o}a@gf9Cwr!(aD_Kk%}F+L32WOSE9n-2i?Qvqfl{fOg`!urO?qGMayV%|A7(34HVfV88fCzYi zJ;*-8KFdDGKF=Ov53@(u3HB)a0(*=-&c4W=U{A8A*wgG8_AKaWzQq2SeVKiQon+G} z4F5Iub@n{_2Ky#^f&B~n7W+2)4m$pun*ZsY?evv@4`HgbPL2Cj*7G@K`m$mt)LV1fsVLO=)#6+)#@C4_{q5D}_{8lhIG6QV*4T784i zIM313H!-{)BQw^UOZSY7XNSio@?uYazZ!#jXn22G*OSX-55#)26GMe&fA+vwp_xe! zOz7nea>Ii|6BfChAMF7#C2e=k3fkIw_7;2})7fh#$2Icejc9vw!|8#(o_rduY)?O06Kb>$%%<}LUUp5s!&!zJd*<9M#H=OIsq+tsK_ ziVSX*L0+63rv(KhG!CB4WO~NseRNpJ=3=8*Z^KO+O^;2)`iJvcT2B-p6}# zxYD7XvB7j6>&NohOj_TUOZVf<`fy^3ew?e8Uif)x`g3-jTw_V_4AJP|{wYT#VH>mp9+JVV2j0QaKGhgiaFE{dk z?2}A(@T@%@pY-|1uVZp7KRG_0&0%gd$$Q`qObm}rrY-U!-TLg<7^X(Oykfj(GM~1~ zZR$Baxrx|7u4goDJZGJ&xP*I%2U|R~5NQY2frocshnM<+h}<1EfeSKK{2uH;R8S$agFqa|8Rdgi)%b-vWW24aw1>3#!m4iAoDSoItE0{S!WO7cg!^e9fE zuw5=ahOa<(#r|r#-_&=h7aQpXV-?n5DdyR1Y}N~ zKG>I;>`&{`2giHH`f2Q;IYE*I<3g7{R1gXMBAR-Pa0uMtv7X!^EpO8x)bSS1W4K4d zo8$chCf>+T_Tua(GijH+cxV(7VFZIRhhCA*9kQOYnik{(93CE5(2|l~@+!Peq;txF z3@%nX0Ex=)w3@dju_e|R=_L(WLbF1;xRf1-`qrd!tr07RCVTl;WyffLzLc3z$xY>8Cfl3V z4Q68#SrUn9^S^MPAG;Te9c4I-0)$HB8ZS2?wS$@ z&Q2(Y$9l5|6~ki#S;KHXKUsVrCV3&3E?gyF%&Ug6_i&$T9}S*EihcR)m}w;4+tV9E z*PR%~oFa~-4~dzc1J(@9e7%zyT*~A`c3}9Rc5aQ4KPT!3Imj~wE$L}uq3gjqY)I`) z&xC=uvN_y)$N|2PPiOFS(R<{rCUWVt{J|tTHQ!E`!83~Ghj5?OB+4=wxPMcpSY~?)-Vh=;TCNGnPKUACZnXco))*0hg3MKvS1uEQ@Wj=&A!feQ5*T z=h%1-<2Zf5kj)MDj13>==d2!w6zLgEYsPyZKl*ycRCvC)@#6UKm|`4b5HFL1M(m0? z9#>A2x4Lj3%Xkh`Ds|de{=!MRBL#axi9VN4Yj~H#g(@$c#L4J$xLee*4&Wi+{_y*p z*JC*0*2eH)^SWGm0MBBq4>KE@_{t$=EXeYirlJ3XZpiSeX*vbznXdS+QW(n-e zFQqu`s&aVF=?Pmdn?+BO2d~_b^=GXTbNRd?H`#kglkdSDqLY&wc?G@e^BBT-CsHrkWX=P|nahGOUy<0`q0H{AT3F`u5mG&Puykvz~8msI(o;dG{7ormm&(xu_= zke#YLb*@9^Jg$KreqXwuZmubxouu19vr{gcvE;`y5EEy{uSs4}9CI2xTlCCyMn^MH zjEi9{Z_=sDt)ZUGfFXZq6e?p3x@6BGQ(*y`BlM)P;%9wOr1EnMr1F>x8{%Ye%&`eu zVHrbX6hdqivPXZ`s^Y%s{TM^V1t=?-{^C|7wJe1_(4*5i>i@dJ3iPt#nW1?A9nVaa9p6*j0Am1~OdKkn9ZADn@nHEe z5}*^sZ_Q8S@HmTy!qQ~l#AGgAJk0DyY}g z%uSAs;T8z{N7V;R1agT(3?UA2fH)2@5+T6};1GiE zckL}z)yx1l_w%`*{1a5Q&XJDJV?Wkj>$e_zOF2rZM18ID)P=hqdieU%x14;tQqC4f z_wIk_{QAbn@A^?bznjmmeDY0CJolsT_>~VS6@H&mmGw6~`xQ5T?KkE>snpztQs4H| zPrv?&r>YCL$`mOQXl+0-^b6r$v)RRM~u%qcML9bF~_04KlQL@ADkH2@Gz4eAC zxIecwq6+gF2x;rXE$_rhY)3 zaZ>e=Dmbh3{Nw5q>hG%mOMTdBsCTKaP~V_7)c;fmPC@;y`VPlazvl!_-qDVuzES;w z6HyC~ng!?qjXsE|O``HDHS)i#E~-oFvbv(KsoPaWRaH%8s;*|xHFIc%d9|Px)sk9P zE2^zds#Vodr_^cHrCrXcHFX{x)l+>nP@8I~w$u%Ehq_bUrS3z++^-%`52{zFJ@v49 zrFxZmM7>(QM(wNDs@JK<)D!AS^?G$vJ*{r3XVe?jv+7Oi&FU@amM>OcqTZ^$RDGHH za`iU#cJ-C&t2pPY)jQPJsIOCBubx-mq`q0bQ~eqBE$ZFs&#G@#?@@nFy;ps^dY}3Z z^_}X^tM5{OLH$Mb-RgVQ_p866{;K+G>Ic;a)DNNi|GN5N^|#cIsK2c~q<&QWnEE^F z$JI}$zo-7b`l$K`>L=AdR6nJDTKyySkJUd>KcoJs`oGoBseh(EuKv0DdG#;UFQ{Ks zzoh<^`q%20)xS}nQvX){iuzU1$iGvcR{vgoM*Ro%AJu2oZ>W*_PwKbSe^I}!KBxYh z`W^Lo_21R+tN)??P<_Esj_deN=;WN(DLG}Q;#8fQlR0&#>C8B@&YaV7=A8v+(OGhq zofW6;oN!J$r<~JH*E!>ybMwyDzEAyS(80F4N8PJlrmj1Qx?An49o0}x^*7+N z+fe!jgB-Eo|B>(VQtR{qTX*Mzj8jX3#YRt9VkD{&V^WAQsYXv)Vsxs}Q+b z>Sx*Cw8Yp|qc>O*BszN9660BoZdqbvtI;!-7~^X6MoVg){j-)B_iFSeOGp4Udb1^D zff~KV5>i2pko`i)2Q>m!3Lz=f=!-2OGt}ryEFnGA=&hEJBWm=emXIiF^ktUR8TMap z2`QsSZ?lBFQKPT0qzddo?n1~QHTp_RqK8IbWeK^YMqh0S38hBwu!O8qqpz`q)KVj9 zM@W5`{nuGSlBvZ{RTv4s3rqrYki-K0i;&63z8BjFDS z?Vv_KXbC-`Mjx<*#!#alvV`tXqYqkA*VzAcOXw3d`Wu$eENb+_me4V3^fxV`ZPe&* zSwioq(T`X{1F6y9wuCNHqYqg^E2)w25QKhGBjF)P?3dAxT0&>3(T`a|d#Taiv4kE| zqaU}Vme~JYOXxN=`Uy*DIW_uwme6-<^bt#FJ~jILme7G}^ifM_LpAybme7l8^plp* zkZSY~Eukyb=wp@yWsH8x68ck(e%cb6RE_?TC3LD9{bNgLS2g-4me8|m^fQ*wxN7vX zme9Rw^mCTb!fN!-ETNCp=;M~q%xd({Euo{;=;tk=t<~sXSVC{Bk&Igi4X#GNUk^Kj;g5u zZV3xPjc-`OR#4+REMYaM@tu~iAJllq5|)G--(?A#LXGdXgmt0D_gGTD#{OPQSR88n zGE3MVYJ8t1tPnNcwS+yQ#xJ*oWunFpSi(k8;|DEat*G%UEMd2(@k5rdVAOce61I#Q zKWquBMvY%-3HwHkUu6kPM~xq`gw3PIueOBsqsFhXgdL>D`%-(<iT4StwcmGAZ1qj&ap$AXZ@O3A*SO#1&3c>OclojZJ;8eL?%;cZ zUk^SX#$hX53-1nJ6W$D;4}T>5q)zqQ^au3s<}T#!&3$L?vr#_Uie4LiXY}K7H{OY# zi@z%VY;r1jXY$eH)5-7Wf%GSe7mAM-pD+GW@pGkmX|wcb>FuTWmwvAF`Es*-t^92HedW(qp02#V@`-Az z`at!A)lb(pYwxdpI$O)s;T;PI$4{`le_F1>H* zrebb^uYPFtbDa~NS9YH7e7N)br&dnA>(nPs|G?=_b~n54?EYl;=evJ+=JJ`ho_Y6~ z51;wPvukHxd-k*E?mqW{wfx%EwWroTy!P{JpF01p^P>y#g|}b$$=+J;TYH~eUtNF2 z`a9QurGK^m{{F}MzrL}w@%YB`8$Z7Bxxw9ow+%kM`N-z?4Rgb5!?z7TvDMuA`mOKT z`ia}V_O=h*_A|Hr*2VPVwTsVRDqUK?^z5Y%Ui!I9pT9hN`M%2^zoM?3z4F|Z_g^ht zz3=LeT>a#BW&3NlKXOf7yL|2WYah7wx!V_R-?;tx+kf%;BiG-1{rj(f==#U5f8s{> z#&b8`f8*Ehc>InJ-SL?_m+pM+o!@=uNACR8&g{<4&JXN-?5>-4ec-N7i(i7*GyK)X zt{>J?M{jhlIvcAip%Cn$L&*Y<}UJ!og0 zdOK(b^LoD5W_Qm$^N01@H|J5{VW&T652xS1W^(@Fd9RW4ww*yoH>ii+p4_MS!t-8+ zO(W0MZAwd>hcB!6l_1gijY`E&I-UIFelJJ<$a%JAS-Y_|`Tk@jD9iaTI?vRchU{FP zoVQyJDnVW+lk+Ce-FEu47G0&=@?5^ZQRa3>&z-hQr>z^qtUqWxF)9DFKH`Vq^(6gS zob+agZl}tb4(pek_00~wnl;*b*l#>Br@MO2_5B^+?>lMQO_K{rdC=0jHSn|^xbq9M ztt^||^8o%`#wNZ-y;rX|8y(nmwdjO8#l2Z8x{ZFV-e2DwZo)M>Tv;9TYs2=ov&pH$ ztIm)whW!OOb zg`w;DjvMB4ML)>pgD~g1ei+2A?|E@93W8!7cmd9GUgacBats999uH$zrnJWbWgqm{ z>sc+-3l7_MC!@@Rjdq9K#_AcT-AwXf5b8owDrudTieXOcVzv{-fp*;}jFRP}%-~sKa(B9BDpf z?VBBH?XLR%$dgFU zUX;$to_$K06JL=T+rtK>frSSBN4EWNem>MQ;mp=8Z{J&5^7dcRYQ5Y>QEc4`_>boB zwVal#2V9n!N(~26bQs$l>I@pfwroXg46~avUfXZ_{=T=}YHhU;60Kg^Zuyzl+Dkky z@wR+_E7m$F(RY3+$l$pXB{;z?otVfZkzI@Us2&!d=WugMl6C28V8JuGg32W-Tce zJRMPvu$C45IC8wC=*3+J9?Tg=^g{S9@+h94E2Nwmy1t`fl*A8x{tt9gYu4!u-|_Ql zs-s*`s#il1(0;X5TbF!&Ei5qE)DYXo+rsYCwwb%StG$k zj`nHmwzPAfmLIf}eb3wX^&YQV-k$I8&1`My^zgc8@GjjZUZ3FreS&V8ciPClEM%Yc z>x~`)q=&lM+;$M1J;o;bVWZPV-m<4*<{g^1mbxoz?#6I%Ul1pZzr;`clscqAAxH{I z;1v9n`lO!2H>aa~F7f;*Mim9#gMJVP{BuR(FnYo`m->Y;fM?Sn&Bct{SZ){Qa&Wqn ztE36{Pb)cRl0zYNFXCHl818H1nLC3gt&bP%5{U8!b$gk3tl&wb+@@S9`bt!t3@{l~ zBJPKiy?mCzhITW@U;J4(`evfHbS#`}+IC0Ak-Sp7`{Umq$XBK(9^BTtZBDe?o_Vje z-M3{}PWtH?>bQ>lcq8>#T`9WZ%6V^N+eILqcj&buTwYrU`+x!j>7cW5-d)vUdu6r5 z2=M48j;=3y2;Z<)Uvv<^T-%oePB@gxi3)qf^<7Upp40FgCq$6s+(5&8@FvnMpU-Pg z2X5dwh&Ly2JtSK`3g{HaLE<@K$XgkR(Znpw2l?5XvKDWVy2xj|U;-9alRKFX?t;>8PEa zL4IANwb0>GD`ZV$ebKFF4eFXABiF(W(@5*m4wkX+B$;Gj#Yw2oRxuYYC$`( z_k$>i@?m`YvQ($w=V)fr8b}J;`bp?}s5v*{UeZsFn@2v-2rJx(rkJ$9H!E#))cg*b zE}tYpq&=hJERK5`J*Po(XT^sPdO}+Fw;j|yqa3**3N9=<2qXY!2I7RfTvT>vvp>va zthdj*17xMvb=}B?g{T%ew}m<<+~R4v$u0N+TMza?oVp3`=wC#e+bdtXR95Mc_-jFcKhXQ2vhI+S=dQ+1@&Q_5pDQ*bDn%aIm{2 zfWqRb1IjJe;#rwqjZQKhfi&LMydKwqNv|S!XqvJgpt~2Bs^&0RI zw1`6QppU_bSDh{2%f$>MH_mzd@m63S;`rDR`&N4)gJx2uJ?@(~xenahSW(uZ8ctPx zXDgS_RWND_Gke)@@)o+D=rqRJ3Dv8=Ih9TXC`3P-fdD7ZpqkWNGU8>{Drt zICLvO?(MDxGMPmm=j;wd$fb|i({o|AXKxXrvhz8?ZRz&pe0#Os9@S~zAeSnB7(8Jz z7$>aR5c~quwHva_*zBMZ!aR5?36gscc*!tra&UUQhfryH09#R>Q&}hW^a`@hpl#X) zU<4SLHkKH%f@G#$*D!{p2A_7OR#vB%ZD}vjR7X^P6KRHU@_lDLr*nXu^gJ8ze~@mc z6$BtqMFY4W7Tswi+ckaiqz(#&l|rCTuIA`WTebsqdIFa(8GGM|BQrG5I~K+nBsaX| zicmI!>v8}@iusR%xF|p&wVResd#zq?DBn%aI49!?G{Q-(Ue=>Mth{*@u{Mzbhpns| zTeCg_dDd+E(>HwN8Y=^yA;#8~*!!?2?9v`sBdD|Hq9!_wp9SZVlc8PlfHki2NgJzJ zq~2bX&qtxwqMvg4*!7}Z&h>L4;5f>8ew+{dw3I&)q3(;A`}s;+)x#)m2U`9xKGP77yDXAzU8aB2ES6t7$kBOg=K zqcx>~$)8w2KP+j1C{o1sw1^Y=@CGBh#X1tXD^89+J29>3f#qryH^*imyE_4c_f#v! z(P4Hcyq#k@TX@CeE_ReKAfy5?$V+$x8^85Z*67Kh7czwHTgk%8OFVvP<)D>`dSC~{ zF_~3JYDrQ%CaL!Kl3Kl%9Fv0;q+mz%9UhzkMu?P9aCd*v#h%6#6q67njt0Ze&Wv#5g(`=&3!Xmb6}`&dfw%3Ky_&y$*}; zBI1qkz;dAEd04r`!5|cXJ3TdZ`p_n&g2MB~$QPBCs|d;~D%HjofBF>n2$y@}XHxuVJf+CPxhhXdwOiDv9VWvxq*8C{a^bD9a zi=WVLQk&jjJB%wyp&k@w=6XTV%lSwQ*Uihto>vb2Bn)&}>qr(Q)g zoA^a0CN_=*(Da~lsB@$;loH_38PwN@`%$S@irm_4&28rjS{F*PIpJtPiFOJLg-85c z5afcZ$j~VC?g4BEzS-R4fSj>O2IAAQI_5cY4KC2;S0yiJ*bYSo^-`z3(izgM?nZ}0 z9k4xSz<_dq_~)Izv@$ysuu+pDu7Jve#&cyiMBm{(%11g(F#x^WFmW(O!hEQW4aA$g zU%GoKN~2TZ>S~yqJDbGCpnymZg0N7o;DV$1@4*GQIOm=3 z5OlNU7aS;j(Lzfe=Lry%i-9XFA?N@)rNzmu3q^e5Sf_Esu<~^=@^DUO+5Nsd`HBk*4R%`DF6}Ghn9ab*Al5rClEsFydHz(k~ z-`H6$m6l8OdK}l2J+7|un&@PEKKemUd}Vp5R9f2N40y=iZ;$&iO@sRPJ9xow7YE5h?nUFdnl(4p{K87I>;5W-LzLSLnYcs;~%y1MVoEiEjVV6Se#{oJB|Arrm99kaugpYu~$MihJ{kz zr5b^h#q!8gc6H^5KP)fZpxaT@)(a=w?S(C4k>r-W=z8QW&s|@h)@_H_Faq=NUXzO_ z=SvywIF#o~kEr1bA9nNjQ?4GrzrAjs>+JZ=hMk6PZEq|uAHeZh{pj9L5pS+bt|C2c zJTIsOT&xT@+rtg(h^~eCB&=lBypzOfxfs;u^;ui3yIU2Viz?W`=!~3B^rmfsN!cG^ zOvRFG2}ZKPGQBvpRxt{<{5f5Xt9q{DBM&`<cksG~s4>0J8k}a*IK@qiSwr9fBM@6Dafp1QL;0&CiUku%H znHd7O%EPppo?4F5cbA2I* zh=K*+!>Ah9N@(6fNidz|MTtXI%}5>ys+yp5;5vSAF?wY{qw8Ctb{4IB`;=EMnkZvX z2KL~h+wr{H_2#xGLCSOI5}#z5sf^#F70ZUpY`}**4#d`xA&EWkR09m|Z+nfPy|sl} zzP~`!uIKw{>YqB@ZRh>`VV_7j3ykn(EKjlACvlA-j!NuuBo0Hc=(HQc8^R;)#?+HB zF=UJ>__8?(fB3VZm$}2erWaO1cP0_vBD7=&PDqH6e+*T^P*OTf)jPMuqb;@NUo2Ao zHK#-&7Aj@5^_6(pi{mbWtzncUOqVSm4;S(7iGyTsXNofBDWGD3ybgAF(Zu--jq}dvF-}*&<>)ErLXEDgI%9y|f%W zURxUwoL{R?@6tVNBzr4=Z*Ti0?IHE@sF%c{5MDS?jCCq?+i5qaH6rZMwE>Xb7nK7> zdQm-w*y__F?4`gL8p3#+_Ig>Xl}&7iTj)yL4>k$}-m#A!@?>ev<9C(M>7g~*YOPHT z%U0_VPZ+@KiUbr+xSXco7pwnXhkO=)q|tW9HZgDn6)aAW!HhRk332hn7(6EfqOyL0 z$gqFVLEHWmx$;uE+(4h9{lUv@vJK=d1?=Ek@wY8miy&K+=2XtrC z>2}*K50B8KFP>D_(1m4_;W2fIHeT|kW#qWue!*^wx>IopEb75HJfe!SK7@>vp?qjNLu+@zK?`_%6KNBfIKp?5Y}(^mXni9^%Zph{1^) zz$6T+t4;^cD}R!DAb}esv=~1E7KfK}CCrx~jmiu7hQl;0=hG@fKEa_M=A#16SGOFH zRZ%VlrBZMUG+E3_glrwZT*{Sm6%6rxubD>0Fd<&yNAq*qch7TvC8+RUMzL7?<&BJq z^W>_%Tyo;^Qy^P$f`C#ReRPLWBIlif(I0T_B8~<0G7;+m#Vit?ZiHGe0LWI}n0hU^ zp$^xaO>qleuX)8ZE|=r9=oQ7;NL^wyi5v3XWmb~{zjnF)r6PF`|LtS%BCZ4oH|s2v(R z9*3+o*C@}#>pwgU2bg#R=?M7mGBn!6?TV7?njnSs^(Cl8z9Rug6yPMrCNf?yaFhCX zh(%mNUMRwAwkP|7emc5lvt8;mqPf zn8dm~!+D%2B^17~d4DOgeg?noDR_Bk=9NyY8wu2zB2zAc!BS9hARx3&h-sZ%8fm=s zjRtuLXW;sEG>!=mV5D-IV3{YR{LD+U~5(A|FhEb76nO(t{BXk2`rI1Jwz$aE=qi6-;C14P2SZ zUh$&{A1;BSC!>@R2)|1g5sAQ`G-X2RSq@5J27MU;Gg%YAt-r}Ah{5PRTm=Zks$a=x zW)x@!Kx+x_T3aA|Z>>wrvf@|G5D+|J`Ad+^i?ed>v8_TM96f&R~g0kzs0-HdSepC;$BuTe2f+Ab(>^TnZh#PV%N%nT= zsU3{{Bb!fKR4!d?v%{nTZ?#+PZra{E$S-#~%X#$NvXu|^x<`@qCnay5)DMADf};Q- z!yXW88~>~EHIWlmZ`8q9xB$pc7OyPsWx0Te2tXz4fn8QNhu0*{Ep$m8@_jO693NjB zUi2V|@t^_10FG%L$U^c5IWt&Yc;396Q;e83A29eOzXG?Mq&Np|CGX3Hew2{nIhPVm zBo$M~7d*lt|9u-QCj=-aNKN7>u*6S(i71G+&OYoRjy|R1K?{;YS6ZzIN6zVf^&9vS$5}MWww*+UacOVJkLJP0Q|Pb3yi2)xH8`g z7X8J?=H~o`h28mie{oTG!mK=W!026=%C$yDv`LKKtY0H<#mcxtNpch}vDFb#;tQ9b za8LVw+x4fy5TnyIY1`yOicpz3?lE6%__bVU)P}_yZ${=8Ozav7 z2YeON1DS@J7s;DRF|lT_yb!eh9=1o(I~}@2@D@UE6+?X4*Dv4AFT3IC6!B4!V9f44 zj9+q^>Xo)k-kIW>sVrqwmHmss%7M(mD6HBi@l3@RZw#9q|B zX;wfDvsR;E0R{H_{wDT6dKu?SIX>)4{J zNJorPY@=lp^Elg-31R2d_U^w)en8TBJw8Tzr8Z^;V^Zkn=+x2YX?@5VEZor`AvS4!n z%ZP`1h+?uaf|{2LklvDp|FVFB6PG61NnTY!(lG)6H>n+xcqnIt*(6vU>yAUZ6hjc5 z9LCOS5MC79mF&_mT*FTtlNd?LiqnxepowqC`i4&;pq!x*0Ct={iGTJi*ya!}G*v$n6~va3ezCQVnMg#>H)x4K z>KELF=UGoXmuvze%t4YT%TAM7K&>h3$tVm~otOw2Ok!%hnjt zj|JrTb{IFLZ+Q{P;k3A!)L}wQhcPe7rE3}+z9VDX4(&!Jqgqll$vYRVBEVG-+vpsO z5fD)&n6-H%ow(Eq@?lsDOJS*2m^qbHy{s9h6>#jf=dI)`+s#vX63W|gH#IC*S;22eyyHXt<`y~2g~rreO}K2l|zRPrRzSu_D@$2h6# zTE8AC5`$V3Rda{Y}?55rE^ z@pG(7Iz@YPm^!pX4Hw@PFc9Ks6$WV6hoNSQ)8%}Y&u25T$+!K4eDNylK{4;6ImdPe zD2wo+B8l`?#X$q5kuHV=Ye{X4QvcTEV(!plK1vj4G*N&hf!!moRziAe%ohwmf0R>u2QW(}Ja&uL72hDJ z1QQUtb--%}6HLlzHk9x=u(L$LXXzK2IfY!1B4uLaio^h5rYkr+khaE`AZ}?z-DqB zb!I%=Pa`$yMzY746eQyojCA@mI4tM*Cr*km06${Wx^%w@JTTVpN6^w$T!NdLOoT9$ zOW-n8x_9!Vj1!~>f`=gz$AY><>;T@R_O{$_WL!u#z2x68LCVpzxH$Dh=S=efm@z20 zr}0=w!eo60%g=8X_1s*!(walkXETjbfL$@0t7nzO%_l$`olkVL)d=$iKS|xV?APX6 zJkUP#Y1X+a)TyaSicVk~BKXogNYJCibVEN$I96@=$-m^FR> zTo{~M>dkoGOpoBr6i@6U*CZBk%KB%xv+xrh2c|*@s$kBCxQK|IteBiqBzG+oyZ&5A z05Pnb3dtoADeihT(iRawd66z|w6oLgRx12gFl;UU?Uus=XL|ms@blC!^9+dm3|U#1 z)ivR$30|dwHgVD*TIx6eHHz<++$)+k!{F%jG@RRV%cL8ayet-z~QE~NhA zrt5Ajdl?Bbkp?fhy~BQyT-3_MTV$d-lF>razq@hOYalrd_n}!Nn8VX#uwTv<0muR3 zoXOAHCno~Wx({|stJN~njxdOy<0TQCsM)A80`jGD38Q!4$pulZhE~B1UPa~JX491> z_)E$%1D>}MM?&Spx@04o%mkbuf~hcjjX~dmSK1jsWDxUsCr6f7Ip-Ftn1(@acBT+! zw?#n{J3CmQF|)WF>x2o;L7LU8328EU9nZ{_!QM@IpAzd5Ua{EsP0aeTv)hPt$pdB3 zHHbCHIK*6?iXen_N6I(o575Lk76T=1cqP9vS7j1nnVJH+$@<}Sfq=JPtYsx~+S5v< z;Fa9^HtM2~dR0O@XxKC=xQ!|fs%it#nbK$#(xXaf=>q0Je2B{>4l)BjNYvrT0U^10 zW+LTOuD>uBGpdok5>;vyw#!nU*SsLvlH85`#tN(;*n8 zpnbuB2vJeD(jhAXWn|Kk=Pv`-J>C+sW26Rp9gmWX*&w%$;pH6apaV3E_-RGNoaYuy z2y4`FaCwW|l}MEc8}Rfb=cCbYJT&wW!R`X4jv3~R{a{|k*YE;UR+|`gATjnaX&&d@ zst@ww6!Y)7F@x^BciuNsBN()~y!5k`c?al+i?Y5GDoqqaPw=HCThqAL8$IS@%0CAi zy#-g?prp86VM8-iuZj$GTp@DrRJeh7cPUR*+oKX_i!H`esAyQtT>m5*y!&2ueg_QK|*F41y)= zFqy=%r}q~uIERG3!ToV-(ir|`cmQ!Iy*WXT0* z^M0%?m(>|a1Z28fg4}i5K(-_;fqzMh3&CfuLs9~X@1S)*qT!L@QnsgAv>}(^lh97W zm0sCx2l+zI-vD3bu~(gxtXl6x!r=7rpUfOqmUgoEMKIoZGH7liyXl&l=_{gBR37eX z$S4wybkHN<_`amJiCJ~{4SAHSoqmYz#iVpRVnAMVfElp3r8Vl$jS?~=y?j0OQg0hd z>-p)?I{+SnZc}|W>P2UDnzskGao2-P33l?|+HxuZR*GOq`J4z`kIEZyyiqpq5B1M8 z=oJ00{ z1*{84CM}XzjcreKDo38A+nXl1AcPWPZQ+6g5SPATx-!-V(i264yP${?nU3Mt!Z%{K zD{KG;s*GJ@G951nCW0yC`sUyjqOEZOz2MOS>v=fj74*saC)E^m_M*on<&uof=`*E| z^Z3Nj%F?YUB4JV93tFN#lf~AaCMCd_Jd ziPft|=S#NIu`9$=n40+vdwXch7JsaR!j;qUbig}1$FwW!A_B^!p{IISU}C2?qyZ#t zL!jz4{CaUW1q(^l;oNtrDCSBFqkSwg~|Hr{{mUk{|jVljQOo~iLr7Hq+^#{0I3qelEv_(oSd0-KS>^z3~k5)f_i z>-_mMGA)rzRxgk9jgm<*Q(STQ-EeWg4wI-*3eTOxCr1xs=;8A`aUwvDA)zU_L@+|; z-^+}kz?E6SNAl{rQ00$Cm{u3^J09s0;fSrbgzC&84N#W3}bwn>%UE#_C7nYT>9 zW%V81&8+51d)-4_c#{!pXYNgcH>8%gi4_lwHFnWIPXMFWi7rPE?&Z8~H~0G1(D!ka zB+2@j7(PBKtIeO3=(9*O6FI@pGXQ4ecI7T6qvVu-a;I%GB!?Rt3oA?sJJLV9ltd%} zFR@LYmcf*G}?UTZx*)RHkipi*;Tn>k!#dDuxU|WYB2Q zYuLGsflgl;1cdO&7m*GkCp~bMA9&#Qrhi#h2Z85g`GK?{&QH%PWgCr&UVAlqt#8&j zU{wdqH=uybcm^q~2#fGZ+{qz`P58&aZtN%UTSqdKhMP!-dt#PqiilJtX|7Pomxypg zOp+Eq^ws`#1SIyn1}?uutD@YiVIN*j>>sK z#yqORbPnZn_$8)tm==^)96-<6*Dh*E{MA-w%bFl%biP@M%O(rZWnfP+%V7fYt+~OL zUiI-jEaGK$mlp26JGn}JTS_3S*OT>CoEGV%q7q1cVtKFc$?6s;-cwweMvgc}(!_}bBJehAc^Bfok_RT_`^CTD4Yy+|n zrv4v`*Gfb>%&fM_n3K*H(9$1ZN>H3ZN77?O^>t+;Y$cT$yswh{FK^J}byHI3K_}={ zn}OqrdTbrG>C@D)Jn_|t>H`wem14ulga&E{IK;=BRPoQvRsFeo-K|vaAR`nDL$-z8 zId9*c^V`eyd+UTHr-$=FVe*Uve6!+5vocP&6<9+RCi!&|{uJM_Bn47T@JY6YgLuc+ ztoC%mscp*G^*81;|4TS6Fz(KJw9 zN!A)5sFEwitn`SzCLtQO7hPvQk=);8-Ve!j!v&8^pjZZJ0TY-?&Nju6;~ZJDjrGf< zuG5(Ifk9t~9%frPjQPP7`N)2n2*1PndAwS!1F6-HzrAg7(P7=50V~X-AFr5*|K@O` zqro_FD$T19{Q<_QsWxQumE2h;~l znUi(k(jJklm}S{~i3JcZX4>T(03!5#&;e39xLZUYKtk{Z9YN3_&kV3!RL`FSp_10a zt&9*AfA%7inkC?Ew#Vut_Duvz(VSTEY1dB~pM z&aE&)+V@x&0HTMG8X;&2a7!ebSf(4?(kI>aeL3b^>~>w|;&2v7neb8Qw``bJY9znS zB`@&}$Xu!=P{k#srb|w|Gr+S;g#vv`n-nm>Npjf0cS~Rz zphISg=}3NI#&d~EIkiGT-r~k(RSQhw+l474EJPd|sK@JmKxEFqqV&+A0j4DC$YVpM ztBa}PU4a7%zQ;T!fm6krbB-8Ao&0lwo`6f=tA{OQe3L{>ZhA(Ogkq3lSoC~K`@kgW zfcePgejvr8F}_bf$*7X~>qsS8YY&YkDfUBr>K*VudQA^5p9;M08P7lEw!E3S>i z3w=MqO_wD8d6xdbB+Z?RPBFbTI_;xuEGoVK_Z1(I$tJTUL*B7P(W z@1!~C&dt}`v^{MU`i;zsxQG{vS4q|k$8VUcB7KdE7*{o}62R)z3~sYB@a5=1nd@-9$2OYyTD2O zG|_=|v`wZ{E#?9u*Ck>TEbPZ6xuq(Z7;d#8;($2YL=U`yGTOWvI*Pd*%$*Qd53@Ul zXzZd}XZCtSU{B-Dut}nLZ9Z384h6ZeuG&e z4Vzo7?E)+v+`I^Hfl6k7ITy}^IVL(sPP|raGFvv02QQ5U{X!5jV0T0>) z+Z||19F$Yd5}YeVvg{hd=cLy#YtC8l0q z$;guNBsBHkb>MUvp|Y3y8_u5`llKR#743U=vx~6cWg#ueq`fr7aVw5kUVKOE?jJ2I zBkRV=VY$d|#nUUV309N2VVQ|g+7)+Yyk0kP5b=$Rsym+XLWo0>Ny#`vuE<_$BXZr$ zQjnsGJMyqvJq~QJs&?IQn}NV}ree&JeQ?e@x4Jra*7qyLRTfH|ymIVWD7cv0XFi7p z=qAM^4oZ)k#j(A?IlLLUjrwel$U;uPELYDproSEg6!KYC4<@ca@*VSYIp1!3q)24;l^%0^=r*46QP`J#?z1b#KenHRLZI;xncF0GVy0TpB^DqE0dq( z8FuzI3xXOGk&0U@QL4V9E8;1uOm-|$a!@yMk)%G^uMmwBzBj*A`3B_cB9V|ao2P5D z*G=lWG%1ir3@yX|#O?s9)FEUO)?k4kFGx7zt)HUXMz6Ss8m+<>M@8DcJ z{myvB01S34V~`J7XBaFm4rqWsZYVT5rvK(TCaatWJ~W7oc=rHgCTp2&tY}aqCk*t8 z+^MlXl&@&I#=tC-Fqu~=d0NaM6$TzWh=q!;3T$Ow8%5T#^jT|w85VW2XIz}qvK}_# z%Vm`TSuvDsZV(%?ltVQ(B2`&dA|P5sBt-sY-A4U7mMtYSv{eOj;_`CmxpPdCflMV} z9Z$lT60jidN&rZ(zUN%$t+>rdF~auaI}U0x7LDmZrZa&a8NF>Y3AC-dlbFjMZChdt zcaWj7rY|``^qFZq{P{Fqhp4z|R`KQZi9JmwJWA|R+9oq?gHLbbel-J`wYN~J0Q@l5 zCGl4lgkmKqsVXTUB!jcQ3!>1YJmPri^W_ zO~$^vVm!7uZKqh{Nq&DyyrV}sxni_+Fx!S_@vGDeEufoD7Yp?#TI9*po3pN)GNq}Xj77E+?qF2g z=qSip8W0)iaysw#w_6Lai&(~3Bf=B6GsR09z`R^Hb8}5*jaMYwkOXky5GP?R>nvj` zn&9^vxbW$1Z26Qqbtc^e|4f#=O0w$UskSTz1~VY8krj3<+BM_*)aX#+8mUW(r@DoA zl6XoI?T{%L!ricdP3Bf2yZ9`@R zHv+v9q;b_R3^tLG5ob+t-IL@MoM#N*F26f6WFZfk^kgFnl7KC6&Kw{!k2GK-zG!ul zU*y})l<~;?u>$QdA%VreBfx@Nt779hU~`$(vgC-UbP z&Yq*GZ;xQ1n=GD9`kzc(#RCJk5IDL4Vs)Ks8!!g1CEI(!dLmJRDVDN)BC`p5^6Pg; z$O9%e+#zE?3Pgnk*D^YS{{ud*QaDK91pl@w=Y z+^n#Y>N~{yf@?}gw<4YA#IEDd7t(TNDT-x2aITVTmI|3WGgF+!mfb{eO0vEQr2Fzq z(EQrpfr(5G&02djFyn*ikwfIN@mdp^$tatdA*XRqilK<6nl7D$r;gy|;WV=TKC4y| zV|Avf;?Uxso1D+SEEg2<0a$){hVsvWU^a|RtFuk=BCv=hF~|r7a@~cyaYy{W7oO~O zUnbz4L&bY%mt60Z?=eLWkH;Td@B^uA1#dk}?e@^bcYzfy{fXtaJ~&yw&Npikh)!Iu zcQR>6vmV3YJ7#p{((-&+SC^_$nOUN>EX`fh9N_?Y^yP@v77k-4@_R9oa}Ls=%Jdsn zsVGlb6Iic>YYlJBYxGW6&sI-Y3rknepAT?bHO`)9wX*`zkYnRS)?{eWrjuFqX0Xt# zGFBM%*nuoKEOXdoWdJ@row%17bN&up-{R+Y!dx3ahLgDMVeWP0LRG}&U-mEUBDiuP z>(jK|gd{K?dCcCF5x->eXpPqsHx^L9$a!(*$n2Rx+jv7^%Och#YIHY`@X5Q~G!xx^ zv5}^0akm~MXF4nz5}XL$ke3ifzSRahj@-P!0`bYmKC-&tSfs7JPSmevh(CS za}IDs97F8Pu#$c|B-RIE?TML*9^FaxhB+|c03YNvkfk3;KbgGJUGm~d)SSQ<6jJZ- z03YNvIWTcla6tSewtvOv&;SeEF27eNvoj^w+Lr#M{Fr^z0V7xB3KoeGF2tX){Ep8% zMwj7`Hwi0*>O_-)J6~6=CZ#fYE9Fvh^p=H0vA9aiV3dWZ6S2_~J^2{MC$Pj=nw2%E za0cNTbDg_hd;@bY)S+iCuU5_FeB>svYj582_$e`#hEG^vMP{Id$A8yI*8G}!h%CBf z6lIsDzLd$Ju~^KEU~!???lSm{WAsgz08iapHCx+NSc60J5s}2}pe+`MmSIg~Ds8cV zVrBsDrEZJHxAI3~b4$cC?xN+_f-9_~j<1y&H1$Of&Z(syxk3Dv2g%*TtG?t;%P)PT zu6Q3O*k1Q9%4|mQgNYj!VG+pEBaAr`3xed77qVD6OKp(3K|@g%maWwu36&B-#su>C z=^xhKOA5M9Lm3N65S!!I@g|e=p@^IFQ*@(17#HUql3oex(}M8WVAvqwxs8#MJWICF7MO7kc** zl+k`s2N%8Q{^<6o!fFZf8)KqBUiv(QpbV+W^TdBKeIAk*K4u`k^m%LT$@7?qF@0cO zmnIKv9zD?d_1}n}MEvM>KFl9Ii(qO@9a>d*i>6ax$g)jlo2i?x&o+wuC`#0*<=wbi zuN9oAK?~E$ZM&NZVQ?06FOb?6KcS~raNQlDT#+#yf$a%vI)R{sw-y0;EUT&U4M-aq z;E%+TH3_D3(&V=V#I_fKgG+}MdJ$A8h(l)s#}Qb-z|5wZ7=Hq$7V!z3nTQzUUjX&C z?av7WxdZHJyt23eIHP4;BCBFF9@RGvz_S5H=Py zu`VoH5+BCFt}NAsTbSm!#XE`E@4k2)LCNTy04+gTgTw30*^|rYqv`XWVXoN?ey^He z3LUQWNRq&^*BQ23<3i7PSICvX$Y{&7U+g)Yul&Z6vr1^UA>mt5)flr{ls4;7))4~) zGNdGjUb8;0CDRpttNMa>kw#0Yy zW~LXRC7FH4SK_@w_L$t@o_u!QM_E_JD=@_k2c0BDN*D_GKuR|5Ez)3N0QrVba6*x% zGRc-lgm*H_j1ws;X|fW#cV+qGoNV&UNu&n22PrP_9s$WC69#5ip6ChIIEwQ{;ISf} zXo=dRI8l{=Z)}gEOShUt|3*g-ZzRd;Vt!#Ezxe7!)1uf;Vou)X1T&-xv8H^_mFYY`r5iJDh!FhL>sfNeHCT;F_ammWhXwJ{!;i?7cT-PRWUTUTR z;D*Ix`JR2s>X8%d@-$;_>dydk8w~*BHi`gS$e8-mqb5o~Q!oX5*tkEaMnR!nNP`vv z%3F@?=XvgK{x+jVe74F`5zK~3TLjupF&|Yvw>;c+tf7?#XX6IlVr8u8@tKKSdw^Kt zfs9Q%7$>Q`n51^z4~?+0%7lA?##%Bf@LL&M8u-*6zC?L+roptAezg^Y*mJB;&Dz%t z9=jGE!-H{^FX6{_oqV-XO_D17Eck#q*=8p{bHJMo`)TssEugRX3@QU zMv5Ph3@$^+<4)#|JChsO-DF0RxLCPs)9wy!N}D$?F**9j2BeOA{af;*I9t0WCQn#i za^msA;zQ!-q0MuE&J|Q)e~og=Pa-P0;k2(s;e%MMw@NfxPwv!eb?q%BEqQ|!&sGbY zhb;d#|76_RH|;j1CnDF@$8=_%F)g#WS0=M6k`$;q3&&|c&$$qCy=TUQT; zuWyGeg(jQJi~P*j`6X{{?ZI+WFKXFrmS$PoxmjLnuyXBG_Z+P)cDSmz(RRiw(9g^Y zdLjn09?R&-@_3leH=Jy5uj!M_U~RN#h_=^ErYC8BZLt)H2jF-T<#a5x_5hPQGk#M? zekz1FMHc;JTD)-@?UCO+^-9Niw^&WPQ`gW$E>VF%_^tk?MoIYJC*OU~L@wJ|NyAuwyC`K+Uy%vA1$=EP^(C!vzO z1yBagDnT10mXS&5ISWRMp^=yu#ZNIw5JA+UuK}0x+n68}v=%>Ih;KY_C7+6Q6XZZ` z(`zvZaZStoI+Iz!1bOZ#lZ{Ys%}_$P+{r1U+W4^{aj?s8_K{1DVdWjpS+PEeDIZL; z`%SD{#CQXey;wEE!}9qSLjgYxNt(O6@{1=#w0nS;JrmGwn=QT^;?>r66n>cA|0R3| z=EjyB3?Bo$z-PMcom*O3SYBRkFD|m=W7s8`DC}OTo+vkKwaQF&RTkK{dR6kpb0&*& z-sBebvvrwNj%H;MKyd*bcQu&Yj8zQI7Qf-L!^$G9t+Omyj+~ab1uK#hv|~Yke!GK- z);aH><(|BhFOi_kT6E`_uw>2%`PIlpIz@ix$;+n`UKM?6+s$Y_8T}IzU!)HtR=dcB zT^ZdpkKMIew~ju@F$O<%o6aSYd`$6ronTd{E6ifsg+tAJ527&4RsYyP!W9T4tUSE^u|X7K4)fZPs2Y z6m~ec%0bZJD#|HsageF%@|`aWO;2Tv%xjb^N17BbJDaQgP^Yvfd4rnpr32IrhotM^ z&?16O5+?$ZBx9IM20u7i8k%#xLX$C<7B!K#jCU-QfFFA%?=cU9LFqd)irSW^MH<)!@GTR^bObF-+1LsG^xF}>Nudo9hDQqP$bxJs-{R@y-(@Eg)(Wh471 z^{{KU3$mQ38WFU@cgG3GgH@OeTo&|bF=JAa+)~-e1Tzz#nDU-rNxSO3oe6)^+}kp8 zNA4~hWL%b$y=Ykp0P#}`n9q-JH+flClUa=PmiOT9?t|WeIexgI3deM**hlk3^VXT$ zCft6kHekU&CXt}gld6(oAgP|XL2S@GY`i#&fD%cC_pZD+KLAIX%_e}adCiJ#0H>Pd zfCHNxu(Q3L{ZX-hmQN$I>i?@sSN00-4gdj=1RF$_-P7(KKrp>Ky*s@-z4yfF&2f6~z4zXG{x`b^fD)tFEVplF zXLkB~Z{EE32E!RfmW5Q9*?;b@ZOi0=Q$0kz4{(L{?X`>^bFK8x^1bx${=l@xRGW@e zMMF|gAsB=#nsHS~(QWqJU#{Nieh}_jrNHMXmtoRQMGuH^15GKDatIY-&OcO>SM`TM z8f*qIPD0uX_x=p5`+VV$yg9U*`79^Lkq zYwkUDwH(XV*%%$YIV4CbjQ^7E{vFdCKH(sqni^!*zkIuhp z8Z&ovnWLV?L#->w3tHTDG|xp6T(eZh>k$`8lq-!Tw>HBi*5&yeujtv8mb*dAmAQcp zQ-Ogxi^J)Psx<4p?JVq5pcN{n8&`GCaZR2*?QEPb&AKgYbrBZ8M}W1;CEVDp+F2Ru3`)_uw3B9@k8YCvg|Y+`+qlV@?+Fd)KJU_|B;!n_T{( z<&1C=&!)xG3~XhNQ>EkXarfM*#bc+BgL8JYkJg^-Oh%B zE+WfHk)r^@=%11t^;PunF{#o8v2Fe z9#olS1GWFcyCBagR`26dWbQU+0vw_*z~OilB?eMQx6pH?!ciEsGHw^kYlX+1Iy&nm z%txVzh&r6ZVypK2`f--mf{>_rv{ISeo*wZEQeDW))hlc4gke7+-%MOf0GXa|~{!Ts%@NX$5DFKtr+`-b-_s0F2=jPpN;1HG1%2WX-RmACS z-LirlTsmD~PfA4(m37CdYOQEg=mKLa!pz&CdHAGO^*u{WG2GSr1uuec2BlV|39D5_ zt7vHvMwT8thmB#S$St?@T5Gv<^jdumB_yni9Q-IX9>Y&Mh69ksMsc=MyHr}_{wgwa zKIzCac++ihj;B5smzLD}qj1iKUQRP*?nvp_MegXt#2w6*?)F^rnpi^LJmdQmWcbWMx?fT?ci>Q*TTLb<}@g>|*KTHAepgA}~C9MENb)3Iq@ z@Sa(NrYG;SZ|uI$zptl#S0BvsYO3A$KKCfA7cXRf>}K3%;ndpt5_S`)WHKZ9UAv5< z7DjRH>ZPkHMqEPE&pHlL?Sw17l2C{9z*(*csUVEkZiS5M283;o{!#le_{|cYi&QJk zEkA9!B!q8l)DZjeUoRHd?THoaA>~H5N$(9uDCn~1f%QSIm*e(qk9jb=M=U2RD9h&b zgie47X0+BpKTJZVq~ch0HhmdGAiet4btOmH-zY#%%{OH}}}W z6j)-#qP1F8hc(D>bh`@6I^|y|`toWGRS>r@x2O~1IxJUHt)c6f zFx6%XE$-;h?Y~L@_U&!d)=~ZTDg9z^X?b4`MpQ+KImaj=cvWLkZbw_dWCUo*0zB`u zfP5wDka{0`37(V{P-1x1uD$U1QRfYXU2Di@MX!8qxGh}Ya`jTRGCWcSRy3?p)vjQ8fb@q58*nxMj|w#Eb?_ z+pLu39c{6?Sz34BzBUOyKXI4!_SJu8SzT)}2dBJ6c3;qv^;^XdsKkqHAxLYN>Dvch zP<Lu((!y>HdTK+(UP#u3#>>aOdLZ^t_aSyNPz9Ex|T2!L^x>6u3OS4x2uPr{b zxX$(P+yi_av6A-V%h=BD%?TfDl1WIk=M?uN8DZ4an(K1;eBs%`g?nFf8WS8m5!`r; z4=^D#f7b8M!e^GjYwJ4Rl*|@S&)n<^=NwMCXq{xa;*=FnUvA>Eo+~pe*qB!AqtM>h zz3%+tJw>ciiY>fVS%3rF^LxXySGMX$>n$GYmk(7)EB?!r4XKE`s+y>cet@9f6WPuoNw{>8t@8TPA3W+vy-CK8P?KwvmlJWD1c8-j5WyD9r;qDWXw=-Fd21 znRDl$JW_5@!H15hjyS27E|H9*ELZ*Fs-Pu5Ptwe1++)p)7n?^8=3_G5OWx%?Fu(3z z{ru=*OYz2|+$^-m+xhlH`U;#~??6hgawgbP|AQ#&;cbpnyNjxVd0D^bgJ1XA)CY_EP^A4b5!M0={yyX(2c4~tsJ)E;Z?=*2tw&u&7>MzycKx@9qz)0 zJql``eTIw{|EqKf-hu-sb}XbPUeF@uQ*-z0I8Z2D3QPts^t8e$oKcPg%+Gh#YFcGU ze1mFn`3CCUs%y<5)84$iX?@t7w7Y3eMQNYzV|D9_lH*m})8&3qR4`|k4BbZuK1use zlup#}&s;maJby+lvbfW5zFDa>m#uc_E8Lc*sLCwf6-x^n$jdN8S9PN~WR>s>{LAOS zv<|C`;{Q_zmC3wV+11t67cH2xjsu(%zBT7k#jc`^V|9StJU-KE%i@c~F6QxiWAL#Y zE)&0=!!`Qvd=9Ig&Fg6y-gt;<;-fi(9pKwJTmXJ!4y#t=n{v2>PTreySUu{#C5LMV zo<8ul9IhW&lM~&j=Xay<2{V`kUYv*W$I~WG{3u+xaQ=Zj<`EN`*h@`srL(h=Yz;1^ zX@8|3N28VNQJ9(_h*rjNbklUx^TRY9zhq;hpU0o?MkDkWBG#C1pCNWL2A*j`ySxMO z^WNQ=@@Z1WSlFnXAVgAhfj)ZRc{v^-O=#13tYSksD^Q6YeiOVb4v?Lt{OuF&^R4K> z2;N|S+kamA^R^BnWMD{>Vp@^_M#p!@O8)==S^ysNyMJS;Dhm&+^U zx;!H9F0YhFpoR)$$s74|zgfD^JQ(^0d5;z2ocUJ>?DZth|@Jx4e(MuXxgt zuJpu6Uj{N1UvA2#1TvCPA{onb5=$bfOk_*8<(BNoZFxU=fB69UK=~l~VEGXFQ28+V zaQO)NNckvvqkOb{jC`zooP4}|f_$QUl6LCSNXJAzvw9C0{LHBVQ|DCtok$Am1q8B;PFGBHt?CCf_dK zA>S!)k?)f4mhX}8mG6`9mmiQHlpm5GmLHL~%8$z1%z* z=<$s(57G;CN!MJSjFqo-2 zH3!egP6NiU<;*&B*v%hw7C5O}at=9%aqhV69CeO4$FcuA>8v>SIHx${I_;c6Dz)z1 z>)hv@bsf->9}Lsk3{FN~5VSSj9TA#(Zhs1!r@237GOlF7$++C1J|-@9zz)Su z6!iFsHY@rw^5V^EC-zOh>m>##@_In23$&e>xX_slDqSxyVb6;j072Ui!6SY+$U@O1 z9njK2Q}eeI6DB^rU`pMg>26l-Z{R2C4A52_g|;cX0c}QO`o=b)HVsY2qynp%b~lI; z)5-%Q@0LIEZ|{Zu(1x4&``9FD6q`oZkGp|s_x&I+y=ESwu7Mw#nLL;TUNW>PCw|JX zcyX^vCNFN&%yKvI$KxF$C|x5;7HaZ4>Dg8Ubk$52oQyS41Vi)jC zl9+=TtTV@p({?}hMy7E`+|o2eA2P7huO7;x$=2Ppx!M*3n7q3C;(Jk=S79$QuYxVK zHT{wqw>x}oe;6zh<;_V_-ZP2!zmBL=ZJIqAmn=wO$z~djcjqZ>-156rowmhZd&}>c zh&Jwvn}$YF#}7=c=lA<wjYAyN(KZOyXICmnDk7|+!}jfPuCt`s+fg! zQ8PO^O6U~;>#T4Pt{-~wPSwJ?2x}Ii9==zx;JDXsS|FKpsQn}`OIdJd1V-3oQO3-Q zDBhX7BU%I52X6c0qJ~QPla%>QOY+vYjU2G>PV+aN_2VT>!h9;ftR2x7JCDb*EKWR&+T!{IyT!gQ9STM|F*5OI|hq* z15+7$;E%2smKk3R;#EAe*o56zBaNUBcScJkardnDgal$djg zsn{t;Lrc$1s7yVkw{&7{GYIsL?dPPsBO`3 z)q+~}3>tbte=sgQP)WUHQ!!PQ{kwrj85^Km5Ck~gS~_G#!4|{`;AGTE6z^0r za565Xj73sPW94Nj7Y-7XqDl5b%4Ror-&sDpz8jp3-E`YeX=-@_LJxX86yxzwgjCyO z;3~`=OZOG9#JIXO%IRATC}*z~j3;487dTrZciW2 Date: Thu, 19 Dec 2019 09:37:36 -0800 Subject: [PATCH 168/241] Fix open path on git-pull-request icon (refs #87438) --- src/vs/base/parts/codicon/browser/codicon.css | 2 +- src/vs/base/parts/codicon/browser/codicon.ttf | Bin 55808 -> 55832 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/parts/codicon/browser/codicon.css b/src/vs/base/parts/codicon/browser/codicon.css index c166a90bfc2..3cb3a855ded 100644 --- a/src/vs/base/parts/codicon/browser/codicon.css +++ b/src/vs/base/parts/codicon/browser/codicon.css @@ -5,7 +5,7 @@ @font-face { font-family: "codicon"; - src: url("./codicon.ttf?a5c81ab5ee50c711f20256281c796746") format("truetype"); + src: url("./codicon.ttf?17db7f5e5f31fd546e62218bb0823c0c") format("truetype"); } .codicon[class*='codicon-'] { diff --git a/src/vs/base/parts/codicon/browser/codicon.ttf b/src/vs/base/parts/codicon/browser/codicon.ttf index 213b189cd914d26a61d666dd3c535e36d345bd34..8788b6e97dad7e2d77a283fe85c34bb359a46646 100644 GIT binary patch delta 796 zcmXwzT}V@L7{>qqGuzbJ+@?EcZfoCeXY-u1xt;AC=RBKR^V^-dEg6VkVG&-`m=Y0@ z5h)^Fq(O*CNOU16A}Y~ENJLl+FG6o*gj5%?x(F{ULQVSM@gbdo4K9mZcwKQPG%0Fcu3jgis4?bBTVvh4IrrzV(h zT+xBfoc9lJA|5_udX{uQxhKMcV4~dge@)o&F=_wyI zOs!JC9IPYbSajr^QfJCJS&Pcoi?@Ag*SF8MzwJOeCOY1BN;@s(yx>n-e_+)%Dfh3}dg~V3kd(xilOMbe9T*`G*-FMb$^%6I>ELZ{tPzwTJ5ZnL& z3PnPZObanofr=!MLO96C&L-y7BBDwG1x-c?f@u*i!$AS+c5%>RrJw{sA&P0C2t8xG zjF*womKunOtR4mvxm{@CUIhpLCC^~kuOdn+b#B?13Z9q826%Htm?Xm$X5Oi*CzY_Z zj8rKs>e3=1&eBqe;IUGpBC3#bDb3Mj8|zH>j+Pbj}!c;gO`_? zFp*q#78Dm(QzWustq`0$c_%cftPX>g@IbnNl|ZgE)SL3eAykq+z4Q17rjqMz3JoqU zbfpoAR87(aCocRyxmsdCeL4qkmWO*&Dwz=WSeY0xPK=X0Ou@HnBxnP=tYjiK`&L zxa7u}|CbpUPAN>>VIXtNHC!j2-{va=H}eYyAh@NNcos%)W?{T6QU6K7OyQm479}^O zKBYxUZy!^De^PN!S*CJF)kJlI>Jv31wKTPT>J94KG&D4(X?)YP(oE63r1?&Z zPivpnEo~-kEA3@E5jwkcKIqEmI_c)?YIX_e_dvmmp5<{IX6%=I-5;4?`*?t57=qhrP%e^owJv*ceAgv-{YX>kmYd8F~V_) zla5oK(*|c5=M3i;E<7%Ju6C|9t}|SZxV~_cbIWj-bN6uXa=+yM%R|p2KFni^XO!m* z&vRZ$eF7H+X$92;Z3_Ap>=ZmDcvtYf5V??`kSihILeGUMg%xZzkzQ@*x{-M`gFJ&a zgE>P4LmC4EBOAM*9HXchACtP09-|$jxu71C9izCJ922_;8%W5^+)P}Kk&lsCiH}iC zT+G~#QIAn|^4V~0Gf6&vb2ml_9cu*xKUHC8U0r8kRX+m-YaIzjlMEG8c5P)DDR~86 z6D1@zlfh)g2rVn7Dpuxy$9cjmAgUu`owyk7xSV1mOc|jDDjRxOv9ogVT03d~^D^|X SK_} From c7f9a2aff167c5865ffa3f029d3f6b4fa5c6694b Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 19 Dec 2019 18:45:03 +0100 Subject: [PATCH 169/241] Fix inline port forward in port view --- src/vs/workbench/contrib/remote/browser/tunnelView.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index c3274afa90d..f2006794e8f 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -425,12 +425,11 @@ export class TunnelPanel extends ViewPane { } protected renderBody(container: HTMLElement): void { - dom.addClass(container, '.tree-explorer-viewlet-tree-view'); - const treeContainer = document.createElement('div'); - dom.addClass(treeContainer, 'customview-tree'); + const panelContainer = dom.append(container, dom.$('.tree-explorer-viewlet-tree-view')); + const treeContainer = dom.append(panelContainer, dom.$('.customview-tree')); dom.addClass(treeContainer, 'file-icon-themable-tree'); dom.addClass(treeContainer, 'show-file-icons'); - container.appendChild(treeContainer); + const renderer = new TunnelTreeRenderer(TunnelPanel.ID, this.menuService, this.contextKeyService, this.instantiationService, this.contextViewService, this.themeService, this.remoteExplorerService); this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'RemoteTunnels', @@ -732,7 +731,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelTitle, ({ group: 'navigation', order: 0, command: { - id: ForwardPortAction.COMMANDPALETTE_ID, + id: ForwardPortAction.INLINE_ID, title: ForwardPortAction.LABEL, icon: { id: 'codicon/plus' } } From 5de1961c8e682f262f618059e00ab4b967a11c11 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 19 Dec 2019 19:44:42 +0100 Subject: [PATCH 170/241] enable small layout with height --- src/vs/workbench/contrib/markers/browser/markersView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 1b586f7cb21..20c6c153a44 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -171,7 +171,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { public layoutBody(height: number, width: number): void { const wasSmallLayout = this.isSmallLayout; - this.isSmallLayout = width < 600; + this.isSmallLayout = width < 600 && height > 100; if (this.isSmallLayout !== wasSmallLayout) { this.updateActions(); if (this.filterActionBar) { From 2775cbd3da4e6c7f99b4e7ea55c110fef3c63527 Mon Sep 17 00:00:00 2001 From: kieferrm Date: Thu, 19 Dec 2019 10:44:54 -0800 Subject: [PATCH 171/241] updated commands.yml --- .github/commands.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/commands.yml b/.github/commands.yml index ea0895b3dd2..24ac951d6f0 100644 --- a/.github/commands.yml +++ b/.github/commands.yml @@ -131,5 +131,11 @@ action: 'updateLabels', addLabel: 'a11ymas' }, + { + type: 'label', + name: '*off-topic', + action: 'close', + comment: "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" + } ] } From 2bcb83de0f131b4d63a9db8d8793d164dee823ff Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 Dec 2019 20:20:07 +0100 Subject: [PATCH 172/241] fix folding test failures --- src/vs/editor/contrib/folding/foldingModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index 399ecf46a0d..386c923f834 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -87,7 +87,7 @@ export class FoldingModel { let maxColumn = this._textModel.getLineMaxColumn(startLineNumber); let decorationRange = { startLineNumber: startLineNumber, - startColumn: 0, + startColumn: maxColumn, endLineNumber: startLineNumber, endColumn: maxColumn }; From 9e7d9d6a929a9ff2809baae8f0de90494bff98d7 Mon Sep 17 00:00:00 2001 From: kieferrm Date: Thu, 19 Dec 2019 12:15:43 -0800 Subject: [PATCH 173/241] update distro --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 04e101c7990..1121416ef0e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.42.0", - "distro": "f2bd019844350f43b12eb22c3e68e9879e36b067", + "distro": "3f06bd6997f121e232bdfdf978d808688483d2c5", "author": { "name": "Microsoft Corporation" }, @@ -166,4 +166,4 @@ "windows-mutex": "0.3.0", "windows-process-tree": "0.2.4" } -} \ No newline at end of file +} From d119e4341025f83232b4d06bfdeee1d42b1bf7e9 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 19 Dec 2019 12:45:47 -0800 Subject: [PATCH 174/241] Update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1121416ef0e..4598ce7a815 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.42.0", - "distro": "3f06bd6997f121e232bdfdf978d808688483d2c5", + "distro": "f524514a50170308b07bd781b316da450c917939", "author": { "name": "Microsoft Corporation" }, From 82ed3d45ee481b547812f02d766398a71f005a92 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 19 Dec 2019 13:44:49 -0800 Subject: [PATCH 175/241] Revert "Merge pull request #87320 from microsoft/misolori/codicon-consolidation" This reverts commit 6c9b23edc4329b3cc6faa6b69d219f3ee1f5a8fc, reversing changes made to 085abd5f1a2c30abb8a10bd00be9b72291ed478d. --- build/gulpfile.editor.js | 2 +- build/gulpfile.vscode.js | 2 +- src/vs/base/browser/markdownRenderer.ts | 2 +- .../codicon}/codicon-animations.css | 0 .../ui/codiconLabel/codicon}/codicon.css | 0 .../ui/codiconLabel/codicon}/codicon.ttf | Bin .../ui/codiconLabel}/codiconLabel.ts | 6 +- .../ui/highlightedlabel/highlightedLabel.ts | 2 +- .../{parts/codicon => }/common/codicon.ts | 0 .../{parts/codicon => }/common/codicons.ts | 0 src/vs/base/common/htmlContent.ts | 2 +- src/vs/base/test/common/codicon.test.ts | 2 +- .../issue/issueReporterMain.ts | 2 +- .../editor/contrib/codelens/codelensWidget.ts | 2 +- .../editor/contrib/suggest/suggestWidget.ts | 2 +- .../browser/quickOpen/quickOutline.ts | 2 +- src/vs/workbench/api/common/extHostTypes.ts | 2 +- .../parts/quickinput/quickInputList.ts | 2 +- .../browser/parts/statusbar/statusbarPart.ts | 2 +- .../runtimeExtensionsEditor.ts | 2 +- .../workbench/contrib/scm/browser/mainPane.ts | 2 +- tslint.json | 81 ++++++++++++------ 22 files changed, 71 insertions(+), 46 deletions(-) rename src/vs/base/{parts/codicon/browser => browser/ui/codiconLabel/codicon}/codicon-animations.css (100%) rename src/vs/base/{parts/codicon/browser => browser/ui/codiconLabel/codicon}/codicon.css (100%) rename src/vs/base/{parts/codicon/browser => browser/ui/codiconLabel/codicon}/codicon.ttf (100%) rename src/vs/base/{parts/codicon/browser => browser/ui/codiconLabel}/codiconLabel.ts (82%) rename src/vs/base/{parts/codicon => }/common/codicon.ts (100%) rename src/vs/base/{parts/codicon => }/common/codicons.ts (100%) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index f4c32ea6c7a..938c82f57be 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -41,7 +41,7 @@ var editorEntryPoints = [ ]; var editorResources = [ - 'out-editor-build/vs/base/parts/codicon/browser/codiconLabel/**/*.ttf' + 'out-editor-build/vs/base/browser/ui/codiconLabel/**/*.ttf' ]; var BUNDLED_FILE_HEADER = [ diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 5465dbe692d..bb504fc09e9 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -67,7 +67,7 @@ const vscodeResources = [ 'out-build/vs/base/common/performance.js', 'out-build/vs/base/node/languagePacks.js', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh,ps.sh}', - 'out-build/vs/base/parts/codicon/browser/codiconLabel/**', + 'out-build/vs/base/browser/ui/codiconLabel/codicon/**', '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/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 0f4cfe96ac5..2918133f5e5 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -15,7 +15,7 @@ import { cloneAndChange } from 'vs/base/common/objects'; import { escape } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { renderCodicons, markdownEscapeEscapedCodicons } from 'vs/base/parts/codicon/common/codicons'; +import { renderCodicons, markdownEscapeEscapedCodicons } from 'vs/base/common/codicons'; export interface MarkdownRenderOptions extends FormattedTextRenderOptions { codeBlockRenderer?: (modeId: string, value: string) => Promise; diff --git a/src/vs/base/parts/codicon/browser/codicon-animations.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css similarity index 100% rename from src/vs/base/parts/codicon/browser/codicon-animations.css rename to src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css diff --git a/src/vs/base/parts/codicon/browser/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css similarity index 100% rename from src/vs/base/parts/codicon/browser/codicon.css rename to src/vs/base/browser/ui/codiconLabel/codicon/codicon.css diff --git a/src/vs/base/parts/codicon/browser/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf similarity index 100% rename from src/vs/base/parts/codicon/browser/codicon.ttf rename to src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf diff --git a/src/vs/base/parts/codicon/browser/codiconLabel.ts b/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts similarity index 82% rename from src/vs/base/parts/codicon/browser/codiconLabel.ts rename to src/vs/base/browser/ui/codiconLabel/codiconLabel.ts index f3f39b79e34..ccec1f655bb 100644 --- a/src/vs/base/parts/codicon/browser/codiconLabel.ts +++ b/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./codicon'; -import 'vs/css!./codicon-animations'; +import 'vs/css!./codicon/codicon'; +import 'vs/css!./codicon/codicon-animations'; import { escape } from 'vs/base/common/strings'; -import { renderCodicons } from 'vs/base/parts/codicon/common/codicons'; +import { renderCodicons } from 'vs/base/common/codicons'; export class CodiconLabel { diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index bcbd085dc95..163dffb2d5a 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as objects from 'vs/base/common/objects'; -import { renderCodicons } from 'vs/base/parts/codicon/common/codicons'; +import { renderCodicons } from 'vs/base/common/codicons'; import { escape } from 'vs/base/common/strings'; export interface IHighlight { diff --git a/src/vs/base/parts/codicon/common/codicon.ts b/src/vs/base/common/codicon.ts similarity index 100% rename from src/vs/base/parts/codicon/common/codicon.ts rename to src/vs/base/common/codicon.ts diff --git a/src/vs/base/parts/codicon/common/codicons.ts b/src/vs/base/common/codicons.ts similarity index 100% rename from src/vs/base/parts/codicon/common/codicons.ts rename to src/vs/base/common/codicons.ts diff --git a/src/vs/base/common/htmlContent.ts b/src/vs/base/common/htmlContent.ts index 26c5e94dce3..be074865f2b 100644 --- a/src/vs/base/common/htmlContent.ts +++ b/src/vs/base/common/htmlContent.ts @@ -5,7 +5,7 @@ import { equals } from 'vs/base/common/arrays'; import { UriComponents } from 'vs/base/common/uri'; -import { escapeCodicons } from 'vs/base/parts/codicon/common/codicons'; +import { escapeCodicons } from 'vs/base/common/codicons'; export interface IMarkdownString { readonly value: string; diff --git a/src/vs/base/test/common/codicon.test.ts b/src/vs/base/test/common/codicon.test.ts index 5d6cc672e30..b3fdb5bde1b 100644 --- a/src/vs/base/test/common/codicon.test.ts +++ b/src/vs/base/test/common/codicon.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { IMatch } from 'vs/base/common/filters'; -import { matchesFuzzyCodiconAware, parseCodicons, IParsedCodicons } from 'vs/base/parts/codicon/common/codicon'; +import { matchesFuzzyCodiconAware, parseCodicons, IParsedCodicons } from 'vs/base/common/codicon'; export interface ICodiconFilter { // Returns null if word doesn't match. diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 4f3d5b9d306..6b1bab2128b 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -8,7 +8,7 @@ import * as os from 'os'; import * as browser from 'vs/base/browser/browser'; import { $ } from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; -import { CodiconLabel } from 'vs/base/parts/codicon/browser/codiconLabel'; +import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import * as collections from 'vs/base/common/collections'; import { debounce } from 'vs/base/common/decorators'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 7e5e92b82a5..a661af5def1 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -5,7 +5,7 @@ import 'vs/css!./codelensWidget'; import * as dom from 'vs/base/browser/dom'; -import { renderCodicons } from 'vs/base/parts/codicon/common/codicons'; +import { renderCodicons } from 'vs/base/common/codicons'; import { escape } from 'vs/base/common/strings'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 60b34bc7ade..b3cf4a333ac 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/suggest'; -import 'vs/base/parts/codicon/browser/codiconLabel'; // The codicon symbol styles are defined here and must be loaded +import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded import * as nls from 'vs/nls'; import { createMatches } from 'vs/base/common/filters'; diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts index 4fea5b682b2..772073939ef 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./quickOutline'; -import 'vs/base/parts/codicon/browser/codiconLabel'; // The codicon symbol styles are defined here and must be loaded +import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded import { CancellationToken } from 'vs/base/common/cancellation'; import { matchesFuzzy } from 'vs/base/common/filters'; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index dc98ab9ae38..19819497a1e 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -14,7 +14,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import * as vscode from 'vscode'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { escapeCodicons } from 'vs/base/parts/codicon/common/codicons'; +import { escapeCodicons } from 'vs/base/common/codicons'; function es5ClassCompat(target: Function): any { ///@ts-ignore diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index 9a5048360bf..1242effa0ff 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -11,7 +11,7 @@ import { WorkbenchList, IWorkbenchListOptions } from 'vs/platform/list/browser/l import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IMatch } from 'vs/base/common/filters'; -import { matchesFuzzyCodiconAware, parseCodicons } from 'vs/base/parts/codicon/common/codicon'; +import { matchesFuzzyCodiconAware, parseCodicons } from 'vs/base/common/codicon'; import { compareAnything } from 'vs/base/common/comparers'; import { Emitter, Event } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 6a2633dca00..425e82cf37b 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/statusbarpart'; import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { dispose, IDisposable, Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { CodiconLabel } from 'vs/base/parts/codicon/browser/codiconLabel'; +import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Part } from 'vs/workbench/browser/part'; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 0899641cc07..d1ad6303939 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -38,7 +38,7 @@ import { randomPort } from 'vs/base/node/ports'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ILabelService } from 'vs/platform/label/common/label'; -import { renderCodicons } from 'vs/base/parts/codicon/common/codicons'; +import { renderCodicons } from 'vs/base/common/codicons'; import { escape } from 'vs/base/common/strings'; import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index bdffb1a3c68..d5ec661f87b 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -25,7 +25,7 @@ import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionba import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { Command } from 'vs/editor/common/modes'; -import { renderCodicons } from 'vs/base/parts/codicon/common/codicons'; +import { renderCodicons } from 'vs/base/common/codicons'; import { escape } from 'vs/base/common/strings'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/tslint.json b/tslint.json index 33ff60030f4..06adddd9c6e 100644 --- a/tslint.json +++ b/tslint.json @@ -68,8 +68,7 @@ "target": "**/vs/base/common/**", "restrictions": [ "vs/nls", - "**/vs/base/common/**", - "**/vs/base/parts/*/common/**" + "**/vs/base/common/**" ] }, { @@ -79,8 +78,7 @@ "sinon", "vs/nls", "**/vs/base/common/**", - "**/vs/base/test/common/**", - "**/vs/base/parts/*/common/**" + "**/vs/base/test/common/**" ] }, { @@ -88,16 +86,14 @@ "restrictions": [ "vs/nls", "vs/css!./**/*", - "**/vs/base/{common,browser}/**", - "**/vs/base/parts/*/{common,browser}/**" + "**/vs/base/{common,browser}/**" ] }, { "target": "**/vs/base/node/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,node}/**", - "**/vs/base/parts/*/{common,node}/**", + "**/vs/base/{common,browser,node}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -109,7 +105,6 @@ "sinon", "vs/nls", "**/vs/base/{common,browser}/**", - "**/vs/base/parts/*/{common,browser}/**", "**/vs/base/test/{common,browser}/**" ] }, @@ -134,8 +129,8 @@ "target": "**/vs/base/parts/*/node/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,node}/**", - "**/vs/base/parts/*/{common,node}/**", + "**/vs/base/{common,browser,node}/**", + "**/vs/base/parts/*/{common,browser,node}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -153,8 +148,8 @@ "target": "**/vs/base/parts/*/electron-main/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,node,electron-main}/**", - "**/vs/base/parts/*/{common,node,electron-main}/**", + "**/vs/base/{common,browser,node,electron-main}/**", + "**/vs/base/parts/*/{common,browser,node,electron-main}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -192,9 +187,9 @@ "target": "**/vs/platform/*/node/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,node}/**", - "**/vs/base/parts/*/{common,node}/**", - "**/vs/platform/*/{common,node}/**", + "**/vs/base/{common,browser,node}/**", + "**/vs/base/parts/*/{common,browser,node}/**", + "**/vs/platform/*/{common,browser,node}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -213,9 +208,9 @@ "target": "**/vs/platform/*/electron-main/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,node,electron-main}/**", - "**/vs/base/parts/*/{common,node,electron-main}/**", - "**/vs/platform/*/{common,node,electron-main}/**", + "**/vs/base/{common,browser,node}/**", + "**/vs/base/parts/*/{common,browser,node,electron-browser}/**", + "**/vs/platform/*/{common,browser,node,electron-main}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -401,7 +396,6 @@ "**/vs/editor/common/**", "**/vs/editor/contrib/*/common/**", "**/vs/workbench/api/common/**", - "**/vs/base/parts/*/common/**", "**/vs/workbench/common/**", "**/vs/workbench/services/*/common/**", "**/vs/workbench/contrib/*/common/**" @@ -514,6 +508,37 @@ "!path" // node modules (except path where we have our own impl) ] }, + { + "target": "**/vs/workbench/contrib/files/common/**", + "restrictions": [ + "vs/nls", + "**/vs/base/common/**", + "**/vs/base/parts/*/common/**", + "**/vs/platform/*/common/**", + "**/vs/editor/common/**", + "**/vs/editor/contrib/*/common/**", + "**/vs/workbench/common/**", + "**/vs/workbench/services/*/common/**", + "**/vs/workbench/contrib/files/common/**", + "assert" + ] + }, + { + "target": "**/vs/workbench/contrib/files/browser/**", + "restrictions": [ + "vs/nls", + "vs/css!./**/*", + "**/vs/base/{common,browser}/**", + "**/vs/base/parts/*/{common,browser}/**", + "**/vs/platform/*/{common,browser}/**", + "**/vs/editor/{common,browser}/**", + "**/vs/editor/contrib/**", // editor/contrib is equivalent to /browser/ by convention + "**/vs/workbench/{common,browser}/**", + "**/vs/workbench/services/*/{common,browser}/**", + "**/vs/workbench/contrib/files/{common,browser}/**", + "assert" + ] + }, { "target": "**/vs/workbench/contrib/terminal/browser/**", "restrictions": [ @@ -541,10 +566,10 @@ "target": "**/vs/code/node/**", "restrictions": [ "vs/nls", - "**/vs/base/**/{common,node}/**", - "**/vs/base/parts/**/{common,node}/**", - "**/vs/platform/**/{common,node}/**", - "**/vs/code/**/{common,node}/**", + "**/vs/base/**/{common,browser,node}/**", + "**/vs/base/parts/**/{common,browser,node}/**", + "**/vs/platform/**/{common,browser,node}/**", + "**/vs/code/**/{common,browser,node}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -564,10 +589,10 @@ "target": "**/vs/code/electron-main/**", "restrictions": [ "vs/nls", - "**/vs/base/**/{common,node,electron-main}/**", - "**/vs/base/parts/**/{common,node,electron-main}/**", - "**/vs/platform/**/{common,node,electron-main}/**", - "**/vs/code/**/{common,node,electron-main}/**", + "**/vs/base/**/{common,browser,node}/**", + "**/vs/base/parts/**/{common,browser,node,electron-main}/**", + "**/vs/platform/**/{common,browser,node,electron-main}/**", + "**/vs/code/**/{common,browser,node,electron-main}/**", "!path" // node modules (except path where we have our own impl) ] }, From 6e6fce1e6530a7c4faac1b322a2c052bc9849822 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 19 Dec 2019 13:55:04 -0800 Subject: [PATCH 176/241] Update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4598ce7a815..3f5a85d028e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.42.0", - "distro": "f524514a50170308b07bd781b316da450c917939", + "distro": "b4449ca84a214ebce11d36505963a1af05ba791e", "author": { "name": "Microsoft Corporation" }, From 1fc02159b78928b312a85a27512802f705bd73bc Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 19 Dec 2019 14:26:11 -0800 Subject: [PATCH 177/241] do not show title : title when merging --- src/vs/workbench/browser/parts/views/viewPaneContainer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 8685606b5e7..083c0dfe25c 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -348,7 +348,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { getTitle(): string { if (this.isViewMergedWithContainer()) { const paneItemTitle = this.paneItems[0].pane.title; - if (this.options.donotShowContainerTitleWhenMergedWithContainer) { + if (this.options.donotShowContainerTitleWhenMergedWithContainer || this.viewContainer.name === paneItemTitle) { return this.paneItems[0].pane.title; } return paneItemTitle ? `${this.viewContainer.name}: ${paneItemTitle}` : this.viewContainer.name; From 49e2ea53c916b46036130365722d79ec0f8807f1 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 19 Dec 2019 16:19:10 -0800 Subject: [PATCH 178/241] Update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f5a85d028e..05744417fcf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.42.0", - "distro": "b4449ca84a214ebce11d36505963a1af05ba791e", + "distro": "bffc885aaa3dc1b9059ee6834d15413b98b51fe3", "author": { "name": "Microsoft Corporation" }, From 3ce39fd2b6d4f859e2d6d5232c2064380db88d00 Mon Sep 17 00:00:00 2001 From: Miguel Solorio Date: Thu, 19 Dec 2019 16:57:17 -0800 Subject: [PATCH 179/241] Update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 05744417fcf..c637e060e38 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.42.0", - "distro": "bffc885aaa3dc1b9059ee6834d15413b98b51fe3", + "distro": "1ebc9f477d7c8a92aa8d6841297335f1b49d9e0d", "author": { "name": "Microsoft Corporation" }, From 4f85c3c94c15457e1d4c9e67da6800630394ea54 Mon Sep 17 00:00:00 2001 From: kieferrm Date: Thu, 19 Dec 2019 17:45:59 -0800 Subject: [PATCH 180/241] update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c637e060e38..62e04eb8235 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.42.0", - "distro": "1ebc9f477d7c8a92aa8d6841297335f1b49d9e0d", + "distro": "19a1535bbb8badb079ca8f299282d89544c6d996", "author": { "name": "Microsoft Corporation" }, From c92aaf501ce70493caf482c64f78e750627d5525 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 20 Dec 2019 07:36:59 +0100 Subject: [PATCH 181/241] fix tslint import rules --- tslint.json | 69 +++++++++++++++-------------------------------------- 1 file changed, 19 insertions(+), 50 deletions(-) diff --git a/tslint.json b/tslint.json index 06adddd9c6e..820aa3103db 100644 --- a/tslint.json +++ b/tslint.json @@ -93,7 +93,7 @@ "target": "**/vs/base/node/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,browser,node}/**", + "**/vs/base/{common,node}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -129,8 +129,8 @@ "target": "**/vs/base/parts/*/node/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,browser,node}/**", - "**/vs/base/parts/*/{common,browser,node}/**", + "**/vs/base/{common,node}/**", + "**/vs/base/parts/*/{common,node}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -148,8 +148,8 @@ "target": "**/vs/base/parts/*/electron-main/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,browser,node,electron-main}/**", - "**/vs/base/parts/*/{common,browser,node,electron-main}/**", + "**/vs/base/{common,node,electron-main}/**", + "**/vs/base/parts/*/{common,node,electron-main}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -187,9 +187,9 @@ "target": "**/vs/platform/*/node/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,browser,node}/**", - "**/vs/base/parts/*/{common,browser,node}/**", - "**/vs/platform/*/{common,browser,node}/**", + "**/vs/base/{common,node}/**", + "**/vs/base/parts/*/{common,node}/**", + "**/vs/platform/*/{common,node}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -208,9 +208,9 @@ "target": "**/vs/platform/*/electron-main/**", "restrictions": [ "vs/nls", - "**/vs/base/{common,browser,node}/**", - "**/vs/base/parts/*/{common,browser,node,electron-browser}/**", - "**/vs/platform/*/{common,browser,node,electron-main}/**", + "**/vs/base/{common,node,electron-main}/**", + "**/vs/base/parts/*/{common,node,electron-main}/**", + "**/vs/platform/*/{common,node,electron-main}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -508,37 +508,6 @@ "!path" // node modules (except path where we have our own impl) ] }, - { - "target": "**/vs/workbench/contrib/files/common/**", - "restrictions": [ - "vs/nls", - "**/vs/base/common/**", - "**/vs/base/parts/*/common/**", - "**/vs/platform/*/common/**", - "**/vs/editor/common/**", - "**/vs/editor/contrib/*/common/**", - "**/vs/workbench/common/**", - "**/vs/workbench/services/*/common/**", - "**/vs/workbench/contrib/files/common/**", - "assert" - ] - }, - { - "target": "**/vs/workbench/contrib/files/browser/**", - "restrictions": [ - "vs/nls", - "vs/css!./**/*", - "**/vs/base/{common,browser}/**", - "**/vs/base/parts/*/{common,browser}/**", - "**/vs/platform/*/{common,browser}/**", - "**/vs/editor/{common,browser}/**", - "**/vs/editor/contrib/**", // editor/contrib is equivalent to /browser/ by convention - "**/vs/workbench/{common,browser}/**", - "**/vs/workbench/services/*/{common,browser}/**", - "**/vs/workbench/contrib/files/{common,browser}/**", - "assert" - ] - }, { "target": "**/vs/workbench/contrib/terminal/browser/**", "restrictions": [ @@ -566,10 +535,10 @@ "target": "**/vs/code/node/**", "restrictions": [ "vs/nls", - "**/vs/base/**/{common,browser,node}/**", - "**/vs/base/parts/**/{common,browser,node}/**", - "**/vs/platform/**/{common,browser,node}/**", - "**/vs/code/**/{common,browser,node}/**", + "**/vs/base/**/{common,node}/**", + "**/vs/base/parts/**/{common,node}/**", + "**/vs/platform/**/{common,node}/**", + "**/vs/code/**/{common,node}/**", "!path" // node modules (except path where we have our own impl) ] }, @@ -589,10 +558,10 @@ "target": "**/vs/code/electron-main/**", "restrictions": [ "vs/nls", - "**/vs/base/**/{common,browser,node}/**", - "**/vs/base/parts/**/{common,browser,node,electron-main}/**", - "**/vs/platform/**/{common,browser,node,electron-main}/**", - "**/vs/code/**/{common,browser,node,electron-main}/**", + "**/vs/base/**/{common,node,electron-main}/**", + "**/vs/base/parts/**/{common,node,electron-main}/**", + "**/vs/platform/**/{common,node,electron-main}/**", + "**/vs/code/**/{common,node,electron-main}/**", "!path" // node modules (except path where we have our own impl) ] }, From 7c0095ee2d064033fc13184127a9adc603454729 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 20 Dec 2019 08:28:45 +0100 Subject: [PATCH 182/241] update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 62e04eb8235..fa917351db5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.42.0", - "distro": "19a1535bbb8badb079ca8f299282d89544c6d996", + "distro": "ec0412876eac04d73be9bcc6919aecfaffe2ab64", "author": { "name": "Microsoft Corporation" }, From 1b8da0d77958db5e665c9778fa9e59801ecb992e Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Fri, 20 Dec 2019 08:35:53 +0100 Subject: [PATCH 183/241] Update copycat.yml --- .github/copycat.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/copycat.yml b/.github/copycat.yml index eccccc16b00..192f225684f 100644 --- a/.github/copycat.yml +++ b/.github/copycat.yml @@ -1,5 +1,5 @@ { - perform: true, + perform: false, target_owner: 'chrmarti', target_repo: 'testissues' -} \ No newline at end of file +} From 714ba18975ceeb2fc0ebb7bbd3964700f1f0c111 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 20 Dec 2019 08:43:02 +0100 Subject: [PATCH 184/241] editors - lift getEditors(order) --- .../workbench/browser/parts/editor/editor.ts | 5 - .../browser/parts/editor/editorActions.ts | 4 +- .../browser/parts/editor/editorGroupView.ts | 18 +- .../browser/parts/editor/editorPicker.ts | 12 +- .../browser/parts/editor/editorsObserver.ts | 8 +- src/vs/workbench/common/editor.ts | 13 ++ src/vs/workbench/common/editor/editorGroup.ts | 6 +- .../electron-browser/backupRestorer.test.ts | 2 +- .../contrib/files/browser/fileCommands.ts | 4 +- .../services/editor/browser/editorService.ts | 30 ++- .../editor/common/editorGroupsService.ts | 33 +--- .../services/editor/common/editorService.ts | 21 +- .../test/browser/editorGroupsService.test.ts | 6 +- .../editor/test/browser/editorService.test.ts | 13 +- .../services/history/browser/history.ts | 10 +- .../services/history/common/history.ts | 7 +- .../test/common/editor/editorGroups.test.ts | 186 +++++++++--------- .../workbench/test/workbenchTestServices.ts | 8 +- 18 files changed, 205 insertions(+), 181 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 7434fe1b1cf..edeb87233fa 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -156,9 +156,4 @@ export interface EditorServiceImpl extends IEditorService { * Emitted when the list of most recently active editors change. */ readonly onDidMostRecentlyActiveEditorsChange: Event; - - /** - * Access to the list of most recently active editors. - */ - readonly mostRecentlyActiveEditors: ReadonlyArray; } diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index f0123947c0b..28cfc1b0ace 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { mixin } from 'vs/base/common/objects'; -import { IEditorInput, EditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection, SaveReason } from 'vs/workbench/common/editor'; +import { IEditorInput, EditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection, SaveReason, EditorsOrder } from 'vs/workbench/common/editor'; import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; @@ -16,7 +16,7 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, MOVE_ACTIVE_EDITOR_COMMAND_ID, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX } from 'vs/workbench/browser/parts/editor/editorCommands'; -import { IEditorGroupsService, IEditorGroup, GroupsArrangement, EditorsOrder, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DisposableStore } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index df3eb524e89..edea4e3744d 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, toResource, SideBySideEditor, SaveReason, SaveContext, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, toResource, SideBySideEditor, SaveReason, SaveContext, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom'; @@ -17,7 +17,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { Themable, EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER } from 'vs/workbench/common/theme'; -import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; @@ -723,7 +723,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } get editors(): EditorInput[] { - return this._group.getEditors(); + return this._group.getEditors(EditorsOrder.SEQUENTIAL); } get count(): number { @@ -750,12 +750,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._group.isActive(editor); } - getEditors(order?: EditorsOrder): EditorInput[] { - if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) { - return this._group.getEditors(true); - } - - return this.editors; + getEditors(order: EditorsOrder): EditorInput[] { + return this._group.getEditors(order); } getEditorByIndex(index: number): EditorInput | undefined { @@ -1371,7 +1367,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const filter = editors; const hasDirection = typeof filter.direction === 'number'; - let editorsToClose = this._group.getEditors(!hasDirection /* in MRU order only if direction is not specified */); + let editorsToClose = this._group.getEditors(hasDirection ? EditorsOrder.SEQUENTIAL : EditorsOrder.MOST_RECENTLY_ACTIVE); // in MRU order only if direction is not specified // Filter: saved only if (filter.savedOnly) { @@ -1432,7 +1428,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Check for dirty and veto - const editors = this._group.getEditors(true); + const editors = this._group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); const veto = await this.handleDirtyClosing(editors.slice(0)); if (veto) { return; diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index 022e5258ed0..65fa73bcb34 100644 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ b/src/vs/workbench/browser/parts/editor/editorPicker.ts @@ -13,12 +13,11 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { toResource, SideBySideEditor, IEditorInput } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditor, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor'; import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; export class EditorPickerEntry extends QuickOpenEntryGroup { @@ -209,14 +208,13 @@ export abstract class BaseAllEditorsPicker extends BaseEditorPicker { constructor( @IInstantiationService instantiationService: IInstantiationService, @IEditorService editorService: IEditorService, - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IHistoryService protected historyService: IHistoryService + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { super(instantiationService, editorService, editorGroupService); } protected count(): number { - return this.historyService.getMostRecentlyUsedOpenEditors().length; + return this.editorService.count; } getEmptyLabel(searchString: string): string { @@ -262,7 +260,7 @@ export class AllEditorsByMostRecentlyUsedPicker extends BaseAllEditorsPicker { protected getEditorEntries(): EditorPickerEntry[] { const entries: EditorPickerEntry[] = []; - for (const { editor, groupId } of this.historyService.getMostRecentlyUsedOpenEditors()) { + for (const { editor, groupId } of this.editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) { entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, this.editorGroupService.getGroup(groupId)!)); } diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index c15fa2c10bb..e888b274b2f 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditorInputFactoryRegistry, IEditorIdentifier, GroupIdentifier, Extensions, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { Event, Emitter } from 'vs/base/common/event'; -import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { coalesce } from 'vs/base/common/arrays'; import { LinkedMap, Touch } from 'vs/base/common/map'; import { equals } from 'vs/base/common/objects'; @@ -41,6 +41,10 @@ export class EditorsObserver extends Disposable { private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange = this._onDidChange.event; + get count(): number { + return this.mostRecentEditorsMap.size; + } + get editors(): IEditorIdentifier[] { return this.mostRecentEditorsMap.values(); } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index d1b3ef8d803..7f8dc048690 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1327,3 +1327,16 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService return coalesce(editors); } + +export const enum EditorsOrder { + + /** + * Editors sorted by most recent activity (most recent active first) + */ + MOST_RECENTLY_ACTIVE, + + /** + * Editors sorted by sequential order + */ + SEQUENTIAL +} diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index 6822bd68eda..0473aee2cbc 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, CloseDirection, SideBySideEditorInput, IEditorInput } from 'vs/workbench/common/editor'; +import { Extensions, IEditorInputFactoryRegistry, EditorInput, IEditorIdentifier, IEditorCloseEvent, GroupIdentifier, CloseDirection, SideBySideEditorInput, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -130,8 +130,8 @@ export class EditorGroup extends Disposable { return this.editors.length; } - getEditors(mru?: boolean): EditorInput[] { - return mru ? this.mru.slice(0) : this.editors.slice(0); + getEditors(order: EditorsOrder): EditorInput[] { + return order === EditorsOrder.MOST_RECENTLY_ACTIVE ? this.mru.slice(0) : this.editors.slice(0); } getEditorByIndex(index: number): EditorInput | undefined { diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts index 19054f498f3..cc5d10a70a4 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts @@ -122,7 +122,7 @@ suite('BackupModelRestorer', () => { // Verify backups restored and opened as dirty await restorer.doRestoreBackups(); - assert.equal(editorService.editors.length, 4); + assert.equal(editorService.count, 4); assert.ok(editorService.editors.every(editor => editor.isDirty())); let counter = 0; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index b837f81fa5d..bb781656124 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { toResource, IEditorCommandsContext, SideBySideEditor, IEditorIdentifier, SaveReason, SideBySideEditorInput } from 'vs/workbench/common/editor'; +import { toResource, IEditorCommandsContext, SideBySideEditor, IEditorIdentifier, SaveReason, SideBySideEditorInput, EditorsOrder } from 'vs/workbench/common/editor'; import { IWindowOpenable, IOpenWindowOptions, isWorkspaceToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -30,7 +30,7 @@ import { Schemas } from 'vs/base/common/network'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IEditorService, SIDE_GROUP, ISaveEditorsOptions } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService, GroupsOrder, EditorsOrder, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, GroupsOrder, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ILabelService } from 'vs/platform/label/common/label'; import { basename, joinPath, isEqual } from 'vs/base/common/resources'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index a50defb71e3..90dbcef6583 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -5,7 +5,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IResourceInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, toResource, SideBySideEditor, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, toResource, SideBySideEditor, IRevertOptions, SaveReason, EditorsOrder } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { ResourceMap } from 'vs/base/common/map'; @@ -17,7 +17,7 @@ import { URI } from 'vs/base/common/uri'; import { basename, isEqual } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { localize } from 'vs/nls'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection, EditorsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IResourceEditor, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -186,19 +186,28 @@ export class EditorService extends Disposable implements EditorServiceImpl { return undefined; } + get count(): number { + return this.editorsObserver.count; + } + get editors(): IEditorInput[] { - const editors: IEditorInput[] = []; - this.editorGroupService.groups.forEach(group => { - editors.push(...group.editors); + return this.getEditors(EditorsOrder.SEQUENTIAL).map(({ editor }) => editor); + } + + getEditors(order: EditorsOrder): ReadonlyArray { + if (order === EditorsOrder.MOST_RECENTLY_ACTIVE) { + return this.editorsObserver.editors; + } + + const editors: IEditorIdentifier[] = []; + + this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(group => { + editors.push(...group.getEditors(EditorsOrder.SEQUENTIAL).map(editor => ({ editor, groupId: group.id }))); }); return editors; } - get mostRecentlyActiveEditors(): IEditorIdentifier[] { - return this.editorsObserver.editors; - } - get activeEditor(): IEditorInput | undefined { const activeGroup = this.editorGroupService.activeGroup; @@ -834,6 +843,9 @@ export class DelegatingEditorService implements IEditorService { get visibleControls(): ReadonlyArray { return this.editorService.visibleControls; } get visibleTextEditorWidgets(): ReadonlyArray { return this.editorService.visibleTextEditorWidgets; } get editors(): ReadonlyArray { return this.editorService.editors; } + get count(): number { return this.editorService.count; } + + getEditors(order: EditorsOrder): ReadonlyArray { return this.editorService.getEditors(order); } openEditors(editors: IEditorInputWithOptions[], group?: OpenInEditorGroup): Promise; openEditors(editors: IResourceEditor[], group?: OpenInEditorGroup): Promise; diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index a86ff3eca5a..ab64d9085c7 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; import { IEditorOptions, ITextEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; @@ -134,19 +134,6 @@ export const enum GroupsOrder { GRID_APPEARANCE } -export const enum EditorsOrder { - - /** - * Editors sorted by most recent activity (most recent active first) - */ - MOST_RECENTLY_ACTIVE, - - /** - * Editors sorted by sequential order - */ - SEQUENTIAL -} - export interface IEditorGroupsService { _serviceBrand: undefined; @@ -224,11 +211,11 @@ export interface IEditorGroupsService { readonly willRestoreEditors: boolean; /** - * Get all groups that are currently visible in the editor area optionally - * sorted by being most recent active or grid order. Will sort by creation - * time by default (oldest group first). + * Get all groups that are currently visible in the editor area. + * + * @param order the order of the editors to use */ - getGroups(order?: GroupsOrder): ReadonlyArray; + getGroups(order: GroupsOrder): ReadonlyArray; /** * Allows to convert a group identifier to a group. @@ -433,16 +420,16 @@ export interface IEditorGroup { readonly count: number; /** - * All opened editors in the group. There can only be one editor active. + * All opened editors in the group in sequential order of their appearance. */ readonly editors: ReadonlyArray; /** - * Get all editors that are currently opened in the group optionally - * sorted by being most recent active. Will sort by sequential appearance - * by default (from left to right). + * Get all editors that are currently opened in the group. + * + * @param order the order of the editors to use */ - getEditors(order?: EditorsOrder): ReadonlyArray; + getEditors(order: EditorsOrder): ReadonlyArray; /** * Returns the editor at a specific index of the group. diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 6eb6cd569cc..ecc324078b4 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -5,7 +5,7 @@ import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IResourceInput, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IEditorIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IEditorIdentifier, ISaveOptions, IRevertOptions, EditorsOrder } from 'vs/workbench/common/editor'; import { Event } from 'vs/base/common/event'; import { IEditor as ICodeEditor, IDiffEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -123,11 +123,26 @@ export interface IEditorService { readonly visibleTextEditorWidgets: ReadonlyArray; /** - * All editors that are opened across all editor groups. This includes active as well as inactive - * editors in each editor group. + * All editors that are opened across all editor groups in sequential order + * of appearance. + * + * This includes active as well as inactive editors in each editor group. */ readonly editors: ReadonlyArray; + /** + * The total number of editors that are opened either inactive or active. + */ + readonly count: number; + + /** + * All editors that are opened across all editor groups with their group + * identifier. + * + * @param order the order of the editors to use + */ + getEditors(order: EditorsOrder): ReadonlyArray; + /** * Open an editor in an editor group. * diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index abf37eb4fd6..1d5cb41cc69 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -6,9 +6,9 @@ import * as assert from 'assert'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; -import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupChangeKind, EditorsOrder, GroupLocation } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupChangeKind, GroupLocation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EditorInput, IFileEditorInput, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorOptions, CloseDirection, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { EditorInput, IFileEditorInput, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorOptions, CloseDirection, IEditorPartOptions, EditorsOrder } from 'vs/workbench/common/editor'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -468,7 +468,7 @@ suite('EditorGroupsService', () => { assert.equal(group.activeEditor, input); assert.ok(group.activeControl instanceof TestEditorControl); - assert.equal(group.editors.length, 2); + assert.equal(group.count, 2); const mru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); assert.equal(mru[0], input); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 11aab22bc60..dccd154b670 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorInput, EditorOptions, IFileEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IFileEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions, EditorsOrder } from 'vs/workbench/common/editor'; import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; @@ -156,6 +156,9 @@ suite('EditorService', () => { assert.ok(editor instanceof TestEditorControl); assert.equal(editor, service.activeControl); + assert.equal(1, service.count); + assert.equal(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor); + assert.equal(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor); assert.equal(input, service.activeEditor); assert.equal(service.visibleControls.length, 1); assert.equal(service.visibleControls[0], editor); @@ -169,6 +172,9 @@ suite('EditorService', () => { // Close input await editor!.group!.closeEditor(input); + assert.equal(0, service.count); + assert.equal(0, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length); + assert.equal(0, service.getEditors(EditorsOrder.SEQUENTIAL).length); assert.equal(didCloseEditorListenerCounter, 1); assert.equal(activeEditorChangeEventCounter, 2); assert.equal(visibleEditorChangeEventCounter, 2); @@ -178,6 +184,11 @@ suite('EditorService', () => { await service.openEditor(input, { pinned: true }); editor = await service.openEditor(otherInput, { pinned: true }); + assert.equal(2, service.count); + assert.equal(otherInput, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor); + assert.equal(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].editor); + assert.equal(input, service.getEditors(EditorsOrder.SEQUENTIAL)[0].editor); + assert.equal(otherInput, service.getEditors(EditorsOrder.SEQUENTIAL)[1].editor); assert.equal(service.visibleControls.length, 1); assert.equal(service.isOpen(input), true); assert.equal(service.isOpen(otherInput), true); diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 9d38b5e6747..02ca4db033b 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -7,7 +7,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IEditor } from 'vs/editor/common/editorCommon'; import { ITextEditorOptions, IResourceInput, ITextEditorSelection } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor as IBaseEditor, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, toResource, IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor as IBaseEditor, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, toResource, IEditorIdentifier, GroupIdentifier, EditorsOrder } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; @@ -18,7 +18,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { Registry } from 'vs/platform/registry/common/platform'; import { Event } from 'vs/base/common/event'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { IEditorGroupsService, EditorsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { getExcludes, ISearchConfiguration } from 'vs/workbench/services/search/common/search'; import { IExpression } from 'vs/base/common/glob'; @@ -1023,7 +1023,7 @@ export class HistoryService extends Disposable implements IHistoryService { // Across groups if (!group) { - editors = this.recentlyUsedEditorsStack || this.editorService.mostRecentlyActiveEditors; + editors = this.recentlyUsedEditorsStack || this.editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); index = this.recentlyUsedEditorsStackIndex; } @@ -1068,10 +1068,6 @@ export class HistoryService extends Disposable implements IHistoryService { } } - getMostRecentlyUsedOpenEditors(): ReadonlyArray { - return this.editorService.mostRecentlyActiveEditors; - } - //#endregion } diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index efb224beb2f..598a446f9df 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -5,7 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorInput, GroupIdentifier } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; export const IHistoryService = createDecorator('historyService'); @@ -87,9 +87,4 @@ export interface IHistoryService { * @param group optional indicator to scope to a specific group. */ openPreviouslyUsedEditor(group?: GroupIdentifier): void; - - /** - * Get a list of most recently used editors that are open. - */ - getMostRecentlyUsedOpenEditors(): ReadonlyArray; } diff --git a/src/vs/workbench/test/common/editor/editorGroups.test.ts b/src/vs/workbench/test/common/editor/editorGroups.test.ts index 990b1fadbe3..ae3dad36505 100644 --- a/src/vs/workbench/test/common/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/common/editor/editorGroups.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { EditorGroup, ISerializedEditorGroup, EditorCloseEvent } from 'vs/workbench/common/editor/editorGroup'; -import { Extensions as EditorExtensions, IEditorInputFactoryRegistry, EditorInput, IFileEditorInput, IEditorInputFactory, CloseDirection } from 'vs/workbench/common/editor'; +import { Extensions as EditorExtensions, IEditorInputFactoryRegistry, EditorInput, IFileEditorInput, IEditorInputFactory, CloseDirection, EditorsOrder } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { TestLifecycleService, TestContextService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -307,7 +307,7 @@ suite('Workbench editor groups', () => { const events = groupListener(group); assert.equal(group.count, 0); - assert.equal(group.getEditors(true).length, 0); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); // Active && Pinned const input1 = input(); @@ -315,7 +315,7 @@ suite('Workbench editor groups', () => { assert.equal(openedEditor, input1); assert.equal(group.count, 1); - assert.equal(group.getEditors(true).length, 1); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 1); assert.equal(group.activeEditor, input1); assert.equal(group.isActive(input1), true); assert.equal(group.isPreview(input1), false); @@ -328,7 +328,7 @@ suite('Workbench editor groups', () => { let editor = group.closeEditor(input1); assert.equal(editor, input1); assert.equal(group.count, 0); - assert.equal(group.getEditors(true).length, 0); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); assert.equal(group.activeEditor, undefined); assert.equal(events.closed[0].editor, input1); assert.equal(events.closed[0].index, 0); @@ -339,7 +339,7 @@ suite('Workbench editor groups', () => { group.openEditor(input2, { active: true, pinned: false }); assert.equal(group.count, 1); - assert.equal(group.getEditors(true).length, 1); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 1); assert.equal(group.activeEditor, input2); assert.equal(group.isActive(input2), true); assert.equal(group.isPreview(input2), true); @@ -351,7 +351,7 @@ suite('Workbench editor groups', () => { group.closeEditor(input2); assert.equal(group.count, 0); - assert.equal(group.getEditors(true).length, 0); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); assert.equal(group.activeEditor, undefined); assert.equal(events.closed[1].editor, input2); assert.equal(events.closed[1].index, 0); @@ -360,7 +360,7 @@ suite('Workbench editor groups', () => { editor = group.closeEditor(input2); assert.ok(!editor); assert.equal(group.count, 0); - assert.equal(group.getEditors(true).length, 0); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); assert.equal(group.activeEditor, undefined); assert.equal(events.closed[1].editor, input2); @@ -369,7 +369,7 @@ suite('Workbench editor groups', () => { group.openEditor(input3, { active: false, pinned: true }); assert.equal(group.count, 1); - assert.equal(group.getEditors(true).length, 1); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 1); assert.equal(group.activeEditor, input3); assert.equal(group.isActive(input3), true); assert.equal(group.isPreview(input3), false); @@ -381,7 +381,7 @@ suite('Workbench editor groups', () => { group.closeEditor(input3); assert.equal(group.count, 0); - assert.equal(group.getEditors(true).length, 0); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); assert.equal(group.activeEditor, undefined); assert.equal(events.closed[2].editor, input3); @@ -390,7 +390,7 @@ suite('Workbench editor groups', () => { group.closeEditor(input3); assert.equal(group.count, 0); - assert.equal(group.getEditors(true).length, 0); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); assert.equal(group.activeEditor, undefined); assert.equal(events.closed[2].editor, input3); @@ -399,7 +399,7 @@ suite('Workbench editor groups', () => { group.openEditor(input4); assert.equal(group.count, 1); - assert.equal(group.getEditors(true).length, 1); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 1); assert.equal(group.activeEditor, input4); assert.equal(group.isActive(input4), true); assert.equal(group.isPreview(input4), true); @@ -411,7 +411,7 @@ suite('Workbench editor groups', () => { group.closeEditor(input4); assert.equal(group.count, 0); - assert.equal(group.getEditors(true).length, 0); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 0); assert.equal(group.activeEditor, undefined); assert.equal(events.closed[3].editor, input4); }); @@ -436,7 +436,7 @@ suite('Workbench editor groups', () => { group.openEditor(input3, { pinned: true, active: true }); assert.equal(group.count, 3); - assert.equal(group.getEditors(true).length, 3); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 3); assert.equal(group.activeEditor, input3); assert.equal(group.isActive(input1), false); assert.equal(group.isPinned(input1), true); @@ -456,7 +456,7 @@ suite('Workbench editor groups', () => { assert.equal(events.activated[1], input2); assert.equal(events.activated[2], input3); - const mru = group.getEditors(true); + const mru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); assert.equal(mru[0], input3); assert.equal(mru[1], input2); assert.equal(mru[2], input1); @@ -496,12 +496,12 @@ suite('Workbench editor groups', () => { group.openEditor(input2, { pinned: true, active: true }); group.openEditor(input3, { pinned: true, active: true }); - assert.equal(input3, group.getEditors()[2]); + assert.equal(input3, group.getEditors(EditorsOrder.SEQUENTIAL)[2]); const input4 = input(); group.openEditor(input4, { pinned: false, active: true }); // this should cause the preview editor to move after input3 - assert.equal(input4, group.getEditors()[2]); + assert.equal(input4, group.getEditors(EditorsOrder.SEQUENTIAL)[2]); }); test('Multiple Editors - Pinned and Active (DEFAULT_OPEN_EDITOR_DIRECTION = Direction.LEFT)', function () { @@ -528,9 +528,9 @@ suite('Workbench editor groups', () => { group.openEditor(input2, { pinned: true, active: true }); group.openEditor(input3, { pinned: true, active: true }); - assert.equal(group.getEditors()[0], input3); - assert.equal(group.getEditors()[1], input2); - assert.equal(group.getEditors()[2], input1); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input3); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], input2); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[2], input1); group.closeAllEditors(); @@ -551,7 +551,7 @@ suite('Workbench editor groups', () => { group.openEditor(input3, { pinned: true }); assert.equal(group.count, 3); - assert.equal(group.getEditors(true).length, 3); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 3); assert.equal(group.activeEditor, input1); assert.equal(group.isActive(input1), true); assert.equal(group.isPinned(input1), true); @@ -566,7 +566,7 @@ suite('Workbench editor groups', () => { assert.equal(group.isPinned(2), true); assert.equal(group.isPreview(input3), false); - const mru = group.getEditors(true); + const mru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); assert.equal(mru[0], input1); assert.equal(mru[1], input3); assert.equal(mru[2], input2); @@ -586,7 +586,7 @@ suite('Workbench editor groups', () => { group.openEditor(input3); // overwrites preview assert.equal(group.count, 1); - assert.equal(group.getEditors(true).length, 1); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).length, 1); assert.equal(group.activeEditor, input3); assert.equal(group.isActive(input3), true); assert.equal(group.isPinned(input3), false); @@ -600,7 +600,7 @@ suite('Workbench editor groups', () => { assert.equal(events.closed[0].replaced, true); assert.equal(events.closed[1].replaced, true); - const mru = group.getEditors(true); + const mru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); assert.equal(mru[0], input3); assert.equal(mru.length, 1); }); @@ -619,7 +619,7 @@ suite('Workbench editor groups', () => { assert.equal(group.activeEditor, input3); - let mru = group.getEditors(true); + let mru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); assert.equal(mru[0], input3); assert.equal(mru[1], input2); assert.equal(mru[2], input1); @@ -634,7 +634,7 @@ suite('Workbench editor groups', () => { assert.equal(group.isActive(input2), false); assert.equal(group.isActive(input3), false); - mru = group.getEditors(true); + mru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); assert.equal(mru[0], input1); assert.equal(mru[1], input3); assert.equal(mru[2], input2); @@ -677,8 +677,8 @@ suite('Workbench editor groups', () => { assert.equal(group.activeEditor, input3); assert.equal(group.count, 2); // 2 previews got merged into one - assert.equal(group.getEditors()[0], input2); - assert.equal(group.getEditors()[1], input3); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input2); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], input3); assert.equal(events.closed[0].editor, input1); assert.equal(group.count, 2); @@ -686,7 +686,7 @@ suite('Workbench editor groups', () => { assert.equal(group.activeEditor, input3); assert.equal(group.count, 1); // pinning replaced the preview - assert.equal(group.getEditors()[0], input3); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input3); assert.equal(events.closed[1].editor, input2); assert.equal(group.count, 1); }); @@ -708,7 +708,7 @@ suite('Workbench editor groups', () => { group.openEditor(input5, { pinned: true, active: true }); assert.equal(group.activeEditor, input5); - assert.equal(group.getEditors(true)[0], input5); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0], input5); assert.equal(group.count, 5); group.closeEditor(input5); @@ -767,7 +767,7 @@ suite('Workbench editor groups', () => { group.openEditor(input5, { pinned: true, active: true }); assert.equal(group.activeEditor, input5); - assert.equal(group.getEditors(true)[0], input5); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0], input5); assert.equal(group.count, 5); group.closeEditor(input5); @@ -814,8 +814,8 @@ suite('Workbench editor groups', () => { group.moveEditor(input1, 1); assert.equal(events.moved[0], input1); - assert.equal(group.getEditors()[0], input2); - assert.equal(group.getEditors()[1], input1); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input2); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], input1); group.setActive(input1); group.openEditor(input3, { pinned: true, active: true }); @@ -825,20 +825,20 @@ suite('Workbench editor groups', () => { group.moveEditor(input4, 0); assert.equal(events.moved[1], input4); - assert.equal(group.getEditors()[0], input4); - assert.equal(group.getEditors()[1], input2); - assert.equal(group.getEditors()[2], input1); - assert.equal(group.getEditors()[3], input3); - assert.equal(group.getEditors()[4], input5); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input4); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], input2); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[2], input1); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[3], input3); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[4], input5); group.moveEditor(input4, 3); group.moveEditor(input2, 1); - assert.equal(group.getEditors()[0], input1); - assert.equal(group.getEditors()[1], input2); - assert.equal(group.getEditors()[2], input3); - assert.equal(group.getEditors()[3], input4); - assert.equal(group.getEditors()[4], input5); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input1); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], input2); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[2], input3); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[3], input4); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[4], input5); }); test('Multiple Editors - move editor across groups', function () { @@ -858,9 +858,9 @@ suite('Workbench editor groups', () => { group1.openEditor(g2_input1, { active: true, pinned: true, index: 1 }); assert.equal(group1.count, 3); - assert.equal(group1.getEditors()[0], g1_input1); - assert.equal(group1.getEditors()[1], g2_input1); - assert.equal(group1.getEditors()[2], g1_input2); + assert.equal(group1.getEditors(EditorsOrder.SEQUENTIAL)[0], g1_input1); + assert.equal(group1.getEditors(EditorsOrder.SEQUENTIAL)[1], g2_input1); + assert.equal(group1.getEditors(EditorsOrder.SEQUENTIAL)[2], g1_input2); }); test('Multiple Editors - move editor across groups (input already exists in group 1)', function () { @@ -882,9 +882,9 @@ suite('Workbench editor groups', () => { group1.openEditor(g2_input1, { active: true, pinned: true, index: 0 }); assert.equal(group1.count, 3); - assert.equal(group1.getEditors()[0], g1_input2); - assert.equal(group1.getEditors()[1], g1_input1); - assert.equal(group1.getEditors()[2], g1_input3); + assert.equal(group1.getEditors(EditorsOrder.SEQUENTIAL)[0], g1_input2); + assert.equal(group1.getEditors(EditorsOrder.SEQUENTIAL)[1], g1_input1); + assert.equal(group1.getEditors(EditorsOrder.SEQUENTIAL)[2], g1_input3); }); test('Multiple Editors - Pinned & Non Active', function () { @@ -894,24 +894,24 @@ suite('Workbench editor groups', () => { group.openEditor(input1); assert.equal(group.activeEditor, input1); assert.equal(group.previewEditor, input1); - assert.equal(group.getEditors()[0], input1); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input1); assert.equal(group.count, 1); const input2 = input(); group.openEditor(input2, { pinned: true, active: false }); assert.equal(group.activeEditor, input1); assert.equal(group.previewEditor, input1); - assert.equal(group.getEditors()[0], input1); - assert.equal(group.getEditors()[1], input2); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input1); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], input2); assert.equal(group.count, 2); const input3 = input(); group.openEditor(input3, { pinned: true, active: false }); assert.equal(group.activeEditor, input1); assert.equal(group.previewEditor, input1); - assert.equal(group.getEditors()[0], input1); - assert.equal(group.getEditors()[1], input3); - assert.equal(group.getEditors()[2], input2); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input1); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], input3); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[2], input2); assert.equal(group.isPinned(input1), false); assert.equal(group.isPinned(input2), true); assert.equal(group.isPinned(input3), true); @@ -951,9 +951,9 @@ suite('Workbench editor groups', () => { group.closeEditors(group.activeEditor!, CloseDirection.LEFT); assert.equal(group.activeEditor, input3); assert.equal(group.count, 3); - assert.equal(group.getEditors()[0], input3); - assert.equal(group.getEditors()[1], input4); - assert.equal(group.getEditors()[2], input5); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input3); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], input4); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[2], input5); group.closeAllEditors(); group.openEditor(input1, { active: true, pinned: true }); @@ -968,9 +968,9 @@ suite('Workbench editor groups', () => { group.closeEditors(group.activeEditor!, CloseDirection.RIGHT); assert.equal(group.activeEditor, input3); assert.equal(group.count, 3); - assert.equal(group.getEditors()[0], input1); - assert.equal(group.getEditors()[1], input2); - assert.equal(group.getEditors()[2], input3); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], input1); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], input2); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[2], input3); }); test('Multiple Editors - real user example', function () { @@ -982,7 +982,7 @@ suite('Workbench editor groups', () => { assert.equal(openedEditor, indexHtml); assert.equal(group.activeEditor, indexHtml); assert.equal(group.previewEditor, indexHtml); - assert.equal(group.getEditors()[0], indexHtml); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], indexHtml); assert.equal(group.count, 1); // /index.html/ -> /index.html/ @@ -991,7 +991,7 @@ suite('Workbench editor groups', () => { assert.equal(openedEditor, indexHtml); assert.equal(group.activeEditor, indexHtml); assert.equal(group.previewEditor, indexHtml); - assert.equal(group.getEditors()[0], indexHtml); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], indexHtml); assert.equal(group.count, 1); // /index.html/ -> /style.css/ @@ -1000,7 +1000,7 @@ suite('Workbench editor groups', () => { assert.equal(openedEditor, styleCss); assert.equal(group.activeEditor, styleCss); assert.equal(group.previewEditor, styleCss); - assert.equal(group.getEditors()[0], styleCss); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], styleCss); assert.equal(group.count, 1); // /style.css/ -> [/style.css/, test.js] @@ -1011,8 +1011,8 @@ suite('Workbench editor groups', () => { assert.equal(group.activeEditor, testJs); assert.equal(group.isPreview(styleCss), true); assert.equal(group.isPinned(testJs), true); - assert.equal(group.getEditors()[0], styleCss); - assert.equal(group.getEditors()[1], testJs); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], styleCss); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], testJs); assert.equal(group.count, 2); // [/style.css/, test.js] -> [test.js, /index.html/] @@ -1022,8 +1022,8 @@ suite('Workbench editor groups', () => { assert.equal(group.previewEditor, indexHtml2); assert.equal(group.isPreview(indexHtml2), true); assert.equal(group.isPinned(testJs), true); - assert.equal(group.getEditors()[0], testJs); - assert.equal(group.getEditors()[1], indexHtml2); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[0], testJs); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], indexHtml2); assert.equal(group.count, 2); // make test.js active @@ -1060,9 +1060,9 @@ suite('Workbench editor groups', () => { group.openEditor(otherTs, { active: true }); assert.equal(group.count, 3); assert.equal(group.activeEditor, otherTs); - assert.ok(group.getEditors()[0].matches(testJs)); - assert.equal(group.getEditors()[1], otherTs); - assert.ok(group.getEditors()[2].matches(indexHtml)); + assert.ok(group.getEditors(EditorsOrder.SEQUENTIAL)[0].matches(testJs)); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], otherTs); + assert.ok(group.getEditors(EditorsOrder.SEQUENTIAL)[2].matches(indexHtml)); // make index.html active const indexHtml4 = input('index.html'); @@ -1073,20 +1073,20 @@ suite('Workbench editor groups', () => { group.closeEditor(indexHtml); assert.equal(group.count, 2); assert.equal(group.activeEditor, otherTs); - assert.ok(group.getEditors()[0].matches(testJs)); - assert.equal(group.getEditors()[1], otherTs); + assert.ok(group.getEditors(EditorsOrder.SEQUENTIAL)[0].matches(testJs)); + assert.equal(group.getEditors(EditorsOrder.SEQUENTIAL)[1], otherTs); // [test.js, /other.ts/] -> [test.js] group.closeEditor(otherTs); assert.equal(group.count, 1); assert.equal(group.activeEditor, testJs); - assert.ok(group.getEditors()[0].matches(testJs)); + assert.ok(group.getEditors(EditorsOrder.SEQUENTIAL)[0].matches(testJs)); // [test.js] -> /test.js/ group.unpin(testJs); assert.equal(group.count, 1); assert.equal(group.activeEditor, testJs); - assert.ok(group.getEditors()[0].matches(testJs)); + assert.ok(group.getEditors(EditorsOrder.SEQUENTIAL)[0].matches(testJs)); assert.equal(group.isPinned(testJs), false); assert.equal(group.isPreview(testJs), true); @@ -1173,13 +1173,13 @@ suite('Workbench editor groups', () => { assert.equal(group1.previewEditor!.matches(g1_input2), true); assert.equal(group2.previewEditor!.matches(g2_input2), true); - assert.equal(group1.getEditors(true)[0].matches(g1_input2), true); - assert.equal(group1.getEditors(true)[1].matches(g1_input3), true); - assert.equal(group1.getEditors(true)[2].matches(g1_input1), true); + assert.equal(group1.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].matches(g1_input2), true); + assert.equal(group1.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].matches(g1_input3), true); + assert.equal(group1.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[2].matches(g1_input1), true); - assert.equal(group2.getEditors(true)[0].matches(g2_input1), true); - assert.equal(group2.getEditors(true)[1].matches(g2_input3), true); - assert.equal(group2.getEditors(true)[2].matches(g2_input2), true); + assert.equal(group2.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].matches(g2_input1), true); + assert.equal(group2.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].matches(g2_input3), true); + assert.equal(group2.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[2].matches(g2_input2), true); // Create model again - should load from storage group1 = inst.createInstance(EditorGroup, group1.serialize()); @@ -1192,13 +1192,13 @@ suite('Workbench editor groups', () => { assert.equal(group1.previewEditor!.matches(g1_input2), true); assert.equal(group2.previewEditor!.matches(g2_input2), true); - assert.equal(group1.getEditors(true)[0].matches(g1_input2), true); - assert.equal(group1.getEditors(true)[1].matches(g1_input3), true); - assert.equal(group1.getEditors(true)[2].matches(g1_input1), true); + assert.equal(group1.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].matches(g1_input2), true); + assert.equal(group1.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].matches(g1_input3), true); + assert.equal(group1.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[2].matches(g1_input1), true); - assert.equal(group2.getEditors(true)[0].matches(g2_input1), true); - assert.equal(group2.getEditors(true)[1].matches(g2_input3), true); - assert.equal(group2.getEditors(true)[2].matches(g2_input2), true); + assert.equal(group2.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].matches(g2_input1), true); + assert.equal(group2.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].matches(g2_input3), true); + assert.equal(group2.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[2].matches(g2_input2), true); }); test('Single group, multiple editors - persist (some not persistable)', function () { @@ -1230,9 +1230,9 @@ suite('Workbench editor groups', () => { assert.equal(group.activeEditor!.matches(nonSerializableInput2), true); assert.equal(group.previewEditor!.matches(nonSerializableInput2), true); - assert.equal(group.getEditors(true)[0].matches(nonSerializableInput2), true); - assert.equal(group.getEditors(true)[1].matches(serializableInput2), true); - assert.equal(group.getEditors(true)[2].matches(serializableInput1), true); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].matches(nonSerializableInput2), true); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].matches(serializableInput2), true); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[2].matches(serializableInput1), true); // Create model again - should load from storage group = inst.createInstance(EditorGroup, group.serialize()); @@ -1241,8 +1241,8 @@ suite('Workbench editor groups', () => { assert.equal(group.activeEditor!.matches(serializableInput2), true); assert.equal(group.previewEditor, null); - assert.equal(group.getEditors(true)[0].matches(serializableInput2), true); - assert.equal(group.getEditors(true)[1].matches(serializableInput1), true); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].matches(serializableInput2), true); + assert.equal(group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[1].matches(serializableInput1), true); }); test('Multiple groups, multiple editors - persist (some not persistable, causes empty group)', function () { @@ -1277,8 +1277,8 @@ suite('Workbench editor groups', () => { group2 = inst.createInstance(EditorGroup, group2.serialize()); assert.equal(group1.count, 2); - assert.equal(group1.getEditors()[0].matches(serializableInput1), true); - assert.equal(group1.getEditors()[1].matches(serializableInput2), true); + assert.equal(group1.getEditors(EditorsOrder.SEQUENTIAL)[0].matches(serializableInput1), true); + assert.equal(group1.getEditors(EditorsOrder.SEQUENTIAL)[1].matches(serializableInput2), true); }); test('Multiple Editors - Editor Dispose', function () { diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index bed812a691c..5863c631b1c 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -11,7 +11,7 @@ import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; +import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorInput, EditorOptions, EditorsOrder } from 'vs/workbench/common/editor'; import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView, IEditorGroupsAccessor } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; @@ -56,7 +56,7 @@ import { IExtensionService, NullExtensionService } from 'vs/workbench/services/e import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/browser/decorations'; import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, EditorsOrder, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverrideHandler, IVisibleEditor, ISaveEditorsOptions, IRevertAllEditorsOptions, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; @@ -377,7 +377,6 @@ export class TestHistoryService implements IHistoryService { getHistory(): ReadonlyArray { return []; } openNextRecentlyUsedEditor(group?: GroupIdentifier): void { } openPreviouslyUsedEditor(group?: GroupIdentifier): void { } - getMostRecentlyUsedOpenEditors(): Array { return []; } getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; } getLastActiveFile(_schemeFilter: string): URI | undefined { return undefined; } openLastEditLocation(): void { } @@ -912,9 +911,12 @@ export class TestEditorService implements EditorServiceImpl { visibleControls: ReadonlyArray = []; visibleTextEditorWidgets = []; visibleEditors: ReadonlyArray = []; + count = this.editors.length; constructor(private editorGroupService?: IEditorGroupsService) { } + getEditors() { return []; } + overrideOpenEditor(_handler: IOpenEditorOverrideHandler): IDisposable { return toDisposable(() => undefined); } From a2c591f8caff05b7fb7c3e04bbe6fed6758f5ab1 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 20 Dec 2019 09:46:22 +0100 Subject: [PATCH 185/241] Allow relative paths for python firstLine detection Fixes #87214 --- extensions/python/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/python/package.json b/extensions/python/package.json index 645cba2bae3..c500b586f45 100644 --- a/extensions/python/package.json +++ b/extensions/python/package.json @@ -14,7 +14,7 @@ "extensions": [ ".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi", ".pyi", ".ipy"], "aliases": [ "Python", "py" ], "filenames": [ "Snakefile" ], - "firstLine": "^#!\\s*/.*\\bpython[0-9.-]*\\b", + "firstLine": "^#!\\s*/?.*\\bpython[0-9.-]*\\b", "configuration": "./language-configuration.json" }], "grammars": [{ From 4858143eb5c2f3b7c855462c21ea7895f9f5d28f Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 20 Dec 2019 10:01:02 +0100 Subject: [PATCH 186/241] editors - openEditors() to return correct editor --- .../browser/parts/editor/editorGroupView.ts | 14 ++++++-------- .../browser/parts/editor/editorsObserver.ts | 4 ++-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index edea4e3744d..4bce5420b38 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1015,7 +1015,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Use the first editor as active editor const { editor, options } = editors.shift()!; - let firstOpenedEditor = await this.openEditor(editor, options); + await this.openEditor(editor, options); // Open the other ones inactive const startingIndex = this.getIndexOfEditor(editor) + 1; @@ -1025,13 +1025,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { adjustedEditorOptions.pinned = true; adjustedEditorOptions.index = startingIndex + index; - const openedEditor = await this.openEditor(editor, adjustedEditorOptions); - if (!firstOpenedEditor) { - firstOpenedEditor = openedEditor; // only take if the first editor opening failed - } + await this.openEditor(editor, adjustedEditorOptions); })); - return firstOpenedEditor; + // Opening many editors at once can put any editor to be + // the active one depending on options. As such, we simply + // return the active control after this operation. + return this.editorControl.activeControl; } //#endregion @@ -1522,8 +1522,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#endregion - //#endregion - //#region Themable protected updateStyles(): void { diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index e888b274b2f..0545c9710d1 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -235,7 +235,7 @@ export class EditorsObserver extends Disposable { if (typeof groupId === 'number') { const group = this.editorGroupsService.getGroup(groupId); if (group) { - this.doEnsureOpenedEditorsLimit(limit, group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => ({ editor, groupId })), exclude); + await this.doEnsureOpenedEditorsLimit(limit, group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => ({ editor, groupId })), exclude); } } @@ -249,7 +249,7 @@ export class EditorsObserver extends Disposable { // Across all editor groups else { - this.doEnsureOpenedEditorsLimit(limit, this.mostRecentEditorsMap.values(), exclude); + await this.doEnsureOpenedEditorsLimit(limit, this.mostRecentEditorsMap.values(), exclude); } } From faf4271f53c65649990a0877136d4769115323d5 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 20 Dec 2019 10:49:26 +0100 Subject: [PATCH 187/241] Put Help+Feedback view back into remote explorer User study last month showed that the ? was less successful than the view, so the view is back --- .../contrib/remote/browser/remote.ts | 288 ++++++++++++++---- .../contrib/remote/browser/remoteViewlet.css | 20 +- .../remote/common/remoteExplorerService.ts | 71 ----- 3 files changed, 253 insertions(+), 126 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index f14bd8e7db2..0dcedfacfa4 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -5,6 +5,7 @@ import 'vs/css!./remoteViewlet'; import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; import { URI } from 'vs/base/common/uri'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -22,7 +23,7 @@ import { IViewDescriptor, IViewsRegistry, Extensions } from 'vs/workbench/common import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction, Viewlet } from 'vs/workbench/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -41,17 +42,124 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { SwitchRemoteViewItem, SwitchRemoteAction } from 'vs/workbench/contrib/remote/browser/explorerViewItems'; import { Action, IActionViewItem, IAction } from 'vs/base/common/actions'; import { isStringArray } from 'vs/base/common/types'; -import { IRemoteExplorerService, HelpInformation } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { startsWith } from 'vs/base/common/strings'; import { TunnelPanelDescriptor, TunnelViewModel, forwardedPortsViewEnabled } from 'vs/workbench/contrib/remote/browser/tunnelView'; import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; -import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; +import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { Event } from 'vs/base/common/event'; +import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; + +export interface HelpInformation { + extensionDescription: IExtensionDescription; + getStarted?: string; + documentation?: string; + feedback?: string; + issues?: string; + remoteName?: string[] | string; +} + +const remoteHelpExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'remoteHelp', + jsonSchema: { + description: nls.localize('RemoteHelpInformationExtPoint', 'Contributes help information for Remote'), + type: 'object', + properties: { + 'getStarted': { + description: nls.localize('RemoteHelpInformationExtPoint.getStarted', "The url to your project's Getting Started page"), + type: 'string' + }, + 'documentation': { + description: nls.localize('RemoteHelpInformationExtPoint.documentation', "The url to your project's documentation page"), + type: 'string' + }, + 'feedback': { + description: nls.localize('RemoteHelpInformationExtPoint.feedback', "The url to your project's feedback reporter"), + type: 'string' + }, + 'issues': { + description: nls.localize('RemoteHelpInformationExtPoint.issues', "The url to your project's issues list"), + type: 'string' + } + } + } +}); + +interface IViewModel { + helpInformation: HelpInformation[]; +} + +class HelpTreeVirtualDelegate implements IListVirtualDelegate { + getHeight(element: IHelpItem): number { + return 22; + } + + getTemplateId(element: IHelpItem): string { + return 'HelpItemTemplate'; + } +} + +interface IHelpItemTemplateData { + parent: HTMLElement; + icon: HTMLElement; +} + +class HelpTreeRenderer implements ITreeRenderer { + templateId: string = 'HelpItemTemplate'; + + renderTemplate(container: HTMLElement): IHelpItemTemplateData { + dom.addClass(container, 'remote-help-tree-node-item'); + const icon = dom.append(container, dom.$('.remote-help-tree-node-item-icon')); + const data = Object.create(null); + data.parent = container; + data.icon = icon; + return data; + } + + renderElement(element: ITreeNode, index: number, templateData: IHelpItemTemplateData, height: number | undefined): void { + const container = templateData.parent; + dom.append(container, templateData.icon); + dom.addClasses(templateData.icon, ...element.element.iconClasses); + const labelContainer = dom.append(container, dom.$('.help-item-label')); + labelContainer.innerText = element.element.label; + } + + disposeTemplate(templateData: IHelpItemTemplateData): void { + + } +} + +class HelpDataSource implements IAsyncDataSource { + hasChildren(element: any) { + return element instanceof HelpModel; + } + + getChildren(element: any) { + if (element instanceof HelpModel && element.items) { + return element.items; + } + + return []; + } +} + +interface IHelpItem { + key: string; + iconClasses: string[]; + label: string; + handleClick(): Promise; +} class HelpModel { items: IHelpItem[] | undefined; constructor( + viewModel: IViewModel, openerService: IOpenerService, quickInputService: IQuickInputService, commandService: ICommandService, @@ -59,12 +167,12 @@ class HelpModel { environmentService: IWorkbenchEnvironmentService ) { let helpItems: IHelpItem[] = []; - const getStarted = remoteExplorerService.helpInformation.filter(info => info.getStarted); + const getStarted = viewModel.helpInformation.filter(info => info.getStarted); if (getStarted.length) { helpItems.push(new HelpItem( - ['getStarted'], - nls.localize('remote.help.getStarted', "$(star) Get Started"), + 'star', + nls.localize('remote.help.getStarted', "Get Started"), getStarted.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, url: info.getStarted!, @@ -77,12 +185,12 @@ class HelpModel { )); } - const documentation = remoteExplorerService.helpInformation.filter(info => info.documentation); + const documentation = viewModel.helpInformation.filter(info => info.documentation); if (documentation.length) { helpItems.push(new HelpItem( - ['documentation'], - nls.localize('remote.help.documentation', "$(book) Read Documentation"), + 'book', + nls.localize('remote.help.documentation', "Read Documentation"), documentation.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, url: info.documentation!, @@ -95,12 +203,12 @@ class HelpModel { )); } - const feedback = remoteExplorerService.helpInformation.filter(info => info.feedback); + const feedback = viewModel.helpInformation.filter(info => info.feedback); if (feedback.length) { helpItems.push(new HelpItem( - ['feedback'], - nls.localize('remote.help.feedback', "$(twitter) Provide Feedback"), + 'twitter', + nls.localize('remote.help.feedback', "Provide Feedback"), feedback.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, url: info.feedback!, @@ -113,12 +221,12 @@ class HelpModel { )); } - const issues = remoteExplorerService.helpInformation.filter(info => info.issues); + const issues = viewModel.helpInformation.filter(info => info.issues); if (issues.length) { helpItems.push(new HelpItem( - ['issues'], - nls.localize('remote.help.issues', "$(issues) Review Issues"), + 'issues', + nls.localize('remote.help.issues', "Review Issues"), issues.map((info: HelpInformation) => ({ extensionDescription: info.extensionDescription, url: info.issues!, @@ -133,9 +241,9 @@ class HelpModel { if (helpItems.length) { helpItems.push(new IssueReporterItem( - ['issueReporter'], - nls.localize('remote.help.report', "$(comment) Report Issue"), - remoteExplorerService.helpInformation.map(info => ({ + 'comment', + nls.localize('remote.help.report', "Report Issue"), + viewModel.helpInformation.map(info => ({ extensionDescription: info.extensionDescription, remoteAuthority: (typeof info.remoteName === 'string') ? [info.remoteName] : info.remoteName })), @@ -152,21 +260,19 @@ class HelpModel { } } -interface IHelpItem extends IQuickPickItem { - label: string; - handleClick(): Promise; -} - abstract class HelpItemBase implements IHelpItem { + public iconClasses: string[] = []; constructor( - public iconClasses: string[], + public key: string, public label: string, public values: { extensionDescription: IExtensionDescription, url?: string, remoteAuthority: string[] | undefined }[], private quickInputService: IQuickInputService, private environmentService: IWorkbenchEnvironmentService, private remoteExplorerService: IRemoteExplorerService ) { - iconClasses.push('remote-help-tree-node-item-icon'); + this.iconClasses.push(`codicon-${key}`); + this.iconClasses.push('remote-help-tree-node-item-icon'); + this.iconClasses.push('codicon'); } async handleClick() { @@ -208,7 +314,7 @@ abstract class HelpItemBase implements IHelpItem { class HelpItem extends HelpItemBase { constructor( - iconClasses: string[], + key: string, label: string, values: { extensionDescription: IExtensionDescription; url: string, remoteAuthority: string[] | undefined }[], quickInputService: IQuickInputService, @@ -216,7 +322,7 @@ class HelpItem extends HelpItemBase { private openerService: IOpenerService, remoteExplorerService: IRemoteExplorerService ) { - super(iconClasses, label, values, quickInputService, environmentService, remoteExplorerService); + super(key, label, values, quickInputService, environmentService, remoteExplorerService); } protected async takeAction(extensionDescription: IExtensionDescription, url: string): Promise { @@ -226,7 +332,7 @@ class HelpItem extends HelpItemBase { class IssueReporterItem extends HelpItemBase { constructor( - iconClasses: string[], + key: string, label: string, values: { extensionDescription: IExtensionDescription; remoteAuthority: string[] | undefined }[], quickInputService: IQuickInputService, @@ -234,7 +340,7 @@ class IssueReporterItem extends HelpItemBase { private commandService: ICommandService, remoteExplorerService: IRemoteExplorerService ) { - super(iconClasses, label, values, quickInputService, environmentService, remoteExplorerService); + super(key, label, values, quickInputService, environmentService, remoteExplorerService); } protected async takeAction(extensionDescription: IExtensionDescription): Promise { @@ -242,33 +348,71 @@ class IssueReporterItem extends HelpItemBase { } } -class HelpAction extends Action { - static readonly ID = 'remote.explorer.help'; - static readonly LABEL = nls.localize('remote.explorer.help', "Help, Documentation, and Feedback"); - private helpModel: HelpModel; +class HelpPanel extends ViewPane { + static readonly ID = '~remote.helpPanel'; + static readonly TITLE = nls.localize('remote.help', "Help and feedback"); + private tree!: WorkbenchAsyncDataTree; - constructor(id: string, - label: string, - @IOpenerService private readonly openerService: IOpenerService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @ICommandService private readonly commandService: ICommandService, - @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + constructor( + protected viewModel: IViewModel, + options: IViewPaneOptions, + @IKeybindingService protected keybindingService: IKeybindingService, + @IContextMenuService protected contextMenuService: IContextMenuService, + @IContextKeyService protected contextKeyService: IContextKeyService, + @IConfigurationService protected configurationService: IConfigurationService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IOpenerService protected openerService: IOpenerService, + @IQuickInputService protected quickInputService: IQuickInputService, + @ICommandService protected commandService: ICommandService, + @IRemoteExplorerService protected readonly remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService protected readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { - super(id, label, 'codicon codicon-question'); - this.helpModel = new HelpModel(openerService, quickInputService, commandService, remoteExplorerService, environmentService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService); } - async run(event?: any): Promise { - if (!this.helpModel.items) { - this.helpModel = new HelpModel(this.openerService, this.quickInputService, this.commandService, this.remoteExplorerService, this.environmentService); - } - if (this.helpModel.items) { - const selection = await this.quickInputService.pick(this.helpModel.items, { placeHolder: nls.localize('remote.explorer.helpPlaceholder', "Help and Feedback") }); - if (selection) { - return selection.handleClick(); + protected renderBody(container: HTMLElement): void { + dom.addClass(container, 'remote-help'); + const treeContainer = document.createElement('div'); + dom.addClass(treeContainer, 'remote-help-content'); + container.appendChild(treeContainer); + + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, + 'RemoteHelp', + treeContainer, + new HelpTreeVirtualDelegate(), + [new HelpTreeRenderer()], + new HelpDataSource(), + { + keyboardSupport: true, } - } + ); + + const model = new HelpModel(this.viewModel, this.openerService, this.quickInputService, this.commandService, this.remoteExplorerService, this.workbenchEnvironmentService); + + this.tree.setInput(model); + + const helpItemNavigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: false, openOnSelection: false })); + + this._register(Event.debounce(helpItemNavigator.onDidOpenResource, (last, event) => event, 75, true)(e => { + e.element.handleClick(); + })); + } + + protected layoutBody(height: number, width: number): void { + this.tree.layout(height, width); + } +} + +class HelpPanelDescriptor implements IViewDescriptor { + readonly id = HelpPanel.ID; + readonly name = HelpPanel.TITLE; + readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly canToggleVisibility = true; + readonly hideByDefault = false; + readonly workspace = true; + + constructor(viewModel: IViewModel) { + this.ctorDescriptor = { ctor: HelpPanel, arguments: [viewModel] }; } } @@ -288,7 +432,9 @@ export class RemoteViewlet extends Viewlet { } } -export class RemoteViewPaneContainer extends FilterViewPaneContainer { +export class RemoteViewPaneContainer extends FilterViewPaneContainer implements IViewModel { + private helpPanelDescriptor = new HelpPanelDescriptor(this); + helpInformation: HelpInformation[] = []; private actions: IAction[] | undefined; private tunnelPanelDescriptor: TunnelPanelDescriptor | undefined; @@ -307,6 +453,41 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer { @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + this.addConstantViewDescriptors([this.helpPanelDescriptor]); + remoteHelpExtPoint.setHandler((extensions) => { + let helpInformation: HelpInformation[] = []; + for (let extension of extensions) { + this._handleRemoteInfoExtensionPoint(extension, helpInformation); + } + + this.helpInformation = helpInformation; + + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + if (this.helpInformation.length) { + viewsRegistry.registerViews([this.helpPanelDescriptor], VIEW_CONTAINER); + } else { + viewsRegistry.deregisterViews([this.helpPanelDescriptor], VIEW_CONTAINER); + } + }); + } + + private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser, helpInformation: HelpInformation[]) { + if (!extension.description.enableProposedApi) { + return; + } + + if (!extension.value.documentation && !extension.value.feedback && !extension.value.getStarted && !extension.value.issues) { + return; + } + + helpInformation.push({ + extensionDescription: extension.description, + getStarted: extension.value.getStarted, + documentation: extension.value.documentation, + feedback: extension.value.feedback, + issues: extension.value.issues, + remoteName: extension.value.remoteName + }); } protected getFilterOn(viewDescriptor: IViewDescriptor): string | undefined { @@ -324,8 +505,7 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer { public getActions(): IAction[] { if (!this.actions) { this.actions = [ - this.instantiationService.createInstance(SwitchRemoteAction, SwitchRemoteAction.ID, SwitchRemoteAction.LABEL), - this.instantiationService.createInstance(HelpAction, HelpAction.ID, HelpAction.LABEL) + this.instantiationService.createInstance(SwitchRemoteAction, SwitchRemoteAction.ID, SwitchRemoteAction.LABEL) ]; this.actions.forEach(a => { this._register(a); diff --git a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css index c8f0d9f2964..c1d72e03738 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css +++ b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css @@ -3,14 +3,32 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.remote-help-tree-node-item-icon { +.remote-help-content .monaco-list .monaco-list-row .remote-help-tree-node-item { + display: flex; + height: 22px; + line-height: 22px; + flex: 1; + text-overflow: ellipsis; + overflow: hidden; + flex-wrap: nowrap; +} + +.remote-help-content .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon { background-size: 16px; background-position: left center; background-repeat: no-repeat; + padding-right: 6px; + padding-top: 3px; + width: 16px; + height: 22px; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } +.remote-help-content .monaco-list .monaco-list-row .monaco-tl-twistie { + width: 0px !important; +} + .remote-help-tree-node-item-icon .monaco-icon-label-description-container { padding-left: 22px; } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 7654610a629..3ef12502500 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -3,13 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEditableData } from 'vs/workbench/common/views'; @@ -208,7 +205,6 @@ export interface IRemoteExplorerService { _serviceBrand: undefined; onDidChangeTargetType: Event; targetType: string; - readonly helpInformation: HelpInformation[]; readonly tunnelModel: TunnelModel; onDidChangeEditable: Event; setEditable(tunnelItem: ITunnelItem | undefined, data: IEditableData | null): void; @@ -220,47 +216,11 @@ export interface IRemoteExplorerService { refresh(): Promise; } -export interface HelpInformation { - extensionDescription: IExtensionDescription; - getStarted?: string; - documentation?: string; - feedback?: string; - issues?: string; - remoteName?: string[] | string; -} - -const remoteHelpExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'remoteHelp', - jsonSchema: { - description: nls.localize('RemoteHelpInformationExtPoint', 'Contributes help information for Remote'), - type: 'object', - properties: { - 'getStarted': { - description: nls.localize('RemoteHelpInformationExtPoint.getStarted', "The url to your project's Getting Started page"), - type: 'string' - }, - 'documentation': { - description: nls.localize('RemoteHelpInformationExtPoint.documentation', "The url to your project's documentation page"), - type: 'string' - }, - 'feedback': { - description: nls.localize('RemoteHelpInformationExtPoint.feedback', "The url to your project's feedback reporter"), - type: 'string' - }, - 'issues': { - description: nls.localize('RemoteHelpInformationExtPoint.issues', "The url to your project's issues list"), - type: 'string' - } - } - } -}); - class RemoteExplorerService implements IRemoteExplorerService { public _serviceBrand: undefined; private _targetType: string = ''; private readonly _onDidChangeTargetType: Emitter = new Emitter(); public readonly onDidChangeTargetType: Event = this._onDidChangeTargetType.event; - private _helpInformation: HelpInformation[] = []; private _tunnelModel: TunnelModel; private _editable: { tunnelItem: ITunnelItem | undefined, data: IEditableData } | undefined; private readonly _onDidChangeEditable: Emitter = new Emitter(); @@ -272,14 +232,6 @@ class RemoteExplorerService implements IRemoteExplorerService { @IConfigurationService configurationService: IConfigurationService ) { this._tunnelModel = new TunnelModel(tunnelService, storageService, configurationService); - remoteHelpExtPoint.setHandler((extensions) => { - let helpInformation: HelpInformation[] = []; - for (let extension of extensions) { - this._handleRemoteInfoExtensionPoint(extension, helpInformation); - } - - this._helpInformation = helpInformation; - }); } set targetType(name: string) { @@ -294,29 +246,6 @@ class RemoteExplorerService implements IRemoteExplorerService { return this._targetType; } - private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser, helpInformation: HelpInformation[]) { - if (!extension.description.enableProposedApi) { - return; - } - - if (!extension.value.documentation && !extension.value.feedback && !extension.value.getStarted && !extension.value.issues) { - return; - } - - helpInformation.push({ - extensionDescription: extension.description, - getStarted: extension.value.getStarted, - documentation: extension.value.documentation, - feedback: extension.value.feedback, - issues: extension.value.issues, - remoteName: extension.value.remoteName - }); - } - - get helpInformation(): HelpInformation[] { - return this._helpInformation; - } - get tunnelModel(): TunnelModel { return this._tunnelModel; } From e94ab8397a8b6750a135d55e23542a094c648ac9 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 19 Dec 2019 16:26:42 +0100 Subject: [PATCH 188/241] debug: use getConfiguratinoNames less often --- src/vs/workbench/contrib/debug/browser/debugActions.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index b44e946b8ca..3883e76be0a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -79,8 +79,7 @@ export class ConfigureAction extends AbstractDebugAction { private updateClass(): void { const configurationManager = this.debugService.getConfigurationManager(); - const configurationCount = configurationManager.getLaunches().map(l => l.getConfigurationNames().length).reduce((sum, current) => sum + current); - this.class = configurationCount > 0 ? 'debug-action codicon codicon-gear' : 'debug-action codicon codicon-gear notification'; + this.class = configurationManager.selectedConfiguration.name ? 'debug-action codicon codicon-gear' : 'debug-action codicon codicon-gear notification'; } async run(event?: any): Promise { @@ -145,7 +144,7 @@ export class StartAction extends AbstractDebugAction { if (debugService.state === State.Initializing) { return false; } - if ((sessions.length > 0) && debugService.getConfigurationManager().getLaunches().every(l => l.getConfigurationNames().length === 0)) { + if ((sessions.length > 0) && !debugService.getConfigurationManager().selectedConfiguration.name) { // There is already a debug session running and we do not have any launch configuration selected return false; } From d27ed199d2f3813a6da5b7dda4510a3ba4bb9320 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 20 Dec 2019 11:09:13 +0100 Subject: [PATCH 189/241] Debug: Support launch config grouping and sorting fixes #82332 --- .../debug/browser/debugActionViewItems.ts | 18 ++++----- .../browser/debugConfigurationManager.ts | 39 ++++++++++++++++++- .../contrib/debug/browser/debugQuickOpen.ts | 22 +++++------ .../workbench/contrib/debug/common/debug.ts | 11 +++++- .../contrib/debug/common/debugSchemas.ts | 28 +++++++++++++ .../contrib/debug/common/debugger.ts | 4 +- 6 files changed, 98 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index bca1d167e25..5104c2b038d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -157,16 +157,14 @@ export class StartDebugActionViewItem implements IActionViewItem { this.selected = 0; this.options = []; const manager = this.debugService.getConfigurationManager(); - const launches = manager.getLaunches(); const inWorkspace = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; - launches.forEach(launch => - launch.getConfigurationNames().forEach(name => { - if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) { - this.selected = this.options.length; - } - const label = inWorkspace ? `${name} (${launch.name})` : name; - this.options.push({ label, handler: () => { manager.selectConfiguration(launch, name); return true; } }); - })); + manager.getAllConfigurations().forEach(({ launch, name }) => { + if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) { + this.selected = this.options.length; + } + const label = inWorkspace ? `${name} (${launch.name})` : name; + this.options.push({ label, handler: () => { manager.selectConfiguration(launch, name); return true; } }); + }); if (this.options.length === 0) { this.options.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: () => false }); @@ -175,7 +173,7 @@ export class StartDebugActionViewItem implements IActionViewItem { } const disabledIdx = this.options.length - 1; - launches.filter(l => !l.hidden).forEach(l => { + manager.getLaunches().filter(l => !l.hidden).forEach(l => { const label = inWorkspace ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration..."); this.options.push({ label, handler: () => { diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 763ad95dd10..e958dd8ef85 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -21,7 +21,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IConfigPresentation } from 'vs/workbench/contrib/debug/common/debug'; import { Debugger } from 'vs/workbench/contrib/debug/common/debugger'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -214,6 +214,43 @@ export class ConfigurationManager implements IConfigurationManager { return results.reduce((first, second) => first.concat(second), []); } + getAllConfigurations(): { launch: ILaunch; name: string; }[] { + const all: { launch: ILaunch, name: string, presentation?: IConfigPresentation }[] = []; + for (let l of this.launches) { + for (let name of l.getConfigurationNames()) { + const config = l.getConfiguration(name) || l.getCompound(name); + if (config && !config.presentation?.hidden) { + all.push({ launch: l, name, presentation: config.presentation }); + } + } + } + + return all.sort((first, second) => { + if (!first.presentation) { + return 1; + } + if (!second.presentation) { + return -1; + } + if (!first.presentation.group) { + return 1; + } + if (!second.presentation.group) { + return -1; + } + if (first.presentation.group !== second.presentation.group) { + return first.presentation.group.localeCompare(second.presentation.group); + } + if (typeof first.presentation.order !== 'number') { + return 1; + } + if (typeof second.presentation.order !== 'number') { + return -1; + } + return first.presentation.order - second.presentation.order; + }); + } + private registerListeners(): void { debuggersExtPoint.setHandler((extensions, delta) => { delta.added.forEach(added => { diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts index 73dda0546df..3100077ca9b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts @@ -96,18 +96,18 @@ export class DebugQuickOpenHandler extends QuickOpenHandler { const configurations: QuickOpenEntry[] = []; const configManager = this.debugService.getConfigurationManager(); - const launches = configManager.getLaunches(); - for (let launch of launches) { - launch.getConfigurationNames().map(config => ({ config: config, highlights: matchesFuzzy(input, config, true) || undefined })) - .filter(({ highlights }) => !!highlights) - .forEach(({ config, highlights }) => { - if (launch === configManager.selectedConfiguration.launch && config === configManager.selectedConfiguration.name) { - this.autoFocusIndex = configurations.length; - } - configurations.push(new StartDebugEntry(this.debugService, this.contextService, this.notificationService, launch, config, highlights)); - }); + const allConfigurations = configManager.getAllConfigurations(); + for (let config of allConfigurations) { + const highlights = matchesFuzzy(input, config.name, true); + if (highlights) { + if (config.launch === configManager.selectedConfiguration.launch && config.name === configManager.selectedConfiguration.name) { + this.autoFocusIndex = configurations.length; + } + configurations.push(new StartDebugEntry(this.debugService, this.contextService, this.notificationService, config.launch, config.name, highlights)); + } } - launches.filter(l => !l.hidden).forEach((l, index) => { + + configManager.getLaunches().filter(l => !l.hidden).forEach((l, index) => { const label = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration..."); const entry = new AddConfigEntry(label, l, this.commandService, this.contextService, matchesFuzzy(input, label, true) || undefined); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index d3eb78bed69..89d79687f46 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -487,13 +487,19 @@ export interface IEnvConfig { noDebug?: boolean; } +export interface IConfigPresentation { + hidden?: boolean; + group?: string; + order?: number; +} + export interface IConfig extends IEnvConfig { // fundamental attributes type: string; request: string; name: string; - + presentation?: IConfigPresentation; // platform specifics windows?: IEnvConfig; osx?: IEnvConfig; @@ -510,6 +516,7 @@ export interface ICompound { name: string; preLaunchTask?: string | TaskIdentifier; configurations: (string | { name: string, folder: string })[]; + presentation?: IConfigPresentation; } export interface IDebugAdapter extends IDisposable { @@ -631,6 +638,8 @@ export interface IConfigurationManager { getLaunch(workspaceUri: uri | undefined): ILaunch | undefined; + getAllConfigurations(): { launch: ILaunch, name: string }[]; + /** * Allows to register on change of selected debug configuration. */ diff --git a/src/vs/workbench/contrib/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts index a2a4b16a6bf..20d546e3a31 100644 --- a/src/vs/workbench/contrib/debug/common/debugSchemas.ts +++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts @@ -132,6 +132,33 @@ export const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registe }); // debug general schema + +export const presentationSchema: IJSONSchema = { + type: 'object', + description: nls.localize('presentation', "Presentation options on how to show this configuration in the debug configuration dropdown and the command palette."), + properties: { + hidden: { + type: 'boolean', + default: false, + description: nls.localize('presentation.hidden', "Controls if this configuration should be shown in the configuration dropdown and the command palette.") + }, + group: { + type: 'string', + default: '', + description: nls.localize('presentation.group', "Group that this configuration belongs to. Used for grouping and sorting in the configuration dropdown and the command palette.") + }, + order: { + type: 'number', + default: 1, + description: nls.localize('presentation.order', "Order of this configuration within a group. Used for grouping and sorting in the configuration dropdown and the command palette.") + } + }, + default: { + hidden: false, + group: '', + order: 1 + } +}; const defaultCompound: ICompound = { name: 'Compound', configurations: [] }; export const launchSchema: IJSONSchema = { id: launchSchemaId, @@ -167,6 +194,7 @@ export const launchSchema: IJSONSchema = { type: 'string', description: nls.localize('app.launch.json.compound.name', "Name of compound. Appears in the launch configuration drop down menu.") }, + presentation: presentationSchema, configurations: { type: 'array', default: [], diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index 0e1045b3822..770bd7ef846 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -22,6 +22,7 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { presentationSchema } from 'vs/workbench/contrib/debug/common/debugSchemas'; export class Debugger implements IDebugger { @@ -222,7 +223,7 @@ export class Debugger implements IDebugger { }; properties['name'] = { type: 'string', - description: nls.localize('debugName', "Name of configuration; appears in the launch configuration drop down menu."), + description: nls.localize('debugName', "Name of configuration; appears in the launch configuration dropdown menu."), default: 'Launch' }; properties['request'] = { @@ -250,6 +251,7 @@ export class Debugger implements IDebugger { defaultSnippets: [{ body: { task: '', type: '' } }], description: nls.localize('debugPostDebugTask', "Task to run after debug session ends.") }; + properties['presentation'] = presentationSchema; properties['internalConsoleOptions'] = INTERNAL_CONSOLE_OPTIONS_SCHEMA; // Clear out windows, linux and osx fields to not have cycles inside the properties object delete properties['windows']; From ecc140e81dc5f2934181ddd40ff733a68646e149 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 20 Dec 2019 11:09:24 +0100 Subject: [PATCH 190/241] adaopt presentationHint in our launch.json --- .vscode/launch.json | 123 +++++++++++++++++++++++++++++++++----------- 1 file changed, 93 insertions(+), 30 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 701470db36c..ccbf4a70ae3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,7 +19,10 @@ "restart": true, "outFiles": [ "${workspaceFolder}/out/**/*.js" - ] + ], + "presentation": { + "hidden": true, + } }, { "type": "chrome", @@ -35,7 +38,10 @@ "port": 5876, "outFiles": [ "${workspaceFolder}/out/**/*.js" - ] + ], + "presentation": { + "hidden": true, + } }, { "type": "node", @@ -53,7 +59,10 @@ "port": 5875, "outFiles": [ "${workspaceFolder}/out/**/*.js" - ] + ], + "presentation": { + "hidden": true, + } }, { "type": "extensionHost", @@ -67,7 +76,11 @@ ], "outFiles": [ "${workspaceFolder}/out/**/*.js" - ] + ], + "presentation": { + "group": "5_tests", + "order": 6 + } }, { "type": "extensionHost", @@ -82,7 +95,11 @@ ], "outFiles": [ "${workspaceFolder}/out/**/*.js" - ] + ], + "presentation": { + "group": "5_tests", + "order": 3 + } }, { "type": "extensionHost", @@ -96,7 +113,11 @@ ], "outFiles": [ "${workspaceFolder}/out/**/*.js" - ] + ], + "presentation": { + "group": "5_tests", + "order": 4 + } }, { "type": "extensionHost", @@ -110,13 +131,20 @@ ], "outFiles": [ "${workspaceFolder}/out/**/*.js" - ] + ], + "presentation": { + "group": "5_tests", + "order": 5 + } }, { "type": "chrome", "request": "attach", "name": "Attach to VS Code", - "port": 9222 + "port": 9222, + "presentation": { + "hidden": true + } }, { "type": "chrome", @@ -142,7 +170,10 @@ "--inspect=5875", "--no-cached-data" ], - "webRoot": "${workspaceFolder}" + "webRoot": "${workspaceFolder}", + "presentation": { + "hidden": true + } }, { "type": "node", @@ -157,7 +188,11 @@ ], "outFiles": [ "${workspaceFolder}/out/**/*.js" - ] + ], + "presentation": { + "group": "2_launch", + "order": 1 + } }, { "type": "node", @@ -167,13 +202,21 @@ "runtimeArgs": [ "web" ], + "presentation": { + "group": "2_launch", + "order": 2 + } }, { "type": "chrome", "request": "launch", "name": "Launch VS Code (Web, Chrome)", "url": "http://localhost:8080", - "preLaunchTask": "Run web" + "preLaunchTask": "Run web", + "presentation": { + "group": "2_launch", + "order": 3 + } }, { "type": "node", @@ -184,7 +227,11 @@ "cwd": "${workspaceFolder}/extensions/git", "outFiles": [ "${workspaceFolder}/extensions/git/out/**/*.js" - ] + ], + "presentation": { + "group": "5_tests", + "order": 10 + } }, { "type": "extensionHost", @@ -198,7 +245,11 @@ ], "outFiles": [ "${workspaceFolder}/extensions/markdown-language-features/out/**/*.js" - ] + ], + "presentation": { + "group": "5_tests", + "order": 7 + } }, { "type": "extensionHost", @@ -212,7 +263,11 @@ ], "outFiles": [ "${workspaceFolder}/extensions/typescript-language-features/out/**/*.js" - ] + ], + "presentation": { + "group": "5_tests", + "order": 8 + } }, { "type": "node", @@ -236,6 +291,9 @@ ], "env": { "MOCHA_COLORS": "true" + }, + "presentation": { + "hidden": true } }, { @@ -276,39 +334,44 @@ "Launch VS Code", "Attach to Main Process", "Attach to Extension Host" - ] + ], + "presentation": { + "group": "1_vscode", + "order": 1 + } }, { "name": "Search and Renderer processes", "configurations": [ "Launch VS Code", "Attach to Search Process" - ] + ], + "presentation": { + "group": "1_vscode", + "order": 4 + } }, { "name": "Renderer and Extension Host processes", "configurations": [ "Launch VS Code", "Attach to Extension Host" - ] + ], + "presentation": { + "group": "1_vscode", + "order": 3 + } }, { "name": "Debug Unit Tests", "configurations": [ "Attach to VS Code", "Run Unit Tests" - ] - }, - { - "type": "node", - "request": "launch", - "name": "HTML Unit Tests", - "program": "${workspaceFolder}/extensions/html-language-features/server/node_modules/mocha/bin/_mocha", - "stopOnEntry": false, - "cwd": "${workspaceFolder}/extensions/html-language-features/server", - "outFiles": [ - "${workspaceFolder}/extensions/html-language-features/server/out/**/*.js" - ] - }, + ], + "presentation": { + "group": "1_vscode", + "order": 2 + } + } ] } From 0f90be23684b53adb5b9b24655ab0346e47658fc Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 20 Dec 2019 11:41:06 +0100 Subject: [PATCH 191/241] Custom tree message doesn't show if message is the same Fixes #87207 --- src/vs/workbench/browser/parts/views/customView.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index d8379e3ae26..157494928b5 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -495,14 +495,12 @@ export class CustomTreeView extends Disposable implements ITreeView { private showMessage(message: string): void { DOM.removeClass(this.messageElement, 'hide'); - if (this._messageValue !== message) { - this.resetMessageElement(); - this._messageValue = message; - if (!isFalsyOrWhitespace(this._message)) { - this.messageElement.textContent = this._messageValue; - } - this.layout(this._height, this._width); + this.resetMessageElement(); + this._messageValue = message; + if (!isFalsyOrWhitespace(this._message)) { + this.messageElement.textContent = this._messageValue; } + this.layout(this._height, this._width); } private hideMessage(): void { From 18932a0474b5e8bff0a00474e6c70795eb96e260 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 20 Dec 2019 11:41:43 +0100 Subject: [PATCH 192/241] better codicon alignment in codelens, #87062 --- src/vs/editor/contrib/codelens/codelensWidget.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/codelens/codelensWidget.css b/src/vs/editor/contrib/codelens/codelensWidget.css index d949272a106..681938c8033 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.css +++ b/src/vs/editor/contrib/codelens/codelensWidget.css @@ -29,11 +29,11 @@ .monaco-editor .codelens-decoration .codicon { line-height: inherit; - font-size: inherit; + font-size: 110%; + vertical-align: inherit; } .monaco-editor .codelens-decoration > a:hover .codicon::before { - text-decoration: underline; cursor: pointer; } From 6408c04ca7f3f12ca01d78729b3f236ea9f715de Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 20 Dec 2019 12:27:24 +0100 Subject: [PATCH 193/241] debug: render config groups in quick open --- .../contrib/debug/browser/debugConfigurationManager.ts | 2 +- src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts | 8 +++++++- src/vs/workbench/contrib/debug/common/debug.ts | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index e958dd8ef85..f5f1df0cf79 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -214,7 +214,7 @@ export class ConfigurationManager implements IConfigurationManager { return results.reduce((first, second) => first.concat(second), []); } - getAllConfigurations(): { launch: ILaunch; name: string; }[] { + getAllConfigurations(): { launch: ILaunch; name: string; presentation?: IConfigPresentation }[] { const all: { launch: ILaunch, name: string, presentation?: IConfigPresentation }[] = []; for (let l of this.launches) { for (let name of l.getConfigurationNames()) { diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts index 3100077ca9b..f9df27bfe5e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts @@ -97,13 +97,19 @@ export class DebugQuickOpenHandler extends QuickOpenHandler { const configManager = this.debugService.getConfigurationManager(); const allConfigurations = configManager.getAllConfigurations(); + let lastGroup: string | undefined; for (let config of allConfigurations) { const highlights = matchesFuzzy(input, config.name, true); if (highlights) { if (config.launch === configManager.selectedConfiguration.launch && config.name === configManager.selectedConfiguration.name) { this.autoFocusIndex = configurations.length; } - configurations.push(new StartDebugEntry(this.debugService, this.contextService, this.notificationService, config.launch, config.name, highlights)); + let entry: QuickOpenEntry = new StartDebugEntry(this.debugService, this.contextService, this.notificationService, config.launch, config.name, highlights); + if (lastGroup !== config.presentation?.group) { + entry = new QuickOpenEntryGroup(entry, undefined, true); + lastGroup = config.presentation?.group; + } + configurations.push(entry); } } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 89d79687f46..8cb55418bb9 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -638,7 +638,7 @@ export interface IConfigurationManager { getLaunch(workspaceUri: uri | undefined): ILaunch | undefined; - getAllConfigurations(): { launch: ILaunch, name: string }[]; + getAllConfigurations(): { launch: ILaunch, name: string, presentation?: IConfigPresentation }[]; /** * Allows to register on change of selected debug configuration. From befcf142f21af868012d90e7f581cf18c981dadd Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 20 Dec 2019 14:58:31 +0100 Subject: [PATCH 194/241] debug: redenr separators also in the select box #82332 --- .../contrib/debug/browser/debugActionViewItems.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 5104c2b038d..c48efd1ee95 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -158,10 +158,18 @@ export class StartDebugActionViewItem implements IActionViewItem { this.options = []; const manager = this.debugService.getConfigurationManager(); const inWorkspace = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; - manager.getAllConfigurations().forEach(({ launch, name }) => { + let lastGroup: string | undefined; + manager.getAllConfigurations().forEach(({ launch, name, presentation }) => { if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) { this.selected = this.options.length; } + if (lastGroup !== presentation?.group) { + lastGroup = presentation?.group; + if (this.options.length) { + this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: undefined }); + } + } + const label = inWorkspace ? `${name} (${launch.name})` : name; this.options.push({ label, handler: () => { manager.selectConfiguration(launch, name); return true; } }); }); From 04c805a943731c0d6da6e024637d2cadbb82ca36 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 20 Dec 2019 15:10:12 +0100 Subject: [PATCH 195/241] debug select make sure all separators are disabled --- .../workbench/contrib/debug/browser/debugActionViewItems.ts | 6 ++++-- src/vs/workbench/contrib/output/browser/outputActions.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index c48efd1ee95..767b547b64f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -159,6 +159,7 @@ export class StartDebugActionViewItem implements IActionViewItem { const manager = this.debugService.getConfigurationManager(); const inWorkspace = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; let lastGroup: string | undefined; + const disabledIdxs: number[] = []; manager.getAllConfigurations().forEach(({ launch, name, presentation }) => { if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) { this.selected = this.options.length; @@ -167,6 +168,7 @@ export class StartDebugActionViewItem implements IActionViewItem { lastGroup = presentation?.group; if (this.options.length) { this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: undefined }); + disabledIdxs.push(this.options.length - 1); } } @@ -178,9 +180,9 @@ export class StartDebugActionViewItem implements IActionViewItem { this.options.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: () => false }); } else { this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: undefined }); + disabledIdxs.push(this.options.length - 1); } - const disabledIdx = this.options.length - 1; manager.getLaunches().filter(l => !l.hidden).forEach(l => { const label = inWorkspace ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration..."); this.options.push({ @@ -191,7 +193,7 @@ export class StartDebugActionViewItem implements IActionViewItem { }); }); - this.selectBox.setOptions(this.options.map((data, index) => { text: data.label, isDisabled: (index === disabledIdx ? true : undefined) }), this.selected); + this.selectBox.setOptions(this.options.map((data, index) => { text: data.label, isDisabled: disabledIdxs.indexOf(index) !== -1 }), this.selected); } } diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index 1d16cfea3b1..4686280ec9a 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -173,7 +173,7 @@ export class SwitchOutputActionViewItem extends SelectActionViewItem { selected = logChannelIndex !== -1 ? separatorIndex + 1 + logChannelIndex : 0; } } - this.setOptions(options.map((label, index) => { text: label, isDisabled: (index === separatorIndex ? true : undefined) }), Math.max(0, selected)); + this.setOptions(options.map((label, index) => { text: label, isDisabled: (index === separatorIndex ? true : false) }), Math.max(0, selected)); } } From 3b673f48151df9b9984c6bd53b07a4a8c5d7bf7d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 20 Dec 2019 15:26:40 +0100 Subject: [PATCH 196/241] no underline for code lens commands, #87062 --- src/vs/editor/contrib/codelens/codelensWidget.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/editor/contrib/codelens/codelensWidget.css b/src/vs/editor/contrib/codelens/codelensWidget.css index 681938c8033..79eae29cea9 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.css +++ b/src/vs/editor/contrib/codelens/codelensWidget.css @@ -23,7 +23,6 @@ } .monaco-editor .codelens-decoration > a:hover { - text-decoration: underline; cursor: pointer; } From de5df4820fba58ce3928ab1dbb9a488f9049b7a4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 20 Dec 2019 15:29:34 +0100 Subject: [PATCH 197/241] Try out new go grammar Fixes #82549 --- extensions/go/cgmanifest.json | 10 +++--- extensions/go/package.json | 38 ++++++++++++++--------- extensions/go/syntaxes/go.tmLanguage.json | 31 +++++++++++------- 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/extensions/go/cgmanifest.json b/extensions/go/cgmanifest.json index a26813ed47e..f47768ba861 100644 --- a/extensions/go/cgmanifest.json +++ b/extensions/go/cgmanifest.json @@ -4,14 +4,14 @@ "component": { "type": "git", "git": { - "name": "language-go", - "repositoryUrl": "https://github.com/atom/language-go", - "commitHash": "b6fd68f74efa109679e31fe6f4a41ac105262d0e" + "name": "better-go-syntax", + "repositoryUrl": "https://github.com/jeff-hykin/better-go-syntax/ ", + "commitHash": "54ff898316f8647d77ffcf83880a9556445326f1" } }, "license": "MIT", - "description": "The file syntaxes/go.json was derived from the Atom package https://atom.io/packages/language-go.", - "version": "0.44.3" + "description": "The file syntaxes/go.tmLanguage.json is from https://github.com/jeff-hykin/better-go-syntax/ .", + "version": "1.0.0" } ], "version": 1 diff --git a/extensions/go/package.json b/extensions/go/package.json index a91d729adaf..121ce0c33e5 100644 --- a/extensions/go/package.json +++ b/extensions/go/package.json @@ -5,26 +5,36 @@ "version": "1.0.0", "publisher": "vscode", "license": "MIT", - "engines": { "vscode": "*" }, + "engines": { + "vscode": "*" + }, "scripts": { - "update-grammar": "node ../../build/npm/update-grammar.js atom/language-go grammars/go.cson ./syntaxes/go.tmLanguage.json" + "update-grammar": "node ../../build/npm/update-grammar.js jeff-hykin/better-go-syntax source/generated.tmLanguage.json ./syntaxes/go.tmLanguage.json" }, "contributes": { - "languages": [{ - "id": "go", - "extensions": [ ".go" ], - "aliases": [ "Go" ], - "configuration": "./language-configuration.json" - }], - "grammars": [{ - "language": "go", - "scopeName": "source.go", - "path": "./syntaxes/go.tmLanguage.json" - }], + "languages": [ + { + "id": "go", + "extensions": [ + ".go" + ], + "aliases": [ + "Go" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "go", + "scopeName": "source.go", + "path": "./syntaxes/go.tmLanguage.json" + } + ], "configurationDefaults": { "[go]": { "editor.insertSpaces": false } } } -} \ No newline at end of file +} diff --git a/extensions/go/syntaxes/go.tmLanguage.json b/extensions/go/syntaxes/go.tmLanguage.json index bc73a1d91bd..ea726a30206 100644 --- a/extensions/go/syntaxes/go.tmLanguage.json +++ b/extensions/go/syntaxes/go.tmLanguage.json @@ -1,14 +1,16 @@ { "information_for_contributors": [ - "This file has been converted from https://github.com/atom/language-go/blob/master/grammars/go.cson", + "This file has been converted from https://github.com/jeff-hykin/better-go-syntax/blob/master/source/generated.tmLanguage.json", "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-go/commit/b6fd68f74efa109679e31fe6f4a41ac105262d0e", + "version": "https://github.com/jeff-hykin/better-go-syntax/commit/54ff898316f8647d77ffcf83880a9556445326f1", "name": "Go", "scopeName": "source.go", - "comment": "Go language", "patterns": [ + { + "include": "#comments" + }, { "include": "#comments" }, @@ -350,24 +352,29 @@ "comments": { "patterns": [ { - "begin": "/\\*", - "end": "\\*/", - "captures": { - "0": { + "name": "comment.block.go", + "begin": "(\\/\\*)", + "beginCaptures": { + "1": { "name": "punctuation.definition.comment.go" } }, - "name": "comment.block.go" + "end": "(\\*\\/)", + "endCaptures": { + "1": { + "name": "punctuation.definition.comment.go" + } + } }, { - "begin": "//", + "name": "comment.line.double-slash.go", + "begin": "(\\/\\/)", "beginCaptures": { - "0": { + "1": { "name": "punctuation.definition.comment.go" } }, - "end": "$", - "name": "comment.line.double-slash.go" + "end": "(?:\\n|$)" } ] }, From 2def84c1e2e84b2d85f5330e5864514500ecdabf Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 20 Dec 2019 16:10:32 +0100 Subject: [PATCH 198/241] #87246 Abstract viewlet and panel registry --- .../api/browser/viewsExtensionPoint.ts | 41 ++------- .../browser/parts/views/viewPaneContainer.ts | 2 +- .../browser/workbench.contribution.ts | 86 +++++++++++++++++++ src/vs/workbench/common/views.ts | 28 +++--- .../debug/browser/debug.contribution.ts | 35 ++++---- .../contrib/debug/browser/debugViewlet.ts | 18 ---- .../workbench/contrib/debug/common/debug.ts | 3 - .../browser/extensions.contribution.ts | 24 +++--- .../extensions/browser/extensionsViewlet.ts | 26 ++---- .../contrib/extensions/common/extensions.ts | 14 ++- .../contrib/files/browser/explorerViewlet.ts | 32 +++---- .../files/browser/files.contribution.ts | 15 +--- .../workbench/contrib/files/common/files.ts | 9 +- .../markers/browser/markers.contribution.ts | 60 +++++-------- .../outline/browser/outline.contribution.ts | 2 +- .../contrib/remote/browser/remote.ts | 46 +++++++--- .../remote/common/remote.contribution.ts | 27 ------ .../contrib/scm/browser/scm.contribution.ts | 21 ++--- .../contrib/scm/browser/scmViewlet.ts | 8 +- src/vs/workbench/contrib/scm/common/scm.ts | 4 - .../search/browser/search.contribution.ts | 26 +++--- .../contrib/search/browser/searchViewlet.ts | 17 ---- .../services/search/common/search.ts | 7 -- .../test/browser/parts/views/views.test.ts | 2 +- 24 files changed, 253 insertions(+), 300 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 61b092a328f..e721bb24267 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -22,14 +22,13 @@ import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug'; import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/common/remote.contribution'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; -import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction, Viewlet } from 'vs/workbench/browser/viewlet'; +import { ViewletRegistry, Extensions as ViewletExtensions, ShowViewletAction } from 'vs/workbench/browser/viewlet'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -313,7 +312,6 @@ class ViewsExtensionHandler implements IWorkbenchContribution { if (!viewContainer) { - viewContainer = this.viewContainersRegistry.registerViewContainer({ id, hideIfEmpty: true, name: title, extensionId }, ViewContainerLocation.Sidebar); class CustomViewPaneContainer extends ViewPaneContainer { constructor( @@ -331,34 +329,13 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } } - // Register a viewlet - class CustomViewlet extends Viewlet { - constructor( - @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITelemetryService telemetryService: ITelemetryService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IStorageService storageService: IStorageService, - @IEditorService editorService: IEditorService, - @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService - ) { - super(id, instantiationService.createInstance(CustomViewPaneContainer), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); - } - } - - const viewletDescriptor = ViewletDescriptor.create( - CustomViewlet, + viewContainer = this.viewContainersRegistry.registerViewContainer({ id, - title, - undefined, - order, - icon - ); - - Registry.as(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor); + name: title, extensionId, + ctorDescriptor: { ctor: CustomViewPaneContainer }, + hideIfEmpty: true, + icon, + }, ViewContainerLocation.Sidebar); // Register Action to Open Viewlet class OpenCustomViewletAction extends ShowViewletAction { @@ -432,8 +409,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution { const order = ExtensionIdentifier.equals(extension.description.identifier, container.extensionId) ? index + 1 - : container.orderDelegate - ? container.orderDelegate.getOrder(item.group) + : container.viewOrderDelegate + ? container.viewOrderDelegate.getOrder(item.group) : undefined; const viewDescriptor = { diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 083c0dfe25c..3400b61747e 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -243,7 +243,7 @@ interface IViewPaneItem { export class ViewPaneContainer extends Component implements IViewPaneContainer { - private readonly viewContainer: ViewContainer; + readonly viewContainer: ViewContainer; private lastFocusedPane: ViewPane | undefined; private paneItems: IViewPaneItem[] = []; private paneview?: PaneView; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index fef0e3ac714..c14125bea17 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -8,6 +8,21 @@ import * as nls from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor, PaneCompositePanel } from 'vs/workbench/browser/panel'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { Viewlet, ViewletDescriptor, ViewletRegistry, Extensions as ViewletExtensions } from 'vs/workbench/browser/viewlet'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { isString } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; // Configuration (function registerConfiguration(): void { @@ -373,3 +388,74 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio } }); })(); + +// Viewlets & Panels +(function registerViewletsAndPanels(): void { + const registerPanel = (viewContainer: ViewContainer): void => { + class PaneContainerPanel extends PaneCompositePanel { + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService contextService: IWorkspaceContextService + ) { + super(viewContainer.id, (instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.arguments || [])), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + } + } + Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( + PaneContainerPanel, + viewContainer.id, + viewContainer.name, + isString(viewContainer.icon) ? viewContainer.icon : undefined, + viewContainer.order, + viewContainer.focusCommand?.id, + )); + }; + + const registerViewlet = (viewContainer: ViewContainer): void => { + class PaneContainerViewlet extends Viewlet { + constructor( + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IStorageService storageService: IStorageService, + @IEditorService editorService: IEditorService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService + ) { + super(viewContainer.id, (instantiationService as any).createInstance(viewContainer.ctorDescriptor!.ctor, ...(viewContainer.ctorDescriptor!.arguments || [])), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); + } + } + const viewletDescriptor = ViewletDescriptor.create( + PaneContainerViewlet, + viewContainer.id, + viewContainer.name, + isString(viewContainer.icon) ? viewContainer.icon : undefined, + viewContainer.order, + viewContainer.icon instanceof URI ? viewContainer.icon : undefined + ); + + Registry.as(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor); + }; + + const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + viewContainerRegistry.getViewContainers(ViewContainerLocation.Panel).forEach(viewContainer => registerPanel(viewContainer)); + viewContainerRegistry.onDidRegister(({ viewContainer, viewContainerLocation }) => { + switch (viewContainerLocation) { + case ViewContainerLocation.Panel: + registerPanel(viewContainer); + return; + case ViewContainerLocation.Sidebar: + if (viewContainer.ctorDescriptor) { + registerViewlet(viewContainer); + } + return; + } + }); +})(); diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 5508b341928..15ee4827de8 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Command } from 'vs/editor/common/modes'; -import { UriComponents } from 'vs/base/common/uri'; +import { UriComponents, URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; @@ -18,6 +18,7 @@ import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry' import { IAction } from 'vs/base/common/actions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { flatten } from 'vs/base/common/arrays'; +import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; export const FocusedViewContext = new RawContextKey('focusedView', ''); @@ -38,6 +39,14 @@ export interface IViewContainerDescriptor { readonly name: string; + readonly ctorDescriptor: { ctor: new (...args: any[]) => IViewPaneContainer, arguments?: any[] }; + + readonly icon?: string | URI; + + readonly order?: number; + + readonly focusCommand?: { id: string, keybindings?: IKeybindings }; + readonly viewOrderDelegate?: ViewOrderDelegate; readonly hideIfEmpty?: boolean; @@ -96,16 +105,7 @@ interface ViewOrderDelegate { getOrder(group?: string): number | undefined; } -export class ViewContainer { - - protected constructor(private readonly descriptor: IViewContainerDescriptor) { } - - readonly id: string = this.descriptor.id; - readonly name: string = this.descriptor.name; - readonly hideIfEmpty: boolean = !!this.descriptor.hideIfEmpty; - readonly extensionId: ExtensionIdentifier | undefined = this.descriptor.extensionId; - readonly orderDelegate: ViewOrderDelegate | undefined = this.descriptor.viewOrderDelegate; -} +export interface ViewContainer extends IViewContainerDescriptor { } class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry { @@ -127,11 +127,7 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe return existing; } - const viewContainer = new class extends ViewContainer { - constructor() { - super(viewContainerDescriptor); - } - }; + const viewContainer: ViewContainer = { ...viewContainerDescriptor }; const viewContainers = getOrSet(this.viewContainers, viewContainerLocation, []); viewContainers.push(viewContainer); this._onDidRegister.fire({ viewContainer, viewContainerLocation }); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 96c6e477b6f..c6cb16e748d 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -13,14 +13,14 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionRegistryExtensions } from 'vs/workbench/common/actions'; -import { ShowViewletAction, Extensions as ViewletExtensions, ViewletRegistry, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; +import { ShowViewletAction } from 'vs/workbench/browser/viewlet'; import { TogglePanelAction, Extensions as PanelExtensions, PanelRegistry, PanelDescriptor } from 'vs/workbench/browser/panel'; import { BreakpointsView } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IDebugService, VIEWLET_ID, REPL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA, - CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, VIEW_CONTAINER, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, + CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -31,7 +31,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, REVERSE_CONTINUE_ID, STEP_BACK_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; -import { IViewsRegistry, Extensions as ViewExtensions } from 'vs/workbench/common/views'; +import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { isMacintosh } from 'vs/base/common/platform'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; @@ -46,9 +46,9 @@ import { WatchExpressionsView } from 'vs/workbench/contrib/debug/browser/watchEx import { VariablesView } from 'vs/workbench/contrib/debug/browser/variablesView'; import { ClearReplAction, Repl } from 'vs/workbench/contrib/debug/browser/repl'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; -import { DebugViewlet } from 'vs/workbench/contrib/debug/browser/debugViewlet'; import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { DebugViewPaneContainer } from 'vs/workbench/contrib/debug/browser/debugViewlet'; class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; @@ -79,14 +79,13 @@ class OpenDebugPanelAction extends TogglePanelAction { } } -// register viewlet -Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( - DebugViewlet, - VIEWLET_ID, - VIEW_CONTAINER.name, - 'codicon-debug-alt', - 3 -)); +const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ + id: VIEWLET_ID, + name: nls.localize('debugAndRun', "Debug and Run"), + ctorDescriptor: { ctor: DebugViewPaneContainer }, + icon: 'codicon-debug-alt', + order: 3 +}, ViewContainerLocation.Sidebar); const openViewletKb: IKeybindings = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_D @@ -107,12 +106,12 @@ Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor // Register default debug views const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); -viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: { ctor: VariablesView }, order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: { ctor: WatchExpressionsView }, order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: { ctor: CallStackView }, order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: { ctor: BreakpointsView }, order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: StartView.ID, name: StartView.LABEL, ctorDescriptor: { ctor: StartView }, order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], VIEW_CONTAINER); -viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: { ctor: LoadedScriptsView }, order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: { ctor: VariablesView }, order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: { ctor: WatchExpressionsView }, order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: { ctor: CallStackView }, order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: { ctor: BreakpointsView }, order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer); +viewsRegistry.registerViews([{ id: StartView.ID, name: StartView.LABEL, ctorDescriptor: { ctor: StartView }, order: 10, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer); +viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: { ctor: LoadedScriptsView }, order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer); registerCommands(); diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 562559bbd05..5afeb4290cb 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -32,26 +32,8 @@ import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryAc import { INotificationService } from 'vs/platform/notification/common/notification'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { Viewlet } from 'vs/workbench/browser/viewlet'; import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; -// Register a lightweight viewlet responsible for making the container -export class DebugViewlet extends Viewlet { - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IStorageService protected storageService: IStorageService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @IExtensionService protected extensionService: IExtensionService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService, - @IConfigurationService protected configurationService: IConfigurationService - ) { - super(VIEWLET_ID, instantiationService.createInstance(DebugViewPaneContainer), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); - } -} - export class DebugViewPaneContainer extends ViewPaneContainer { private startDebugActionViewItem: StartDebugActionViewItem | undefined; diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 8cb55418bb9..41ea3f8b2ca 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -24,11 +24,8 @@ import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; -import { Registry } from 'vs/platform/registry/common/platform'; export const VIEWLET_ID = 'workbench.view.debug'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: nls.localize('debugAndRun', "Debug and Run") }, ViewContainerLocation.Sidebar); export const VARIABLES_VIEW_ID = 'workbench.debug.variablesView'; export const WATCH_VIEW_ID = 'workbench.debug.watchExpressionsView'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 49b9286c12b..4bebc6eb61f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { VIEWLET_ID, IExtensionsWorkbenchService, VIEW_CONTAINER } from 'vs/workbench/contrib/extensions/common/extensions'; +import { VIEWLET_ID, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, @@ -22,9 +22,8 @@ import { EnableAllAction, EnableAllWorkspaceAction, DisableAllAction, DisableAllWorkspaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; -import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; -import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewlet } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; +import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; @@ -44,6 +43,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { RemoteExtensionsInstaller } from 'vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller'; import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; +import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -75,17 +75,15 @@ Registry.as(EditorExtensions.Editors).registerEditor( new SyncDescriptor(ExtensionsInput) ]); -// Viewlet -const viewletDescriptor = ViewletDescriptor.create( - ExtensionsViewlet, - VIEWLET_ID, - VIEW_CONTAINER.name, - 'codicon-extensions', - 4 -); +Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( + { + id: VIEWLET_ID, + name: localize('extensions', "Extensions"), + ctorDescriptor: { ctor: ExtensionsViewPaneContainer }, + icon: 'codicon-extensions', + order: 4 + }, ViewContainerLocation.Sidebar); -Registry.as(ViewletExtensions.Viewlets) - .registerViewlet(viewletDescriptor); // Global actions const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 72c39d72b82..eb05d9ef19a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -18,7 +18,7 @@ import { append, $, addClass, toggleClass, Dimension } from 'vs/base/browser/dom import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, AutoUpdateConfigurationKey, ShowRecommendationsOnlyOnDemandKey, CloseExtensionDetailsOnViewChangeKey, VIEW_CONTAINER } from '../common/extensions'; +import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, AutoUpdateConfigurationKey, ShowRecommendationsOnlyOnDemandKey, CloseExtensionDetailsOnViewChangeKey } from '../common/extensions'; import { ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction, ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction, @@ -35,7 +35,7 @@ import Severity from 'vs/base/common/severity'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -57,7 +57,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { RemoteNameContext } from 'vs/workbench/browser/contextkeys'; import { ILabelService } from 'vs/platform/label/common/label'; import { MementoObject } from 'vs/workbench/common/memento'; -import { Viewlet } from 'vs/workbench/browser/viewlet'; const NonEmptyWorkspaceContext = new RawContextKey('nonEmptyWorkspace', false); const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); @@ -88,10 +87,13 @@ const viewIdNameMappings: { [id: string]: string } = { export class ExtensionsViewletViewsContribution implements IWorkbenchContribution { + private readonly container: ViewContainer; + constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @ILabelService private readonly labelService: ILabelService, ) { + this.container = Registry.as(Extensions.ViewContainersRegistry).get(VIEWLET_ID)!; this.registerViews(); } @@ -117,7 +119,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.remoteExtensionManagementServer)); } - Registry.as(Extensions.ViewsRegistry).registerViews(viewDescriptors, VIEW_CONTAINER); + Registry.as(Extensions.ViewsRegistry).registerViews(viewDescriptors, this.container); } // View used for any kind of searching @@ -314,22 +316,6 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio } -export class ExtensionsViewlet extends Viewlet { - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IStorageService protected storageService: IStorageService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @IExtensionService protected extensionService: IExtensionService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService, - @IConfigurationService protected configurationService: IConfigurationService - ) { - super(VIEWLET_ID, instantiationService.createInstance(ExtensionsViewPaneContainer), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); - } -} - export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IExtensionsViewPaneContainer { private readonly _onSearchChange: Emitter = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index f7dd43dadca..8ed7a6817c4 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -14,12 +14,9 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; -import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { localize } from 'vs/nls'; +import { IAction } from 'vs/base/common/actions'; export const VIEWLET_ID = 'workbench.view.extensions'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('extensions', "Extensions") }, ViewContainerLocation.Sidebar); export const EXTENSIONS_CONFIG = '.vscode/extensions.json'; @@ -144,3 +141,12 @@ export class ExtensionContainers extends Disposable { } } } + +export interface IExtensionMenuAction extends IAction { + run(context: IExtensionMenuActionContext): Promise; +} + +export interface IExtensionMenuActionContext { + id: string; + packageJSON: IExtensionManifest; +} diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index a96695b8c01..94d591094b6 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/explorerviewlet'; import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; -import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration, OpenEditorsVisibleContext, VIEW_CONTAINER } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration, OpenEditorsVisibleContext } from 'vs/workbench/contrib/files/common/files'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { ExplorerView } from 'vs/workbench/contrib/files/browser/views/explorerView'; @@ -20,7 +20,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -33,7 +33,6 @@ import { ViewPane, ViewPaneContainer } from 'vs/workbench/browser/parts/views/vi import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { Viewlet } from 'vs/workbench/browser/viewlet'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -146,22 +145,6 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor } } -export class ExplorerViewlet extends Viewlet { - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IStorageService protected storageService: IStorageService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @IExtensionService protected extensionService: IExtensionService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService, - @IConfigurationService protected configurationService: IConfigurationService - ) { - super(VIEWLET_ID, instantiationService.createInstance(ExplorerViewPaneContainer), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); - } -} - export class ExplorerViewPaneContainer extends ViewPaneContainer { private static readonly EXPLORER_VIEWS_STATE = 'workbench.explorer.views.state'; @@ -257,3 +240,14 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { } } } + +/** + * Explorer viewlet container. + */ +export const VIEW_CONTAINER: ViewContainer = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer({ + id: VIEWLET_ID, + name: localize('explore', "Explorer"), + ctorDescriptor: { ctor: ExplorerViewPaneContainer }, + icon: 'codicon-files', + order: 0 +}, ViewContainerLocation.Sidebar); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 3912c5b7350..edb3d92e1c6 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI, UriComponents } from 'vs/base/common/uri'; -import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet'; +import { ViewletRegistry, Extensions as ViewletExtensions, ShowViewletAction } from 'vs/workbench/browser/viewlet'; import * as nls from 'vs/nls'; import { sep } from 'vs/base/common/path'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; @@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; -import { VIEWLET_ID, VIEW_CONTAINER, SortOrder, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, SortOrder, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; @@ -25,7 +25,7 @@ import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry' import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; -import { ExplorerViewletViewsContribution, ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; +import { ExplorerViewletViewsContribution } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -73,15 +73,6 @@ class FileUriLabelContribution implements IWorkbenchContribution { } } -// Register Viewlet -Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( - ExplorerViewlet, - VIEWLET_ID, - VIEW_CONTAINER.name, - 'codicon-files', - 0 -)); - registerSingleton(IExplorerService, ExplorerService, true); Registry.as(ViewletExtensions.Viewlets).setDefaultViewletId(VIEWLET_ID); diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index f445cadae83..c2bdc69cf7b 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -16,26 +16,19 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, IEditableData, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IEditableData } from 'vs/workbench/common/views'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { once } from 'vs/base/common/functional'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { localize } from 'vs/nls'; /** * Explorer viewlet id. */ export const VIEWLET_ID = 'workbench.view.explorer'; -/** - * Explorer viewlet container. - */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('explore', "Explorer") }, ViewContainerLocation.Sidebar); - export interface IExplorerService { _serviceBrand: undefined; readonly roots: ExplorerItem[]; diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 5c42c1d979b..1baf6e1c4f9 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -14,7 +14,7 @@ import { localize } from 'vs/nls'; import { Marker, RelatedInformation } from 'vs/workbench/contrib/markers/browser/markersModel'; import { MarkersView, getMarkersView } from 'vs/workbench/contrib/markers/browser/markersView'; import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction } from 'vs/platform/actions/common/actions'; -import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor, PaneCompositePanel, TogglePanelAction } from 'vs/workbench/browser/panel'; +import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { Registry } from 'vs/platform/registry/common/platform'; import { ShowProblemsPanelAction } from 'vs/workbench/contrib/markers/browser/markersViewActions'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; @@ -30,13 +30,6 @@ import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatus import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -100,30 +93,6 @@ Registry.as(Extensions.Configuration).registerConfigurat } }); - -// markers view container -const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: Constants.MARKERS_PANEL_ID, name: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, ViewContainerLocation.Panel); -Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ - id: Constants.MARKERS_VIEW_ID, - name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, - canToggleVisibility: false, - ctorDescriptor: { ctor: MarkersView }, -}], VIEW_CONTAINER); - -// markers panel -class MarkersPanel extends PaneCompositePanel { - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IStorageService storageService: IStorageService, - @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - @IWorkspaceContextService contextService: IWorkspaceContextService) { - super(Constants.MARKERS_PANEL_ID, instantiationService.createInstance(ViewPaneContainer, Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_STORAGE_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }), - telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); - } -} class ToggleMarkersPanelAction extends TogglePanelAction { public static readonly ID = 'workbench.actions.view.problems'; @@ -136,14 +105,25 @@ class ToggleMarkersPanelAction extends TogglePanelAction { super(id, label, Constants.MARKERS_PANEL_ID, panelService, layoutService); } } -Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( - MarkersPanel, - Constants.MARKERS_PANEL_ID, - VIEW_CONTAINER.name, - 'markersPanel', - 10, - ToggleMarkersPanelAction.ID -)); + +// markers view container +const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ + id: Constants.MARKERS_PANEL_ID, + name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, + ctorDescriptor: { ctor: ViewPaneContainer, arguments: [Constants.MARKERS_PANEL_ID, Constants.MARKERS_PANEL_STORAGE_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }] }, + focusCommand: { + id: ToggleMarkersPanelAction.ID, keybindings: { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_M + } + } +}, ViewContainerLocation.Panel); + +Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews([{ + id: Constants.MARKERS_VIEW_ID, + name: Messages.MARKERS_PANEL_TITLE_PROBLEMS, + canToggleVisibility: false, + ctorDescriptor: { ctor: MarkersView }, +}], VIEW_CONTAINER); // workbench const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index dc855f0f9d6..d3d712fe18a 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -6,10 +6,10 @@ import { localize } from 'vs/nls'; import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { OutlinePane } from './outlinePane'; -import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/common/files'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymbols/outline'; +import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/browser/explorerViewlet'; // import './outlineNavigation'; diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 0dcedfacfa4..7db68a7cb13 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -17,15 +17,15 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { VIEWLET_ID, VIEW_CONTAINER } from 'vs/workbench/contrib/remote/common/remote.contribution'; +import { VIEWLET_ID } from 'vs/workbench/contrib/remote/common/remote.contribution'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewDescriptor, IViewsRegistry, Extensions } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction, Viewlet } from 'vs/workbench/browser/viewlet'; +import { ShowViewletAction, Viewlet } from 'vs/workbench/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; @@ -496,7 +496,7 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer implements public getActionViewItem(action: Action): IActionViewItem | undefined { if (action.id === SwitchRemoteAction.ID) { - return this.instantiationService.createInstance(SwitchRemoteViewItem, action, SwitchRemoteViewItem.createOptionItems(Registry.as(Extensions.ViewsRegistry).getViews(VIEW_CONTAINER), this.contextKeyService)); + return this.instantiationService.createInstance(SwitchRemoteViewItem, action, SwitchRemoteViewItem.createOptionItems(Registry.as(Extensions.ViewsRegistry).getViews(this.viewContainer), this.contextKeyService)); } return super.getActionViewItem(action); @@ -527,19 +527,41 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer implements if (this.environmentService.configuration.remoteAuthority && !this.tunnelPanelDescriptor && viewEnabled) { this.tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService), this.environmentService); const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - viewsRegistry.registerViews([this.tunnelPanelDescriptor!], VIEW_CONTAINER); + viewsRegistry.registerViews([this.tunnelPanelDescriptor!], this.viewContainer); } return panels; } } -Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( - RemoteViewlet, - VIEWLET_ID, - VIEW_CONTAINER.name, - 'codicon-remote-explorer', - 4 -)); +Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( + { + id: VIEWLET_ID, + name: nls.localize('remote.explorer', "Remote Explorer"), + ctorDescriptor: { ctor: RemoteViewPaneContainer }, + hideIfEmpty: true, + viewOrderDelegate: { + getOrder: (group?: string) => { + if (!group) { + return; + } + + let matches = /^targets@(\d+)$/.exec(group); + if (matches) { + return -1000; + } + + matches = /^details(@(\d+))?$/.exec(group); + + if (matches) { + return -500; + } + + return; + } + }, + icon: 'codicon-remote-explorer', + order: 4 + }, ViewContainerLocation.Sidebar); class OpenRemoteViewletAction extends ShowViewletAction { diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 18989f3dca1..bd643628670 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -16,35 +16,8 @@ import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/s import { localize } from 'vs/nls'; import { joinPath } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation } from 'vs/workbench/common/views'; export const VIEWLET_ID = 'workbench.view.remote'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( - { - id: VIEWLET_ID, - name: localize('remote.explorer', "Remote Explorer"), - hideIfEmpty: true, - viewOrderDelegate: { - getOrder: (group?: string) => { - if (!group) { - return; - } - - let matches = /^targets@(\d+)$/.exec(group); - if (matches) { - return -1000; - } - - matches = /^details(@(\d+))?$/.exec(group); - - if (matches) { - return -500; - } - - return; - } - } - }, ViewContainerLocation.Sidebar); export class LabelContribution implements IWorkbenchContribution { constructor( diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 8300e45cb3a..73e1959661d 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -7,14 +7,13 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; -import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet'; -import { VIEWLET_ID, VIEW_CONTAINER, ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; +import { ShowViewletAction } from 'vs/workbench/browser/viewlet'; +import { VIEWLET_ID, ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { SCMStatusController } from './activity'; -import { SCMViewlet } from 'vs/workbench/contrib/scm/browser/scmViewlet'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -24,6 +23,8 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { SCMService } from 'vs/workbench/contrib/scm/common/scmService'; +import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; +import { SCMViewPaneContainer } from 'vs/workbench/contrib/scm/browser/scmViewlet'; class OpenSCMViewletAction extends ShowViewletAction { @@ -38,13 +39,13 @@ class OpenSCMViewletAction extends ShowViewletAction { Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(DirtyDiffWorkbenchController, LifecyclePhase.Restored); -Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( - SCMViewlet, - VIEWLET_ID, - VIEW_CONTAINER.name, - 'codicon-source-control', - 2 -)); +Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ + id: VIEWLET_ID, + name: localize('source control', "Source Control"), + ctorDescriptor: { ctor: SCMViewPaneContainer }, + icon: 'codicon-source-control', + order: 2 +}, ViewContainerLocation.Sidebar); Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(SCMStatusController, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 95fbff130af..b01fe0e08d7 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { append, $, toggleClass, addClasses } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { VIEWLET_ID, ISCMService, ISCMRepository, VIEW_CONTAINER } from 'vs/workbench/contrib/scm/common/scm'; +import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -124,7 +124,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode this.message = $('.empty-message', { tabIndex: 0 }, localize('no open repo', "No source control providers registered.")); const viewsRegistry = Registry.as(Extensions.ViewsRegistry); - viewsRegistry.registerViews([new MainPaneDescriptor(this)], VIEW_CONTAINER); + viewsRegistry.registerViews([new MainPaneDescriptor(this)], this.viewContainer); this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('scm.alwaysShowProviders') && configurationService.getValue('scm.alwaysShowProviders')) { @@ -153,7 +153,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode this._repositories.push(repository); const viewDescriptor = new RepositoryViewDescriptor(repository, false); - Registry.as(Extensions.ViewsRegistry).registerViews([viewDescriptor], VIEW_CONTAINER); + Registry.as(Extensions.ViewsRegistry).registerViews([viewDescriptor], this.viewContainer); this.viewDescriptors.push(viewDescriptor); this._onDidSplice.fire({ index, deleteCount: 0, elements: [repository] }); @@ -169,7 +169,7 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode return; } - Registry.as(Extensions.ViewsRegistry).deregisterViews([this.viewDescriptors[index]], VIEW_CONTAINER); + Registry.as(Extensions.ViewsRegistry).deregisterViews([this.viewDescriptors[index]], this.viewContainer); this._repositories.splice(index, 1); this.viewDescriptors.splice(index, 1); diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 861a297c815..cf63ad255d9 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -9,12 +9,8 @@ import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Command } from 'vs/editor/common/modes'; import { ISequence } from 'vs/base/common/sequence'; -import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { localize } from 'vs/nls'; export const VIEWLET_ID = 'workbench.view.scm'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('source control', "Source Control"), }, ViewContainerLocation.Sidebar); export interface IBaselineResourceProvider { getBaselineResource(resource: URI): Promise; diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 418fd0257d0..6a83637408e 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -31,10 +31,9 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as PanelExtensions, PanelDescriptor, PanelRegistry } from 'vs/workbench/browser/panel'; import { defaultQuickOpenContextKey } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; -import { Extensions as ViewletExtensions, ViewletDescriptor, ViewletRegistry } from 'vs/workbench/browser/viewlet'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Extensions as ViewExtensions, IViewsRegistry } from 'vs/workbench/common/views'; +import { Extensions as ViewExtensions, IViewsRegistry, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition, IExplorerService, VIEWLET_ID as VIEWLET_ID_FILES } from 'vs/workbench/contrib/files/common/files'; import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; @@ -43,7 +42,6 @@ import { registerContributions as replaceContributions } from 'vs/workbench/cont import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction, RerunEditorSearchWithContextAction, ExpandAllAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView'; -import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { getWorkspaceSymbols } from 'vs/workbench/contrib/search/common/search'; @@ -51,12 +49,13 @@ import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contri import { FileMatchOrMatch, ISearchWorkbenchService, RenderableMatch, SearchWorkbenchService, FileMatch, Match, FolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ISearchConfiguration, ISearchConfigurationProperties, PANEL_ID, VIEWLET_ID, VIEW_ID, VIEW_CONTAINER, SearchSortOrder } from 'vs/workbench/services/search/common/search'; +import { ISearchConfiguration, ISearchConfigurationProperties, PANEL_ID, VIEWLET_ID, VIEW_ID, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { assertType } from 'vs/base/common/types'; +import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); registerSingleton(ISearchHistoryService, SearchHistoryService, true); @@ -505,13 +504,14 @@ class ShowAllSymbolsAction extends Action { } } -Registry.as(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( - SearchViewlet, - VIEWLET_ID, - VIEW_CONTAINER.name, - 'codicon-search', - 1 -)); +const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ + id: VIEWLET_ID, + name: nls.localize('name', "Search"), + ctorDescriptor: { ctor: SearchViewPaneContainer }, + hideIfEmpty: true, + icon: 'codicon-search', + order: 1 +}, ViewContainerLocation.Sidebar); Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( SearchPanel, @@ -532,7 +532,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { const updateSearchViewLocation = (open: boolean) => { const config = configurationService.getValue(); if (config.search.location === 'panel') { - viewsRegistry.deregisterViews(viewsRegistry.getViews(VIEW_CONTAINER), VIEW_CONTAINER); + viewsRegistry.deregisterViews(viewsRegistry.getViews(viewContainer), viewContainer); Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( SearchPanel, PANEL_ID, @@ -545,7 +545,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { } } else { Registry.as(PanelExtensions.Panels).deregisterPanel(PANEL_ID); - viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: { ctor: SearchView, arguments: [SearchViewPosition.SideBar] }, canToggleVisibility: false }], VIEW_CONTAINER); + viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: { ctor: SearchView, arguments: [SearchViewPosition.SideBar] }, canToggleVisibility: false }], viewContainer); if (open) { viewletService.openViewlet(VIEWLET_ID); } diff --git a/src/vs/workbench/contrib/search/browser/searchViewlet.ts b/src/vs/workbench/contrib/search/browser/searchViewlet.ts index 827ab6cc96b..592a3153e67 100644 --- a/src/vs/workbench/contrib/search/browser/searchViewlet.ts +++ b/src/vs/workbench/contrib/search/browser/searchViewlet.ts @@ -14,26 +14,9 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; -import { Viewlet } from 'vs/workbench/browser/viewlet'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -export class SearchViewlet extends Viewlet { - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IStorageService protected storageService: IStorageService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @IExtensionService protected extensionService: IExtensionService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService, - @IConfigurationService protected configurationService: IConfigurationService - ) { - super(VIEWLET_ID, instantiationService.createInstance(SearchViewPaneContainer), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); - } -} - export class SearchViewPaneContainer extends ViewPaneContainer { constructor( diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 5e7d4a890c6..5d64c576132 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -16,17 +16,10 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { Event } from 'vs/base/common/event'; import { relative } from 'vs/base/common/path'; -import { Extensions as ViewContainerExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { localize } from 'vs/nls'; export const VIEWLET_ID = 'workbench.view.search'; export const PANEL_ID = 'workbench.view.search'; export const VIEW_ID = 'workbench.view.search'; -/** - * Search viewlet container. - */ -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, name: localize('name', "Search"), hideIfEmpty: true }, ViewContainerLocation.Sidebar); export const ISearchService = createDecorator('searchService'); diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index 5b4b006e769..794ff35443b 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -15,7 +15,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import sinon = require('sinon'); -const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'test', name: 'test' }, ViewContainerLocation.Sidebar); +const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: { ctor: {} } }, ViewContainerLocation.Sidebar); const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); class ViewDescriptorSequence { From bbafd3eb7a7aa826f04c579f0f1991d4bb9fdd49 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 20 Dec 2019 16:15:57 +0100 Subject: [PATCH 199/241] #87246 fix compilation --- src/vs/workbench/contrib/remote/browser/remote.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 7db68a7cb13..7fca6c9c0c0 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -464,9 +464,9 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer implements const viewsRegistry = Registry.as(Extensions.ViewsRegistry); if (this.helpInformation.length) { - viewsRegistry.registerViews([this.helpPanelDescriptor], VIEW_CONTAINER); + viewsRegistry.registerViews([this.helpPanelDescriptor], this.viewContainer); } else { - viewsRegistry.deregisterViews([this.helpPanelDescriptor], VIEW_CONTAINER); + viewsRegistry.deregisterViews([this.helpPanelDescriptor], this.viewContainer); } }); } From c97c457d0a57d48afbdea3adcaeb1231e899471f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 20 Dec 2019 16:19:20 +0100 Subject: [PATCH 200/241] :lipstick: --- .../workbench/contrib/remote/browser/remote.ts | 18 +----------------- .../contrib/scm/browser/scmViewlet.ts | 17 ----------------- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 7fca6c9c0c0..3edc8581464 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -25,7 +25,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ShowViewletAction, Viewlet } from 'vs/workbench/browser/viewlet'; +import { ShowViewletAction } from 'vs/workbench/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; @@ -416,22 +416,6 @@ class HelpPanelDescriptor implements IViewDescriptor { } } -export class RemoteViewlet extends Viewlet { - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IStorageService protected storageService: IStorageService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @IExtensionService protected extensionService: IExtensionService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService, - @IConfigurationService protected configurationService: IConfigurationService - ) { - super(VIEWLET_ID, instantiationService.createInstance(RemoteViewPaneContainer), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); - } -} - export class RemoteViewPaneContainer extends FilterViewPaneContainer implements IViewModel { private helpPanelDescriptor = new HelpPanelDescriptor(this); helpInformation: HelpInformation[] = []; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index b01fe0e08d7..adc0b970af7 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -31,7 +31,6 @@ import { nextTick } from 'vs/base/common/process'; import { RepositoryPane, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPane'; import { MainPaneDescriptor, MainPane } from 'vs/workbench/contrib/scm/browser/mainPane'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { Viewlet } from 'vs/workbench/browser/viewlet'; export interface ISpliceEvent { index: number; @@ -51,22 +50,6 @@ export interface IViewModel { readonly onDidChangeVisibility: Event; } -export class SCMViewlet extends Viewlet { - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IStorageService protected storageService: IStorageService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @IExtensionService protected extensionService: IExtensionService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService, - @IConfigurationService protected configurationService: IConfigurationService - ) { - super(VIEWLET_ID, instantiationService.createInstance(SCMViewPaneContainer), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService); - } -} - export class SCMViewPaneContainer extends ViewPaneContainer implements IViewModel { private static readonly STATE_KEY = 'workbench.scm.views.state'; From 18bab626b9d54e836412b7a6aae620645da0422c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 20 Dec 2019 16:27:17 +0100 Subject: [PATCH 201/241] [html] issues caused by HTML grammar associating '<' with JS. Fixes #86998 --- extensions/html/build/update-grammar.js | 44 + extensions/html/package.json | 2 +- extensions/html/syntaxes/html.tmLanguage.json | 12 +- .../colorize-fixtures/test-embedding.html | 6 + .../test/colorize-results/12750_html.json | 4 +- .../test/colorize-results/25920_html.json | 2 +- .../colorize-results/test-embedding_html.json | 1003 +++++++++++++++++ .../html/test/colorize-results/test_html.json | 10 +- .../test/colorize-results/test_md.json | 2 +- .../colorize-results/issue-28354_php.json | 5 +- 10 files changed, 1072 insertions(+), 18 deletions(-) create mode 100644 extensions/html/build/update-grammar.js create mode 100644 extensions/html/test/colorize-fixtures/test-embedding.html create mode 100644 extensions/html/test/colorize-results/test-embedding_html.json diff --git a/extensions/html/build/update-grammar.js b/extensions/html/build/update-grammar.js new file mode 100644 index 00000000000..3277cb16cca --- /dev/null +++ b/extensions/html/build/update-grammar.js @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +'use strict'; + +var updateGrammar = require('../../../build/npm/update-grammar'); + +function patchGrammar(grammar) { + let patchCount = 0; + + let visit = function (rule, parent) { + if (rule.name === 'source.js' || rule.name === 'source.css') { + if (parent.parent && parent.parent.property === 'endCaptures') { + rule.name = rule.name + '-ignored-vscode'; + patchCount++; + } + } + for (let property in rule) { + let value = rule[property]; + if (typeof value === 'object') { + visit(value, { node: rule, property: property, parent: parent }); + } + } + }; + + let repository = grammar.repository; + for (let key in repository) { + visit(repository[key], { node: repository, property: key, parent: undefined }); + } + if (patchCount !== 6) { + console.warn(`Expected to patch 6 occurrences of source.js & source.css: Was ${patchCount}`); + } + + + return grammar; +} + +const tsGrammarRepo = 'textmate/html.tmbundle'; +const grammarPath = 'Syntaxes/HTML.plist'; +updateGrammar.update(tsGrammarRepo, grammarPath, './syntaxes/html.tmLanguage.json', grammar => patchGrammar(grammar)); + + diff --git a/extensions/html/package.json b/extensions/html/package.json index 1b28ff2e5eb..065eb26669b 100644 --- a/extensions/html/package.json +++ b/extensions/html/package.json @@ -9,7 +9,7 @@ "vscode": "0.10.x" }, "scripts": { - "update-grammar": "node ../../build/npm/update-grammar.js textmate/html.tmbundle Syntaxes/HTML.plist ./syntaxes/html.tmLanguage.json Syntaxes/HTML%20%28Derivative%29.tmLanguage ./syntaxes/html-derivative.tmLanguage.json" + "update-grammar": "node ./build/update-grammar.js" }, "contributes": { "languages": [ diff --git a/extensions/html/syntaxes/html.tmLanguage.json b/extensions/html/syntaxes/html.tmLanguage.json index a071d90a9e3..1e1c85c899f 100644 --- a/extensions/html/syntaxes/html.tmLanguage.json +++ b/extensions/html/syntaxes/html.tmLanguage.json @@ -108,7 +108,7 @@ "name": "punctuation.definition.string.end.html" }, "1": { - "name": "source.css" + "name": "source.css-ignored-vscode" } }, "name": "string.quoted.double.html", @@ -132,7 +132,7 @@ "name": "punctuation.definition.string.end.html" }, "1": { - "name": "source.css" + "name": "source.css-ignored-vscode" } }, "name": "string.quoted.single.html", @@ -207,7 +207,7 @@ "name": "punctuation.definition.string.end.html" }, "1": { - "name": "source.js" + "name": "source.js-ignored-vscode" } }, "name": "string.quoted.double.html", @@ -265,7 +265,7 @@ "name": "punctuation.definition.string.end.html" }, "1": { - "name": "source.js" + "name": "source.js-ignored-vscode" } }, "name": "string.quoted.single.html", @@ -1785,7 +1785,7 @@ "name": "punctuation.definition.tag.begin.html" }, "2": { - "name": "source.css" + "name": "source.css-ignored-vscode" }, "3": { "name": "entity.name.tag.html" @@ -1892,7 +1892,7 @@ "name": "punctuation.definition.tag.begin.html" }, "2": { - "name": "source.js" + "name": "source.js-ignored-vscode" } }, "patterns": [ diff --git a/extensions/html/test/colorize-fixtures/test-embedding.html b/extensions/html/test/colorize-fixtures/test-embedding.html new file mode 100644 index 00000000000..87f92b6264b --- /dev/null +++ b/extensions/html/test/colorize-fixtures/test-embedding.html @@ -0,0 +1,6 @@ + + + +
+
+
diff --git a/extensions/html/test/colorize-results/12750_html.json b/extensions/html/test/colorize-results/12750_html.json index c70c7afdb8b..a4f0184b51c 100644 --- a/extensions/html/test/colorize-results/12750_html.json +++ b/extensions/html/test/colorize-results/12750_html.json @@ -210,7 +210,7 @@ }, { "c": "<", - "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js-ignored-vscode", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -397,7 +397,7 @@ }, { "c": "<", - "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js-ignored-vscode", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", diff --git a/extensions/html/test/colorize-results/25920_html.json b/extensions/html/test/colorize-results/25920_html.json index 4f4b04e21f5..73c94747231 100644 --- a/extensions/html/test/colorize-results/25920_html.json +++ b/extensions/html/test/colorize-results/25920_html.json @@ -518,7 +518,7 @@ }, { "c": "<", - "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js-ignored-vscode", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", diff --git a/extensions/html/test/colorize-results/test-embedding_html.json b/extensions/html/test/colorize-results/test-embedding_html.json new file mode 100644 index 00000000000..1931950a02c --- /dev/null +++ b/extensions/html/test/colorize-results/test-embedding_html.json @@ -0,0 +1,1003 @@ +[ + { + "c": "<", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.start.html punctuation.definition.tag.begin.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "script", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.start.html entity.name.tag.html", + "r": { + "dark_plus": "entity.name.tag: #569CD6", + "light_plus": "entity.name.tag: #800000", + "dark_vs": "entity.name.tag: #569CD6", + "light_vs": "entity.name.tag: #800000", + "hc_black": "entity.name.tag: #569CD6" + } + }, + { + "c": ">", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.start.html punctuation.definition.tag.end.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "console", + "t": "text.html.derivative meta.embedded.block.html source.js meta.function-call.js support.class.console.js", + "r": { + "dark_plus": "support.class: #4EC9B0", + "light_plus": "support.class: #267F99", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "support.class: #4EC9B0" + } + }, + { + "c": ".", + "t": "text.html.derivative meta.embedded.block.html source.js meta.function-call.js punctuation.accessor.js", + "r": { + "dark_plus": "meta.embedded: #D4D4D4", + "light_plus": "meta.embedded: #000000", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": "log", + "t": "text.html.derivative meta.embedded.block.html source.js meta.function-call.js support.function.console.js", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "text.html.derivative meta.embedded.block.html source.js meta.brace.round.js", + "r": { + "dark_plus": "meta.embedded: #D4D4D4", + "light_plus": "meta.embedded: #000000", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": "'", + "t": "text.html.derivative meta.embedded.block.html source.js string.quoted.single.js punctuation.definition.string.begin.js", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "x", + "t": "text.html.derivative meta.embedded.block.html source.js string.quoted.single.js", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "'", + "t": "text.html.derivative meta.embedded.block.html source.js string.quoted.single.js punctuation.definition.string.end.js", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ")", + "t": "text.html.derivative meta.embedded.block.html source.js meta.brace.round.js", + "r": { + "dark_plus": "meta.embedded: #D4D4D4", + "light_plus": "meta.embedded: #000000", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": "<", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js-ignored-vscode", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "/", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "script", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html entity.name.tag.html", + "r": { + "dark_plus": "entity.name.tag: #569CD6", + "light_plus": "entity.name.tag: #800000", + "dark_vs": "entity.name.tag: #569CD6", + "light_vs": "entity.name.tag: #800000", + "hc_black": "entity.name.tag: #569CD6" + } + }, + { + "c": ">", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.end.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "<", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.style.start.html punctuation.definition.tag.begin.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "style", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.style.start.html entity.name.tag.html", + "r": { + "dark_plus": "entity.name.tag: #569CD6", + "light_plus": "entity.name.tag: #800000", + "dark_vs": "entity.name.tag: #569CD6", + "light_vs": "entity.name.tag: #800000", + "hc_black": "entity.name.tag: #569CD6" + } + }, + { + "c": ">", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.style.start.html punctuation.definition.tag.end.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "test", + "t": "text.html.derivative meta.embedded.block.html source.css meta.selector.css", + "r": { + "dark_plus": "meta.embedded: #D4D4D4", + "light_plus": "meta.embedded: #000000", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": " ", + "t": "text.html.derivative meta.embedded.block.html source.css", + "r": { + "dark_plus": "meta.embedded: #D4D4D4", + "light_plus": "meta.embedded: #000000", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": "{", + "t": "text.html.derivative meta.embedded.block.html source.css meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", + "r": { + "dark_plus": "meta.embedded: #D4D4D4", + "light_plus": "meta.embedded: #000000", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": " ", + "t": "text.html.derivative meta.embedded.block.html source.css meta.property-list.css", + "r": { + "dark_plus": "meta.embedded: #D4D4D4", + "light_plus": "meta.embedded: #000000", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": "display", + "t": "text.html.derivative meta.embedded.block.html source.css meta.property-list.css meta.property-name.css support.type.property-name.css", + "r": { + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #FF0000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #FF0000", + "hc_black": "support.type.property-name: #D4D4D4" + } + }, + { + "c": ":", + "t": "text.html.derivative meta.embedded.block.html source.css meta.property-list.css punctuation.separator.key-value.css", + "r": { + "dark_plus": "meta.embedded: #D4D4D4", + "light_plus": "meta.embedded: #000000", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": " ", + "t": "text.html.derivative meta.embedded.block.html source.css meta.property-list.css", + "r": { + "dark_plus": "meta.embedded: #D4D4D4", + "light_plus": "meta.embedded: #000000", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": "none", + "t": "text.html.derivative meta.embedded.block.html source.css meta.property-list.css meta.property-value.css support.constant.property-value.css", + "r": { + "dark_plus": "support.constant.property-value: #CE9178", + "light_plus": "support.constant.property-value: #0451A5", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "support.constant.property-value: #0451A5", + "hc_black": "support.constant.property-value: #CE9178" + } + }, + { + "c": " ", + "t": "text.html.derivative meta.embedded.block.html source.css meta.property-list.css", + "r": { + "dark_plus": "meta.embedded: #D4D4D4", + "light_plus": "meta.embedded: #000000", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": "}", + "t": "text.html.derivative meta.embedded.block.html source.css meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", + "r": { + "dark_plus": "meta.embedded: #D4D4D4", + "light_plus": "meta.embedded: #000000", + "dark_vs": "meta.embedded: #D4D4D4", + "light_vs": "meta.embedded: #000000", + "hc_black": "meta.embedded: #FFFFFF" + } + }, + { + "c": "<", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.style.end.html punctuation.definition.tag.begin.html source.css-ignored-vscode", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "/", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.style.end.html punctuation.definition.tag.begin.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "style", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.style.end.html entity.name.tag.html", + "r": { + "dark_plus": "entity.name.tag: #569CD6", + "light_plus": "entity.name.tag: #800000", + "dark_vs": "entity.name.tag: #569CD6", + "light_vs": "entity.name.tag: #800000", + "hc_black": "entity.name.tag: #569CD6" + } + }, + { + "c": ">", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.style.end.html punctuation.definition.tag.end.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "<", + "t": "text.html.derivative meta.tag.inline.a.start.html punctuation.definition.tag.begin.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "a", + "t": "text.html.derivative meta.tag.inline.a.start.html entity.name.tag.html", + "r": { + "dark_plus": "entity.name.tag: #569CD6", + "light_plus": "entity.name.tag: #800000", + "dark_vs": "entity.name.tag: #569CD6", + "light_vs": "entity.name.tag: #800000", + "hc_black": "entity.name.tag: #569CD6" + } + }, + { + "c": " ", + "t": "text.html.derivative meta.tag.inline.a.start.html", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "onblur", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.blur.html entity.other.attribute-name.html", + "r": { + "dark_plus": "entity.other.attribute-name: #9CDCFE", + "light_plus": "entity.other.attribute-name: #FF0000", + "dark_vs": "entity.other.attribute-name: #9CDCFE", + "light_vs": "entity.other.attribute-name: #FF0000", + "hc_black": "entity.other.attribute-name: #9CDCFE" + } + }, + { + "c": "=", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.blur.html punctuation.separator.key-value.html", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\"", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.blur.html meta.embedded.line.js string.quoted.double.html punctuation.definition.string.begin.html", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.quoted.double.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.double.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": "doBlur", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.blur.html meta.embedded.line.js string.quoted.double.html source.js meta.function-call.js entity.name.function.js", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.double.html: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "()", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.blur.html meta.embedded.line.js string.quoted.double.html source.js meta.brace.round.js", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.quoted.double.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.double.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\"", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.blur.html meta.embedded.line.js string.quoted.double.html punctuation.definition.string.end.html source.js-ignored-vscode", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.quoted.double.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.double.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": " ", + "t": "text.html.derivative meta.tag.inline.a.start.html", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "onclick", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.click.html entity.other.attribute-name.html", + "r": { + "dark_plus": "entity.other.attribute-name: #9CDCFE", + "light_plus": "entity.other.attribute-name: #FF0000", + "dark_vs": "entity.other.attribute-name: #9CDCFE", + "light_vs": "entity.other.attribute-name: #FF0000", + "hc_black": "entity.other.attribute-name: #9CDCFE" + } + }, + { + "c": "=", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.click.html punctuation.separator.key-value.html", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "'", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.click.html meta.embedded.line.js string.quoted.single.html punctuation.definition.string.begin.html", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.quoted.single.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.single.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": "doClick", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.click.html meta.embedded.line.js string.quoted.single.html source.js meta.function-call.js entity.name.function.js", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.single.html: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "()", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.click.html meta.embedded.line.js string.quoted.single.html source.js meta.brace.round.js", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.quoted.single.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.single.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": "'", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.click.html meta.embedded.line.js string.quoted.single.html punctuation.definition.string.end.html source.js-ignored-vscode", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.quoted.single.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.single.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": " ", + "t": "text.html.derivative meta.tag.inline.a.start.html", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "ondrag", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.drag.html entity.other.attribute-name.html", + "r": { + "dark_plus": "entity.other.attribute-name: #9CDCFE", + "light_plus": "entity.other.attribute-name: #FF0000", + "dark_vs": "entity.other.attribute-name: #9CDCFE", + "light_vs": "entity.other.attribute-name: #FF0000", + "hc_black": "entity.other.attribute-name: #9CDCFE" + } + }, + { + "c": "=", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.drag.html punctuation.separator.key-value.html", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "doDrag", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.drag.html meta.embedded.line.js string.unquoted.html meta.function-call.js entity.name.function.js", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "string: #CE9178", + "light_vs": "string.unquoted.html: #0000FF", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "()", + "t": "text.html.derivative meta.tag.inline.a.start.html meta.attribute.event-handler.drag.html meta.embedded.line.js string.unquoted.html meta.brace.round.js", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.unquoted.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.unquoted.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": ">", + "t": "text.html.derivative meta.tag.inline.a.start.html punctuation.definition.tag.end.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "", + "t": "text.html.derivative meta.tag.inline.a.end.html punctuation.definition.tag.end.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "<", + "t": "text.html.derivative meta.tag.structure.div.start.html punctuation.definition.tag.begin.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "div", + "t": "text.html.derivative meta.tag.structure.div.start.html entity.name.tag.html", + "r": { + "dark_plus": "entity.name.tag: #569CD6", + "light_plus": "entity.name.tag: #800000", + "dark_vs": "entity.name.tag: #569CD6", + "light_vs": "entity.name.tag: #800000", + "hc_black": "entity.name.tag: #569CD6" + } + }, + { + "c": " ", + "t": "text.html.derivative meta.tag.structure.div.start.html", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "style", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html entity.other.attribute-name.html", + "r": { + "dark_plus": "entity.other.attribute-name: #9CDCFE", + "light_plus": "entity.other.attribute-name: #FF0000", + "dark_vs": "entity.other.attribute-name: #9CDCFE", + "light_vs": "entity.other.attribute-name: #FF0000", + "hc_black": "entity.other.attribute-name: #9CDCFE" + } + }, + { + "c": "=", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html punctuation.separator.key-value.html", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\"", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html meta.embedded.line.css string.quoted.double.html punctuation.definition.string.begin.html", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.quoted.double.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.double.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": "x { }", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html meta.embedded.line.css string.quoted.double.html source.css", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.quoted.double.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.double.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\"", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html meta.embedded.line.css string.quoted.double.html punctuation.definition.string.end.html source.css-ignored-vscode", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.quoted.double.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.double.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": ">", + "t": "text.html.derivative meta.tag.structure.div.start.html punctuation.definition.tag.end.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "", + "t": "text.html.derivative meta.tag.structure.div.end.html punctuation.definition.tag.end.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "<", + "t": "text.html.derivative meta.tag.structure.div.start.html punctuation.definition.tag.begin.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "div", + "t": "text.html.derivative meta.tag.structure.div.start.html entity.name.tag.html", + "r": { + "dark_plus": "entity.name.tag: #569CD6", + "light_plus": "entity.name.tag: #800000", + "dark_vs": "entity.name.tag: #569CD6", + "light_vs": "entity.name.tag: #800000", + "hc_black": "entity.name.tag: #569CD6" + } + }, + { + "c": " ", + "t": "text.html.derivative meta.tag.structure.div.start.html", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "style", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html entity.other.attribute-name.html", + "r": { + "dark_plus": "entity.other.attribute-name: #9CDCFE", + "light_plus": "entity.other.attribute-name: #FF0000", + "dark_vs": "entity.other.attribute-name: #9CDCFE", + "light_vs": "entity.other.attribute-name: #FF0000", + "hc_black": "entity.other.attribute-name: #9CDCFE" + } + }, + { + "c": "=", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html punctuation.separator.key-value.html", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "'", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html meta.embedded.line.css string.quoted.single.html punctuation.definition.string.begin.html", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.quoted.single.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.single.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": "y { }", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html meta.embedded.line.css string.quoted.single.html source.css", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.quoted.single.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.single.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": "'", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html meta.embedded.line.css string.quoted.single.html punctuation.definition.string.end.html source.css-ignored-vscode", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.quoted.single.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.quoted.single.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": ">", + "t": "text.html.derivative meta.tag.structure.div.start.html punctuation.definition.tag.end.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "", + "t": "text.html.derivative meta.tag.structure.div.end.html punctuation.definition.tag.end.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "<", + "t": "text.html.derivative meta.tag.structure.div.start.html punctuation.definition.tag.begin.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "div", + "t": "text.html.derivative meta.tag.structure.div.start.html entity.name.tag.html", + "r": { + "dark_plus": "entity.name.tag: #569CD6", + "light_plus": "entity.name.tag: #800000", + "dark_vs": "entity.name.tag: #569CD6", + "light_vs": "entity.name.tag: #800000", + "hc_black": "entity.name.tag: #569CD6" + } + }, + { + "c": " ", + "t": "text.html.derivative meta.tag.structure.div.start.html", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "style", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html entity.other.attribute-name.html", + "r": { + "dark_plus": "entity.other.attribute-name: #9CDCFE", + "light_plus": "entity.other.attribute-name: #FF0000", + "dark_vs": "entity.other.attribute-name: #9CDCFE", + "light_vs": "entity.other.attribute-name: #FF0000", + "hc_black": "entity.other.attribute-name: #9CDCFE" + } + }, + { + "c": "=", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html punctuation.separator.key-value.html", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "z{}", + "t": "text.html.derivative meta.tag.structure.div.start.html meta.attribute.style.html meta.embedded.line.css string.unquoted.html source.css", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string.unquoted.html: #0000FF", + "dark_vs": "string: #CE9178", + "light_vs": "string.unquoted.html: #0000FF", + "hc_black": "string: #CE9178" + } + }, + { + "c": ">", + "t": "text.html.derivative meta.tag.structure.div.start.html punctuation.definition.tag.end.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + }, + { + "c": "", + "t": "text.html.derivative meta.tag.structure.div.end.html punctuation.definition.tag.end.html", + "r": { + "dark_plus": "punctuation.definition.tag: #808080", + "light_plus": "punctuation.definition.tag: #800000", + "dark_vs": "punctuation.definition.tag: #808080", + "light_vs": "punctuation.definition.tag: #800000", + "hc_black": "punctuation.definition.tag: #808080" + } + } +] \ No newline at end of file diff --git a/extensions/html/test/colorize-results/test_html.json b/extensions/html/test/colorize-results/test_html.json index 970d4214a74..dd8a4aa2043 100644 --- a/extensions/html/test/colorize-results/test_html.json +++ b/extensions/html/test/colorize-results/test_html.json @@ -782,7 +782,7 @@ }, { "c": "<", - "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.style.end.html punctuation.definition.tag.begin.html source.css", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.style.end.html punctuation.definition.tag.begin.html source.css-ignored-vscode", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -1200,7 +1200,7 @@ }, { "c": "<", - "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js-ignored-vscode", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -1354,7 +1354,7 @@ }, { "c": "<", - "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js-ignored-vscode", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -2212,7 +2212,7 @@ }, { "c": "<", - "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js", + "t": "text.html.derivative meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js-ignored-vscode", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -3189,4 +3189,4 @@ "hc_black": "punctuation.definition.tag: #808080" } } -] +] \ No newline at end of file diff --git a/extensions/markdown-basics/test/colorize-results/test_md.json b/extensions/markdown-basics/test/colorize-results/test_md.json index 9bc9aa7595f..6963f66e797 100644 --- a/extensions/markdown-basics/test/colorize-results/test_md.json +++ b/extensions/markdown-basics/test/colorize-results/test_md.json @@ -1134,7 +1134,7 @@ }, { "c": "<", - "t": "text.html.markdown meta.embedded.block.html meta.tag.metadata.style.end.html punctuation.definition.tag.begin.html source.css", + "t": "text.html.markdown meta.embedded.block.html meta.tag.metadata.style.end.html punctuation.definition.tag.begin.html source.css-ignored-vscode", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", diff --git a/extensions/php/test/colorize-results/issue-28354_php.json b/extensions/php/test/colorize-results/issue-28354_php.json index 3a34ef9deb8..27983076fa7 100644 --- a/extensions/php/test/colorize-results/issue-28354_php.json +++ b/extensions/php/test/colorize-results/issue-28354_php.json @@ -1,4 +1,5 @@ -[{ +[ + { "c": "<", "t": "text.html.php meta.embedded.block.html meta.tag.metadata.script.start.html punctuation.definition.tag.begin.html", "r": { @@ -495,7 +496,7 @@ }, { "c": "<", - "t": "text.html.php meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js", + "t": "text.html.php meta.embedded.block.html meta.tag.metadata.script.end.html punctuation.definition.tag.begin.html source.js-ignored-vscode", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", From d1cba2217e3c7b2395a8a54765126d93869f064a Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 20 Dec 2019 17:02:41 +0100 Subject: [PATCH 202/241] [folding] shift+click folds region when no children to collapse (for #78557) --- src/vs/editor/contrib/folding/folding.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 198a751ff01..1af69c1cfb5 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -32,7 +32,6 @@ import { InitializingRangeProvider, ID_INIT_PROVIDER } from 'vs/editor/contrib/f import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { onUnexpectedError } from 'vs/base/common/errors'; import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IMouseEvent } from 'vs/base/browser/mouseEvent'; const CONTEXT_FOLDING_ENABLED = new RawContextKey('foldingEnabled', false); @@ -423,12 +422,17 @@ export class FoldingController extends Disposable implements IEditorContribution let isCollapsed = region.isCollapsed; if (iconClicked || isCollapsed) { let toToggle = []; - let considerRegionsInside = this.shouldConsiderRegionsInside(e.event); - if (isCollapsed || (!isCollapsed && !considerRegionsInside)) { - toToggle.push(region); + let recursive = e.event.middleButton || e.event.shiftKey; + if (recursive) { + for (const r of foldingModel.getRegionsInside(region)) { + if (r.isCollapsed === isCollapsed) { + toToggle.push(r); + } + } } - if (considerRegionsInside) { - toToggle.push(...foldingModel.getRegionsInside(region, (r: FoldingRegion) => r.isCollapsed === isCollapsed)); + // when recursive, first only collapse all children. If all are already folded or there are no children, also fold parent. + if (isCollapsed || !recursive || toToggle.length === 0) { + toToggle.push(region); } foldingModel.toggleCollapseState(toToggle); this.reveal({ lineNumber, column: 1 }); @@ -438,10 +442,6 @@ export class FoldingController extends Disposable implements IEditorContribution }).then(undefined, onUnexpectedError); } - private shouldConsiderRegionsInside(event: IMouseEvent): boolean { - return event.middleButton || event.shiftKey; - } - public reveal(position: IPosition): void { this.editor.revealPositionInCenterIfOutsideViewport(position, ScrollType.Smooth); } From 57c30ddb82979a58a40441c02568a865da63285e Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Fri, 20 Dec 2019 08:21:59 -0800 Subject: [PATCH 203/241] Port view container logic from activitybarpart to panelpart (#87444) * update panel with view container logic * testing with outline contrib * remove outline command and unnecessary checks --- .../browser/parts/panel/panelActions.ts | 30 ++- .../browser/parts/panel/panelPart.ts | 207 ++++++++++++++---- 2 files changed, 198 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 0bb5ac80bf5..1293a2149e8 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -13,7 +13,7 @@ import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/ import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService, Parts, Position, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; -import { ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions'; +import { ActivityAction, ToggleCompositePinnedAction, ICompositeBar } from 'vs/workbench/browser/parts/compositeBarActions'; import { IActivity } from 'vs/workbench/common/activity'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel'; @@ -149,7 +149,7 @@ function createPositionPanelActionConfig(id: string, alias: string, label: strin }; } -export const PositionPanelActionConfigs = [ +export const PositionPanelActionConfigs: PanelActionConfig[] = [ createPositionPanelActionConfig(PositionPanelActionId.LEFT, 'View: Panel Position Left', nls.localize('positionPanelLeft', 'Move Panel Left'), Position.LEFT), createPositionPanelActionConfig(PositionPanelActionId.RIGHT, 'View: Panel Position Right', nls.localize('positionPanelRight', 'Move Panel Right'), Position.RIGHT), createPositionPanelActionConfig(PositionPanelActionId.BOTTOM, 'View: Panel Position Bottom', nls.localize('positionPanelBottom', 'Move Panel To Bottom'), Position.BOTTOM), @@ -187,8 +187,34 @@ export class PanelActivityAction extends ActivityAction { this.activate(); return Promise.resolve(); } + + setActivity(activity: IActivity): void { + this.activity = activity; + } } +export class PlaceHolderPanelActivityAction extends PanelActivityAction { + + constructor( + id: string, + @IPanelService panelService: IPanelService + ) { + super({ id, name: id }, panelService); + } +} + +export class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinnedAction { + + constructor(id: string, compositeBar: ICompositeBar) { + super({ id, name: id, cssClass: undefined }, compositeBar); + } + + setActivity(activity: IActivity): void { + this.label = activity.name; + } +} + + export class SwitchPanelViewAction extends Action { constructor( diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 9ded18492b0..354466a8da0 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/panelpart'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Action } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -18,7 +18,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions'; +import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -28,17 +28,19 @@ import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Dimension, trackFocus } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; -import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, IViewDescriptorCollection } from 'vs/workbench/common/views'; interface ICachedPanel { id: string; pinned: boolean; order?: number; visible: boolean; + views?: { when?: string }[]; } export class PanelPart extends CompositePart implements IPanelService { @@ -80,9 +82,13 @@ export class PanelPart extends CompositePart implements IPanelService { private compositeBar: CompositeBar; private compositeActions: Map = new Map(); + private readonly panelDisposables: Map = new Map(); + private blockOpeningPanel = false; private _contentDimension: Dimension | undefined; + private panelRegistry: PanelRegistry; + constructor( @INotificationService notificationService: INotificationService, @IStorageService storageService: IStorageService, @@ -92,8 +98,9 @@ export class PanelPart extends CompositePart implements IPanelService { @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, - @IContextKeyService contextKeyService: IContextKeyService, - @ILifecycleService private readonly lifecycleService: ILifecycleService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IExtensionService private readonly extensionService: IExtensionService, + @IViewsService private readonly viewsService: IViewsService, ) { super( notificationService, @@ -114,6 +121,8 @@ export class PanelPart extends CompositePart implements IPanelService { { hasTitle: true } ); + this.panelRegistry = Registry.as(PanelExtensions.Panels); + this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.getCachedPanels(), { icon: false, orientation: ActionsOrientation.HORIZONTAL, @@ -127,8 +136,8 @@ export class PanelPart extends CompositePart implements IPanelService { .filter(({ when }) => contextKeyService.contextMatchesRules(when)) .map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)), this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel")) - ], - getDefaultCompositeId: () => Registry.as(PanelExtensions.Panels).getDefaultPanelId(), + ] as Action[], + getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(), hidePart: () => this.layoutService.setPanelHidden(true), compositeSize: 0, overflowActionSize: 44, @@ -144,47 +153,156 @@ export class PanelPart extends CompositePart implements IPanelService { }) })); - for (const panel of this.getPanels()) { - this.compositeBar.addComposite(panel); - } - this.activePanelContextKey = ActivePanelContext.bindTo(contextKeyService); this.panelFocusContextKey = PanelFocusContext.bindTo(contextKeyService); this.registerListeners(); + this.onDidRegisterPanels([...this.getPanels()]); + } + + private onDidRegisterPanels(panels: PanelDescriptor[]): void { + for (const panel of panels) { + const cachedPanel = this.getCachedPanels().filter(({ id }) => id === panel.id)[0]; + const activePanel = this.getActivePanel(); + const isActive = activePanel?.getId() === panel.id; + + if (isActive || !this.shouldBeHidden(panel.id, cachedPanel)) { + this.compositeBar.addComposite(panel); + + // Pin it by default if it is new + if (!cachedPanel) { + this.compositeBar.pin(panel.id); + } + + if (isActive) { + this.compositeBar.activateComposite(panel.id); + } + } + } + + for (const panel of panels) { + this.enableCompositeActions(panel); + const viewContainer = this.getViewContainer(panel.id); + if (viewContainer?.hideIfEmpty) { + const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + if (viewDescriptors) { + this.onDidChangeActiveViews(panel, viewDescriptors); + this.panelDisposables.set(panel.id, viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(panel, viewDescriptors))); + } + } + } + } + + private onDidDeregisterPanel(panelId: string): void { + const disposable = this.panelDisposables.get(panelId); + if (disposable) { + disposable.dispose(); + } + + this.panelDisposables.delete(panelId); + this.hideComposite(panelId); + } + + private enableCompositeActions(panel: PanelDescriptor): void { + const { activityAction, pinnedAction } = this.getCompositeActions(panel.id); + if (activityAction instanceof PlaceHolderPanelActivityAction) { + activityAction.setActivity(panel); + } + + if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { + pinnedAction.setActivity(panel); + } + } + + private onDidChangeActiveViews(panel: PanelDescriptor, viewDescriptors: IViewDescriptorCollection): void { + if (viewDescriptors.activeViewDescriptors.length) { + this.compositeBar.addComposite(panel); + } else { + this.hideComposite(panel.id); + } + } + + private shouldBeHidden(panelId: string, cachedPanel?: ICachedPanel): boolean { + const viewContainer = this.getViewContainer(panelId); + if (!viewContainer || !viewContainer.hideIfEmpty) { + return false; + } + + return cachedPanel?.views && cachedPanel.views.length + ? cachedPanel.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when))) + : false; } private registerListeners(): void { + // Panel registration + this._register(this.registry.onDidRegister(panel => this.onDidRegisterPanels([panel]))); + this._register(this.registry.onDidDeregister(panel => this.onDidDeregisterPanel(panel.id))); - // Panel open/close + // Activate on panel open this._register(this.onDidPanelOpen(({ panel }) => this.onPanelOpen(panel))); + + // Deactivate on panel close this._register(this.onDidPanelClose(this.onPanelClose, this)); - // Panel register/deregister - this._register(this.registry.onDidRegister(panelDescriptor => this.compositeBar.addComposite(panelDescriptor))); - this._register(this.registry.onDidDeregister(panelDescriptor => { - this.compositeBar.hideComposite(panelDescriptor.id); - this.removeComposite(panelDescriptor.id); + // Extension registration + let disposables = this._register(new DisposableStore()); + this._register(this.extensionService.onDidRegisterExtensions(() => { + disposables.clear(); + this.onDidRegisterExtensions(); + this.compositeBar.onDidChange(() => this.saveCachedPanels(), this, disposables); + this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e), this, disposables); })); + } - // Activate panel action on opening of a panel - this._register(this.onDidPanelOpen(({ panel }) => { - this.compositeBar.activateComposite(panel.getId()); - this.layoutCompositeBar(); // Need to relayout composite bar since different panels have different action bar width - })); + private onDidRegisterExtensions(): void { + this.removeNotExistingComposites(); - // Deactivate panel action on close - this._register(this.onDidPanelClose(panel => this.compositeBar.deactivateComposite(panel.getId()))); + this.saveCachedPanels(); + } - // State - this.lifecycleService.when(LifecyclePhase.Eventually).then(() => { - this._register(this.compositeBar.onDidChange(() => this.saveCachedPanels())); - this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); - }); + private removeNotExistingComposites(): void { + const panels = this.getPanels(); + for (const { id } of this.getCachedPanels()) { // should this value match viewlet (load on ctor) + if (panels.every(panel => panel.id !== id)) { + this.hideComposite(id); + } + } + } + + private hideComposite(compositeId: string): void { + this.compositeBar.hideComposite(compositeId); + + const compositeActions = this.compositeActions.get(compositeId); + if (compositeActions) { + compositeActions.activityAction.dispose(); + compositeActions.pinnedAction.dispose(); + this.compositeActions.delete(compositeId); + } } private onPanelOpen(panel: IPanel): void { this.activePanelContextKey.set(panel.getId()); + + const foundPanel = this.panelRegistry.getPanel(panel.getId()); + if (foundPanel) { + this.compositeBar.addComposite(foundPanel); + } + + // Activate composite when opened + this.compositeBar.activateComposite(panel.getId()); + + const panelDescriptor = this.panelRegistry.getPanel(panel.getId()); + if (panelDescriptor) { + const viewContainer = this.getViewContainer(panelDescriptor.id); + if (viewContainer?.hideIfEmpty) { + const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + if (viewDescriptors?.activeViewDescriptors.length === 0) { + this.hideComposite(panelDescriptor.id); // Update the composite bar by hiding + } + } + } + + this.layoutCompositeBar(); // Need to relayout composite bar since different panels have different action bar width } private onPanelClose(panel: IPanel): void { @@ -193,6 +311,8 @@ export class PanelPart extends CompositePart implements IPanelService { if (this.activePanelContextKey.get() === id) { this.activePanelContextKey.reset(); } + + this.compositeBar.deactivateComposite(panel.getId()); } create(parent: HTMLElement): void { @@ -243,11 +363,11 @@ export class PanelPart extends CompositePart implements IPanelService { } getPanel(panelId: string): IPanelIdentifier | undefined { - return Registry.as(PanelExtensions.Panels).getPanel(panelId); + return this.panelRegistry.getPanel(panelId); } getPanels(): readonly PanelDescriptor[] { - return Registry.as(PanelExtensions.Panels).getPanels() + return this.panelRegistry.getPanels() .sort((v1, v2) => typeof v1.order === 'number' && typeof v2.order === 'number' ? v1.order - v2.order : NaN); } @@ -331,10 +451,18 @@ export class PanelPart extends CompositePart implements IPanelService { private getCompositeActions(compositeId: string): { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction; } { let compositeActions = this.compositeActions.get(compositeId); if (!compositeActions) { - compositeActions = { - activityAction: this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))), - pinnedAction: new ToggleCompositePinnedAction(this.getPanel(compositeId), this.compositeBar) - }; + const panel = this.getPanel(compositeId); + if (panel) { + compositeActions = { + activityAction: new PanelActivityAction(assertIsDefined(this.getPanel(compositeId)), this), + pinnedAction: new ToggleCompositePinnedAction(this.getPanel(compositeId), this.compositeBar) + }; + } else { + compositeActions = { + activityAction: new PlaceHolderPanelActivityAction(compositeId, this), + pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar) + }; + } this.compositeActions.set(compositeId, compositeActions); } @@ -448,6 +576,11 @@ export class PanelPart extends CompositePart implements IPanelService { this.storageService.store(PanelPart.PINNED_PANELS, value, StorageScope.GLOBAL); } + private getViewContainer(panelId: string): ViewContainer | undefined { + const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + return viewContainerRegistry.get(panelId); + } + toJSON(): object { return { type: Parts.PANEL_PART From c0c4eb6a5171810e8a8596363ff1f98960fcc944 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 20 Dec 2019 11:55:24 -0800 Subject: [PATCH 204/241] Always use same range for jsdoc completions For #87091 JSdoc completions do not have different behavior for replace vs insert --- .../src/features/jsDocCompletions.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/extensions/typescript-language-features/src/features/jsDocCompletions.ts b/extensions/typescript-language-features/src/features/jsDocCompletions.ts index 82aacc8927a..d4d59c8d6d5 100644 --- a/extensions/typescript-language-features/src/features/jsDocCompletions.ts +++ b/extensions/typescript-language-features/src/features/jsDocCompletions.ts @@ -27,9 +27,8 @@ class JsDocCompletionItem extends vscode.CompletionItem { const prefix = line.slice(0, position.character).match(/\/\**\s*$/); const suffix = line.slice(position.character).match(/^\s*\**\//); const start = position.translate(0, prefix ? -prefix[0].length : 0); - this.range = new vscode.Range( - start, - position.translate(0, suffix ? suffix[0].length : 0)); + const range = new vscode.Range(start, position.translate(0, suffix ? suffix[0].length : 0)); + this.range2 = { inserting: range, replacing: range }; } } From d06f0e877ceaf3a35a283f1bfdc50927ec8dfd1e Mon Sep 17 00:00:00 2001 From: Bura Chuhadar Date: Sat, 21 Dec 2019 02:51:23 -0500 Subject: [PATCH 205/241] 87180: Wrong Markdown preview format for files in UNC path (#87501) Fixing a typo in the code to fix this issue. --- src/vs/workbench/contrib/webview/common/resourceLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/webview/common/resourceLoader.ts b/src/vs/workbench/contrib/webview/common/resourceLoader.ts index e34d1ec28bd..68d4c606728 100644 --- a/src/vs/workbench/contrib/webview/common/resourceLoader.ts +++ b/src/vs/workbench/contrib/webview/common/resourceLoader.ts @@ -104,5 +104,5 @@ function containsResource(root: URI, resource: URI): boolean { resourceFsPath = resourceFsPath.toLowerCase(); } - return startsWith(resource.fsPath, rootPath); + return startsWith(resourceFsPath, rootPath); } From 8cddb0bbde231ac26fafa3fc2a7b18e8209c71e8 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Sun, 22 Dec 2019 14:12:51 +0100 Subject: [PATCH 206/241] fix typo; fixes #87553 --- src/vs/base/common/lifecycle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 651c5fa22a4..06e3746750c 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -163,7 +163,7 @@ export abstract class Disposable implements IDisposable { /** * Manages the lifecycle of a disposable value that may be changed. * - * This ensures that when the the disposable value is changed, the previously held disposable is disposed of. You can + * This ensures that when the disposable value is changed, the previously held disposable is disposed of. You can * also register a `MutableDisposable` on a `Disposable` to ensure it is automatically cleaned up. */ export class MutableDisposable implements IDisposable { From 2395bc070a2ddae7723ab1aac773918254144685 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 23 Dec 2019 11:22:26 +0100 Subject: [PATCH 207/241] rename to window.autoDetectColorScheme, off by default --- .../services/themes/browser/workbenchThemeService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index ed8db767922..a914477d227 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -40,7 +40,7 @@ import { IExtensionResourceLoaderService } from 'vs/workbench/services/extension const PREFERRED_DARK_THEME_SETTING = 'workbench.preferredDarkColorTheme'; const PREFERRED_LIGHT_THEME_SETTING = 'workbench.preferredLightColorTheme'; const PREFERRED_HC_THEME_SETTING = 'workbench.preferredHighContrastColorTheme'; -const DETECT_COLOR_SCHEME_SETTING = 'workbench.autoDetectColorScheme'; +const DETECT_COLOR_SCHEME_SETTING = 'window.autoDetectColorScheme'; const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; // implementation @@ -741,7 +741,7 @@ const preferredHCThemeSettingSchema: IConfigurationPropertySchema = { const detectColorSchemeSettingSchema: IConfigurationPropertySchema = { type: 'boolean', description: nls.localize('detectColorScheme', 'If set, automatically switch to the preferred color theme based on the OS appearance.'), - default: true + default: false }; const iconThemeSettingSchema: IConfigurationPropertySchema = { From 4daf254dda03d417f966b97d7b04aae0ba6a0710 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 23 Dec 2019 12:31:20 +0100 Subject: [PATCH 208/241] #87285 Contribute copy and configure action using menu --- src/vs/platform/actions/common/actions.ts | 3 +- .../browser/extensions.contribution.ts | 56 +++++++- .../extensions/browser/extensionsActions.ts | 128 +++++------------- .../extensions/browser/extensionsViews.ts | 32 +++-- 4 files changed, 114 insertions(+), 105 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 8eb6380e0f9..13b05bd3949 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -70,6 +70,8 @@ export const enum MenuId { EditorTitleContext, EmptyEditorGroupContext, ExplorerContext, + ExtensionContext, + GlobalActivity, MenubarAppearanceMenu, MenubarDebugMenu, MenubarEditMenu, @@ -106,7 +108,6 @@ export const enum MenuId { CommentThreadActions, CommentTitle, CommentActions, - GlobalActivity } export interface IMenuActionOptions { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 4bebc6eb61f..42d38471455 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; -import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId, registerAction } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionsLabel, ExtensionsChannelId, PreferencesLabel, IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionManagementServerService, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { VIEWLET_ID, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionMenuActionContext } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, @@ -44,6 +44,10 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { RemoteExtensionsInstaller } from 'vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller'; import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -336,6 +340,54 @@ MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { order: 3 }); +// Extension Context Menu + +registerAction({ + id: 'workbench.extensions.action.copyExtension', + title: { value: localize('workbench.extensions.action.copyExtension', "Copy"), original: 'Copy' }, + async handler(accessor, context: IExtensionMenuActionContext) { + const productService = accessor.get(IProductService); + const name = localize('extensionInfoName', 'Name: {0}', context.packageJSON.displayName); + const id = localize('extensionInfoId', 'Id: {0}', context.id); + const description = localize('extensionInfoDescription', 'Description: {0}', context.packageJSON.description); + const verision = localize('extensionInfoVersion', 'Version: {0}', context.packageJSON.version); + const publisher = localize('extensionInfoPublisher', 'Publisher: {0}', context.packageJSON.publisher); + const link = productService.extensionsGallery ? localize('extensionInfoVSMarketplaceLink', 'VS Marketplace Link: {0}', `${productService.extensionsGallery!.itemUrl}?itemName=${context.id}`) : null; + const clipboardStr = `${name}\n${id}\n${description}\n${verision}\n${publisher}${link ? '\n' + link : ''}`; + await accessor.get(IClipboardService).writeText(clipboardStr); + }, + menu: { + menuId: MenuId.ExtensionContext, + group: '1_copy' + }, +}); + +registerAction({ + id: 'workbench.extensions.action.copyExtensionId', + title: { value: localize('workbench.extensions.action.copyExtensionId', "Copy Extension Id"), original: 'Copy Extension Id' }, + async handler(accessor, context: IExtensionMenuActionContext) { + await accessor.get(IClipboardService).writeText(context.id); + }, + menu: { + menuId: MenuId.ExtensionContext, + group: '1_copy' + }, +}); + +registerAction({ + id: 'workbench.extensions.action.configure', + title: { value: localize('workbench.extensions.action.configure', "Configure..."), original: 'Configure...' }, + async handler(accessor, context: IExtensionMenuActionContext) { + await accessor.get(IPreferencesService).openSettings(false, `@ext:${context.id}`); + }, + menu: { + menuId: MenuId.ExtensionContext, + group: '2_configure', + when: ContextKeyExpr.and(ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.has('extensionHasConfiguration')) + }, + +}); + const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); class ExtensionsContributions implements IWorkbenchContribution { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index e279bd9087e..bb11457473f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -13,7 +13,7 @@ import * as json from 'vs/base/common/json'; import { ActionViewItem, Separator, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { dispose, Disposable } from 'vs/base/common/lifecycle'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, IExtensionMenuAction } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -39,7 +39,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { PagedModel } from 'vs/base/common/paging'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { MenuRegistry, MenuId, IMenuService } from 'vs/platform/actions/common/actions'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -55,10 +55,8 @@ import { coalesce } from 'vs/base/common/arrays'; import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IFileIconTheme, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ILabelService } from 'vs/platform/label/common/label'; import { prefersExecuteOnUI, prefersExecuteOnWorkspace } from 'vs/workbench/services/extensions/common/extensionsUtil'; -import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -671,7 +669,9 @@ export class ManageExtensionAction extends ExtensionDropDownAction { constructor( @IInstantiationService instantiationService: IInstantiationService, @IExtensionService private readonly extensionService: IExtensionService, - @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService + @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { super(ManageExtensionAction.ID, '', '', true, true, instantiationService); @@ -708,11 +708,10 @@ export class ManageExtensionAction extends ExtensionDropDownAction { groups.push([this.instantiationService.createInstance(UninstallAction)]); groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); - const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(CopyExtensionInfoAction), this.instantiationService.createInstance(CopyExtensionIdAction)]; - if (this.extension && this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { - extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); - } - groups.push(extensionActions); + const contextKeyService = this.contextKeyService.createScoped(); + contextKeyService.createKey('extensionStatus', 'installed'); + contextKeyService.createKey('extensionHasConfiguration', !!this.extension && !!this.extension.local && !!this.extension.local.manifest.contributes && !!this.extension.local.manifest.contributes.configuration); + MenuItemExtensionAction.getContextMenuActions(this.menuService, contextKeyService).forEach(actions => groups.push(actions)); groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension)); @@ -738,6 +737,32 @@ export class ManageExtensionAction extends ExtensionDropDownAction { } } +export class MenuItemExtensionAction extends ExtensionAction { + + constructor(private readonly action: IExtensionMenuAction) { + super(action.id, action.label); + } + + static getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService): ExtensionAction[][] { + const groups: ExtensionAction[][] = []; + const menu = menuService.createMenu(MenuId.ExtensionContext, contextKeyService); + menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => new MenuItemExtensionAction(action)))); + menu.dispose(); + return groups; + } + + update() { } + + async run(): Promise { + if (this.extension) { + const packageJSON = await this.extension.getManifest(CancellationToken.None); + if (packageJSON) { + return this.action.run({ id: this.extension.identifier.id, packageJSON }); + } + } + } +} + export class InstallAnotherVersionAction extends ExtensionAction { static readonly ID = 'workbench.extensions.action.install.anotherVersion'; @@ -790,89 +815,6 @@ export class InstallAnotherVersionAction extends ExtensionAction { } } -export class CopyExtensionInfoAction extends ExtensionAction { - - static readonly ID = 'workbench.extensions.action.copyExtension'; - static readonly LABEL = localize('workbench.extensions.action.copyExtension', "Copy"); - - constructor( - @IClipboardService private readonly clipboardService: IClipboardService - ) { - super(CopyExtensionInfoAction.ID, CopyExtensionInfoAction.LABEL); - this.update(); - } - - update(): void { - this.enabled = !!this.extension; - } - - async run(): Promise { - if (!this.extension) { - return; - } - - const name = localize('extensionInfoName', 'Name: {0}', this.extension.displayName); - const id = localize('extensionInfoId', 'Id: {0}', this.extension.identifier.id); - const description = localize('extensionInfoDescription', 'Description: {0}', this.extension.description); - const verision = localize('extensionInfoVersion', 'Version: {0}', this.extension.version); - const publisher = localize('extensionInfoPublisher', 'Publisher: {0}', this.extension.publisherDisplayName); - const link = this.extension.url ? localize('extensionInfoVSMarketplaceLink', 'VS Marketplace Link: {0}', this.extension.url.toString()) : null; - - const clipboardStr = `${name}\n${id}\n${description}\n${verision}\n${publisher}${link ? '\n' + link : ''}`; - - return this.clipboardService.writeText(clipboardStr); - } -} - -export class CopyExtensionIdAction extends ExtensionAction { - - static readonly ID = 'workbench.extensions.action.copyExtensionId'; - static readonly LABEL = localize('workbench.extensions.action.copyExtensionId', "Copy Extension Id"); - - constructor( - @IClipboardService private readonly clipboardService: IClipboardService - ) { - super(CopyExtensionIdAction.ID, CopyExtensionIdAction.LABEL); - this.update(); - } - - update(): void { - this.enabled = !!this.extension; - } - - async run(): Promise { - if (!this.extension) { - return; - } - return this.clipboardService.writeText(this.extension.identifier.id); - } -} - -export class ExtensionSettingsAction extends ExtensionAction { - - static readonly ID = 'extensions.extensionSettings'; - static readonly LABEL = localize('extensionSettingsAction', "Configure Extension Settings"); - - constructor( - @IPreferencesService private readonly preferencesService: IPreferencesService - ) { - super(ExtensionSettingsAction.ID, ExtensionSettingsAction.LABEL); - this.update(); - } - - update(): void { - this.enabled = !!this.extension; - } - - async run(): Promise { - if (!this.extension) { - return; - } - this.preferencesService.openSettings(false, `@ext:${this.extension.identifier.id}`); - return Promise.resolve(); - } -} - export class EnableForWorkspaceAction extends ExtensionAction { static readonly ID = 'extensions.enableForWorkspace'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index adaa265da5c..904a0f80f22 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -28,7 +28,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction, MenuItemExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { WorkbenchPagedList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -48,6 +48,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IMenuService } from 'vs/platform/actions/common/actions'; // Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.search-result']; @@ -106,7 +107,8 @@ export class ExtensionsListView extends ViewPane { @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, @IProductService protected readonly productService: IProductService, - @IContextKeyService contextKeyService: IContextKeyService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IMenuService private readonly menuService: IMenuService, ) { super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService); this.server = options.server; @@ -228,16 +230,27 @@ export class ExtensionsListView extends ViewPane { const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); const manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction); manageExtensionAction.extension = e.element; - const groups = manageExtensionAction.getActionGroups(runningExtensions, colorThemes, fileIconThemes); - let actions: IAction[] = []; - for (const menuActions of groups) { - actions = [...actions, ...menuActions, new Separator()]; - } if (manageExtensionAction.enabled) { + const groups = manageExtensionAction.getActionGroups(runningExtensions, colorThemes, fileIconThemes); + let actions: IAction[] = []; + for (const menuActions of groups) { + actions = [...actions, ...menuActions, new Separator()]; + } this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions.slice(0, actions.length - 1) }); + } else if (e.element) { + const groups = MenuItemExtensionAction.getContextMenuActions(this.menuService, this.contextKeyService.createScoped()); + groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = e.element!)); + let actions: IAction[] = []; + for (const menuActions of groups) { + actions = [...actions, ...menuActions, new Separator()]; + } + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => actions + }); } } } @@ -866,10 +879,11 @@ export class ServerExtensionsView extends ExtensionsListView { @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, @IProductService productService: IProductService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IMenuService menuService: IMenuService, ) { options.server = server; - super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService); + super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService, menuService); this._register(onDidChangeTitle(title => this.updateTitle(title))); } From 5c9a0af433136b518d444c8cbe1a86d20b241cbb Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 23 Dec 2019 13:51:27 +0100 Subject: [PATCH 209/241] debug.console.closeOnEnd #82931 --- .../contrib/debug/browser/debug.contribution.ts | 7 +++---- .../contrib/debug/browser/debugService.ts | 14 +++----------- src/vs/workbench/contrib/debug/common/debug.ts | 2 +- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index eed05336f40..569c6e57d25 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -213,10 +213,9 @@ configurationRegistry.registerConfiguration({ }, 'debug.internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA, 'debug.console.closeOnEnd': { - enum: ['never', 'always', 'whenOpenedByDebug'], - description: nls.localize('debug.console.closeOnEnd', "Controls what to do with the debug console when the debug session ends."), - enumDescriptions: [nls.localize('neverClose', "Remain it as-is"), nls.localize('alwaysClose', "Close it (if opened)"), nls.localize('closeWhenOpenedByDebug', "Close if the debugging process opened it, see debug.internalConsoleOptions")], - default: 'never' + type: 'boolean', + description: nls.localize('debug.console.closeOnEnd', "Controls if the debug console should be automatically closed when the debug session ends."), + default: false }, 'debug.openDebug': { enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart', 'openOnDebugBreak'], diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index a7222f4db65..4202fb46cc6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -74,9 +74,6 @@ export class DebugService implements IDebugService { private initCancellationToken: CancellationTokenSource | undefined; private activity: IDisposable | undefined; - // Enable undefined because that makes the test easier. - private replWasOpened : boolean | undefined = false; - constructor( @IStorageService private readonly storageService: IStorageService, @IEditorService private readonly editorService: IEditorService, @@ -455,8 +452,6 @@ export class DebugService implements IDebugService { await this.launchOrAttachToSession(session); const internalConsoleOptions = session.configuration.internalConsoleOptions || this.configurationService.getValue('debug').internalConsoleOptions; - const activePanel = this.panelService.getActivePanel(); - this.replWasOpened = activePanel && activePanel.getId() === REPL_ID; if (internalConsoleOptions === 'openOnSessionStart' || (this.viewModel.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) { this.panelService.openPanel(REPL_ID, false); } @@ -568,14 +563,11 @@ export class DebugService implements IDebugService { // Data breakpoints that can not be persisted should be cleared when a session ends const dataBreakpoints = this.model.getDataBreakpoints().filter(dbp => !dbp.canPersist); dataBreakpoints.forEach(dbp => this.model.removeDataBreakpoints(dbp.getId())); - } - const closeConsoleOnEnd = this.configurationService.getValue('debug').console.closeOnEnd; - if (this.panelService.getLastActivePanelId() === REPL_ID && - (closeConsoleOnEnd === 'always' || (closeConsoleOnEnd === 'whenOpenedByDebug' && !this.replWasOpened))) { - this.panelService.hideActivePanel(); + if (this.panelService.getLastActivePanelId() === REPL_ID && this.configurationService.getValue('debug').console.closeOnEnd) { + this.panelService.hideActivePanel(); + } } - })); } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 7f79d65778a..6720b09c23a 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -463,7 +463,7 @@ export interface IDebugConfiguration { fontFamily: string; lineHeight: number; wordWrap: boolean; - closeOnEnd: 'never' | 'always' | 'whenOpenedByDebug'; + closeOnEnd: boolean; }; focusWindowOnBreak: boolean; onTaskErrors: 'debugAnyway' | 'showErrors' | 'prompt'; From dba0076c9fba7b857e7b4de96f5f46f4c004a412 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Mon, 23 Dec 2019 16:36:46 +0100 Subject: [PATCH 210/241] add API resolveDebugConfigurationWithSubstitutedVariables; see #85206 --- src/vs/vscode.proposed.d.ts | 20 ++++++++++++++++++- .../api/browser/mainThreadDebugService.ts | 7 ++++++- .../workbench/api/common/extHost.protocol.ts | 3 ++- .../api/common/extHostDebugService.ts | 15 ++++++++++++++ .../browser/debugConfigurationManager.ts | 16 +++++++++++++++ .../contrib/debug/browser/debugService.ts | 14 ++++++++++++- .../workbench/contrib/debug/common/debug.ts | 1 + 7 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index ebb5d76daed..a36026d282f 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -788,7 +788,25 @@ declare module 'vscode' { //#endregion - //#region Debug + //#region Debug: + + export interface DebugConfigurationProvider { + + /** + * This hook is directly called after 'resolveDebugConfiguration' but with all variables substituted. + * It can be used to resolve or verify a [debug configuration](#DebugConfiguration) by filling in missing values or by adding/changing/removing attributes. + * If more than one debug configuration provider is registered for the same type, the 'resolveDebugConfigurationWithSubstitutedVariables' calls are chained + * in arbitrary order and the initial debug configuration is piped through the chain. + * Returning the value 'undefined' prevents the debug session from starting. + * Returning the value 'null' prevents the debug session from starting and opens the underlying debug configuration instead. + * + * @param folder The workspace folder from which the configuration originates from or `undefined` for a folderless setup. + * @param debugConfiguration The [debug configuration](#DebugConfiguration) to resolve. + * @param token A cancellation token. + * @return The resolved debug configuration or undefined or null. + */ + resolveDebugConfigurationWithSubstitutedVariables?(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult; + } // deprecated diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 673177f0f01..4e48c4bf864 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -154,7 +154,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return Promise.resolve(); } - public $registerDebugConfigurationProvider(debugType: string, hasProvide: boolean, hasResolve: boolean, hasProvideDebugAdapter: boolean, handle: number): Promise { + public $registerDebugConfigurationProvider(debugType: string, hasProvide: boolean, hasResolve: boolean, hasResolve2: boolean, hasProvideDebugAdapter: boolean, handle: number): Promise { const provider = { type: debugType @@ -169,6 +169,11 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return this._proxy.$resolveDebugConfiguration(handle, folder, config, token); }; } + if (hasResolve2) { + provider.resolveDebugConfigurationWithSubstitutedVariables = (folder, config, token) => { + return this._proxy.$resolveDebugConfigurationWithSubstitutedVariables(handle, folder, config, token); + }; + } if (hasProvideDebugAdapter) { console.info('DebugConfigurationProvider.debugAdapterExecutable is deprecated and will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.'); provider.debugAdapterExecutable = (folder) => { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ef5b382c099..076f847e2f6 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -750,7 +750,7 @@ export interface MainThreadDebugServiceShape extends IDisposable { $acceptDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void; $acceptDAError(handle: number, name: string, message: string, stack: string | undefined): void; $acceptDAExit(handle: number, code: number | undefined, signal: string | undefined): void; - $registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, hasProvideDaMethod: boolean, handle: number): Promise; + $registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, hasResolve2Method: boolean, hasProvideDaMethod: boolean, handle: number): Promise; $registerDebugAdapterDescriptorFactory(type: string, handle: number): Promise; $unregisterDebugConfigurationProvider(handle: number): void; $unregisterDebugAdapterDescriptorFactory(handle: number): void; @@ -1337,6 +1337,7 @@ export interface ExtHostDebugServiceShape { $stopDASession(handle: number): Promise; $sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void; $resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; + $resolveDebugConfigurationWithSubstitutedVariables(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; $provideDebugConfigurations(handle: number, folder: UriComponents | undefined, token: CancellationToken): Promise; $legacyDebugAdapterExecutable(handle: number, folderUri: UriComponents | undefined): Promise; // TODO@AW legacy $provideDebugAdapter(handle: number, session: IDebugSessionDto): Promise; diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index c1352774e0d..446a36120d4 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -315,6 +315,7 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb this._debugServiceProxy.$registerDebugConfigurationProvider(type, !!provider.provideDebugConfigurations, !!provider.resolveDebugConfiguration, + !!provider.resolveDebugConfigurationWithSubstitutedVariables, !!provider.debugAdapterExecutable, // TODO@AW: deprecated handle); @@ -628,6 +629,20 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb }); } + public $resolveDebugConfigurationWithSubstitutedVariables(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration, token: CancellationToken): Promise { + return asPromise(async () => { + const provider = this.getConfigProviderByHandle(configProviderHandle); + if (!provider) { + throw new Error('no DebugConfigurationProvider found'); + } + if (!provider.resolveDebugConfigurationWithSubstitutedVariables) { + throw new Error('DebugConfigurationProvider has no method resolveDebugConfigurationWithSubstitutedVariables'); + } + const folder = await this.getFolder(folderUri); + return provider.resolveDebugConfigurationWithSubstitutedVariables(folder, debugConfiguration, token); + }); + } + // TODO@AW deprecated and legacy public $legacyDebugAdapterExecutable(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { return asPromise(async () => { diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index f5f1df0cf79..8e925287d4c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -207,6 +207,22 @@ export class ConfigurationManager implements IConfigurationManager { return result; } + async resolveDebugConfigurationWithSubstitutedVariables(folderUri: uri | undefined, type: string | undefined, config: IConfig, token: CancellationToken): Promise { + // pipe the config through the promises sequentially. Append at the end the '*' types + const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfigurationWithSubstitutedVariables) + .concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfigurationWithSubstitutedVariables)); + + let result: IConfig | null | undefined = config; + await sequence(providers.map(provider => async () => { + // If any provider returned undefined or null make sure to respect that and do not pass the result to more resolver + if (result) { + result = await provider.resolveDebugConfigurationWithSubstitutedVariables!(folderUri, result, token); + } + })); + + return result; + } + async provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise { await this.activateDebuggers('onDebugInitialConfigurations'); const results = await Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token))); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 4202fb46cc6..e11852d3e88 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -379,13 +379,22 @@ export class DebugService implements IDebugService { // a falsy config indicates an aborted launch if (configByProviders && configByProviders.type) { try { - const resolvedConfig = await this.substituteVariables(launch, configByProviders); + let resolvedConfig = await this.substituteVariables(launch, configByProviders); if (!resolvedConfig) { // User canceled resolving of interactive variables, silently return return false; } + const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig, this.initCancellationToken.token); + if (!cfg) { + if (launch && type && cfg === null) { // show launch.json only for "config" being "null". + await launch.openConfigFile(false, true, type, this.initCancellationToken ? this.initCancellationToken.token : undefined); + } + return false; + } + resolvedConfig = cfg; + if (!this.configurationManager.getDebugger(resolvedConfig.type) || (configByProviders.request !== 'attach' && configByProviders.request !== 'launch')) { let message: string; if (configByProviders.request !== 'attach' && configByProviders.request !== 'launch') { @@ -639,6 +648,9 @@ export class DebugService implements IDebugService { const resolvedByProviders = await this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, this.initCancellationToken.token); if (resolvedByProviders) { resolved = await this.substituteVariables(launch, resolvedByProviders); + if (resolved) { + resolved = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, unresolved.type, resolved, this.initCancellationToken.token); + } } else { resolved = resolvedByProviders; } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 6720b09c23a..bbfe0526e1a 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -599,6 +599,7 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut export interface IDebugConfigurationProvider { readonly type: string; resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; + resolveDebugConfigurationWithSubstitutedVariables?(folderUri: uri | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; provideDebugConfigurations?(folderUri: uri | undefined, token: CancellationToken): Promise; debugAdapterExecutable?(folderUri: uri | undefined): Promise; // TODO@AW legacy } From f33b7afcb3d4ce9e006ad1a64c20f7b30c4b0118 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 20 Dec 2019 16:05:38 -0800 Subject: [PATCH 211/241] Implement "collapseWorkspaceFoldersFirst" on explorer. Fix #87487 --- .../files/browser/files.contribution.ts | 5 +++++ .../files/browser/views/explorerView.ts | 18 ++++++++++++++++++ src/vs/workbench/contrib/files/common/files.ts | 1 + 3 files changed, 24 insertions(+) diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index edb3d92e1c6..73f27caaced 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -422,6 +422,11 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('compressSingleChildFolders', "Controls whether the explorer should render folders in a compact form. In such a form, single child folders will be compressed in a combined tree element. Useful for Java package structures, for example."), 'default': true }, + 'explorer.collapseWorkspaceFoldersFirst': { + 'type': 'boolean', + 'description': nls.localize('collapseWorkspaceFoldersFirst', "Controls whether the explorer should collapse expanded items inside workspace folders before collapsing the workspace folder level."), + 'default': true + }, } }); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 67bad15f747..14745d2cb02 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -684,6 +684,24 @@ export class ExplorerView extends ViewPane { } collapseAll(): void { + const collapseWorkspaceFoldersFirst = this.configurationService.getValue().explorer.collapseWorkspaceFoldersFirst; + if (collapseWorkspaceFoldersFirst) { + const treeInput = this.tree.getInput(); + if (Array.isArray(treeInput)) { + const hasExpandedItemInsideFolder = treeInput.some(folder => + Array.from(folder.children.entries()).some(([_id, child]) => + this.tree.hasNode(child) && !this.tree.isCollapsed(child))); + + if (hasExpandedItemInsideFolder) { + treeInput.forEach(folder => { + folder.children.forEach(child => this.tree.hasNode(child) && this.tree.collapse(child)); + }); + + return; + } + } + } + this.tree.collapseAll(); } diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index c2bdc69cf7b..a3f5694fdc8 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -117,6 +117,7 @@ export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkb badges: boolean; }; incrementalNaming: 'simple' | 'smart'; + collapseWorkspaceFoldersFirst: boolean; }; editor: IEditorOptions; } From 1cc28eb10f2557676eaea52de1b47b3b08989c7a Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 23 Dec 2019 09:52:52 -0600 Subject: [PATCH 212/241] For #87487 - remove the setting --- .../files/browser/files.contribution.ts | 5 ---- .../files/browser/views/explorerView.ts | 23 ++++++++----------- .../workbench/contrib/files/common/files.ts | 1 - 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 73f27caaced..edb3d92e1c6 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -422,11 +422,6 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('compressSingleChildFolders', "Controls whether the explorer should render folders in a compact form. In such a form, single child folders will be compressed in a combined tree element. Useful for Java package structures, for example."), 'default': true }, - 'explorer.collapseWorkspaceFoldersFirst': { - 'type': 'boolean', - 'description': nls.localize('collapseWorkspaceFoldersFirst', "Controls whether the explorer should collapse expanded items inside workspace folders before collapsing the workspace folder level."), - 'default': true - }, } }); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 14745d2cb02..08a8a21aa70 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -684,21 +684,18 @@ export class ExplorerView extends ViewPane { } collapseAll(): void { - const collapseWorkspaceFoldersFirst = this.configurationService.getValue().explorer.collapseWorkspaceFoldersFirst; - if (collapseWorkspaceFoldersFirst) { - const treeInput = this.tree.getInput(); - if (Array.isArray(treeInput)) { - const hasExpandedItemInsideFolder = treeInput.some(folder => - Array.from(folder.children.entries()).some(([_id, child]) => - this.tree.hasNode(child) && !this.tree.isCollapsed(child))); + const treeInput = this.tree.getInput(); + if (Array.isArray(treeInput)) { + const hasExpandedItemInsideFolder = treeInput.some(folder => + Array.from(folder.children.entries()).some(([_id, child]) => + this.tree.hasNode(child) && !this.tree.isCollapsed(child))); - if (hasExpandedItemInsideFolder) { - treeInput.forEach(folder => { - folder.children.forEach(child => this.tree.hasNode(child) && this.tree.collapse(child)); - }); + if (hasExpandedItemInsideFolder) { + treeInput.forEach(folder => { + folder.children.forEach(child => this.tree.hasNode(child) && this.tree.collapse(child)); + }); - return; - } + return; } } diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index a3f5694fdc8..c2bdc69cf7b 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -117,7 +117,6 @@ export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkb badges: boolean; }; incrementalNaming: 'simple' | 'smart'; - collapseWorkspaceFoldersFirst: boolean; }; editor: IEditorOptions; } From 2a4f3dfa400f4d1d795ad3f79220091f07a6a7a4 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 23 Dec 2019 10:01:10 -0600 Subject: [PATCH 213/241] For #87487 - refactor 'some' to for loops --- .../files/browser/views/explorerView.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 08a8a21aa70..f2b2175cc08 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -686,11 +686,7 @@ export class ExplorerView extends ViewPane { collapseAll(): void { const treeInput = this.tree.getInput(); if (Array.isArray(treeInput)) { - const hasExpandedItemInsideFolder = treeInput.some(folder => - Array.from(folder.children.entries()).some(([_id, child]) => - this.tree.hasNode(child) && !this.tree.isCollapsed(child))); - - if (hasExpandedItemInsideFolder) { + if (this.hasExpandedItemInsideFolder(treeInput)) { treeInput.forEach(folder => { folder.children.forEach(child => this.tree.hasNode(child) && this.tree.collapse(child)); }); @@ -702,6 +698,20 @@ export class ExplorerView extends ViewPane { this.tree.collapseAll(); } + private hasExpandedItemInsideFolder(treeInput: ExplorerItem[]): boolean { + for (const folder of treeInput) { + if (this.tree.hasNode(folder) && !this.tree.isCollapsed(folder)) { + for (const [, child] of folder.children.entries()) { + if (this.tree.hasNode(child) && !this.tree.isCollapsed(child)) { + return true; + } + } + } + } + + return false; + } + previousCompressedStat(): void { const focused = this.tree.getFocus(); if (!focused.length) { From c4ed5444acea24509be0c40bb5aeefafec6612fd Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 23 Dec 2019 17:38:36 +0100 Subject: [PATCH 214/241] debug start view: remove run button, so we have just Run and Debug fixes #87162 --- .../contrib/debug/browser/startView.ts | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index 840f81a3b2e..4d0f9f91191 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -14,7 +14,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { StartAction, RunAction, ConfigureAction } from 'vs/workbench/contrib/debug/browser/debugActions'; +import { StartAction, ConfigureAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -31,7 +31,6 @@ export class StartView extends ViewPane { static LABEL = localize('start', "Start"); private debugButton!: Button; - private runButton!: Button; private firstMessageContainer!: HTMLElement; private secondMessageContainer!: HTMLElement; private clickElement: HTMLElement | undefined; @@ -63,21 +62,13 @@ export class StartView extends ViewPane { const enabled = this.debuggerLabels.length > 0; this.debugButton.enabled = enabled; - this.runButton.enabled = enabled; const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID); - let debugLabel = this.debuggerLabels.length !== 1 ? localize('debug', "Debug") : localize('debugWith', "Debug with {0}", this.debuggerLabels[0]); + let debugLabel = this.debuggerLabels.length !== 1 ? localize('debug', "Run and Debug") : localize('debugWith', "Run and Debug {0}", this.debuggerLabels[0]); if (debugKeybinding) { debugLabel += ` (${debugKeybinding.getLabel()})`; } this.debugButton.label = debugLabel; - let runLabel = this.debuggerLabels.length !== 1 ? localize('run', "Run") : localize('runWith', "Run with {0}", this.debuggerLabels[0]); - const runKeybinding = this.keybindingService.lookupKeybinding(RunAction.ID); - if (runKeybinding) { - runLabel += ` (${runKeybinding.getLabel()})`; - } - this.runButton.label = runLabel; - const emptyWorkbench = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY; this.firstMessageContainer.innerHTML = ''; this.secondMessageContainer.innerHTML = ''; @@ -152,14 +143,7 @@ export class StartView extends ViewPane { })); attachButtonStyler(this.debugButton, this.themeService); - this.runButton = new Button(container); - this.runButton.label = localize('run', "Run"); - dom.addClass(container, 'debug-start-view'); - this._register(this.runButton.onDidClick(() => { - this.commandService.executeCommand(RunAction.ID); - })); - attachButtonStyler(this.runButton, this.themeService); this.secondMessageContainer = $('.section'); container.appendChild(this.secondMessageContainer); From 977a603f38959f2831182a19282e35b764233724 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 23 Dec 2019 17:48:25 +0100 Subject: [PATCH 215/241] debug start view label polish #87162 --- src/vs/workbench/contrib/debug/browser/debug.contribution.ts | 2 +- src/vs/workbench/contrib/debug/browser/startView.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 569c6e57d25..fa2b03fb4e1 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -81,7 +81,7 @@ class OpenDebugPanelAction extends TogglePanelAction { const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ id: VIEWLET_ID, - name: nls.localize('debugAndRun', "Debug and Run"), + name: nls.localize('runAndDebug', "Run and Debug"), ctorDescriptor: { ctor: DebugViewPaneContainer }, icon: 'codicon-debug-alt', order: 3 diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index 4d0f9f91191..84184580e11 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -76,12 +76,12 @@ export class StartView extends ViewPane { this.secondMessageContainer.appendChild(secondMessageElement); const setSecondMessage = () => { - secondMessageElement.textContent = localize('specifyHowToRun', "To further configure Debug and Run"); + secondMessageElement.textContent = localize('specifyHowToRun', "To further configure Run and Debug"); this.clickElement = this.createClickElement(localize('configure', " create a launch.json file."), () => this.commandService.executeCommand(ConfigureAction.ID)); this.secondMessageContainer.appendChild(this.clickElement); }; const setSecondMessageWithFolder = () => { - secondMessageElement.textContent = localize('noLaunchConfiguration', "To further configure Debug and Run, "); + secondMessageElement.textContent = localize('noLaunchConfiguration', "To further configure Run and Debug, "); this.clickElement = this.createClickElement(localize('openFolder', " open a folder"), () => this.dialogService.pickFolderAndOpen({ forceNewWindow: false })); this.secondMessageContainer.appendChild(this.clickElement); From 3abc086894ff5162ee49478fcbf8ad7051c89944 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 23 Dec 2019 17:50:50 +0100 Subject: [PATCH 216/241] debug start: better wording thanks to @eamodio --- src/vs/workbench/contrib/debug/browser/startView.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index 84184580e11..6e7aee1172d 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -76,12 +76,12 @@ export class StartView extends ViewPane { this.secondMessageContainer.appendChild(secondMessageElement); const setSecondMessage = () => { - secondMessageElement.textContent = localize('specifyHowToRun', "To further configure Run and Debug"); + secondMessageElement.textContent = localize('specifyHowToRun', "To customize Run and Debug"); this.clickElement = this.createClickElement(localize('configure', " create a launch.json file."), () => this.commandService.executeCommand(ConfigureAction.ID)); this.secondMessageContainer.appendChild(this.clickElement); }; const setSecondMessageWithFolder = () => { - secondMessageElement.textContent = localize('noLaunchConfiguration', "To further configure Run and Debug, "); + secondMessageElement.textContent = localize('noLaunchConfiguration', "To customize Run and Debug, "); this.clickElement = this.createClickElement(localize('openFolder', " open a folder"), () => this.dialogService.pickFolderAndOpen({ forceNewWindow: false })); this.secondMessageContainer.appendChild(this.clickElement); From 13a0145704a9c5414c6b71c89ca9d21c4542be08 Mon Sep 17 00:00:00 2001 From: rzj17 Date: Mon, 23 Dec 2019 21:27:56 +0000 Subject: [PATCH 217/241] Correct LocationLink link syntax --- src/vs/vscode.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index bd8edc7b91c..61ae5ed152b 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2318,7 +2318,7 @@ declare module 'vscode' { /** * The declaration of a symbol representation as one or many [locations](#Location) - * or [location links][#LocationLink]. + * or [location links](#LocationLink). */ export type Declaration = Location | Location[] | LocationLink[]; From a1875d15c8114b791f9a42dd0fd89b9c24c0bbc6 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 24 Dec 2019 11:01:26 +0100 Subject: [PATCH 218/241] fixes #87605 --- src/vs/workbench/contrib/debug/browser/debugCommands.ts | 7 ++++++- src/vs/workbench/contrib/debug/browser/rawDebugSession.ts | 8 +++++++- src/vs/workbench/contrib/debug/common/debugModel.ts | 4 ---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 473869c258c..7ea1d5ed7cf 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -303,9 +303,14 @@ export function registerCommands(): void { id: RESTART_FRAME_ID, handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { const debugService = accessor.get(IDebugService); + const notificationService = accessor.get(INotificationService); let frame = getFrame(debugService, context); if (frame) { - await frame.restart(); + try { + await frame.restart(); + } catch (e) { + notificationService.error(e); + } } } }); diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index f7bbb068852..c26fcda85d7 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -623,7 +623,13 @@ export class RawDebugSession implements IDisposable { } }); } - }).then(undefined, err => Promise.reject(this.handleErrorResponse(err))); + }).then(res => { + if (typeof res.success === 'boolean' && res.success === false) { + return Promise.reject(this.handleErrorResponse(res)); + } + + return res as R; + }, err => Promise.reject(this.handleErrorResponse(err))); } private handleErrorResponse(errorResponse: DebugProtocol.Response): Error { diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 0eef4b21d26..d4e7d5310d5 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -160,10 +160,6 @@ export class ExpressionContainer implements IExpressionContainer { this.session = session; try { const response = await session.evaluate(expression, stackFrame ? stackFrame.frameId : undefined, context); - if (response && response.success === false) { - this.value = response.message || ''; - return false; - } if (response && response.body) { this.value = response.body.result || ''; From 7531960f4e420e4e0ded48996b6ee5633b1e1266 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 24 Dec 2019 11:35:29 +0100 Subject: [PATCH 219/241] fixes #83705 --- src/vs/workbench/contrib/debug/browser/debugService.ts | 2 +- .../workbench/contrib/debug/browser/debugTaskRunner.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index e11852d3e88..62cbe5f78c9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -675,13 +675,13 @@ export class DebugService implements IDebugService { } stopSession(session: IDebugSession): Promise { - if (session) { return session.terminate(); } const sessions = this.model.getSessions(); if (sessions.length === 0) { + this.taskRunner.cancel(); this.endInitializingState(); } diff --git a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts index 19e63db2dac..f4e6bad2774 100644 --- a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts +++ b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts @@ -38,6 +38,8 @@ export const enum TaskRunResult { export class DebugTaskRunner { + private canceled = false; + constructor( @ITaskService private readonly taskService: ITaskService, @IMarkerService private readonly markerService: IMarkerService, @@ -46,9 +48,17 @@ export class DebugTaskRunner { @IDialogService private readonly dialogService: IDialogService, ) { } + cancel(): void { + this.canceled = true; + } + async runTaskAndCheckErrors(root: IWorkspaceFolder | IWorkspace | undefined, taskId: string | TaskIdentifier | undefined, onError: (msg: string, actions: IAction[]) => Promise): Promise { try { + this.canceled = false; const taskSummary = await this.runTask(root, taskId); + if (this.canceled) { + return TaskRunResult.Failure; + } const errorCount = taskId ? this.markerService.getStatistics().errors : 0; const successExitCode = taskSummary && taskSummary.exitCode === 0; From 76c3af8217e73b3c90e430290f12e3b53a6dffcf Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 24 Dec 2019 12:03:11 +0100 Subject: [PATCH 220/241] fixes #83992 --- src/vs/workbench/contrib/debug/browser/debugService.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 62cbe5f78c9..de2993cc598 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -290,7 +290,7 @@ export class DebugService implements IDebugService { "Compound must have \"configurations\" attribute set in order to start multiple configurations.")); } if (compound.preLaunchTask) { - const taskResult = await this.taskRunner.runTaskAndCheckErrors(launch?.workspace || this.contextService.getWorkspace(), compound.preLaunchTask, this.showError); + const taskResult = await this.taskRunner.runTaskAndCheckErrors(launch?.workspace || this.contextService.getWorkspace(), compound.preLaunchTask, (msg, actions) => this.showError(msg, actions)); if (taskResult === TaskRunResult.Failure) { this.endInitializingState(); return false; @@ -410,8 +410,8 @@ export class DebugService implements IDebugService { return false; } - const workspace = launch ? launch.workspace : this.contextService.getWorkspace(); - const taskResult = await this.taskRunner.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask, this.showError); + const workspace = launch?.workspace || this.contextService.getWorkspace(); + const taskResult = await this.taskRunner.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask, (msg, actions) => this.showError(msg, actions)); if (taskResult === TaskRunResult.Success) { return this.doCreateSession(launch?.workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options); } @@ -591,7 +591,7 @@ export class DebugService implements IDebugService { } await this.taskRunner.runTask(session.root, session.configuration.postDebugTask); - return this.taskRunner.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask, this.showError); + return this.taskRunner.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask, (msg, actions) => this.showError(msg, actions)); }; const extensionDebugSession = getExtensionHostDebugSession(session); From eef751dde2ab51b71185e8e180211f2df3bdb889 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 24 Dec 2019 15:06:07 +0100 Subject: [PATCH 221/241] debug: fix adding to watch --- src/vs/workbench/contrib/debug/browser/debugService.ts | 6 ++++-- src/vs/workbench/contrib/debug/common/debugModel.ts | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index de2993cc598..215e33e08a5 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -775,9 +775,11 @@ export class DebugService implements IDebugService { //---- watches - addWatchExpression(name: string): void { + addWatchExpression(name?: string): void { const we = this.model.addWatchExpression(name); - this.viewModel.setSelectedExpression(we); + if (!name) { + this.viewModel.setSelectedExpression(we); + } this.storeWatchExpressions(); } diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index d4e7d5310d5..1fafbec02bf 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -1182,8 +1182,8 @@ export class DebugModel implements IDebugModel { return this.watchExpressions; } - addWatchExpression(name: string): IExpression { - const we = new Expression(name); + addWatchExpression(name?: string): IExpression { + const we = new Expression(name || ''); this.watchExpressions.push(we); this._onDidChangeWatchExpressions.fire(we); From 484fa870cff23e77765d987af0da34650c1b5d60 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 24 Dec 2019 15:27:17 +0100 Subject: [PATCH 222/241] callStack view: do not hide state if the debugger does not support restart frame --- src/vs/workbench/contrib/debug/browser/media/debugViewlet.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 235181c8108..ad02435a58b 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -169,7 +169,7 @@ text-transform: uppercase; } -.debug-viewlet .debug-call-stack .monaco-list-row:hover .state { +.debug-viewlet .debug-call-stack .monaco-list-row:hover .stack-frame.has-actions .state { display: none; } From 1f3ff416b59cc9a630286e992e1155bbf4120524 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 24 Dec 2019 15:38:17 +0100 Subject: [PATCH 223/241] debug: minor fix in callstack --- .../workbench/contrib/debug/browser/callStackView.ts | 12 ++++++------ .../contrib/debug/browser/media/debugViewlet.css | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 17f8cbd7da1..990db3c9a84 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -93,7 +93,7 @@ export class CallStackView extends ViewPane { if (thread && thread.stoppedDetails) { this.pauseMessageLabel.textContent = thread.stoppedDetails.description || nls.localize('debugStopped', "Paused on {0}", thread.stoppedDetails.reason || ''); this.pauseMessageLabel.title = thread.stoppedDetails.text || ''; - dom.toggleClass(this.pauseMessageLabel, 'exception', thread.stoppedDetails.reason === 'exception'); + this.pauseMessageLabel.toggleAttribute('exception', thread.stoppedDetails.reason === 'exception'); this.pauseMessage.hidden = false; if (this.toolbar) { this.toolbar.setActions([])(); @@ -511,11 +511,11 @@ class StackFramesRenderer implements ITreeRenderer, index: number, data: IStackFrameTemplateData): void { const stackFrame = element.element; - dom.toggleClass(data.stackFrame, 'disabled', !stackFrame.source || !stackFrame.source.available || isDeemphasized(stackFrame)); - dom.toggleClass(data.stackFrame, 'label', stackFrame.presentationHint === 'label'); - dom.toggleClass(data.stackFrame, 'subtle', stackFrame.presentationHint === 'subtle'); - const hasActions = stackFrame.thread.session.capabilities.supportsRestartFrame && stackFrame.presentationHint !== 'label' && stackFrame.presentationHint !== 'subtle'; - dom.toggleClass(data.stackFrame, 'has-actions', hasActions); + data.stackFrame.toggleAttribute('disabled', !stackFrame.source || !stackFrame.source.available || isDeemphasized(stackFrame)); + data.stackFrame.toggleAttribute('label', stackFrame.presentationHint === 'label'); + data.stackFrame.toggleAttribute('subtle', stackFrame.presentationHint === 'subtle'); + const hasActions = !!stackFrame.thread.session.capabilities.supportsRestartFrame && stackFrame.presentationHint !== 'label' && stackFrame.presentationHint !== 'subtle'; + data.stackFrame.toggleAttribute('has-actions', hasActions); data.file.title = stackFrame.source.inMemory ? stackFrame.source.uri.path : this.labelService.getUriLabel(stackFrame.source.uri); if (stackFrame.source.raw.origin) { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index ad02435a58b..235181c8108 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -169,7 +169,7 @@ text-transform: uppercase; } -.debug-viewlet .debug-call-stack .monaco-list-row:hover .stack-frame.has-actions .state { +.debug-viewlet .debug-call-stack .monaco-list-row:hover .state { display: none; } From a73867d8cb932bbd327f022ce226542464ad6c7b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 24 Dec 2019 16:15:23 +0100 Subject: [PATCH 224/241] replace 'declare var...' with 'declare const...', related #87644 --- src/vs/base/common/buffer.ts | 2 +- src/vs/base/parts/ipc/common/ipc.ts | 2 +- src/vs/editor/browser/controller/mouseTarget.ts | 2 +- src/vs/editor/common/core/stringBuilder.ts | 2 +- src/vs/editor/common/services/editorSimpleWorker.ts | 2 +- src/vs/platform/instantiation/common/instantiationService.ts | 2 +- .../test/electron-browser/quickopen.perf.integrationTest.ts | 2 +- .../test/electron-browser/textsearch.perf.integrationTest.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index 25371f5ec79..1126f89c7d8 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -6,7 +6,7 @@ import * as strings from 'vs/base/common/strings'; import * as streams from 'vs/base/common/stream'; -declare var Buffer: any; +declare const Buffer: any; const hasBuffer = (typeof Buffer !== 'undefined'); const hasTextEncoder = (typeof TextEncoder !== 'undefined'); diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 603414de3aa..662a29a9d86 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -187,7 +187,7 @@ const BufferPresets = { Object: createOneByteBuffer(DataType.Object), }; -declare var Buffer: any; +declare const Buffer: any; const hasBuffer = (typeof Buffer !== 'undefined'); function serialize(writer: IWriter, data: any): void { diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 7e7e84a613e..c38191a19c6 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -79,7 +79,7 @@ interface IETextRange { setEndPoint(how: string, SourceRange: IETextRange): void; } -declare var IETextRange: { +declare const IETextRange: { prototype: IETextRange; new(): IETextRange; }; diff --git a/src/vs/editor/common/core/stringBuilder.ts b/src/vs/editor/common/core/stringBuilder.ts index 36cbe87f54e..661fd6642e2 100644 --- a/src/vs/editor/common/core/stringBuilder.ts +++ b/src/vs/editor/common/core/stringBuilder.ts @@ -5,7 +5,7 @@ import * as strings from 'vs/base/common/strings'; -declare var TextDecoder: any; // TODO@TypeScript +declare const TextDecoder: any; // TODO@TypeScript interface TextDecoder { decode(view: Uint16Array): string; } diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 01ca792e497..4960aa3984a 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -322,7 +322,7 @@ export interface IForeignModuleFactory { (ctx: IWorkerContext, createData: any): any; } -declare var require: any; +declare const require: any; /** * @internal diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index f4b681204c8..86f21d18e76 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -16,7 +16,7 @@ const _enableTracing = false; // PROXY // Ghetto-declare of the global Proxy object. This isn't the proper way // but allows us to run this code in the browser without IE11. -declare var Proxy: any; +declare const Proxy: any; const _canUseProxy = typeof Proxy === 'function'; class CyclicDependencyError extends Error { diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts index d83eae09e5d..5c3e43ffb5b 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -49,7 +49,7 @@ namespace Timer { } } -declare var __dirname: string; +// declare var __dirname: string; // Checkout sources to run against: // git clone --separate-git-dir=testGit --no-checkout --single-branch https://chromium.googlesource.com/chromium/src testWorkspace diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index ca92d4799fd..e79af8a3d3d 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -36,7 +36,7 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/textRe import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -declare var __dirname: string; +// declare var __dirname: string; // Checkout sources to run against: // git clone --separate-git-dir=testGit --no-checkout --single-branch https://chromium.googlesource.com/chromium/src testWorkspace From ff5f581425da6230b6f9216ecf19abf6c9d285a6 Mon Sep 17 00:00:00 2001 From: isidor Date: Tue, 24 Dec 2019 17:09:54 +0100 Subject: [PATCH 225/241] explorer view: minor polish --- .../files/browser/views/explorerView.ts | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index f2b2175cc08..52008b759ec 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -64,6 +64,20 @@ interface IExplorerViewStyles { listDropBackground?: Color; } +function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree, treeInput: ExplorerItem[]): boolean { + for (const folder of treeInput) { + if (tree.hasNode(folder) && !tree.isCollapsed(folder)) { + for (const [, child] of folder.children.entries()) { + if (tree.hasNode(child) && !tree.isCollapsed(child)) { + return true; + } + } + } + } + + return false; +} + export class ExplorerView extends ViewPane { static readonly ID: string = 'workbench.explorer.fileView'; static readonly TREE_VIEW_STATE_STORAGE_KEY: string = 'workbench.explorer.treeViewState'; @@ -139,7 +153,7 @@ export class ExplorerView extends ViewPane { return this.name; } - set title(value: string) { + set title(_: string) { // noop } @@ -570,8 +584,6 @@ export class ExplorerView extends ViewPane { return DOM.getLargestChildWidth(parentNode, childNodes); } - // private didLoad = false; - private setTreeInput(): Promise { if (!this.isBodyVisible()) { this.shouldRefresh = true; @@ -686,7 +698,7 @@ export class ExplorerView extends ViewPane { collapseAll(): void { const treeInput = this.tree.getInput(); if (Array.isArray(treeInput)) { - if (this.hasExpandedItemInsideFolder(treeInput)) { + if (hasExpandedRootChild(this.tree, treeInput)) { treeInput.forEach(folder => { folder.children.forEach(child => this.tree.hasNode(child) && this.tree.collapse(child)); }); @@ -698,20 +710,6 @@ export class ExplorerView extends ViewPane { this.tree.collapseAll(); } - private hasExpandedItemInsideFolder(treeInput: ExplorerItem[]): boolean { - for (const folder of treeInput) { - if (this.tree.hasNode(folder) && !this.tree.isCollapsed(folder)) { - for (const [, child] of folder.children.entries()) { - if (this.tree.hasNode(child) && !this.tree.isCollapsed(child)) { - return true; - } - } - } - } - - return false; - } - previousCompressedStat(): void { const focused = this.tree.getFocus(); if (!focused.length) { From 64fd341760fdeb688bb13a9d1b5805291d4ff3a4 Mon Sep 17 00:00:00 2001 From: Pen Tree Date: Thu, 26 Dec 2019 13:16:22 +0800 Subject: [PATCH 226/241] Fix a typo in vscode.d.ts --- src/vs/vscode.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 61ae5ed152b..f4c8fa12dba 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -4298,7 +4298,7 @@ declare module 'vscode' { * * The *effective* value (returned by [`get`](#WorkspaceConfiguration.get)) * is computed like this: `defaultValue` overridden by `globalValue`, - * `globalValue` overridden by `workspaceValue`. `workspaceValue` overwridden by `workspaceFolderValue`. + * `globalValue` overridden by `workspaceValue`. `workspaceValue` overridden by `workspaceFolderValue`. * Refer to [Settings](https://code.visualstudio.com/docs/getstarted/settings) * for more information. * From 7909462634a44f09e7f805bdeb9c082fd533e7e5 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 27 Dec 2019 08:57:03 +0100 Subject: [PATCH 227/241] tslint - rewrite globals rule to not use tslint (#87754) * tslint - rewrite globals rule to not use tslint * comments --- .github/workflows/ci.yml | 6 + .../darwin/continuous-build-darwin.yml | 3 + .../linux/continuous-build-linux.yml | 3 + build/azure-pipelines/product-compile.yml | 3 +- .../win32/continuous-build-win32.yml | 3 + build/lib/globalsLinter.js | 176 +++++++++++++++ build/lib/globalsLinter.ts | 209 ++++++++++++++++++ build/lib/tslint/abstractGlobalsRule.js | 45 ---- build/lib/tslint/abstractGlobalsRule.ts | 57 ----- build/lib/tslint/noDomGlobalsRule.js | 34 --- build/lib/tslint/noDomGlobalsRule.ts | 45 ---- build/lib/tslint/noNodejsGlobalsRule.js | 41 ---- build/lib/tslint/noNodejsGlobalsRule.ts | 52 ----- build/package.json | 1 + package.json | 1 + tslint.json | 42 ---- 16 files changed, 404 insertions(+), 317 deletions(-) create mode 100644 build/lib/globalsLinter.js create mode 100644 build/lib/globalsLinter.ts delete mode 100644 build/lib/tslint/abstractGlobalsRule.js delete mode 100644 build/lib/tslint/abstractGlobalsRule.ts delete mode 100644 build/lib/tslint/noDomGlobalsRule.js delete mode 100644 build/lib/tslint/noDomGlobalsRule.ts delete mode 100644 build/lib/tslint/noNodejsGlobalsRule.js delete mode 100644 build/lib/tslint/noNodejsGlobalsRule.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55415410f7c..e9a2a3e71bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,8 @@ jobs: name: Run TSLint Checks - run: yarn monaco-compile-check name: Run Monaco Editor Checks + - run: yarn valid-globals-check + name: Run Valid Globals Checks - run: yarn compile name: Compile Sources - run: yarn download-builtin-extensions @@ -73,6 +75,8 @@ jobs: name: Run TSLint Checks - run: yarn monaco-compile-check name: Run Monaco Editor Checks + - run: yarn valid-globals-check + name: Run Valid Globals Checks - run: yarn compile name: Compile Sources - run: yarn download-builtin-extensions @@ -102,6 +106,8 @@ jobs: name: Run TSLint Checks - run: yarn monaco-compile-check name: Run Monaco Editor Checks + - run: yarn valid-globals-check + name: Run Valid Globals Checks - run: yarn compile name: Compile Sources - run: yarn download-builtin-extensions diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml index c98aa3770de..1609e4a0027 100644 --- a/build/azure-pipelines/darwin/continuous-build-darwin.yml +++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml @@ -32,6 +32,9 @@ steps: - script: | yarn monaco-compile-check displayName: Run Monaco Editor Checks +- script: | + yarn valid-globals-check + displayName: Run Valid Globals Checks - script: | yarn compile displayName: Compile Sources diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index 0f611bd439d..649c12593da 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -40,6 +40,9 @@ steps: - script: | yarn monaco-compile-check displayName: Run Monaco Editor Checks +- script: | + yarn valid-globals-check + displayName: Run Valid Globals Checks - script: | yarn compile displayName: Compile Sources diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 8029f8a5661..9ec01fd5db1 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -91,7 +91,8 @@ steps: yarn gulp hygiene --skip-tslint yarn gulp tslint yarn monaco-compile-check - displayName: Run hygiene, tslint and monaco compile checks + yarn valid-globals-check + displayName: Run hygiene, tslint, monaco compile & valid globals checks condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml index 9351bafa0bd..050a967629b 100644 --- a/build/azure-pipelines/win32/continuous-build-win32.yml +++ b/build/azure-pipelines/win32/continuous-build-win32.yml @@ -37,6 +37,9 @@ steps: - powershell: | yarn monaco-compile-check displayName: Run Monaco Editor Checks +- script: | + yarn valid-globals-check + displayName: Run Valid Globals Checks - powershell: | yarn compile displayName: Compile Sources diff --git a/build/lib/globalsLinter.js b/build/lib/globalsLinter.js new file mode 100644 index 00000000000..cfcb05c2689 --- /dev/null +++ b/build/lib/globalsLinter.js @@ -0,0 +1,176 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const ts = require("typescript"); +const fs_1 = require("fs"); +const path_1 = require("path"); +const minimatch_1 = require("minimatch"); +// +// ############################################################################################# +// +// A custom typescript linter for the specific task of detecting the use of certain globals in a +// layer that does not allow the use. For example: +// - using DOM globals in common/node/electron-main layer (e.g. HTMLElement) +// - using node.js globals in common/browser layer (e.g. process) +// +// Make changes to below RULES to lift certain files from these checks only if absolutely needed +// +// ############################################################################################# +// +const RULES = { + "no-nodejs-globals": [ + { + "target": "**/vs/**/test/{common,browser}/**", + "allowed": [ + "process", + "Buffer", + "__filename", + "__dirname" + ] + }, + { + "target": "**/vs/workbench/api/common/extHostExtensionService.ts", + "allowed": [ + "global" // -> safe access to 'global' + ] + }, + { + "target": "**/vs/**/{common,browser}/**", + "allowed": [ /* none */] + } + ], + "no-dom-globals": [ + { + "target": "**/vs/base/parts/quickopen/common/quickOpen.ts", + "allowed": [ + "HTMLElement" // quick open will be replaced with a different widget soon + ] + }, + { + "target": "**/vs/**/test/{common,node,electron-main}/**", + "allowed": [ + "document", + "HTMLElement", + "createElement" + ] + }, + { + "target": "**/vs/**/{common,node,electron-main}/**", + "allowed": [ /* none */] + } + ] +}; +const TS_CONFIG_PATH = path_1.join(__dirname, '../../', 'src', 'tsconfig.json'); +const DOM_GLOBALS_DEFINITION = 'lib.dom.d.ts'; +const DISALLOWED_DOM_GLOBALS = [ + "window", + "document", + "HTMLElement", + "createElement" +]; +const NODE_GLOBALS_DEFINITION = '@types/node'; +const DISALLOWED_NODE_GLOBALS = [ + // https://nodejs.org/api/globals.html#globals_global_objects + "NodeJS", + "Buffer", + "__dirname", + "__filename", + "clearImmediate", + "exports", + "global", + "module", + "process", + "setImmediate" +]; +let hasErrors = false; +function checkFile(program, sourceFile, rule) { + checkNode(sourceFile); + function checkNode(node) { + if (node.kind !== ts.SyntaxKind.Identifier) { + return ts.forEachChild(node, checkNode); // recurse down + } + const text = node.getText(sourceFile); + if (!rule.disallowedGlobals.some(disallowedGlobal => disallowedGlobal === text)) { + return; // only if disallowed + } + if (rule.allowedGlobals.some(allowed => allowed === text)) { + return; // override + } + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(node); + if (symbol) { + const declarations = symbol.declarations; + if (Array.isArray(declarations) && symbol.declarations.some(declaration => { + if (declaration) { + const parent = declaration.parent; + if (parent) { + const sourceFile = parent.getSourceFile(); + if (sourceFile) { + const fileName = sourceFile.fileName; + if (fileName && fileName.indexOf(rule.definition) >= 0) { + return true; + } + } + } + } + return false; + })) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + console.log(`build/lib/globalsLinter.ts: Cannot use global '${text}' in ${sourceFile.fileName} (${line + 1},${character + 1})`); + hasErrors = true; + } + } + } +} +function createProgram(tsconfigPath) { + const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile); + const configHostParser = { fileExists: fs_1.existsSync, readDirectory: ts.sys.readDirectory, readFile: file => fs_1.readFileSync(file, "utf8"), useCaseSensitiveFileNames: process.platform === 'linux' }; + const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, path_1.resolve(path_1.dirname(tsconfigPath)), { noEmit: true }); + const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true); + return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost); +} +// +// Create program and start checking +// +const program = createProgram(TS_CONFIG_PATH); +for (const sourceFile of program.getSourceFiles()) { + let noDomGlobalsLinter = undefined; + let noNodeJSGlobalsLinter = undefined; + for (const rules of RULES["no-dom-globals"]) { + if (minimatch_1.match([sourceFile.fileName], rules.target).length > 0) { + noDomGlobalsLinter = { allowed: rules.allowed }; + break; + } + } + for (const rules of RULES["no-nodejs-globals"]) { + if (minimatch_1.match([sourceFile.fileName], rules.target).length > 0) { + noNodeJSGlobalsLinter = { allowed: rules.allowed }; + break; + } + } + if (!noDomGlobalsLinter && !noNodeJSGlobalsLinter) { + continue; // no rule to run + } + // No DOM Globals + if (noDomGlobalsLinter) { + checkFile(program, sourceFile, { + definition: DOM_GLOBALS_DEFINITION, + disallowedGlobals: DISALLOWED_DOM_GLOBALS, + allowedGlobals: noDomGlobalsLinter.allowed + }); + } + // No node.js Globals + if (noNodeJSGlobalsLinter) { + checkFile(program, sourceFile, { + definition: NODE_GLOBALS_DEFINITION, + disallowedGlobals: DISALLOWED_NODE_GLOBALS, + allowedGlobals: noNodeJSGlobalsLinter.allowed + }); + } +} +if (hasErrors) { + process.exit(1); +} diff --git a/build/lib/globalsLinter.ts b/build/lib/globalsLinter.ts new file mode 100644 index 00000000000..1f931ec6e0e --- /dev/null +++ b/build/lib/globalsLinter.ts @@ -0,0 +1,209 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as ts from "typescript"; +import { readFileSync, existsSync } from "fs"; +import { resolve, dirname, join } from "path"; +import { match } from 'minimatch'; + +// +// ############################################################################################# +// +// A custom typescript linter for the specific task of detecting the use of certain globals in a +// layer that does not allow the use. For example: +// - using DOM globals in common/node/electron-main layer (e.g. HTMLElement) +// - using node.js globals in common/browser layer (e.g. process) +// +// Make changes to below RULES to lift certain files from these checks only if absolutely needed +// +// ############################################################################################# +// + +const RULES = { + "no-nodejs-globals": [ + { + "target": "**/vs/**/test/{common,browser}/**", + "allowed": [ // -> less strict for test files + "process", + "Buffer", + "__filename", + "__dirname" + ] + }, + { + "target": "**/vs/workbench/api/common/extHostExtensionService.ts", + "allowed": [ + "global" // -> safe access to 'global' + ] + }, + { + "target": "**/vs/**/{common,browser}/**", + "allowed": [ /* none */] + } + ], + "no-dom-globals": [ + { + "target": "**/vs/base/parts/quickopen/common/quickOpen.ts", + "allowed": [ + "HTMLElement" // quick open will be replaced with a different widget soon + ] + }, + { + "target": "**/vs/**/test/{common,node,electron-main}/**", + "allowed": [ // -> less strict for test files + "document", + "HTMLElement", + "createElement" + ] + }, + { + "target": "**/vs/**/{common,node,electron-main}/**", + "allowed": [ /* none */] + } + ] +}; + +const TS_CONFIG_PATH = join(__dirname, '../../', 'src', 'tsconfig.json'); + +const DOM_GLOBALS_DEFINITION = 'lib.dom.d.ts'; + +const DISALLOWED_DOM_GLOBALS = [ + "window", + "document", + "HTMLElement", + "createElement" +]; + +const NODE_GLOBALS_DEFINITION = '@types/node'; + +const DISALLOWED_NODE_GLOBALS = [ + // https://nodejs.org/api/globals.html#globals_global_objects + "NodeJS", + "Buffer", + "__dirname", + "__filename", + "clearImmediate", + "exports", + "global", + "module", + "process", + "setImmediate" +]; + +interface IRule { + definition: string; + disallowedGlobals: string[]; + allowedGlobals: string[]; +} + +let hasErrors = false; + +function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) { + checkNode(sourceFile); + + function checkNode(node: ts.Node): void { + if (node.kind !== ts.SyntaxKind.Identifier) { + return ts.forEachChild(node, checkNode); // recurse down + } + + const text = node.getText(sourceFile); + + if (!rule.disallowedGlobals.some(disallowedGlobal => disallowedGlobal === text)) { + return; // only if disallowed + } + + if (rule.allowedGlobals.some(allowed => allowed === text)) { + return; // override + } + + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(node); + if (symbol) { + const declarations = symbol.declarations; + if (Array.isArray(declarations) && symbol.declarations.some(declaration => { + if (declaration) { + const parent = declaration.parent; + if (parent) { + const sourceFile = parent.getSourceFile(); + if (sourceFile) { + const fileName = sourceFile.fileName; + if (fileName && fileName.indexOf(rule.definition) >= 0) { + return true; + } + } + } + } + + return false; + })) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + console.log(`build/lib/globalsLinter.ts: Cannot use global '${text}' in ${sourceFile.fileName} (${line + 1},${character + 1})`); + + hasErrors = true; + } + } + } +} + +function createProgram(tsconfigPath: string): ts.Program { + const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile); + + const configHostParser: ts.ParseConfigHost = { fileExists: existsSync, readDirectory: ts.sys.readDirectory, readFile: file => readFileSync(file, "utf8"), useCaseSensitiveFileNames: process.platform === 'linux' }; + const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, resolve(dirname(tsconfigPath)), { noEmit: true }); + + const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true); + + return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost); +} + +// +// Create program and start checking +// +const program = createProgram(TS_CONFIG_PATH); + +for (const sourceFile of program.getSourceFiles()) { + let noDomGlobalsLinter: { allowed: string[] } | undefined = undefined; + let noNodeJSGlobalsLinter: { allowed: string[] } | undefined = undefined; + + for (const rules of RULES["no-dom-globals"]) { + if (match([sourceFile.fileName], rules.target).length > 0) { + noDomGlobalsLinter = { allowed: rules.allowed }; + break; + } + } + + for (const rules of RULES["no-nodejs-globals"]) { + if (match([sourceFile.fileName], rules.target).length > 0) { + noNodeJSGlobalsLinter = { allowed: rules.allowed }; + break; + } + } + + if (!noDomGlobalsLinter && !noNodeJSGlobalsLinter) { + continue; // no rule to run + } + + // No DOM Globals + if (noDomGlobalsLinter) { + checkFile(program, sourceFile, { + definition: DOM_GLOBALS_DEFINITION, + disallowedGlobals: DISALLOWED_DOM_GLOBALS, + allowedGlobals: noDomGlobalsLinter.allowed + }); + } + + // No node.js Globals + if (noNodeJSGlobalsLinter) { + checkFile(program, sourceFile, { + definition: NODE_GLOBALS_DEFINITION, + disallowedGlobals: DISALLOWED_NODE_GLOBALS, + allowedGlobals: noNodeJSGlobalsLinter.allowed + }); + } +} + +if (hasErrors) { + process.exit(1); +} diff --git a/build/lib/tslint/abstractGlobalsRule.js b/build/lib/tslint/abstractGlobalsRule.js deleted file mode 100644 index 1566c0aa576..00000000000 --- a/build/lib/tslint/abstractGlobalsRule.js +++ /dev/null @@ -1,45 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const Lint = require("tslint"); -class AbstractGlobalsRuleWalker extends Lint.RuleWalker { - constructor(file, program, opts, _config) { - super(file, opts); - this.program = program; - this._config = _config; - } - visitIdentifier(node) { - if (this.getDisallowedGlobals().some(disallowedGlobal => disallowedGlobal === node.text)) { - if (this._config.allowed && this._config.allowed.some(allowed => allowed === node.text)) { - return; // override - } - const checker = this.program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(node); - if (symbol) { - const declarations = symbol.declarations; - if (Array.isArray(declarations) && symbol.declarations.some(declaration => { - if (declaration) { - const parent = declaration.parent; - if (parent) { - const sourceFile = parent.getSourceFile(); - if (sourceFile) { - const fileName = sourceFile.fileName; - if (fileName && fileName.indexOf(this.getDefinitionPattern()) >= 0) { - return true; - } - } - } - } - return false; - })) { - this.addFailureAtNode(node, `Cannot use global '${node.text}' in '${this._config.target}'`); - } - } - } - super.visitIdentifier(node); - } -} -exports.AbstractGlobalsRuleWalker = AbstractGlobalsRuleWalker; diff --git a/build/lib/tslint/abstractGlobalsRule.ts b/build/lib/tslint/abstractGlobalsRule.ts deleted file mode 100644 index 543720455c3..00000000000 --- a/build/lib/tslint/abstractGlobalsRule.ts +++ /dev/null @@ -1,57 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as ts from 'typescript'; -import * as Lint from 'tslint'; - -interface AbstractGlobalsRuleConfig { - target: string; - allowed: string[]; -} - -export abstract class AbstractGlobalsRuleWalker extends Lint.RuleWalker { - - constructor(file: ts.SourceFile, private program: ts.Program, opts: Lint.IOptions, private _config: AbstractGlobalsRuleConfig) { - super(file, opts); - } - - protected abstract getDisallowedGlobals(): string[]; - - protected abstract getDefinitionPattern(): string; - - visitIdentifier(node: ts.Identifier) { - if (this.getDisallowedGlobals().some(disallowedGlobal => disallowedGlobal === node.text)) { - if (this._config.allowed && this._config.allowed.some(allowed => allowed === node.text)) { - return; // override - } - - const checker = this.program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(node); - if (symbol) { - const declarations = symbol.declarations; - if (Array.isArray(declarations) && symbol.declarations.some(declaration => { - if (declaration) { - const parent = declaration.parent; - if (parent) { - const sourceFile = parent.getSourceFile(); - if (sourceFile) { - const fileName = sourceFile.fileName; - if (fileName && fileName.indexOf(this.getDefinitionPattern()) >= 0) { - return true; - } - } - } - } - - return false; - })) { - this.addFailureAtNode(node, `Cannot use global '${node.text}' in '${this._config.target}'`); - } - } - } - - super.visitIdentifier(node); - } -} diff --git a/build/lib/tslint/noDomGlobalsRule.js b/build/lib/tslint/noDomGlobalsRule.js deleted file mode 100644 index a83ac8f7f59..00000000000 --- a/build/lib/tslint/noDomGlobalsRule.js +++ /dev/null @@ -1,34 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const Lint = require("tslint"); -const minimatch = require("minimatch"); -const abstractGlobalsRule_1 = require("./abstractGlobalsRule"); -class Rule extends Lint.Rules.TypedRule { - applyWithProgram(sourceFile, program) { - const configs = this.getOptions().ruleArguments; - for (const config of configs) { - if (minimatch(sourceFile.fileName, config.target)) { - return this.applyWithWalker(new NoDOMGlobalsRuleWalker(sourceFile, program, this.getOptions(), config)); - } - } - return []; - } -} -exports.Rule = Rule; -class NoDOMGlobalsRuleWalker extends abstractGlobalsRule_1.AbstractGlobalsRuleWalker { - getDefinitionPattern() { - return 'lib.dom.d.ts'; - } - getDisallowedGlobals() { - // intentionally not complete - return [ - "window", - "document", - "HTMLElement" - ]; - } -} diff --git a/build/lib/tslint/noDomGlobalsRule.ts b/build/lib/tslint/noDomGlobalsRule.ts deleted file mode 100644 index df9e67bf78b..00000000000 --- a/build/lib/tslint/noDomGlobalsRule.ts +++ /dev/null @@ -1,45 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as ts from 'typescript'; -import * as Lint from 'tslint'; -import * as minimatch from 'minimatch'; -import { AbstractGlobalsRuleWalker } from './abstractGlobalsRule'; - -interface NoDOMGlobalsRuleConfig { - target: string; - allowed: string[]; -} - -export class Rule extends Lint.Rules.TypedRule { - - applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { - const configs = this.getOptions().ruleArguments; - - for (const config of configs) { - if (minimatch(sourceFile.fileName, config.target)) { - return this.applyWithWalker(new NoDOMGlobalsRuleWalker(sourceFile, program, this.getOptions(), config)); - } - } - - return []; - } -} - -class NoDOMGlobalsRuleWalker extends AbstractGlobalsRuleWalker { - - getDefinitionPattern(): string { - return 'lib.dom.d.ts'; - } - - getDisallowedGlobals(): string[] { - // intentionally not complete - return [ - "window", - "document", - "HTMLElement" - ]; - } -} diff --git a/build/lib/tslint/noNodejsGlobalsRule.js b/build/lib/tslint/noNodejsGlobalsRule.js deleted file mode 100644 index 8c36fa342c2..00000000000 --- a/build/lib/tslint/noNodejsGlobalsRule.js +++ /dev/null @@ -1,41 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const Lint = require("tslint"); -const minimatch = require("minimatch"); -const abstractGlobalsRule_1 = require("./abstractGlobalsRule"); -class Rule extends Lint.Rules.TypedRule { - applyWithProgram(sourceFile, program) { - const configs = this.getOptions().ruleArguments; - for (const config of configs) { - if (minimatch(sourceFile.fileName, config.target)) { - return this.applyWithWalker(new NoNodejsGlobalsRuleWalker(sourceFile, program, this.getOptions(), config)); - } - } - return []; - } -} -exports.Rule = Rule; -class NoNodejsGlobalsRuleWalker extends abstractGlobalsRule_1.AbstractGlobalsRuleWalker { - getDefinitionPattern() { - return '@types/node'; - } - getDisallowedGlobals() { - // https://nodejs.org/api/globals.html#globals_global_objects - return [ - "NodeJS", - "Buffer", - "__dirname", - "__filename", - "clearImmediate", - "exports", - "global", - "module", - "process", - "setImmediate" - ]; - } -} diff --git a/build/lib/tslint/noNodejsGlobalsRule.ts b/build/lib/tslint/noNodejsGlobalsRule.ts deleted file mode 100644 index 7e5767d8570..00000000000 --- a/build/lib/tslint/noNodejsGlobalsRule.ts +++ /dev/null @@ -1,52 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as ts from 'typescript'; -import * as Lint from 'tslint'; -import * as minimatch from 'minimatch'; -import { AbstractGlobalsRuleWalker } from './abstractGlobalsRule'; - -interface NoNodejsGlobalsConfig { - target: string; - allowed: string[]; -} - -export class Rule extends Lint.Rules.TypedRule { - - applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { - const configs = this.getOptions().ruleArguments; - - for (const config of configs) { - if (minimatch(sourceFile.fileName, config.target)) { - return this.applyWithWalker(new NoNodejsGlobalsRuleWalker(sourceFile, program, this.getOptions(), config)); - } - } - - return []; - } -} - -class NoNodejsGlobalsRuleWalker extends AbstractGlobalsRuleWalker { - - getDefinitionPattern(): string { - return '@types/node'; - } - - getDisallowedGlobals(): string[] { - // https://nodejs.org/api/globals.html#globals_global_objects - return [ - "NodeJS", - "Buffer", - "__dirname", - "__filename", - "clearImmediate", - "exports", - "global", - "module", - "process", - "setImmediate" - ]; - } -} diff --git a/build/package.json b/build/package.json index bafb409c97b..7d72317ff5e 100644 --- a/build/package.json +++ b/build/package.json @@ -36,6 +36,7 @@ "gulp-uglify": "^3.0.0", "iconv-lite": "0.4.23", "mime": "^1.3.4", + "minimatch": "3.0.4", "minimist": "^1.2.0", "request": "^2.85.0", "terser": "4.3.8", diff --git a/package.json b/package.json index fa917351db5..6645783674b 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "smoketest": "cd test/smoke && node test/index.js", "download-builtin-extensions": "node build/lib/builtInExtensions.js", "monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit", + "valid-globals-check": "node build/lib/globalsLinter.js", "strict-function-types-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictFunctionTypes", "update-distro": "node build/npm/update-distro.js", "web": "node scripts/code-web.js" diff --git a/tslint.json b/tslint.json index 820aa3103db..9b5b07a5dd7 100644 --- a/tslint.json +++ b/tslint.json @@ -609,48 +609,6 @@ "restrictions": "**/vs/**" } ], - "no-nodejs-globals": [ - true, - { - "target": "**/vs/base/common/{path,process,platform}.ts", - "allowed": [ - "process" // -> defines safe access to process - ] - }, - { - "target": "**/vs/**/test/{common,browser}/**", - "allowed": [ - "process", - "Buffer", - "__filename", - "__dirname" - ] - }, - { - "target": "**/vs/workbench/api/common/extHostExtensionService.ts", - "allowed": [ - "global" // -> safe access to 'global' - ] - }, - { - "target": "**/vs/**/{common,browser}/**", - "allowed": [ /* none */] - } - ], - "no-dom-globals": [ - true, - { - "target": "**/vs/**/test/{common,node,electron-main}/**", - "allowed": [ - "document", - "HTMLElement" - ] - }, - { - "target": "**/vs/**/{common,node,electron-main}/**", - "allowed": [ /* none */] - } - ], "duplicate-imports": true, "no-new-buffer": true, "translation-remind": true, From 06bb703b9eb6209aa6e42a6a5b5896ff6c70dc2d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 27 Dec 2019 09:26:29 +0100 Subject: [PATCH 228/241] Editor limit: can end up opening a disposed editor if limit is 1 (fixes #87447) --- .../browser/parts/editor/editorGroupView.ts | 11 ++++- .../editor/test/browser/editorService.test.ts | 49 ++++++++++++++----- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 4bce5420b38..2e06af3dc2e 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -821,6 +821,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise { + // Guard against invalid inputs. Disposed inputs + // should never open because they emit no events + // e.g. to indicate dirty changes. + if (editor.isDisposed()) { + return Promise.resolve(undefined); + } + // Determine options const openEditorOptions: IEditorOpenOptions = { index: options ? options.index : undefined, @@ -1492,10 +1499,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { }); // Handle inactive first - inactiveReplacements.forEach(({ editor, replacement, options }) => { + inactiveReplacements.forEach(async ({ editor, replacement, options }) => { // Open inactive editor - this.doOpenEditor(replacement, options); + await this.doOpenEditor(replacement, options); // Close replaced inactive editor unless they match if (!editor.matches(replacement)) { diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index dccd154b670..b36462a5491 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -131,8 +131,8 @@ suite('EditorService', () => { const service: EditorServiceImpl = testInstantiationService.createInstance(EditorService); - const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-basics')); - const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-basics')); + let input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-basics')); + let otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-basics')); let activeEditorChangeEventCounter = 0; const activeEditorChangeListener = service.onDidActiveEditorChange(() => { @@ -180,7 +180,14 @@ suite('EditorService', () => { assert.equal(visibleEditorChangeEventCounter, 2); assert.ok(input.gotDisposed); - // Open again 2 inputs + // Open again 2 inputs (disposed editors are ignored!) + await service.openEditor(input, { pinned: true }); + assert.equal(0, service.count); + + // Open again 2 inputs (recreate because disposed) + input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-basics')); + otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-basics')); + await service.openEditor(input, { pinned: true }); editor = await service.openEditor(otherInput, { pinned: true }); @@ -511,8 +518,8 @@ suite('EditorService', () => { const service: EditorServiceImpl = testInstantiationService.createInstance(EditorService); - const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + let input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); + let otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); let activeEditorChangeEventFired = false; const activeEditorChangeListener = service.onDidActiveEditorChange(() => { @@ -563,7 +570,9 @@ suite('EditorService', () => { assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); - // 2.) open, open same (forced open) + // 2.) open, open same (forced open) (recreate inputs that got disposed) + input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); + otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); editor = await service.openEditor(input); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -574,7 +583,9 @@ suite('EditorService', () => { await closeEditorAndWaitForNextToOpen(group, input); - // 3.) open, open inactive, close + // 3.) open, open inactive, close (recreate inputs that got disposed) + input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); + otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -587,7 +598,9 @@ suite('EditorService', () => { assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); - // 4.) open, open inactive, close inactive + // 4.) open, open inactive, close inactive (recreate inputs that got disposed) + input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); + otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -604,7 +617,9 @@ suite('EditorService', () => { assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); - // 5.) add group, remove group + // 5.) add group, remove group (recreate inputs that got disposed) + input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); + otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -625,7 +640,9 @@ suite('EditorService', () => { assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); - // 6.) open editor in inactive group + // 6.) open editor in inactive group (recreate inputs that got disposed) + input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); + otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -646,7 +663,9 @@ suite('EditorService', () => { assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); - // 7.) activate group + // 7.) activate group (recreate inputs that got disposed) + input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); + otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -671,7 +690,9 @@ suite('EditorService', () => { assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); - // 8.) move editor + // 8.) move editor (recreate inputs that got disposed) + input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); + otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -688,7 +709,9 @@ suite('EditorService', () => { assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); - // 9.) close editor in inactive group + // 9.) close editor in inactive group (recreate inputs that got disposed) + input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); + otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); From 3d3a7e22cb5d004f0362e1ccdcc85cce811186de Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 27 Dec 2019 11:41:51 +0100 Subject: [PATCH 229/241] fixes #87745 --- src/vs/workbench/contrib/debug/browser/debugSession.ts | 5 ++++- src/vs/workbench/contrib/debug/browser/rawDebugSession.ts | 8 +------- .../contrib/debug/test/browser/debugANSIHandling.test.ts | 2 +- .../contrib/debug/test/browser/debugModel.test.ts | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 2d7078c9096..aa9aea3369a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -34,6 +34,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { variableSetEmitter } from 'vs/workbench/contrib/debug/browser/variablesView'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { distinct } from 'vs/base/common/arrays'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class DebugSession implements IDebugSession { @@ -74,7 +75,8 @@ export class DebugSession implements IDebugSession { @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IProductService private readonly productService: IProductService, @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService, - @IOpenerService private readonly openerService: IOpenerService + @IOpenerService private readonly openerService: IOpenerService, + @INotificationService private readonly notificationService: INotificationService ) { this.id = generateUuid(); this._options = options || {}; @@ -711,6 +713,7 @@ export class DebugSession implements IDebugSession { await this.raw.configurationDone(); } catch (e) { // Disconnect the debug session on configuration done error #10596 + this.notificationService.error(e); if (this.raw) { this.raw.disconnect(); } diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index c26fcda85d7..f7bbb068852 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -623,13 +623,7 @@ export class RawDebugSession implements IDisposable { } }); } - }).then(res => { - if (typeof res.success === 'boolean' && res.success === false) { - return Promise.reject(this.handleErrorResponse(res)); - } - - return res as R; - }, err => Promise.reject(this.handleErrorResponse(err))); + }).then(undefined, err => Promise.reject(this.handleErrorResponse(err))); } private handleErrorResponse(errorResponse: DebugProtocol.Response): Error { diff --git a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts index bead472a090..97d165e501a 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts @@ -30,7 +30,7 @@ suite('Debug - ANSI Handling', () => { */ setup(() => { model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); - session = new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); + session = new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!); const instantiationService: TestInstantiationService = workbenchInstantiationService(); linkDetector = instantiationService.createInstance(LinkDetector); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts index 97c4bc39c37..977a5a0d861 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts @@ -18,7 +18,7 @@ import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSess import { timeout } from 'vs/base/common/async'; function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { - return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); + return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!); } suite('Debug - Model', () => { From 8960b3fd1a47e9c0fc2a2664bb11c1e2812fe8d3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 27 Dec 2019 12:17:14 +0100 Subject: [PATCH 230/241] editor observer - throttle group active event changes --- .../browser/parts/editor/editorsObserver.ts | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index 0545c9710d1..3cfcf98009a 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -41,6 +41,8 @@ export class EditorsObserver extends Disposable { private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange = this._onDidChange.event; + private activeGroupChangeTimeout: number | undefined = undefined; + get count(): number { return this.mostRecentEditorsMap.size; } @@ -91,8 +93,21 @@ export class EditorsObserver extends Disposable { // Group gets active: put active editor as most recent case GroupChangeKind.GROUP_ACTIVE: { - if (this.editorGroupsService.activeGroup === group && group.activeEditor) { - this.addMostRecentEditor(group, group.activeEditor, true /* is active */); + const groupActiveEditor = group.activeEditor; + if (this.editorGroupsService.activeGroup === group && groupActiveEditor) { + clearTimeout(this.activeGroupChangeTimeout); + + // Reduce the likelyhood of recording a false active editor on + // this kind of group event. Often a group-active event is fired + // before opening an editor and we want to record that editor + // as the new most recent one, not the previous one of the group. + // This fixes the following case: + // - 2 groups are open with editors in each + // - user clicks on a non-active editor of the inactive group + // => that clicked editor should be the most recent one and the + // previous one of that group should not be the second most + // active + this.activeGroupChangeTimeout = setTimeout(() => this.addMostRecentEditor(group, groupActiveEditor, true /* is active */)); } break; @@ -102,6 +117,8 @@ export class EditorsObserver extends Disposable { // if group is active, otherwise second most recent case GroupChangeKind.EDITOR_ACTIVE: { if (e.editor) { + clearTimeout(this.activeGroupChangeTimeout); + this.addMostRecentEditor(group, e.editor, this.editorGroupsService.activeGroup === group); } @@ -114,6 +131,8 @@ export class EditorsObserver extends Disposable { // start to close oldest ones if needed. case GroupChangeKind.EDITOR_OPEN: { if (e.editor) { + clearTimeout(this.activeGroupChangeTimeout); + this.addMostRecentEditor(group, e.editor, false /* is not active */); this.ensureOpenedEditorsLimit({ groupId: group.id, editor: e.editor }, group.id); } @@ -124,6 +143,8 @@ export class EditorsObserver extends Disposable { // Editor closes: remove from recently opened case GroupChangeKind.EDITOR_CLOSE: { if (e.editor) { + clearTimeout(this.activeGroupChangeTimeout); + this.removeMostRecentEditor(group, e.editor); } From 511a6242328d844ab826332bc057d53a35179844 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 27 Dec 2019 12:23:23 +0100 Subject: [PATCH 231/241] Revert "editor observer - throttle group active event changes" This reverts commit 8960b3fd1a47e9c0fc2a2664bb11c1e2812fe8d3. --- .../browser/parts/editor/editorsObserver.ts | 25 ++----------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorsObserver.ts b/src/vs/workbench/browser/parts/editor/editorsObserver.ts index 3cfcf98009a..0545c9710d1 100644 --- a/src/vs/workbench/browser/parts/editor/editorsObserver.ts +++ b/src/vs/workbench/browser/parts/editor/editorsObserver.ts @@ -41,8 +41,6 @@ export class EditorsObserver extends Disposable { private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange = this._onDidChange.event; - private activeGroupChangeTimeout: number | undefined = undefined; - get count(): number { return this.mostRecentEditorsMap.size; } @@ -93,21 +91,8 @@ export class EditorsObserver extends Disposable { // Group gets active: put active editor as most recent case GroupChangeKind.GROUP_ACTIVE: { - const groupActiveEditor = group.activeEditor; - if (this.editorGroupsService.activeGroup === group && groupActiveEditor) { - clearTimeout(this.activeGroupChangeTimeout); - - // Reduce the likelyhood of recording a false active editor on - // this kind of group event. Often a group-active event is fired - // before opening an editor and we want to record that editor - // as the new most recent one, not the previous one of the group. - // This fixes the following case: - // - 2 groups are open with editors in each - // - user clicks on a non-active editor of the inactive group - // => that clicked editor should be the most recent one and the - // previous one of that group should not be the second most - // active - this.activeGroupChangeTimeout = setTimeout(() => this.addMostRecentEditor(group, groupActiveEditor, true /* is active */)); + if (this.editorGroupsService.activeGroup === group && group.activeEditor) { + this.addMostRecentEditor(group, group.activeEditor, true /* is active */); } break; @@ -117,8 +102,6 @@ export class EditorsObserver extends Disposable { // if group is active, otherwise second most recent case GroupChangeKind.EDITOR_ACTIVE: { if (e.editor) { - clearTimeout(this.activeGroupChangeTimeout); - this.addMostRecentEditor(group, e.editor, this.editorGroupsService.activeGroup === group); } @@ -131,8 +114,6 @@ export class EditorsObserver extends Disposable { // start to close oldest ones if needed. case GroupChangeKind.EDITOR_OPEN: { if (e.editor) { - clearTimeout(this.activeGroupChangeTimeout); - this.addMostRecentEditor(group, e.editor, false /* is not active */); this.ensureOpenedEditorsLimit({ groupId: group.id, editor: e.editor }, group.id); } @@ -143,8 +124,6 @@ export class EditorsObserver extends Disposable { // Editor closes: remove from recently opened case GroupChangeKind.EDITOR_CLOSE: { if (e.editor) { - clearTimeout(this.activeGroupChangeTimeout); - this.removeMostRecentEditor(group, e.editor); } From 502fe137129301c43cdc3da0b0516b42907d9544 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 27 Dec 2019 08:51:42 -0800 Subject: [PATCH 232/241] Remove ; and : from terminal word separators These are included in both Windows paths and many links (for ports), it's probably best to not include these. Fixes #87583 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index b50a1b58d58..d78ae0bb9f1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -478,7 +478,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { fastScrollSensitivity: editorOptions.fastScrollSensitivity, scrollSensitivity: editorOptions.mouseWheelScrollSensitivity, rendererType: config.rendererType === 'auto' || config.rendererType === 'experimentalWebgl' ? 'canvas' : config.rendererType, - wordSeparator: ' ()[]{}\',:;"`' + wordSeparator: ' ()[]{}\',"`' }); this._xterm = xterm; this._xtermCore = (xterm as any)._core as XTermCore; From f3df207f448d216aefe2e56b047074a4edfdcee0 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Sat, 28 Dec 2019 21:52:38 +0800 Subject: [PATCH 233/241] Fix #87804 --- src/vs/workbench/contrib/files/browser/files.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index c501bd0f486..1274f2b97d5 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -57,7 +57,7 @@ export function getMultiSelectedResources(resource: URI | object | undefined, li const list = listService.lastFocusedList; if (list?.getHTMLElement() === document.activeElement) { // Explorer - if (list instanceof AsyncDataTree) { + if (list instanceof AsyncDataTree && list.getFocus().every(item => item instanceof ExplorerItem)) { // Explorer const context = explorerService.getContext(true); if (context.length) { From faa3aa6f1304392ae175e1420a012e4948794aeb Mon Sep 17 00:00:00 2001 From: Fabien Launay Date: Mon, 30 Dec 2019 08:37:49 +0900 Subject: [PATCH 234/241] fix word repetition in lifecycle.ts comment (#87554) From d6245d285e11afc7e63eebcfbf99f3cf00804c31 Mon Sep 17 00:00:00 2001 From: Jiaxun Wei Date: Mon, 30 Dec 2019 07:40:03 +0800 Subject: [PATCH 235/241] fix: add 'allow-forms' to sandbox attribute. (#87334) Extensions like "mtxr.sqltools" using form to update configuration, this patch will help them to work with the VS Online Web Edition. --- src/vs/workbench/contrib/webview/browser/webviewElement.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index ab0760b511f..03133778a0b 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -61,7 +61,7 @@ export class IFrameWebview extends BaseWebview implements Web protected createElement(options: WebviewOptions) { const element = document.createElement('iframe'); element.className = `webview ${options.customClasses || ''}`; - element.sandbox.add('allow-scripts', 'allow-same-origin'); + element.sandbox.add('allow-scripts', 'allow-same-origin', 'allow-forms'); element.setAttribute('src', `${this.externalEndpoint}/index.html?id=${this.id}`); element.style.border = 'none'; element.style.width = '100%'; From 780d875d6f806b6bc872b5c501e43c65b4ad5c3d Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Sun, 29 Dec 2019 16:11:38 -0800 Subject: [PATCH 236/241] Remove scheme restriction for Markdown/Emmet language features (#87541) * Remove scheme restriction for Markdown/Emmet * Explicitly marking scheme support --- extensions/emmet/src/extension.ts | 4 ++-- extensions/markdown-language-features/src/extension.ts | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/extensions/emmet/src/extension.ts b/extensions/emmet/src/extension.ts index a5195af577c..4fe40db638b 100644 --- a/extensions/emmet/src/extension.ts +++ b/extensions/emmet/src/extension.ts @@ -165,7 +165,7 @@ function registerCompletionProviders(context: vscode.ExtensionContext) { completionProvidersMapping.delete(language); } - const provider = vscode.languages.registerCompletionItemProvider([{ language, scheme: 'file' }, { language, scheme: 'untitled' }], completionProvider, ...LANGUAGE_MODES[includedLanguages[language]]); + const provider = vscode.languages.registerCompletionItemProvider({ language, scheme: '*' }, completionProvider, ...LANGUAGE_MODES[includedLanguages[language]]); context.subscriptions.push(provider); languageMappingForCompletionProviders.set(language, includedLanguages[language]); @@ -174,7 +174,7 @@ function registerCompletionProviders(context: vscode.ExtensionContext) { Object.keys(LANGUAGE_MODES).forEach(language => { if (!languageMappingForCompletionProviders.has(language)) { - const provider = vscode.languages.registerCompletionItemProvider([{ language, scheme: 'file' }, { language, scheme: 'untitled' }], completionProvider, ...LANGUAGE_MODES[language]); + const provider = vscode.languages.registerCompletionItemProvider({ language, scheme: '*' }, completionProvider, ...LANGUAGE_MODES[language]); context.subscriptions.push(provider); languageMappingForCompletionProviders.set(language, language); diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index 80595ff26f7..0bcc8473745 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -49,10 +49,7 @@ function registerMarkdownLanguageFeatures( symbolProvider: MDDocumentSymbolProvider, engine: MarkdownEngine ): vscode.Disposable { - const selector: vscode.DocumentSelector = [ - { language: 'markdown', scheme: 'file' }, - { language: 'markdown', scheme: 'untitled' } - ]; + const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' }; const charPattern = '(\\p{Alphabetic}|\\p{Number}|\\p{Nonspacing_Mark})'; From ccd38490b21f49efb4bae6a9ca15c9663cfbdf4d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 30 Dec 2019 10:52:22 +0100 Subject: [PATCH 237/241] use const instead of var --- extensions/json-language-features/extension.webpack.config.js | 4 ++-- .../json-language-features/server/extension.webpack.config.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/json-language-features/extension.webpack.config.js b/extensions/json-language-features/extension.webpack.config.js index a4d4ff955d7..39e5e567a7a 100644 --- a/extensions/json-language-features/extension.webpack.config.js +++ b/extensions/json-language-features/extension.webpack.config.js @@ -9,7 +9,7 @@ const withDefaults = require('../shared.webpack.config'); const path = require('path'); -var webpack = require('webpack'); +const webpack = require('webpack'); const config = withDefaults({ context: path.join(__dirname, 'client'), @@ -25,4 +25,4 @@ const config = withDefaults({ // add plugin, don't replace inherited config.plugins.push(new webpack.IgnorePlugin(/vertx/)); // request-light dependency -module.exports = config; \ No newline at end of file +module.exports = config; diff --git a/extensions/json-language-features/server/extension.webpack.config.js b/extensions/json-language-features/server/extension.webpack.config.js index 22b23c1d94b..2c90a0b0751 100644 --- a/extensions/json-language-features/server/extension.webpack.config.js +++ b/extensions/json-language-features/server/extension.webpack.config.js @@ -9,7 +9,7 @@ const withDefaults = require('../../shared.webpack.config'); const path = require('path'); -var webpack = require('webpack'); +const webpack = require('webpack'); const config = withDefaults({ context: path.join(__dirname), From 6eaf748366a48a5b6f9645cfa5e2257f51a027a6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 30 Dec 2019 12:46:54 +0100 Subject: [PATCH 238/241] Fix #87285 --- .../api/common/menusExtensionPoint.ts | 6 ++++ .../browser/extensions.contribution.ts | 35 ++++++++++--------- .../extensions/browser/extensionsActions.ts | 27 +++++++------- .../extensions/browser/extensionsViews.ts | 4 +-- .../contrib/extensions/common/extensions.ts | 10 ------ 5 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 4801b91d288..4f893d318d4 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -51,6 +51,7 @@ namespace schema { case 'comments/commentThread/context': return MenuId.CommentThreadActions; case 'comments/comment/title': return MenuId.CommentTitle; case 'comments/comment/context': return MenuId.CommentActions; + case 'extension/context': return MenuId.ExtensionContext; } return undefined; @@ -209,6 +210,11 @@ namespace schema { type: 'array', items: menuItem }, + 'extension/context': { + description: localize('menus.extensionContext', "The extension context menu"), + type: 'array', + items: menuItem + }, } }; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 42d38471455..80f565cbd2d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionMenuActionContext } from 'vs/workbench/contrib/extensions/common/extensions'; +import { VIEWLET_ID, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, @@ -45,7 +45,6 @@ import { RemoteExtensionsInstaller } from 'vs/workbench/contrib/extensions/brows import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IProductService } from 'vs/platform/product/common/productService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -345,16 +344,20 @@ MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { registerAction({ id: 'workbench.extensions.action.copyExtension', title: { value: localize('workbench.extensions.action.copyExtension', "Copy"), original: 'Copy' }, - async handler(accessor, context: IExtensionMenuActionContext) { - const productService = accessor.get(IProductService); - const name = localize('extensionInfoName', 'Name: {0}', context.packageJSON.displayName); - const id = localize('extensionInfoId', 'Id: {0}', context.id); - const description = localize('extensionInfoDescription', 'Description: {0}', context.packageJSON.description); - const verision = localize('extensionInfoVersion', 'Version: {0}', context.packageJSON.version); - const publisher = localize('extensionInfoPublisher', 'Publisher: {0}', context.packageJSON.publisher); - const link = productService.extensionsGallery ? localize('extensionInfoVSMarketplaceLink', 'VS Marketplace Link: {0}', `${productService.extensionsGallery!.itemUrl}?itemName=${context.id}`) : null; - const clipboardStr = `${name}\n${id}\n${description}\n${verision}\n${publisher}${link ? '\n' + link : ''}`; - await accessor.get(IClipboardService).writeText(clipboardStr); + async handler(accessor, extensionId: string) { + const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); + let extension = extensionWorkbenchService.local.filter(e => areSameExtensions(e.identifier, { id: extensionId }))[0] + || (await extensionWorkbenchService.queryGallery({ names: [extensionId], pageSize: 1 }, CancellationToken.None)).firstPage[0]; + if (extension) { + const name = localize('extensionInfoName', 'Name: {0}', extension.displayName); + const id = localize('extensionInfoId', 'Id: {0}', extensionId); + const description = localize('extensionInfoDescription', 'Description: {0}', extension.description); + const verision = localize('extensionInfoVersion', 'Version: {0}', extension.version); + const publisher = localize('extensionInfoPublisher', 'Publisher: {0}', extension.publisherDisplayName); + const link = extension.url ? localize('extensionInfoVSMarketplaceLink', 'VS Marketplace Link: {0}', `${extension.url}`) : null; + const clipboardStr = `${name}\n${id}\n${description}\n${verision}\n${publisher}${link ? '\n' + link : ''}`; + await accessor.get(IClipboardService).writeText(clipboardStr); + } }, menu: { menuId: MenuId.ExtensionContext, @@ -365,8 +368,8 @@ registerAction({ registerAction({ id: 'workbench.extensions.action.copyExtensionId', title: { value: localize('workbench.extensions.action.copyExtensionId', "Copy Extension Id"), original: 'Copy Extension Id' }, - async handler(accessor, context: IExtensionMenuActionContext) { - await accessor.get(IClipboardService).writeText(context.id); + async handler(accessor, id: string) { + await accessor.get(IClipboardService).writeText(id); }, menu: { menuId: MenuId.ExtensionContext, @@ -377,8 +380,8 @@ registerAction({ registerAction({ id: 'workbench.extensions.action.configure', title: { value: localize('workbench.extensions.action.configure', "Configure..."), original: 'Configure...' }, - async handler(accessor, context: IExtensionMenuActionContext) { - await accessor.get(IPreferencesService).openSettings(false, `@ext:${context.id}`); + async handler(accessor, id: string) { + await accessor.get(IPreferencesService).openSettings(false, `@ext:${id}`); }, menu: { menuId: MenuId.ExtensionContext, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index bb11457473f..aec0c61777e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -13,7 +13,7 @@ import * as json from 'vs/base/common/json'; import { ActionViewItem, Separator, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { dispose, Disposable } from 'vs/base/common/lifecycle'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, IExtensionMenuAction } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -660,6 +660,14 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem { } } +export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService): ExtensionAction[][] { + const groups: ExtensionAction[][] = []; + const menu = menuService.createMenu(MenuId.ExtensionContext, contextKeyService); + menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => new MenuItemExtensionAction(action)))); + menu.dispose(); + return groups; +} + export class ManageExtensionAction extends ExtensionDropDownAction { static readonly ID = 'extensions.manage'; @@ -711,7 +719,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { const contextKeyService = this.contextKeyService.createScoped(); contextKeyService.createKey('extensionStatus', 'installed'); contextKeyService.createKey('extensionHasConfiguration', !!this.extension && !!this.extension.local && !!this.extension.local.manifest.contributes && !!this.extension.local.manifest.contributes.configuration); - MenuItemExtensionAction.getContextMenuActions(this.menuService, contextKeyService).forEach(actions => groups.push(actions)); + getContextMenuActions(this.menuService, contextKeyService).forEach(actions => groups.push(actions)); groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension)); @@ -739,26 +747,15 @@ export class ManageExtensionAction extends ExtensionDropDownAction { export class MenuItemExtensionAction extends ExtensionAction { - constructor(private readonly action: IExtensionMenuAction) { + constructor(private readonly action: IAction) { super(action.id, action.label); } - static getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService): ExtensionAction[][] { - const groups: ExtensionAction[][] = []; - const menu = menuService.createMenu(MenuId.ExtensionContext, contextKeyService); - menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => new MenuItemExtensionAction(action)))); - menu.dispose(); - return groups; - } - update() { } async run(): Promise { if (this.extension) { - const packageJSON = await this.extension.getManifest(CancellationToken.None); - if (packageJSON) { - return this.action.run({ id: this.extension.identifier.id, packageJSON }); - } + return this.action.run(this.extension.identifier.id); } } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 904a0f80f22..6047594ffb5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -28,7 +28,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction, MenuItemExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction, getContextMenuActions } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { WorkbenchPagedList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -241,7 +241,7 @@ export class ExtensionsListView extends ViewPane { getActions: () => actions.slice(0, actions.length - 1) }); } else if (e.element) { - const groups = MenuItemExtensionAction.getContextMenuActions(this.menuService, this.contextKeyService.createScoped()); + const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped()); groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = e.element!)); let actions: IAction[] = []; for (const menuActions of groups) { diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 8ed7a6817c4..d54dd9ac3f2 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -14,7 +14,6 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; -import { IAction } from 'vs/base/common/actions'; export const VIEWLET_ID = 'workbench.view.extensions'; @@ -141,12 +140,3 @@ export class ExtensionContainers extends Disposable { } } } - -export interface IExtensionMenuAction extends IAction { - run(context: IExtensionMenuActionContext): Promise; -} - -export interface IExtensionMenuActionContext { - id: string; - packageJSON: IExtensionManifest; -} From 7e68a32075b92f7497d179e778b0e144e6722059 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 30 Dec 2019 15:18:30 +0100 Subject: [PATCH 239/241] fixes #87873 --- .../workbench/contrib/files/browser/fileActions.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 3fe0e232847..71deaf282a2 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -40,7 +40,7 @@ import { Constants } from 'vs/base/common/uint'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { coalesce } from 'vs/base/common/arrays'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; -import { onUnexpectedError, getErrorMessage } from 'vs/base/common/errors'; +import { getErrorMessage } from 'vs/base/common/errors'; import { asDomUri, triggerDownload } from 'vs/base/browser/dom'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; @@ -916,6 +916,7 @@ CommandsRegistry.registerCommand({ export const renameHandler = (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); const textFileService = accessor.get(ITextFileService); + const notificationService = accessor.get(INotificationService); const stats = explorerService.getContext(false); const stat = stats.length > 0 ? stats[0] : undefined; @@ -925,12 +926,17 @@ export const renameHandler = (accessor: ServicesAccessor) => { explorerService.setEditable(stat, { validationMessage: value => validateFileName(stat, value), - onFinish: (value, success) => { + onFinish: async (value, success) => { if (success) { const parentResource = stat.parent!.resource; const targetResource = resources.joinPath(parentResource, value); if (stat.resource.toString() !== targetResource.toString()) { - textFileService.move(stat.resource, targetResource).then(() => refreshIfSeparator(value, explorerService), onUnexpectedError); + try { + await textFileService.move(stat.resource, targetResource); + refreshIfSeparator(value, explorerService); + } catch (e) { + notificationService.error(e); + } } } explorerService.setEditable(stat, null); From 5db448e37bc034b1b434b6e73e90da17bdf885a6 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 30 Dec 2019 16:57:22 +0100 Subject: [PATCH 240/241] refactor stdin code --- src/vs/base/node/encoding.ts | 86 --------------- src/vs/base/node/terminalEncoding.ts | 98 +++++++++++++++++ .../base/test/node/encoding/encoding.test.ts | 7 +- src/vs/code/node/cli.ts | 100 ++++++------------ src/vs/platform/environment/node/stdin.ts | 56 ++++++++++ 5 files changed, 190 insertions(+), 157 deletions(-) create mode 100644 src/vs/base/node/terminalEncoding.ts create mode 100644 src/vs/platform/environment/node/stdin.ts diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index 8da85d8ce39..3b459fa4037 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -4,8 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as iconv from 'iconv-lite'; -import { isLinux, isMacintosh } from 'vs/base/common/platform'; -import { exec } from 'child_process'; import { Readable, Writable } from 'stream'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -353,87 +351,3 @@ export function detectEncodingFromBuffer({ buffer, bytesRead }: IReadResult, aut return { seemsBinary, encoding }; } - -// https://ss64.com/nt/chcp.html -const windowsTerminalEncodings = { - '437': 'cp437', // United States - '850': 'cp850', // Multilingual(Latin I) - '852': 'cp852', // Slavic(Latin II) - '855': 'cp855', // Cyrillic(Russian) - '857': 'cp857', // Turkish - '860': 'cp860', // Portuguese - '861': 'cp861', // Icelandic - '863': 'cp863', // Canadian - French - '865': 'cp865', // Nordic - '866': 'cp866', // Russian - '869': 'cp869', // Modern Greek - '936': 'cp936', // Simplified Chinese - '1252': 'cp1252' // West European Latin -}; - -export async function resolveTerminalEncoding(verbose?: boolean): Promise { - let rawEncodingPromise: Promise; - - // Support a global environment variable to win over other mechanics - const cliEncodingEnv = process.env['VSCODE_CLI_ENCODING']; - if (cliEncodingEnv) { - if (verbose) { - console.log(`Found VSCODE_CLI_ENCODING variable: ${cliEncodingEnv}`); - } - - rawEncodingPromise = Promise.resolve(cliEncodingEnv); - } - - // Linux/Mac: use "locale charmap" command - else if (isLinux || isMacintosh) { - rawEncodingPromise = new Promise(resolve => { - if (verbose) { - console.log('Running "locale charmap" to detect terminal encoding...'); - } - - exec('locale charmap', (err, stdout, stderr) => resolve(stdout)); - }); - } - - // Windows: educated guess - else { - rawEncodingPromise = new Promise(resolve => { - if (verbose) { - console.log('Running "chcp" to detect terminal encoding...'); - } - - exec('chcp', (err, stdout, stderr) => { - if (stdout) { - const windowsTerminalEncodingKeys = Object.keys(windowsTerminalEncodings) as Array; - for (const key of windowsTerminalEncodingKeys) { - if (stdout.indexOf(key) >= 0) { - return resolve(windowsTerminalEncodings[key]); - } - } - } - - return resolve(undefined); - }); - }); - } - - const rawEncoding = await rawEncodingPromise; - if (verbose) { - console.log(`Detected raw terminal encoding: ${rawEncoding}`); - } - - if (!rawEncoding || rawEncoding.toLowerCase() === 'utf-8' || rawEncoding.toLowerCase() === UTF8) { - return UTF8; - } - - const iconvEncoding = toIconvLiteEncoding(rawEncoding); - if (iconv.encodingExists(iconvEncoding)) { - return iconvEncoding; - } - - if (verbose) { - console.log('Unsupported terminal encoding, falling back to UTF-8.'); - } - - return UTF8; -} diff --git a/src/vs/base/node/terminalEncoding.ts b/src/vs/base/node/terminalEncoding.ts new file mode 100644 index 00000000000..3077f3a9633 --- /dev/null +++ b/src/vs/base/node/terminalEncoding.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * This code is also used by standalone cli's. Avoid adding dependencies to keep the size of the cli small. + */ +import { exec } from 'child_process'; +import * as os from 'os'; + +const windowsTerminalEncodings = { + '437': 'cp437', // United States + '850': 'cp850', // Multilingual(Latin I) + '852': 'cp852', // Slavic(Latin II) + '855': 'cp855', // Cyrillic(Russian) + '857': 'cp857', // Turkish + '860': 'cp860', // Portuguese + '861': 'cp861', // Icelandic + '863': 'cp863', // Canadian - French + '865': 'cp865', // Nordic + '866': 'cp866', // Russian + '869': 'cp869', // Modern Greek + '936': 'cp936', // Simplified Chinese + '1252': 'cp1252' // West European Latin +}; + +function toIconvLiteEncoding(encodingName: string): string { + const normalizedEncodingName = encodingName.replace(/[^a-zA-Z0-9]/g, '').toLowerCase(); + const mapped = JSCHARDET_TO_ICONV_ENCODINGS[normalizedEncodingName]; + + return mapped || normalizedEncodingName; +} + +const JSCHARDET_TO_ICONV_ENCODINGS: { [name: string]: string } = { + 'ibm866': 'cp866', + 'big5': 'cp950' +}; + +const UTF8 = 'utf8'; + + +export async function resolveTerminalEncoding(verbose?: boolean): Promise { + let rawEncodingPromise: Promise; + + // Support a global environment variable to win over other mechanics + const cliEncodingEnv = process.env['VSCODE_CLI_ENCODING']; + if (cliEncodingEnv) { + if (verbose) { + console.log(`Found VSCODE_CLI_ENCODING variable: ${cliEncodingEnv}`); + } + + rawEncodingPromise = Promise.resolve(cliEncodingEnv); + } + + // Windows: educated guess + else if (os.platform() === 'win32') { + rawEncodingPromise = new Promise(resolve => { + if (verbose) { + console.log('Running "chcp" to detect terminal encoding...'); + } + + exec('chcp', (err, stdout, stderr) => { + if (stdout) { + const windowsTerminalEncodingKeys = Object.keys(windowsTerminalEncodings) as Array; + for (const key of windowsTerminalEncodingKeys) { + if (stdout.indexOf(key) >= 0) { + return resolve(windowsTerminalEncodings[key]); + } + } + } + + return resolve(undefined); + }); + }); + } + // Linux/Mac: use "locale charmap" command + else { + rawEncodingPromise = new Promise(resolve => { + if (verbose) { + console.log('Running "locale charmap" to detect terminal encoding...'); + } + + exec('locale charmap', (err, stdout, stderr) => resolve(stdout)); + }); + } + + const rawEncoding = await rawEncodingPromise; + if (verbose) { + console.log(`Detected raw terminal encoding: ${rawEncoding}`); + } + + if (!rawEncoding || rawEncoding.toLowerCase() === 'utf-8' || rawEncoding.toLowerCase() === UTF8) { + return UTF8; + } + + return toIconvLiteEncoding(rawEncoding); +} diff --git a/src/vs/base/test/node/encoding/encoding.test.ts b/src/vs/base/test/node/encoding/encoding.test.ts index a78f2ed6b46..1931c76aa40 100644 --- a/src/vs/base/test/node/encoding/encoding.test.ts +++ b/src/vs/base/test/node/encoding/encoding.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as encoding from 'vs/base/node/encoding'; +import * as terminalEncoding from 'vs/base/node/terminalEncoding'; import { Readable } from 'stream'; import { getPathFromAmdModule } from 'vs/base/common/amd'; @@ -118,14 +119,14 @@ suite('Encoding', () => { }); test('resolve terminal encoding (detect)', async function () { - const enc = await encoding.resolveTerminalEncoding(); - assert.ok(encoding.encodingExists(enc)); + const enc = await terminalEncoding.resolveTerminalEncoding(); + assert.ok(enc.length > 0); }); test('resolve terminal encoding (environment)', async function () { process.env['VSCODE_CLI_ENCODING'] = 'utf16le'; - const enc = await encoding.resolveTerminalEncoding(); + const enc = await terminalEncoding.resolveTerminalEncoding(); assert.ok(encoding.encodingExists(enc)); assert.equal(enc, 'utf16le'); }); diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 5a6c573b36d..609395b535a 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -14,10 +14,10 @@ import product from 'vs/platform/product/common/product'; import * as paths from 'vs/base/common/path'; import { whenDeleted, writeFileSync } from 'vs/base/node/pfs'; import { findFreePort, randomPort } from 'vs/base/node/ports'; -import { resolveTerminalEncoding } from 'vs/base/node/encoding'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { ProfilingSession, Target } from 'v8-inspect-profiler'; import { isString } from 'vs/base/common/types'; +import { hasStdinWithoutTty, stdinDataListener, getStdinFilePath, readFromStdin } from 'vs/platform/environment/node/stdin'; function shouldSpawnCliProcess(argv: ParsedArgs): boolean { return !!argv['install-source'] @@ -142,91 +142,55 @@ export async function main(argv: string[]): Promise { }); } - let stdinWithoutTty: boolean = false; - try { - stdinWithoutTty = !process.stdin.isTTY; // Via https://twitter.com/MylesBorins/status/782009479382626304 - } catch (error) { - // Windows workaround for https://github.com/nodejs/node/issues/11656 - } - - const readFromStdin = args._.some(a => a === '-'); - if (readFromStdin) { + const hasReadStdinArg = args._.some(a => a === '-'); + if (hasReadStdinArg) { // remove the "-" argument when we read from stdin args._ = args._.filter(a => a !== '-'); argv = argv.filter(a => a !== '-'); } - let stdinFilePath: string; - if (stdinWithoutTty) { + let stdinFilePath: string | undefined; + if (hasStdinWithoutTty()) { // Read from stdin: we require a single "-" argument to be passed in order to start reading from // stdin. We do this because there is no reliable way to find out if data is piped to stdin. Just // checking for stdin being connected to a TTY is not enough (https://github.com/Microsoft/vscode/issues/40351) - if (args._.length === 0 && readFromStdin) { - // prepare temp file to read stdin to - stdinFilePath = paths.join(os.tmpdir(), `code-stdin-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 3)}.txt`); + if (args._.length === 0) { + if (hasReadStdinArg) { + stdinFilePath = getStdinFilePath(); - // open tmp file for writing - let stdinFileError: Error | undefined; - let stdinFileStream: fs.WriteStream; - try { - stdinFileStream = fs.createWriteStream(stdinFilePath); - } catch (error) { - stdinFileError = error; - } + // returns a file path where stdin input is written into (write in progress). + try { + readFromStdin(stdinFilePath, !!verbose); // throws error if file can not be written - if (!stdinFileError) { + // Make sure to open tmp file + addArg(argv, stdinFilePath); - // Pipe into tmp file using terminals encoding - resolveTerminalEncoding(verbose).then(async encoding => { - const iconv = await import('iconv-lite'); - const converterStream = iconv.decodeStream(encoding); - process.stdin.pipe(converterStream).pipe(stdinFileStream); - }); + // Enable --wait to get all data and ignore adding this to history + addArg(argv, '--wait'); + addArg(argv, '--skip-add-to-recently-opened'); + args.wait = true; - // Make sure to open tmp file - addArg(argv, stdinFilePath); - - // Enable --wait to get all data and ignore adding this to history - addArg(argv, '--wait'); - addArg(argv, '--skip-add-to-recently-opened'); - args.wait = true; - } - - if (verbose) { - if (stdinFileError) { - console.error(`Failed to create file to read via stdin: ${stdinFileError.toString()}`); - } else { console.log(`Reading from stdin via: ${stdinFilePath}`); + } catch (e) { + console.log(`Failed to create file to read via stdin: ${e.toString()}`); + stdinFilePath = undefined; } - } - } + } else { - // If the user pipes data via stdin but forgot to add the "-" argument, help by printing a message - // if we detect that data flows into via stdin after a certain timeout. - else if (args._.length === 0) { - processCallbacks.push(child => new Promise(c => { - const dataListener = () => { - if (isWindows) { - console.log(`Run with '${product.applicationName} -' to read output from another program (e.g. 'echo Hello World | ${product.applicationName} -').`); - } else { - console.log(`Run with '${product.applicationName} -' to read from stdin (e.g. 'ps aux | grep code | ${product.applicationName} -').`); + // If the user pipes data via stdin but forgot to add the "-" argument, help by printing a message + // if we detect that data flows into via stdin after a certain timeout. + processCallbacks.push(_ => stdinDataListener(1000).then(dataReceived => { + if (dataReceived) { + if (isWindows) { + console.log(`Run with '${product.applicationName} -' to read output from another program (e.g. 'echo Hello World | ${product.applicationName} -').`); + } else { + console.log(`Run with '${product.applicationName} -' to read from stdin (e.g. 'ps aux | grep code | ${product.applicationName} -').`); + } } - - c(undefined); - }; - - // wait for 1s maximum... - setTimeout(() => { - process.stdin.removeListener('data', dataListener); - - c(undefined); - }, 1000); - - // ...but finish early if we detect data - process.stdin.once('data', dataListener); - })); + })); + } } } diff --git a/src/vs/platform/environment/node/stdin.ts b/src/vs/platform/environment/node/stdin.ts new file mode 100644 index 00000000000..2cd928e2507 --- /dev/null +++ b/src/vs/platform/environment/node/stdin.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +/** + * This code is also used by standalone cli's. Avoid adding dependencies to keep the size of the cli small. + */ +import * as paths from 'vs/base/common/path'; +import * as fs from 'fs'; +import * as os from 'os'; +import { resolveTerminalEncoding } from 'vs/base/node/terminalEncoding'; + +export function hasStdinWithoutTty() { + try { + return !process.stdin.isTTY; // Via https://twitter.com/MylesBorins/status/782009479382626304 + } catch (error) { + // Windows workaround for https://github.com/nodejs/node/issues/11656 + } + return false; +} + +export function stdinDataListener(durationinMs: number): Promise { + return new Promise(c => { + const dataListener = () => c(true); + + // wait for 1s maximum... + setTimeout(() => { + process.stdin.removeListener('data', dataListener); + + c(false); + }, durationinMs); + + // ...but finish early if we detect data + process.stdin.once('data', dataListener); + }); +} + +export function getStdinFilePath(): string { + return paths.join(os.tmpdir(), `code-stdin-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 3)}.txt`); +} + +export function readFromStdin(targetPath: string, verbose: boolean): Promise { + // open tmp file for writing + const stdinFileStream = fs.createWriteStream(targetPath); + // Pipe into tmp file using terminals encoding + return resolveTerminalEncoding(verbose).then(async encoding => { + + const iconv = await import('iconv-lite'); + if (!iconv.encodingExists(encoding)) { + console.log(`Unsupported terminal encoding: ${encoding}, falling back to UTF-8.`); + encoding = 'utf8'; + } + const converterStream = iconv.decodeStream(encoding); + process.stdin.pipe(converterStream).pipe(stdinFileStream); + }); +} From f3a78d36689388babb33091dc5ac0084d05087dd Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 30 Dec 2019 17:36:03 +0100 Subject: [PATCH 241/241] Move dialog details to description row in custom dialog fixes #85057 --- src/vs/platform/dialogs/common/dialogs.ts | 5 +- .../contrib/files/browser/fileActions.ts | 60 +++++++++++++------ .../files/browser/views/explorerViewer.ts | 14 +++-- .../browser/abstractFileDialogService.ts | 8 ++- 4 files changed, 59 insertions(+), 28 deletions(-) diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 81615b1c9e3..68f8ac9c6d4 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -257,9 +257,8 @@ export const enum ConfirmResult { } const MAX_CONFIRM_FILES = 10; -export function getConfirmMessage(start: string, fileNamesOrResources: readonly (string | URI)[]): string { - const message = [start]; - message.push(''); +export function getFileNamesMessage(fileNamesOrResources: readonly (string | URI)[]): string { + const message: string[] = []; message.push(...fileNamesOrResources.slice(0, MAX_CONFIRM_FILES).map(fileNameOrResource => typeof fileNameOrResource === 'string' ? fileNameOrResource : basename(fileNameOrResource))); if (fileNamesOrResources.length > MAX_CONFIRM_FILES) { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 71deaf282a2..3cdf705bf44 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -33,7 +33,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Schemas } from 'vs/base/common/network'; -import { IDialogService, IConfirmationResult, getConfirmMessage, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IConfirmationResult, getFileNamesMessage, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Constants } from 'vs/base/common/uint'; @@ -199,11 +199,17 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi // Confirm for moving to trash else if (useTrash) { - const message = getMoveToTrashMessage(distinctElements); + let { message, detail } = getMoveToTrashMessage(distinctElements); + detail += detail ? '\n' : ''; + if (isWindows) { + detail += nls.localize('undoBin', "You can restore from the Recycle Bin."); + } else { + detail += nls.localize('undoTrash', "You can restore from the Trash."); + } confirmDeletePromise = dialogService.confirm({ message, - detail: isWindows ? nls.localize('undoBin', "You can restore from the Recycle Bin.") : nls.localize('undoTrash', "You can restore from the Trash."), + detail, primaryButton, checkbox: { label: nls.localize('doNotAskAgain', "Do not ask me again") @@ -214,10 +220,12 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi // Confirm for deleting permanently else { - const message = getDeleteMessage(distinctElements); + let { message, detail } = getDeleteMessage(distinctElements); + detail += detail ? '\n' : ''; + detail += nls.localize('irreversible', "This action is irreversible!"); confirmDeletePromise = dialogService.confirm({ message, - detail: nls.localize('irreversible', "This action is irreversible!"), + detail, primaryButton, type: 'warning' }); @@ -280,44 +288,62 @@ async function deleteFiles(textFileService: ITextFileService, dialogService: IDi }); } -function getMoveToTrashMessage(distinctElements: ExplorerItem[]): string { +function getMoveToTrashMessage(distinctElements: ExplorerItem[]): { message: string, detail: string } { if (containsBothDirectoryAndFile(distinctElements)) { - return getConfirmMessage(nls.localize('confirmMoveTrashMessageFilesAndDirectories', "Are you sure you want to delete the following {0} files/directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource)); + return { + message: nls.localize('confirmMoveTrashMessageFilesAndDirectories', "Are you sure you want to delete the following {0} files/directories and their contents?", distinctElements.length), + detail: getFileNamesMessage(distinctElements.map(e => e.resource)) + }; } if (distinctElements.length > 1) { if (distinctElements[0].isDirectory) { - return getConfirmMessage(nls.localize('confirmMoveTrashMessageMultipleDirectories', "Are you sure you want to delete the following {0} directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource)); + return { + message: nls.localize('confirmMoveTrashMessageMultipleDirectories', "Are you sure you want to delete the following {0} directories and their contents?", distinctElements.length), + detail: getFileNamesMessage(distinctElements.map(e => e.resource)) + }; } - return getConfirmMessage(nls.localize('confirmMoveTrashMessageMultiple', "Are you sure you want to delete the following {0} files?", distinctElements.length), distinctElements.map(e => e.resource)); + return { + message: nls.localize('confirmMoveTrashMessageMultiple', "Are you sure you want to delete the following {0} files?", distinctElements.length), + detail: getFileNamesMessage(distinctElements.map(e => e.resource)) + }; } if (distinctElements[0].isDirectory) { - return nls.localize('confirmMoveTrashMessageFolder', "Are you sure you want to delete '{0}' and its contents?", distinctElements[0].name); + return { message: nls.localize('confirmMoveTrashMessageFolder', "Are you sure you want to delete '{0}' and its contents?", distinctElements[0].name), detail: '' }; } - return nls.localize('confirmMoveTrashMessageFile', "Are you sure you want to delete '{0}'?", distinctElements[0].name); + return { message: nls.localize('confirmMoveTrashMessageFile', "Are you sure you want to delete '{0}'?", distinctElements[0].name), detail: '' }; } -function getDeleteMessage(distinctElements: ExplorerItem[]): string { +function getDeleteMessage(distinctElements: ExplorerItem[]): { message: string, detail: string } { if (containsBothDirectoryAndFile(distinctElements)) { - return getConfirmMessage(nls.localize('confirmDeleteMessageFilesAndDirectories', "Are you sure you want to permanently delete the following {0} files/directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource)); + return { + message: nls.localize('confirmDeleteMessageFilesAndDirectories', "Are you sure you want to permanently delete the following {0} files/directories and their contents?", distinctElements.length), + detail: getFileNamesMessage(distinctElements.map(e => e.resource)) + }; } if (distinctElements.length > 1) { if (distinctElements[0].isDirectory) { - return getConfirmMessage(nls.localize('confirmDeleteMessageMultipleDirectories', "Are you sure you want to permanently delete the following {0} directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource)); + return { + message: nls.localize('confirmDeleteMessageMultipleDirectories', "Are you sure you want to permanently delete the following {0} directories and their contents?", distinctElements.length), + detail: getFileNamesMessage(distinctElements.map(e => e.resource)) + }; } - return getConfirmMessage(nls.localize('confirmDeleteMessageMultiple', "Are you sure you want to permanently delete the following {0} files?", distinctElements.length), distinctElements.map(e => e.resource)); + return { + message: nls.localize('confirmDeleteMessageMultiple', "Are you sure you want to permanently delete the following {0} files?", distinctElements.length), + detail: getFileNamesMessage(distinctElements.map(e => e.resource)) + }; } if (distinctElements[0].isDirectory) { - return nls.localize('confirmDeleteMessageFolder', "Are you sure you want to permanently delete '{0}' and its contents?", distinctElements[0].name); + return { message: nls.localize('confirmDeleteMessageFolder', "Are you sure you want to permanently delete '{0}' and its contents?", distinctElements[0].name), detail: '' }; } - return nls.localize('confirmDeleteMessageFile', "Are you sure you want to permanently delete '{0}'?", distinctElements[0].name); + return { message: nls.localize('confirmDeleteMessageFile', "Are you sure you want to permanently delete '{0}'?", distinctElements[0].name), detail: '' }; } function containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 7ca8ee5af60..c98ad1df6a6 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -36,7 +36,7 @@ import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { Schemas } from 'vs/base/common/network'; import { DesktopDragAndDropData, ExternalElementsDragAndDropData, ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; -import { IDialogService, IConfirmation, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IConfirmation, getFileNamesMessage } from 'vs/platform/dialogs/common/dialogs'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; @@ -971,11 +971,15 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Handle confirm setting const confirmDragAndDrop = !isCopy && this.configurationService.getValue(FileDragAndDrop.CONFIRM_DND_SETTING_KEY); if (confirmDragAndDrop) { + const message = items.length > 1 && items.every(s => s.isRoot) ? localize('confirmRootsMove', "Are you sure you want to change the order of multiple root folders in your workspace?") + : items.length > 1 ? localize('confirmMultiMove', "Are you sure you want to move the following {0} files into '{1}'?", items.length, target.name) + : items[0].isRoot ? localize('confirmRootMove', "Are you sure you want to change the order of root folder '{0}' in your workspace?", items[0].name) + : localize('confirmMove', "Are you sure you want to move '{0}' into '{1}'?", items[0].name, target.name); + const detail = items.length > 1 && !items.every(s => s.isRoot) ? getFileNamesMessage(items.map(i => i.resource)) : undefined; + const confirmation = await this.dialogService.confirm({ - message: items.length > 1 && items.every(s => s.isRoot) ? localize('confirmRootsMove', "Are you sure you want to change the order of multiple root folders in your workspace?") - : items.length > 1 ? getConfirmMessage(localize('confirmMultiMove', "Are you sure you want to move the following {0} files into '{1}'?", items.length, target.name), items.map(s => s.resource)) - : items[0].isRoot ? localize('confirmRootMove', "Are you sure you want to change the order of root folder '{0}' in your workspace?", items[0].name) - : localize('confirmMove', "Are you sure you want to move '{0}' into '{1}'?", items[0].name, target.name), + message, + detail, checkbox: { label: localize('doNotAskAgain', "Do not ask me again") }, diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index 9fdab42186d..60fe745cfc8 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; -import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter, IFileDialogService, IDialogService, ConfirmResult, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter, IFileDialogService, IDialogService, ConfirmResult, getFileNamesMessage } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -90,10 +90,12 @@ export abstract class AbstractFileDialogService implements IFileDialogService { } let message: string; + let detail = nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them."); if (fileNamesOrResources.length === 1) { message = nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", typeof fileNamesOrResources[0] === 'string' ? fileNamesOrResources[0] : resources.basename(fileNamesOrResources[0])); } else { - message = getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", fileNamesOrResources.length), fileNamesOrResources); + message = nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", fileNamesOrResources.length); + detail = getFileNamesMessage(fileNamesOrResources) + '\n' + detail; } const buttons: string[] = [ @@ -104,7 +106,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService { const { choice } = await this.dialogService.show(Severity.Warning, message, buttons, { cancelId: 2, - detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") + detail }); switch (choice) {