From c719034c28f99ffdbcb8fadcd473653c7d85fce8 Mon Sep 17 00:00:00 2001 From: dormesica Date: Mon, 17 Feb 2020 21:46:49 +0200 Subject: [PATCH 01/48] Fix #90415. Wait for processReady event before runnigs file. --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 07f51c5d09f..7efd8924fe2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -690,6 +690,8 @@ export class RunActiveFileInTerminalAction extends Action { if (!instance) { return Promise.resolve(undefined); } + await instance.processReady; + const editor = this.codeEditorService.getActiveCodeEditor(); if (!editor || !editor.hasModel()) { return Promise.resolve(undefined); From 0b883c507c06fed2b1edf86410a6c482a959c9c5 Mon Sep 17 00:00:00 2001 From: nrayburn Date: Mon, 17 Feb 2020 15:43:45 -0600 Subject: [PATCH 02/48] Add a ClipboardService to the StandaloneDiffEditor --- .../standalone/browser/simpleServices.ts | 67 +++++++++++++++++++ .../standalone/browser/standaloneEditor.ts | 3 +- .../standalone/browser/standaloneServices.ts | 5 +- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 773523bab16..a9b6b4cae4d 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -46,6 +46,7 @@ import { ILayoutService, IDimension } from 'vs/platform/layout/browser/layoutSer import { SimpleServicesNLS } from 'vs/editor/common/standaloneStrings'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { basename } from 'vs/base/common/resources'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; export class SimpleModel implements IResolvedTextEditorModel { @@ -250,6 +251,72 @@ export class SimpleNotificationService implements INotificationService { public setFilter(filter: NotificationsFilter): void { } } +export class StandaloneClipboardService implements IClipboardService { + _serviceBrand: undefined; + + private _internalResourcesClipboard: URI[] | undefined; + + async writeText(text: string, type?: string): Promise { + if (type) { + return; + } + + if (navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(text); + } else { + const activeElement = document.activeElement; + const newTextarea = document.createElement('textarea'); + newTextarea.className = 'clipboard-copy'; + newTextarea.style.visibility = 'false'; + newTextarea.style.height = '1px'; + newTextarea.style.width = '1px'; + newTextarea.setAttribute('aria-hidden', 'true'); + newTextarea.style.position = 'absolute'; + newTextarea.style.top = '-1000'; + newTextarea.style.left = '-1000'; + document.body.appendChild(newTextarea); + newTextarea.value = text; + newTextarea.focus(); + newTextarea.select(); + document.execCommand('copy'); + activeElement.focus(); + document.body.removeChild(newTextarea); + } + return; + } + + async readText(type?: string): Promise { + if (type) { + return ''; + } + + return navigator.clipboard.readText(); + } + + readTextSync(): string | undefined { + return undefined; + } + + readFindText(): string { + // @ts-ignore + return undefined; + } + + writeFindText(text: string): void { } + + writeResources(resources: URI[]): void { + this._internalResourcesClipboard = resources; + } + + readResources(): URI[] { + return this._internalResourcesClipboard || []; + } + + hasResources(): boolean { + return this._internalResourcesClipboard !== undefined && this._internalResourcesClipboard.length > 0; + } +} + export class StandaloneCommandService implements ICommandService { _serviceBrand: undefined; diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index e35ab13d4f4..a7b3f43dba8 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -39,6 +39,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; type Omit = Pick>; @@ -122,7 +123,7 @@ export function createDiffEditor(domElement: HTMLElement, options?: IDiffEditorC services.get(IConfigurationService), services.get(IContextMenuService), services.get(IEditorProgressService), - null + services.get(IClipboardService) ); }); } diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 44581138ad5..2a89fae1e1c 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -13,7 +13,7 @@ import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleEditorProgressService, SimpleResourceConfigurationService, SimpleResourcePropertiesService, SimpleUriLabelService, SimpleWorkspaceContextService, StandaloneCommandService, StandaloneKeybindingService, StandaloneTelemetryService, SimpleLayoutService } from 'vs/editor/standalone/browser/simpleServices'; +import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleEditorProgressService, SimpleResourceConfigurationService, SimpleResourcePropertiesService, SimpleUriLabelService, SimpleWorkspaceContextService, StandaloneCommandService, StandaloneKeybindingService, StandaloneTelemetryService, SimpleLayoutService, StandaloneClipboardService } from 'vs/editor/standalone/browser/simpleServices'; import { StandaloneCodeEditorServiceImpl } from 'vs/editor/standalone/browser/standaloneCodeServiceImpl'; import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; @@ -48,6 +48,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; export interface IEditorOverrideServices { [index: string]: any; @@ -204,6 +205,8 @@ export class DynamicStandaloneServices extends Disposable { let contextViewService = ensure(IContextViewService, () => this._register(new ContextViewService(layoutService))); + ensure(IClipboardService, () => new StandaloneClipboardService()); + ensure(IContextMenuService, () => { const contextMenuService = new ContextMenuService(telemetryService, notificationService, contextViewService, keybindingService, themeService); contextMenuService.configure({ blockMouse: false }); // we do not want that in the standalone editor From daaf0f485448ecce30fe1209314549710f842dc6 Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Tue, 18 Feb 2020 13:27:33 -0500 Subject: [PATCH 03/48] Address feedback for reset editor.lineNumbers after disabling zen mode --- src/vs/workbench/browser/layout.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index e102a1666a0..53b4a803ee9 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -122,8 +122,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private workbenchGrid!: SerializableGrid; - private editorWidgetSet = new Set(); - private disposed: boolean | undefined; private titleBarPartView!: ISerializableView; @@ -198,7 +196,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi wasSideBarVisible: false, wasPanelVisible: false, transitionDisposables: new DisposableStore(), - setNotificationsFilter: false + setNotificationsFilter: false, + editorWidgetSet: new Set() }, }; @@ -708,15 +707,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi editor.updateOptions({ lineNumbers }); }; + const editorWidgetSet = this.state.zenMode.editorWidgetSet; if (!lineNumbers) { // Reset line numbers on all editors visible and non-visible - for (const editor of this.editorWidgetSet) { + for (const editor of editorWidgetSet) { setEditorLineNumbers(editor); } - this.editorWidgetSet.clear(); + editorWidgetSet.clear(); } else { this.editorService.visibleTextEditorWidgets.forEach(editor => { - this.editorWidgetSet.add(editor); + if (!editorWidgetSet.has(editor)) { + editorWidgetSet.add(editor); + this.state.zenMode.transitionDisposables.add(editor.onDidDispose(() => { + editorWidgetSet.delete(editor); + })); + } setEditorLineNumbers(editor); }); } From 7d9dd5055c9fe2ad9d7e77c4a9f52605a2273d77 Mon Sep 17 00:00:00 2001 From: bolinfest Date: Tue, 18 Feb 2020 23:33:32 -0800 Subject: [PATCH 04/48] Define IIdentifiedSingleEditOperation.range as IRange instead of Range With the upcoming changes in TypeScript 3.8 to support `import type`, it is even more compelling to declare weak references via types rather than strong references via concrete classes. Most APIs in Monaco already do this well, accepting `monaco.IRange` instead of `monaco.Range`, though `IIdentifiedSingleEditOperation` appears to be an exception in this regard, which means a strong dependency on `monaco.Range` is required to use `ITextModel.pushEditOperations()`. It would be nice to relax this constraint, though I admit I have not yet attempted to trace through whether this breaks anything since I am submitting this via GitHub's web UI. --- src/vs/editor/common/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index b150ede69bb..2a632baf1c8 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -335,7 +335,7 @@ export interface IIdentifiedSingleEditOperation { /** * The range to replace. This can be empty to emulate a simple insert. */ - range: Range; + range: IRange; /** * The text to replace with. This can be null to emulate a simple delete. */ From df7ce7ef9ea5dd259a58464895c50fbe4441da23 Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Tue, 18 Feb 2020 15:31:00 +0100 Subject: [PATCH 05/48] improve doc for debug hover API --- src/vs/vscode.proposed.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 637dff30817..2b31bf45926 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -914,8 +914,8 @@ declare module 'vscode' { * Provide an evaluatable expression for the given document and position. * The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression. * - * @param document The document in which the command was invoked. - * @param position The position where the command was invoked. + * @param document The document in which the debug hover is opened. + * @param position The position in the document where the debug hover is opened. * @param token A cancellation token. * @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be * signaled by returning `undefined` or `null`. From 6685bfc1f46678019e72a23758acb204685153fe Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Wed, 19 Feb 2020 10:16:25 +0100 Subject: [PATCH 06/48] finalize debug hover API; fixes #89084 --- src/vs/vscode.d.ts | 58 ++++++++++++++++++++++++++++++++++ src/vs/vscode.proposed.d.ts | 62 +------------------------------------ 2 files changed, 59 insertions(+), 61 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 4f97500f6c2..ecc91c1ff8d 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2465,6 +2465,53 @@ declare module 'vscode' { provideHover(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; } + /** + * An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime. + * The result of this evaluation is shown in a tooltip-like widget. + * If only a range is specified, the expression will be extracted from the underlying document. + * An optional expression can be used to override the extracted expression. + * In this case the range is still used to highlight the range in the document. + */ + export class EvaluatableExpression { + + /* + * The range is used to extract the evaluatable expression from the underlying document and to highlight it. + */ + readonly range: Range; + + /* + * If specified the expression overrides the extracted expression. + */ + readonly expression?: string; + + /** + * Creates a new evaluatable expression object. + * + * @param range The range in the underlying document from which the evaluatable expression is extracted. + * @param expression If specified overrides the extracted expression. + */ + constructor(range: Range, expression?: string); + } + + /** + * The evaluatable expression provider interface defines the contract between extensions and + * the debug hover. + */ + export interface EvaluatableExpressionProvider { + + /** + * Provide an evaluatable expression for the given document and position. + * The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression. + * + * @param document The document in which the debug hover is opened. + * @param position The position in the document where the debug hover is opened. + * @param token A cancellation token. + * @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + } + /** * A document highlight kind. */ @@ -9080,6 +9127,17 @@ declare module 'vscode' { */ export function registerHoverProvider(selector: DocumentSelector, provider: HoverProvider): Disposable; + /** + * Register a provider that locates evaluatable expressions in text documents. + * + * If multiple providers are registered for a language an arbitrary provider will be used. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An evaluatable expression provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable; + /** * Register a document highlight provider. * diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 2b31bf45926..095df41de3e 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -876,67 +876,7 @@ declare module 'vscode' { //#endregion - //#region locate evaluatable expressions for debug hover: https://github.com/microsoft/vscode/issues/89084 - - /** - * An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime. - * The result of this evaluation is shown in a tooltip-like widget. - * If only a range is specified, the expression will be extracted from the underlying document. - * An optional expression can be used to override the extracted expression. - * In this case the range is still used to highlight the range in the document. - */ - export class EvaluatableExpression { - /* - * The range is used to extract the evaluatable expression from the underlying document and to highlight it. - */ - readonly range: Range; - /* - * If specified the expression overrides the extracted expression. - */ - readonly expression?: string; - - /** - * Creates a new evaluatable expression object. - * - * @param range The range in the underlying document from which the evaluatable expression is extracted. - * @param expression If specified overrides the extracted expression. - */ - constructor(range: Range, expression?: string); - } - - /** - * The evaluatable expression provider interface defines the contract between extensions and - * the debug hover. - */ - export interface EvaluatableExpressionProvider { - - /** - * Provide an evaluatable expression for the given document and position. - * The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression. - * - * @param document The document in which the debug hover is opened. - * @param position The position in the document where the debug hover is opened. - * @param token A cancellation token. - * @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be - * signaled by returning `undefined` or `null`. - */ - provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; - } - - export namespace languages { - /** - * Register a provider that locates evaluatable expressions in text documents. - * - * If multiple providers are registered for a language an arbitrary provider will be used. - * - * @param selector A selector that defines the documents this provider is applicable to. - * @param provider An evaluatable expression provider. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable; - } - - // deprecated + //#region deprecated debug API export interface DebugConfigurationProvider { /** From 225c1b406c6910e1f6602035a3b944742c08b79e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 19 Feb 2020 11:09:43 +0100 Subject: [PATCH 07/48] Move BrowserClipboardService to /platform/ so it can be reused in the standalone editor --- .../standalone/browser/simpleServices.ts | 67 ----------------- .../standalone/browser/standaloneServices.ts | 5 +- .../clipboard/browser/clipboardService.ts | 74 +++++++++++++++++++ .../clipboard/browser/clipboardService.ts | 69 +---------------- 4 files changed, 78 insertions(+), 137 deletions(-) create mode 100644 src/vs/platform/clipboard/browser/clipboardService.ts diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index a9b6b4cae4d..773523bab16 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -46,7 +46,6 @@ import { ILayoutService, IDimension } from 'vs/platform/layout/browser/layoutSer import { SimpleServicesNLS } from 'vs/editor/common/standaloneStrings'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { basename } from 'vs/base/common/resources'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; export class SimpleModel implements IResolvedTextEditorModel { @@ -251,72 +250,6 @@ export class SimpleNotificationService implements INotificationService { public setFilter(filter: NotificationsFilter): void { } } -export class StandaloneClipboardService implements IClipboardService { - _serviceBrand: undefined; - - private _internalResourcesClipboard: URI[] | undefined; - - async writeText(text: string, type?: string): Promise { - if (type) { - return; - } - - if (navigator.clipboard && navigator.clipboard.writeText) { - return navigator.clipboard.writeText(text); - } else { - const activeElement = document.activeElement; - const newTextarea = document.createElement('textarea'); - newTextarea.className = 'clipboard-copy'; - newTextarea.style.visibility = 'false'; - newTextarea.style.height = '1px'; - newTextarea.style.width = '1px'; - newTextarea.setAttribute('aria-hidden', 'true'); - newTextarea.style.position = 'absolute'; - newTextarea.style.top = '-1000'; - newTextarea.style.left = '-1000'; - document.body.appendChild(newTextarea); - newTextarea.value = text; - newTextarea.focus(); - newTextarea.select(); - document.execCommand('copy'); - activeElement.focus(); - document.body.removeChild(newTextarea); - } - return; - } - - async readText(type?: string): Promise { - if (type) { - return ''; - } - - return navigator.clipboard.readText(); - } - - readTextSync(): string | undefined { - return undefined; - } - - readFindText(): string { - // @ts-ignore - return undefined; - } - - writeFindText(text: string): void { } - - writeResources(resources: URI[]): void { - this._internalResourcesClipboard = resources; - } - - readResources(): URI[] { - return this._internalResourcesClipboard || []; - } - - hasResources(): boolean { - return this._internalResourcesClipboard !== undefined && this._internalResourcesClipboard.length > 0; - } -} - export class StandaloneCommandService implements ICommandService { _serviceBrand: undefined; diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 2a89fae1e1c..d3e251b6c54 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -13,7 +13,7 @@ import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; -import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleEditorProgressService, SimpleResourceConfigurationService, SimpleResourcePropertiesService, SimpleUriLabelService, SimpleWorkspaceContextService, StandaloneCommandService, StandaloneKeybindingService, StandaloneTelemetryService, SimpleLayoutService, StandaloneClipboardService } from 'vs/editor/standalone/browser/simpleServices'; +import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleEditorProgressService, SimpleResourceConfigurationService, SimpleResourcePropertiesService, SimpleUriLabelService, SimpleWorkspaceContextService, StandaloneCommandService, StandaloneKeybindingService, StandaloneTelemetryService, SimpleLayoutService } from 'vs/editor/standalone/browser/simpleServices'; import { StandaloneCodeEditorServiceImpl } from 'vs/editor/standalone/browser/standaloneCodeServiceImpl'; import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; @@ -49,6 +49,7 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; export interface IEditorOverrideServices { [index: string]: any; @@ -205,7 +206,7 @@ export class DynamicStandaloneServices extends Disposable { let contextViewService = ensure(IContextViewService, () => this._register(new ContextViewService(layoutService))); - ensure(IClipboardService, () => new StandaloneClipboardService()); + ensure(IClipboardService, () => new BrowserClipboardService()); ensure(IContextMenuService, () => { const contextMenuService = new ContextMenuService(telemetryService, notificationService, contextViewService, keybindingService, themeService); diff --git a/src/vs/platform/clipboard/browser/clipboardService.ts b/src/vs/platform/clipboard/browser/clipboardService.ts new file mode 100644 index 00000000000..aaaf41477ba --- /dev/null +++ b/src/vs/platform/clipboard/browser/clipboardService.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { URI } from 'vs/base/common/uri'; + +export class BrowserClipboardService implements IClipboardService { + + _serviceBrand: undefined; + + private _internalResourcesClipboard: URI[] | undefined; + + async writeText(text: string, type?: string): Promise { + if (type) { + return; // TODO@sbatten + } + + if (navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(text); + } else { + const activeElement = document.activeElement; + const newTextarea = document.createElement('textarea'); + newTextarea.className = 'clipboard-copy'; + newTextarea.style.visibility = 'false'; + newTextarea.style.height = '1px'; + newTextarea.style.width = '1px'; + newTextarea.setAttribute('aria-hidden', 'true'); + newTextarea.style.position = 'absolute'; + newTextarea.style.top = '-1000'; + newTextarea.style.left = '-1000'; + document.body.appendChild(newTextarea); + newTextarea.value = text; + newTextarea.focus(); + newTextarea.select(); + document.execCommand('copy'); + activeElement.focus(); + document.body.removeChild(newTextarea); + } + return; + } + + async readText(type?: string): Promise { + if (type) { + return ''; // TODO@sbatten + } + + return navigator.clipboard.readText(); + } + + readTextSync(): string | undefined { + return undefined; + } + + readFindText(): string { + // @ts-ignore + return undefined; + } + + writeFindText(text: string): void { } + + writeResources(resources: URI[]): void { + this._internalResourcesClipboard = resources; + } + + readResources(): URI[] { + return this._internalResourcesClipboard || []; + } + + hasResources(): boolean { + return this._internalResourcesClipboard !== undefined && this._internalResourcesClipboard.length > 0; + } +} diff --git a/src/vs/workbench/services/clipboard/browser/clipboardService.ts b/src/vs/workbench/services/clipboard/browser/clipboardService.ts index 85a5d9db60c..cd3e76e7c63 100644 --- a/src/vs/workbench/services/clipboard/browser/clipboardService.ts +++ b/src/vs/workbench/services/clipboard/browser/clipboardService.ts @@ -5,73 +5,6 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { URI } from 'vs/base/common/uri'; - -export class BrowserClipboardService implements IClipboardService { - - _serviceBrand: undefined; - - private _internalResourcesClipboard: URI[] | undefined; - - async writeText(text: string, type?: string): Promise { - if (type) { - return; // TODO@sbatten - } - - if (navigator.clipboard && navigator.clipboard.writeText) { - return navigator.clipboard.writeText(text); - } else { - const activeElement = document.activeElement; - const newTextarea = document.createElement('textarea'); - newTextarea.className = 'clipboard-copy'; - newTextarea.style.visibility = 'false'; - newTextarea.style.height = '1px'; - newTextarea.style.width = '1px'; - newTextarea.setAttribute('aria-hidden', 'true'); - newTextarea.style.position = 'absolute'; - newTextarea.style.top = '-1000'; - newTextarea.style.left = '-1000'; - document.body.appendChild(newTextarea); - newTextarea.value = text; - newTextarea.focus(); - newTextarea.select(); - document.execCommand('copy'); - activeElement.focus(); - document.body.removeChild(newTextarea); - } - return; - } - - async readText(type?: string): Promise { - if (type) { - return ''; // TODO@sbatten - } - - return navigator.clipboard.readText(); - } - - readTextSync(): string | undefined { - return undefined; - } - - readFindText(): string { - // @ts-ignore - return undefined; - } - - writeFindText(text: string): void { } - - writeResources(resources: URI[]): void { - this._internalResourcesClipboard = resources; - } - - readResources(): URI[] { - return this._internalResourcesClipboard || []; - } - - hasResources(): boolean { - return this._internalResourcesClipboard !== undefined && this._internalResourcesClipboard.length > 0; - } -} +import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; registerSingleton(IClipboardService, BrowserClipboardService, true); From 8501e0e8daabec8292811655a82192a494402f2f Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 19 Feb 2020 11:12:01 +0100 Subject: [PATCH 08/48] shift --- src/vs/workbench/browser/actions/listCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 0cde9b1dd69..a2754c7be3e 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -740,7 +740,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.toggleSelection', weight: KeybindingWeight.WorkbenchContrib, when: WorkbenchListFocusContextKey, - primary: KeyMod.CtrlCmd | KeyCode.Enter, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter, handler: (accessor) => { const widget = accessor.get(IListService).lastFocusedList; From 06f35807795eb1d0656455865357226c3b417105 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 19 Feb 2020 11:14:34 +0100 Subject: [PATCH 09/48] IClipboardService is no longer an optional service in the standalone editor --- src/vs/editor/browser/widget/diffEditorWidget.ts | 6 +++--- src/vs/editor/standalone/browser/standaloneCodeEditor.ts | 4 ++-- src/vs/workbench/browser/parts/editor/textDiffEditor.ts | 6 ++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 366335f5fa5..e46867f1122 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -84,7 +84,7 @@ class VisualEditorState { constructor( private _contextMenuService: IContextMenuService, - private _clipboardService: IClipboardService | null + private _clipboardService: IClipboardService ) { this._zones = []; this.inlineDiffMargins = []; @@ -134,7 +134,7 @@ class VisualEditorState { this._zones.push(zoneId); this._zonesMap[String(zoneId)] = true; - if (newDecorations.zones[i].diff && viewZone.marginDomNode && this._clipboardService) { + if (newDecorations.zones[i].diff && viewZone.marginDomNode) { viewZone.suppressMouseDown = false; this.inlineDiffMargins.push(new InlineDiffMargin(zoneId, viewZone.marginDomNode, editor, newDecorations.zones[i].diff!, this._contextMenuService, this._clipboardService)); } @@ -220,7 +220,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE constructor( domElement: HTMLElement, options: IDiffEditorOptions, - clipboardService: IClipboardService | null, + @IClipboardService clipboardService: IClipboardService, @IEditorWorkerService editorWorkerService: IEditorWorkerService, @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService instantiationService: IInstantiationService, diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 30f7f5e350c..b9793e2a86b 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -23,7 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; -import { IInstantiationService, optional, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -448,7 +448,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon @IConfigurationService configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @IEditorProgressService editorProgressService: IEditorProgressService, - @optional(IClipboardService) clipboardService: IClipboardService | null, + @IClipboardService clipboardService: IClipboardService, ) { applyConfigurationValues(configurationService, options, true); const themeDomRegistration = (themeService).registerEditorContainer(domElement); diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 81476558064..ec42a8b48cb 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -30,7 +30,6 @@ import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/commo import { CancellationToken } from 'vs/base/common/cancellation'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; /** * The text editor that leverages the diff text editor for the editing experience. @@ -49,8 +48,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @IEditorService editorService: IEditorService, @IThemeService themeService: IThemeService, - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IClipboardService private clipboardService: IClipboardService + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); } @@ -68,7 +66,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { } createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor { - return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration, this.clipboardService); + return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration); } async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { From 6b1e0937e87d05877d84efbc011a2f2037e36079 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Feb 2020 11:11:37 +0100 Subject: [PATCH 10/48] do not check for enablement when resetting local --- src/vs/platform/userDataSync/common/userDataSyncService.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index df589b14163..372861177f5 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -188,7 +188,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } async resetLocal(): Promise { - await this.checkEnablement(); this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL); for (const synchroniser of this.synchronisers) { try { From 1f6ce336caa8f6ef37fd404ba62edef3148f6fc7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Feb 2020 11:16:23 +0100 Subject: [PATCH 11/48] Fix #90925 --- .../userDataSync/browser/userDataSync.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 3c4f49387b8..9bbe2b206ad 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -652,6 +652,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }, when: turnOnSyncWhenContext, }); + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '5_sync', + command: { + id: turnOnSyncCommandId, + title: localize('global activity turn on sync', "Turn on Sync...") + }, + when: turnOnSyncWhenContext, + }); const signInCommandId = 'workbench.userData.actions.signin'; const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut)); @@ -697,6 +705,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), }); + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '5_sync', + command: { + id: stopSyncCommandId, + title: localize('global activity stop sync', "Turn off Sync") + }, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), + }); const resolveSettingsConflictsCommandId = 'workbench.userData.actions.resolveSettingsConflicts'; const resolveSettingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i); @@ -767,15 +783,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), }); - const resetLocalCommandId = 'workbench.userData.actions.resetLocal'; - CommandsRegistry.registerCommand(resetLocalCommandId, () => this.userDataSyncService.resetLocal()); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: resetLocalCommandId, - title: localize('reset local', "Developer: Reset Local (Sync)") - }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), - }); } } From abe0f4946a225d2673be77fe2a81591bdabe22a2 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Feb 2020 11:24:47 +0100 Subject: [PATCH 12/48] Fix #90924 --- .../userDataSync/browser/userDataSync.ts | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 9bbe2b206ad..5504e703126 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -21,7 +21,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; -import { IMenuItem, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey, ContextKeyRegexExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -547,7 +547,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } else { await this.userDataSyncService.resetLocal(); } - await this.signOut(); this.disableSync(); } } @@ -574,13 +573,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private async signOut(): Promise { - if (this.activeAccount) { - await this.authenticationService.logout(this.userDataSyncStore!.authenticationProviderId, this.activeAccount.id); - await this.setActiveAccount(undefined); - } - } - private getConflictsEditorInput(source: SyncSource): IEditorInput | undefined { const previewResource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource : source === SyncSource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource @@ -752,17 +744,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo when: resolveKeybindingsConflictsWhenContext, }); - const signOutMenuItem: IMenuItem = { - group: '5_sync', - command: { - id: 'workbench.userData.actions.signout', - title: localize('sign out', "Sync: Sign out") - }, - when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn)), - }; - CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut()); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem); - const configureSyncCommandId = 'workbench.userData.actions.configureSync'; CommandsRegistry.registerCommand(configureSyncCommandId, () => this.configureSyncOptions()); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { From 7d6245ffbf394d22b1da1983de152ba1e3633e7a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Feb 2020 11:34:51 +0100 Subject: [PATCH 13/48] Fix #90916 --- .../userDataSync/common/userDataSyncService.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 372861177f5..75920bcd8a8 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -49,7 +49,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ @IInstantiationService private readonly instantiationService: IInstantiationService, @ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, - @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IStorageService private readonly storageService: IStorageService, ) { @@ -62,7 +61,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(this.userDataAuthTokenService.onDidChangeToken(e => this.onDidChangeAuthTokenStatus(e))); } this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal)); @@ -140,6 +138,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } async stop(): Promise { + await this.checkEnablement(); if (this.status === SyncStatus.Idle) { return; } @@ -188,6 +187,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } async resetLocal(): Promise { + await this.checkEnablement(); this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL); for (const synchroniser of this.synchronisers) { try { @@ -200,7 +200,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } private async hasPreviouslySynced(): Promise { - await this.checkEnablement(); for (const synchroniser of this.synchronisers) { if (await synchroniser.hasPreviouslySynced()) { return true; @@ -210,7 +209,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } private async hasLocalData(): Promise { - await this.checkEnablement(); for (const synchroniser of this.synchronisers) { if (await synchroniser.hasLocalData()) { return true; @@ -287,14 +285,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } - if (!(await this.userDataAuthTokenService.getToken())) { - throw new UserDataSyncError('Not Authenticated. Please sign in to start sync.', UserDataSyncErrorCode.Unauthorized); - } } - private onDidChangeAuthTokenStatus(token: string | undefined): void { - if (!token) { - this.stop(); - } - } } From fa1874bf59b217dd103b198ed2a1d228446f854b Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 19 Feb 2020 11:41:24 +0100 Subject: [PATCH 14/48] adopt welcome content in explorer --- .../contrib/files/browser/explorerViewlet.ts | 20 ++++- .../contrib/files/browser/views/emptyView.ts | 74 +++---------------- 2 files changed, 30 insertions(+), 64 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 2850d3ea76b..99005648e5a 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -18,7 +18,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; 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 { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -34,6 +34,7 @@ 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 { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { WorkbenchStateContext, RemoteNameContext, IsWebContext } from 'vs/workbench/browser/contextkeys'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -47,6 +48,23 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor ) { super(); + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + + viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + content: localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.\n[Add Folder](command:workbench.action.addRootFolder)"), + when: WorkbenchStateContext.isEqualTo('workspace') + }); + + viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + content: localize('remoteNoFolderHelp', "You have not yet opened a folder on the current remote.\n[Open Folder](command:workbench.action.files.openFolder)"), + when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated()) + }); + + viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { + content: localize('noFolderHelp', "You have not yet opened a folder.\n[Open Folder](command:workbench.action.files.openFolder)"), + when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)) + }); + progressService.withProgress({ location: ProgressLocation.Explorer }, () => workspaceContextService.getCompleteWorkspace()).finally(() => { this.registerViews(); diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 2cae1194184..e9296281846 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -4,13 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as errors from 'vs/base/common/errors'; -import * as DOM from 'vs/base/browser/dom'; -import { Button } from 'vs/base/browser/ui/button/button'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { OpenFolderAction, AddRootFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -20,23 +15,16 @@ import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/vie import { ResourcesDropHandler, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { listDropBackground } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { Schemas } from 'vs/base/common/network'; -import { isWeb } from 'vs/base/common/platform'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { ActionRunner } from 'vs/base/common/actions'; export class EmptyView extends ViewPane { static readonly ID: string = 'workbench.explorer.emptyView'; static readonly NAME = nls.localize('noWorkspace', "No Folder Opened"); - private button!: Button; - private messageElement!: HTMLElement; - constructor( options: IViewletViewOptions, @IThemeService themeService: IThemeService, @@ -46,53 +34,31 @@ export class EmptyView extends ViewPane { @IContextMenuService contextMenuService: IContextMenuService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService configurationService: IConfigurationService, - @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, @ILabelService private labelService: ILabelService, @IContextKeyService contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService ) { super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: No Folder Opened") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); - this._register(this.contextService.onDidChangeWorkbenchState(() => this.setLabels())); - this._register(this.labelService.onDidChangeFormatters(() => this.setLabels())); + + this._register(this.contextService.onDidChangeWorkbenchState(() => this.refreshTitle())); + this._register(this.labelService.onDidChangeFormatters(() => this.refreshTitle())); + } + + shouldShowWelcome(): boolean { + return true; } protected renderBody(container: HTMLElement): void { super.renderBody(container); - DOM.addClass(container, 'explorer-empty-view'); - container.tabIndex = 0; - - const messageContainer = document.createElement('div'); - DOM.addClass(messageContainer, 'section'); - container.appendChild(messageContainer); - - this.messageElement = document.createElement('p'); - messageContainer.appendChild(this.messageElement); - - this.button = new Button(messageContainer); - attachButtonStyler(this.button, this.themeService); - - const actionRunner = new ActionRunner(); - this._register(this.button.onDidClick(() => { - const action = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE - ? this.instantiationService.createInstance(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL) - : this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL); - actionRunner.run(action).then(() => { - action.dispose(); - }, err => { - action.dispose(); - errors.onUnexpectedError(err); - }); - })); - this._register(new DragAndDropObserver(container, { onDrop: e => { const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); container.style.backgroundColor = color ? color.toString() : ''; const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true }); - dropHandler.handleDrop(e, () => undefined, targetGroup => undefined); + dropHandler.handleDrop(e, () => undefined, () => undefined); }, - onDragEnter: (e) => { + onDragEnter: () => { const color = this.themeService.getTheme().getColor(listDropBackground); container.style.backgroundColor = color ? color.toString() : ''; }, @@ -111,26 +77,13 @@ export class EmptyView extends ViewPane { } })); - this.setLabels(); + this.refreshTitle(); } - private setLabels(): void { + private refreshTitle(): void { if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { - this.messageElement.textContent = nls.localize('noWorkspaceHelp', "You have not yet added a folder to the workspace."); - if (this.button) { - this.button.label = nls.localize('addFolder', "Add Folder"); - } this.updateTitle(EmptyView.NAME); } else { - if (this.environmentService.configuration.remoteAuthority && !isWeb) { - const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.configuration.remoteAuthority); - this.messageElement.textContent = hostLabel ? nls.localize('remoteNoFolderHelp', "Connected to {0}", hostLabel) : nls.localize('connecting', "Connecting..."); - } else { - this.messageElement.textContent = nls.localize('noFolderHelp', "You have not yet opened a folder."); - } - if (this.button) { - this.button.label = nls.localize('openFolder', "Open Folder"); - } this.updateTitle(this.title); } } @@ -138,9 +91,4 @@ export class EmptyView extends ViewPane { layoutBody(_size: number): void { // no-op } - - focus(): void { - this.button.element.focus(); - } - } From 334b664a8bfb034a2697551ecf36b9931dbda179 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 19 Feb 2020 11:50:43 +0100 Subject: [PATCH 15/48] Fixes #90848: Add support for relative file paths --- src/vs/editor/contrib/links/links.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index bf5399f274a..14b1b29fd01 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -25,6 +25,10 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import * as resources from 'vs/base/common/resources'; +import * as strings from 'vs/base/common/strings'; function getHoverMessage(link: Link, useMetaKey: boolean): MarkdownString { const executeCmd = link.url && /^command:/i.test(link.url.toString()); @@ -291,7 +295,29 @@ class LinkDetector implements IEditorContribution { const { link } = occurrence; link.resolve(CancellationToken.None).then(uri => { - // open the uri + + // Support for relative file URIs of the shape file://./relativeFile.txt or file:///./relativeFile.txt + if (typeof uri === 'string' && this.editor.hasModel()) { + const modelUri = this.editor.getModel().uri; + if (modelUri.scheme === Schemas.file && strings.startsWith(uri, 'file:')) { + const parsedUri = URI.parse(uri); + if (parsedUri.scheme === Schemas.file) { + const fsPath = resources.originalFSPath(parsedUri); + + let relativePath: string | null = null; + if (strings.startsWith(fsPath, '/./')) { + relativePath = `.${fsPath.substr(1)}`; + } else if (strings.startsWith(fsPath, '//./')) { + relativePath = `.${fsPath.substr(2)}`; + } + + if (relativePath) { + uri = resources.joinPath(modelUri, relativePath); + } + } + } + } + return this.openerService.open(uri, { openToSide, fromUserGesture }); }, err => { From 27606615ba5b305e3f2872821d74bfd9db088d89 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 19 Feb 2020 11:48:28 +0100 Subject: [PATCH 16/48] rewrite --- extensions/git/package.nls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 53325c0f5f7..02ea573e2e8 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -152,7 +152,7 @@ "colors.submodule": "Color for submodule resources.", "view.workbench.scm.missing": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use Git and source control in VS Code in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.", "view.workbench.scm.disabled": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", - "view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone from URL](command:git.clone)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.clone)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm)." } From e7ebb137b766ede77016de3912e052f9a7a9b093 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 19 Feb 2020 11:58:50 +0100 Subject: [PATCH 17/48] git: easy case for git init --- extensions/git/package.json | 7 ++++++- extensions/git/package.nls.json | 5 +++-- extensions/git/src/commands.ts | 31 ++++++++++++++++++------------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 5e8bf47d716..f667a06e704 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1847,7 +1847,12 @@ { "view": "workbench.scm", "contents": "%view.workbench.scm.workspace%", - "when": "config.git.enabled && !git.missing && workbenchState == workspace" + "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount != 0" + }, + { + "view": "workbench.scm", + "contents": "%view.workbench.scm.emptyWorkspace%", + "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0" } ] }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 02ea573e2e8..78388b8b1a0 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -153,6 +153,7 @@ "view.workbench.scm.missing": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use Git and source control in VS Code in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.", "view.workbench.scm.disabled": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.clone)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", - "view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", - "view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm)." + "view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.scm.emptyWorkspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm)." } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 9d8fe2a4ef4..2cd44bc0f05 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -566,24 +566,29 @@ export class CommandCenter { } @command('git.init') - async init(): Promise { + async init(skipFolderPrompt = false): Promise { let repositoryPath: string | undefined = undefined; let askToOpen = true; if (workspace.workspaceFolders) { - const placeHolder = localize('init', "Pick workspace folder to initialize git repo in"); - const pick = { label: localize('choose', "Choose Folder...") }; - const items: { label: string, folder?: WorkspaceFolder }[] = [ - ...workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder })), - pick - ]; - const item = await window.showQuickPick(items, { placeHolder, ignoreFocusOut: true }); - - if (!item) { - return; - } else if (item.folder) { - repositoryPath = item.folder.uri.fsPath; + if (skipFolderPrompt && workspace.workspaceFolders.length === 1) { + repositoryPath = workspace.workspaceFolders[0].uri.fsPath; askToOpen = false; + } else { + const placeHolder = localize('init', "Pick workspace folder to initialize git repo in"); + const pick = { label: localize('choose', "Choose Folder...") }; + const items: { label: string, folder?: WorkspaceFolder }[] = [ + ...workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder })), + pick + ]; + const item = await window.showQuickPick(items, { placeHolder, ignoreFocusOut: true }); + + if (!item) { + return; + } else if (item.folder) { + repositoryPath = item.folder.uri.fsPath; + askToOpen = false; + } } } From c0f3d96aca1c267c4945408879679684846ac5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 19 Feb 2020 12:06:41 +0100 Subject: [PATCH 18/48] Update src/vs/workbench/contrib/files/browser/explorerViewlet.ts --- src/vs/workbench/contrib/files/browser/explorerViewlet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 99005648e5a..77144d4f025 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -56,7 +56,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor }); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize('remoteNoFolderHelp', "You have not yet opened a folder on the current remote.\n[Open Folder](command:workbench.action.files.openFolder)"), + content: localize('remoteNoFolderHelp', "Connected to remote.\n[Open Folder](command:workbench.action.files.openFolder)"), when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated()) }); From ecf4d5ea3d21e69a2e01df9cd7e26547e77f4368 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 19 Feb 2020 12:11:23 +0100 Subject: [PATCH 19/48] move stuff around --- .../contrib/files/browser/explorerViewlet.ts | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 77144d4f025..eb24e038dfc 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -48,6 +48,19 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor ) { super(); + progressService.withProgress({ location: ProgressLocation.Explorer }, () => workspaceContextService.getCompleteWorkspace()).finally(() => { + this.registerViews(); + + this.openEditorsVisibleContextKey = OpenEditorsVisibleContext.bindTo(contextKeyService); + this.updateOpenEditorsVisibility(); + + this._register(workspaceContextService.onDidChangeWorkbenchState(() => this.registerViews())); + this._register(workspaceContextService.onDidChangeWorkspaceFolders(() => this.registerViews())); + this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); + }); + } + + private registerViews(): void { const viewsRegistry = Registry.as(Extensions.ViewsRegistry); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { @@ -65,20 +78,6 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)) }); - progressService.withProgress({ location: ProgressLocation.Explorer }, () => workspaceContextService.getCompleteWorkspace()).finally(() => { - this.registerViews(); - - this.openEditorsVisibleContextKey = OpenEditorsVisibleContext.bindTo(contextKeyService); - this.updateOpenEditorsVisibility(); - - this._register(workspaceContextService.onDidChangeWorkbenchState(() => this.registerViews())); - this._register(workspaceContextService.onDidChangeWorkspaceFolders(() => this.registerViews())); - this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); - }); - } - - private registerViews(): void { - const viewsRegistry = Registry.as(Extensions.ViewsRegistry); const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER); let viewDescriptorsToRegister: IViewDescriptor[] = []; From c2902aa9a8ae85b63f6b6f41df025582a9ced8ca Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 19 Feb 2020 12:14:37 +0100 Subject: [PATCH 20/48] fix welcome view focus --- src/vs/workbench/browser/parts/views/viewPaneContainer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index c3a4362a168..4ba49cfc9b2 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -307,7 +307,9 @@ export abstract class ViewPane extends Pane implements IView { } focus(): void { - if (this.element) { + if (this.shouldShowWelcome()) { + this.viewWelcomeContainer.focus(); + } else if (this.element) { this.element.focus(); this._onDidFocus.fire(); } From e9b1d613fe3d3c23f932272448e7175798faef09 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Feb 2020 12:15:36 +0100 Subject: [PATCH 21/48] :lipstick: --- src/vs/workbench/browser/dnd.ts | 9 +- .../browser/parts/editor/editorActions.ts | 132 +++++------------- .../files/browser/editors/textFileEditor.ts | 4 +- .../editors/textFileSaveErrorHandler.ts | 22 +-- .../files/common/editors/fileEditorInput.ts | 4 +- .../contrib/search/browser/replaceService.ts | 9 +- .../browser/languageSurveys.contribution.ts | 3 +- .../browser/telemetry.contribution.ts | 2 +- .../textfile/browser/textFileService.ts | 2 - 9 files changed, 49 insertions(+), 138 deletions(-) diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index deecdfed219..6467fe70efd 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -28,7 +28,6 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo import { withNullAsUndefined } from 'vs/base/common/types'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { isStandalone } from 'vs/base/browser/browser'; -import { IModelService } from 'vs/editor/common/services/modelService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; export interface IDraggedResource { @@ -343,7 +342,6 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: // Editors: enables cross window DND of tabs into the editor area const textFileService = accessor.get(ITextFileService); const editorService = accessor.get(IEditorService); - const modelService = accessor.get(IModelService); const draggedEditors: ISerializedDraggedEditor[] = []; files.forEach(file => { @@ -374,11 +372,8 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: // If the resource is dirty or untitled, send over its content // to restore dirty state. Get that from the text model directly let content: string | undefined = undefined; - if (textFileService.isDirty(file.resource)) { - const textModel = modelService.getModel(file.resource); - if (textModel) { - content = textModel.getValue(); - } + if (model?.isDirty()) { + content = model.textEditorModel.getValue(); } // Add as dragged editor diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 7fcca6a05f2..5aeb9a3efba 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -71,10 +71,8 @@ export class BaseSplitEditorAction extends Action { })); } - run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { splitEditor(this.editorGroupService, this.direction, context); - - return Promise.resolve(true); } } @@ -183,7 +181,7 @@ export class JoinTwoGroupsAction extends Action { super(id, label); } - run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { let sourceGroup: IEditorGroup | undefined; if (context && typeof context.groupId === 'number') { sourceGroup = this.editorGroupService.getGroup(context.groupId); @@ -198,12 +196,10 @@ export class JoinTwoGroupsAction extends Action { if (targetGroup && sourceGroup !== targetGroup) { this.editorGroupService.mergeGroup(sourceGroup, targetGroup); - return Promise.resolve(true); + break; } } } - - return Promise.resolve(true); } } @@ -220,10 +216,8 @@ export class JoinAllGroupsAction extends Action { super(id, label); } - run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { mergeAllGroups(this.editorGroupService); - - return Promise.resolve(true); } } @@ -240,11 +234,9 @@ export class NavigateBetweenGroupsAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const nextGroup = this.editorGroupService.findGroup({ location: GroupLocation.NEXT }, this.editorGroupService.activeGroup, true); nextGroup.focus(); - - return Promise.resolve(true); } } @@ -261,10 +253,8 @@ export class FocusActiveGroupAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.editorGroupService.activeGroup.focus(); - - return Promise.resolve(true); } } @@ -279,13 +269,11 @@ export abstract class BaseFocusGroupAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const group = this.editorGroupService.findGroup(this.scope, this.editorGroupService.activeGroup, true); if (group) { group.focus(); } - - return Promise.resolve(true); } } @@ -421,7 +409,7 @@ export class OpenToSideFromQuickOpenAction extends Action { this.class = (preferredDirection === GroupDirection.RIGHT) ? 'codicon-split-horizontal' : 'codicon-split-vertical'; } - run(context: any): Promise { + async run(context: any): Promise { const entry = toEditorQuickOpenEntry(context); if (entry) { const input = entry.getInput(); @@ -436,8 +424,6 @@ export class OpenToSideFromQuickOpenAction extends Action { return this.editorService.openEditor(resourceInput, SIDE_GROUP); } } - - return Promise.resolve(false); } } @@ -490,7 +476,7 @@ export class CloseOneEditorAction extends Action { super(id, label, 'codicon-close'); } - run(context?: IEditorCommandsContext): Promise { + async run(context?: IEditorCommandsContext): Promise { let group: IEditorGroup | undefined; let editorIndex: number | undefined; if (context) { @@ -517,8 +503,6 @@ export class CloseOneEditorAction extends Action { if (group.activeEditor) { return group.closeEditor(group.activeEditor); } - - return Promise.resolve(false); } } @@ -554,8 +538,6 @@ export class RevertAndCloseEditorAction extends Action { group.closeEditor(editor); } - - return true; } } @@ -573,13 +555,11 @@ export class CloseLeftEditorsInGroupAction extends Action { super(id, label); } - run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { const { group, editor } = getTarget(this.editorService, this.editorGroupService, context); if (group && editor) { return group.closeEditors({ direction: CloseDirection.LEFT, except: editor }); } - - return Promise.resolve(false); } } @@ -736,9 +716,9 @@ export class CloseEditorsInOtherGroupsAction extends Action { run(context?: IEditorIdentifier): Promise { const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup; - return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => { + return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async g => { if (groupToSkip && g.id === groupToSkip.id) { - return Promise.resolve(); + return; } return g.closeAllEditors(); @@ -760,13 +740,11 @@ export class CloseEditorInAllGroupsAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const activeEditor = this.editorService.activeEditor; if (activeEditor) { return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor))); } - - return Promise.resolve(); } } @@ -781,7 +759,7 @@ export class BaseMoveGroupAction extends Action { super(id, label); } - run(context?: IEditorIdentifier): Promise { + async run(context?: IEditorIdentifier): Promise { let sourceGroup: IEditorGroup | undefined; if (context && typeof context.groupId === 'number') { sourceGroup = this.editorGroupService.getGroup(context.groupId); @@ -795,8 +773,6 @@ export class BaseMoveGroupAction extends Action { this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction); } } - - return Promise.resolve(true); } private findTargetGroup(sourceGroup: IEditorGroup): IEditorGroup | undefined { @@ -892,10 +868,8 @@ export class MinimizeOtherGroupsAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); - - return Promise.resolve(false); } } @@ -908,10 +882,8 @@ export class ResetGroupSizesAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.editorGroupService.arrangeGroups(GroupsArrangement.EVEN); - - return Promise.resolve(false); } } @@ -924,10 +896,8 @@ export class ToggleGroupSizesAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.editorGroupService.arrangeGroups(GroupsArrangement.TOGGLE); - - return Promise.resolve(false); } } @@ -946,13 +916,11 @@ export class MaximizeGroupAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { if (this.editorService.activeEditor) { this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); this.layoutService.setSideBarHidden(true); } - - return Promise.resolve(false); } } @@ -967,23 +935,21 @@ export abstract class BaseNavigateEditorAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const result = this.navigate(); if (!result) { - return Promise.resolve(false); + return; } const { groupId, editor } = result; if (!editor) { - return Promise.resolve(false); + return; } const group = this.editorGroupService.getGroup(groupId); if (group) { return group.openEditor(editor); } - - return Promise.resolve(); } protected abstract navigate(): IEditorIdentifier | undefined; @@ -1158,10 +1124,8 @@ export class NavigateForwardAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.forward(); - - return Promise.resolve(); } } @@ -1174,10 +1138,8 @@ export class NavigateBackwardsAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.back(); - - return Promise.resolve(); } } @@ -1190,10 +1152,8 @@ export class NavigateToLastEditLocationAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.openLastEditLocation(); - - return Promise.resolve(); } } @@ -1206,10 +1166,8 @@ export class NavigateLastAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.last(); - - return Promise.resolve(); } } @@ -1226,10 +1184,8 @@ export class ReopenClosedEditorAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.reopenLastClosedEditor(); - - return Promise.resolve(false); } } @@ -1247,15 +1203,13 @@ export class ClearRecentFilesAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { // Clear global recently opened this.workspacesService.clearRecentlyOpened(); // Clear workspace specific recently opened this.historyService.clearRecentlyOpened(); - - return Promise.resolve(false); } } @@ -1313,12 +1267,10 @@ export class BaseQuickOpenEditorAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const keybindings = this.keybindingService.lookupKeybindings(this.id); this.quickOpenService.show(this.prefix, { quickNavigateConfiguration: { keybindings } }); - - return Promise.resolve(true); } } @@ -1396,12 +1348,10 @@ export class QuickOpenPreviousEditorFromHistoryAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { const keybindings = this.keybindingService.lookupKeybindings(this.id); this.quickOpenService.show(undefined, { quickNavigateConfiguration: { keybindings } }); - - return Promise.resolve(true); } } @@ -1418,10 +1368,8 @@ export class OpenNextRecentlyUsedEditorAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.openNextRecentlyUsedEditor(); - - return Promise.resolve(); } } @@ -1438,10 +1386,8 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.openPreviouslyUsedEditor(); - - return Promise.resolve(); } } @@ -1459,10 +1405,8 @@ export class OpenNextRecentlyUsedEditorInGroupAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.openNextRecentlyUsedEditor(this.editorGroupsService.activeGroup.id); - - return Promise.resolve(); } } @@ -1480,10 +1424,8 @@ export class OpenPreviousRecentlyUsedEditorInGroupAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.historyService.openPreviouslyUsedEditor(this.editorGroupsService.activeGroup.id); - - return Promise.resolve(); } } @@ -1500,12 +1442,10 @@ export class ClearEditorHistoryAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { // Editor history this.historyService.clear(); - - return Promise.resolve(true); } } @@ -1772,10 +1712,8 @@ export class BaseCreateEditorGroupAction extends Action { super(id, label); } - run(): Promise { + async run(): Promise { this.editorGroupService.addGroup(this.editorGroupService.activeGroup, this.direction, { activate: true }); - - return Promise.resolve(true); } } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index f2a644f7b09..e47a31e898d 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -10,7 +10,7 @@ import { isValidBasename } from 'vs/base/common/extpath'; import { basename } from 'vs/base/common/resources'; import { Action } from 'vs/base/common/actions'; import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; -import { ITextFileEditorModel, ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; import { EditorOptions, TextEditorOptions, IEditorCloseEvent } from 'vs/workbench/common/editor'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; @@ -135,7 +135,7 @@ export class TextFileEditor extends BaseTextEditor { return this.openAsBinary(input, options); } - const textFileModel = resolvedModel; + const textFileModel = resolvedModel; // Editor const textEditor = assertIsDefined(this.getControl()); diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index f957b4b3541..2170391364a 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -221,13 +221,11 @@ class DoNotShowResolveConflictLearnMoreAction extends Action { super('workbench.files.action.resolveConflictLearnMoreDoNotShowAgain', nls.localize('dontShowAgain', "Don't Show Again")); } - run(notification: IDisposable): Promise { + async run(notification: IDisposable): Promise { this.storageService.store(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, true, StorageScope.GLOBAL); // Hide notification notification.dispose(); - - return Promise.resolve(); } } @@ -262,8 +260,6 @@ class ResolveSaveConflictAction extends Action { Event.once(handle.onDidClose)(() => dispose(actions.primary!)); pendingResolveSaveConflictMessages.push(handle); } - - return Promise.resolve(true); } } @@ -276,7 +272,7 @@ class SaveElevatedAction extends Action { super('workbench.files.action.saveElevated', triedToMakeWriteable ? isWindows ? nls.localize('overwriteElevated', "Overwrite as Admin...") : nls.localize('overwriteElevatedSudo', "Overwrite as Sudo...") : isWindows ? nls.localize('saveElevated', "Retry as Admin...") : nls.localize('saveElevatedSudo', "Retry as Sudo...")); } - run(): Promise { + async run(): Promise { if (!this.model.isDisposed()) { this.model.save({ writeElevated: true, @@ -284,8 +280,6 @@ class SaveElevatedAction extends Action { reason: SaveReason.EXPLICIT }); } - - return Promise.resolve(true); } } @@ -297,12 +291,10 @@ class OverwriteReadonlyAction extends Action { super('workbench.files.action.overwrite', nls.localize('overwrite', "Overwrite")); } - run(): Promise { + async run(): Promise { if (!this.model.isDisposed()) { this.model.save({ overwriteReadonly: true, reason: SaveReason.EXPLICIT }); } - - return Promise.resolve(true); } } @@ -314,12 +306,10 @@ class SaveIgnoreModifiedSinceAction extends Action { super('workbench.files.action.saveIgnoreModifiedSince', nls.localize('overwrite', "Overwrite")); } - run(): Promise { + async run(): Promise { if (!this.model.isDisposed()) { this.model.save({ ignoreModifiedSince: true, reason: SaveReason.EXPLICIT }); } - - return Promise.resolve(true); } } @@ -331,10 +321,8 @@ class ConfigureSaveConflictAction extends Action { super('workbench.files.action.configureSaveConflict', nls.localize('configure', "Configure")); } - run(): Promise { + async run(): Promise { this.preferencesService.openSettings(undefined, 'files.saveConflictResolution'); - - return Promise.resolve(true); } } diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 63575040192..5b4e50eed05 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -105,10 +105,10 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi this.modelListeners.add(model.onDidSaveError(() => this._onDidChangeDirty.fire())); // remove model association once it gets disposed - Event.once(model.onDispose)(() => { + this.modelListeners.add(Event.once(model.onDispose)(() => { this.modelListeners.clear(); this.model = undefined; - }); + })); } getEncoding(): string | undefined { diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index aff890ade35..23b193da924 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -104,14 +104,7 @@ export class ReplaceService implements IReplaceService { const edits: WorkspaceTextEdit[] = this.createEdits(arg, resource); await this.bulkEditorService.apply({ edits }, { progress }); - return Promise.all(edits.map(e => { - const model = this.textFileService.files.get(e.resource); - if (model) { - return model.save(); - } - - return Promise.resolve(undefined); - })); + return Promise.all(edits.map(e => this.textFileService.files.get(e.resource)?.save())); } async openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { diff --git a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts index 08f28124a04..aec8abafbca 100644 --- a/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts @@ -53,8 +53,7 @@ class LanguageSurvey extends Disposable { // Process model-save event every 250ms to reduce load const onModelsSavedWorker = this._register(new RunOnceWorker(models => { models.forEach(m => { - const model = modelService.getModel(m.resource); - if (model && model.getModeId() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) { + if (m.getMode() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) { const editedCount = storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) + 1; storageService.store(EDITED_LANGUAGE_COUNT_KEY, editedCount, StorageScope.GLOBAL); storageService.store(EDITED_LANGUAGE_DATE_KEY, date, StorageScope.GLOBAL); diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index 6f94e27d1a6..c9da631ce79 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -58,7 +58,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IConfigurationService configurationService: IConfigurationService, @IViewletService viewletService: IViewletService, - @ITextFileService textFileService: ITextFileService, + @ITextFileService textFileService: ITextFileService ) { super(); diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 2490066a9a6..1d1f71f63f2 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -204,8 +204,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex else { const model = this.files.get(resource); if (model) { - - // Save with options return await model.save(options) ? resource : undefined; } } From c02b8d1ed7df1be8fa5c96b59429789c02b765e8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Feb 2020 12:17:40 +0100 Subject: [PATCH 22/48] text file - getAll() => models --- .../browser/browserTextFileService.ts | 2 +- .../common/textFileEditorModelManager.ts | 22 +++++++++---------- .../services/textfile/common/textfiles.ts | 3 ++- .../textFileEditorModelManager.test.ts | 10 ++++----- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts index c671d0dc643..a5a5ed8208f 100644 --- a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -24,7 +24,7 @@ export class BrowserTextFileService extends AbstractTextFileService { } protected onBeforeShutdown(reason: ShutdownReason): boolean { - if (this.files.getAll().some(model => model.hasState(ModelState.PENDING_SAVE))) { + if (this.files.models.some(model => model.hasState(ModelState.PENDING_SAVE))) { console.warn('Unload prevented: pending file saves'); return true; // files are pending to be saved: veto diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index ed257999403..74240ecf283 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -50,6 +50,13 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private readonly _onDidChangeEncoding = this._register(new Emitter()); readonly onDidChangeEncoding = this._onDidChangeEncoding.event; + private readonly mapResourceToModel = new ResourceMap(); + private readonly mapResourceToModelListeners = new ResourceMap(); + private readonly mapResourceToDisposeListener = new ResourceMap(); + private readonly mapResourceToPendingModelLoaders = new ResourceMap>(); + + private readonly modelLoadQueue = this._register(new ResourceQueue()); + saveErrorHandler = (() => { const notificationService = this.notificationService; @@ -60,12 +67,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE }; })(); - private readonly mapResourceToModel = new ResourceMap(); - private readonly mapResourceToModelListeners = new ResourceMap(); - private readonly mapResourceToDisposeListener = new ResourceMap(); - private readonly mapResourceToPendingModelLoaders = new ResourceMap>(); - - private readonly modelLoadQueue = this._register(new ResourceQueue()); + get models(): ITextFileEditorModel[] { + return this.mapResourceToModel.values(); + } constructor( @ILifecycleService private readonly lifecycleService: ILifecycleService, @@ -135,7 +139,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // find all models that related to either source or target (can be many if resource is a folder) const sourceModels: ITextFileEditorModel[] = []; const targetModels: ITextFileEditorModel[] = []; - for (const model of this.getAll()) { + for (const model of this.models) { const resource = model.resource; if (isEqualOrParent(resource, e.target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { @@ -331,10 +335,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } - getAll(): ITextFileEditorModel[] { - return this.mapResourceToModel.values(); - } - add(resource: URI, model: ITextFileEditorModel): void { const knownModel = this.mapResourceToModel.get(resource); if (knownModel === model) { diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index d0953192fc5..c248689f965 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -337,10 +337,11 @@ export interface ITextFileEditorModelManager { readonly onDidRevert: Event; readonly onDidChangeEncoding: Event; + readonly models: ITextFileEditorModel[]; + saveErrorHandler: ISaveErrorHandler; get(resource: URI): ITextFileEditorModel | undefined; - getAll(): ITextFileEditorModel[]; resolve(resource: URI, options?: IModelLoadOrCreateOptions): Promise; diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts index f7223065532..c7c2b081547 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts @@ -51,7 +51,7 @@ suite('Files - TextFileEditorModelManager', () => { assert.ok(!manager.get(fileUpper)); - let results = manager.getAll(); + let results = manager.models; assert.strictEqual(3, results.length); let result = manager.get(URI.file('/yes')); @@ -68,19 +68,19 @@ suite('Files - TextFileEditorModelManager', () => { manager.remove(URI.file('')); - results = manager.getAll(); + results = manager.models; assert.strictEqual(3, results.length); manager.remove(URI.file('/some/other.html')); - results = manager.getAll(); + results = manager.models; assert.strictEqual(2, results.length); manager.remove(fileUpper); - results = manager.getAll(); + results = manager.models; assert.strictEqual(2, results.length); manager.clear(); - results = manager.getAll(); + results = manager.models; assert.strictEqual(0, results.length); model1.dispose(); From d43bb97bb02cbbf35295c5cdab9ed564bdf2d909 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 19 Feb 2020 12:23:02 +0100 Subject: [PATCH 23/48] git: contribute Clone to explorer --- extensions/git/package.json | 4 ++++ extensions/git/package.nls.json | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index f667a06e704..1eaf39ad44b 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1853,6 +1853,10 @@ "view": "workbench.scm", "contents": "%view.workbench.scm.emptyWorkspace%", "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0" + }, + { + "view": "workbench.explorer.emptyView", + "contents": "%view.workbench.cloneRepository%" } ] }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 78388b8b1a0..6328edd99bf 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -155,5 +155,6 @@ "view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.clone)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", - "view.workbench.scm.emptyWorkspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm)." + "view.workbench.scm.emptyWorkspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", + "view.workbench.cloneRepository": "You can also clone a repository from a URL. To learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).\n[Clone Repository](command:git.clone)" } From 84cab0b38a200b91f2e2dc441e7f26aedd14dc5d Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Wed, 19 Feb 2020 12:35:54 +0100 Subject: [PATCH 24/48] DAP: support selection on debug completion items --- .../workbench/contrib/debug/common/debugProtocol.d.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index 179f1ddff88..5807fa4a3b6 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -1773,6 +1773,16 @@ declare module DebugProtocol { If missing the value 0 is assumed which results in the completion text being inserted. */ length?: number; + /** Determines the start of the new selection after the text has been inserted (or replaced). + The start position must in the range 0 and length of the completion text. + If omitted the selection starts at the end of the completion text. + */ + selectionStart?: number; + /** Determines the length of the new selection after the text has been inserted (or replaced). + The selection can not extend beyond the bounds of the completion text. + If omitted the length is assumed to be 0. + */ + selectionLength?: number; } /** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. */ From f4c6b7cdf9210d403b47c7dca34503740e7b7205 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Feb 2020 12:46:11 +0100 Subject: [PATCH 25/48] Fix #86915 --- .../userDataSync/common/userDataSync.ts | 1 + .../browser/extensions.contribution.ts | 31 ++++++++++++++++++- .../extensions/browser/extensionsActions.ts | 22 +++++++++---- .../extensions/browser/extensionsViews.ts | 2 +- .../contrib/extensions/common/extensions.ts | 2 ++ .../preferences/browser/settingsTree.ts | 7 +++-- .../userDataSync/browser/userDataSync.ts | 3 +- 7 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 456c21f56b8..b6cbde7d12f 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -335,6 +335,7 @@ export interface ISettingsSyncService extends IUserDataSynchroniser { //#endregion export const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); +export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey('syncEnabled', false); export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync'; export function toRemoteContentResource(source: SyncSource): URI { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 49ffa58bb98..265c9164ea4 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, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; +import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, @@ -48,6 +48,8 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -435,6 +437,33 @@ registerAction2(class extends Action2 { } }); +registerAction2(class extends Action2 { + + constructor() { + super({ + id: TOGGLE_IGNORE_EXTENSION_ACTION_ID, + title: { value: localize('workbench.extensions.action.toggleIgnoreExtension', "Don't Sync This Extension"), original: `Don't Sync This Extension` }, + menu: { + id: MenuId.ExtensionContext, + group: '2_configure', + when: CONTEXT_SYNC_ENABLEMENT + }, + }); + } + + async run(accessor: ServicesAccessor, id: string) { + const configurationService = accessor.get(IConfigurationService); + const ignoredExtensions = [...configurationService.getValue('sync.ignoredExtensions')]; + const index = ignoredExtensions.findIndex(ignoredExtension => areSameExtensions({ id: ignoredExtension }, { id })); + if (index !== -1) { + ignoredExtensions.splice(index, 1); + } else { + ignoredExtensions.push(id); + } + return configurationService.updateValue('sync.ignoredExtensions', ignoredExtensions.length ? ignoredExtensions : undefined, ConfigurationTarget.USER); + } +}); + 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 88e2d06be26..8b70db39953 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, TOGGLE_IGNORE_EXTENSION_ACTION_ID } 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 { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -660,7 +660,7 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem { } } -export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, extension: IExtension | undefined | null): ExtensionAction[][] { +export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, instantiationService: IInstantiationService, extension: IExtension | undefined | null): ExtensionAction[][] { const scopedContextKeyService = contextKeyService.createScoped(); if (extension) { scopedContextKeyService.createKey('isBuiltinExtension', extension.type === ExtensionType.System); @@ -672,7 +672,7 @@ export function getContextMenuActions(menuService: IMenuService, contextKeyServi const groups: ExtensionAction[][] = []; const menu = menuService.createMenu(MenuId.ExtensionContext, scopedContextKeyService); - menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => new MenuItemExtensionAction(action)))); + menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => instantiationService.createInstance(MenuItemExtensionAction, action)))); menu.dispose(); return groups; @@ -726,7 +726,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction { groups.push([this.instantiationService.createInstance(UninstallAction)]); groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); - getContextMenuActions(this.menuService, this.contextKeyService, this.extension).forEach(actions => groups.push(actions)); + getContextMenuActions(this.menuService, this.contextKeyService, this.instantiationService, this.extension).forEach(actions => groups.push(actions)); groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension)); @@ -754,11 +754,21 @@ export class ManageExtensionAction extends ExtensionDropDownAction { export class MenuItemExtensionAction extends ExtensionAction { - constructor(private readonly action: IAction) { + constructor( + private readonly action: IAction, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { super(action.id, action.label); } - update() { } + update() { + if (!this.extension) { + return; + } + if (this.action.id === TOGGLE_IGNORE_EXTENSION_ACTION_ID) { + this.checked = this.configurationService.getValue('sync.ignoredExtensions').some(id => areSameExtensions({ id }, this.extension!.identifier)); + } + } async run(): Promise { if (this.extension) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 2d091013be3..0821aeea684 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -247,7 +247,7 @@ export class ExtensionsListView extends ViewPane { getActions: () => actions.slice(0, actions.length - 1) }); } else if (e.element) { - const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), e.element); + const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), this.instantiationService, e.element); 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 1d64d4abe01..ade46093ea2 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -140,3 +140,5 @@ export class ExtensionContainers extends Disposable { } } } + +export const TOGGLE_IGNORE_EXTENSION_ACTION_ID = 'workbench.extensions.action.toggleIgnoreExtension'; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index ca5fb320cbb..823554a88dd 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -1640,12 +1640,13 @@ class StopSyncingSettingAction extends Action { } async run(): Promise { - const currentValue = this.configService.getValue('sync.ignoredSettings'); + let currentValue = [...this.configService.getValue('sync.ignoredSettings')]; if (this.checked) { - this.configService.updateValue('sync.ignoredSettings', currentValue.filter(v => v !== this.setting.key)); + currentValue = currentValue.filter(v => v !== this.setting.key); } else { - this.configService.updateValue('sync.ignoredSettings', [...currentValue, this.setting.key]); + currentValue.push(this.setting.key); } + this.configService.updateValue('sync.ignoredSettings', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER); return Promise.resolve(undefined); } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 5504e703126..1e68000afcc 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -31,7 +31,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { CONTEXT_SYNC_STATE, getSyncSourceFromRemoteContentResource, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { CONTEXT_SYNC_STATE, getSyncSourceFromRemoteContentResource, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -52,7 +52,6 @@ const enum AuthStatus { SignedOut = 'SignedOut', Unavailable = 'Unavailable' } -const CONTEXT_SYNC_ENABLEMENT = new RawContextKey('syncEnabled', false); const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthStatus.Initializing); const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); From 08b831637ab06b20c4bb5c3252982f6b8becb835 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Feb 2020 12:22:35 +0100 Subject: [PATCH 26/48] debt - simplify updateDocumentEdited --- src/vs/workbench/electron-browser/window.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index fdac09565c0..d4c3246d98d 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -289,10 +289,9 @@ export class ElectronWindow extends Disposable { private updateDocumentEdited(isDirty = this.workingCopyService.hasDirty): void { if ((!this.isDocumentedEdited && isDirty) || (this.isDocumentedEdited && !isDirty)) { - const hasDirtyFiles = this.workingCopyService.hasDirty; - this.isDocumentedEdited = hasDirtyFiles; + this.isDocumentedEdited = isDirty; - this.electronService.setDocumentEdited(hasDirtyFiles); + this.electronService.setDocumentEdited(isDirty); } } From 157abd2a71ee6decb63610c10b371b9db1dd735b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Feb 2020 13:32:54 +0100 Subject: [PATCH 27/48] untitled - align event names with files --- src/vs/workbench/browser/labels.ts | 4 +-- .../browser/parts/editor/editorStatus.ts | 4 +-- .../browser/editors/fileEditorTracker.ts | 6 ++--- .../contrib/search/browser/searchView.ts | 2 +- .../common/untitledTextEditorService.ts | 26 +++++++++---------- .../test/browser/untitledTextEditor.test.ts | 16 ++++++------ 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index bcbae8f4e2d..1d346c89ff3 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -148,8 +148,8 @@ export class ResourceLabels extends Disposable { })); // notify when untitled labels change - this.textFileService.untitled.onDidChangeLabel(resource => { - this._widgets.forEach(widget => widget.notifyUntitledLabelChange(resource)); + this.textFileService.untitled.onDidChangeLabel(model => { + this._widgets.forEach(widget => widget.notifyUntitledLabelChange(model.resource)); }); } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 09929dceb86..64e258c60b0 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -314,8 +314,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private registerListeners(): void { this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar())); - this._register(this.textFileService.untitled.onDidChangeEncoding(r => this.onResourceEncodingChange(r))); - this._register(this.textFileService.files.onDidChangeEncoding(m => this.onResourceEncodingChange((m.resource)))); + this._register(this.textFileService.untitled.onDidChangeEncoding(model => this.onResourceEncodingChange(model.resource))); + this._register(this.textFileService.files.onDidChangeEncoding(model => this.onResourceEncodingChange((model.resource)))); this._register(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange())); } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index 181480f8135..392394a7a76 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -59,9 +59,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); // Ensure dirty text file and untitled models are always opened as editors - this._register(this.textFileService.files.onDidChangeDirty(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource))); - this._register(this.textFileService.files.onDidSaveError(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource))); - this._register(this.textFileService.untitled.onDidChangeDirty(r => this.ensureDirtyFilesAreOpenedWorker.work(r))); + this._register(this.textFileService.files.onDidChangeDirty(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource))); + this._register(this.textFileService.files.onDidSaveError(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource))); + this._register(this.textFileService.untitled.onDidChangeDirty(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource))); // Out of workspace file watchers this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange())); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 82d47028f91..c2fdc1c21fc 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -213,7 +213,7 @@ export class SearchView extends ViewPane { this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE); this._register(this.fileService.onDidFilesChange(e => this.onFilesChanged(e))); - this._register(this.textFileService.untitled.onDidDisposeModel(e => this.onUntitledDidDispose(e))); + this._register(this.textFileService.untitled.onDidDispose(model => this.onUntitledDidDispose(model.resource))); this._register(this.contextService.onDidChangeWorkbenchState(() => this.onDidChangeWorkbenchState())); this._register(this.searchHistoryService.onDidClearHistory(() => this.clearHistory())); diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index 612132dd170..10c06b14c5e 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -67,22 +67,22 @@ export interface IUntitledTextEditorModelManager { /** * Events for when untitled text editors change (e.g. getting dirty, saved or reverted). */ - readonly onDidChangeDirty: Event; + readonly onDidChangeDirty: Event; /** * Events for when untitled text editor encodings change. */ - readonly onDidChangeEncoding: Event; + readonly onDidChangeEncoding: Event; /** * Events for when untitled text editor labels change. */ - readonly onDidChangeLabel: Event; + readonly onDidChangeLabel: Event; /** * Events for when untitled text editors are disposed. */ - readonly onDidDisposeModel: Event; + readonly onDidDispose: Event; /** * Creates a new untitled editor model with the provided options. If the `untitledResource` @@ -117,16 +117,16 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe _serviceBrand: undefined; - private readonly _onDidChangeDirty = this._register(new Emitter()); + private readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; - private readonly _onDidChangeEncoding = this._register(new Emitter()); + private readonly _onDidChangeEncoding = this._register(new Emitter()); readonly onDidChangeEncoding = this._onDidChangeEncoding.event; - private readonly _onDidDisposeModel = this._register(new Emitter()); - readonly onDidDisposeModel = this._onDidDisposeModel.event; + private readonly _onDidDispose = this._register(new Emitter()); + readonly onDidDispose = this._onDidDispose.event; - private readonly _onDidChangeLabel = this._register(new Emitter()); + private readonly _onDidChangeLabel = this._register(new Emitter()); readonly onDidChangeLabel = this._onDidChangeLabel.event; private readonly mapResourceToModel = new ResourceMap(); @@ -220,10 +220,10 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe private registerModel(model: UntitledTextEditorModel): void { const modelDisposables = new DisposableStore(); - modelDisposables.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model.resource))); - modelDisposables.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model.resource))); - modelDisposables.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model.resource))); - modelDisposables.add(model.onDispose(() => this._onDidDisposeModel.fire(model.resource))); + modelDisposables.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model))); + modelDisposables.add(model.onDidChangeName(() => this._onDidChangeLabel.fire(model))); + modelDisposables.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(model))); + modelDisposables.add(model.onDispose(() => this._onDidDispose.fire(model))); // Remove from cache on dispose Event.once(model.onDispose)(() => { diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index dc89004fe92..7234536f6e9 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -108,10 +108,10 @@ suite('Untitled text editors', () => { function awaitDidChangeDirty(service: IUntitledTextEditorService): Promise { return new Promise(c => { - const listener = service.onDidChangeDirty(async resource => { + const listener = service.onDidChangeDirty(async model => { listener.dispose(); - c(resource); + c(model.resource); }); }); } @@ -328,9 +328,9 @@ suite('Untitled text editors', () => { let counter = 0; - service.onDidChangeEncoding(r => { + service.onDidChangeEncoding(model => { counter++; - assert.equal(r.toString(), input.resource.toString()); + assert.equal(model.resource.toString(), input.resource.toString()); }); // encoding @@ -347,9 +347,9 @@ suite('Untitled text editors', () => { let counter = 0; - service.onDidChangeLabel(r => { + service.onDidChangeLabel(model => { counter++; - assert.equal(r.toString(), input.resource.toString()); + assert.equal(model.resource.toString(), input.resource.toString()); }); // label @@ -366,9 +366,9 @@ suite('Untitled text editors', () => { let counter = 0; - service.onDidDisposeModel(r => { + service.onDidDispose(model => { counter++; - assert.equal(r.toString(), input.resource.toString()); + assert.equal(model.resource.toString(), input.resource.toString()); }); const model = await input.resolve(); From 754875a9867aaba18e248d79845c32094d5a7349 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Feb 2020 13:38:22 +0100 Subject: [PATCH 28/48] explorer - no need to revert before using working copy file service --- .../workbench/contrib/files/browser/fileActions.ts | 1 - .../contrib/files/browser/views/explorerViewer.ts | 14 ++------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 9b527817332..93fc4f9c5d9 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -183,7 +183,6 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyF confirmed = false; } else { skipConfirm = true; - await Promise.all(dirtyWorkingCopies.map(dirty => dirty.revert())); } } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index ff97dc81a12..9ae1935e2cd 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -55,7 +55,6 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { isNumber } from 'vs/base/common/types'; import { domEvent } from 'vs/base/browser/event'; import { IEditableData } from 'vs/workbench/common/views'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -643,8 +642,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { @IInstantiationService private instantiationService: IInstantiationService, @IWorkingCopyFileService private workingCopyFileService: IWorkingCopyFileService, @IHostService private hostService: IHostService, - @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, - @IWorkingCopyService private workingCopyService: IWorkingCopyService + @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService ) { this.toDispose = []; @@ -945,15 +943,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { const sourceFile = resource; const targetFile = joinPath(target.resource, basename(sourceFile)); - // if the target exists and is dirty, make sure to revert it. otherwise the dirty contents - // of the target file would replace the contents of the added file. since we already - // confirmed the overwrite before, this is OK. - if (this.workingCopyService.isDirty(targetFile)) { - await Promise.all(this.workingCopyService.getWorkingCopies(targetFile).map(workingCopy => workingCopy.revert({ soft: true }))); - } - - const copyTarget = joinPath(target.resource, basename(sourceFile)); - const stat = await this.workingCopyFileService.copy(sourceFile, copyTarget, true); + const stat = await this.workingCopyFileService.copy(sourceFile, targetFile, true); // if we only add one file, just open it directly if (resources.length === 1 && !stat.isDirectory) { this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); From 00ad0a93720a4f8990e91298e0fa55e77677de8d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Feb 2020 13:38:45 +0100 Subject: [PATCH 29/48] Fix #90931 --- .../configuration-editing/src/extension.ts | 33 ++--------------- .../src/extensionsProposals.ts | 35 +++++++++++++++++++ .../src/settingsDocumentHelper.ts | 14 ++++++-- 3 files changed, 50 insertions(+), 32 deletions(-) create mode 100644 extensions/configuration-editing/src/extensionsProposals.ts diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index 644e9bb1192..966073e23f8 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -7,6 +7,7 @@ import { getLocation, parse, visit } from 'jsonc-parser'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { SettingsDocument } from './settingsDocumentHelper'; +import { provideInstalledExtensionProposals } from './extensionsProposals'; const localize = nls.loadMessageBundle(); export function activate(context: vscode.ExtensionContext): void { @@ -80,7 +81,7 @@ function registerExtensionsCompletionsInExtensionsDocument(): vscode.Disposable const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position); if (location.path[0] === 'recommendations') { const extensionsContent = parse(document.getText()); - return provideInstalledExtensionProposals(extensionsContent, range); + return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], range, false); } return []; } @@ -94,41 +95,13 @@ function registerExtensionsCompletionsInWorkspaceConfigurationDocument(): vscode const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position); if (location.path[0] === 'extensions' && location.path[1] === 'recommendations') { const extensionsContent = parse(document.getText())['extensions']; - return provideInstalledExtensionProposals(extensionsContent, range); + return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], range, false); } return []; } }); } -function provideInstalledExtensionProposals(extensionsContent: IExtensionsContent, range: vscode.Range): vscode.ProviderResult { - const alreadyEnteredExtensions = extensionsContent && extensionsContent.recommendations || []; - if (Array.isArray(alreadyEnteredExtensions)) { - const knownExtensionProposals = vscode.extensions.all.filter(e => - !(e.id.startsWith('vscode.') - || e.id === 'Microsoft.vscode-markdown' - || alreadyEnteredExtensions.indexOf(e.id) > -1)); - if (knownExtensionProposals.length) { - return knownExtensionProposals.map(e => { - const item = new vscode.CompletionItem(e.id); - const insertText = `"${e.id}"`; - item.kind = vscode.CompletionItemKind.Value; - item.insertText = insertText; - item.range = range; - item.filterText = insertText; - return item; - }); - } else { - const example = new vscode.CompletionItem(localize('exampleExtension', "Example")); - example.insertText = '"vscode.csharp"'; - example.kind = vscode.CompletionItemKind.Value; - example.range = range; - return [example]; - } - } - return undefined; -} - vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', language: 'jsonc' }, { provideDocumentSymbols(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult { const result: vscode.SymbolInformation[] = []; diff --git a/extensions/configuration-editing/src/extensionsProposals.ts b/extensions/configuration-editing/src/extensionsProposals.ts new file mode 100644 index 00000000000..60533ae2975 --- /dev/null +++ b/extensions/configuration-editing/src/extensionsProposals.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); + + +export function provideInstalledExtensionProposals(existing: string[], range: vscode.Range, includeBuiltinExtensions: boolean): vscode.ProviderResult { + if (Array.isArray(existing)) { + const extensions = includeBuiltinExtensions ? vscode.extensions.all : vscode.extensions.all.filter(e => !(e.id.startsWith('vscode.') || e.id === 'Microsoft.vscode-markdown')); + const knownExtensionProposals = extensions.filter(e => existing.indexOf(e.id) === -1); + if (knownExtensionProposals.length) { + return knownExtensionProposals.map(e => { + const item = new vscode.CompletionItem(e.id); + const insertText = `"${e.id}"`; + item.kind = vscode.CompletionItemKind.Value; + item.insertText = insertText; + item.range = range; + item.filterText = insertText; + return item; + }); + } else { + const example = new vscode.CompletionItem(localize('exampleExtension', "Example")); + example.insertText = '"vscode.csharp"'; + example.kind = vscode.CompletionItemKind.Value; + example.range = range; + return [example]; + } + } + return undefined; +} + diff --git a/extensions/configuration-editing/src/settingsDocumentHelper.ts b/extensions/configuration-editing/src/settingsDocumentHelper.ts index 3fd25056379..4a7f80c2a2a 100644 --- a/extensions/configuration-editing/src/settingsDocumentHelper.ts +++ b/extensions/configuration-editing/src/settingsDocumentHelper.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { getLocation, Location } from 'jsonc-parser'; +import { getLocation, Location, parse } from 'jsonc-parser'; import * as nls from 'vscode-nls'; +import { provideInstalledExtensionProposals } from './extensionsProposals'; const localize = nls.loadMessageBundle(); @@ -13,7 +14,7 @@ export class SettingsDocument { constructor(private document: vscode.TextDocument) { } - public provideCompletionItems(position: vscode.Position, _token: vscode.CancellationToken): vscode.ProviderResult { + public provideCompletionItems(position: vscode.Position, _token: vscode.CancellationToken): vscode.ProviderResult { const location = getLocation(this.document.getText(), this.document.offsetAt(position)); const range = this.document.getWordRangeAtPosition(position) || new vscode.Range(position, position); @@ -41,6 +42,15 @@ export class SettingsDocument { }); } + // sync.ignoredExtensions + if (location.path[0] === 'sync.ignoredExtensions') { + let ignoredExtensions = []; + try { + ignoredExtensions = parse(this.document.getText())['sync.ignoredExtensions']; + } catch (e) {/* ignore error */ } + return provideInstalledExtensionProposals(ignoredExtensions, range, true); + } + return this.provideLanguageOverridesCompletionItems(location, position); } From 2fed7f87aea8be511b9c2e798db6eb0566a1f16d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Feb 2020 13:43:46 +0100 Subject: [PATCH 30/48] Fix #90930 --- src/vs/platform/userDataSync/common/userDataSync.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index b6cbde7d12f..e6787a3d605 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -5,7 +5,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; -import { IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionIdentifier, EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, allSettings } from 'vs/platform/configuration/common/configurationRegistry'; @@ -83,7 +83,12 @@ export function registerConfiguration(): IDisposable { }, 'sync.ignoredExtensions': { 'type': 'array', - description: localize('sync.ignoredExtensions', "Configure extensions to be ignored while synchronizing."), + 'description': localize('sync.ignoredExtensions', "List of extensions to be ignored while synchronizing. The identifier of an extension is always ${publisher}.${name}. For example: vscode.csharp."), + items: { + type: 'string', + pattern: EXTENSION_IDENTIFIER_PATTERN, + errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.") + }, 'default': [], 'scope': ConfigurationScope.APPLICATION, uniqueItems: true From 618db14dbcb57f45644ff852feaee6a2c9e9dba8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Feb 2020 13:52:36 +0100 Subject: [PATCH 31/48] working copy file - add getDirty() method with support for directories --- .../contrib/files/browser/fileActions.ts | 24 ++++++++------ .../common/workingCopyFileService.ts | 23 ++++++++++++-- .../browser/workingCopyFileService.test.ts | 31 +++++++++++++++++++ 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 93fc4f9c5d9..5c8be8905a0 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -145,7 +145,7 @@ export class GlobalNewUntitledFileAction extends Action { } } -async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { +async function deleteFiles(workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { let primaryButton: string; if (useTrash) { primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash"); @@ -153,20 +153,24 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyF primaryButton = nls.localize({ key: 'deleteButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete"); } - const distinctElements = resources.distinctParents(elements, e => e.resource); - // Handle dirty + const distinctElements = resources.distinctParents(elements, e => e.resource); + const dirtyWorkingCopies = new Set(); + for (const distinctElement of distinctElements) { + for (const dirtyWorkingCopy of workingCopyFileService.getDirty(distinctElement.resource)) { + dirtyWorkingCopies.add(dirtyWorkingCopy); + } + } let confirmed = true; - const dirtyWorkingCopies = workingCopyService.dirtyWorkingCopies.filter(workingCopy => distinctElements.some(e => resources.isEqualOrParent(workingCopy.resource, e.resource))); - if (dirtyWorkingCopies.length) { + if (dirtyWorkingCopies.size) { let message: string; if (distinctElements.length > 1) { message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?"); } else if (distinctElements[0].isDirectory) { - if (dirtyWorkingCopies.length === 1) { + if (dirtyWorkingCopies.size === 1) { message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder {0} with unsaved changes in 1 file. Do you want to continue?", distinctElements[0].name); } else { - message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder {0} with unsaved changes in {1} files. Do you want to continue?", distinctElements[0].name, dirtyWorkingCopies.length); + message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder {0} with unsaved changes in {1} files. Do you want to continue?", distinctElements[0].name, dirtyWorkingCopies.size); } } else { message = nls.localize('dirtyMessageFileDelete', "You are deleting {0} with unsaved changes. Do you want to continue?", distinctElements[0].name); @@ -274,7 +278,7 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyF skipConfirm = true; - return deleteFiles(workingCopyService, workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm); + return deleteFiles(workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm); } } } @@ -967,7 +971,7 @@ export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); const stats = explorerService.getContext(true).filter(s => !s.isRoot); if (stats.length) { - await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); + await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); } }; @@ -976,7 +980,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => { const stats = explorerService.getContext(true).filter(s => !s.isRoot); if (stats.length) { - await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); + await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); } }; diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts index 166cb52c36e..fdb12e44d28 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts @@ -118,6 +118,18 @@ export interface IWorkingCopyFileService { delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise; //#endregion + + + //#region Path related + + /** + * Will return all working copies that are dirty matching the provided resource. + * If the resource is a folder and the scheme supports file operations, a working + * copy that is dirty and is a child of that folder will also be returned. + */ + getDirty(resource: URI): IWorkingCopy[]; + + //#endregion } export class WorkingCopyFileService extends Disposable implements IWorkingCopyFileService { @@ -167,7 +179,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi // handle dirty working copies depending on the operation: // - move: revert both source and target (if any) // - copy: revert target (if any) - const dirtyWorkingCopies = (move ? [...this.getDirtyWorkingCopies(source), ...this.getDirtyWorkingCopies(target)] : this.getDirtyWorkingCopies(target)); + const dirtyWorkingCopies = (move ? [...this.getDirty(source), ...this.getDirty(target)] : this.getDirty(target)); await Promise.all(dirtyWorkingCopies.map(dirtyWorkingCopy => dirtyWorkingCopy.revert({ soft: true }))); // now we can rename the source to target via file operation @@ -202,7 +214,7 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi // Check for any existing dirty working copies for the resource // and do a soft revert before deleting to be able to close // any opened editor with these working copies - const dirtyWorkingCopies = this.getDirtyWorkingCopies(resource); + const dirtyWorkingCopies = this.getDirty(resource); await Promise.all(dirtyWorkingCopies.map(dirtyWorkingCopy => dirtyWorkingCopy.revert({ soft: true }))); // Now actually delete from disk @@ -220,7 +232,10 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi await this._onDidRunWorkingCopyFileOperation.fireAsync(event, CancellationToken.None); } - private getDirtyWorkingCopies(resource: URI): IWorkingCopy[] { + + //#region Path related + + getDirty(resource: URI): IWorkingCopy[] { return this.workingCopyService.dirtyWorkingCopies.filter(dirty => { if (this.fileService.canHandleResource(resource)) { // only check for parents if the resource can be handled @@ -232,6 +247,8 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi return isEqual(dirty.resource, resource); }); } + + //#endregion } registerSingleton(IWorkingCopyFileService, WorkingCopyFileService, true); diff --git a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts index 8b847827370..642486db70c 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts @@ -165,4 +165,35 @@ suite('WorkingCopyFileService', () => { listener1.dispose(); listener2.dispose(); } + + test('getDirty', async function () { + const model1 = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file-1.txt'), 'utf8', undefined); + (accessor.textFileService.files).add(model.resource, model); + + const model2 = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file-2.txt'), 'utf8', undefined); + (accessor.textFileService.files).add(model.resource, model); + + let dirty = accessor.workingCopyFileService.getDirty(model1.resource); + assert.equal(dirty.length, 0); + + await model1.load(); + model1.textEditorModel!.setValue('foo'); + + dirty = accessor.workingCopyFileService.getDirty(model1.resource); + assert.equal(dirty.length, 1); + assert.equal(dirty[0], model1); + + dirty = accessor.workingCopyFileService.getDirty(toResource.call(this, '/path')); + assert.equal(dirty.length, 1); + assert.equal(dirty[0], model1); + + await model2.load(); + model2.textEditorModel!.setValue('bar'); + + dirty = accessor.workingCopyFileService.getDirty(toResource.call(this, '/path')); + assert.equal(dirty.length, 2); + + model1.dispose(); + model2.dispose(); + }); }); From 89eb9fe9e6415f669720928aa22201c3a1ef24d3 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 19 Feb 2020 13:53:39 +0100 Subject: [PATCH 32/48] Relax IIdentifiedSingleEditOperation.range --- src/vs/editor/common/controller/cursor.ts | 23 +++++----- src/vs/editor/common/core/range.ts | 18 +++++++- src/vs/editor/common/editorCommon.ts | 8 ++-- src/vs/editor/common/model.ts | 43 +++++++++++++++++-- src/vs/editor/common/model/editStack.ts | 6 +-- .../pieceTreeTextBuffer.ts | 6 +-- src/vs/editor/common/model/textModel.ts | 41 ++++++++++++------ .../common/services/modelServiceImpl.ts | 4 +- .../contrib/comment/blockCommentCommand.ts | 2 +- .../contrib/comment/lineCommentCommand.ts | 2 +- src/vs/editor/test/browser/testCommand.ts | 10 ++--- .../textBufferAutoTestUtils.ts | 31 +++++-------- src/vs/monaco.d.ts | 36 +++++++++++++--- .../workbench/api/browser/mainThreadEditor.ts | 2 +- 14 files changed, 157 insertions(+), 75 deletions(-) diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 2b4be5b293c..82204018f2c 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -12,10 +12,10 @@ import { DeleteOperations } from 'vs/editor/common/controller/cursorDeleteOperat import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { TypeOperations, TypeWithAutoClosingCommand } from 'vs/editor/common/controller/cursorTypeOperations'; import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; +import { Range, IRange } from 'vs/editor/common/core/range'; import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer } from 'vs/editor/common/model'; +import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model'; import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; @@ -903,8 +903,8 @@ class CommandExecutor { if (commandsData.hadTrackedEditOperation && filteredOperations.length > 0) { filteredOperations[0]._isTracked = true; } - let selectionsAfter = ctx.model.pushEditOperations(ctx.selectionsBefore, filteredOperations, (inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] => { - let groupedInverseEditOperations: IIdentifiedSingleEditOperation[][] = []; + let selectionsAfter = ctx.model.pushEditOperations(ctx.selectionsBefore, filteredOperations, (inverseEditOperations: IValidEditOperation[]): Selection[] => { + let groupedInverseEditOperations: IValidEditOperation[][] = []; for (let i = 0; i < ctx.selectionsBefore.length; i++) { groupedInverseEditOperations[i] = []; } @@ -915,7 +915,7 @@ class CommandExecutor { } groupedInverseEditOperations[op.identifier.major].push(op); } - const minorBasedSorter = (a: IIdentifiedSingleEditOperation, b: IIdentifiedSingleEditOperation) => { + const minorBasedSorter = (a: IValidEditOperation, b: IValidEditOperation) => { return a.identifier!.minor - b.identifier!.minor; }; let cursorSelections: Selection[] = []; @@ -1000,8 +1000,8 @@ class CommandExecutor { let operations: IIdentifiedSingleEditOperation[] = []; let operationMinor = 0; - const addEditOperation = (selection: Range, text: string | null, forceMoveMarkers: boolean = false) => { - if (selection.isEmpty() && text === '') { + const addEditOperation = (range: IRange, text: string | null, forceMoveMarkers: boolean = false) => { + if (Range.isEmpty(range) && text === '') { // This command wants to add a no-op => no thank you return; } @@ -1010,7 +1010,7 @@ class CommandExecutor { major: majorIdentifier, minor: operationMinor++ }, - range: selection, + range: range, text: text, forceMoveMarkers: forceMoveMarkers, isAutoWhitespaceEdit: command.insertsAutoWhitespace @@ -1018,12 +1018,13 @@ class CommandExecutor { }; let hadTrackedEditOperation = false; - const addTrackedEditOperation = (selection: Range, text: string | null, forceMoveMarkers?: boolean) => { + const addTrackedEditOperation = (selection: IRange, text: string | null, forceMoveMarkers?: boolean) => { hadTrackedEditOperation = true; addEditOperation(selection, text, forceMoveMarkers); }; - const trackSelection = (selection: Selection, trackPreviousOnEmpty?: boolean) => { + const trackSelection = (_selection: ISelection, trackPreviousOnEmpty?: boolean) => { + const selection = Selection.liftSelection(_selection); let stickiness: TrackedRangeStickiness; if (selection.isEmpty()) { if (typeof trackPreviousOnEmpty === 'boolean') { @@ -1093,7 +1094,7 @@ class CommandExecutor { const previousOp = operations[i - 1]; const currentOp = operations[i]; - if (previousOp.range.getStartPosition().isBefore(currentOp.range.getEndPosition())) { + if (Range.getStartPosition(previousOp.range).isBefore(Range.getEndPosition(currentOp.range))) { let loserMajor: number; diff --git a/src/vs/editor/common/core/range.ts b/src/vs/editor/common/core/range.ts index e212e757dce..d684e3b9520 100644 --- a/src/vs/editor/common/core/range.ts +++ b/src/vs/editor/common/core/range.ts @@ -264,14 +264,28 @@ export class Range { * Return the end position (which will be after or equal to the start position) */ public getEndPosition(): Position { - return new Position(this.endLineNumber, this.endColumn); + return Range.getEndPosition(this); + } + + /** + * Return the end position (which will be after or equal to the start position) + */ + public static getEndPosition(range: IRange): Position { + return new Position(range.endLineNumber, range.endColumn); } /** * Return the start position (which will be before or equal to the end position) */ public getStartPosition(): Position { - return new Position(this.startLineNumber, this.startColumn); + return Range.getStartPosition(this); + } + + /** + * Return the start position (which will be before or equal to the end position) + */ + public static getStartPosition(range: IRange): Position { + return new Position(range.startLineNumber, range.startColumn); } /** diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 426b028ed3d..fe7e0f9eab8 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -10,7 +10,7 @@ import { ConfigurationChangedEvent, IComputedEditorOptions, IEditorOptions } fro import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; -import { IIdentifiedSingleEditOperation, IModelDecorationsChangeAccessor, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IModelDecorationsChangeAccessor, ITextModel, OverviewRulerLane, TrackedRangeStickiness, IValidEditOperation } from 'vs/editor/common/model'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; /** @@ -22,7 +22,7 @@ export interface IEditOperationBuilder { * @param range The range to replace (delete). May be empty to represent a simple insert. * @param text The text to replace with. May be null to represent a simple delete. */ - addEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void; + addEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void; /** * Add a new edit operation (a replace operation). @@ -30,7 +30,7 @@ export interface IEditOperationBuilder { * @param range The range to replace (delete). May be empty to represent a simple insert. * @param text The text to replace with. May be null to represent a simple delete. */ - addTrackedEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void; + addTrackedEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void; /** * Track `selection` when applying edit operations. @@ -51,7 +51,7 @@ export interface ICursorStateComputerData { /** * Get the inverse edit operations of the added edit operations. */ - getInverseEditOperations(): IIdentifiedSingleEditOperation[]; + getInverseEditOperations(): IValidEditOperation[]; /** * Get a previously tracked selection. * @param id The unique identifier returned by `trackSelection`. diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 2a632baf1c8..566aca3d7a1 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -358,6 +358,27 @@ export interface IIdentifiedSingleEditOperation { _isTracked?: boolean; } +export interface IValidEditOperation { + /** + * An identifier associated with this single edit operation. + * @internal + */ + identifier: ISingleEditOperationIdentifier | null; + /** + * The range to replace. This can be empty to emulate a simple insert. + */ + range: Range; + /** + * The text to replace with. This can be null to emulate a simple delete. + */ + text: string | null; + /** + * This indicates that this operation has "insert" semantics. + * i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved. + */ + forceMoveMarkers: boolean; +} + /** * A callback that can compute the cursor state after applying a series of edit operations. */ @@ -365,7 +386,7 @@ export interface ICursorStateComputer { /** * A callback that can compute the resulting cursors state after some edit operations have been executed. */ - (inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] | null; + (inverseEditOperations: IValidEditOperation[]): Selection[] | null; } export class TextModelResolvedOptions { @@ -1063,7 +1084,7 @@ export interface ITextModel { * @param operations The edit operations. * @return The inverse edit operations, that, when applied, will bring the model back to the previous state. */ - applyEdits(operations: IIdentifiedSingleEditOperation[]): IIdentifiedSingleEditOperation[]; + applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[]; /** * Change the end of line sequence without recording in the undo stack. @@ -1206,6 +1227,20 @@ export const enum ModelConstants { FIRST_LINE_DETECTION_LENGTH_LIMIT = 1000 } +/** + * @internal + */ +export class ValidAnnotatedEditOperation implements IIdentifiedSingleEditOperation { + constructor( + public readonly identifier: ISingleEditOperationIdentifier | null, + public readonly range: Range, + public readonly text: string | null, + public readonly forceMoveMarkers: boolean, + public readonly isAutoWhitespaceEdit: boolean, + public readonly _isTracked: boolean, + ) { } +} + /** * @internal */ @@ -1234,7 +1269,7 @@ export interface ITextBuffer { getLineLastNonWhitespaceColumn(lineNumber: number): number; setEOL(newEOL: '\r\n' | '\n'): void; - applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult; + applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult; findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[]; } @@ -1244,7 +1279,7 @@ export interface ITextBuffer { export class ApplyEditsResult { constructor( - public readonly reverseEdits: IIdentifiedSingleEditOperation[], + public readonly reverseEdits: IValidEditOperation[], public readonly changes: IInternalModelContentChange[], public readonly trimAutoWhitespaceLineNumbers: number[] | null ) { } diff --git a/src/vs/editor/common/model/editStack.ts b/src/vs/editor/common/model/editStack.ts index add6b310ea5..a870a0822d1 100644 --- a/src/vs/editor/common/model/editStack.ts +++ b/src/vs/editor/common/model/editStack.ts @@ -5,11 +5,11 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Selection } from 'vs/editor/common/core/selection'; -import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; +import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; interface IEditOperation { - operations: IIdentifiedSingleEditOperation[]; + operations: IValidEditOperation[]; } interface IStackElement { @@ -174,7 +174,7 @@ export class EditStack { return stackElement!.afterCursorState; } - private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] | null { + private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IValidEditOperation[]): Selection[] | null { try { return cursorStateComputer ? cursorStateComputer(inverseEditOperations) : null; } catch (e) { diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts index a987231c3ab..0d9c539ff8a 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts @@ -6,7 +6,7 @@ import * as strings from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { ApplyEditsResult, EndOfLinePreference, FindMatch, IIdentifiedSingleEditOperation, IInternalModelContentChange, ISingleEditOperationIdentifier, ITextBuffer, ITextSnapshot } from 'vs/editor/common/model'; +import { ApplyEditsResult, EndOfLinePreference, FindMatch, IInternalModelContentChange, ISingleEditOperationIdentifier, ITextBuffer, ITextSnapshot, ValidAnnotatedEditOperation, IValidEditOperation } from 'vs/editor/common/model'; import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase'; import { SearchData } from 'vs/editor/common/model/textModelSearch'; @@ -21,7 +21,7 @@ export interface IValidatedEditOperation { isAutoWhitespaceEdit: boolean; } -export interface IReverseSingleEditOperation extends IIdentifiedSingleEditOperation { +export interface IReverseSingleEditOperation extends IValidEditOperation { sortIndex: number; } @@ -201,7 +201,7 @@ export class PieceTreeTextBuffer implements ITextBuffer { this._pieceTree.setEOL(newEOL); } - public applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult { + public applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult { let mightContainRTL = this._mightContainRTL; let mightContainNonBasicASCII = this._mightContainNonBasicASCII; let canReduceOperations = true; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 8aa9b5a53d4..68660134bf7 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -1154,18 +1154,40 @@ export class TextModel extends Disposable implements model.ITextModel { } } + private _validateEditOperation(rawOperation: model.IIdentifiedSingleEditOperation): model.ValidAnnotatedEditOperation { + if (rawOperation instanceof model.ValidAnnotatedEditOperation) { + return rawOperation; + } + return new model.ValidAnnotatedEditOperation( + rawOperation.identifier || null, + this.validateRange(rawOperation.range), + rawOperation.text, + rawOperation.forceMoveMarkers || false, + rawOperation.isAutoWhitespaceEdit || false, + rawOperation._isTracked || false + ); + } + + private _validateEditOperations(rawOperations: model.IIdentifiedSingleEditOperation[]): model.ValidAnnotatedEditOperation[] { + const result: model.ValidAnnotatedEditOperation[] = []; + for (let i = 0, len = rawOperations.length; i < len; i++) { + result[i] = this._validateEditOperation(rawOperations[i]); + } + return result; + } + public pushEditOperations(beforeCursorState: Selection[], editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); - return this._pushEditOperations(beforeCursorState, editOperations, cursorStateComputer); + return this._pushEditOperations(beforeCursorState, this._validateEditOperations(editOperations), cursorStateComputer); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } } - private _pushEditOperations(beforeCursorState: Selection[], editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { + private _pushEditOperations(beforeCursorState: Selection[], editOperations: model.ValidAnnotatedEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) { // Go through each saved line number and insert a trim whitespace edit // if it is safe to do so (no conflicts with other edits). @@ -1238,10 +1260,8 @@ export class TextModel extends Disposable implements model.ITextModel { } if (allowTrimLine) { - editOperations.push({ - range: new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn), - text: null - }); + const trimRange = new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn); + editOperations.push(new model.ValidAnnotatedEditOperation(null, trimRange, null, false, false, false)); } } @@ -1252,21 +1272,18 @@ export class TextModel extends Disposable implements model.ITextModel { return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); } - public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IIdentifiedSingleEditOperation[] { + public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IValidEditOperation[] { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); - return this._applyEdits(rawOperations); + return this._applyEdits(this._validateEditOperations(rawOperations)); } finally { this._eventEmitter.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit(); } } - private _applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IIdentifiedSingleEditOperation[] { - for (let i = 0, len = rawOperations.length; i < len; i++) { - rawOperations[i].range = this.validateRange(rawOperations[i].range); - } + private _applyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] { const oldLineCount = this._buffer.getLineCount(); const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace); diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index bc8824d7f89..a46a64c4db5 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -11,7 +11,7 @@ import { URI } from 'vs/base/common/uri'; import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; -import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model'; +import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions, IValidEditOperation } from 'vs/editor/common/model'; import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { LanguageIdentifier, DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits, TokenMetadata, FontStyle, MetadataConsts } from 'vs/editor/common/modes'; @@ -305,7 +305,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { model.pushEditOperations( [], ModelServiceImpl._computeEdits(model, textBuffer), - (inverseEditOperations: IIdentifiedSingleEditOperation[]) => [] + (inverseEditOperations: IValidEditOperation[]) => [] ); model.pushStackElement(); } diff --git a/src/vs/editor/contrib/comment/blockCommentCommand.ts b/src/vs/editor/contrib/comment/blockCommentCommand.ts index efa593a811d..0a6d6a59ea5 100644 --- a/src/vs/editor/contrib/comment/blockCommentCommand.ts +++ b/src/vs/editor/contrib/comment/blockCommentCommand.ts @@ -9,7 +9,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ICommand, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; +import { ITextModel, IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; export class BlockCommentCommand implements ICommand { diff --git a/src/vs/editor/contrib/comment/lineCommentCommand.ts b/src/vs/editor/contrib/comment/lineCommentCommand.ts index f266b70380a..bf088f0d67c 100644 --- a/src/vs/editor/contrib/comment/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/lineCommentCommand.ts @@ -205,7 +205,7 @@ export class LineCommentCommand implements ICommand { for (let i = 0, len = ops.length; i < len; i++) { builder.addEditOperation(ops[i].range, ops[i].text); - if (ops[i].range.isEmpty() && ops[i].range.getStartPosition().equals(cursorPosition)) { + if (Range.isEmpty(ops[i].range) && Range.getStartPosition(ops[i].range).equals(cursorPosition)) { const lineContent = model.getLineContent(cursorPosition.lineNumber); if (lineContent.length + 1 === cursorPosition.column) { this._deltaColumn = (ops[i].text || '').length; diff --git a/src/vs/editor/test/browser/testCommand.ts b/src/vs/editor/test/browser/testCommand.ts index 8126f24735a..f5670377c6e 100644 --- a/src/vs/editor/test/browser/testCommand.ts +++ b/src/vs/editor/test/browser/testCommand.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; +import { IRange } from 'vs/editor/common/core/range'; +import { Selection, ISelection } from 'vs/editor/common/core/selection'; import { ICommand, Handler, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; @@ -50,7 +50,7 @@ export function testCommand( export function getEditOperation(model: ITextModel, command: ICommand): IIdentifiedSingleEditOperation[] { let operations: IIdentifiedSingleEditOperation[] = []; let editOperationBuilder: IEditOperationBuilder = { - addEditOperation: (range: Range, text: string, forceMoveMarkers: boolean = false) => { + addEditOperation: (range: IRange, text: string, forceMoveMarkers: boolean = false) => { operations.push({ range: range, text: text, @@ -58,7 +58,7 @@ export function getEditOperation(model: ITextModel, command: ICommand): IIdentif }); }, - addTrackedEditOperation: (range: Range, text: string, forceMoveMarkers: boolean = false) => { + addTrackedEditOperation: (range: IRange, text: string, forceMoveMarkers: boolean = false) => { operations.push({ range: range, text: text, @@ -67,7 +67,7 @@ export function getEditOperation(model: ITextModel, command: ICommand): IIdentif }, - trackSelection: (selection: Selection) => { + trackSelection: (selection: ISelection) => { return ''; } }; diff --git a/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts b/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts index c483bb64f81..49841dabc05 100644 --- a/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts +++ b/src/vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts @@ -5,7 +5,7 @@ import { CharCode } from 'vs/base/common/charCode'; import { Range } from 'vs/editor/common/core/range'; -import { DefaultEndOfLine, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferBuilder } from 'vs/editor/common/model'; +import { DefaultEndOfLine, ITextBuffer, ITextBufferBuilder, ValidAnnotatedEditOperation } from 'vs/editor/common/model'; export function getRandomInt(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min; @@ -31,7 +31,7 @@ export function getRandomString(minLength: number, maxLength: number): string { return r; } -export function generateRandomEdits(chunks: string[], editCnt: number): IIdentifiedSingleEditOperation[] { +export function generateRandomEdits(chunks: string[], editCnt: number): ValidAnnotatedEditOperation[] { let lines: string[] = []; for (const chunk of chunks) { let newLines = chunk.split(/\r\n|\r|\n/); @@ -43,7 +43,7 @@ export function generateRandomEdits(chunks: string[], editCnt: number): IIdentif } } - let ops: IIdentifiedSingleEditOperation[] = []; + let ops: ValidAnnotatedEditOperation[] = []; for (let i = 0; i < editCnt; i++) { let line = getRandomInt(1, lines.length); @@ -54,17 +54,14 @@ export function generateRandomEdits(chunks: string[], editCnt: number): IIdentif text = getRandomString(5, 10); } - ops.push({ - text: text, - range: new Range(line, startColumn, line, endColumn) - }); + ops.push(new ValidAnnotatedEditOperation(null, new Range(line, startColumn, line, endColumn), text, false, false, false)); lines[line - 1] = lines[line - 1].substring(0, startColumn - 1) + text + lines[line - 1].substring(endColumn - 1); } return ops; } -export function generateSequentialInserts(chunks: string[], editCnt: number): IIdentifiedSingleEditOperation[] { +export function generateSequentialInserts(chunks: string[], editCnt: number): ValidAnnotatedEditOperation[] { let lines: string[] = []; for (const chunk of chunks) { let newLines = chunk.split(/\r\n|\r|\n/); @@ -76,7 +73,7 @@ export function generateSequentialInserts(chunks: string[], editCnt: number): II } } - let ops: IIdentifiedSingleEditOperation[] = []; + let ops: ValidAnnotatedEditOperation[] = []; for (let i = 0; i < editCnt; i++) { let line = lines.length; @@ -90,16 +87,13 @@ export function generateSequentialInserts(chunks: string[], editCnt: number): II lines[line - 1] += text; } - ops.push({ - text: text, - range: new Range(line, column, line, column) - }); + ops.push(new ValidAnnotatedEditOperation(null, new Range(line, column, line, column), text, false, false, false)); } return ops; } -export function generateRandomReplaces(chunks: string[], editCnt: number, searchStringLen: number, replaceStringLen: number): IIdentifiedSingleEditOperation[] { +export function generateRandomReplaces(chunks: string[], editCnt: number, searchStringLen: number, replaceStringLen: number): ValidAnnotatedEditOperation[] { let lines: string[] = []; for (const chunk of chunks) { let newLines = chunk.split(/\r\n|\r|\n/); @@ -111,7 +105,7 @@ export function generateRandomReplaces(chunks: string[], editCnt: number, search } } - let ops: IIdentifiedSingleEditOperation[] = []; + let ops: ValidAnnotatedEditOperation[] = []; let chunkSize = Math.max(1, Math.floor(lines.length / editCnt)); let chunkCnt = Math.floor(lines.length / chunkSize); let replaceString = getRandomString(replaceStringLen, replaceStringLen); @@ -125,10 +119,7 @@ export function generateRandomReplaces(chunks: string[], editCnt: number, search let startColumn = getRandomInt(1, maxColumn); let endColumn = Math.min(maxColumn, startColumn + searchStringLen); - ops.push({ - text: replaceString, - range: new Range(line, startColumn, line, endColumn) - }); + ops.push(new ValidAnnotatedEditOperation(null, new Range(line, startColumn, line, endColumn), replaceString, false, false, false)); previousChunksLength = endLine; } @@ -166,4 +157,4 @@ export function generateRandomChunkWithLF(minLength: number, maxLength: number): } } return r; -} \ No newline at end of file +} diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ccd369acc0d..fac22d2960d 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -638,10 +638,18 @@ declare namespace monaco { * Return the end position (which will be after or equal to the start position) */ getEndPosition(): Position; + /** + * Return the end position (which will be after or equal to the start position) + */ + static getEndPosition(range: IRange): Position; /** * Return the start position (which will be before or equal to the end position) */ getStartPosition(): Position; + /** + * Return the start position (which will be before or equal to the end position) + */ + static getStartPosition(range: IRange): Position; /** * Transform to a user presentable string representation. */ @@ -1508,7 +1516,7 @@ declare namespace monaco.editor { /** * The range to replace. This can be empty to emulate a simple insert. */ - range: Range; + range: IRange; /** * The text to replace with. This can be null to emulate a simple delete. */ @@ -1520,6 +1528,22 @@ declare namespace monaco.editor { forceMoveMarkers?: boolean; } + export interface IValidEditOperation { + /** + * The range to replace. This can be empty to emulate a simple insert. + */ + range: Range; + /** + * The text to replace with. This can be null to emulate a simple delete. + */ + text: string | null; + /** + * This indicates that this operation has "insert" semantics. + * i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved. + */ + forceMoveMarkers: boolean; + } + /** * A callback that can compute the cursor state after applying a series of edit operations. */ @@ -1527,7 +1551,7 @@ declare namespace monaco.editor { /** * A callback that can compute the resulting cursors state after some edit operations have been executed. */ - (inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] | null; + (inverseEditOperations: IValidEditOperation[]): Selection[] | null; } export class TextModelResolvedOptions { @@ -1867,7 +1891,7 @@ declare namespace monaco.editor { * @param operations The edit operations. * @return The inverse edit operations, that, when applied, will bring the model back to the previous state. */ - applyEdits(operations: IIdentifiedSingleEditOperation[]): IIdentifiedSingleEditOperation[]; + applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[]; /** * Change the end of line sequence without recording in the undo stack. * This can have dire consequences on the undo stack! See @pushEOL for the preferred way. @@ -1919,14 +1943,14 @@ declare namespace monaco.editor { * @param range The range to replace (delete). May be empty to represent a simple insert. * @param text The text to replace with. May be null to represent a simple delete. */ - addEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void; + addEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void; /** * Add a new edit operation (a replace operation). * The inverse edits will be accessible in `ICursorStateComputerData.getInverseEditOperations()` * @param range The range to replace (delete). May be empty to represent a simple insert. * @param text The text to replace with. May be null to represent a simple delete. */ - addTrackedEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void; + addTrackedEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void; /** * Track `selection` when applying edit operations. * A best effort will be made to not grow/expand the selection. @@ -1946,7 +1970,7 @@ declare namespace monaco.editor { /** * Get the inverse edit operations of the added edit operations. */ - getInverseEditOperations(): IIdentifiedSingleEditOperation[]; + getInverseEditOperations(): IValidEditOperation[]; /** * Get a previously tracked selection. * @param id The unique identifier returned by `trackSelection`. diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 4de14d85d61..e7d981fe98e 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -10,7 +10,7 @@ import { RenderLineNumbersType, TextEditorCursorStyle, cursorStyleToString, Edit import { IRange, Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { IDecorationOptions, ScrollType } from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, ISingleEditOperation, ITextModel, ITextModelUpdateOptions } from 'vs/editor/common/model'; +import { ISingleEditOperation, ITextModel, ITextModelUpdateOptions, IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol'; From 8031bfdfaf95966dbbec2743b07a6c8aafdb44b1 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 19 Feb 2020 15:05:44 +0100 Subject: [PATCH 33/48] Add test for #90973 --- .../test/browser/controller/cursor.test.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index c4b0df1d3cb..6c6595c516e 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -2880,6 +2880,33 @@ suite('Editor Controller - Cursor Configuration', () => { model.dispose(); }); + + test('issue #90973: Undo brings back model alternative version', () => { + let model = createTextModel( + [ + '' + ].join('\n'), + { + insertSpaces: false, + } + ); + + withTestCodeEditor(null, { model: model }, (editor, cursor) => { + const beforeVersion = model.getVersionId(); + const beforeAltVersion = model.getAlternativeVersionId(); + cursorCommand(cursor, H.Type, { text: 'Hello' }, 'keyboard'); + cursorCommand(cursor, H.Undo, {}); + const afterVersion = model.getVersionId(); + const afterAltVersion = model.getAlternativeVersionId(); + + assert.notEqual(beforeVersion, afterVersion); + assert.equal(beforeAltVersion, afterAltVersion); + }); + + model.dispose(); + }); + + }); suite('Editor Controller - Indentation Rules', () => { From 9d27bbe7ae49d7b3d843f59a3bbffa1c8090fc90 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 19 Feb 2020 15:32:03 +0100 Subject: [PATCH 34/48] fixes #90940 --- .../workbench/contrib/debug/browser/debugSession.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 8af3f1ee82f..00ed0d36faf 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -915,14 +915,13 @@ export class DebugSession implements IDebugSession { // Disconnects and clears state. Session can be initialized again for a new connection. private shutdown(): void { dispose(this.rawListeners); + if (this.raw) { + this.raw.disconnect(); + this.raw.dispose(); + this.raw = undefined; + } this.fetchThreadsScheduler = undefined; this.model.clearThreads(this.getId(), true); - if (this.raw) { - const raw = this.raw; - this.raw = undefined; - raw.disconnect(); - raw.dispose(); - } this._onDidChangeState.fire(); } From 4957d8d6df8fcd20280a93c94b7ec3c5d983d25f Mon Sep 17 00:00:00 2001 From: Gustavo Cassel Date: Wed, 19 Feb 2020 11:38:44 -0300 Subject: [PATCH 35/48] Exposed Open/Save dialog title on public VSCode Proposed API (#90493) Co-authored-by: Alex Ross --- src/vs/vscode.proposed.d.ts | 29 +++++++++++++++++++ .../api/browser/mainThreadDialogs.ts | 6 ++-- .../workbench/api/common/extHost.protocol.ts | 2 ++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 095df41de3e..c726e767cde 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1674,4 +1674,33 @@ declare module 'vscode' { } //#endregion + + //#region Dialog title: https://github.com/microsoft/vscode/issues/82871 + + /** + * Options to configure the behaviour of a file open dialog. + * + * * Note 1: A dialog can select files, folders, or both. This is not true for Windows + * which enforces to open either files or folder, but *not both*. + * * Note 2: Explicitly setting `canSelectFiles` and `canSelectFolders` to `false` is futile + * and the editor then silently adjusts the options to select files. + */ + export interface OpenDialogOptions { + /** + * Dialog title + */ + title?: string; + } + + /** + * Options to configure the behaviour of a file save dialog. + */ + export interface SaveDialogOptions { + /** + * Dialog title + */ + title?: string; + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadDialogs.ts b/src/vs/workbench/api/browser/mainThreadDialogs.ts index 94ab09b7eba..79c26c34570 100644 --- a/src/vs/workbench/api/browser/mainThreadDialogs.ts +++ b/src/vs/workbench/api/browser/mainThreadDialogs.ts @@ -37,7 +37,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { canSelectFiles: options.canSelectFiles || (!options.canSelectFiles && !options.canSelectFolders), canSelectFolders: options.canSelectFolders, canSelectMany: options.canSelectMany, - defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined + defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined, + title: options.title || undefined }; if (options.filters) { result.filters = []; @@ -49,7 +50,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { private static _convertSaveOptions(options: MainThreadDialogSaveOptions): ISaveDialogOptions { const result: ISaveDialogOptions = { defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined, - saveLabel: options.saveLabel || undefined + saveLabel: options.saveLabel || undefined, + title: options.title || undefined }; if (options.filters) { result.filters = []; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 56f2530c0ed..ddaa2ecd71a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -174,12 +174,14 @@ export interface MainThreadDialogOpenOptions { canSelectFolders?: boolean; canSelectMany?: boolean; filters?: { [name: string]: string[]; }; + title?: string; } export interface MainThreadDialogSaveOptions { defaultUri?: UriComponents; saveLabel?: string; filters?: { [name: string]: string[]; }; + title?: string; } export interface MainThreadDiaglogsShape extends IDisposable { From 6a660fde54e1d6175061b6a50f1111608d974891 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 19 Feb 2020 15:45:03 +0100 Subject: [PATCH 36/48] Fix eager suggest in SCM message --- src/vs/workbench/contrib/scm/browser/repositoryPane.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index a43d2fa6f66..c5b5c59c295 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -735,7 +735,8 @@ export class RepositoryPane extends ViewPane { wrappingStrategy: 'advanced', wrappingIndent: 'none', padding: { top: 3, bottom: 3 }, - suggest: { showWords: false } + suggest: { showWords: false }, + quickSuggestions: false }; const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { From da49b057d1b33c3efe59dc44634d04dbbe922010 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Feb 2020 15:58:13 +0100 Subject: [PATCH 37/48] Fix #89832 --- .../userDataSync/common/settingsMerge.ts | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/userDataSync/common/settingsMerge.ts b/src/vs/platform/userDataSync/common/settingsMerge.ts index 9b9b146fd39..212b9ccafc4 100644 --- a/src/vs/platform/userDataSync/common/settingsMerge.ts +++ b/src/vs/platform/userDataSync/common/settingsMerge.ts @@ -576,15 +576,17 @@ function parseSettings(content: string): INode[] { if (hierarchyLevel === 0) { if (sep === ',') { const node = nodes.pop(); - nodes.push({ - startOffset: node!.startOffset, - endOffset: node!.endOffset, - value: node!.value, - setting: { - key: node!.setting!.key, - hasCommaSeparator: true - } - }); + if (node) { + nodes.push({ + startOffset: node!.startOffset, + endOffset: node!.endOffset, + value: node!.value, + setting: { + key: node!.setting!.key, + hasCommaSeparator: true + } + }); + } } } }, From 1233630ededb7722f593890c579cf82b827a11f5 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Wed, 19 Feb 2020 16:01:14 +0100 Subject: [PATCH 38/48] Emit change events from the configuration service (microsoft/monaco-editor#1833) --- .../standalone/browser/simpleServices.ts | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 773523bab16..caa9f3d6276 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -26,7 +26,7 @@ import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfigurationService'; import { CommandsRegistry, ICommand, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel, IConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { Configuration, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { Configuration, ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -448,10 +448,6 @@ export class SimpleConfigurationService implements IConfigurationService { this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); } - private configuration(): Configuration { - return this._configuration; - } - getValue(): T; getValue(section: string): T; getValue(overrides: IConfigurationOverrides): T; @@ -459,20 +455,43 @@ export class SimpleConfigurationService implements IConfigurationService { getValue(arg1?: any, arg2?: any): any { const section = typeof arg1 === 'string' ? arg1 : undefined; const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {}; - return this.configuration().getValue(section, overrides, undefined); + return this._configuration.getValue(section, overrides, undefined); } - public updateValue(key: string, value: any, arg3?: any, arg4?: any): Promise { - this.configuration().updateValue(key, value); + public updateValues(values: [string, any][]): Promise { + const previous = { data: this._configuration.toData() }; + + let changedKeys: string[] = []; + + for (const entry of values) { + const [key, value] = entry; + if (this.getValue(key) === value) { + continue; + } + this._configuration.updateValue(key, value); + changedKeys.push(key); + } + + if (changedKeys.length > 0) { + const configurationChangeEvent = new ConfigurationChangeEvent({ keys: changedKeys, overrides: [] }, previous, this._configuration); + configurationChangeEvent.source = ConfigurationTarget.MEMORY; + configurationChangeEvent.sourceConfig = null; + this._onDidChangeConfiguration.fire(configurationChangeEvent); + } + return Promise.resolve(); } + public updateValue(key: string, value: any, arg3?: any, arg4?: any): Promise { + return this.updateValues([[key, value]]); + } + public inspect(key: string, options: IConfigurationOverrides = {}): IConfigurationValue { - return this.configuration().inspect(key, options, undefined); + return this._configuration.inspect(key, options, undefined); } public keys() { - return this.configuration().keys(undefined); + return this._configuration.keys(undefined); } public reloadConfiguration(): Promise { @@ -622,14 +641,18 @@ export function applyConfigurationValues(configurationService: IConfigurationSer if (!(configurationService instanceof SimpleConfigurationService)) { return; } + let toUpdate: [string, any][] = []; Object.keys(source).forEach((key) => { if (isEditorConfigurationKey(key)) { - configurationService.updateValue(`editor.${key}`, source[key]); + toUpdate.push([`editor.${key}`, source[key]]); } if (isDiffEditor && isDiffEditorConfigurationKey(key)) { - configurationService.updateValue(`diffEditor.${key}`, source[key]); + toUpdate.push([`diffEditor.${key}`, source[key]]); } }); + if (toUpdate.length > 0) { + configurationService.updateValues(toUpdate); + } } export class SimpleBulkEditService implements IBulkEditService { From 1311b224cf6aa32ed3bd370fd2547b5cc8c8ff99 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Feb 2020 16:03:34 +0100 Subject: [PATCH 39/48] text files - :lipstick: names --- .../browser/editors/fileEditorTracker.ts | 4 +- .../files/common/editors/fileEditorInput.ts | 8 +- .../browser/telemetry.contribution.ts | 6 +- .../browser/browserTextFileService.ts | 4 +- .../textfile/common/textFileEditorModel.ts | 30 +++--- .../common/textFileEditorModelManager.ts | 52 +++++----- .../services/textfile/common/textfiles.ts | 98 +++++++++++-------- .../test/browser/textFileEditorModel.test.ts | 20 ++-- .../common/textModelResolverService.ts | 4 +- 9 files changed, 122 insertions(+), 104 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index 392394a7a76..7130668cbd3 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -7,7 +7,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; 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, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileEditorModelState } from 'vs/workbench/services/textfile/common/textfiles'; 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'; @@ -285,7 +285,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut } const model = this.textFileService.files.get(resource); - if (model?.hasState(ModelState.PENDING_SAVE)) { + if (model?.hasState(TextFileEditorModelState.PENDING_SAVE)) { return false; // resource must not be pending to save } diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 5b4e50eed05..00873b73ad5 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { EncodingMode, IFileEditorInput, Verbosity, TextResourceEditorInput } from 'vs/workbench/common/editor'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; -import { ITextFileService, ModelState, LoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileEditorModelState, TextFileLoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IReference, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -174,7 +174,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi } private decorateLabel(label: string): string { - const orphaned = this.model?.hasState(ModelState.ORPHAN); + const orphaned = this.model?.hasState(TextFileEditorModelState.ORPHAN); const readonly = this.isReadonly(); if (orphaned && readonly) { @@ -205,7 +205,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi } isSaving(): boolean { - if (this.model?.hasState(ModelState.SAVED) || this.model?.hasState(ModelState.CONFLICT) || this.model?.hasState(ModelState.ERROR)) { + if (this.model?.hasState(TextFileEditorModelState.SAVED) || this.model?.hasState(TextFileEditorModelState.CONFLICT) || this.model?.hasState(TextFileEditorModelState.ERROR)) { return false; // require the model to be dirty and not in conflict or error state } @@ -241,7 +241,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi encoding: this.preferredEncoding, reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model allowBinary: this.forceOpenAs === ForceOpenAs.Text, - reason: LoadReason.EDITOR + reason: TextFileLoadReason.EDITOR }); // This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index c9da631ce79..ee6a1f86d1e 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -19,7 +19,7 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { ITextFileService, ITextFileModelSaveEvent, ITextFileModelLoadEvent } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileSaveEvent, ITextFileLoadEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { extname, basename, isEqual, isEqualOrParent, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; @@ -131,7 +131,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr this._register(lifecycleService.onShutdown(() => this.dispose())); } - private onTextFileModelLoaded(e: ITextFileModelLoadEvent): void { + private onTextFileModelLoaded(e: ITextFileLoadEvent): void { const settingsType = this.getTypeIfSettings(e.model.resource); if (settingsType) { type SettingsReadClassification = { @@ -146,7 +146,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr } } - private onTextFileModelSaved(e: ITextFileModelSaveEvent): void { + private onTextFileModelSaved(e: ITextFileSaveEvent): void { const settingsType = this.getTypeIfSettings(e.model.resource); if (settingsType) { type SettingsWrittenClassification = { diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts index a5a5ed8208f..83d5fba161a 100644 --- a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; -import { ITextFileService, IResourceEncodings, IResourceEncoding, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, IResourceEncodings, IResourceEncoding, TextFileEditorModelState } from 'vs/workbench/services/textfile/common/textfiles'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; @@ -24,7 +24,7 @@ export class BrowserTextFileService extends AbstractTextFileService { } protected onBeforeShutdown(reason: ShutdownReason): boolean { - if (this.files.models.some(model => model.hasState(ModelState.PENDING_SAVE))) { + if (this.files.models.some(model => model.hasState(TextFileEditorModelState.PENDING_SAVE))) { console.warn('Unload prevented: pending file saves'); return true; // files are pending to be saved: veto diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 0def6b30769..6538d59f8c9 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; -import { ITextFileService, ModelState, ITextFileEditorModel, ITextFileStreamContent, ILoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, LoadReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileLoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, TextFileLoadReason } from 'vs/workbench/services/textfile/common/textfiles'; import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; @@ -43,7 +43,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private readonly _onDidChangeContent = this._register(new Emitter()); readonly onDidChangeContent = this._onDidChangeContent.event; - private readonly _onDidLoad = this._register(new Emitter()); + private readonly _onDidLoad = this._register(new Emitter()); readonly onDidLoad = this._onDidLoad.event; private readonly _onDidChangeDirty = this._register(new Emitter()); @@ -248,7 +248,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil //#region Load - async load(options?: ILoadOptions): Promise { + async load(options?: ITextFileLoadOptions): Promise { this.logService.trace('[text file model] load() - enter', this.resource.toString()); // It is very important to not reload the model when the model is dirty. @@ -281,7 +281,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.loadFromFile(options); } - private async loadFromBackup(backup: IResolvedBackup, options?: ILoadOptions): Promise { + private async loadFromBackup(backup: IResolvedBackup, options?: ITextFileLoadOptions): Promise { // Load with backup this.loadFromContent({ @@ -303,7 +303,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this; } - private async loadFromFile(options?: ILoadOptions): Promise { + private async loadFromFile(options?: ITextFileLoadOptions): Promise { const forceReadFromDisk = options?.forceReadFromDisk; const allowBinary = this.isResolved() /* always allow if we resolved previously */ || options?.allowBinary; @@ -358,7 +358,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - private loadFromContent(content: ITextFileStreamContent, options?: ILoadOptions, fromBackup?: boolean): TextFileEditorModel { + private loadFromContent(content: ITextFileStreamContent, options?: ITextFileLoadOptions, fromBackup?: boolean): TextFileEditorModel { this.logService.trace('[text file model] load() - resolved content', this.resource.toString()); // Update our resolved disk stat model @@ -396,7 +396,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Emit as event - this._onDidLoad.fire(options?.reason ?? LoadReason.OTHER); + this._onDidLoad.fire(options?.reason ?? TextFileLoadReason.OTHER); return this; } @@ -544,7 +544,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } if ( - (this.hasState(ModelState.CONFLICT) || this.hasState(ModelState.ERROR)) && + (this.hasState(TextFileEditorModelState.CONFLICT) || this.hasState(TextFileEditorModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE) ) { this.logService.trace('[text file model] save() - ignoring auto save request for model that is in conflict or error', this.resource.toString()); @@ -794,19 +794,19 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil //#endregion - hasState(state: ModelState): boolean { + hasState(state: TextFileEditorModelState): boolean { switch (state) { - case ModelState.CONFLICT: + case TextFileEditorModelState.CONFLICT: return this.inConflictMode; - case ModelState.DIRTY: + case TextFileEditorModelState.DIRTY: return this.dirty; - case ModelState.ERROR: + case TextFileEditorModelState.ERROR: return this.inErrorMode; - case ModelState.ORPHAN: + case TextFileEditorModelState.ORPHAN: return this.inOrphanMode; - case ModelState.PENDING_SAVE: + case TextFileEditorModelState.PENDING_SAVE: return this.saveSequentializer.hasPending(); - case ModelState.SAVED: + case TextFileEditorModelState.SAVED: return !this.dirty; } } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 74240ecf283..6f5824b53ed 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -9,7 +9,7 @@ import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { ITextFileEditorModel, ITextFileEditorModelManager, IModelLoadOrCreateOptions, ITextFileModelLoadEvent, ITextFileModelSaveEvent, ITextFileSaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileEditorModel, ITextFileEditorModelManager, ITextFileEditorModelLoadOrCreateOptions, ITextFileLoadEvent, ITextFileSaveEvent, ITextFileSaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; @@ -29,31 +29,31 @@ import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { - private readonly _onDidCreate = this._register(new Emitter()); + private readonly _onDidCreate = this._register(new Emitter()); readonly onDidCreate = this._onDidCreate.event; - private readonly _onDidLoad = this._register(new Emitter()); + private readonly _onDidLoad = this._register(new Emitter()); readonly onDidLoad = this._onDidLoad.event; - private readonly _onDidChangeDirty = this._register(new Emitter()); + private readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; - private readonly _onDidSaveError = this._register(new Emitter()); + private readonly _onDidSaveError = this._register(new Emitter()); readonly onDidSaveError = this._onDidSaveError.event; - private readonly _onDidSave = this._register(new Emitter()); + private readonly _onDidSave = this._register(new Emitter()); readonly onDidSave = this._onDidSave.event; - private readonly _onDidRevert = this._register(new Emitter()); + private readonly _onDidRevert = this._register(new Emitter()); readonly onDidRevert = this._onDidRevert.event; - private readonly _onDidChangeEncoding = this._register(new Emitter()); + private readonly _onDidChangeEncoding = this._register(new Emitter()); readonly onDidChangeEncoding = this._onDidChangeEncoding.event; - private readonly mapResourceToModel = new ResourceMap(); + private readonly mapResourceToModel = new ResourceMap(); private readonly mapResourceToModelListeners = new ResourceMap(); private readonly mapResourceToDisposeListener = new ResourceMap(); - private readonly mapResourceToPendingModelLoaders = new ResourceMap>(); + private readonly mapResourceToPendingModelLoaders = new ResourceMap>(); private readonly modelLoadQueue = this._register(new ResourceQueue()); @@ -67,7 +67,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE }; })(); - get models(): ITextFileEditorModel[] { + get models(): TextFileEditorModel[] { return this.mapResourceToModel.values(); } @@ -111,7 +111,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE ).forEach(model => this.queueModelLoad(model)); } - private queueModelLoad(model: ITextFileEditorModel): void { + private queueModelLoad(model: TextFileEditorModel): void { // Load model to update (use a queue to prevent accumulation of loads // when the load actually takes long. At most we only want the queue @@ -137,8 +137,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE if (source && (e.operation === FileOperation.COPY || e.operation === FileOperation.MOVE)) { // find all models that related to either source or target (can be many if resource is a folder) - const sourceModels: ITextFileEditorModel[] = []; - const targetModels: ITextFileEditorModel[] = []; + const sourceModels: TextFileEditorModel[] = []; + const targetModels: TextFileEditorModel[] = []; for (const model of this.models) { const resource = model.resource; @@ -238,11 +238,11 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } - get(resource: URI): ITextFileEditorModel | undefined { + get(resource: URI): TextFileEditorModel | undefined { return this.mapResourceToModel.get(resource); } - async resolve(resource: URI, options?: IModelLoadOrCreateOptions): Promise { + async resolve(resource: URI, options?: ITextFileEditorModelLoadOrCreateOptions): Promise { // Return early if model is currently being loaded const pendingLoad = this.mapResourceToPendingModelLoaders.get(resource); @@ -250,7 +250,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return pendingLoad; } - let modelPromise: Promise; + let modelPromise: Promise; let model = this.get(resource); let didCreateModel = false; @@ -281,15 +281,15 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE modelPromise = model.load(options); // Install model listeners - const listeners = new DisposableStore(); - listeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model: newModel, reason }))); - listeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(newModel))); - listeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(newModel))); - listeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: newModel, reason }))); - listeners.add(model.onDidRevert(() => this._onDidRevert.fire(newModel))); - listeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(newModel))); + const modelListeners = new DisposableStore(); + modelListeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model: newModel, reason }))); + modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(newModel))); + modelListeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(newModel))); + modelListeners.add(model.onDidSave(reason => this._onDidSave.fire({ model: newModel, reason }))); + modelListeners.add(model.onDidRevert(() => this._onDidRevert.fire(newModel))); + modelListeners.add(model.onDidChangeEncoding(() => this._onDidChangeEncoding.fire(newModel))); - this.mapResourceToModelListeners.set(resource, listeners); + this.mapResourceToModelListeners.set(resource, modelListeners); } // Store pending loads to avoid race conditions @@ -335,7 +335,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } - add(resource: URI, model: ITextFileEditorModel): void { + add(resource: URI, model: TextFileEditorModel): void { const knownModel = this.mapResourceToModel.get(resource); if (knownModel === model) { return; // already cached diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index c248689f965..25ebaac45cd 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -161,13 +161,18 @@ export const enum TextFileOperationResult { } export class TextFileOperationError extends FileOperationError { - constructor(message: string, public textFileOperationResult: TextFileOperationResult, public options?: IReadTextFileOptions & IWriteTextFileOptions) { - super(message, FileOperationResult.FILE_OTHER_ERROR); - } static isTextFileOperationError(obj: unknown): obj is TextFileOperationError { return obj instanceof Error && !isUndefinedOrNull((obj as TextFileOperationError).textFileOperationResult); } + + constructor( + message: string, + public textFileOperationResult: TextFileOperationResult, + public options?: IReadTextFileOptions & IWriteTextFileOptions + ) { + super(message, FileOperationResult.FILE_OTHER_ERROR); + } } export interface IResourceEncodings { @@ -193,7 +198,7 @@ export interface ISaveErrorHandler { /** * States the text file editor model can be in. */ -export const enum ModelState { +export const enum TextFileEditorModelState { /** * A model is saved. @@ -228,17 +233,7 @@ export const enum ModelState { ERROR } -export interface ITextFileOperationResult { - results: IResult[]; -} - -export interface IResult { - source: URI; - target?: URI; - error?: boolean; -} - -export const enum LoadReason { +export const enum TextFileLoadReason { EDITOR = 1, REFERENCE = 2, OTHER = 3 @@ -268,12 +263,12 @@ export interface ITextFileStreamContent extends IBaseTextFileContent { value: ITextBufferFactory; } -export interface IModelLoadOrCreateOptions { +export interface ITextFileEditorModelLoadOrCreateOptions { /** * Context why the model is being loaded or created. */ - reason?: LoadReason; + reason?: TextFileLoadReason; /** * The language mode to use for the model text content. @@ -303,14 +298,14 @@ export interface IModelLoadOrCreateOptions { allowBinary?: boolean; } -export interface ITextFileModelSaveEvent { +export interface ITextFileSaveEvent { model: ITextFileEditorModel; reason: SaveReason; } -export interface ITextFileModelLoadEvent { +export interface ITextFileLoadEvent { model: ITextFileEditorModel; - reason: LoadReason; + reason: TextFileLoadReason; } export interface ITextFileSaveParticipant { @@ -330,36 +325,69 @@ export interface ITextFileSaveParticipant { export interface ITextFileEditorModelManager { readonly onDidCreate: Event; - readonly onDidLoad: Event; + readonly onDidLoad: Event; readonly onDidChangeDirty: Event; - readonly onDidSaveError: Event; - readonly onDidSave: Event; - readonly onDidRevert: Event; readonly onDidChangeEncoding: Event; + readonly onDidSaveError: Event; + readonly onDidSave: Event; + readonly onDidRevert: Event; readonly models: ITextFileEditorModel[]; saveErrorHandler: ISaveErrorHandler; + /** + * Returns the text file editor model for the provided resource + * or undefined if none. + */ get(resource: URI): ITextFileEditorModel | undefined; - resolve(resource: URI, options?: IModelLoadOrCreateOptions): Promise; + /** + * Allows to load a text file model from disk. + */ + resolve(resource: URI, options?: ITextFileEditorModelLoadOrCreateOptions): Promise; + /** + * Adds a participant for saving text file models. + */ addSaveParticipant(participant: ITextFileSaveParticipant): IDisposable; + runSaveParticipants(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise disposeModel(model: ITextFileEditorModel): void; } export interface ITextFileSaveOptions extends ISaveOptions { + + /** + * Makes the file writable if it is readonly. + */ overwriteReadonly?: boolean; + + /** + * Overwrite the encoding of the file on disk as configured. + */ overwriteEncoding?: boolean; + + /** + * Save the file with elevated privileges. + * + * Note: This may not be supported in all environments. + */ writeElevated?: boolean; + + /** + * Allows to write to a file even if it has been modified on disk. + */ ignoreModifiedSince?: boolean; + + /** + * If set, will bubble up the error to the caller instead of handling it. + */ ignoreErrorHandler?: boolean; } -export interface ILoadOptions { +export interface ITextFileLoadOptions { /** * Go to disk bypassing any cache of the model if any. @@ -374,39 +402,29 @@ export interface ILoadOptions { /** * Context why the model is being loaded. */ - reason?: LoadReason; + reason?: TextFileLoadReason; } export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport, IWorkingCopy { readonly onDidChangeContent: Event; - readonly onDidLoad: Event; readonly onDidSaveError: Event; - readonly onDidSave: Event; - readonly onDidRevert: Event; - readonly onDidChangeEncoding: Event; readonly onDidChangeOrphaned: Event; - hasState(state: ModelState): boolean; + hasState(state: TextFileEditorModelState): boolean; updatePreferredEncoding(encoding: string | undefined): void; - updateTextEditorModel(newValue?: ITextBufferFactory, preferredMode?: string): void; - save(options?: ITextFileSaveOptions): Promise; - - load(options?: ILoadOptions): Promise; - revert(options?: IRevertOptions): Promise; - isDirty(): this is IResolvedTextFileEditorModel; + load(options?: ITextFileLoadOptions): Promise; - setDirty(dirty: boolean): void; + isDirty(): this is IResolvedTextFileEditorModel; getMode(): string | undefined; isResolved(): this is IResolvedTextFileEditorModel; - isDisposed(): boolean; } diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index af2880b2e16..5cbb088fd35 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EncodingMode } from 'vs/workbench/common/editor'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, ModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileEditorModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { createFileInput, TestFileService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; @@ -100,7 +100,7 @@ suite('Files - TextFileEditorModel', () => { model.textEditorModel!.setValue('bar'); assert.ok(getLastModifiedTime(model) <= Date.now()); - assert.ok(model.hasState(ModelState.DIRTY)); + assert.ok(model.hasState(TextFileEditorModelState.DIRTY)); assert.equal(accessor.workingCopyService.dirtyCount, 1); assert.equal(accessor.workingCopyService.isDirty(model.resource), true); @@ -116,11 +116,11 @@ suite('Files - TextFileEditorModel', () => { }); const pendingSave = model.save(); - assert.ok(model.hasState(ModelState.PENDING_SAVE)); + assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE)); await pendingSave; - assert.ok(model.hasState(ModelState.SAVED)); + assert.ok(model.hasState(TextFileEditorModelState.SAVED)); assert.ok(!model.isDirty()); assert.ok(savedEvent); assert.ok(workingCopyEvent); @@ -169,11 +169,11 @@ suite('Files - TextFileEditorModel', () => { accessor.fileService.writeShouldThrowError = new Error('failed to write'); try { const pendingSave = model.save(); - assert.ok(model.hasState(ModelState.PENDING_SAVE)); + assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE)); await pendingSave; - assert.ok(model.hasState(ModelState.ERROR)); + assert.ok(model.hasState(TextFileEditorModelState.ERROR)); assert.ok(model.isDirty()); assert.ok(saveErrorEvent); @@ -199,11 +199,11 @@ suite('Files - TextFileEditorModel', () => { accessor.fileService.writeShouldThrowError = new FileOperationError('save conflict', FileOperationResult.FILE_MODIFIED_SINCE); try { const pendingSave = model.save(); - assert.ok(model.hasState(ModelState.PENDING_SAVE)); + assert.ok(model.hasState(TextFileEditorModelState.PENDING_SAVE)); await pendingSave; - assert.ok(model.hasState(ModelState.CONFLICT)); + assert.ok(model.hasState(TextFileEditorModelState.CONFLICT)); assert.ok(model.isDirty()); assert.ok(saveErrorEvent); @@ -273,7 +273,7 @@ suite('Files - TextFileEditorModel', () => { test('Load does not trigger save', async function () { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined); - assert.ok(model.hasState(ModelState.SAVED)); + assert.ok(model.hasState(TextFileEditorModelState.SAVED)); model.onDidSave(e => assert.fail()); model.onDidChangeDirty(e => assert.fail()); @@ -290,7 +290,7 @@ suite('Files - TextFileEditorModel', () => { await model.load(); model.textEditorModel!.setValue('foo'); assert.ok(model.isDirty()); - assert.ok(model.hasState(ModelState.DIRTY)); + assert.ok(model.hasState(TextFileEditorModelState.DIRTY)); await model.load(); assert.ok(model.isDirty()); diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index cec284d34d7..dd95e155a03 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -10,7 +10,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { IDisposable, toDisposable, IReference, ReferenceCollection, ImmortalReference } from 'vs/base/common/lifecycle'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; -import { ITextFileService, LoadReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileLoadReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as network from 'vs/base/common/network'; import { ITextModelService, ITextModelContentProvider, ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; @@ -38,7 +38,7 @@ class ResourceModelCollection extends ReferenceCollection Date: Wed, 19 Feb 2020 16:16:05 +0100 Subject: [PATCH 40/48] add telemetry --- .../common/userDataSyncEnablementService.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts index ea7cbca4cd6..7adbf97471b 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts @@ -7,6 +7,11 @@ import { IUserDataSyncEnablementService, ResourceKey, ALL_RESOURCE_KEYS } from ' import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +type SyncEnablementClassification = { + enabled?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; const enablementKey = 'sync.enable'; function getEnablementKey(resourceKey: ResourceKey) { return `${enablementKey}.${resourceKey}`; } @@ -22,7 +27,8 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]> = this._onDidChangeResourceEnablement.event; constructor( - @IStorageService private readonly storageService: IStorageService + @IStorageService private readonly storageService: IStorageService, + @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); @@ -34,6 +40,7 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa setEnablement(enabled: boolean): void { if (this.isEnabled() !== enabled) { + this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(enablementKey, { enabled }); this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL); } } @@ -44,7 +51,9 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa setResourceEnablement(resourceKey: ResourceKey, enabled: boolean): void { if (this.isResourceEnabled(resourceKey) !== enabled) { - this.storageService.store(getEnablementKey(resourceKey), enabled, StorageScope.GLOBAL); + const resourceEnablementKey = getEnablementKey(resourceKey); + this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(resourceEnablementKey, { enabled }); + this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL); } } From f357cdc5315c09c5628c2cc95a75c9814b438929 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Feb 2020 16:18:11 +0100 Subject: [PATCH 41/48] #89367 check for version compatibility --- .../userDataSync/common/abstractSynchronizer.ts | 14 +++++++++++--- .../platform/userDataSync/common/userDataSync.ts | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 92811919935..0de2f29696d 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -17,8 +17,9 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ParseError, parse } from 'vs/base/common/json'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { IStringDictionary } from 'vs/base/common/collections'; +import { localize } from 'vs/nls'; -type SyncConflictsClassification = { +type SyncSourceClassification = { source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; @@ -76,11 +77,11 @@ export abstract class AbstractSynchroniser extends Disposable { this._onDidChangStatus.fire(status); if (status === SyncStatus.HasConflicts) { // Log to telemetry when there is a sync conflict - this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsDetected', { source: this.source }); + this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsDetected', { source: this.source }); } if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) { // Log to telemetry when conflicts are resolved - this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsResolved', { source: this.source }); + this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.source }); } } } @@ -106,6 +107,13 @@ export abstract class AbstractSynchroniser extends Disposable { const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = ref && lastSyncUserData && lastSyncUserData.ref === ref ? lastSyncUserData : await this.getRemoteUserData(lastSyncUserData); + + if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { + // current version is not compatible with cloud version + this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: this.source }); + throw new UserDataSyncError(localize('incompatible', "Cannot sync {0} as its version {1} is not compatible with cloud {2}", this.source, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.source); + } + return this.doSync(remoteUserData, lastSyncUserData); } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index e6787a3d605..5e417841855 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -176,6 +176,7 @@ export enum UserDataSyncErrorCode { // Local Errors LocalPreconditionFailed = 'LocalPreconditionFailed', LocalInvalidContent = 'LocalInvalidContent', + Incompatible = 'Incompatible', Unknown = 'Unknown', } From 13d7b3066ab6e8993f12c28b0ced297781055ffc Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Feb 2020 16:37:37 +0100 Subject: [PATCH 42/48] :lipstick: --- src/vs/platform/userDataSync/common/settingsMerge.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/userDataSync/common/settingsMerge.ts b/src/vs/platform/userDataSync/common/settingsMerge.ts index 212b9ccafc4..4942add903f 100644 --- a/src/vs/platform/userDataSync/common/settingsMerge.ts +++ b/src/vs/platform/userDataSync/common/settingsMerge.ts @@ -578,11 +578,11 @@ function parseSettings(content: string): INode[] { const node = nodes.pop(); if (node) { nodes.push({ - startOffset: node!.startOffset, - endOffset: node!.endOffset, - value: node!.value, + startOffset: node.startOffset, + endOffset: node.endOffset, + value: node.value, setting: { - key: node!.setting!.key, + key: node.setting!.key, hasCommaSeparator: true } }); From 05ba01fc873f533f5e738f1370ccbb251c359b33 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 19 Feb 2020 16:55:40 +0100 Subject: [PATCH 43/48] add remoteExtensionTips --- src/vs/platform/product/common/productService.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 120fd666444..23026219040 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -49,6 +49,7 @@ export interface IProductConfiguration { readonly extensionTips?: { [id: string]: string; }; readonly extensionImportantTips?: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; }; readonly exeBasedExtensionTips?: { [id: string]: IExeBasedExtensionTip; }; + readonly remoteExtensionTips?: { [remoteName: string]: IRemoteExtensionTip; }; readonly extensionKeywords?: { [extension: string]: readonly string[]; }; readonly keymapExtensionTips?: readonly string[]; @@ -112,6 +113,11 @@ export interface IExeBasedExtensionTip { exeFriendlyName?: string; } +export interface IRemoteExtensionTip { + friendlyName: string; + extensionId: string; +} + export interface ISurveyData { surveyId: string; surveyUrl: string; From e3d78d5a2c222e691078400fcb8c6025439c45b1 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 19 Feb 2020 16:57:11 +0100 Subject: [PATCH 44/48] cli: new window when --remote is used without paths --- src/vs/code/electron-main/app.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 7f2fced207b..f6966b2772c 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -677,8 +677,8 @@ export class CodeApplication extends Disposable { const noRecentEntry = args['skip-add-to-recently-opened'] === true; const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined; - // new window if "-n" was used without paths - if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { + // new window if "-n" or "--remote" was used without paths + if ((args['new-window'] || args.remote) && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { return windowsMainService.open({ context, cli: args, From e346071dfc017a0ec41603d99ad083c4117dcfbd Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 19 Feb 2020 17:14:31 +0100 Subject: [PATCH 45/48] improve getRemoteName signature --- src/vs/platform/remote/common/remoteHosts.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/remote/common/remoteHosts.ts b/src/vs/platform/remote/common/remoteHosts.ts index 5578246cc82..feb46455ac4 100644 --- a/src/vs/platform/remote/common/remoteHosts.ts +++ b/src/vs/platform/remote/common/remoteHosts.ts @@ -12,6 +12,9 @@ export function getRemoteAuthority(uri: URI): string | undefined { return uri.scheme === REMOTE_HOST_SCHEME ? uri.authority : undefined; } +export function getRemoteName(authority: string): string; +export function getRemoteName(authority: undefined): undefined; +export function getRemoteName(authority: string | undefined): string | undefined; export function getRemoteName(authority: string | undefined): string | undefined { if (!authority) { return undefined; @@ -22,4 +25,4 @@ export function getRemoteName(authority: string | undefined): string | undefined return authority; } return authority.substr(0, pos); -} \ No newline at end of file +} From 50c2f7fb66509b140177fba0cbe948f8b8b929ab Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 19 Feb 2020 17:32:52 +0100 Subject: [PATCH 46/48] emplty explorer: use proper action on mac --- .../workbench/contrib/files/browser/explorerViewlet.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index eb24e038dfc..720b01e66ed 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -35,6 +35,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { WorkbenchStateContext, RemoteNameContext, IsWebContext } from 'vs/workbench/browser/contextkeys'; +import { AddRootFolderAction, OpenFolderAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; +import { isMacintosh } from 'vs/base/common/platform'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -64,17 +66,18 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor const viewsRegistry = Registry.as(Extensions.ViewsRegistry); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.\n[Add Folder](command:workbench.action.addRootFolder)"), + content: localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID), when: WorkbenchStateContext.isEqualTo('workspace') }); + const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID; viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize('remoteNoFolderHelp', "Connected to remote.\n[Open Folder](command:workbench.action.files.openFolder)"), + content: localize('remoteNoFolderHelp', "Connected to remote.\n[Open Folder](command:{0})", commandId), when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated()) }); viewsRegistry.registerViewWelcomeContent(EmptyView.ID, { - content: localize('noFolderHelp', "You have not yet opened a folder.\n[Open Folder](command:workbench.action.files.openFolder)"), + content: localize('noFolderHelp', "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId), when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext)) }); From f063e48322e5b3c96cfc26fe6f20561890bd6014 Mon Sep 17 00:00:00 2001 From: isidor Date: Wed, 19 Feb 2020 17:39:54 +0100 Subject: [PATCH 47/48] Make initial debug / run view extensible #85548 --- .../debug/browser/media/debugViewlet.css | 25 --- .../contrib/debug/browser/startView.ts | 189 ++++-------------- 2 files changed, 39 insertions(+), 175 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 2408b52df02..8a1dd5969f9 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -13,31 +13,6 @@ height: 100%; } -.debug-pane .debug-start-view { - padding: 0 20px 0 20px; -} - -.debug-pane .debug-start-view .monaco-button, -.debug-pane .debug-start-view .section { - margin-top: 20px; -} - -.debug-pane .debug-start-view .top-section { - margin-top: 10px; -} - -.debug-pane .debug-start-view .monaco-button { - max-width: 260px; - margin-left: auto; - margin-right: auto; - display: block; -} - -.debug-pane .debug-start-view .click { - cursor: pointer; - color: #007ACC; -} - .monaco-workbench .debug-action.notification:after { content: ''; width: 6px; diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index f561b8cc42b..89a25705c94 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -3,64 +3,33 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as dom from 'vs/base/browser/dom'; -import { Button } from 'vs/base/browser/ui/button/button'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; -import { ICommandService } from 'vs/platform/commands/common/commands'; 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'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { equals } from 'vs/base/common/arrays'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewsRegistry, Extensions } from 'vs/workbench/common/views'; +import { Registry } from 'vs/platform/registry/common/platform'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -const $ = dom.$; +import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { OpenFolderAction, OpenFileAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; +import { isMacintosh } from 'vs/base/common/platform'; -interface DebugStartMetrics { - debuggers?: string[]; -} -type DebugStartMetricsClassification = { - debuggers?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; -}; - -function createClickElement(textContent: string, action: () => any): HTMLSpanElement { - const clickElement = $('span.click'); - clickElement.textContent = textContent; - clickElement.onclick = action; - clickElement.tabIndex = 0; - clickElement.onkeyup = (e) => { - const keyboardEvent = new StandardKeyboardEvent(e); - if (keyboardEvent.keyCode === KeyCode.Enter || (keyboardEvent.keyCode === KeyCode.Space)) { - action(); - } - }; - - return clickElement; -} +const CONTEXT_DEBUGGER_INTERESTED = new RawContextKey('debuggerInterested', false); export class StartView extends ViewPane { static ID = 'workbench.debug.startView'; static LABEL = localize('start', "Start"); - private debugButton!: Button; - private firstMessageContainer!: HTMLElement; - private secondMessageContainer!: HTMLElement; - private clickElement: HTMLElement | undefined; - private debuggerLabels: string[] | undefined = undefined; + private debuggerInterestedContext: IContextKey; constructor( options: IViewletViewOptions, @@ -69,125 +38,45 @@ export class StartView extends ViewPane { @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, - @ICommandService private readonly commandService: ICommandService, @IDebugService private readonly debugService: IDebugService, @IEditorService private readonly editorService: IEditorService, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IFileDialogService private readonly dialogService: IFileDialogService, @IInstantiationService instantiationService: IInstantiationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @ITelemetryService private readonly telemetryService: ITelemetryService, @IOpenerService openerService: IOpenerService, ) { super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); - this._register(editorService.onDidActiveEditorChange(() => this.updateView())); - this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(() => this.updateView())); + + this.debuggerInterestedContext = CONTEXT_DEBUGGER_INTERESTED.bindTo(contextKeyService); + const setContextKey = () => { + const activeEditor = this.editorService.activeTextEditorWidget; + const debuggerLabels = this.debugService.getConfigurationManager().getDebuggerLabelsForEditor(activeEditor); + this.debuggerInterestedContext.set(debuggerLabels.length > 0); + }; + this._register(editorService.onDidActiveEditorChange(setContextKey)); + this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(setContextKey)); } - private updateView(): void { - const activeEditor = this.editorService.activeTextEditorWidget; - const debuggerLabels = this.debugService.getConfigurationManager().getDebuggerLabelsForEditor(activeEditor); - if (!equals(this.debuggerLabels, debuggerLabels)) { - this.debuggerLabels = debuggerLabels; - const enabled = this.debuggerLabels.length > 0; - - this.debugButton.enabled = enabled; - const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID); - 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; - - const emptyWorkbench = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY; - this.firstMessageContainer.innerHTML = ''; - this.secondMessageContainer.innerHTML = ''; - const secondMessageElement = $('span'); - this.secondMessageContainer.appendChild(secondMessageElement); - - const setSecondMessage = () => { - secondMessageElement.textContent = localize('specifyHowToRun', "To customize Run and Debug"); - this.clickElement = createClickElement(localize('configure', " create a launch.json file."), () => { - this.telemetryService.publicLog2('debugStart.configure', { debuggers: this.debuggerLabels }); - this.commandService.executeCommand(ConfigureAction.ID); - }); - this.secondMessageContainer.appendChild(this.clickElement); - }; - const setSecondMessageWithFolder = () => { - secondMessageElement.textContent = localize('noLaunchConfiguration', "To customize Run and Debug, "); - this.clickElement = createClickElement(localize('openFolder', " open a folder"), () => { - this.telemetryService.publicLog2('debugStart.openFolder', { debuggers: this.debuggerLabels }); - this.dialogService.pickFolderAndOpen({ forceNewWindow: false }); - }); - this.secondMessageContainer.appendChild(this.clickElement); - - const moreText = $('span.moreText'); - moreText.textContent = localize('andconfigure', " and create a launch.json file."); - this.secondMessageContainer.appendChild(moreText); - }; - - if (enabled && !emptyWorkbench) { - setSecondMessage(); - } - - if (enabled && emptyWorkbench) { - setSecondMessageWithFolder(); - } - - if (!enabled && !emptyWorkbench) { - const firstMessageElement = $('span'); - this.firstMessageContainer.appendChild(firstMessageElement); - firstMessageElement.textContent = localize('simplyDebugAndRun', "Open a file which can be debugged or run."); - - setSecondMessage(); - } - - if (!enabled && emptyWorkbench) { - this.clickElement = createClickElement(localize('openFile', "Open a file"), () => { - this.telemetryService.publicLog2('debugStart.openFile'); - this.dialogService.pickFileAndOpen({ forceNewWindow: false }); - }); - this.firstMessageContainer.appendChild(this.clickElement); - const firstMessageElement = $('span'); - this.firstMessageContainer.appendChild(firstMessageElement); - firstMessageElement.textContent = localize('canBeDebuggedOrRun', " which can be debugged or run."); - - setSecondMessageWithFolder(); - } - } - } - - protected renderBody(container: HTMLElement): void { - super.renderBody(container); - - this.firstMessageContainer = $('.top-section'); - container.appendChild(this.firstMessageContainer); - - this.debugButton = new Button(container); - this._register(this.debugButton.onDidClick(() => { - this.commandService.executeCommand(StartAction.ID); - this.telemetryService.publicLog2('debugStart.runAndDebug', { debuggers: this.debuggerLabels }); - })); - attachButtonStyler(this.debugButton, this.themeService); - - dom.addClass(this.element, 'debug-pane'); - dom.addClass(container, 'debug-start-view'); - - this.secondMessageContainer = $('.section'); - container.appendChild(this.secondMessageContainer); - - this.updateView(); - } - - protected layoutBody(_: number, __: number): void { - // no-op - } - - focus(): void { - if (this.debugButton.enabled) { - this.debugButton.focus(); - } else if (this.clickElement) { - this.clickElement.focus(); - } + shouldShowWelcome(): boolean { + return true; } } + +const viewsRegistry = Registry.as(Extensions.ViewsRegistry); +viewsRegistry.registerViewWelcomeContent(StartView.ID, { + content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID), + when: CONTEXT_DEBUGGER_INTERESTED.toNegated() +}); + +viewsRegistry.registerViewWelcomeContent(StartView.ID, { + content: localize('runAndDebugAction', "[Run and Debug](command:{0})", StartAction.ID) +}); + +viewsRegistry.registerViewWelcomeContent(StartView.ID, { + content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID), + when: WorkbenchStateContext.notEqualsTo('empty') +}); + +viewsRegistry.registerViewWelcomeContent(StartView.ID, { + content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID), + when: WorkbenchStateContext.isEqualTo('empty') +}); From e683dce828edccc6053bebab48a1954fb61f8e29 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 19 Feb 2020 17:40:54 +0100 Subject: [PATCH 48/48] progress - remove fallback to statusbar for now when notification closed --- .../progress/browser/progressService.ts | 30 ++----------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 4ffbfbbd4c8..296e4f47bf5 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -191,22 +191,6 @@ export class ProgressService extends Disposable implements IProgressService { } }; - const createWindowProgress = () => { - this.withWindowProgress({ - location: ProgressLocation.Window, - title: options.title - }, progress => { - if (progressStateModel.step) { - progress.report(progressStateModel.step); - } - - const disposable = progressStateModel.onDidReport(step => progress.report(step)); - Event.once(progressStateModel.onDispose)(() => disposable.dispose()); - - return progressStateModel.promise; - }); - }; - const createNotification = (message: string, increment?: number): INotificationHandle => { const notificationDisposables = new DisposableStore(); @@ -254,18 +238,8 @@ export class ProgressService extends Disposable implements IProgressService { updateProgress(handle, increment); - Event.once(handle.onDidClose)(() => { - - // Switch to window based progress once the notification - // is being closed even though still running and not - // cancelled. - if (!progressStateModel.done) { - createWindowProgress(); - } - - // Clear disposables - notificationDisposables.dispose(); - }); + // Clear upon dispose + Event.once(handle.onDidClose)(() => notificationDisposables.dispose()); return handle; };