diff --git a/extensions/debug-server-ready/src/extension.ts b/extensions/debug-server-ready/src/extension.ts index 57863fd995c..d6e122f9624 100644 --- a/extensions/debug-server-ready/src/extension.ts +++ b/extensions/debug-server-ready/src/extension.ts @@ -147,7 +147,7 @@ class ServerReadyDetector extends vscode.Disposable { webRoot: args.webRoot || WEB_ROOT }, session); } else { - const errMsg = localize('server.ready.chrome.not.installed', "The action 'debugWithChrome' requires the '{0}'", 'Debugger for Chrome'); + const errMsg = localize('server.ready.chrome.not.installed', "The action '{0}' requires the '{1}' extension.", 'debugWithChrome', 'Debugger for Chrome'); vscode.window.showErrorMessage(errMsg, { modal: true }).then(_ => undefined); } break; diff --git a/extensions/markdown-language-features/src/features/documentLinkProvider.ts b/extensions/markdown-language-features/src/features/documentLinkProvider.ts index 39190f78a53..548205566af 100644 --- a/extensions/markdown-language-features/src/features/documentLinkProvider.ts +++ b/extensions/markdown-language-features/src/features/documentLinkProvider.ts @@ -5,17 +5,20 @@ import * as path from 'path'; import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; import { OpenDocumentLinkCommand } from '../commands/openDocumentLink'; import { getUriForLinkWithKnownExternalScheme } from '../util/links'; -function normalizeLink( +const localize = nls.loadMessageBundle(); + +function parseLink( document: vscode.TextDocument, link: string, base: string -): vscode.Uri { +): { uri: vscode.Uri, tooltip?: string } { const externalSchemeUri = getUriForLinkWithKnownExternalScheme(link); if (externalSchemeUri) { - return externalSchemeUri; + return { uri: externalSchemeUri }; } // Assume it must be an relative or absolute file path @@ -34,7 +37,10 @@ function normalizeLink( resourcePath = base ? path.join(base, tempUri.path) : tempUri.path; } - return OpenDocumentLinkCommand.createCommandUri(resourcePath, tempUri.fragment); + return { + uri: OpenDocumentLinkCommand.createCommandUri(resourcePath, tempUri.fragment), + tooltip: localize('documentLink.tooltip', 'follow link') + }; } function matchAll( @@ -61,9 +67,12 @@ function extractDocumentLink( const linkStart = document.positionAt(offset); const linkEnd = document.positionAt(offset + link.length); try { - return new vscode.DocumentLink( + const { uri, tooltip } = parseLink(document, link, base); + const documentLink = new vscode.DocumentLink( new vscode.Range(linkStart, linkEnd), - normalizeLink(document, link, base)); + uri); + documentLink.tooltip = tooltip; + return documentLink; } catch (e) { return undefined; } @@ -144,11 +153,10 @@ export default class LinkProvider implements vscode.DocumentLinkProvider { } } - for (const definition of Array.from(definitions.values())) { + for (const definition of definitions.values()) { try { - results.push(new vscode.DocumentLink( - definition.linkRange, - normalizeLink(document, definition.link, base))); + const { uri } = parseLink(document, definition.link, base); + results.push(new vscode.DocumentLink(definition.linkRange, uri)); } catch (e) { // noop } diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index 074f70c5738..fdfabd4365f 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -74,7 +74,9 @@ class MyCompletionItem extends vscode.CompletionItem { } this.insertText = tsEntry.insertText; - this.filterText = tsEntry.insertText; + // Set filterText for intelliCode and bracket accessors , but not for `this.` completions since it results in + // them being overly prioritized. #74164 + this.filterText = tsEntry.insertText && !/^this\./.test(tsEntry.insertText) ? tsEntry.insertText : undefined; if (completionContext.isMemberCompletion && completionContext.dotAccessorContext) { this.filterText = completionContext.dotAccessorContext.text + (this.insertText || this.label); diff --git a/extensions/typescript-language-features/src/test/completions.test.ts b/extensions/typescript-language-features/src/test/completions.test.ts index 563930d78d5..4d1d79dccdb 100644 --- a/extensions/typescript-language-features/src/test/completions.test.ts +++ b/extensions/typescript-language-features/src/test/completions.test.ts @@ -131,6 +131,9 @@ suite('TypeScript Completions', () => { 'const x = { "hello world2": 1 };', `x["hello world2"]${insert}` )); + + disposeAll(_disposables); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); } }); @@ -160,7 +163,7 @@ suite('TypeScript Completions', () => { `f('abc.abc$0')` ); - const document = await acceptFirstSuggestion(testDocumentUri, _disposables); + const document = await acceptFirstSuggestion(testDocumentUri, _disposables, { useLineRange: true }); assert.strictEqual( document.getText(), joinLines( @@ -264,15 +267,38 @@ suite('TypeScript Completions', () => { `abcdef(1, 2, 3)` )); }); + + test('should not de-prioritized this.member suggestion, #74164', async () => { + await createTestEditor(testDocumentUri, + `class A {`, + ` private detail = '';`, + ` foo() {`, + ` det$0`, + ` }`, + `}`, + ); + + const document = await acceptFirstSuggestion(testDocumentUri, _disposables); + assert.strictEqual( + document.getText(), + joinLines( + `class A {`, + ` private detail = '';`, + ` foo() {`, + ` this.detail`, + ` }`, + `}`, + )); + }); }); const joinLines = (...args: string[]) => args.join('\n'); const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); -async function acceptFirstSuggestion(uri: vscode.Uri, _disposables: vscode.Disposable[]) { +async function acceptFirstSuggestion(uri: vscode.Uri, _disposables: vscode.Disposable[], options?: { useLineRange?: boolean }) { const didChangeDocument = onChangedDocument(uri, _disposables); - const didSuggest = onDidSuggest(_disposables); + const didSuggest = onDidSuggest(_disposables, options); await vscode.commands.executeCommand('editor.action.triggerSuggest'); await didSuggest; // TODO: depends on reverting fix for https://github.com/Microsoft/vscode/issues/64257 @@ -310,12 +336,14 @@ async function createTestEditor(uri: vscode.Uri, ...lines: string[]) { await activeEditor.insertSnippet(new vscode.SnippetString(joinLines(...lines)), new vscode.Range(0, 0, 1000, 0)); } -function onDidSuggest(disposables: vscode.Disposable[]) { +function onDidSuggest(disposables: vscode.Disposable[], options?: { useLineRange?: boolean }) { return new Promise(resolve => disposables.push(vscode.languages.registerCompletionItemProvider('typescript', new class implements vscode.CompletionItemProvider { provideCompletionItems(doc: vscode.TextDocument, position: vscode.Position): vscode.ProviderResult { // Return a fake item that will come first - const range = new vscode.Range(new vscode.Position(position.line, 0), position); + const range = options && options.useLineRange + ? new vscode.Range(new vscode.Position(position.line, 0), position) + : doc.getWordRangeAtPosition(position); return [{ label: '🦄', insertText: doc.getText(range), diff --git a/package.json b/package.json index 4dce12fd3a2..0155c31d5e2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.35.0", - "distro": "d09aaf91e70f2ffd489eee73fa50e86187b6ebcc", + "distro": "2f821d658ad5413bcca540e3c6a3ccb988e6f89b", "author": { "name": "Microsoft Corporation" }, @@ -154,4 +154,4 @@ "windows-mutex": "0.2.1", "windows-process-tree": "0.2.3" } -} +} \ No newline at end of file diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index 5d94e04008d..b937a7fc9e5 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -17,7 +17,7 @@ import { cloneAndChange } from 'vs/base/common/objects'; export interface IContentActionHandler { callback: (content: string, event?: IMouseEvent) => void; - disposeables: IDisposable[]; + readonly disposeables: IDisposable[]; } export interface RenderOptions { diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 28658f3ee60..e00a5072157 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -9,7 +9,7 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle import { commonPrefixLength } from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; -import { dispose, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import 'vs/css!./breadcrumbsWidget'; @@ -134,14 +134,14 @@ export class BreadcrumbsWidget { } private _updateDimensions(dim: dom.Dimension): IDisposable { - let disposables: IDisposable[] = []; + const disposables = new DisposableStore(); disposables.push(dom.modify(() => { this._dimension = dim; this._domNode.style.width = `${dim.width}px`; this._domNode.style.height = `${dim.height}px`; disposables.push(this._updateScrollbar()); })); - return combinedDisposable(disposables); + return disposables; } private _updateScrollbar(): IDisposable { diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index a4419eda426..7058fa3f89c 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -12,7 +12,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import * as objects from 'vs/base/common/objects'; import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; export interface ICheckboxOpts extends ICheckboxStyles { readonly actionClassName?: string; @@ -31,19 +31,20 @@ const defaultOpts = { export class CheckboxActionViewItem extends BaseActionViewItem { private checkbox: Checkbox; - private disposables: IDisposable[] = []; + private disposables = new DisposableStore(); render(container: HTMLElement): void { this.element = container; - this.disposables = dispose(this.disposables); + this.disposables.dispose(); + this.disposables = new DisposableStore(); this.checkbox = new Checkbox({ actionClassName: this._action.class, isChecked: this._action.checked, title: this._action.label }); this.disposables.push(this.checkbox); - this.checkbox.onChange(() => this._action.checked = this.checkbox.checked, this, this.disposables); + this.disposables.push(this.checkbox.onChange(() => this._action.checked = this.checkbox.checked, this)); this.element.appendChild(this.checkbox.domNode); } @@ -64,10 +65,9 @@ export class CheckboxActionViewItem extends BaseActionViewItem { } dipsose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } - } export class Checkbox extends Widget { diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index af21d4d8cf6..8e27c026220 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -5,7 +5,7 @@ import 'vs/css!./contextview'; import * as DOM from 'vs/base/browser/dom'; -import { IDisposable, dispose, toDisposable, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Range } from 'vs/base/common/range'; export interface IAnchor { @@ -128,7 +128,7 @@ export class ContextView extends Disposable { this.container = container; this.container.appendChild(this.view); - const toDisposeOnSetContainer: IDisposable[] = []; + const toDisposeOnSetContainer = new DisposableStore(); ContextView.BUBBLE_UP_EVENTS.forEach(event => { toDisposeOnSetContainer.push(DOM.addStandardDisposableListener(this.container!, event, (e: Event) => { @@ -142,7 +142,7 @@ export class ContextView extends Disposable { }, true)); }); - this.toDisposeOnSetContainer = combinedDisposable(toDisposeOnSetContainer); + this.toDisposeOnSetContainer = toDisposeOnSetContainer; } } diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 44e3deacd6c..4178efc9e98 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -5,7 +5,7 @@ import 'vs/css!./gridview'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { tail2 as tail, equals } from 'vs/base/common/arrays'; import { orthogonal, IView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles } from './gridview'; import { Event, Emitter } from 'vs/base/common/event'; @@ -191,12 +191,10 @@ export interface IGridOptions { proportionalLayout?: boolean; } -export class Grid implements IDisposable { +export class Grid extends Disposable { protected gridview: GridView; private views = new Map(); - private disposables: IDisposable[] = []; - get orientation(): Orientation { return this.gridview.orientation; } set orientation(orientation: Orientation) { this.gridview.orientation = orientation; } @@ -214,10 +212,11 @@ export class Grid implements IDisposable { sashResetSizing: Sizing = Sizing.Distribute; constructor(view: T, options: IGridOptions = {}) { + super(); this.gridview = new GridView(options); - this.disposables.push(this.gridview); + this._register(this.gridview); - this.gridview.onDidSashReset(this.doResetViewSize, this, this.disposables); + this._register(this.gridview.onDidSashReset(this.doResetViewSize, this)); this._addView(view, 0, [0]); } @@ -375,10 +374,6 @@ export class Grid implements IDisposable { this.gridview.distributeViewSizes(parentLocation); } } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } export interface ISerializableView extends IView { diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 631b81f7075..985c171f999 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -15,7 +15,7 @@ import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; import { asArray } from 'vs/base/common/arrays'; @@ -896,7 +896,7 @@ interface IModifierKeyStatus { class ModifierKeyEmitter extends Emitter { - private _subscriptions: IDisposable[] = []; + private readonly _subscriptions = new DisposableStore(); private _keyStatus: IModifierKeyStatus; private static instance: ModifierKeyEmitter; @@ -992,6 +992,6 @@ class ModifierKeyEmitter extends Emitter { dispose() { super.dispose(); - this._subscriptions = dispose(this._subscriptions); + this._subscriptions.dispose(); } } \ No newline at end of file diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts index 98dee79c329..d8101444c3c 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import * as dom from 'vs/base/browser/dom'; @@ -11,18 +11,17 @@ import * as arrays from 'vs/base/common/arrays'; import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox'; import { isMacintosh } from 'vs/base/common/platform'; -export class SelectBoxNative implements ISelectBoxDelegate { +export class SelectBoxNative extends Disposable implements ISelectBoxDelegate { private selectElement: HTMLSelectElement; private selectBoxOptions: ISelectBoxOptions; private options: ISelectOptionItem[]; private selected: number; private readonly _onDidSelect: Emitter; - private toDispose: IDisposable[]; private styles: ISelectBoxStyles; constructor(options: ISelectOptionItem[], selected: number, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) { - this.toDispose = []; + super(); this.selectBoxOptions = selectBoxOptions || Object.create(null); this.options = []; @@ -36,7 +35,7 @@ export class SelectBoxNative implements ISelectBoxDelegate { } this._onDidSelect = new Emitter(); - this.toDispose.push(this._onDidSelect); + this._register(this._onDidSelect); this.styles = styles; @@ -46,7 +45,7 @@ export class SelectBoxNative implements ISelectBoxDelegate { private registerListeners() { - this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => { + this._register(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => { this.selectElement.title = e.target.value; this._onDidSelect.fire({ index: e.target.selectedIndex, @@ -54,7 +53,7 @@ export class SelectBoxNative implements ISelectBoxDelegate { }); })); - this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => { + this._register(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => { let showSelect = false; if (isMacintosh) { @@ -168,8 +167,4 @@ export class SelectBoxNative implements ISelectBoxDelegate { return option; } - - public dispose(): void { - this.toDispose = dispose(this.toDispose); - } } diff --git a/src/vs/base/browser/ui/splitview/panelview.ts b/src/vs/base/browser/ui/splitview/panelview.ts index 735c201e8db..d9e41a7ee46 100644 --- a/src/vs/base/browser/ui/splitview/panelview.ts +++ b/src/vs/base/browser/ui/splitview/panelview.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./panelview'; -import { IDisposable, dispose, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -397,16 +397,16 @@ export class PanelView extends Disposable { } addPanel(panel: Panel, size: number, index = this.splitview.length): void { - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); // https://github.com/Microsoft/vscode/issues/59950 let shouldAnimate = false; disposables.push(scheduleAtNextAnimationFrame(() => shouldAnimate = true)); - Event.filter(panel.onDidChange, () => shouldAnimate) - (this.setupAnimation, this, disposables); + disposables.push(Event.filter(panel.onDidChange, () => shouldAnimate) + (this.setupAnimation, this)); - const panelItem = { panel, disposable: combinedDisposable(disposables) }; + const panelItem = { panel, disposable: disposables }; this.panelItems.splice(index, 0, panelItem); panel.width = this.width; this.splitview.addView(panel, size, index); @@ -414,7 +414,7 @@ export class PanelView extends Disposable { if (this.dnd) { const draggable = new PanelDraggable(panel, this.dnd, this.dndContext); disposables.push(draggable); - draggable.onDidDrop(this._onDidDrop.fire, this._onDidDrop, disposables); + disposables.push(draggable.onDidDrop(this._onDidDrop.fire, this._onDidDrop)); } } diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 48b0cef6866..fc104cc5543 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./splitview'; -import { IDisposable, combinedDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as types from 'vs/base/common/types'; import * as dom from 'vs/base/browser/dom'; @@ -199,7 +199,7 @@ export class SplitView extends Disposable { const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size)); const containerDisposable = toDisposable(() => this.viewContainer.removeChild(container)); - const disposable = combinedDisposable([onChangeDisposable, containerDisposable]); + const disposable = combinedDisposable(onChangeDisposable, containerDisposable); const layoutContainer = this.orientation === Orientation.VERTICAL ? () => item.container.style.height = `${item.size}px` @@ -245,7 +245,7 @@ export class SplitView extends Disposable { const onEndDisposable = onEnd(this.onSashEnd, this); const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash))); - const disposable = combinedDisposable([onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash]); + const disposable = combinedDisposable(onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash); const sashItem: ISashItem = { sash, disposable }; this.sashItems.splice(index - 1, 0, sashItem); @@ -369,10 +369,10 @@ export class SplitView extends Disposable { const index = firstIndex(this.sashItems, item => item.sash === sash); // This way, we can press Alt while we resize a sash, macOS style! - const disposable = combinedDisposable([ + const disposable = combinedDisposable( domEvent(document.body, 'keydown')(e => resetSashDragState(this.sashDragState.current, e.altKey)), domEvent(document.body, 'keyup')(() => resetSashDragState(this.sashDragState.current, false)) - ]); + ); const resetSashDragState = (start: number, alt: boolean) => { const sizes = this.viewItems.map(i => i.size); diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 19267ce8ff5..b0fea386d30 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; export interface ITelemetryData { @@ -217,8 +217,8 @@ export class RadioGroup extends Disposable { constructor(readonly actions: Action[]) { super(); - this._register(combinedDisposable(actions.map(action => { - return action.onDidChange(e => { + for (const action of actions) { + this._register(action.onDidChange(e => { if (e.checked && action.checked) { for (const candidate of actions) { if (candidate !== action) { @@ -226,7 +226,7 @@ export class RadioGroup extends Disposable { } } } - }); - }))); + })); + } } } diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 0ef9379e6a5..77883c9affb 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -5,7 +5,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { once as onceFn } from 'vs/base/common/functional'; -import { combinedDisposable, Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; /** @@ -86,7 +86,7 @@ export namespace Event { * whenever any of the provided events emit. */ export function any(...events: Event[]): Event { - return (listener, thisArgs = null, disposables?) => combinedDisposable(events.map(event => event(e => listener.call(thisArgs, e), null, disposables))); + return (listener, thisArgs = null, disposables?) => combinedDisposable(...events.map(event => event(e => listener.call(thisArgs, e), null, disposables))); } /** diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 512d2d1225f..72a74f2d5b5 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -34,7 +34,7 @@ export function dispose(first: T | T[], ...rest: T[]): T } } -export function combinedDisposable(disposables: IDisposable[]): IDisposable { +export function combinedDisposable(...disposables: IDisposable[]): IDisposable { return { dispose: () => dispose(disposables) }; } @@ -42,22 +42,17 @@ export function toDisposable(fn: () => void): IDisposable { return { dispose() { fn(); } }; } -export abstract class Disposable implements IDisposable { - - static None = Object.freeze({ dispose() { } }); - - protected _toDispose: IDisposable[] = []; - protected get toDispose(): IDisposable[] { return this._toDispose; } - - private _lifecycle_disposable_isDisposed = false; +export class DisposableStore implements IDisposable { + private _toDispose: IDisposable[] = []; + private _isDisposed = false; public dispose(): void { - this._lifecycle_disposable_isDisposed = true; + this._isDisposed = true; this._toDispose = dispose(this._toDispose); } - protected _register(t: T): T { - if (this._lifecycle_disposable_isDisposed) { + public push(t: T): T { + if (this._isDisposed) { console.warn('Registering disposable on object that has already been disposed.'); t.dispose(); } else { @@ -68,6 +63,21 @@ export abstract class Disposable implements IDisposable { } } +export abstract class Disposable implements IDisposable { + + static None = Object.freeze({ dispose() { } }); + + private readonly _store = new DisposableStore(); + + public dispose(): void { + this._store.dispose(); + } + + protected _register(t: T): T { + return this._store.push(t); + } +} + export interface IReference extends IDisposable { readonly object: T; } diff --git a/src/vs/base/common/uriIpc.ts b/src/vs/base/common/uriIpc.ts index 4cee99c11f7..ef2291d49b1 100644 --- a/src/vs/base/common/uriIpc.ts +++ b/src/vs/base/common/uriIpc.ts @@ -10,6 +10,51 @@ export interface IURITransformer { transformIncoming(uri: UriComponents): UriComponents; transformOutgoing(uri: UriComponents): UriComponents; transformOutgoingURI(uri: URI): URI; + transformOutgoingScheme(scheme: string): string; +} + +export interface UriParts { + scheme: string; + authority?: string; + path?: string; +} + +export interface IRawURITransformer { + transformIncoming(uri: UriParts): UriParts; + transformOutgoing(uri: UriParts): UriParts; + transformOutgoingScheme(scheme: string): string; +} + +function toJSON(uri: URI): UriComponents { + return uri.toJSON(); +} + +export class URITransformer implements IURITransformer { + + private readonly _uriTransformer: IRawURITransformer; + + constructor(uriTransformer: IRawURITransformer) { + this._uriTransformer = uriTransformer; + } + + public transformIncoming(uri: UriComponents): UriComponents { + const result = this._uriTransformer.transformIncoming(uri); + return (result === uri ? uri : toJSON(URI.from(result))); + } + + public transformOutgoing(uri: UriComponents): UriComponents { + const result = this._uriTransformer.transformOutgoing(uri); + return (result === uri ? uri : toJSON(URI.from(result))); + } + + public transformOutgoingURI(uri: URI): URI { + const result = this._uriTransformer.transformOutgoing(uri); + return (result === uri ? uri : URI.from(result)); + } + + public transformOutgoingScheme(scheme: string): string { + return this._uriTransformer.transformOutgoingScheme(scheme); + } } export const DefaultURITransformer: IURITransformer = new class { @@ -24,6 +69,10 @@ export const DefaultURITransformer: IURITransformer = new class { transformOutgoingURI(uri: URI): URI { return uri; } + + transformOutgoingScheme(scheme: string): string { + return scheme; + } }; function _transformOutgoingURIs(obj: any, transformer: IURITransformer, depth: number): any { diff --git a/src/vs/base/node/config.ts b/src/vs/base/node/config.ts index bf942fb5a45..ecfb0965701 100644 --- a/src/vs/base/node/config.ts +++ b/src/vs/base/node/config.ts @@ -6,7 +6,7 @@ import * as fs from 'fs'; import { dirname } from 'vs/base/common/path'; import * as objects from 'vs/base/common/objects'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import { statLink } from 'vs/base/node/pfs'; @@ -47,12 +47,10 @@ export class ConfigWatcher implements IConfigWatcher, IDisposable { private disposed: boolean; private loaded: boolean; private timeoutHandle: NodeJS.Timer | null; - private disposables: IDisposable[]; + private readonly disposables = new DisposableStore(); private readonly _onDidUpdateConfiguration: Emitter>; constructor(private _path: string, private options: IConfigOptions = { defaultConfig: Object.create(null), onError: error => console.error(error) }) { - this.disposables = []; - this._onDidUpdateConfiguration = new Emitter>(); this.disposables.push(this._onDidUpdateConfiguration); @@ -187,6 +185,6 @@ export class ConfigWatcher implements IConfigWatcher, IDisposable { dispose(): void { this.disposed = true; - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } \ No newline at end of file diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 6a2ff55e2d1..135f3016b37 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -464,7 +464,7 @@ export class ChannelClient implements IChannelClient, IDisposable { }; const cancellationTokenListener = cancellationToken.onCancellationRequested(cancel); - disposable = combinedDisposable([toDisposable(cancel), cancellationTokenListener]); + disposable = combinedDisposable(toDisposable(cancel), cancellationTokenListener); this.activeRequests.add(disposable); }); diff --git a/src/vs/base/parts/tree/browser/treeModel.ts b/src/vs/base/parts/tree/browser/treeModel.ts index 1ddbe4cb462..54ec61756da 100644 --- a/src/vs/base/parts/tree/browser/treeModel.ts +++ b/src/vs/base/parts/tree/browser/treeModel.ts @@ -159,7 +159,7 @@ export class ItemRegistry { public register(item: Item): void { Assert.ok(!this.isRegistered(item.id), 'item already registered: ' + item.id); - const disposable = combinedDisposable([ + const disposable = combinedDisposable( this._onDidRevealItem.add(item.onDidReveal), this._onExpandItem.add(item.onExpand), this._onDidExpandItem.add(item.onDidExpand), @@ -171,7 +171,7 @@ export class ItemRegistry { this._onRefreshItemChildren.add(item.onRefreshChildren), this._onDidRefreshItemChildren.add(item.onDidRefreshChildren), this._onDidDisposeItem.add(item.onDidDispose) - ]); + ); this.items[item.id] = { item, disposable }; } diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts index e7f1eec7742..0f791bb8c6e 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts @@ -8,7 +8,7 @@ import * as pfs from 'vs/base/node/pfs'; import { IStringDictionary } from 'vs/base/common/collections'; import product from 'vs/platform/product/node/product'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -30,14 +30,13 @@ interface LanguagePackFile { [locale: string]: LanguagePackEntry; } -export class LanguagePackCachedDataCleaner { - - private _disposables: IDisposable[] = []; +export class LanguagePackCachedDataCleaner extends Disposable { constructor( @IEnvironmentService private readonly _environmentService: IEnvironmentService, @ILogService private readonly _logService: ILogService ) { + super(); // We have no Language pack support for dev version (run from source) // So only cleanup when we have a build version. if (this._environmentService.isBuilt) { @@ -45,10 +44,6 @@ export class LanguagePackCachedDataCleaner { } } - dispose(): void { - this._disposables = dispose(this._disposables); - } - private _manageCachedDataSoon(): void { let handle: any = setTimeout(async () => { handle = undefined; @@ -101,12 +96,10 @@ export class LanguagePackCachedDataCleaner { } }, 40 * 1000); - this._disposables.push({ - dispose() { - if (handle !== undefined) { - clearTimeout(handle); - } + this._register(toDisposable(() => { + if (handle !== undefined) { + clearTimeout(handle); } - }); + })); } } \ No newline at end of file diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 4822a564951..e0125000316 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -165,12 +165,12 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I // update localizations cache (localizationsService as LocalizationsService).update(); // cache clean ups - disposables.push(combinedDisposable([ + disposables.push(combinedDisposable( instantiationService2.createInstance(NodeCachedDataCleaner), instantiationService2.createInstance(LanguagePackCachedDataCleaner), instantiationService2.createInstance(StorageDataCleaner), instantiationService2.createInstance(LogsDataCleaner) - ])); + )); disposables.push(extensionManagementService as ExtensionManagementService); }); }); diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index 536ba2549b3..7070141cca2 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -3,7 +3,7 @@ - + diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 90833069146..f5c02abdf05 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1000,6 +1000,7 @@ export interface IInplaceReplaceSupportResult { export interface ILink { range: IRange; url?: URI | string; + tooltip?: string; } export interface ILinksList { diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 47180269ef6..7d46f79b6a0 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -20,7 +20,7 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { CodeActionModel, SUPPORTED_CODE_ACTIONS, CodeActionsState } from './codeActionModel'; import { CodeActionAutoApply, CodeActionFilter, CodeActionKind } from './codeActionTrigger'; import { CodeActionContextMenu } from './codeActionWidget'; @@ -54,7 +54,7 @@ export class QuickFixController extends Disposable implements IEditorContributio editor: ICodeEditor, @IMarkerService markerService: IMarkerService, @IContextKeyService contextKeyService: IContextKeyService, - @IProgressService progressService: IProgressService, + @ILocalProgressService progressService: ILocalProgressService, @IContextMenuService contextMenuService: IContextMenuService, @ICommandService private readonly _commandService: ICommandService, @IKeybindingService private readonly _keybindingService: IKeybindingService, diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index 100f89cbff4..46b5956d0d2 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -14,7 +14,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { CodeActionProviderRegistry } from 'vs/editor/common/modes'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { getCodeActions, CodeActionSet } from './codeAction'; import { CodeActionTrigger } from './codeActionTrigger'; @@ -30,7 +30,7 @@ export class CodeActionOracle { private readonly _markerService: IMarkerService, private readonly _signalChange: (newState: CodeActionsState.State) => void, private readonly _delay: number = 250, - private readonly _progressService?: IProgressService, + private readonly _progressService?: ILocalProgressService, ) { this._disposables.push( this._markerService.onMarkerChanged(e => this._onMarkerChanges(e)), @@ -179,7 +179,7 @@ export class CodeActionModel { private readonly _editor: ICodeEditor, private readonly _markerService: IMarkerService, contextKeyService: IContextKeyService, - private readonly _progressService: IProgressService + private readonly _progressService: ILocalProgressService ) { this._supportedCodeActions = SUPPORTED_CODE_ACTIONS.bindTo(contextKeyService); diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts index e376dbeaa91..bf6e607945c 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts @@ -25,7 +25,7 @@ import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition } from './goToDefinition'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; @@ -58,7 +58,7 @@ export class DefinitionAction extends EditorAction { } const notificationService = accessor.get(INotificationService); const editorService = accessor.get(ICodeEditorService); - const progressService = accessor.get(IProgressService); + const progressService = accessor.get(ILocalProgressService); const symbolNavService = accessor.get(ISymbolNavigationService); const model = editor.getModel(); @@ -176,7 +176,6 @@ export class DefinitionAction extends EditorAction { resource: reference.uri, options: { selection: Range.collapseToStart(range), - revealIfOpened: true, revealInCenterIfOutsideViewport: true } }, editor, sideBySide); diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts index 68c34213275..557e5125896 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts @@ -13,7 +13,7 @@ import { registerEditorCommand, EditorCommand } from 'vs/editor/browser/editorEx import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { Range } from 'vs/editor/common/core/range'; -import { Disposable, dispose, combinedDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { localize } from 'vs/nls'; @@ -126,8 +126,7 @@ class SymbolNavigationService implements ISymbolNavigationService { resource: reference.uri, options: { selection: Range.collapseToStart(reference.range), - revealInCenterIfOutsideViewport: true, - revealIfOpened: true + revealInCenterIfOutsideViewport: true } }, source).finally(() => { this._ignoreEditorChange = false; @@ -141,7 +140,7 @@ class SymbolNavigationService implements ISymbolNavigationService { const kb = this._keybindingService.lookupKeybinding('editor.gotoNextSymbolFromResult'); const message = kb - ? localize('location.kb', "Symbol {0} of {1}, press {2} to reveal next", this._currentIdx + 1, this._currentModel!.references.length, kb.getLabel()) + ? localize('location.kb', "Symbol {0} of {1}, {2} for next", this._currentIdx + 1, this._currentModel!.references.length, kb.getLabel()) : localize('location', "Symbol {0} of {1}", this._currentIdx + 1, this._currentModel!.references.length); this._currentMessage = this._statusbarService.setStatusMessage(message); @@ -199,10 +198,10 @@ class EditorStatus extends Disposable { } private _onDidAddEditor(editor: ICodeEditor): void { - this._listener.set(editor, combinedDisposable([ + this._listener.set(editor, combinedDisposable( editor.onDidChangeCursorPosition(_ => this._onDidChange.fire({ editor })), editor.onDidChangeModelContent(_ => this._onDidChange.fire({ editor })), - ])); + )); } private _onDidRemoveEditor(editor: ICodeEditor): void { diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index b630d4fc220..2ac2c54af5e 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/gotoErrorWidget'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IMarker, MarkerSeverity, IRelatedInformation } from 'vs/platform/markers/common/markers'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -28,7 +28,7 @@ import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/action import { peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -class MessageWidget { +class MessageWidget extends Disposable { private _lines: number = 0; private _longestLineLength: number = 0; @@ -38,9 +38,9 @@ class MessageWidget { private readonly _relatedBlock: HTMLDivElement; private readonly _scrollable: ScrollableElement; private readonly _relatedDiagnostics = new WeakMap(); - private readonly _disposables: IDisposable[] = []; constructor(parent: HTMLElement, editor: ICodeEditor, onRelatedInformation: (related: IRelatedInformation) => void) { + super(); this._editor = editor; const domNode = document.createElement('div'); @@ -54,7 +54,7 @@ class MessageWidget { this._relatedBlock = document.createElement('div'); domNode.appendChild(this._relatedBlock); - this._disposables.push(dom.addStandardDisposableListener(this._relatedBlock, 'click', event => { + this._register(dom.addStandardDisposableListener(this._relatedBlock, 'click', event => { event.preventDefault(); const related = this._relatedDiagnostics.get(event.target); if (related) { @@ -70,15 +70,11 @@ class MessageWidget { verticalScrollbarSize: 3 }); parent.appendChild(this._scrollable.getDomNode()); - this._disposables.push(this._scrollable.onScroll(e => { + this._register(this._scrollable.onScroll(e => { domNode.style.left = `-${e.scrollLeft}px`; domNode.style.top = `-${e.scrollTop}px`; })); - this._disposables.push(this._scrollable); - } - - dispose(): void { - dispose(this._disposables); + this._register(this._scrollable); } update({ source, message, relatedInformation, code }: IMarker): void { diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index b977abf6cdf..320a36402da 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -7,7 +7,7 @@ import 'vs/css!./hover'; import * as nls from 'vs/nls'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -34,7 +34,7 @@ export class ModesHoverController implements IEditorContribution { private static readonly ID = 'editor.contrib.hover'; - private _toUnhook: IDisposable[]; + private _toUnhook = new DisposableStore(); private readonly _didChangeConfigurationHandler: IDisposable; private _contentWidget: ModesContentHoverWidget; @@ -73,8 +73,6 @@ export class ModesHoverController implements IEditorContribution { @ICommandService private readonly _commandService: ICommandService, @IThemeService private readonly _themeService: IThemeService ) { - this._toUnhook = []; - this._isMouseDown = false; this._hoverClicked = false; @@ -111,7 +109,8 @@ export class ModesHoverController implements IEditorContribution { } private _unhookEvents(): void { - this._toUnhook = dispose(this._toUnhook); + this._toUnhook.dispose(); + this._toUnhook = new DisposableStore(); } private _onModelDecorationsChanged(): void { diff --git a/src/vs/editor/contrib/hover/hoverWidgets.ts b/src/vs/editor/contrib/hover/hoverWidgets.ts index 71452506714..6489db5a9fa 100644 --- a/src/vs/editor/contrib/hover/hoverWidgets.ts +++ b/src/vs/editor/contrib/hover/hoverWidgets.ts @@ -8,7 +8,6 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Widget } from 'vs/base/browser/ui/widget'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; @@ -25,7 +24,6 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent protected _showAtRange: Range | null; private _stoleFocus: boolean; private readonly scrollbar: DomScrollableElement; - private disposables: IDisposable[] = []; // Editor.IContentWidget.allowEditorOverflow public allowEditorOverflow = true; @@ -53,7 +51,7 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent this._domNode.className = 'monaco-editor-hover-content'; this.scrollbar = new DomScrollableElement(this._domNode, {}); - this.disposables.push(this.scrollbar); + this._register(this.scrollbar); this._containerDomNode.appendChild(this.scrollbar.getDomNode()); this.onkeydown(this._containerDomNode, (e: IKeyboardEvent) => { @@ -129,7 +127,6 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent public dispose(): void { this._editor.removeContentWidget(this); - this.disposables = dispose(this.disposables); super.dispose(); } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 94d846e38a0..045298ca469 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -8,7 +8,7 @@ import * as dom from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; import { IMarkdownString, MarkdownString, isEmptyMarkdownString, markedStringsEquals } from 'vs/base/common/htmlContent'; -import { Disposable, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -436,7 +436,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this.updateContents(fragment); this._colorPicker.layout(); - this.renderDisposable = combinedDisposable([colorListener, colorChangeListener, widget, ...markdownDisposeables]); + this.renderDisposable = combinedDisposable(colorListener, colorChangeListener, widget, ...markdownDisposeables); }); } else { if (msg instanceof MarkerHover) { @@ -526,7 +526,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private renderMarkerStatusbar(markerHover: MarkerHover): HTMLElement { const hoverElement = $('div.hover-row.status-bar'); - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const actionsElement = dom.append(hoverElement, $('div.actions')); disposables.push(this.renderAction(actionsElement, { label: nls.localize('quick fixes', "Quick Fix..."), @@ -553,7 +553,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { } })); } - this.renderDisposable = combinedDisposable(disposables); + this.renderDisposable = disposables; return hoverElement; } diff --git a/src/vs/editor/contrib/links/getLinks.ts b/src/vs/editor/contrib/links/getLinks.ts index 5745767952c..140d1f0ef45 100644 --- a/src/vs/editor/contrib/links/getLinks.ts +++ b/src/vs/editor/contrib/links/getLinks.ts @@ -27,7 +27,8 @@ export class Link implements ILink { toJSON(): ILink { return { range: this.range, - url: this.url + url: this.url, + tooltip: this.tooltip }; } @@ -39,6 +40,10 @@ export class Link implements ILink { return this._link.url; } + get tooltip(): string | undefined { + return this._link.tooltip; + } + resolve(token: CancellationToken): Promise { if (this._link.url) { try { diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index f44dbff1b0b..ed3b65ef042 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -111,6 +111,23 @@ class LinkOccurrence { } private static _getOptions(link: Link, useMetaKey: boolean, isActive: boolean): ModelDecorationOptions { + const options = { ...this._getBaseOptions(link, useMetaKey, isActive) }; + if (typeof link.tooltip === 'string') { + const message = new MarkdownString().appendText( + platform.isMacintosh + ? useMetaKey + ? nls.localize('links.custom.mac', "Cmd + click to {0}", link.tooltip) + : nls.localize('links.custom.mac.al', "Option + click to {0}", link.tooltip) + : useMetaKey + ? nls.localize('links.custom', "Ctrl + click to {0}", link.tooltip) + : nls.localize('links.custom.al', "Alt + click to {0}", link.tooltip) + ); + options.hoverMessage = message; + } + return options; + } + + private static _getBaseOptions(link: Link, useMetaKey: boolean, isActive: boolean): ModelDecorationOptions { if (link.url && /^command:/i.test(link.url.toString())) { if (useMetaKey) { return (isActive ? decoration.metaCommandActive : decoration.metaCommand); diff --git a/src/vs/editor/contrib/markdown/markdownRenderer.ts b/src/vs/editor/contrib/markdown/markdownRenderer.ts index e013de91cd4..2016c0e642d 100644 --- a/src/vs/editor/contrib/markdown/markdownRenderer.ts +++ b/src/vs/editor/contrib/markdown/markdownRenderer.ts @@ -78,7 +78,7 @@ export class MarkdownRenderer { } render(markdown: IMarkdownString | undefined): IMarkdownRenderResult { - let disposeables: IDisposable[] = []; + const disposeables: IDisposable[] = []; let element: HTMLElement; if (!markdown) { diff --git a/src/vs/editor/contrib/referenceSearch/referencesModel.ts b/src/vs/editor/contrib/referenceSearch/referencesModel.ts index 29b3d8aa0a8..f727f3699a7 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesModel.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesModel.ts @@ -14,6 +14,7 @@ import { Range, IRange } from 'vs/editor/common/core/range'; import { Location, LocationLink } from 'vs/editor/common/modes'; import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { Position } from 'vs/editor/common/core/position'; +import { IMatch } from 'vs/base/common/filters'; export class OneReference { readonly id: string; @@ -61,7 +62,7 @@ export class FilePreview implements IDisposable { dispose(this._modelReference); } - preview(range: IRange, n: number = 8): { before: string; inside: string; after: string } | undefined { + preview(range: IRange, n: number = 8): { value: string; highlight: IMatch } | undefined { const model = this._modelReference.object.textEditorModel; if (!model) { @@ -73,13 +74,14 @@ export class FilePreview implements IDisposable { const beforeRange = new Range(startLineNumber, word.startColumn, startLineNumber, startColumn); const afterRange = new Range(endLineNumber, endColumn, endLineNumber, Number.MAX_VALUE); - const ret = { - before: model.getValueInRange(beforeRange).replace(/^\s+/, strings.empty), - inside: model.getValueInRange(range), - after: model.getValueInRange(afterRange).replace(/\s+$/, strings.empty) - }; + const before = model.getValueInRange(beforeRange).replace(/^\s+/, strings.empty); + const inside = model.getValueInRange(range); + const after = model.getValueInRange(afterRange).replace(/\s+$/, strings.empty); - return ret; + return { + value: before + inside + after, + highlight: { start: before.length, end: before.length + inside.length } + }; } } diff --git a/src/vs/editor/contrib/referenceSearch/referencesTree.ts b/src/vs/editor/contrib/referenceSearch/referencesTree.ts index 2a8388900e8..e563158bda9 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesTree.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesTree.ts @@ -15,7 +15,6 @@ import * as dom from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { getBaseLabel } from 'vs/base/common/labels'; import { dirname, basename } from 'vs/base/common/resources'; -import { escape } from 'vs/base/common/strings'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; @@ -23,6 +22,7 @@ import { IListVirtualDelegate, IKeyboardNavigationLabelProvider, IIdentityProvid import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { FuzzyScore, createMatches, IMatch } from 'vs/base/common/filters'; +import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; //#region data source @@ -82,8 +82,14 @@ export class StringRepresentationProvider implements IKeyboardNavigationLabelPro constructor(@IKeybindingService private readonly _keybindingService: IKeybindingService) { } getKeyboardNavigationLabel(element: TreeElement): { toString(): string; } { - // todo@joao `OneReference` elements are lazy and their "real" label - // isn't known yet + if (element instanceof OneReference) { + const { preview } = element.parent; + const parts = preview && preview.preview(element.range); + if (parts) { + return parts.value; + } + } + // FileReferences or unresolved OneReference return basename(element.uri); } @@ -161,31 +167,29 @@ export class FileReferencesRenderer implements ITreeRenderer, index: number, templateData: OneReferenceTemplate): void { - templateData.set(element.element); + renderElement(node: ITreeNode, index: number, templateData: OneReferenceTemplate): void { + templateData.set(node.element, node.filterData); } disposeTemplate(): void { - // } } diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index cfb4b6f932a..3c9e6e91b33 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -566,7 +566,7 @@ export const peekViewEditorMatchHighlightBorder = registerColor('peekViewEditor. registerThemingParticipant((theme, collector) => { const findMatchHighlightColor = theme.getColor(peekViewResultsMatchHighlight); if (findMatchHighlightColor) { - collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch { background-color: ${findMatchHighlightColor}; }`); + collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight { background-color: ${findMatchHighlightColor}; }`); } const referenceHighlightColor = theme.getColor(peekViewEditorMatchHighlight); if (referenceHighlightColor) { @@ -578,7 +578,7 @@ registerThemingParticipant((theme, collector) => { } const hcOutline = theme.getColor(activeContrastBorder); if (hcOutline) { - collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch { border: 1px dotted ${hcOutline}; box-sizing: border-box; }`); + collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight { border: 1px dotted ${hcOutline}; box-sizing: border-box; }`); } const resultsBackground = theme.getColor(peekViewResultsBackground); if (resultsBackground) { diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 460fcc040c2..28ec66cfaf7 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; @@ -114,7 +114,7 @@ class RenameController extends Disposable implements IEditorContribution { private readonly editor: ICodeEditor, @INotificationService private readonly _notificationService: INotificationService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, - @IProgressService private readonly _progressService: IProgressService, + @ILocalProgressService private readonly _progressService: ILocalProgressService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IThemeService private readonly _themeService: IThemeService, ) { diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index b1dce91bfc7..0c3b41c2c73 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./renameInputField'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; @@ -16,7 +16,7 @@ import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameInputVisible', false); -export class RenameInputField implements IContentWidget, IDisposable { +export class RenameInputField extends Disposable implements IContentWidget { private _editor: ICodeEditor; private _position: Position; @@ -24,7 +24,6 @@ export class RenameInputField implements IContentWidget, IDisposable { private _inputField: HTMLInputElement; private _visible: boolean; private readonly _visibleContextKey: IContextKey; - private _disposables: IDisposable[] = []; // Editor.IContentWidget.allowEditorOverflow public allowEditorOverflow: boolean = true; @@ -34,18 +33,19 @@ export class RenameInputField implements IContentWidget, IDisposable { private readonly themeService: IThemeService, contextKeyService: IContextKeyService, ) { + super(); this._visibleContextKey = CONTEXT_RENAME_INPUT_VISIBLE.bindTo(contextKeyService); this._editor = editor; this._editor.addContentWidget(this); - this._disposables.push(editor.onDidChangeConfiguration(e => { + this._register(editor.onDidChangeConfiguration(e => { if (e.fontInfo) { this.updateFont(); } })); - this._disposables.push(themeService.onThemeChange(theme => this.onThemeChange(theme))); + this._register(themeService.onThemeChange(theme => this.onThemeChange(theme))); } private onThemeChange(theme: ITheme): void { @@ -53,7 +53,7 @@ export class RenameInputField implements IContentWidget, IDisposable { } public dispose(): void { - this._disposables = dispose(this._disposables); + super.dispose(); this._editor.removeContentWidget(this); } @@ -138,9 +138,9 @@ export class RenameInputField implements IContentWidget, IDisposable { this._inputField.setAttribute('selectionEnd', selectionEnd.toString()); this._inputField.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); - const disposeOnDone: IDisposable[] = []; + const disposeOnDone = new DisposableStore(); const always = () => { - dispose(disposeOnDone); + disposeOnDone.dispose(); this._hide(); }; diff --git a/src/vs/editor/contrib/smartSelect/smartSelect.ts b/src/vs/editor/contrib/smartSelect/smartSelect.ts index 164723bb251..4fe561f4c29 100644 --- a/src/vs/editor/contrib/smartSelect/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/smartSelect.ts @@ -284,12 +284,12 @@ export function provideSelectionRanges(model: ITextModel, positions: Position[], if (cur.startLineNumber !== prev.startLineNumber || cur.endLineNumber !== prev.endLineNumber) { // add line/block range without leading/failing whitespace const rangeNoWhitespace = new Range(prev.startLineNumber, model.getLineFirstNonWhitespaceColumn(prev.startLineNumber), prev.endLineNumber, model.getLineLastNonWhitespaceColumn(prev.endLineNumber)); - if (rangeNoWhitespace.containsRange(prev) && !rangeNoWhitespace.equalsRange(prev)) { + if (rangeNoWhitespace.containsRange(prev) && !rangeNoWhitespace.equalsRange(prev) && cur.containsRange(rangeNoWhitespace) && !cur.equalsRange(rangeNoWhitespace)) { oneRangesWithTrivia.push(rangeNoWhitespace); } // add line/block range const rangeFull = new Range(prev.startLineNumber, 1, prev.endLineNumber, model.getLineMaxColumn(prev.endLineNumber)); - if (rangeFull.containsRange(prev) && !rangeFull.equalsRange(rangeNoWhitespace)) { + if (rangeFull.containsRange(prev) && !rangeFull.equalsRange(rangeNoWhitespace) && cur.containsRange(rangeFull) && !cur.equalsRange(rangeFull)) { oneRangesWithTrivia.push(rangeFull); } } diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index de3a2ed6a75..b26f7fdc08f 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { Range, IRange } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; -import { LanguageIdentifier, SelectionRangeProvider } from 'vs/editor/common/modes'; +import { LanguageIdentifier, SelectionRangeProvider, SelectionRangeRegistry } from 'vs/editor/common/modes'; import { MockMode, StaticLanguageSelector } from 'vs/editor/test/common/mocks/mockMode'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; @@ -320,4 +320,25 @@ suite('SmartSelect', () => { new Range(1, 1, 1, 21), ); }); + + test('Smart select: only add line ranges if they’re contained by the next range #73850', async function () { + + const reg = SelectionRangeRegistry.register('*', { + provideSelectionRanges() { + return [[ + { range: { startLineNumber: 1, startColumn: 10, endLineNumber: 1, endColumn: 11 }, kind: 'somekind' }, + { range: { startLineNumber: 1, startColumn: 10, endLineNumber: 3, endColumn: 2 }, kind: 'somekind' }, + { range: { startLineNumber: 1, startColumn: 1, endLineNumber: 3, endColumn: 2 }, kind: 'somekind' }, + ]]; + } + }); + + await assertGetRangesToPosition(['type T = {', '\tx: number', '}'], 1, 10, [ + new Range(1, 1, 3, 2), // all + new Range(1, 10, 3, 2), // { ... } + new Range(1, 10, 1, 11), // { + ]); + + reg.dispose(); + }); }); diff --git a/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts b/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts index ba0789bf893..7075aceb641 100644 --- a/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts +++ b/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts @@ -4,14 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ISelectedSuggestion, SuggestWidget } from './suggestWidget'; import { CharacterSet } from 'vs/editor/common/core/characterClassifier'; -export class CommitCharacterController { - - private _disposables: IDisposable[] = []; +export class CommitCharacterController extends Disposable { private _active?: { readonly acceptCharacters: CharacterSet; @@ -19,12 +17,13 @@ export class CommitCharacterController { }; constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (selected: ISelectedSuggestion) => any) { + super(); - this._disposables.push(widget.onDidShow(() => this._onItem(widget.getFocusedItem()))); - this._disposables.push(widget.onDidFocus(this._onItem, this)); - this._disposables.push(widget.onDidHide(this.reset, this)); + this._register(widget.onDidShow(() => this._onItem(widget.getFocusedItem()))); + this._register(widget.onDidFocus(this._onItem, this)); + this._register(widget.onDidHide(this.reset, this)); - this._disposables.push(editor.onWillType(text => { + this._register(editor.onWillType(text => { if (this._active) { const ch = text.charCodeAt(text.length - 1); if (this._active.acceptCharacters.has(ch) && editor.getConfiguration().contribInfo.acceptSuggestionOnCommitCharacter) { @@ -52,8 +51,4 @@ export class CommitCharacterController { reset(): void { this._active = undefined; } - - dispose() { - dispose(this._disposables); - } } diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index d267a41ca4e..76b83ec0c7a 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -7,7 +7,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -34,7 +34,7 @@ import { IdleValue } from 'vs/base/common/async'; import { isObject } from 'vs/base/common/types'; import { CommitCharacterController } from './suggestCommitCharacters'; -export class SuggestController implements IEditorContribution { +export class SuggestController extends Disposable implements IEditorContribution { private static readonly ID: string = 'editor.contrib.suggestController'; @@ -45,7 +45,6 @@ export class SuggestController implements IEditorContribution { private readonly _model: SuggestModel; private readonly _widget: IdleValue; private readonly _alternatives: IdleValue; - private _toDispose: IDisposable[] = []; private readonly _sticky = false; // for development purposes only @@ -57,29 +56,28 @@ export class SuggestController implements IEditorContribution { @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { + super(); this._model = new SuggestModel(this._editor, editorWorker); this._widget = new IdleValue(() => { const widget = this._instantiationService.createInstance(SuggestWidget, this._editor); - this._toDispose.push(widget); - this._toDispose.push(widget.onDidSelect(item => this._insertSuggestion(item, false, true), this)); + this._register(widget); + this._register(widget.onDidSelect(item => this._insertSuggestion(item, false, true), this)); // Wire up logic to accept a suggestion on certain characters - const commitCharacterController = new CommitCharacterController(this._editor, widget, item => this._insertSuggestion(item, false, true)); - this._toDispose.push( - commitCharacterController, - this._model.onDidSuggest(e => { - if (e.completionModel.items.length === 0) { - commitCharacterController.reset(); - } - }) + const commitCharacterController = this._register(new CommitCharacterController(this._editor, widget, item => this._insertSuggestion(item, false, true))); + this._register(this._model.onDidSuggest(e => { + if (e.completionModel.items.length === 0) { + commitCharacterController.reset(); + } + }) ); // Wire up makes text edit context key let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService); - this._toDispose.push(widget.onDidFocus(({ item }) => { + this._register(widget.onDidFocus(({ item }) => { const position = this._editor.getPosition()!; const startColumn = item.completion.range.startColumn; @@ -103,36 +101,36 @@ export class SuggestController implements IEditorContribution { } makesTextEdit.set(value); })); - this._toDispose.push({ - dispose() { makesTextEdit.reset(); } - }); + this._register(toDisposable(() => { + makesTextEdit.reset(); + })); return widget; }); this._alternatives = new IdleValue(() => { let res = new SuggestAlternatives(this._editor, this._contextKeyService); - this._toDispose.push(res); + this._register(res); return res; }); - this._toDispose.push(_instantiationService.createInstance(WordContextKey, _editor)); + this._register(_instantiationService.createInstance(WordContextKey, _editor)); - this._toDispose.push(this._model.onDidTrigger(e => { + this._register(this._model.onDidTrigger(e => { this._widget.getValue().showTriggered(e.auto, e.shy ? 250 : 50); })); - this._toDispose.push(this._model.onDidSuggest(e => { + this._register(this._model.onDidSuggest(e => { if (!e.shy) { let index = this._memoryService.select(this._editor.getModel()!, this._editor.getPosition()!, e.completionModel.items); this._widget.getValue().showSuggestions(e.completionModel, index, e.isFrozen, e.auto); } })); - this._toDispose.push(this._model.onDidCancel(e => { + this._register(this._model.onDidCancel(e => { if (this._widget && !e.retrigger) { this._widget.getValue().hideWidget(); } })); - this._toDispose.push(this._editor.onDidBlurEditorWidget(() => { + this._register(this._editor.onDidBlurEditorWidget(() => { if (!this._sticky) { this._model.cancel(); } @@ -144,7 +142,7 @@ export class SuggestController implements IEditorContribution { const { acceptSuggestionOnEnter } = this._editor.getConfiguration().contribInfo; acceptSuggestionsOnEnter.set(acceptSuggestionOnEnter === 'on' || acceptSuggestionOnEnter === 'smart'); }; - this._toDispose.push(this._editor.onDidChangeConfiguration((e) => updateFromConfig())); + this._register(this._editor.onDidChangeConfiguration((e) => updateFromConfig())); updateFromConfig(); } @@ -154,7 +152,7 @@ export class SuggestController implements IEditorContribution { } dispose(): void { - this._toDispose = dispose(this._toDispose); + super.dispose(); this._widget.dispose(); if (this._model) { this._model.dispose(); diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 6bd70b0ac3e..3776acb11b5 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -8,7 +8,7 @@ import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Emitter, Event } from 'vs/base/common/event'; import { Keybinding, ResolvedKeybinding, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; -import { IDisposable, IReference, ImmortalReference, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, IReference, ImmortalReference, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { OS, isLinux, isMacintosh } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; @@ -38,7 +38,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { ILabelService, ResourceLabelFormatter } from 'vs/platform/label/common/label'; import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification } from 'vs/platform/notification/common/notification'; -import { IProgressRunner, IProgressService } from 'vs/platform/progress/common/progress'; +import { IProgressRunner, ILocalProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; @@ -136,7 +136,7 @@ export class SimpleEditorModelResolverService implements ITextModelService { } } -export class SimpleProgressService implements IProgressService { +export class SimpleLocalProgressService implements ILocalProgressService { _serviceBrand: any; private static NULL_PROGRESS_RUNNER: IProgressRunner = { @@ -148,7 +148,7 @@ export class SimpleProgressService implements IProgressService { show(infinite: true, delay?: number): IProgressRunner; show(total: number, delay?: number): IProgressRunner; show(): IProgressRunner { - return SimpleProgressService.NULL_PROGRESS_RUNNER; + return SimpleLocalProgressService.NULL_PROGRESS_RUNNER; } showWhile(promise: Promise, delay?: number): Promise { @@ -291,7 +291,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { throw new Error(`Invalid keybinding`); } - let toDispose: IDisposable[] = []; + const toDispose = new DisposableStore(); this._dynamicKeybindings.push({ keybinding: keybinding, @@ -323,7 +323,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { } this.updateResolver({ source: KeybindingSource.Default }); - return combinedDisposable(toDispose); + return toDispose; } private updateResolver(event: IKeybindingEvent): void { diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 75fae893624..b747e6aa3e9 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -5,7 +5,7 @@ import * as browser from 'vs/base/browser/browser'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { Disposable, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -227,7 +227,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon }; - let toDispose: IDisposable[] = []; + const toDispose = new DisposableStore(); // Generate a unique id to allow the same descriptor.id across multiple editor instances const uniqueId = this.getId() + ':' + id; @@ -251,11 +251,9 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon // Register the keybindings if (Array.isArray(keybindings)) { - toDispose = toDispose.concat( - keybindings.map((kb) => { - return this._standaloneKeybindingService.addDynamicKeybinding(uniqueId, kb, run, keybindingsWhen); - }) - ); + for (const kb of keybindings) { + toDispose.push(this._standaloneKeybindingService.addDynamicKeybinding(uniqueId, kb, run, keybindingsWhen)); + } } // Finally, register an internal editor action @@ -274,7 +272,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon delete this._actions[id]; })); - return combinedDisposable(toDispose); + return toDispose; } } diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 0f3d46ac1a2..a9943538a82 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/resourceConfiguration'; -import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleProgressService, SimpleResourceConfigurationService, SimpleResourcePropertiesService, SimpleUriLabelService, SimpleWorkspaceContextService, StandaloneCommandService, StandaloneKeybindingService, StandaloneTelemetryService, SimpleLayoutService } from 'vs/editor/standalone/browser/simpleServices'; +import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleLocalProgressService, 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'; @@ -36,7 +36,7 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { MarkerService } from 'vs/platform/markers/common/markerService'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -150,7 +150,7 @@ export module StaticServices { export const codeEditorService = define(ICodeEditorService, (o) => new StandaloneCodeEditorServiceImpl(standaloneThemeService.get(o))); - export const progressService = define(IProgressService, () => new SimpleProgressService()); + export const localProgressService = define(ILocalProgressService, () => new SimpleLocalProgressService()); export const storageService = define(IStorageService, () => new InMemoryStorageService()); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index a99de0ecaf3..625a4707015 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5254,6 +5254,7 @@ declare namespace monaco.languages { export interface ILink { range: IRange; url?: Uri | string; + tooltip?: string; } export interface ILinksList { diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index 53ba4d21e04..6b67d1cb58b 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -5,7 +5,7 @@ import 'vs/css!./contextMenuHandler'; -import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { ActionRunner, IRunEvent } from 'vs/base/common/actions'; import { Menu } from 'vs/base/browser/ui/menu/menu'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -104,7 +104,7 @@ export class ContextMenuHandler { this.contextViewService.hideContextView(true); }, null, menuDisposables); - return combinedDisposable([...menuDisposables, menu]); + return combinedDisposable(...menuDisposables, menu); }, focus: () => { diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 8a202409c45..2b341fb2ed6 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -210,5 +210,5 @@ export async function serve( const channel = new DriverChannel(driver); server.registerChannel('driver', channel); - return combinedDisposable([server, windowServer]); + return combinedDisposable(server, windowServer); } diff --git a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts index 162cf268b71..bad92e00651 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts @@ -5,6 +5,7 @@ import { ILocalExtension, IGalleryExtension, IExtensionIdentifier, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { compareIgnoreCase } from 'vs/base/common/strings'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean { if (a.uuid && b.uuid) { @@ -16,6 +17,24 @@ export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifi return compareIgnoreCase(a.id, b.id) === 0; } +export class ExtensionIdentifierWithVersion { + constructor( + readonly identifier: IExtensionIdentifier, + readonly version: string + ) { } + + key(): string { + return `${this.identifier.id}-${this.version}`; + } + + equals(o: any): boolean { + if (!(o instanceof ExtensionIdentifierWithVersion)) { + return false; + } + return areSameExtensions(this.identifier, o.identifier) && this.version === o.version; + } +} + export function adoptToGalleryExtensionId(id: string): string { return id.toLocaleLowerCase(); } @@ -85,7 +104,7 @@ export function getGalleryExtensionTelemetryData(extension: IGalleryExtension): }; } -export const BetterMergeId = 'pprice.better-merge'; +export const BetterMergeId = new ExtensionIdentifier('pprice.better-merge'); export function getMaliciousExtensionsSet(report: IReportedExtension[]): Set { const result = new Set(); diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 39942d397f7..66d9d6dafa9 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -21,7 +21,7 @@ import { INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localizeManifest } from '../common/extensionNls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Limiter, createCancelablePromise, CancelablePromise, Queue } from 'vs/base/common/async'; @@ -44,7 +44,7 @@ import { Schemas } from 'vs/base/common/network'; import { CancellationToken } from 'vs/base/common/cancellation'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; -import { IExtensionManifest, ExtensionType, ExtensionIdentifierWithVersion } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser'; @@ -347,7 +347,7 @@ export class ExtensionManagementService extends Disposable implements IExtension const compatibleExtension = await this.galleryService.getCompatibleExtension(extension); if (!compatibleExtension) { - return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE)); + return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE)); } return compatibleExtension; diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 3d2eb91ca81..2a2edf29312 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import * as strings from 'vs/base/common/strings'; import { ILocalization } from 'vs/platform/localizations/common/localizations'; import { URI } from 'vs/base/common/uri'; @@ -109,24 +108,6 @@ export interface IExtensionContributions { export type ExtensionKind = 'ui' | 'workspace'; -export class ExtensionIdentifierWithVersion { - constructor( - readonly identifier: IExtensionIdentifier, - readonly version: string - ) { } - - key(): string { - return `${this.identifier.id}-${this.version}`; - } - - equals(o: any): boolean { - if (!(o instanceof ExtensionIdentifierWithVersion)) { - return false; - } - return areSameExtensions(this.identifier, o.identifier) && this.version === o.version; - } -} - export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier { return thing && typeof thing === 'object' diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index d9d37956a76..a13c639276a 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -8,7 +8,7 @@ import { IListMouseEvent, IListTouchEvent, IListRenderer, IListVirtualDelegate } import { IPagedRenderer, PagedList } from 'vs/base/browser/ui/list/listPaging'; import { DefaultStyleController, IListOptions, IMultipleSelectionController, IOpenController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List } from 'vs/base/browser/ui/list/listWidget'; import { Emitter, Event } from 'vs/base/common/event'; -import { combinedDisposable, Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable, toDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { ITree, ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; @@ -78,7 +78,7 @@ export class ListService implements IListService { this._lastFocusedWidget = widget; } - const result = combinedDisposable([ + return combinedDisposable( widget.onDidFocus(() => this._lastFocusedWidget = widget), toDisposable(() => this.lists.splice(this.lists.indexOf(registeredList), 1)), widget.onDidDispose(() => { @@ -87,9 +87,7 @@ export class ListService implements IListService { this._lastFocusedWidget = undefined; } }) - ]); - - return result; + ); } } @@ -200,7 +198,7 @@ class WorkbenchOpenController extends Disposable implements IOpenController { } function toWorkbenchListOptions(options: IListOptions, configurationService: IConfigurationService, keybindingService: IKeybindingService): [IListOptions, IDisposable] { - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const result = { ...options }; if (options.multipleSelectionSupport !== false && !options.multipleSelectionController) { @@ -222,7 +220,7 @@ function toWorkbenchListOptions(options: IListOptions, configurationServic }; } - return [result, combinedDisposable(disposables)]; + return [result, disposables]; } let sharedListStyleSheet: HTMLStyleElement; @@ -283,7 +281,7 @@ export class WorkbenchList extends List { this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); - this.disposables.push(combinedDisposable([ + this.disposables.push( this.contextKeyService, (listService as ListService).register(this), attachListStyler(this, themeService), @@ -301,7 +299,7 @@ export class WorkbenchList extends List { this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0); }) - ])); + ); this.registerListeners(); } @@ -324,7 +322,7 @@ export class WorkbenchPagedList extends PagedList { readonly contextKeyService: IContextKeyService; private readonly configurationService: IConfigurationService; - private disposables: IDisposable[]; + private readonly disposables: DisposableStore; private _useAltAsMultipleSelectionModifier: boolean; @@ -351,7 +349,8 @@ export class WorkbenchPagedList extends PagedList { } ); - this.disposables = [workbenchListOptionsDisposable]; + this.disposables = new DisposableStore(); + this.disposables.push(workbenchListOptionsDisposable); this.contextKeyService = createScopedContextKeyService(contextKeyService, this); this.configurationService = configurationService; @@ -361,11 +360,9 @@ export class WorkbenchPagedList extends PagedList { this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); - this.disposables.push(combinedDisposable([ - this.contextKeyService, - (listService as ListService).register(this), - attachListStyler(this, themeService) - ])); + this.disposables.push(this.contextKeyService); + this.disposables.push((listService as ListService).register(this)); + this.disposables.push(attachListStyler(this, themeService)); this.registerListeners(); } @@ -385,7 +382,7 @@ export class WorkbenchPagedList extends PagedList { dispose(): void { super.dispose(); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } @@ -533,7 +530,7 @@ function massageControllerOptions(options: IControllerOptions): IControllerOptio */ export class WorkbenchTreeController extends DefaultController { - protected disposables: IDisposable[] = []; + protected readonly disposables = new DisposableStore(); constructor( options: IControllerOptions, @@ -561,7 +558,7 @@ export class WorkbenchTreeController extends DefaultController { } dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 72f4b528aba..5edca00feb5 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -10,8 +10,13 @@ export const IProductService = createDecorator('productService' export interface IProductService { _serviceBrand: any; - version?: string; + version: string; commit?: string; + nameLong: string; + urlProtocol: string; + extensionAllowedProposedApi: string[]; + uiExtensions?: string[]; + enableTelemetry: boolean; } \ No newline at end of file diff --git a/src/vs/platform/product/node/productService.ts b/src/vs/platform/product/node/productService.ts index 884b645a722..29ef31f50b9 100644 --- a/src/vs/platform/product/node/productService.ts +++ b/src/vs/platform/product/node/productService.ts @@ -11,9 +11,17 @@ export class ProductService implements IProductService { _serviceBrand: any; - get version(): string | undefined { return pkg.version; } + get version(): string { return pkg.version; } get commit(): string | undefined { return product.commit; } + get nameLong(): string { return product.nameLong; } + + get urlProtocol(): string { return product.urlProtocol; } + + get extensionAllowedProposedApi(): string[] { return product.extensionAllowedProposedApi; } + + get uiExtensions(): string[] | undefined { return product.uiExtensions; } + get enableTelemetry(): boolean { return product.enableTelemetry; } } \ No newline at end of file diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index f3e60f24455..f88fc5d1fbb 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -3,15 +3,32 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { IAction } from 'vs/base/common/actions'; export const IProgressService = createDecorator('progressService'); +/** + * A progress service that can be used to report progress to various locations of the UI. + */ export interface IProgressService { - _serviceBrand: any; + + _serviceBrand: ServiceIdentifier; + + withProgress(options: IProgressOptions | IProgressNotificationOptions | IProgressCompositeOptions, task: (progress: IProgress) => Promise, onDidCancel?: () => void): Promise; +} + +export const ILocalProgressService = createDecorator('localProgressService'); + +/** + * A progress service that will report progress local to the UI piece triggered from. E.g. + * if used from an action of a viewlet, the progress will be reported in that viewlet. + */ +export interface ILocalProgressService { + + _serviceBrand: ServiceIdentifier; /** * Show progress customized with the provided flags. @@ -49,20 +66,16 @@ export interface IProgressNotificationOptions extends IProgressOptions { secondaryActions?: IAction[]; } +export interface IProgressCompositeOptions extends IProgressOptions { + location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string; + delay?: number; +} + export interface IProgressStep { message?: string; increment?: number; } -export const IProgressService2 = createDecorator('progressService2'); - -export interface IProgressService2 { - - _serviceBrand: any; - - withProgress(options: IProgressOptions, task: (progress: IProgress) => Promise, onDidCancel?: () => void): Promise; -} - export interface IProgressRunner { total(value: number): void; worked(value: number): void; @@ -118,7 +131,7 @@ export class LongRunningOperation { private currentProgressTimeout: any; constructor( - private progressService: IProgressService + private localProgressService: ILocalProgressService ) { } start(progressDelay: number): IOperation { @@ -131,7 +144,7 @@ export class LongRunningOperation { const newOperationToken = new CancellationTokenSource(); this.currentProgressTimeout = setTimeout(() => { if (newOperationId === this.currentOperationId) { - this.currentProgressRunner = this.progressService.show(true); + this.currentProgressRunner = this.localProgressService.show(true); } }, progressDelay); diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index 58a1ef576d1..37d42a5c391 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -21,14 +21,11 @@ export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverS } clearResolvedAuthority(authority: string): void { - throw new Error(`Not implemented`); } setResolvedAuthority(resolvedAuthority: ResolvedAuthority) { - throw new Error(`Not implemented`); } setResolvedAuthorityError(authority: string, err: any): void { - throw new Error(`Not implemented`); } } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e428d0d2ebc..7c35b7decfa 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1437,4 +1437,14 @@ declare module 'vscode' { //#endregion + //#region DocumentLink tooltip mjbvz + + interface DocumentLink { + /** + * The tooltip text when you hover over this link. + */ + tooltip?: string; + } + + // #endregion } diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 3371106e1a5..9a6f866362c 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -366,6 +366,14 @@ export class MainThreadCommentController { thread.dispose(); } + deleteCommentThreadMain(commentThreadId: string) { + this._threads.forEach(thread => { + if (thread.threadId === commentThreadId) { + this._proxy.$deleteCommentThread(this._handle, thread.commentThreadHandle); + } + }); + } + updateInput(input: string) { let thread = this.activeCommentThread; diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index af99b8a7720..62b7978fa29 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { IDisposable, IReference, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, IReference, dispose, Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; @@ -64,7 +64,7 @@ export class BoundModelReferenceCollection { } } -export class MainThreadDocuments implements MainThreadDocumentsShape { +export class MainThreadDocuments extends Disposable implements MainThreadDocumentsShape { private readonly _modelService: IModelService; private readonly _textModelResolverService: ITextModelService; @@ -73,7 +73,6 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { private readonly _untitledEditorService: IUntitledEditorService; private readonly _environmentService: IWorkbenchEnvironmentService; - private _toDispose: IDisposable[]; private _modelToDisposeMap: { [modelUrl: string]: IDisposable; }; private readonly _proxy: ExtHostDocumentsShape; private readonly _modelIsSynced: { [modelId: string]: boolean; }; @@ -90,6 +89,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { @IUntitledEditorService untitledEditorService: IUntitledEditorService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { + super(); this._modelService = modelService; this._textModelResolverService = textModelResolverService; this._textFileService = textFileService; @@ -100,23 +100,22 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocuments); this._modelIsSynced = {}; - this._toDispose = []; - this._toDispose.push(documentsAndEditors.onDocumentAdd(models => models.forEach(this._onModelAdded, this))); - this._toDispose.push(documentsAndEditors.onDocumentRemove(urls => urls.forEach(this._onModelRemoved, this))); - this._toDispose.push(this._modelReferenceCollection); - this._toDispose.push(modelService.onModelModeChanged(this._onModelModeChanged, this)); + this._register(documentsAndEditors.onDocumentAdd(models => models.forEach(this._onModelAdded, this))); + this._register(documentsAndEditors.onDocumentRemove(urls => urls.forEach(this._onModelRemoved, this))); + this._register(this._modelReferenceCollection); + this._register(modelService.onModelModeChanged(this._onModelModeChanged, this)); - this._toDispose.push(textFileService.models.onModelSaved(e => { + this._register(textFileService.models.onModelSaved(e => { if (this._shouldHandleFileEvent(e)) { this._proxy.$acceptModelSaved(e.resource); } })); - this._toDispose.push(textFileService.models.onModelReverted(e => { + this._register(textFileService.models.onModelReverted(e => { if (this._shouldHandleFileEvent(e)) { this._proxy.$acceptDirtyStateChanged(e.resource, false); } })); - this._toDispose.push(textFileService.models.onModelDirty(e => { + this._register(textFileService.models.onModelDirty(e => { if (this._shouldHandleFileEvent(e)) { this._proxy.$acceptDirtyStateChanged(e.resource, true); } @@ -130,7 +129,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { this._modelToDisposeMap[modelUrl].dispose(); }); this._modelToDisposeMap = Object.create(null); - this._toDispose = dispose(this._toDispose); + super.dispose(); } private _shouldHandleFileEvent(e: TextFileModelChangeEvent): boolean { diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index dda2fa8a663..5c8a9b248bb 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -176,11 +176,11 @@ class MainThreadDocumentAndEditorStateComputer { } private _onDidAddEditor(e: ICodeEditor): void { - this._toDisposeOnEditorRemove.set(e.getId(), combinedDisposable([ + this._toDisposeOnEditorRemove.set(e.getId(), combinedDisposable( e.onDidChangeModel(() => this._updateState()), e.onDidFocusEditorText(() => this._updateState()), e.onDidFocusEditorWidget(() => this._updateState(e)) - ])); + )); this._updateState(); } diff --git a/src/vs/workbench/api/browser/mainThreadProgress.ts b/src/vs/workbench/api/browser/mainThreadProgress.ts index 7c45dd4b1b5..e3b6422e891 100644 --- a/src/vs/workbench/api/browser/mainThreadProgress.ts +++ b/src/vs/workbench/api/browser/mainThreadProgress.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IProgress, IProgressService2, IProgressStep, ProgressLocation, IProgressOptions, IProgressNotificationOptions } from 'vs/platform/progress/common/progress'; +import { IProgress, IProgressService, IProgressStep, ProgressLocation, IProgressOptions, IProgressNotificationOptions } from 'vs/platform/progress/common/progress'; import { MainThreadProgressShape, MainContext, IExtHostContext, ExtHostProgressShape, ExtHostContext } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { Action } from 'vs/base/common/actions'; @@ -22,13 +22,13 @@ class ManageExtensionAction extends Action { @extHostNamedCustomer(MainContext.MainThreadProgress) export class MainThreadProgress implements MainThreadProgressShape { - private readonly _progressService: IProgressService2; + private readonly _progressService: IProgressService; private _progress = new Map void, progress: IProgress }>(); private readonly _proxy: ExtHostProgressShape; constructor( extHostContext: IExtHostContext, - @IProgressService2 progressService: IProgressService2, + @IProgressService progressService: IProgressService, @ICommandService private readonly _commandService: ICommandService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostProgress); diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 76bed28b12e..8048a97d703 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -28,7 +28,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ISaveParticipant, SaveReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; @@ -362,7 +362,7 @@ export class SaveParticipant implements ISaveParticipant { constructor( extHostContext: IExtHostContext, @IInstantiationService instantiationService: IInstantiationService, - @IProgressService2 private readonly _progressService: IProgressService2, + @IProgressService private readonly _progressService: IProgressService, @ILogService private readonly _logService: ILogService ) { this._saveParticipants = new IdleValue(() => [ diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 5044849a7a8..ed5dec5a66f 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -996,6 +996,7 @@ export interface LinkDto { cacheId?: ChainedCacheId; range: IRange; url?: string | UriComponents; + tooltip?: string; } export interface CodeLensDto extends ObjectIdentifier { @@ -1210,6 +1211,7 @@ export interface ExtHostCommentsShape { $createNewCommentThread(handle: number, document: UriComponents, range: IRange, text: string): Promise; $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange): void; $onCommentWidgetInputChange(commentControllerHandle: number, document: UriComponents, range: IRange, input: string | undefined): Promise; + $deleteCommentThread(commentControllerHandle: number, commentThreadHandle: number): void; $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise; $checkStaticContribution(commentControllerHandle: number): Promise; $provideReactionGroup(commentControllerHandle: number): Promise; diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index fb783e83775..9276ca5f685 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -173,6 +173,14 @@ export class ExtHostComments implements ExtHostCommentsShape { return Promise.resolve(commentControllerHandle); } + $deleteCommentThread(commentControllerHandle: number, commentThreadHandle: number) { + const commentController = this._commentControllers.get(commentControllerHandle); + + if (commentController) { + commentController.$deleteCommentThread(commentThreadHandle); + } + } + $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise { const commentController = this._commentControllers.get(commentControllerHandle); @@ -764,6 +772,16 @@ class ExtHostCommentController implements vscode.CommentController { return commentThread; } + $deleteCommentThread(threadHandle: number) { + let thread = this._threads.get(threadHandle); + + if (thread) { + thread.dispose(); + } + + this._threads.delete(threadHandle); + } + $onCommentWidgetInputChange(uriComponents: UriComponents, range: IRange, input: string) { if (!this.inputBox) { this.inputBox = new ExtHostCommentInputBox(this._proxy, this.handle, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), input); diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 41174822845..d3ed77cbf8d 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -30,6 +30,7 @@ import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; import { generateUuid } from 'vs/base/common/uuid'; import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { LRUCache } from 'vs/base/common/map'; +import { IURITransformer } from 'vs/base/common/uriIpc'; // --- adapter @@ -1036,15 +1037,11 @@ class AdapterData { ) { } } -export interface ISchemeTransformer { - transformOutgoing(scheme: string): string; -} - export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { private static _handlePool: number = 0; - private readonly _schemeTransformer: ISchemeTransformer | null; + private readonly _uriTransformer: IURITransformer | null; private _proxy: MainThreadLanguageFeaturesShape; private _documents: ExtHostDocuments; private _commands: ExtHostCommands; @@ -1056,14 +1053,14 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { constructor( mainContext: IMainContext, - schemeTransformer: ISchemeTransformer | null, + uriTransformer: IURITransformer | null, documents: ExtHostDocuments, commands: ExtHostCommands, heapMonitor: ExtHostHeapService, diagnostics: ExtHostDiagnostics, logService: ILogService ) { - this._schemeTransformer = schemeTransformer; + this._uriTransformer = uriTransformer; this._proxy = mainContext.getProxy(MainContext.MainThreadLanguageFeatures); this._documents = documents; this._commands = commands; @@ -1099,8 +1096,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { } private _transformScheme(scheme: string | undefined): string | undefined { - if (this._schemeTransformer && typeof scheme === 'string') { - return this._schemeTransformer.transformOutgoing(scheme); + if (this._uriTransformer && typeof scheme === 'string') { + return this._uriTransformer.transformOutgoingScheme(scheme); } return scheme; } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 2a499df5b16..49c911e2f9c 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -808,7 +808,8 @@ export namespace DocumentLink { export function from(link: vscode.DocumentLink): modes.ILink { return { range: Range.from(link.range), - url: link.target + url: link.target, + tooltip: link.tooltip }; } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index e9cc8187307..9c2bcd503a5 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1440,6 +1440,8 @@ export class DocumentLink { target?: URI; + tooltip?: string; + constructor(range: Range, target: URI | undefined) { if (target && !(target instanceof URI)) { throw illegalArgument('target'); diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index fc4983b446a..5f241ad0e54 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -21,7 +21,6 @@ namespace schema { export interface IUserFriendlyMenuItem { command: string; alt?: string; - precondition?: string; when?: string; group?: string; } @@ -79,10 +78,6 @@ namespace schema { collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'alt')); return false; } - if (item.precondition && typeof item.precondition !== 'string') { - collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'precondition')); - return false; - } if (item.when && typeof item.when !== 'string') { collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when')); return false; @@ -107,10 +102,6 @@ namespace schema { description: localize('vscode.extension.contributes.menuItem.alt', 'Identifier of an alternative command to execute. The command must be declared in the \'commands\'-section'), type: 'string' }, - precondition: { - description: localize('vscode.extension.contributes.menuItem.precondition', 'Condition which must be true to enable this item'), - type: 'string' - }, when: { description: localize('vscode.extension.contributes.menuItem.when', 'Condition which must be true to show this item'), type: 'string' @@ -224,6 +215,7 @@ namespace schema { export interface IUserFriendlyCommand { command: string; title: string | ILocalizedString; + enablement?: string; category?: string | ILocalizedString; icon?: IUserFriendlyIcon; } @@ -242,6 +234,10 @@ namespace schema { if (!isValidLocalizedString(command.title, collector, 'title')) { return false; } + if (command.enablement && typeof command.enablement !== 'string') { + collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'precondition')); + return false; + } if (command.category && !isValidLocalizedString(command.category, collector, 'category')) { return false; } @@ -295,6 +291,10 @@ namespace schema { description: localize('vscode.extension.contributes.commandType.category', '(Optional) Category string by the command is grouped in the UI'), type: 'string' }, + enablement: { + description: localize('vscode.extension.contributes.commandType.precondition', '(Optional) Condition which must be true to enable the command'), + type: 'string' + }, icon: { description: localize('vscode.extension.contributes.commandType.icon', '(Optional) Icon which is used to represent the command in the UI. Either a file path or a themable configuration'), anyOf: [{ @@ -342,7 +342,7 @@ ExtensionsRegistry.registerExtensionPoint { const viewType = webview.state.viewType; if (viewType) { @@ -80,9 +80,9 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews reviveWebview: () => { throw new Error('not implemented'); } })); - lifecycleService.onBeforeShutdown(e => { + this._register(lifecycleService.onBeforeShutdown(e => { e.veto(this._onBeforeShutdown()); - }, this, this._toDispose); + }, this)); } public $createWebviewPanel( diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 6e21039b1a5..3d78d2af472 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -33,7 +34,7 @@ import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionS import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem'; import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService'; import { ExtHostHeapService } from 'vs/workbench/api/common/extHostHeapService'; -import { ExtHostLanguageFeatures, ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures'; +import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures'; import { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages'; import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; import { ExtHostMessageService } from 'vs/workbench/api/common/extHostMessageService'; @@ -65,6 +66,7 @@ import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; import { withNullAsUndefined } from 'vs/base/common/types'; import { values } from 'vs/base/common/collections'; import { Schemas } from 'vs/base/common/network'; +import { IURITransformer } from 'vs/base/common/uriIpc'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -89,8 +91,7 @@ export function createApiFactory( extensionService: ExtHostExtensionService, extHostLogService: ExtHostLogService, extHostStorage: ExtHostStorage, - schemeTransformer: ISchemeTransformer | null, - outputChannelName: string + uriTransformer: IURITransformer | null ): IExtensionApiFactory { // Addressable instances @@ -109,7 +110,7 @@ export function createApiFactory( rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol)); - const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, schemeTransformer, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics, extHostLogService)); + const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics, extHostLogService)); const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostDocumentsAndEditors)); const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); @@ -117,7 +118,7 @@ export function createApiFactory( const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace, extensionService, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService, extHostCommands)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); - const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol, schemeTransformer, extHostLogService)); + const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol, uriTransformer, extHostLogService)); const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService)); const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); @@ -149,6 +150,7 @@ export function createApiFactory( const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments); // Register an output channel for exthost log + const outputChannelName = initData.remoteAuthority ? nls.localize('remote extension host Log', "Remote Extension Host") : nls.localize('extension host Log', "Extension Host"); extHostOutputService.createOutputChannelFromLogFile(outputChannelName, extHostLogService.logFile); // Register API-ish commands diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 316bc670997..5f4bd8be095 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -30,10 +30,10 @@ import { IWorkspace } from 'vs/platform/workspace/common/workspace'; import { Schemas } from 'vs/base/common/network'; import { withNullAsUndefined } from 'vs/base/common/types'; import { VSBuffer } from 'vs/base/common/buffer'; -import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures'; import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths'; import { RemoteAuthorityResolverError, ExtensionExecutionContext } from 'vs/workbench/api/common/extHostTypes'; +import { IURITransformer } from 'vs/base/common/uriIpc'; interface ITestRunner { run(testsRoot: string, clb: (error: Error, failures?: number) => void): void; @@ -83,8 +83,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { extHostConfiguration: ExtHostConfiguration, environment: IEnvironment, extHostLogService: ExtHostLogService, - schemeTransformer: ISchemeTransformer | null, - outputChannelName: string + uriTransformer: IURITransformer | null ) { this._hostUtils = hostUtils; this._initData = initData; @@ -134,8 +133,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { this, this._extHostLogService, this._storage, - schemeTransformer, - outputChannelName + uriTransformer ); this._resolvers = Object.create(null); diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index 6ba3602f833..e06299a5a32 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -51,7 +51,7 @@ export const LogOutputChannelFactory = new class implements IOutputChannelFactor try { const outputDirPath = join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); const outputDir = await dirExists(outputDirPath).then(exists => exists ? exists : mkdirp(outputDirPath).then(() => true)).then(() => outputDirPath); - const fileName = `${this._namePool++}-${name}`; + const fileName = `${this._namePool++}-${name.replace(/[\\/:\*\?"<>\|]/g, '')}`; const file = URI.file(join(outputDir, `${fileName}.log`)); const appender = new OutputAppender(fileName, file.fsPath); return new ExtHostOutputChannelBackedByFile(name, appender, proxy); diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 2830a32d3d9..5f6be2d3e60 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -16,10 +16,7 @@ import { OutputChannel } from 'vs/workbench/services/search/node/ripgrepSearchUt import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; import * as vscode from 'vscode'; import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from '../common/extHost.protocol'; - -export interface ISchemeTransformer { - transformOutgoing(scheme: string): string; -} +import { IURITransformer } from 'vs/base/common/uriIpc'; export class ExtHostSearch implements ExtHostSearchShape { @@ -35,14 +32,14 @@ export class ExtHostSearch implements ExtHostSearchShape { private _fileSearchManager: FileSearchManager; - constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer | null, private _logService: ILogService, private _pfs = pfs) { + constructor(mainContext: IMainContext, private _uriTransformer: IURITransformer | null, private _logService: ILogService, private _pfs = pfs) { this._proxy = mainContext.getProxy(MainContext.MainThreadSearch); this._fileSearchManager = new FileSearchManager(); } private _transformScheme(scheme: string): string { - if (this._schemeTransformer) { - return this._schemeTransformer.transformOutgoing(scheme); + if (this._uriTransformer) { + return this._uriTransformer.transformOutgoingScheme(scheme); } return scheme; } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index adbbceb087c..d3497bb3aa8 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/compositepart'; import * as nls from 'vs/nls'; import { defaultGenerator } from 'vs/base/common/idGenerator'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; @@ -18,13 +18,13 @@ import { IAction } from 'vs/base/common/actions'; import { Part, IPartOptions } from 'vs/workbench/browser/part'; import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite'; import { IComposite } from 'vs/workbench/common/composite'; -import { ScopedProgressService } from 'vs/workbench/services/progress/browser/progressService'; +import { ScopedProgressService } from 'vs/workbench/services/progress/browser/localProgressService'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -50,7 +50,7 @@ export interface ICompositeTitleLabel { interface CompositeItem { composite: Composite; disposable: IDisposable; - progressService: IProgressService; + localProgressService: ILocalProgressService; } export abstract class CompositePart extends Part { @@ -171,17 +171,17 @@ export abstract class CompositePart extends Part { // Instantiate composite from registry otherwise const compositeDescriptor = this.registry.getComposite(id); if (compositeDescriptor) { - const progressService = this.instantiationService.createInstance(ScopedProgressService, this.progressBar, compositeDescriptor.id, isActive); - const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([IProgressService, progressService])); + const localProgressService = this.instantiationService.createInstance(ScopedProgressService, this.progressBar, compositeDescriptor.id, isActive); + const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([ILocalProgressService, localProgressService])); const composite = compositeDescriptor.instantiate(compositeInstantiationService); - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); // Remember as Instantiated - this.instantiatedCompositeItems.set(id, { composite, disposable: toDisposable(() => dispose(disposables)), progressService }); + this.instantiatedCompositeItems.set(id, { composite, disposable: disposables, localProgressService: localProgressService }); // Register to title area update events from the composite - composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId()), this, disposables); + disposables.push(composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId()), this)); return composite; } @@ -221,12 +221,6 @@ export abstract class CompositePart extends Part { this.mapCompositeToCompositeContainer[composite.getId()] = compositeContainer; } - // Report progress for slow loading composites (but only if we did not create the composites before already) - const compositeItem = this.instantiatedCompositeItems.get(composite.getId()); - if (compositeItem && !compositeContainer) { - compositeItem.progressService.showWhile(Promise.resolve(), this.layoutService.isRestored() ? 800 : 3200 /* less ugly initial startup */); - } - // Fill Content and Actions // Make sure that the user meanwhile did not open another composite or closed the part containing the composite if (!this.activeComposite || composite.getId() !== this.activeComposite.getId()) { @@ -462,10 +456,10 @@ export abstract class CompositePart extends Part { return contentContainer; } - getProgressIndicator(id: string): IProgressService | null { + getProgressIndicator(id: string): ILocalProgressService | null { const compositeItem = this.instantiatedCompositeItems.get(id); - return compositeItem ? compositeItem.progressService : null; + return compositeItem ? compositeItem.localProgressService : null; } protected getActions(): IAction[] { diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index e4d4e9694a1..b8a8325af39 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -15,11 +15,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ResourceViewerContext, ResourceViewer } from 'vs/workbench/browser/parts/editor/resourceViewer'; import { URI } from 'vs/base/common/uri'; import { Dimension, size, clearNode } from 'vs/base/browser/dom'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { CancellationToken } from 'vs/base/common/cancellation'; import { dispose } from 'vs/base/common/lifecycle'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IFileService } from 'vs/platform/files/common/files'; export interface IOpenCallbacks { openInternal: (input: EditorInput, options: EditorOptions) => Promise; @@ -48,7 +48,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { callbacks: IOpenCallbacks, telemetryService: ITelemetryService, themeService: IThemeService, - @ITextFileService private readonly textFileService: ITextFileService, + @IFileService private readonly fileService: IFileService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IStorageService storageService: IStorageService ) { @@ -89,7 +89,11 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { } // Render Input - this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, this.textFileService, this.binaryContainer, this.scrollbar, { + if (this.resourceViewerContext) { + this.resourceViewerContext.dispose(); + } + + this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, this.fileService, this.binaryContainer, this.scrollbar, { openInternalClb: () => this.handleOpenInternalCallback(input, options), openExternalClb: this.environmentService.configuration.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource), metadataClb: meta => this.handleMetadataChanged(meta) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index b11d2e59ccc..265f7b286b4 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -122,7 +122,7 @@ Registry.as(Extensions.Configuration).registerConfigurat 'breadcrumbs.enabled': { description: localize('enabled', "Enable/disable navigation breadcrumbs."), type: 'boolean', - default: false + default: true }, // 'breadcrumbs.useQuickPick': { // description: localize('useQuickPick', "Use quick pick instead of breadcrumb-pickers."), diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 5cd799b070a..7f09ad3f84a 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -384,14 +384,14 @@ export class BreadcrumbsControl { this._breadcrumbsPickerShowing = true; this._updateCkBreadcrumbsActive(); - return combinedDisposable([ + return combinedDisposable( picker, selectListener, focusListener, zoomListener, focusTracker, blurListener - ]); + ); }, getAnchor: () => { let maxInnerWidth = window.innerWidth - 8 /*a little less the full widget*/; diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 7b03b2a8de4..1f70301f300 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -11,7 +11,7 @@ import { IEditorRegistry, Extensions as EditorExtensions, IEditorDescriptor } fr import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; @@ -47,11 +47,11 @@ export class EditorControl extends Disposable { private groupView: IEditorGroupView, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IProgressService progressService: IProgressService + @ILocalProgressService localProgressService: ILocalProgressService ) { super(); - this.editorOperation = this._register(new LongRunningOperation(progressService)); + this.editorOperation = this._register(new LongRunningOperation(localProgressService)); } get activeControl(): IVisibleEditor | null { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 8f4d5491d6e..dce6b872cd8 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -20,8 +20,8 @@ import { Themable, EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BAC import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl'; -import { IProgressService } from 'vs/platform/progress/common/progress'; -import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; +import { LocalProgressService } from 'vs/workbench/services/progress/browser/localProgressService'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; @@ -178,7 +178,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element)); this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IContextKeyService, scopedContextKeyService], - [IProgressService, new ProgressService(this.progressBar)] + [ILocalProgressService, new LocalProgressService(this.progressBar)] )); // Context keys @@ -279,7 +279,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView { }); // Toolbar actions - const removeGroupAction = this._register(new Action(CLOSE_EDITOR_GROUP_COMMAND_ID, localize('closeGroupAction', "Close"), 'close-editor-group', true, () => { this.accessor.removeGroup(this); return Promise.resolve(true); })); + const removeGroupAction = this._register(new Action( + CLOSE_EDITOR_GROUP_COMMAND_ID, + localize('closeGroupAction', "Close"), + 'close-editor-group', + true, + () => { + this.accessor.removeGroup(this); + + return Promise.resolve(true); + })); + const keybinding = this.keybindingService.lookupKeybinding(removeGroupAction.id); containerToolbar.push(removeGroupAction, { icon: true, label: false, keybinding: keybinding ? keybinding.getLabel() : undefined }); } @@ -402,7 +412,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private async restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): Promise { if (this._group.count === 0) { - return Promise.resolve(); // nothing to show + return; // nothing to show } // Determine editor options @@ -415,7 +425,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const activeEditor = this._group.activeEditor; if (!activeEditor) { - return Promise.resolve(); + return; } options.pinned = this._group.isPinned(activeEditor); // preserve pinned state @@ -1102,7 +1112,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private async handleDirty(editors: EditorInput[]): Promise { if (!editors.length) { - return Promise.resolve(false); // no veto + return false; // no veto } const editor = editors.shift()!; @@ -1135,7 +1145,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.accessor.groups.some(groupView => groupView !== this && groupView.group.contains(editor, true /* support side by side */)) || // editor is opened in other group editor instanceof SideBySideEditorInput && this.isOpened(editor.master) // side by side editor master is still opened ) { - return Promise.resolve(false); + return false; } // Switch to editor that we want to handle and confirm to save/revert diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 9c89adfcc22..4d7e0baf088 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -8,14 +8,14 @@ import * as nls from 'vs/nls'; import { $, append, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { format } from 'vs/base/common/strings'; import { extname, basename } from 'vs/base/common/resources'; -import { areFunctions, withNullAsUndefined } from 'vs/base/common/types'; +import { areFunctions, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { Action } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor'; -import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence } from 'vs/editor/common/model'; @@ -316,7 +316,7 @@ export class EditorStatus implements IStatusbarItem { private eolElement: StatusBarItem; private modeElement: StatusBarItem; private metadataElement: StatusBarItem; - private toDispose: IDisposable[]; + private readonly toDispose = new DisposableStore(); private activeEditorListeners: IDisposable[]; private delayedRender: IDisposable | null; private toRender: StateChange | null; @@ -333,7 +333,6 @@ export class EditorStatus implements IStatusbarItem { @INotificationService private readonly notificationService: INotificationService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService ) { - this.toDispose = []; this.activeEditorListeners = []; this.state = new State(); } @@ -385,20 +384,18 @@ export class EditorStatus implements IStatusbarItem { this.delayedRender = null; this.toRender = null; - this.toDispose.push( - toDisposable(() => { - if (this.delayedRender) { - this.delayedRender.dispose(); - this.delayedRender = null; - } - }), - this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()), - this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)), - this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)), - TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()), - ); + this.toDispose.push(toDisposable(() => { + if (this.delayedRender) { + this.delayedRender.dispose(); + this.delayedRender = null; + } + })); + this.toDispose.push(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar())); + this.toDispose.push(this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r))); + this.toDispose.push(this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange((e.resource)))); + this.toDispose.push(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange())); - return combinedDisposable(this.toDispose); + return this.toDispose; } private updateState(update: StateDelta): void { @@ -896,7 +893,7 @@ export class ChangeModeAction extends Action { // Compute mode let currentModeId: string | undefined; - let modeId: string; + let modeId: string | undefined; if (textModel) { modeId = textModel.getLanguageIdentifier().language; currentModeId = this.modeService.getLanguageName(modeId) || undefined; @@ -936,9 +933,9 @@ export class ChangeModeAction extends Action { } // Offer action to configure via settings - let configureModeAssociations: IQuickPickItem; - let configureModeSettings: IQuickPickItem; - let galleryAction: Action; + let configureModeAssociations: IQuickPickItem | undefined; + let configureModeSettings: IQuickPickItem | undefined; + let galleryAction: Action | undefined; if (hasLanguageSupport && resource) { const ext = extname(resource) || basename(resource); @@ -962,56 +959,55 @@ export class ChangeModeAction extends Action { picks.unshift(autoDetectMode); } - return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => { - if (!pick) { - return; + const pick = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }); + if (!pick) { + return; + } + + if (pick === galleryAction) { + galleryAction.run(); + return; + } + + // User decided to permanently configure associations, return right after + if (pick === configureModeAssociations) { + if (resource) { + this.configureFileAssociation(resource); } + return; + } - if (pick === galleryAction) { - galleryAction.run(); - return; - } + // User decided to configure settings for current language + if (pick === configureModeSettings) { + this.preferencesService.configureSettingsForLanguage(withUndefinedAsNull(modeId)); + return; + } - // User decided to permanently configure associations, return right after - if (pick === configureModeAssociations) { - if (resource) { - this.configureFileAssociation(resource); - } - return; - } + // Change mode for active editor + const activeEditor = this.editorService.activeEditor; + if (activeEditor) { + const modeSupport = toEditorWithModeSupport(activeEditor); + if (modeSupport) { - // User decided to configure settings for current language - if (pick === configureModeSettings) { - this.preferencesService.configureSettingsForLanguage(modeId); - return; - } - - // Change mode for active editor - const activeEditor = this.editorService.activeEditor; - if (activeEditor) { - const modeSupport = toEditorWithModeSupport(activeEditor); - if (modeSupport) { - - // Find mode - let languageSelection: ILanguageSelection | undefined; - if (pick === autoDetectMode) { - if (textModel) { - const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); - if (resource) { - languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1)); - } + // Find mode + let languageSelection: ILanguageSelection | undefined; + if (pick === autoDetectMode) { + if (textModel) { + const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); + if (resource) { + languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1)); } - } else { - languageSelection = this.modeService.createByLanguageName(pick.label); } + } else { + languageSelection = this.modeService.createByLanguageName(pick.label); + } - // Change mode - if (typeof languageSelection !== 'undefined') { - modeSupport.setMode(languageSelection.languageIdentifier.language); - } + // Change mode + if (typeof languageSelection !== 'undefined') { + modeSupport.setMode(languageSelection.languageIdentifier.language); } } - }); + } } private configureFileAssociation(resource: URI): void { @@ -1174,7 +1170,7 @@ export class ChangeEncodingAction extends Action { super(actionId, actionLabel); } - run(): Promise { + async run(): Promise { if (!getCodeEditor(this.editorService.activeTextEditorWidget)) { return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); } @@ -1199,93 +1195,88 @@ export class ChangeEncodingAction extends Action { reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding"), detail: 'Reopen with Encoding' }; } - let pickActionPromise: Promise; + let action: IQuickPickItem; if (encodingSupport instanceof UntitledEditorInput) { - pickActionPromise = Promise.resolve(saveWithEncodingPick); + action = saveWithEncodingPick; } else if (!isWritableBaseEditor(activeControl)) { - pickActionPromise = Promise.resolve(reopenWithEncodingPick); + action = reopenWithEncodingPick; } else { - pickActionPromise = this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); + action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); } - return pickActionPromise.then(action => { - if (!action) { - return undefined; - } + if (!action) { + return; + } - const resource = toResource(activeControl!.input, { supportSideBySide: SideBySideEditor.MASTER }); + await timeout(50); // quick open is sensitive to being opened so soon after another - return timeout(50 /* quick open is sensitive to being opened so soon after another */) - .then(() => { - if (!resource || !this.fileService.canHandleResource(resource)) { - return Promise.resolve(null); // encoding detection only possible for resources the file service can handle - } + const resource = toResource(activeControl!.input, { supportSideBySide: SideBySideEditor.MASTER }); + if (!resource || !this.fileService.canHandleResource(resource)) { + return null; // encoding detection only possible for resources the file service can handle + } - return this.textFileService.read(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null); - }) - .then((guessedEncoding: string) => { - const isReopenWithEncoding = (action === reopenWithEncodingPick); + const content = await this.textFileService.read(resource, { autoGuessEncoding: true, acceptTextOnly: true }); + const guessedEncoding = content.encoding; - const configuredEncoding = this.textResourceConfigurationService.getValue(withNullAsUndefined(resource), 'files.encoding'); + const isReopenWithEncoding = (action === reopenWithEncodingPick); - let directMatchIndex: number | undefined; - let aliasMatchIndex: number | undefined; + const configuredEncoding = this.textResourceConfigurationService.getValue(withNullAsUndefined(resource), 'files.encoding'); - // All encodings are valid picks - const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS) - .sort((k1, k2) => { - if (k1 === configuredEncoding) { - return -1; - } else if (k2 === configuredEncoding) { - return 1; - } + let directMatchIndex: number | undefined; + let aliasMatchIndex: number | undefined; - return SUPPORTED_ENCODINGS[k1].order - SUPPORTED_ENCODINGS[k2].order; - }) - .filter(k => { - if (k === guessedEncoding && guessedEncoding !== configuredEncoding) { - return false; // do not show encoding if it is the guessed encoding that does not match the configured - } + // All encodings are valid picks + const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS) + .sort((k1, k2) => { + if (k1 === configuredEncoding) { + return -1; + } else if (k2 === configuredEncoding) { + return 1; + } - return !isReopenWithEncoding || !SUPPORTED_ENCODINGS[k].encodeOnly; // hide those that can only be used for encoding if we are about to decode - }) - .map((key, index) => { - if (key === encodingSupport.getEncoding()) { - directMatchIndex = index; - } else if (SUPPORTED_ENCODINGS[key].alias === encodingSupport.getEncoding()) { - aliasMatchIndex = index; - } + return SUPPORTED_ENCODINGS[k1].order - SUPPORTED_ENCODINGS[k2].order; + }) + .filter(k => { + if (k === guessedEncoding && guessedEncoding !== configuredEncoding) { + return false; // do not show encoding if it is the guessed encoding that does not match the configured + } - return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key }; - }); + return !isReopenWithEncoding || !SUPPORTED_ENCODINGS[k].encodeOnly; // hide those that can only be used for encoding if we are about to decode + }) + .map((key, index) => { + if (key === encodingSupport.getEncoding()) { + directMatchIndex = index; + } else if (SUPPORTED_ENCODINGS[key].alias === encodingSupport.getEncoding()) { + aliasMatchIndex = index; + } - const items = picks.slice() as IQuickPickItem[]; + return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key }; + }); - // If we have a guessed encoding, show it first unless it matches the configured encoding - if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) { - picks.unshift({ type: 'separator' }); - picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") }); - } + const items = picks.slice() as IQuickPickItem[]; - return this.quickInputService.pick(picks, { - placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"), - activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1] - }).then(encoding => { - if (!encoding) { - return; - } + // If we have a guessed encoding, show it first unless it matches the configured encoding + if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) { + picks.unshift({ type: 'separator' }); + picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") }); + } - const activeControl = this.editorService.activeControl; - if (!activeControl) { - return; - } - - const encodingSupport = toEditorWithEncodingSupport(activeControl.input); - if (typeof encoding.id !== 'undefined' && encodingSupport && encodingSupport.getEncoding() !== encoding.id) { - encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding - } - }); - }); + const encoding = await this.quickInputService.pick(picks, { + placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"), + activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1] }); + + if (!encoding) { + return; + } + + if (!this.editorService.activeControl) { + return; + } + + const activeEncodingSupport = toEditorWithEncodingSupport(this.editorService.activeControl.input); + if (typeof encoding.id !== 'undefined' && activeEncodingSupport && activeEncodingSupport.getEncoding() !== encoding.id) { + activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding + } } } diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 885211a42be..50995662333 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -47,7 +47,7 @@ export class NoTabsTitleControl extends TitleControl { // Breadcrumbs this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent }); toggleClass(this.titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl)); - this.toDispose.push({ dispose: () => removeClass(this.titleContainer, 'breadcrumbs') }); // import to remove because the container is a shared dom node + this._register({ dispose: () => removeClass(this.titleContainer, 'breadcrumbs') }); // import to remove because the container is a shared dom node // Right Actions Container const actionsContainer = document.createElement('div'); diff --git a/src/vs/workbench/browser/parts/editor/resourceViewer.ts b/src/vs/workbench/browser/parts/editor/resourceViewer.ts index 131cf402e57..08a3903f270 100644 --- a/src/vs/workbench/browser/parts/editor/resourceViewer.ts +++ b/src/vs/workbench/browser/parts/editor/resourceViewer.ts @@ -15,13 +15,13 @@ import { clamp } from 'vs/base/common/numbers'; import { Themable } from 'vs/workbench/common/theme'; import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Action } from 'vs/base/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { memoize } from 'vs/base/common/decorators'; import * as platform from 'vs/base/common/platform'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IFileService } from 'vs/platform/files/common/files'; export interface IResourceDescriptor { readonly resource: URI; @@ -78,7 +78,7 @@ export class ResourceViewer { static show( descriptor: IResourceDescriptor, - textFileService: ITextFileService, + fileService: IFileService, container: HTMLElement, scrollbar: DomScrollableElement, delegate: ResourceViewerDelegate @@ -89,7 +89,7 @@ export class ResourceViewer { // Images if (ResourceViewer.isImageResource(descriptor)) { - return ImageView.create(container, descriptor, textFileService, scrollbar, delegate); + return ImageView.create(container, descriptor, fileService, scrollbar, delegate); } // Large Files @@ -118,12 +118,12 @@ class ImageView { static create( container: HTMLElement, descriptor: IResourceDescriptor, - textFileService: ITextFileService, + fileService: IFileService, scrollbar: DomScrollableElement, delegate: ResourceViewerDelegate ): ResourceViewerContext { if (ImageView.shouldShowImageInline(descriptor)) { - return InlineImageView.create(container, descriptor, textFileService, scrollbar, delegate); + return InlineImageView.create(container, descriptor, fileService, scrollbar, delegate); } return LargeImageView.create(container, descriptor, delegate); @@ -160,7 +160,7 @@ class LargeImageView { DOM.clearNode(container); - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const label = document.createElement('p'); label.textContent = nls.localize('largeImageError', "The image is not displayed in the editor because it is too large ({0}).", size); @@ -175,7 +175,7 @@ class LargeImageView { disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => openExternal(descriptor.resource))); } - return combinedDisposable(disposables); + return disposables; } } @@ -212,7 +212,7 @@ class FileSeemsBinaryFileView { DOM.clearNode(container); - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const label = document.createElement('p'); label.textContent = nls.localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding."); @@ -228,7 +228,7 @@ class FileSeemsBinaryFileView { scrollbar.scanDomNode(); - return combinedDisposable(disposables); + return disposables; } } @@ -359,15 +359,15 @@ class InlineImageView { static create( container: HTMLElement, descriptor: IResourceDescriptor, - textFileService: ITextFileService, + fileService: IFileService, scrollbar: DomScrollableElement, delegate: ResourceViewerDelegate ) { - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const context: ResourceViewerContext = { layout(dimension: DOM.Dimension) { }, - dispose: () => combinedDisposable(disposables).dispose() + dispose: () => disposables.dispose() }; const cacheKey = `${descriptor.resource.toString()}:${descriptor.etag}`; @@ -557,25 +557,29 @@ class InlineImageView { } })); - InlineImageView.imageSrc(descriptor, textFileService).then(dataUri => { - const imgs = container.getElementsByTagName('img'); - if (imgs.length) { - imgs[0].src = dataUri; + InlineImageView.imageSrc(descriptor, fileService).then(src => { + const img = container.querySelector('img'); + if (img) { + if (typeof src === 'string') { + img.src = src; + } else { + const url = URL.createObjectURL(src); + disposables.push(toDisposable(() => URL.revokeObjectURL(url))); + img.src = url; + } } }); return context; } - private static async imageSrc(descriptor: IResourceDescriptor, textFileService: ITextFileService): Promise { + private static async imageSrc(descriptor: IResourceDescriptor, fileService: IFileService): Promise { if (descriptor.resource.scheme === Schemas.data) { - return Promise.resolve(descriptor.resource.toString(true /* skip encoding */)); + return descriptor.resource.toString(true /* skip encoding */); } - const data = await textFileService.read(descriptor.resource, { encoding: 'base64' }); - const mime = getMime(descriptor); - - return `data:${mime};base64,${data.value}`; + const { value } = await fileService.readFile(descriptor.resource); + return new Blob([value.buffer], { type: getMime(descriptor) }); } } diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 639c4ada7d8..99f284eb2dc 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -169,7 +169,7 @@ export class SideBySideEditor extends BaseEditor { } if (!this.detailsEditor || !this.masterEditor) { - return Promise.resolve(); + return; } await Promise.all([ diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 919151455e1..7b516cd7077 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -20,7 +20,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; @@ -454,13 +454,13 @@ export class TabsTitleControl extends TitleControl { // Eventing const eventsDisposable = this.registerTabListeners(tabContainer, index); - this.tabDisposeables.push(combinedDisposable([eventsDisposable, tabActionBar, tabActionRunner, editorLabel])); + this.tabDisposeables.push(combinedDisposable(eventsDisposable, tabActionBar, tabActionRunner, editorLabel)); return tabContainer; } private registerTabListeners(tab: HTMLElement, index: number): IDisposable { - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const handleClickOrTouch = (e: MouseEvent | GestureEvent): void => { tab.blur(); @@ -669,7 +669,7 @@ export class TabsTitleControl extends TitleControl { } })); - return combinedDisposable(disposables); + return disposables; } private isSupportedDropTransfer(e: DragEvent): boolean { diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 4a428b5cea0..d4b3e4adef9 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -70,7 +70,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { // Assert Model instance if (!(resolvedModel instanceof BaseTextEditorModel)) { - return Promise.reject(new Error('Unable to open file as text')); + throw new Error('Unable to open file as text'); } // Set Editor Model diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index c44249707e9..e8a82735246 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -523,7 +523,7 @@ export class QuickOpenController extends Component implements IQuickOpenService const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(placeHolderLabel)], this.actionProvider); this.showModel(model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); - return Promise.resolve(undefined); + return; } // Support extra class from handler @@ -579,8 +579,8 @@ export class QuickOpenController extends Component implements IQuickOpenService return mapEntryToPath; } - private resolveHandler(handler: QuickOpenHandlerDescriptor): Promise { - let result = this._resolveHandler(handler); + private async resolveHandler(handler: QuickOpenHandlerDescriptor): Promise { + let result = this.doResolveHandler(handler); const id = handler.getId(); if (!this.handlerOnOpenCalled[id]) { @@ -594,14 +594,16 @@ export class QuickOpenController extends Component implements IQuickOpenService }); } - return result.then(null, (error) => { + try { + return await result; + } catch (error) { delete this.mapResolvedHandlersToPrefix[id]; - return Promise.reject(new Error(`Unable to instantiate quick open handler ${handler.getId()}: ${JSON.stringify(error)}`)); - }); + throw new Error(`Unable to instantiate quick open handler ${handler.getId()}: ${JSON.stringify(error)}`); + } } - private _resolveHandler(handler: QuickOpenHandlerDescriptor): Promise { + private doResolveHandler(handler: QuickOpenHandlerDescriptor): Promise { const id = handler.getId(); // Return Cached diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index ffbee03d079..1831daad7f6 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -191,7 +191,7 @@ export class SidebarPart extends CompositePart implements IViewletServi async openViewlet(id: string | undefined, focus?: boolean): Promise { if (typeof id === 'string' && this.getViewlet(id)) { - return Promise.resolve(this.doOpenViewlet(id, focus)); + return this.doOpenViewlet(id, focus); } await this.extensionService.whenInstalledExtensionsRegistered(); diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index de4b7c1512b..db11dfab1ea 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -17,7 +17,7 @@ import { IViewsService, ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeVie import { IViewletViewOptions, FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IProgressService2 } from 'vs/platform/progress/common/progress'; +import { IProgressService } from 'vs/platform/progress/common/progress'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -216,7 +216,7 @@ export class CustomTreeView extends Disposable implements ITreeView { @IInstantiationService private readonly instantiationService: IInstantiationService, @ICommandService private readonly commandService: ICommandService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IProgressService2 private readonly progressService: IProgressService2 + @IProgressService private readonly progressService: IProgressService ) { super(); this.root = new Root(); diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index 10e2fd42d31..ec1b456a8a1 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -355,7 +355,7 @@ export class PanelViewlet extends Viewlet { headerBorder: SIDE_BAR_SECTION_HEADER_BORDER, dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND }, panel); - const disposable = combinedDisposable([onDidFocus, onDidChangeTitleArea, panelStyler, onDidChange]); + const disposable = combinedDisposable(onDidFocus, onDidChangeTitleArea, panelStyler, onDidChange); const panelItem: IViewletPanelItem = { panel, disposable }; this.panelItems.splice(index, 0, panelItem); diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 679a4c15742..8dfe43f5871 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -207,7 +207,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView this.viewsModel.setCollapsed(viewDescriptor.id, collapsed); }); - this.viewDisposables.splice(index, 0, combinedDisposable([contextMenuDisposable, collapseDisposable])); + this.viewDisposables.splice(index, 0, combinedDisposable(contextMenuDisposable, collapseDisposable)); panelsToAdd.push({ panel, size: size || panel.minimumSize, index }); } diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index 840adf3a830..aa86b9fd6e3 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -20,7 +20,6 @@ import { IExtensionHostDebugParams, IDebugParams } from 'vs/platform/environment import { IExtensionGalleryService, IQueryOptions, IGalleryExtension, InstallOperation, StatisticType, ITranslation, IGalleryExtensionVersion, IExtensionIdentifier, IReportedExtension, IExtensionManagementService, ILocalExtension, IGalleryMetadata, IExtensionTipsService, ExtensionRecommendationReason, IExtensionRecommendation, IExtensionEnablementService, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IPager } from 'vs/base/common/paging'; import { IExtensionManifest, ExtensionType, ExtensionIdentifier, IExtension } from 'vs/platform/extensions/common/extensions'; -import { NullExtensionService, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -59,6 +58,8 @@ import { Color, RGBA } from 'vs/base/common/color'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; +import { IReloadSessionEvent, IExtensionHostDebugService, ICloseSessionEvent, IAttachSessionEvent, ILogToSessionEvent, ITerminateSessionEvent } from 'vs/workbench/services/extensions/common/extensionHostDebug'; +import { IRemoteConsoleLog } from 'vs/base/common/console'; export const workspaceResource = URI.file((self).USER_HOME_DIR || '/').with({ scheme: Schemas.vscodeRemote, @@ -257,7 +258,10 @@ export class SimpleWorkbenchEnvironmentService implements IWorkbenchEnvironmentS extensionsPath: string; extensionDevelopmentLocationURI?: URI[]; extensionTestsPath?: string; - debugExtensionHost: IExtensionHostDebugParams; + debugExtensionHost: IExtensionHostDebugParams = { + port: null, + break: false + }; debugSearch: IDebugParams; logExtensionHostCommunication: boolean; isBuilt: boolean; @@ -360,10 +364,10 @@ export class SimpleExtensionEnablementService implements IExtensionEnablementSer readonly onEnablementChanged = Event.None; - readonly allUserExtensionsDisabled = true; + readonly allUserExtensionsDisabled = false; getEnablementState(extension: IExtension): EnablementState { - return EnablementState.Disabled; + return EnablementState.Enabled; } canChangeEnablement(extension: IExtension): boolean { @@ -375,7 +379,7 @@ export class SimpleExtensionEnablementService implements IExtensionEnablementSer } isEnabled(extension: IExtension): boolean { - return false; + return true; } } @@ -479,14 +483,6 @@ registerSingleton(IExtensionManagementService, SimpleExtensionManagementService) //#endregion -//#region Extensions - -export class SimpleExtensionService extends NullExtensionService { } - -registerSingleton(IExtensionService, SimpleExtensionService); - -//#endregion - //#region Extension URL Handler export const IExtensionUrlHandler = createDecorator('inactiveExtensionUrlHandler'); @@ -659,9 +655,12 @@ export class SimpleProductService implements IProductService { _serviceBrand: any; - version?: string; + version: string = '0.0.0'; commit?: string; - + nameLong: string = ''; + urlProtocol: string = ''; + extensionAllowedProposedApi: string[] = []; + uiExtensions?: string[]; enableTelemetry: boolean = false; } @@ -923,7 +922,7 @@ export class SimpleWindowConfiguration implements IWindowConfiguration { workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; - remoteAuthority?: string; + remoteAuthority: string = document.location.host; zoomLevel?: number; fullscreen?: boolean; @@ -1071,6 +1070,30 @@ registerSingleton(IWindowService, SimpleWindowService); //#endregion +//#region ExtensionHostDebugService + +export class SimpleExtensionHostDebugService implements IExtensionHostDebugService { + _serviceBrand: any; + + reload(sessionId: string): void { } + onReload: Event = Event.None; + + close(sessionId: string): void { } + onClose: Event = Event.None; + + attachSession(sessionId: string, port: number, subId?: string): void { } + onAttachSession: Event = Event.None; + + logToSession(sessionId: string, log: IRemoteConsoleLog): void { } + onLogToSession: Event = Event.None; + + terminateSession(sessionId: string, subId?: string): void { } + onTerminateSession: Event = Event.None; +} +registerSingleton(IExtensionHostDebugService, SimpleExtensionHostDebugService); + +//#endregion + //#region Window export class SimpleWindowsService implements IWindowsService { diff --git a/src/vs/workbench/common/actions.ts b/src/vs/workbench/common/actions.ts index af3e7f2e497..aafca62c310 100644 --- a/src/vs/workbench/common/actions.ts +++ b/src/vs/workbench/common/actions.ts @@ -8,7 +8,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { SyncActionDescriptor, MenuRegistry, MenuId, ICommandAction } from 'vs/platform/actions/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -33,7 +33,7 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR } private registerWorkbenchCommandFromAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpr): IDisposable { - let registrations: IDisposable[] = []; + const registrations = new DisposableStore(); // command registrations.push(CommandsRegistry.registerCommand(descriptor.id, this.createCommandHandler(descriptor))); @@ -78,7 +78,7 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR // TODO@alex,joh // support removal of keybinding rule // support removal of command-ui - return combinedDisposable(registrations); + return registrations; } private createCommandHandler(descriptor: SyncActionDescriptor): ICommandHandler { diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index b6691ee28f3..6d652b4e8ca 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -90,7 +90,7 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport { ref.dispose(); this.modelReference = null; - return Promise.reject(new Error(`Unexpected model for ResourceInput: ${this.resource}`)); + throw new Error(`Unexpected model for ResourceInput: ${this.resource}`); } this.cachedModel = model; diff --git a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts index 72f669eda8a..75a4c0d4a08 100644 --- a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts @@ -6,24 +6,25 @@ import * as DOM from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; import { IAction } from 'vs/base/common/actions'; -import { Disposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { IMenu } from 'vs/platform/actions/common/actions'; import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -export class CommentFormActions extends Disposable { +export class CommentFormActions { private _buttonElements: HTMLElement[] = []; + private _toDispose = new DisposableStore(); constructor( private container: HTMLElement, private actionHandler: (action: IAction) => void, private themeService: IThemeService - ) { - super(); - } + ) { } setActions(menu: IMenu) { - dispose(this._toDispose); + this._toDispose.dispose(); + this._toDispose = new DisposableStore(); + this._buttonElements.forEach(b => DOM.removeNode(b)); const groups = menu.getActions({ shouldForwardArgs: true }); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 8d9cfdc2585..56c2a8667f9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -178,8 +178,8 @@ export class CommentNode extends Disposable { let commentMenus = this.commentService.getCommentMenus(this.owner); const menu = commentMenus.getCommentTitleActions(this.comment, this._contextKeyService); - this._toDispose.push(menu); - this._toDispose.push(menu.onDidChange(e => { + this._register(menu); + this._register(menu.onDidChange(e => { const contributedActions = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], []); this.toolbar.setActions(contributedActions); })); @@ -217,7 +217,7 @@ export class CommentNode extends Disposable { this.registerActionBarListeners(this._actionsToolbarContainer); this.toolbar.setActions(actions, [])(); - this._toDispose.push(this.toolbar); + this._register(this.toolbar); } } @@ -354,7 +354,7 @@ export class CommentNode extends Disposable { return this.actionViewItemProvider(action as Action); } }); - this._toDispose.push(this._reactionsActionBar); + this._register(this._reactionsActionBar); this.comment.commentReactions!.map(reaction => { let action = new ReactionAction(`reaction.${reaction.label}`, `${reaction.label}`, reaction.hasReacted && reaction.canEdit ? 'active' : '', reaction.canEdit, async () => { @@ -444,8 +444,8 @@ export class CommentNode extends Disposable { })); } - this._toDispose.push(this._commentEditor); - this._toDispose.push(this._commentEditorModel); + this._register(this._commentEditor); + this._register(this._commentEditorModel); } private removeCommentEditor() { @@ -561,8 +561,8 @@ export class CommentNode extends Disposable { const menus = this.commentService.getCommentMenus(this.owner); const menu = menus.getCommentActions(this.comment, this._contextKeyService); - this._toDispose.push(menu); - this._toDispose.push(menu.onDidChange(() => { + this._register(menu); + this._register(menu.onDidChange(() => { this._commentFormActions.setActions(menu); })); @@ -599,17 +599,17 @@ export class CommentNode extends Disposable { const cancelEditButton = new Button(formActions); cancelEditButton.label = nls.localize('label.cancel', "Cancel"); - this._toDispose.push(attachButtonStyler(cancelEditButton, this.themeService)); + this._register(attachButtonStyler(cancelEditButton, this.themeService)); - this._toDispose.push(cancelEditButton.onDidClick(_ => { + this._register(cancelEditButton.onDidClick(_ => { this.removeCommentEditor(); })); this._updateCommentButton = new Button(formActions); this._updateCommentButton.label = UPDATE_COMMENT_LABEL; - this._toDispose.push(attachButtonStyler(this._updateCommentButton, this.themeService)); + this._register(attachButtonStyler(this._updateCommentButton, this.themeService)); - this._toDispose.push(this._updateCommentButton.onDidClick(_ => { + this._register(this._updateCommentButton.onDidClick(_ => { this.editComment(); })); @@ -622,21 +622,21 @@ export class CommentNode extends Disposable { } private registerActionBarListeners(actionsContainer: HTMLElement): void { - this._toDispose.push(dom.addDisposableListener(this._domNode, 'mouseenter', () => { + this._register(dom.addDisposableListener(this._domNode, 'mouseenter', () => { actionsContainer.classList.remove('hidden'); })); - this._toDispose.push(dom.addDisposableListener(this._domNode, 'focus', () => { + this._register(dom.addDisposableListener(this._domNode, 'focus', () => { actionsContainer.classList.remove('hidden'); })); - this._toDispose.push(dom.addDisposableListener(this._domNode, 'mouseleave', () => { + this._register(dom.addDisposableListener(this._domNode, 'mouseleave', () => { if (!this._domNode.contains(document.activeElement)) { actionsContainer.classList.add('hidden'); } })); - this._toDispose.push(dom.addDisposableListener(this._domNode, 'focusout', (e: FocusEvent) => { + this._register(dom.addDisposableListener(this._domNode, 'focusout', (e: FocusEvent) => { if (!this._domNode.contains((e.relatedTarget))) { actionsContainer.classList.add('hidden'); @@ -709,8 +709,4 @@ export class CommentNode extends Disposable { }, 3000); } } - - dispose() { - this._toDispose.forEach(disposeable => disposeable.dispose()); - } } \ No newline at end of file diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 86ee098d6b2..4823e3aec3a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -52,6 +52,7 @@ export interface ICommentService { registerDataProvider(owner: string, commentProvider: DocumentCommentProvider): void; unregisterDataProvider(owner: string): void; updateComments(ownerId: string, event: CommentThreadChangedEvent): void; + disposeCommentThread(ownerId: string, threadId: string): void; createNewCommentThread(owner: string, resource: URI, range: Range, text: string): Promise; replyToCommentThread(owner: string, resource: URI, range: Range, thread: CommentThread, text: string): Promise; editComment(owner: string, resource: URI, comment: Comment, text: string): Promise; @@ -144,6 +145,13 @@ export class CommentService extends Disposable implements ICommentService { commentController.createCommentThreadTemplate(resource, range); } + disposeCommentThread(owner: string, threadId: string) { + let controller = this.getCommentController(owner); + if (controller) { + controller.deleteCommentThreadMain(threadId); + } + } + getCommentMenus(owner: string): CommentMenus { if (this._commentMenus.get(owner)) { return this._commentMenus.get(owner)!; diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index d9a4fb60eac..03dc6863c89 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -267,6 +267,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget if (deleteCommand) { return this.commandService.executeCommand(deleteCommand.id, ...(deleteCommand.arguments || [])); } else if (this._commentEditor.getValue() === '') { + this.commentService.disposeCommentThread(this._owner, this._commentThread.threadId!); this.dispose(); return Promise.resolve(); } diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 5a250925b81..b9c11d7fe1b 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -9,7 +9,7 @@ import { Expression, Variable, ExpressionContainer } from 'vs/workbench/contrib/ import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -127,7 +127,7 @@ export interface IExpressionTemplateData { value: HTMLSpanElement; inputBoxContainer: HTMLElement; enableInputBox(expression: IExpression, options: IInputBoxOptions): void; - toDispose: IDisposable[]; + readonly toDispose: IDisposable; label: HighlightedLabel; } @@ -148,7 +148,7 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { name.style.display = 'none'; @@ -180,7 +180,7 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer !style.match(new RegExp(`^code-${colorType}-(\\d+|custom)$`))); - if (newClass) { - styleNames.push(`code-${colorType}-${newClass}`); - } + function changeColor(colorType: 'foreground' | 'background', color?: RGBA | undefined): void { if (colorType === 'foreground') { - customFgColor = customColor; - } else { - customBgColor = customColor; + customFgColor = color; + } else if (colorType === 'background') { + customBgColor = color; + } + styleNames = styleNames.filter(style => style !== `code-${colorType}-colored`); + if (color !== undefined) { + styleNames.push(`code-${colorType}-colored`); } } @@ -137,22 +136,37 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML */ function setBasicFormatters(styleCodes: number[]): void { for (let code of styleCodes) { - if (code === 0) { - styleNames = []; - } else if (code === 1) { - styleNames.push('code-bold'); - } else if (code === 3) { - styleNames.push('code-italic'); - } else if (code === 4) { - styleNames.push('code-underline'); - } else if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97)) { - changeColor(code, 'foreground'); - } else if ((code >= 40 && code <= 47) || (code >= 100 && code <= 107)) { - changeColor(code, 'background'); - } else if (code === 39) { - changeColor(undefined, 'foreground'); - } else if (code === 49) { - changeColor(undefined, 'background'); + switch (code) { + case 0: { + styleNames = []; + customFgColor = undefined; + customBgColor = undefined; + break; + } + case 1: { + styleNames.push('code-bold'); + break; + } + case 3: { + styleNames.push('code-italic'); + break; + } + case 4: { + styleNames.push('code-underline'); + break; + } + case 39: { + changeColor('foreground', undefined); + break; + } + case 49: { + changeColor('background', undefined); + break; + } + default: { + setBasicColor(code); + break; + } } } } @@ -171,7 +185,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML styleCodes[3] >= 0 && styleCodes[3] <= 255 && styleCodes[4] >= 0 && styleCodes[4] <= 255) { const customColor = new RGBA(styleCodes[2], styleCodes[3], styleCodes[4]); - changeColor('custom', colorType, customColor); + changeColor(colorType, customColor); } } @@ -188,7 +202,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML const color = calcANSI8bitColor(colorNumber); if (color) { - changeColor('custom', colorType, color); + changeColor(colorType, color); } else if (colorNumber >= 0 && colorNumber <= 15) { // Need to map to one of the four basic color ranges (30-37, 90-97, 40-47, 100-107) colorNumber += 30; @@ -199,7 +213,43 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML if (colorType === 'background') { colorNumber += 10; } - changeColor(colorNumber, colorType); + setBasicColor(colorNumber); + } + } + + /** + * Calculate and set styling for basic bright and dark ANSI color codes. Uses + * theme colors if available. Automatically distinguishes between foreground + * and background colors; does not support color-clearing codes 39 and 49. + * @param styleCode Integer color code on one of the following ranges: + * [30-37, 90-97, 40-47, 100-107]. If not on one of these ranges, will do + * nothing. + */ + function setBasicColor(styleCode: number): void { + const theme = themeService.getTheme(); + let colorType: 'foreground' | 'background' | undefined; + let colorIndex: number | undefined; + + if (styleCode >= 30 && styleCode <= 37) { + colorIndex = styleCode - 30; + colorType = 'foreground'; + } else if (styleCode >= 90 && styleCode <= 97) { + colorIndex = (styleCode - 90) + 8; // High-intensity (bright) + colorType = 'foreground'; + } else if (styleCode >= 40 && styleCode <= 47) { + colorIndex = styleCode - 40; + colorType = 'background'; + } else if (styleCode >= 100 && styleCode <= 107) { + colorIndex = (styleCode - 100) + 8; // High-intensity (bright) + colorType = 'background'; + } + + if (colorIndex !== undefined && colorType) { + const colorName = ansiColorIdentifiers[colorIndex]; + const color = theme.getColor(colorName); + if (color) { + changeColor(colorType, color.rgba); + } } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index f07ca7f2219..0e160e29040 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -199,9 +199,9 @@ export class FocusSessionActionViewItem extends SelectActionViewItem { ) { super(null, action, [], -1, contextViewService, { ariaLabel: nls.localize('debugSession', 'Debug Session') }); - this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService)); + this._register(attachSelectBoxStyler(this.selectBox, themeService)); - this.toDispose.push(this.debugService.getViewModel().onDidFocusSession(() => { + this._register(this.debugService.getViewModel().onDidFocusSession(() => { const session = this.debugService.getViewModel().focusedSession; if (session) { const index = this.getSessions().indexOf(session); @@ -209,8 +209,8 @@ export class FocusSessionActionViewItem extends SelectActionViewItem { } })); - this.toDispose.push(this.debugService.onDidNewSession(() => this.update())); - this.toDispose.push(this.debugService.onDidEndSession(() => this.update())); + this._register(this.debugService.onDidNewSession(() => this.update())); + this._register(this.debugService.onDidEndSession(() => this.update())); this.update(); } diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 965861470ce..ff64d75d22b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -16,7 +16,7 @@ import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPosit import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDebugService, IExpression, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug'; import { Expression } from 'vs/workbench/contrib/debug/common/debugModel'; -import { renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderExpressionValue, replaceWhitespace } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -237,7 +237,7 @@ export class DebugHoverWidget implements IContentWidget { this.complexValueContainer.hidden = false; return this.tree.setInput(expression).then(() => { - this.complexValueTitle.textContent = expression.value; + this.complexValueTitle.textContent = replaceWhitespace(expression.value); this.complexValueTitle.title = expression.value; this.layoutTreeAndContainer(); this.editor.layoutContentWidget(this); diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 84a184174ff..74ab3b5584b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -81,7 +81,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { const actionBarContainer = dom.append(this.$el, dom.$('div.action-bar-container')); this.debugToolBarMenu = menuService.createMenu(MenuId.DebugToolBar, contextKeyService); - this.toDispose.push(this.debugToolBarMenu); + this._register(this.debugToolBarMenu); this.activeActions = []; this.actionBar = this._register(new ActionBar(actionBarContainer, { diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 2f2d7c2ea09..09f06f523ce 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -14,7 +14,7 @@ import { StartAction, ConfigureAction, SelectAndStartAction, FocusSessionAction import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; +import { IProgressService } from 'vs/platform/progress/common/progress'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -37,7 +37,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; export class DebugViewlet extends ViewContainerViewlet { private startDebugActionViewItem: StartDebugActionViewItem; - private progressRunner: IProgressRunner; + private progressResolve: (() => void) | undefined; private breakpointView: ViewletPanel; private panelListeners = new Map(); private debugToolBarMenu: IMenu; @@ -112,7 +112,7 @@ export class DebugViewlet extends ViewContainerViewlet { if (!this.debugToolBarMenu) { this.debugToolBarMenu = this.menuService.createMenu(MenuId.DebugToolBar, this.contextKeyService); - this.toDispose.push(this.debugToolBarMenu); + this._register(this.debugToolBarMenu); } return DebugToolBar.getActions(this.debugToolBarMenu, this.debugService, this.instantiationService); } @@ -153,12 +153,15 @@ export class DebugViewlet extends ViewContainerViewlet { } private onDebugServiceStateChange(state: State): void { - if (this.progressRunner) { - this.progressRunner.done(); + if (this.progressResolve) { + this.progressResolve(); + this.progressResolve = undefined; } if (state === State.Initializing) { - this.progressRunner = this.progressService.show(true); + this.progressService.withProgress({ location: VIEWLET_ID }, _progress => { + return new Promise(resolve => this.progressResolve = resolve); + }); } this.updateToolBar(); diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css index f08164c7538..a78dd13c2d8 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css @@ -18,6 +18,5 @@ .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .inputContainer { flex: 1; - margin-top: 6px; - margin-bottom: 6px; + margin-top: 8px; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index f5e8961e09f..a8bc115e9c9 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -142,6 +142,7 @@ overflow: hidden; text-overflow: ellipsis; font-family: var(--monaco-monospace-font); + white-space: pre; } .monaco-workbench.mac .debug-viewlet .monaco-list-row .expression, diff --git a/src/vs/workbench/contrib/debug/browser/media/debugHover.css b/src/vs/workbench/contrib/debug/browser/media/debugHover.css index ea18e36199f..de0027f8dc9 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugHover.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugHover.css @@ -26,6 +26,7 @@ text-overflow: ellipsis; height: 18px; overflow: hidden; + white-space: pre; border-bottom: 1px solid rgba(128, 128, 128, 0.35); } @@ -60,10 +61,6 @@ max-height: 500px; } -.monaco-editor .debug-hover-widget .monaco-tl-contents .value { - white-space: nowrap; -} - .monaco-editor .debug-hover-widget .error { color: #E51400; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index d5685e1d80c..56f4ecfe8d4 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -246,10 +246,6 @@ opacity: 0.35; } -.monaco-workbench .debug-viewlet .monaco-list-row .expression { - white-space: pre; -} - .debug-viewlet .debug-call-stack .error { font-style: italic; text-overflow: ellipsis; diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index c12de58c3ad..ab9f53ecf2a 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -127,61 +127,6 @@ .monaco-workbench .repl .repl-tree .output.expression .code-italic { font-style: italic; } .monaco-workbench .repl .repl-tree .output.expression .code-underline { text-decoration: underline; } -/* Regular and bright color codes are currently treated the same. */ -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-30, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-90 { color: gray; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-31, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-91 { color: #BE1717; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-32, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-92 { color: #338A2F; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-33, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-93 { color: #BEB817; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-34, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-94 { color: darkblue; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-35, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-95 { color: darkmagenta; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-36, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-96 { color: darkcyan; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-37, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-97 { color: #BDBDBD; } - -.monaco-workbench .repl .repl-tree .output.expression .code-background-40, .monaco-workbench .repl .repl-tree .output.expression .code-background-100 { background-color: gray; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-41, .monaco-workbench .repl .repl-tree .output.expression .code-background-101 { background-color: #BE1717; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-42, .monaco-workbench .repl .repl-tree .output.expression .code-background-102 { background-color: #338A2F; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-43, .monaco-workbench .repl .repl-tree .output.expression .code-background-103 { background-color: #BEB817; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-44, .monaco-workbench .repl .repl-tree .output.expression .code-background-104 { background-color: darkblue; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-45, .monaco-workbench .repl .repl-tree .output.expression .code-background-105 { background-color: darkmagenta; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-46, .monaco-workbench .repl .repl-tree .output.expression .code-background-106 { background-color: darkcyan; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-47, .monaco-workbench .repl .repl-tree .output.expression .code-background-107 { background-color: #BDBDBD; } - -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-30, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-90 { color: #A0A0A0; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-31, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-91 { color: #A74747; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-32, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-92 { color: #348F34; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-33, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-93 { color: #5F4C29; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-34, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-94 { color: #6286BB; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-35, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-95 { color: #914191; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-36, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-96 { color: #218D8D; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-37, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-97 { color: #707070; } - -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-40, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-100 { background-color: #A0A0A0; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-41, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-101 { background-color: #A74747; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-42, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-102 { background-color: #348F34; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-43, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-103 { background-color: #5F4C29; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-44, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-104 { background-color: #6286BB; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-45, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-105 { background-color: #914191; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-46, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-106 { background-color: #218D8D; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-47, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-107 { background-color: #707070; } - -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-30, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-90 { color: gray; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-31, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-91 { color: #A74747; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-32, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-92 { color: #348F34; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-33, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-93 { color: #5F4C29; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-34, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-94 { color: #6286BB; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-35, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-95 { color: #914191; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-36, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-96 { color: #218D8D; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-37, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-97 { color: #707070; } - -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-40, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-100 { background-color: gray; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-41, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-101 { background-color: #A74747; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-42, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-102 { background-color: #348F34; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-43, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-103 { background-color: #5F4C29; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-44, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-104 { background-color: #6286BB; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-45, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-105 { background-color: #914191; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-46, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-106 { background-color: #218D8D; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-47, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-107 { background-color: #707070; } - /* Links */ .monaco-workbench .repl .repl-tree .output.expression a { text-decoration: underline; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index d2acc3d92b4..c090bf4bdfd 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -142,6 +142,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.updateTitleArea(); })); this._register(this.themeService.onThemeChange(() => { + this.refreshReplElements(false); if (this.isVisible()) { this.updateInputDecoration(); } @@ -282,7 +283,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati const lineDelimiter = this.textResourcePropertiesService.getEOL(this.model.uri); const traverseAndAppend = (node: ITreeNode) => { node.children.forEach(child => { - text += child.element.toString() + lineDelimiter; + text += child.element.toString().trimRight() + lineDelimiter; if (!child.collapsed && child.children.length) { traverseAndAppend(child); } @@ -381,7 +382,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati supportDynamicHeights: true }) as WorkbenchAsyncDataTree; - this.toDispose.push(this.tree.onContextMenu(e => this.onContextMenu(e))); + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); // Make sure to select the session if debugging is already active this.selectSession(); this.styleElement = dom.createStyleSheet(this.container); @@ -585,7 +586,7 @@ class ReplExpressionsRenderer implements ITreeRenderer { - s = s.replace(/\"/g, '\\"'); - return (s.indexOf(' ') >= 0 || s.indexOf('\\') >= 0) ? `"${s}"` : s; + s = s.replace(/([\"\\])/g, '\\$1'); + return s.indexOf(' ') >= 0 ? `"${s}"` : s; }; const hardQuote = (s: string) => { diff --git a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts index 65522e21e2e..9064ef14567 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts @@ -11,17 +11,28 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { Color, RGBA } from 'vs/base/common/color'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService, TestTheme } from 'vs/platform/theme/test/common/testThemeService'; +import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; suite('Debug - ANSI Handling', () => { let linkDetector: LinkDetector; + let themeService: IThemeService; /** - * Instantiate a {@link LinkDetector} for use by the functions being tested. + * Instantiate services for use by the functions being tested. */ setup(() => { const instantiationService: TestInstantiationService = workbenchInstantiationService(); linkDetector = instantiationService.createInstance(LinkDetector); + + const colors: { [id: string]: string; } = {}; + for (let color in ansiColorMap) { + colors[color] = ansiColorMap[color].defaults.dark; + } + const testTheme = new TestTheme(colors); + themeService = new TestThemeService(testTheme); }); test('appendStylizedStringToContainer', () => { @@ -61,7 +72,7 @@ suite('Debug - ANSI Handling', () => { * @returns An {@link HTMLSpanElement} that contains the stylized text. */ function getSequenceOutput(sequence: string): HTMLSpanElement { - const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector); + const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector, themeService); assert.equal(1, root.children.length); const child: Node = root.lastChild!; if (child instanceof HTMLSpanElement) { @@ -86,166 +97,167 @@ suite('Debug - ANSI Handling', () => { assertion(child); } + /** + * Assert that a given DOM element has the custom inline CSS style matching + * the color value provided. + * @param element The HTML span element to look at. + * @param colorType If `foreground`, will check the element's css `color`; + * if `background`, will check the element's css `backgroundColor`. + * @param color RGBA object to compare color to. If `undefined` or not provided, + * will assert that no value is set. + * @param message Optional custom message to pass to assertion. + */ + function assertInlineColor(element: HTMLSpanElement, colorType: 'background' | 'foreground', color?: RGBA | undefined, message?: string): void { + if (color !== undefined) { + const cssColor = Color.Format.CSS.formatRGB( + new Color(color) + ); + if (colorType === 'background') { + const styleBefore = element.style.backgroundColor; + element.style.backgroundColor = cssColor; + assert(styleBefore === element.style.backgroundColor, message || `Incorrect ${colorType} color style found (found color: ${styleBefore}, expected ${cssColor}).`); + } else { + const styleBefore = element.style.color; + element.style.color = cssColor; + assert(styleBefore === element.style.color, message || `Incorrect ${colorType} color style found (found color: ${styleBefore}, expected ${cssColor}).`); + } + } else { + if (colorType === 'background') { + assert(!element.style.backgroundColor, message || `Defined ${colorType} color style found when it should not have been defined`); + } else { + assert(!element.style.color, message || `Defined ${colorType} color style found when it should not have been defined`); + } + } + + } + test('Expected single sequence operation', () => { // Bold code assertSingleSequenceElement('\x1b[1m', (child) => { - assert(dom.hasClass(child, 'code-bold')); + assert(dom.hasClass(child, 'code-bold'), 'Bold formatting not detected after bold ANSI code.'); }); // Italic code assertSingleSequenceElement('\x1b[3m', (child) => { - assert(dom.hasClass(child, 'code-italic')); + assert(dom.hasClass(child, 'code-italic'), 'Italic formatting not detected after italic ANSI code.'); }); // Underline code assertSingleSequenceElement('\x1b[4m', (child) => { - assert(dom.hasClass(child, 'code-underline')); + assert(dom.hasClass(child, 'code-underline'), 'Underline formatting not detected after underline ANSI code.'); }); for (let i = 30; i <= 37; i++) { - const style: string = 'code-foreground-' + i; + const customClassName: string = 'code-foreground-colored'; - // Foreground colour codes + // Foreground colour class assertSingleSequenceElement('\x1b[' + i + 'm', (child) => { - assert(dom.hasClass(child, style)); + assert(dom.hasClass(child, customClassName), `Custom foreground class not found on element after foreground ANSI code #${i}.`); }); - // Cancellation code removes colour code + // Cancellation code removes colour class assertSingleSequenceElement('\x1b[' + i + ';39m', (child) => { - assert(dom.hasClass(child, style) === false); + assert(dom.hasClass(child, customClassName) === false, 'Custom foreground class still found after foreground cancellation code.'); + assertInlineColor(child, 'foreground', undefined, 'Custom color style still found after foreground cancellation code.'); }); } for (let i = 40; i <= 47; i++) { - const style: string = 'code-background-' + i; + const customClassName: string = 'code-background-colored'; - // Foreground colour codes + // Foreground colour class assertSingleSequenceElement('\x1b[' + i + 'm', (child) => { - assert(dom.hasClass(child, style)); + assert(dom.hasClass(child, customClassName), `Custom background class not found on element after background ANSI code #${i}.`); }); - // Cancellation code removes colour code + // Cancellation code removes colour class assertSingleSequenceElement('\x1b[' + i + ';49m', (child) => { - assert(dom.hasClass(child, style) === false); + assert(dom.hasClass(child, customClassName) === false, 'Custom background class still found after background cancellation code.'); + assertInlineColor(child, 'foreground', undefined, 'Custom color style still found after background cancellation code.'); }); } - // Different codes do not interfere + // Different codes do not cancel each other assertSingleSequenceElement('\x1b[1;3;4;30;41m', (child) => { - assert.equal(5, child.classList.length); + assert.equal(5, child.classList.length, 'Incorrect number of classes found for different ANSI codes.'); assert(dom.hasClass(child, 'code-bold')); - assert(dom.hasClass(child, 'code-italic')); - assert(dom.hasClass(child, 'code-underline')); - assert(dom.hasClass(child, 'code-foreground-30')); - assert(dom.hasClass(child, 'code-background-41')); - }); - - // New foreground color codes remove old codes - assertSingleSequenceElement('\x1b[30;31;32;33;34;35;36;37m', (child) => { - assert.equal(1, child.classList.length); - - assert(dom.hasClass(child, 'code-foreground-37')); - }); - - // New background color codes remove old codes - assertSingleSequenceElement('\x1b[40;41;42;43;44;45;46;47m', (child) => { - assert.equal(1, child.classList.length); - - assert(dom.hasClass(child, 'code-background-47')); + assert(dom.hasClass(child, 'code-italic'), 'Different ANSI codes should not cancel each other.'); + assert(dom.hasClass(child, 'code-underline'), 'Different ANSI codes should not cancel each other.'); + assert(dom.hasClass(child, 'code-foreground-colored'), 'Different ANSI codes should not cancel each other.'); + assert(dom.hasClass(child, 'code-background-colored'), 'Different ANSI codes should not cancel each other.'); }); // New foreground codes don't remove old background codes and vice versa assertSingleSequenceElement('\x1b[40;31;42;33m', (child) => { assert.equal(2, child.classList.length); - assert(dom.hasClass(child, 'code-background-42')); - assert(dom.hasClass(child, 'code-foreground-33')); + assert(dom.hasClass(child, 'code-background-colored'), 'New foreground ANSI code should not cancel existing background formatting.'); + assert(dom.hasClass(child, 'code-foreground-colored'), 'New background ANSI code should not cancel existing foreground formatting.'); }); // Duplicate codes do not change output assertSingleSequenceElement('\x1b[1;1;4;1;4;4;1;4m', (child) => { - assert(dom.hasClass(child, 'code-bold')); - assert(dom.hasClass(child, 'code-underline')); + assert(dom.hasClass(child, 'code-bold'), 'Duplicate formatting codes should have no effect.'); + assert(dom.hasClass(child, 'code-underline'), 'Duplicate formatting codes should have no effect.'); }); // Extra terminating semicolon does not change output assertSingleSequenceElement('\x1b[1;4;m', (child) => { - assert(dom.hasClass(child, 'code-bold')); - assert(dom.hasClass(child, 'code-underline')); + assert(dom.hasClass(child, 'code-bold'), 'Extra semicolon after ANSI codes should have no effect.'); + assert(dom.hasClass(child, 'code-underline'), 'Extra semicolon after ANSI codes should have no effect.'); }); // Cancellation code removes multiple codes assertSingleSequenceElement('\x1b[1;4;30;41;32;43;34;45;36;47;0m', (child) => { - assert.equal(0, child.classList.length); + assert.equal(0, child.classList.length, 'Cancellation ANSI code should clear ALL formatting.'); + assertInlineColor(child, 'background', undefined, 'Cancellation ANSI code should clear ALL formatting.'); + assertInlineColor(child, 'foreground', undefined, 'Cancellation ANSI code should clear ALL formatting.'); }); }); test('Expected single 8-bit color sequence operation', () => { - // Basic color codes specified with 8-bit color code format - for (let i = 0; i <= 7; i++) { + // Basic and bright color codes specified with 8-bit color code format + for (let i = 0; i <= 15; i++) { + // As these are controlled by theme, difficult to check actual color value // Foreground codes should add standard classes - const fgStyle: string = 'code-foreground-' + (i + 30); assertSingleSequenceElement('\x1b[38;5;' + i + 'm', (child) => { - assert(dom.hasClass(child, fgStyle)); + assert(dom.hasClass(child, 'code-foreground-colored'), `Custom color class not found after foreground 8-bit color code 38;5;${i}`); }); // Background codes should add standard classes - const bgStyle: string = 'code-background-' + (i + 40); assertSingleSequenceElement('\x1b[48;5;' + i + 'm', (child) => { - assert(dom.hasClass(child, bgStyle)); - }); - } - - // Bright color codes specified with 8-bit color code format - for (let i = 8; i <= 15; i++) { - // Foreground codes should add standard classes - const fgStyle: string = 'code-foreground-' + (i - 8 + 90); - assertSingleSequenceElement('\x1b[38;5;' + i + 'm', (child) => { - assert(dom.hasClass(child, fgStyle)); - }); - - // Background codes should add standard classes - const bgStyle: string = 'code-background-' + (i - 8 + 100); - assertSingleSequenceElement('\x1b[48;5;' + i + 'm', (child) => { - assert(dom.hasClass(child, bgStyle)); + assert(dom.hasClass(child, 'code-background-colored'), `Custom color class not found after background 8-bit color code 48;5;${i}`); }); } // 8-bit advanced colors for (let i = 16; i <= 255; i++) { - const color = Color.Format.CSS.formatRGB( - new Color((calcANSI8bitColor(i) as RGBA)) - ); - // Foreground codes should add custom class and inline style assertSingleSequenceElement('\x1b[38;5;' + i + 'm', (child) => { - assert(dom.hasClass(child, 'code-foreground-custom')); - assert(child.style.color === color); + assert(dom.hasClass(child, 'code-foreground-colored'), `Custom color class not found after foreground 8-bit color code 38;5;${i}`); + assertInlineColor(child, 'foreground', (calcANSI8bitColor(i) as RGBA), `Incorrect or no color styling found after foreground 8-bit color code 38;5;${i}`); }); // Background codes should add custom class and inline style assertSingleSequenceElement('\x1b[48;5;' + i + 'm', (child) => { - assert(dom.hasClass(child, 'code-background-custom')); - assert(child.style.backgroundColor === color); + assert(dom.hasClass(child, 'code-background-colored'), `Custom color class not found after background 8-bit color code 48;5;${i}`); + assertInlineColor(child, 'background', (calcANSI8bitColor(i) as RGBA), `Incorrect or no color styling found after background 8-bit color code 48;5;${i}`); }); } // Bad (nonexistent) color should not render assertSingleSequenceElement('\x1b[48;5;300m', (child) => { - assert.equal(0, child.classList.length); + assert.equal(0, child.classList.length, 'Bad ANSI color codes should have no effect.'); }); // Should ignore any codes after the ones needed to determine color assertSingleSequenceElement('\x1b[48;5;100;42;77;99;4;24m', (child) => { - const color = Color.Format.CSS.formatRGB( - new Color((calcANSI8bitColor(100) as RGBA)) - ); - assert(dom.hasClass(child, 'code-background-custom')); + assert(dom.hasClass(child, 'code-background-colored')); assert.equal(1, child.classList.length); - assert(child.style.backgroundColor === color); + assertInlineColor(child, 'background', (calcANSI8bitColor(100) as RGBA)); }); }); @@ -254,21 +266,17 @@ suite('Debug - ANSI Handling', () => { for (let r = 0; r <= 255; r += 64) { for (let g = 0; g <= 255; g += 64) { for (let b = 0; b <= 255; b += 64) { - let cssColor: string = `rgb(${r},${g},${b})`; - // Foreground codes should add custom class and inline style + let color = new RGBA(r, g, b); + // Foreground codes should add class and inline style assertSingleSequenceElement(`\x1b[38;2;${r};${g};${b}m`, (child) => { - assert(dom.hasClass(child, 'code-foreground-custom'), 'DOM should have "code-foreground-custom" class for advanced ANSI colors.'); - const styleBefore = child.style.color; - child.style.color = cssColor; - assert(styleBefore === child.style.color, `Incorrect inline color style found for ${cssColor} (found color: ${styleBefore}).`); + assert(dom.hasClass(child, 'code-foreground-colored'), 'DOM should have "code-foreground-colored" class for advanced ANSI colors.'); + assertInlineColor(child, 'foreground', color); }); - // Background codes should add custom class and inline style + // Background codes should add class and inline style assertSingleSequenceElement(`\x1b[48;2;${r};${g};${b}m`, (child) => { - assert(dom.hasClass(child, 'code-background-custom'), 'DOM should have "code-foreground-custom" class for advanced ANSI colors.'); - const styleBefore = child.style.backgroundColor; - child.style.backgroundColor = cssColor; - assert(styleBefore === child.style.backgroundColor, `Incorrect inline color style found for ${cssColor} (found color: ${styleBefore}).`); + assert(dom.hasClass(child, 'code-background-colored'), 'DOM should have "code-foreground-colored" class for advanced ANSI colors.'); + assertInlineColor(child, 'background', color); }); } } @@ -277,7 +285,7 @@ suite('Debug - ANSI Handling', () => { // Invalid color should not render assertSingleSequenceElement('\x1b[38;2;4;4m', (child) => { assert.equal(0, child.classList.length, `Invalid color code "38;2;4;4" should not add a class (classes found: ${child.classList}).`); - assert(child.style.color === '', `Invalid color code "38;2;4;4" should not add a custom color CSS (found color: ${child.style.color}).`); + assert(!child.style.color, `Invalid color code "38;2;4;4" should not add a custom color CSS (found color: ${child.style.color}).`); }); // Bad (nonexistent) color should not render @@ -287,11 +295,9 @@ suite('Debug - ANSI Handling', () => { // Should ignore any codes after the ones needed to determine color assertSingleSequenceElement('\x1b[48;2;100;42;77;99;200;75m', (child) => { - assert(dom.hasClass(child, 'code-background-custom'), `Color code with extra (valid) items "48;2;100;42;77;99;200;75" should still treat initial part as valid code and add class "code-background-custom".`); + assert(dom.hasClass(child, 'code-background-colored'), `Color code with extra (valid) items "48;2;100;42;77;99;200;75" should still treat initial part as valid code and add class "code-background-custom".`); assert.equal(1, child.classList.length, `Color code with extra items "48;2;100;42;77;99;200;75" should add one and only one class. (classes found: ${child.classList}).`); - let styleBefore = child.style.backgroundColor; - child.style.backgroundColor = 'rgb(100,42,77)'; - assert(child.style.backgroundColor === styleBefore, `Color code "48;2;100;42;77;99;200;75" should style background-color as rgb(100,42,77).`); + assertInlineColor(child, 'background', new RGBA(100, 42, 77), `Color code "48;2;100;42;77;99;200;75" should style background-color as rgb(100,42,77).`); }); }); @@ -307,7 +313,7 @@ suite('Debug - ANSI Handling', () => { if (elementsExpected === undefined) { elementsExpected = assertions.length; } - const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector); + const root: HTMLSpanElement = handleANSIOutput(sequence, linkDetector, themeService); assert.equal(elementsExpected, root.children.length); for (let i = 0; i < elementsExpected; i++) { const child: Node = root.children[i]; @@ -323,62 +329,60 @@ suite('Debug - ANSI Handling', () => { // Multiple codes affect the same text assertSingleSequenceElement('\x1b[1m\x1b[3m\x1b[4m\x1b[32m', (child) => { - assert(dom.hasClass(child, 'code-bold')); - assert(dom.hasClass(child, 'code-italic')); - assert(dom.hasClass(child, 'code-underline')); - assert(dom.hasClass(child, 'code-foreground-32')); + assert(dom.hasClass(child, 'code-bold'), 'Bold class not found after multiple different ANSI codes.'); + assert(dom.hasClass(child, 'code-italic'), 'Italic class not found after multiple different ANSI codes.'); + assert(dom.hasClass(child, 'code-underline'), 'Underline class not found after multiple different ANSI codes.'); + assert(dom.hasClass(child, 'code-foreground-colored'), 'Foreground color class not found after multiple different ANSI codes.'); }); // Consecutive codes do not affect previous ones assertMultipleSequenceElements('\x1b[1mbold\x1b[32mgreen\x1b[4munderline\x1b[3mitalic\x1b[0mnothing', [ (bold) => { assert.equal(1, bold.classList.length); - assert(dom.hasClass(bold, 'code-bold')); + assert(dom.hasClass(bold, 'code-bold'), 'Bold class not found after bold ANSI code.'); }, (green) => { assert.equal(2, green.classList.length); - assert(dom.hasClass(green, 'code-bold')); - assert(dom.hasClass(green, 'code-foreground-32')); + assert(dom.hasClass(green, 'code-bold'), 'Bold class not found after both bold and color ANSI codes.'); + assert(dom.hasClass(green, 'code-foreground-colored'), 'Color class not found after color ANSI code.'); }, (underline) => { assert.equal(3, underline.classList.length); - assert(dom.hasClass(underline, 'code-bold')); - assert(dom.hasClass(underline, 'code-foreground-32')); - assert(dom.hasClass(underline, 'code-underline')); + assert(dom.hasClass(underline, 'code-bold'), 'Bold class not found after bold, color, and underline ANSI codes.'); + assert(dom.hasClass(underline, 'code-foreground-colored'), 'Color class not found after color and underline ANSI codes.'); + assert(dom.hasClass(underline, 'code-underline'), 'Underline class not found after underline ANSI code.'); }, (italic) => { assert.equal(4, italic.classList.length); - assert(dom.hasClass(italic, 'code-bold')); - assert(dom.hasClass(italic, 'code-foreground-32')); - assert(dom.hasClass(italic, 'code-underline')); - assert(dom.hasClass(italic, 'code-italic')); + assert(dom.hasClass(italic, 'code-bold'), 'Bold class not found after bold, color, underline, and italic ANSI codes.'); + assert(dom.hasClass(italic, 'code-foreground-colored'), 'Color class not found after color, underline, and italic ANSI codes.'); + assert(dom.hasClass(italic, 'code-underline'), 'Underline class not found after underline and italic ANSI codes.'); + assert(dom.hasClass(italic, 'code-italic'), 'Italic class not found after italic ANSI code.'); }, (nothing) => { - assert.equal(0, nothing.classList.length); + assert.equal(0, nothing.classList.length, 'One or more style classes still found after reset ANSI code.'); }, ], 5); // Different types of color codes still cancel each other assertMultipleSequenceElements('\x1b[34msimple\x1b[38;2;100;100;100m24bit\x1b[38;5;3m8bitsimple\x1b[38;5;101m8bitadvanced', [ (simple) => { - assert.equal(1, simple.classList.length); - assert(dom.hasClass(simple, 'code-foreground-34')); + assert.equal(1, simple.classList.length, 'Foreground ANSI color code should add one class.'); + assert(dom.hasClass(simple, 'code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); }, (adv24Bit) => { - assert.equal(1, adv24Bit.classList.length); - assert(dom.hasClass(adv24Bit, 'code-foreground-custom')); - let styleBefore = adv24Bit.style.color; - adv24Bit.style.color = 'rgb(100,100,100)'; - assert(adv24Bit.style.color === styleBefore); + assert.equal(1, adv24Bit.classList.length, 'Multiple foreground ANSI color codes should only add a single class.'); + assert(dom.hasClass(adv24Bit, 'code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); + assertInlineColor(adv24Bit, 'foreground', new RGBA(100, 100, 100), '24-bit RGBA ANSI color code (100,100,100) should add matching color inline style.'); }, (adv8BitSimple) => { - assert.equal(1, adv8BitSimple.classList.length); - assert(dom.hasClass(adv8BitSimple, 'code-foreground-33')); - assert(adv8BitSimple.style.color === ''); + assert.equal(1, adv8BitSimple.classList.length, 'Multiple foreground ANSI color codes should only add a single class.'); + assert(dom.hasClass(adv8BitSimple, 'code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); + // Won't assert color because it's theme based }, (adv8BitAdvanced) => { - assert.equal(1, adv8BitAdvanced.classList.length); - assert(dom.hasClass(adv8BitAdvanced, 'code-foreground-custom')); + assert.equal(1, adv8BitAdvanced.classList.length, 'Multiple foreground ANSI color codes should only add a single class.'); + assert(dom.hasClass(adv8BitAdvanced, 'code-foreground-colored'), 'Foreground ANSI color codes should add custom foreground color class.'); } ], 4); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index 900e726c360..34a7d2fa6e1 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -49,6 +49,7 @@ import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/electron-browser/extensionsDependencyChecker'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -407,4 +408,35 @@ CommandsRegistry.registerCommand({ onUnexpectedError(e); } } +}); + +CommandsRegistry.registerCommand({ + id: 'workbench.extensions.uninstallExtension', + description: { + description: localize('workbench.extensions.uninstallExtension.description', "Uninstall the given extension"), + args: [ + { + name: localize('workbench.extensions.uninstallExtension.arg.name', "Id of the extension to uninstall"), + schema: { + 'type': 'string' + } + } + ] + }, + handler: async (accessor, id: string) => { + if (!id) { + throw new Error(localize('id required', "Extension id required.")); + } + const extensionManagementService = accessor.get(IExtensionManagementService); + try { + const installed = await extensionManagementService.getInstalled(ExtensionType.User); + const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id })); + if (!extensionToUninstall) { + return Promise.reject(new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, eg: ms-vscode.csharp.", id))); + } + await extensionManagementService.uninstall(extensionToUninstall, true); + } catch (e) { + onUnexpectedError(e); + } + } }); \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index 831798ea014..c6f12969b54 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -12,7 +12,7 @@ import { Event } from 'vs/base/common/event'; 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 { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionRecommendation, IGalleryExtension, IExtensionsConfigContent, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -57,10 +57,11 @@ import { coalesce } from 'vs/base/common/arrays'; import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IFileIconTheme, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ILabelService } from 'vs/platform/label/common/label'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; +import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IProductService } from 'vs/platform/product/common/product'; function toExtensionDescription(local: ILocalExtension): IExtensionDescription { return { @@ -147,7 +148,7 @@ export class InstallAction extends ExtensionAction { private static readonly Class = 'extension-action prominent install'; private static readonly InstallingClass = 'extension-action install installing'; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); private _manifest: IExtensionManifest | null; set manifest(manifest: IExtensionManifest) { @@ -164,11 +165,12 @@ export class InstallAction extends ExtensionAction { @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, @ILabelService private readonly labelService: ILabelService ) { super(`extensions.install`, InstallAction.INSTALL_LABEL, InstallAction.Class, false); this.update(); - this.labelService.onDidChangeFormatters(() => this.updateLabel(), this, this.disposables); + this.disposables.push(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this)); } update(): void { @@ -193,7 +195,7 @@ export class InstallAction extends ExtensionAction { this.tooltip = InstallAction.INSTALLING_LABEL; } else { if (this._manifest && this.workbenchEnvironmentService.configuration.remoteAuthority) { - if (isUIExtension(this._manifest, this.configurationService)) { + if (isUIExtension(this._manifest, this.productService, this.configurationService)) { this.label = `${InstallAction.INSTALL_LABEL} ${localize('locally', "Locally")}`; this.tooltip = `${InstallAction.INSTALL_LABEL} ${localize('locally', "Locally")}`; } else { @@ -270,7 +272,7 @@ export class InstallAction extends ExtensionAction { } dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } @@ -284,7 +286,7 @@ export class RemoteInstallAction extends ExtensionAction { private static readonly InstallingClass = 'extension-action install installing'; updateWhenCounterExtensionChanges: boolean = true; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); private installing: boolean = false; constructor( @@ -293,9 +295,10 @@ export class RemoteInstallAction extends ExtensionAction { @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, ) { super(`extensions.remoteinstall`, RemoteInstallAction.INSTALL_LABEL, RemoteInstallAction.Class, false); - this.labelService.onDidChangeFormatters(() => this.updateLabel(), this, this.disposables); + this.disposables.push(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this)); this.updateLabel(); this.update(); } @@ -328,7 +331,7 @@ export class RemoteInstallAction extends ExtensionAction { // Installed User Extension && this.extension && this.extension.local && this.extension.type === ExtensionType.User && this.extension.state === ExtensionState.Installed // Local Workspace Extension - && this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || !isUIExtension(this.extension.local.manifest, this.configurationService)) + && this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || !isUIExtension(this.extension.local.manifest, this.productService, this.configurationService)) // Extension does not exist in remote && !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer) && this.extensionsWorkbenchService.canInstall(this.extension) @@ -354,7 +357,7 @@ export class RemoteInstallAction extends ExtensionAction { } dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } @@ -368,7 +371,7 @@ export class LocalInstallAction extends ExtensionAction { private static readonly InstallingClass = 'extension-action install installing'; updateWhenCounterExtensionChanges: boolean = true; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); private installing: boolean = false; constructor( @@ -377,9 +380,10 @@ export class LocalInstallAction extends ExtensionAction { @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, ) { super(`extensions.localinstall`, LocalInstallAction.INSTALL_LABEL, LocalInstallAction.Class, false); - this.labelService.onDidChangeFormatters(() => this.updateLabel(), this, this.disposables); + this.disposables.push(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this)); this.updateLabel(); this.update(); } @@ -407,7 +411,7 @@ export class LocalInstallAction extends ExtensionAction { // Installed User Extension && this.extension && this.extension.local && this.extension.type === ExtensionType.User && this.extension.state === ExtensionState.Installed // Remote UI or Language pack Extension - && this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || isUIExtension(this.extension.local.manifest, this.configurationService)) + && this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || isUIExtension(this.extension.local.manifest, this.productService, this.configurationService)) // Extension does not exist in local && !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.localExtensionManagementServer) && this.extensionsWorkbenchService.canInstall(this.extension) @@ -433,7 +437,7 @@ export class LocalInstallAction extends ExtensionAction { } dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } @@ -470,6 +474,7 @@ export class UninstallAction extends ExtensionAction { this.label = UninstallAction.UninstallLabel; this.class = UninstallAction.UninstallClass; + this.tooltip = UninstallAction.UninstallLabel; if (state !== ExtensionState.Installed) { this.enabled = false; @@ -498,16 +503,15 @@ export class CombinedInstallAction extends ExtensionAction { private static readonly NoExtensionClass = 'extension-action prominent install no-extension'; private installAction: InstallAction; private uninstallAction: UninstallAction; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( @IInstantiationService instantiationService: IInstantiationService ) { super('extensions.combinedInstall', '', '', false); - this.installAction = instantiationService.createInstance(InstallAction); - this.uninstallAction = instantiationService.createInstance(UninstallAction); - this.disposables.push(this.installAction, this.uninstallAction); + this.installAction = this.disposables.push(instantiationService.createInstance(InstallAction)); + this.uninstallAction = this.disposables.push(instantiationService.createInstance(UninstallAction)); this.update(); } @@ -563,7 +567,7 @@ export class CombinedInstallAction extends ExtensionAction { dispose(): void { super.dispose(); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } @@ -667,7 +671,7 @@ export class ExtensionActionViewItem extends ActionViewItem { export abstract class ExtensionDropDownAction extends ExtensionAction { - protected disposables: IDisposable[] = []; + protected readonly disposables = new DisposableStore(); constructor( id: string, @@ -694,14 +698,14 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { } dispose(): void { - dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } export class DropDownMenuActionViewItem extends ExtensionActionViewItem { - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor(action: ExtensionDropDownAction, tabOnlyOnFocus: boolean, @@ -734,7 +738,7 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem { dispose(): void { super.dispose(); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } @@ -1187,7 +1191,7 @@ export class UpdateAllAction extends Action { static readonly ID = 'workbench.extensions.action.updateAllExtensions'; static LABEL = localize('updateAll', "Update All Extensions"); - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( id = UpdateAllAction.ID, @@ -1225,7 +1229,7 @@ export class UpdateAllAction extends Action { dispose(): void { super.dispose(); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } @@ -1235,7 +1239,7 @@ export class ReloadAction extends ExtensionAction { private static readonly DisabledClass = `${ReloadAction.EnabledClass} disabled`; updateWhenCounterExtensionChanges: boolean = true; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); private _runningExtensions: IExtensionDescription[] | null = null; constructor( @@ -1245,10 +1249,11 @@ export class ReloadAction extends ExtensionAction { @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, ) { super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false); - this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this, this.disposables); + this.disposables.push(this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this)); this.updateRunningExtensions(); } @@ -1325,7 +1330,7 @@ export class ReloadAction extends ExtensionAction { return; } if (this.workbenchEnvironmentService.configuration.remoteAuthority) { - const uiExtension = isUIExtension(this.extension.local.manifest, this.configurationService); + const uiExtension = isUIExtension(this.extension.local.manifest, this.productService, this.configurationService); // Local Workspace Extension if (!uiExtension && this.extension.server === this.extensionManagementServerService.localExtensionManagementServer) { const remoteExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer)[0]; @@ -1360,7 +1365,7 @@ export class ReloadAction extends ExtensionAction { } dispose(): void { - dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } @@ -1374,7 +1379,7 @@ export class SetColorThemeAction extends ExtensionAction { private static readonly EnabledClass = 'extension-action theme'; private static readonly DisabledClass = `${SetColorThemeAction.EnabledClass} disabled`; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( private readonly colorThemes: IColorTheme[], @@ -1384,7 +1389,7 @@ export class SetColorThemeAction extends ExtensionAction { @IConfigurationService private readonly configurationService: IConfigurationService ) { super(`extensions.colorTheme`, localize('color theme', "Set Color Theme"), SetColorThemeAction.DisabledClass, false); - Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidColorThemeChange)(() => this.update(), this, this.disposables); + this.disposables.push(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidColorThemeChange)(() => this.update(), this)); this.update(); } @@ -1432,7 +1437,7 @@ export class SetColorThemeAction extends ExtensionAction { } dispose() { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } @@ -1442,7 +1447,7 @@ export class SetFileIconThemeAction extends ExtensionAction { private static readonly EnabledClass = 'extension-action theme'; private static readonly DisabledClass = `${SetFileIconThemeAction.EnabledClass} disabled`; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); static getFileIconThemes(fileIconThemes: IFileIconTheme[], extension: IExtension): IFileIconTheme[] { return fileIconThemes.filter(c => c.extensionData && ExtensionIdentifier.equals(c.extensionData.extensionId, extension.identifier.id)); @@ -1456,7 +1461,7 @@ export class SetFileIconThemeAction extends ExtensionAction { @IConfigurationService private readonly configurationService: IConfigurationService ) { super(`extensions.fileIconTheme`, localize('file icon theme', "Set File Icon Theme"), SetFileIconThemeAction.DisabledClass, false); - Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidFileIconThemeChange)(() => this.update(), this, this.disposables); + this.disposables.push(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidFileIconThemeChange)(() => this.update(), this)); this.update(); } @@ -1504,7 +1509,7 @@ export class SetFileIconThemeAction extends ExtensionAction { } dispose() { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } @@ -1604,7 +1609,7 @@ export class ClearExtensionsInputAction extends Action { static readonly ID = 'workbench.extensions.action.clearExtensionsInput'; static LABEL = localize('clearExtensionsInput', "Clear Extensions Input"); - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( id: string, @@ -1615,7 +1620,7 @@ export class ClearExtensionsInputAction extends Action { ) { super(id, label, 'clear-extensions', true); this.onSearchChange(value); - onSearchChange(this.onSearchChange, this, this.disposables); + this.disposables.push(onSearchChange(this.onSearchChange, this)); } private onSearchChange(value: string): void { @@ -1632,7 +1637,7 @@ export class ClearExtensionsInputAction extends Action { } dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } @@ -1747,7 +1752,8 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { @IOpenerService private readonly openerService: IOpenerService, @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IProductService private readonly productService: IProductService, ) { super(id, label, 'extension-action'); this.recommendations = recommendations; @@ -1774,7 +1780,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { private async installExtension(extension: IExtension): Promise { try { if (extension.local && extension.gallery) { - if (isUIExtension(extension.local.manifest, this.configurationService)) { + if (isUIExtension(extension.local.manifest, this.productService, this.configurationService)) { await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(extension.gallery); return; } else if (this.extensionManagementServerService.remoteExtensionManagementServer) { @@ -1837,7 +1843,7 @@ export class IgnoreExtensionRecommendationAction extends Action { private static readonly Class = 'extension-action ignore'; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); extension: IExtension; constructor( @@ -1857,7 +1863,7 @@ export class IgnoreExtensionRecommendationAction extends Action { dispose(): void { super.dispose(); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } @@ -1867,7 +1873,7 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { private static readonly Class = 'extension-action undo-ignore'; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); extension: IExtension; constructor( @@ -1887,7 +1893,7 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { dispose(): void { super.dispose(); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } @@ -1964,7 +1970,7 @@ export class ShowAzureExtensionsAction extends Action { export class ChangeSortAction extends Action { private query: Query; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( id: string, @@ -1981,7 +1987,7 @@ export class ChangeSortAction extends Action { this.query = Query.parse(''); this.enabled = false; - onSearchChange(this.onSearchChange, this, this.disposables); + this.disposables.push(onSearchChange(this.onSearchChange, this)); } private onSearchChange(value: string): void { @@ -2286,7 +2292,7 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi static readonly ID = 'workbench.extensions.action.configureWorkspaceRecommendedExtensions'; static LABEL = localize('configureWorkspaceRecommendedExtensions', "Configure Recommended Extensions (Workspace)"); - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( id: string, @@ -2299,7 +2305,7 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi @ITextModelService textModelResolverService: ITextModelService ) { super(id, label, contextService, fileService, textFileService, editorService, jsonEditingService, textModelResolverService); - this.contextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables); + this.disposables.push(this.contextService.onDidChangeWorkbenchState(() => this.update(), this)); this.update(); } @@ -2318,7 +2324,7 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi } dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } @@ -2328,7 +2334,7 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac static readonly ID = 'workbench.extensions.action.configureWorkspaceFolderRecommendedExtensions'; static LABEL = localize('configureWorkspaceFolderRecommendedExtensions', "Configure Recommended Extensions (Workspace Folder)"); - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( id: string, @@ -2342,7 +2348,7 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac @ICommandService private readonly commandService: ICommandService ) { super(id, label, contextService, fileService, textFileService, editorService, jsonEditingService, textModelResolverService); - this.contextService.onDidChangeWorkspaceFolders(() => this.update(), this, this.disposables); + this.disposables.push(this.contextService.onDidChangeWorkspaceFolders(() => this.update(), this)); this.update(); } @@ -2363,7 +2369,7 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac } dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } @@ -2654,7 +2660,7 @@ export class DisabledLabelAction extends ExtensionAction { private static readonly Class = 'disable-status'; updateWhenCounterExtensionChanges: boolean = true; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); private _runningExtensions: IExtensionDescription[] | null = null; constructor( @@ -2663,8 +2669,8 @@ export class DisabledLabelAction extends ExtensionAction { @IExtensionService private readonly extensionService: IExtensionService, ) { super('extensions.disabledLabel', warningAction.tooltip, `${DisabledLabelAction.Class} hide`, false); - warningAction.onDidChange(() => this.update(), this, this.disposables); - this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this, this.disposables); + this.disposables.push(warningAction.onDidChange(() => this.update(), this)); + this.disposables.push(this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this)); this.updateRunningExtensions(); } @@ -2699,7 +2705,7 @@ export class DisabledLabelAction extends ExtensionAction { } dispose(): void { - dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } @@ -2711,20 +2717,21 @@ export class SystemDisabledWarningAction extends ExtensionAction { private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} info`; updateWhenCounterExtensionChanges: boolean = true; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); private _runningExtensions: IExtensionDescription[] | null = null; constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, @ILabelService private readonly labelService: ILabelService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionService private readonly extensionService: IExtensionService, ) { super('extensions.install', '', `${SystemDisabledWarningAction.CLASS} hide`, false); - this.labelService.onDidChangeFormatters(() => this.update(), this, this.disposables); - this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this, this.disposables); + this.disposables.push(this.labelService.onDidChangeFormatters(() => this.update(), this)); + this.disposables.push(this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this)); this.updateRunningExtensions(); this.update(); } @@ -2760,7 +2767,7 @@ export class SystemDisabledWarningAction extends ExtensionAction { const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation) : null; const localExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0]; const localExtensionServer = localExtension ? localExtension.server : null; - if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && !isUIExtension(this.extension.local.manifest, this.configurationService)) { + if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && !isUIExtension(this.extension.local.manifest, this.productService, this.configurationService)) { if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; this.tooltip = localize('disabled locally', "Extension is enabled on '{0}' and disabled locally.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer)); @@ -2772,7 +2779,7 @@ export class SystemDisabledWarningAction extends ExtensionAction { return; } } - if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && isUIExtension(this.extension.local.manifest, this.configurationService)) { + if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && isUIExtension(this.extension.local.manifest, this.productService, this.configurationService)) { if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; this.tooltip = localize('disabled remotely', "Extension is enabled locally and disabled on '{0}'.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer)); @@ -2797,7 +2804,7 @@ export class SystemDisabledWarningAction extends ExtensionAction { } dispose(): void { - dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } @@ -2807,7 +2814,7 @@ export class DisableAllAction extends Action { static readonly ID = 'workbench.extensions.action.disableAll'; static LABEL = localize('disableAll', "Disable All Installed Extensions"); - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( id: string = DisableAllAction.ID, label: string = DisableAllAction.LABEL, @@ -2829,7 +2836,7 @@ export class DisableAllAction extends Action { dispose(): void { super.dispose(); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } @@ -2838,7 +2845,7 @@ export class DisableAllWorkpsaceAction extends Action { static readonly ID = 'workbench.extensions.action.disableAllWorkspace'; static LABEL = localize('disableAllWorkspace', "Disable All Installed Extensions for this Workspace"); - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( id: string = DisableAllWorkpsaceAction.ID, label: string = DisableAllWorkpsaceAction.LABEL, @@ -2847,8 +2854,8 @@ export class DisableAllWorkpsaceAction extends Action { ) { super(id, label); this.update(); - this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables); - this.extensionsWorkbenchService.onChange(() => this.update(), this, this.disposables); + this.disposables.push(this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this)); + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update(), this)); } private update(): void { @@ -2861,7 +2868,7 @@ export class DisableAllWorkpsaceAction extends Action { dispose(): void { super.dispose(); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } @@ -2870,7 +2877,7 @@ export class EnableAllAction extends Action { static readonly ID = 'workbench.extensions.action.enableAll'; static LABEL = localize('enableAll', "Enable All Extensions"); - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( id: string = EnableAllAction.ID, label: string = EnableAllAction.LABEL, @@ -2892,7 +2899,7 @@ export class EnableAllAction extends Action { dispose(): void { super.dispose(); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } @@ -2901,7 +2908,7 @@ export class EnableAllWorkpsaceAction extends Action { static readonly ID = 'workbench.extensions.action.enableAllWorkspace'; static LABEL = localize('enableAllWorkspace', "Enable All Extensions for this Workspace"); - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( id: string = EnableAllWorkpsaceAction.ID, label: string = EnableAllWorkpsaceAction.LABEL, @@ -2911,8 +2918,8 @@ export class EnableAllWorkpsaceAction extends Action { ) { super(id, label); this.update(); - this.extensionsWorkbenchService.onChange(() => this.update(), this, this.disposables); - this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables); + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update(), this)); + this.disposables.push(this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this)); } private update(): void { @@ -2925,7 +2932,7 @@ export class EnableAllWorkpsaceAction extends Action { dispose(): void { super.dispose(); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress.ts index 644b4523b6f..636edeccca3 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress.ts @@ -5,7 +5,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { localize } from 'vs/nls'; import { IDisposable } from 'vs/base/common/lifecycle'; import { timeout } from 'vs/base/common/async'; @@ -17,7 +17,7 @@ export class ExtensionActivationProgress implements IWorkbenchContribution { constructor( @IExtensionService extensionService: IExtensionService, - @IProgressService2 progressService: IProgressService2, + @IProgressService progressService: IProgressService, @ILogService logService: ILogService, ) { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts index 5658c7df148..7bad502bda4 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts @@ -28,7 +28,7 @@ import { IExtensionManagementService, IExtensionManagementServerService, IExtens import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView } from './extensionsViews'; import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; -import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; @@ -344,7 +344,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, - @IProgressService2 private readonly progressService: IProgressService2, + @IProgressService private readonly progressService: IProgressService, @IInstantiationService instantiationService: IInstantiationService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts index 623c29529f2..ed90afcd03a 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts @@ -45,7 +45,8 @@ import { ExtensionType, ExtensionIdentifier, IExtensionDescription, isLanguagePa import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import product from 'vs/platform/product/node/product'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; -import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; +import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { IProductService } from 'vs/platform/product/common/product'; class ExtensionsViewState extends Disposable implements IExtensionsViewState { @@ -95,7 +96,8 @@ export class ExtensionsListView extends ViewletPanel { @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IExperimentService private readonly experimentService: IExperimentService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, - @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, + @IProductService protected readonly productService: IProductService, ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService); this.server = options.server; @@ -716,6 +718,7 @@ export class ExtensionsListView extends ViewletPanel { } private openExtension(extension: IExtension): void { + extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0] || extension; this.extensionsWorkbenchService.open(extension).then(undefined, err => this.onError(err)); } @@ -849,10 +852,11 @@ export class ServerExtensionsView extends ExtensionsListView { @IExperimentService experimentService: IExperimentService, @IWorkbenchThemeService workbenchThemeService: IWorkbenchThemeService, @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, + @IProductService productService: IProductService, ) { options.server = server; - super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, modeService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService); + super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, modeService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService); this.disposables.push(onDidChangeTitle(title => this.updateTitle(title))); } @@ -999,7 +1003,7 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { if (!extension || !extension.local || extension.state !== ExtensionState.Installed) { return true; } - return isUIExtension(extension.local.manifest, this.configurationService) ? extension.server !== this.extensionManagementServerService.localExtensionManagementServer : extension.server !== this.extensionManagementServerService.remoteExtensionManagementServer; + return isUIExtension(extension.local.manifest, this.productService, this.configurationService) ? extension.server !== this.extensionManagementServerService.localExtensionManagementServer : extension.server !== this.extensionManagementServerService.remoteExtensionManagementServer; })); } } diff --git a/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts index 021dfe442f4..1362c838196 100644 --- a/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts @@ -16,7 +16,7 @@ import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWindowService } from 'vs/platform/windows/common/windows'; @@ -28,13 +28,13 @@ import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import product from 'vs/platform/product/node/product'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { INotificationService } from 'vs/platform/notification/common/notification'; import * as resources from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IFileService } from 'vs/platform/files/common/files'; -import { IExtensionManifest, ExtensionType, ExtensionIdentifierWithVersion, IExtension as IPlatformExtension, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; interface IExtensionStateProvider { (extension: Extension): T; @@ -497,7 +497,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, @IWindowService private readonly windowService: IWindowService, @ILogService private readonly logService: ILogService, - @IProgressService2 private readonly progressService: IProgressService2, + @IProgressService private readonly progressService: IProgressService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IStorageService private readonly storageService: IStorageService, @IFileService private readonly fileService: IFileService diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index e5c1375072e..adfaf8b1aa8 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -30,8 +30,8 @@ import { TestContextService, TestWindowService, TestSharedProcessService } from import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { IProgressService2 } from 'vs/platform/progress/common/progress'; -import { ProgressService2 } from 'vs/workbench/services/progress/browser/progressService2'; +import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { URLService } from 'vs/platform/url/common/urlService'; import { URI } from 'vs/base/common/uri'; @@ -61,7 +61,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(ILogService, NullLogService); instantiationService.stub(IWindowService, TestWindowService); - instantiationService.stub(IProgressService2, ProgressService2); + instantiationService.stub(IProgressService, ProgressService); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); instantiationService.stub(IURLService, URLService); diff --git a/src/vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem.ts index 0beff3f7ec2..cf63a507426 100644 --- a/src/vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem.ts @@ -97,14 +97,14 @@ export class FeedbackStatusbarItem extends Themable implements IStatusbarItem { this.container = element; // Prevent showing dropdown on anything but left click - this.toDispose.push(addDisposableListener(this.container, 'mousedown', (e: MouseEvent) => { + this._register(addDisposableListener(this.container, 'mousedown', (e: MouseEvent) => { if (e.button !== 0) { EventHelper.stop(e, true); } }, true)); // Offer context menu to hide status bar entry - this.toDispose.push(addDisposableListener(this.container, 'contextmenu', e => { + this._register(addDisposableListener(this.container, 'contextmenu', e => { EventHelper.stop(e, true); this.contextMenuService.showContextMenu({ diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index 21242fc0210..f9719911f6c 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -14,8 +14,8 @@ import { URI } from 'vs/base/common/uri'; import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IFileService } from 'vs/platform/files/common/files'; /** * An implementation of editor for binary files like images. @@ -30,7 +30,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { @IWindowsService private readonly windowsService: IWindowsService, @IEditorService private readonly editorService: IEditorService, @IStorageService storageService: IStorageService, - @ITextFileService textFileService: ITextFileService, + @IFileService fileService: IFileService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, ) { super( @@ -41,7 +41,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { }, telemetryService, themeService, - textFileService, + fileService, environmentService, storageService, ); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 97733dddb2c..31cd7cb1006 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -18,7 +18,6 @@ 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 { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views'; @@ -34,7 +33,7 @@ import { IEditorInput, IEditor } from 'vs/workbench/common/editor'; import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -44,7 +43,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, - @IProgressService2 progressService: IProgressService2 + @IProgressService progressService: IProgressService ) { super(); @@ -187,7 +186,7 @@ export class ExplorerViewlet extends ViewContainerViewlet { // We try to be smart and only use the delay if we recognize that the user action is likely to cause // a new entry in the opened editors view. const delegatingEditorService = this.instantiationService.createInstance(DelegatingEditorService); - delegatingEditorService.setEditorOpenHandler((group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise => { + delegatingEditorService.setEditorOpenHandler(async (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise => { let openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { let delay = 0; @@ -203,16 +202,19 @@ export class ExplorerViewlet extends ViewContainerViewlet { openEditorsView.setStructuralRefreshDelay(delay); } - const onSuccessOrError = (editor: BaseEditor | null) => { + let openedEditor: IEditor | null = null; + try { + openedEditor = await this.editorService.openEditor(editor, options, group); + } catch (error) { + // ignore + } finally { const openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { openEditorsView.setStructuralRefreshDelay(0); } + } - return editor; - }; - - return this.editorService.openEditor(editor, options, group).then(onSuccessOrError, onSuccessOrError); + return openedEditor; }); const explorerInstantiator = this.instantiationService.createChild(new ServiceCollection([IEditorService, delegatingEditorService])); diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 89e2def4556..c661cd4ce37 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -5,13 +5,12 @@ import * as nls from 'vs/nls'; import * as errors from 'vs/base/common/errors'; -import * as env from 'vs/base/common/platform'; import * as DOM from 'vs/base/browser/dom'; import { IAction } from 'vs/base/common/actions'; 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, OpenFileFolderAction, AddRootFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; +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'; @@ -73,7 +72,7 @@ export class EmptyView extends ViewletPanel { if (!this.actionRunner) { return; } - const actionClass = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? AddRootFolderAction : env.isMacintosh ? OpenFileFolderAction : OpenFolderAction; + const actionClass = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? AddRootFolderAction : OpenFolderAction; const action = this.instantiationService.createInstance(actionClass, actionClass.ID, actionClass.LABEL); this.actionRunner.run(action).then(() => { action.dispose(); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 466ee3bb498..2f9ddd7bc90 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -20,7 +20,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ResourceContextKey } from 'vs/workbench/common/resources'; @@ -369,11 +369,21 @@ export class ExplorerView extends ViewletPanel { } } + private setContextKeys(stat: ExplorerItem | null): void { + const isSingleFolder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER; + const resource = stat ? stat.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : null; + this.resourceContext.set(resource); + this.folderContext.set((isSingleFolder && !stat) || !!stat && stat.isDirectory); + this.readonlyContext.set(!!stat && stat.isReadonly); + this.rootContext.set(!stat || (stat && stat.isRoot)); + } + private onContextMenu(e: ITreeContextMenuEvent): void { const stat = e.element; // update dynamic contexts this.fileCopiedContextKey.set(this.clipboardService.hasResources()); + this.setContextKeys(stat); const selection = this.tree.getSelection(); this.contextMenuService.showContextMenu({ @@ -398,13 +408,8 @@ export class ExplorerView extends ViewletPanel { } private onFocusChanged(elements: ExplorerItem[]): void { - const stat = elements && elements.length ? elements[0] : undefined; - const isSingleFolder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER; - const resource = stat ? stat.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : null; - this.resourceContext.set(resource); - this.folderContext.set((isSingleFolder && !stat) || !!stat && stat.isDirectory); - this.readonlyContext.set(!!stat && stat.isReadonly); - this.rootContext.set(!stat || (stat && stat.isRoot)); + const stat = elements && elements.length ? elements[0] : null; + this.setContextKeys(stat); if (stat) { const enableTrash = this.configurationService.getValue().files.enableTrash; @@ -490,7 +495,11 @@ export class ExplorerView extends ViewletPanel { } }); - this.progressService.showWhile(promise, this.layoutService.isRestored() ? 800 : 1200 /* less ugly initial startup */); + this.progressService.withProgress({ + location: ProgressLocation.Explorer, + delay: this.layoutService.isRestored() ? 800 : 1200 // less ugly initial startup + }, _progress => promise); + return promise; } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 6a46cb17e53..e4302b56630 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -7,7 +7,7 @@ import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import * as DOM from 'vs/base/browser/dom'; import * as glob from 'vs/base/common/glob'; import { IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IFileService, FileKind, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -99,7 +99,11 @@ export class ExplorerDataSource implements IAsyncDataSource promise); + return promise; } } diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 99d47901672..1161e7e0f57 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -243,27 +243,23 @@ export class ExplorerItem { return this.children.get(this.getPlatformAwareName(name)); } - fetchChildren(fileService: IFileService, explorerService: IExplorerService): Promise { - let promise: Promise = Promise.resolve(undefined); + async fetchChildren(fileService: IFileService, explorerService: IExplorerService): Promise { if (!this._isDirectoryResolved) { // Resolve metadata only when the mtime is needed since this can be expensive // Mtime is only used when the sort order is 'modified' const resolveMetadata = explorerService.sortOrder === 'modified'; - promise = fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }).then(stat => { - const resolved = ExplorerItem.create(stat, this); - ExplorerItem.mergeLocalWithDisk(resolved, this); - this._isDirectoryResolved = true; - }); + const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); + const resolved = ExplorerItem.create(stat, this); + ExplorerItem.mergeLocalWithDisk(resolved, this); + this._isDirectoryResolved = true; } - return promise.then(() => { - const items: ExplorerItem[] = []; - this.children.forEach(child => { - items.push(child); - }); - - return items; + const items: ExplorerItem[] = []; + this.children.forEach(child => { + items.push(child); }); + + return items; } /** diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 6bec4dbd5da..204b23e1f02 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -147,7 +147,7 @@ export class ExplorerService implements IExplorerService { return !!this.editable && (this.editable.stat === stat || !stat); } - select(resource: URI, reveal?: boolean): Promise { + async select(resource: URI, reveal?: boolean): Promise { const fileStat = this.findClosest(resource); if (fileStat) { this._onDidSelectResource.fire({ resource: fileStat.resource, reveal }); @@ -159,7 +159,9 @@ export class ExplorerService implements IExplorerService { const workspaceFolder = this.contextService.getWorkspaceFolder(resource); const rootUri = workspaceFolder ? workspaceFolder.uri : this.roots[0].resource; const root = this.roots.filter(r => r.resource.toString() === rootUri.toString()).pop()!; - return this.fileService.resolve(rootUri, options).then(stat => { + + try { + const stat = await this.fileService.resolve(rootUri, options); // Convert to model const modelStat = ExplorerItem.create(stat, undefined, options.resolveTo); @@ -170,10 +172,10 @@ export class ExplorerService implements IExplorerService { // Select and Reveal this._onDidSelectResource.fire({ resource: item ? item.resource : undefined, reveal }); - }, () => { + } catch (error) { root.isError = true; this._onDidChangeItem.fire({ item: root, recursive: false }); - }); + } } refresh(): void { diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index bad977ca281..89e12070e37 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -348,7 +348,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } })); - this.tree.onContextMenu(this.onContextMenu, this, this._toDispose); + this._register(this.tree.onContextMenu(this.onContextMenu, this)); this._register(this.configurationService.onDidChangeConfiguration(e => { if (this.filterAction.useFilesExclude && e.affectsConfiguration('files.exclude')) { diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index a7d2ae2d3cc..d54b975b717 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -141,10 +141,10 @@ export class SwitchOutputActionViewItem extends SelectActionViewItem { super(null, action, [], 0, contextViewService, { ariaLabel: nls.localize('outputChannels', 'Output Channels.') }); let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - this.toDispose.push(outputChannelRegistry.onDidRegisterChannel(() => this.updateOtions())); - this.toDispose.push(outputChannelRegistry.onDidRemoveChannel(() => this.updateOtions())); - this.toDispose.push(this.outputService.onActiveOutputChannel(() => this.updateOtions())); - this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService)); + this._register(outputChannelRegistry.onDidRegisterChannel(() => this.updateOtions())); + this._register(outputChannelRegistry.onDidRemoveChannel(() => this.updateOtions())); + this._register(this.outputService.onActiveOutputChannel(() => this.updateOtions())); + this._register(attachSelectBoxStyler(this.selectBox, themeService)); this.updateOtions(); } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index aa8da6134ca..3813a7ff628 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; import { OS } from 'vs/base/common/platform'; -import { dispose, Disposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, Disposable, toDisposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { CheckboxActionViewItem } from 'vs/base/browser/ui/checkbox/checkbox'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; @@ -804,7 +804,7 @@ class KeybindingItemRenderer implements IListRenderer element); this.keybindingsEditor.layoutColumns(elements); @@ -814,7 +814,7 @@ class KeybindingItemRenderer implements IListRenderer dispose(disposables)) + disposable: disposables }; } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index c8eacc925a2..5d394de29a2 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -31,7 +31,7 @@ import { ConfigurationTarget } from 'vs/platform/configuration/common/configurat import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -94,7 +94,7 @@ export class PreferencesEditor extends BaseEditor { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, - @IProgressService private readonly progressService: IProgressService, + @ILocalProgressService private readonly progressService: ILocalProgressService, @IStorageService storageService: IStorageService ) { super(PreferencesEditor.ID, telemetryService, themeService, storageService); diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index 5ee73f788d1..8d72b3c257a 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -294,7 +294,7 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { this.onBeforeRun(this.commandId); // Use a timeout to give the quick open widget a chance to close itself first - setTimeout(() => { + setTimeout(async () => { if (action && (!(action instanceof Action) || action.enabled)) { try { /* __GDPR__ @@ -304,11 +304,17 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { } */ this.telemetryService.publicLog('workbenchActionExecuted', { id: action.id, from: 'quick open' }); - (action.run() || Promise.resolve()).then(() => { - if (action instanceof Action) { - action.dispose(); + + const promise = action.run(); + if (promise) { + try { + await promise; + } finally { + if (action instanceof Action) { + action.dispose(); + } } - }, err => this.onError(err)); + } } catch (error) { this.onError(error); } @@ -402,7 +408,7 @@ export class CommandsHandler extends QuickOpenHandler { this.commandHistoryEnabled = resolveCommandHistory(this.configurationService) > 0; } - getResults(searchValue: string, token: CancellationToken): Promise { + async getResults(searchValue: string, token: CancellationToken): Promise { if (this.waitedForExtensionsRegistered) { return this.doGetResults(searchValue, token); } @@ -411,11 +417,10 @@ export class CommandsHandler extends QuickOpenHandler { // a chance to register so that the complete set of commands shows up as result // We do not want to delay functionality beyond that time though to keep the commands // functional. - return Promise.race([timeout(800), this.extensionService.whenInstalledExtensionsRegistered().then(() => undefined)]).then(() => { - this.waitedForExtensionsRegistered = true; + await Promise.race([timeout(800).then(), this.extensionService.whenInstalledExtensionsRegistered()]); + this.waitedForExtensionsRegistered = true; - return this.doGetResults(searchValue, token); - }); + return this.doGetResults(searchValue, token); } private doGetResults(searchValue: string, token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts index bc97910fe3c..ed5f3203846 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts @@ -495,7 +495,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { return this.cachedOutlineRequest; } - private doGetActiveOutline(): Promise { + private async doGetActiveOutline(): Promise { const activeTextEditorWidget = this.editorService.activeTextEditorWidget; if (activeTextEditorWidget) { let model = activeTextEditorWidget.getModel(); @@ -504,13 +504,13 @@ export class GotoSymbolHandler extends QuickOpenHandler { } if (model && types.isFunction((model).getLanguageIdentifier)) { - return Promise.resolve(asPromise(() => getDocumentSymbols(model, true, this.pendingOutlineRequest!.token)).then(entries => { - return new OutlineModel(this.toQuickOpenEntries(entries)); - })); + const entries = await asPromise(() => getDocumentSymbols(model, true, this.pendingOutlineRequest!.token)); + + return new OutlineModel(this.toQuickOpenEntries(entries)); } } - return Promise.resolve(null); + return null; } decorateOutline(fullRange: IRange, startRange: IRange, editor: IEditor, group: IEditorGroup): void { diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index fddefa02d14..2ddf2907a6e 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -32,7 +32,7 @@ import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc'; import { ipcRenderer as ipc } from 'electron'; import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IProgressService2, IProgress, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, IProgress, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; @@ -272,7 +272,7 @@ class ProgressReporter { class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IProgressService2 progressService: IProgressService2, + @IProgressService progressService: IProgressService, @IDialogService dialogService: IDialogService, @ICommandService commandService: ICommandService ) { diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 2a22287760a..5e64a44a7ab 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import 'vs/css!./media/dirtydiffDecorator'; import { ThrottledDelayer, first } from 'vs/base/common/async'; -import { IDisposable, dispose, toDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as ext from 'vs/workbench/common/contributions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -554,7 +554,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -export class DirtyDiffController implements IEditorContribution { +export class DirtyDiffController extends Disposable implements IEditorContribution { private static readonly ID = 'editor.contrib.dirtydiff'; @@ -571,20 +571,20 @@ export class DirtyDiffController implements IEditorContribution { private session: IDisposable = Disposable.None; private mouseDownInfo: { lineNumber: number } | null = null; private enabled = false; - private disposables: IDisposable[] = []; constructor( private editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { + super(); this.enabled = !contextKeyService.getContextKeyValue('isInDiffEditor'); if (this.enabled) { this.isDirtyDiffVisible = isDirtyDiffVisible.bindTo(contextKeyService); - this.disposables.push(editor.onMouseDown(e => this.onEditorMouseDown(e))); - this.disposables.push(editor.onMouseUp(e => this.onEditorMouseUp(e))); - this.disposables.push(editor.onDidChangeModel(() => this.close())); + this._register(editor.onMouseDown(e => this.onEditorMouseDown(e))); + this._register(editor.onMouseUp(e => this.onEditorMouseUp(e))); + this._register(editor.onDidChangeModel(() => this.close())); } } @@ -674,22 +674,20 @@ export class DirtyDiffController implements IEditorContribution { this.widget = this.instantiationService.createInstance(DirtyDiffWidget, this.editor, model); this.isDirtyDiffVisible.set(true); - const disposables: IDisposable[] = []; - Event.once(this.widget.onDidClose)(this.close, this, disposables); - model.onDidChange(this.onDidModelChange, this, disposables); + const disposables = new DisposableStore(); + disposables.push(Event.once(this.widget.onDidClose)(this.close, this)); + disposables.push(model.onDidChange(this.onDidModelChange, this)); - disposables.push( - this.widget, - toDisposable(() => { - this.model = null; - this.widget = null; - this.currentIndex = -1; - this.isDirtyDiffVisible.set(false); - this.editor.focus(); - }) - ); + disposables.push(this.widget); + disposables.push(toDisposable(() => { + this.model = null; + this.widget = null; + this.currentIndex = -1; + this.isDirtyDiffVisible.set(false); + this.editor.focus(); + })); - this.session = combinedDisposable(disposables); + this.session = disposables; return true; } @@ -808,10 +806,6 @@ export class DirtyDiffController implements IEditorContribution { return model.changes; } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } export const editorGutterModifiedBackground = registerColor('editorGutter.modifiedBackground', { @@ -837,7 +831,7 @@ export const overviewRulerModifiedForeground = registerColor('editorOverviewRule export const overviewRulerAddedForeground = registerColor('editorOverviewRuler.addedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerAddedForeground', 'Overview ruler marker color for added content.')); export const overviewRulerDeletedForeground = registerColor('editorOverviewRuler.deletedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerDeletedForeground', 'Overview ruler marker color for deleted content.')); -class DirtyDiffDecorator { +class DirtyDiffDecorator extends Disposable { static createDecoration(className: string, foregroundColor: string, options: { gutter: boolean, overview: boolean, isWholeLine: boolean }): ModelDecorationOptions { const decorationOptions: IModelDecorationOptions = { @@ -862,7 +856,6 @@ class DirtyDiffDecorator { private addedOptions: ModelDecorationOptions; private deletedOptions: ModelDecorationOptions; private decorations: string[] = []; - private disposables: IDisposable[] = []; private editorModel: ITextModel | null; constructor( @@ -870,6 +863,7 @@ class DirtyDiffDecorator { private model: DirtyDiffModel, @IConfigurationService configurationService: IConfigurationService ) { + super(); this.editorModel = editorModel; const decorations = configurationService.getValue('scm.diffDecorations'); const gutter = decorations === 'all' || decorations === 'gutter'; @@ -880,7 +874,7 @@ class DirtyDiffDecorator { this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', overviewRulerAddedForeground, options); this.deletedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-deleted', overviewRulerDeletedForeground, { ...options, isWholeLine: false }); - model.onDidChange(this.onDidChange, this, this.disposables); + this._register(model.onDidChange(this.onDidChange, this)); } private onDidChange(): void { @@ -924,7 +918,7 @@ class DirtyDiffDecorator { } dispose(): void { - this.disposables = dispose(this.disposables); + super.dispose(); if (this.editorModel && !this.editorModel.isDisposed()) { this.editorModel.deltaDecorations(this.decorations, []); @@ -957,7 +951,7 @@ function compareChanges(a: IChange, b: IChange): number { return a.originalEndLineNumber - b.originalEndLineNumber; } -export class DirtyDiffModel { +export class DirtyDiffModel extends Disposable { private _originalModel: ITextModel | null; get original(): ITextModel | null { return this._originalModel; } @@ -965,9 +959,8 @@ export class DirtyDiffModel { private diffDelayer: ThrottledDelayer | null; private _originalURIPromise?: Promise; - private repositoryDisposables = new Set(); - private originalModelDisposables: IDisposable[] = []; - private disposables: IDisposable[] = []; + private repositoryDisposables = new Set(); + private originalModelDisposables: IDisposable = Disposable.None; private _onDidChange = new Emitter[]>(); readonly onDidChange: Event[]> = this._onDidChange.event; @@ -985,27 +978,28 @@ export class DirtyDiffModel { @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, @ITextModelService private readonly textModelResolverService: ITextModelService ) { + super(); this._editorModel = editorModel; this.diffDelayer = new ThrottledDelayer(200); - this.disposables.push(editorModel.onDidChangeContent(() => this.triggerDiff())); - scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); + this._register(editorModel.onDidChangeContent(() => this.triggerDiff())); + this._register(scmService.onDidAddRepository(this.onDidAddRepository, this)); scmService.repositories.forEach(r => this.onDidAddRepository(r)); this.triggerDiff(); } private onDidAddRepository(repository: ISCMRepository): void { - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); this.repositoryDisposables.add(disposables); disposables.push(toDisposable(() => this.repositoryDisposables.delete(disposables))); const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources); - onDidChange(this.triggerDiff, this, disposables); + disposables.push(onDidChange(this.triggerDiff, this)); const onDidRemoveThis = Event.filter(this.scmService.onDidRemoveRepository, r => r === repository); - onDidRemoveThis(() => dispose(disposables), null, disposables); + disposables.push(onDidRemoveThis(() => dispose(disposables), null)); this.triggerDiff(); } @@ -1075,11 +1069,11 @@ export class DirtyDiffModel { this._originalModel = ref.object.textEditorModel; - const originalModelDisposables: IDisposable[] = []; + const originalModelDisposables = new DisposableStore(); originalModelDisposables.push(ref); originalModelDisposables.push(ref.object.textEditorModel.onDidChangeContent(() => this.triggerDiff())); - dispose(this.originalModelDisposables); + this.originalModelDisposables.dispose(); this.originalModelDisposables = originalModelDisposables; return originalUri; @@ -1138,7 +1132,7 @@ export class DirtyDiffModel { dispose(): void { this.originalModelDisposables = dispose(this.originalModelDisposables); - this.disposables = dispose(this.disposables); + super.dispose(); this._editorModel = null; this._originalModel = null; @@ -1163,25 +1157,25 @@ class DirtyDiffItem { } } -export class DirtyDiffWorkbenchController implements ext.IWorkbenchContribution, IModelRegistry { +export class DirtyDiffWorkbenchController extends Disposable implements ext.IWorkbenchContribution, IModelRegistry { private enabled = false; private models: ITextModel[] = []; private items: { [modelId: string]: DirtyDiffItem; } = Object.create(null); private transientDisposables: IDisposable[] = []; private stylesheet: HTMLStyleElement; - private disposables: IDisposable[] = []; constructor( @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService ) { + super(); this.stylesheet = createStyleSheet(); - this.disposables.push(toDisposable(() => this.stylesheet.parentElement!.removeChild(this.stylesheet))); + this._register(toDisposable(() => this.stylesheet.parentElement!.removeChild(this.stylesheet))); const onDidChangeConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorations')); - onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); + this._register(onDidChangeConfiguration(this.onDidChangeConfiguration, this)); this.onDidChangeConfiguration(); const onDidChangeDiffWidthConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorationsGutterWidth')); @@ -1284,7 +1278,7 @@ export class DirtyDiffWorkbenchController implements ext.IWorkbenchContribution, dispose(): void { this.disable(); - this.disposables = dispose(this.disposables); + super.dispose(); } } diff --git a/src/vs/workbench/contrib/scm/browser/scmActivity.ts b/src/vs/workbench/contrib/scm/browser/scmActivity.ts index 2bbb403db14..3c64a29beda 100644 --- a/src/vs/workbench/contrib/scm/browser/scmActivity.ts +++ b/src/vs/workbench/contrib/scm/browser/scmActivity.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { basename } from 'vs/base/common/resources'; -import { IDisposable, dispose, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; @@ -46,7 +46,7 @@ export class StatusUpdater implements IWorkbenchContribution { this.render(); }); - const disposable = combinedDisposable([changeDisposable, removeDisposable]); + const disposable = combinedDisposable(changeDisposable, removeDisposable); this.disposables.push(disposable); } @@ -151,7 +151,7 @@ export class StatusBarController implements IWorkbenchContribution { } }); - const disposable = combinedDisposable([changeDisposable, removeDisposable]); + const disposable = combinedDisposable(changeDisposable, removeDisposable); this.disposables.push(disposable); if (!this.focusedRepository) { @@ -187,14 +187,17 @@ export class StatusBarController implements IWorkbenchContribution { ? `${basename(repository.provider.rootUri)} (${repository.provider.label})` : repository.provider.label; - const disposables = commands.map(c => this.statusbarService.addEntry({ - text: c.title, - tooltip: `${label} - ${c.tooltip}`, - command: c.id, - arguments: c.arguments - }, MainThreadStatusBarAlignment.LEFT, 10000)); + const disposables = new DisposableStore(); + for (const c of commands) { + disposables.push(this.statusbarService.addEntry({ + text: c.title, + tooltip: `${label} - ${c.tooltip}`, + command: c.id, + arguments: c.arguments + }, MainThreadStatusBarAlignment.LEFT, 10000)); + } - this.statusBarDisposable = combinedDisposable(disposables); + this.statusBarDisposable = disposables; } dispose(): void { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index caa82d3520f..b7a5b0887f8 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { basename } from 'vs/base/common/resources'; -import { IDisposable, dispose, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { append, $, addClass, toggleClass, trackFocus, removeClass, addClasses } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -162,14 +162,14 @@ class ProviderRenderer implements IListRenderer new StatusBarActionViewItem(a as StatusBarAction) }); const disposable = Disposable.None; - const templateDisposable = combinedDisposable([actionBar, badgeStyler]); + const templateDisposable = combinedDisposable(actionBar, badgeStyler); return { title, type, countContainer, count, actionBar, disposable, templateDisposable }; } renderElement(repository: ISCMRepository, index: number, templateData: RepositoryTemplateData): void { templateData.disposable.dispose(); - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); if (repository.provider.rootUri) { templateData.title.textContent = basename(repository.provider.rootUri); @@ -198,10 +198,10 @@ class ProviderRenderer implements IListRenderer template.count.setCount(group.elements.length); - group.onDidSplice(updateCount, null, disposables); + disposables.push(group.onDidSplice(updateCount, null)); updateCount(); - template.elementDisposable = combinedDisposable(disposables); + template.elementDisposable = disposables; } disposeElement(group: ISCMResourceGroup, index: number, template: ResourceGroupTemplate): void { @@ -489,7 +489,7 @@ class ResourceRenderer implements IListRenderer template.fileLabel.setFile(resource.sourceUri, { fileDecorations: { colors: false, badges: !icon, data: resource.decorations } }); template.actionBar.context = resource; - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); disposables.push(connectPrimaryMenuToInlineActionBar(this.menus.getResourceMenu(resource.resourceGroup), template.actionBar)); toggleClass(template.name, 'strike-through', resource.decorations.strikeThrough); @@ -505,7 +505,7 @@ class ResourceRenderer implements IListRenderer } template.element.setAttribute('data-tooltip', resource.decorations.tooltip || ''); - template.elementDisposable = combinedDisposable(disposables); + template.elementDisposable = disposables; } disposeElement(resource: ISCMResource, index: number, template: ResourceTemplate): void { @@ -602,10 +602,10 @@ class ResourceGroupSplicer { absoluteToInsert.push(element); } - const disposable = combinedDisposable([ + const disposable = combinedDisposable( group.onDidChange(() => this.onDidChangeGroup(group)), group.onDidSplice(splice => this.onDidSpliceGroup(group, splice)) - ]); + ); itemsToInsert.push({ group, visible, disposable }); } @@ -1106,15 +1106,15 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { super(VIEWLET_ID, SCMViewlet.STATE_KEY, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); this.menus = instantiationService.createInstance(SCMMenus, undefined); - this.menus.onDidChangeTitle(this.updateTitleArea, this, this.toDispose); + this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this)); this.message = $('.empty-message', { tabIndex: 0 }, localize('no open repo', "No source control providers registered.")); - configurationService.onDidChangeConfiguration(e => { + this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('scm.alwaysShowProviders')) { this.onDidChangeRepositories(); } - }, this.toDispose); + })); } create(parent: HTMLElement): void { @@ -1124,8 +1124,8 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { addClasses(parent, 'scm-viewlet', 'empty'); append(parent, this.message); - this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.toDispose); - this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.toDispose); + this._register(this.scmService.onDidAddRepository(this.onDidAddRepository, this)); + this._register(this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this)); this.scmService.repositories.forEach(r => this.onDidAddRepository(r)); } diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index 6599e4982e7..207662ab1ae 100644 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -156,12 +156,12 @@ export class OpenSymbolHandler extends QuickOpenHandler { return true; } - getResults(searchValue: string, token: CancellationToken): Promise { + async getResults(searchValue: string, token: CancellationToken): Promise { searchValue = searchValue.trim(); - let promise: Promise; + let entries: QuickOpenEntry[]; if (!this.options.skipDelay) { - promise = this.delayer.trigger(() => { + entries = await this.delayer.trigger(() => { if (token.isCancellationRequested) { return Promise.resolve([]); } @@ -169,32 +169,31 @@ export class OpenSymbolHandler extends QuickOpenHandler { return this.doGetResults(searchValue, token); }); } else { - promise = this.doGetResults(searchValue, token); + entries = await this.doGetResults(searchValue, token); } - return promise.then(e => new QuickOpenModel(e)); + return new QuickOpenModel(entries); } - private doGetResults(searchValue: string, token: CancellationToken): Promise { - return getWorkspaceSymbols(searchValue, token).then(tuples => { - if (token.isCancellationRequested) { - return []; - } + private async doGetResults(searchValue: string, token: CancellationToken): Promise { + const tuples = await getWorkspaceSymbols(searchValue, token); + if (token.isCancellationRequested) { + return []; + } - const result: SymbolEntry[] = []; - for (let tuple of tuples) { - const [provider, bearings] = tuple; - this.fillInSymbolEntries(result, provider, bearings, searchValue); - } + const result: SymbolEntry[] = []; + for (let tuple of tuples) { + const [provider, bearings] = tuple; + this.fillInSymbolEntries(result, provider, bearings, searchValue); + } - // Sort (Standalone only) - if (!this.options.skipSorting) { - searchValue = searchValue ? strings.stripWildcards(searchValue.toLowerCase()) : searchValue; - return result.sort((a, b) => SymbolEntry.compare(a, b, searchValue)); - } + // Sort (Standalone only) + if (!this.options.skipSorting) { + searchValue = searchValue ? strings.stripWildcards(searchValue.toLowerCase()) : searchValue; + return result.sort((a, b) => SymbolEntry.compare(a, b, searchValue)); + } - return result; - }); + return result; } private fillInSymbolEntries(bucket: SymbolEntry[], provider: IWorkspaceSymbolProvider, types: IWorkspaceSymbol[], searchValue: string): void { diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 23e6379be64..9ce59eb7572 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -33,7 +33,7 @@ import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/file import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TreeResourceNavigator2, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, SearchErrorCode, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -129,7 +129,7 @@ export class SearchView extends ViewletPanel { options: IViewletPanelOptions, @IFileService private readonly fileService: IFileService, @IEditorService private readonly editorService: IEditorService, - @IProgressService private readonly progressService: IProgressService, + @ILocalProgressService private readonly progressService: ILocalProgressService, @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @IContextViewService private readonly contextViewService: IContextViewService, diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index b659d7a0b75..73c9839bd60 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -5,7 +5,7 @@ import { join } from 'vs/base/common/path'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { combinedDisposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; import * as resources from 'vs/base/common/resources'; import { endsWith, isFalsyOrWhitespace } from 'vs/base/common/strings'; @@ -114,7 +114,7 @@ namespace snippetExt { } function watch(service: IFileService, resource: URI, callback: (type: FileChangeType, resource: URI) => any): IDisposable { - return combinedDisposable([ + return combinedDisposable( service.watch(resource), service.onFileChanges(e => { for (const change of e.changes) { @@ -123,7 +123,7 @@ function watch(service: IFileService, resource: URI, callback: (type: FileChange } } }) - ]); + ); } class SnippetsService implements ISnippetsService { @@ -295,9 +295,10 @@ class SnippetsService implements ISnippetsService { } private _initFolderSnippets(source: SnippetSource, folder: URI, bucket: IDisposable[]): Promise { - let disposables: IDisposable[] = []; + let disposables = new DisposableStore(); let addFolderSnippets = (type?: FileChangeType) => { - disposables = dispose(disposables); + disposables.dispose(); + disposables = new DisposableStore(); if (type === FileChangeType.DELETED) { return Promise.resolve(); } @@ -311,7 +312,7 @@ class SnippetsService implements ISnippetsService { }; bucket.push(watch(this._fileService, folder, addFolderSnippets)); - bucket.push(combinedDisposable(disposables)); + bucket.push(disposables); return addFolderSnippets(); } diff --git a/src/vs/workbench/contrib/stats/node/workspaceStats.ts b/src/vs/workbench/contrib/stats/node/workspaceStats.ts index c160f33c9bd..3305494a9c0 100644 --- a/src/vs/workbench/contrib/stats/node/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/node/workspaceStats.ts @@ -53,6 +53,7 @@ const ModulesToLookFor = [ // JS frameworks 'react', 'react-native', + 'rnpm-plugin-windows', '@angular/core', '@ionic', 'vue', @@ -268,6 +269,7 @@ export class WorkspaceStats implements IWorkbenchContribution { "workspace.npm.hapi" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.socket.io" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.restify" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.rnpm-plugin-windows" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.react" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@angular/core" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.vue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -288,6 +290,7 @@ export class WorkspaceStats implements IWorkbenchContribution { "workspace.reactNative" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.ionic" : { "classification" : "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": "true" }, "workspace.nativeScript" : { "classification" : "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": "true" }, + "workspace.java.pom" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.requirements" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.requirements.star" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.Pipfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -387,6 +390,8 @@ export class WorkspaceStats implements IWorkbenchContribution { tags['workspace.npm'] = nameSet.has('package.json') || nameSet.has('node_modules'); tags['workspace.bower'] = nameSet.has('bower.json') || nameSet.has('bower_components'); + tags['workspace.java.pom'] = nameSet.has('pom.xml'); + tags['workspace.yeoman.code.ext'] = nameSet.has('vsc-extension-quickstart.md'); tags['workspace.py.requirements'] = nameSet.has('requirements.txt'); @@ -502,23 +507,24 @@ export class WorkspaceStats implements IWorkbenchContribution { const packageJsonPromises = getFilePromises('package.json', this.fileService, this.textFileService, content => { try { const packageJsonContents = JSON.parse(content.value); - if (packageJsonContents['dependencies']) { - for (let module of ModulesToLookFor) { - if ('react-native' === module) { - if (packageJsonContents['dependencies'][module]) { - tags['workspace.reactNative'] = true; - } - } else if ('tns-core-modules' === module) { - if (packageJsonContents['dependencies'][module]) { - tags['workspace.nativescript'] = true; - } - } else { - if (packageJsonContents['dependencies'][module]) { - tags['workspace.npm.' + module] = true; - } + let dependencies = packageJsonContents['dependencies']; + let devDependencies = packageJsonContents['devDependencies']; + for (let module of ModulesToLookFor) { + if ('react-native' === module) { + if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) { + tags['workspace.reactNative'] = true; + } + } else if ('tns-core-modules' === module) { + if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) { + tags['workspace.nativescript'] = true; + } + } else { + if ((dependencies && dependencies[module]) || (devDependencies && devDependencies[module])) { + tags['workspace.npm.' + module] = true; } } } + } catch (e) { // Ignore errors when resolving file or parsing file contents diff --git a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts index 2a9a0b42a15..09a617f78d5 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts @@ -40,7 +40,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ProblemMatcherRegistry, NamedProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IProgressService2, IProgressOptions, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, IProgressOptions, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWindowService } from 'vs/platform/windows/common/windows'; @@ -482,7 +482,7 @@ class TaskService extends Disposable implements ITaskService { @IConfigurationResolverService private readonly configurationResolverService: IConfigurationResolverService, @ITerminalService private readonly terminalService: ITerminalService, @IStorageService private readonly storageService: IStorageService, - @IProgressService2 private readonly progressService: IProgressService2, + @IProgressService private readonly progressService: IProgressService, @IOpenerService private readonly openerService: IOpenerService, @IWindowService private readonly _windowService: IWindowService, @IDialogService private readonly dialogService: IDialogService, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 3fe9c77fdc6..6bc2ac9e721 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -748,10 +748,10 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem { ) { super(null, action, terminalService.getTabLabels().map(label => { text: label }), terminalService.activeTabIndex, contextViewService, { ariaLabel: nls.localize('terminals', 'Open Terminals.') }); - this.toDispose.push(terminalService.onInstancesChanged(this._updateItems, this)); - this.toDispose.push(terminalService.onActiveTabChanged(this._updateItems, this)); - this.toDispose.push(terminalService.onInstanceTitleChanged(this._updateItems, this)); - this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService)); + this._register(terminalService.onInstancesChanged(this._updateItems, this)); + this._register(terminalService.onActiveTabChanged(this._updateItems, this)); + this._register(terminalService.onInstanceTitleChanged(this._updateItems, this)); + this._register(attachSelectBoxStyler(this.selectBox, themeService)); } private _updateItems(): void { diff --git a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts index 52c7544f34d..1c53f04f3ff 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts @@ -37,7 +37,7 @@ export const TERMINAL_BORDER_COLOR = registerColor('terminal.border', { hc: PANEL_BORDER }, nls.localize('terminal.border', 'The color of the border that separates split panes within the terminal. This defaults to panel.border.')); -const ansiColorMap = { +export const ansiColorMap = { 'terminal.ansiBlack': { index: 0, defaults: { diff --git a/src/vs/workbench/contrib/update/electron-browser/update.ts b/src/vs/workbench/contrib/update/electron-browser/update.ts index 785e702c130..cea347aca03 100644 --- a/src/vs/workbench/contrib/update/electron-browser/update.ts +++ b/src/vs/workbench/contrib/update/electron-browser/update.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import severity from 'vs/base/common/severity'; import { IAction, Action } from 'vs/base/common/actions'; -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import pkg from 'vs/platform/product/node/package'; import product from 'vs/platform/product/node/product'; @@ -270,7 +270,7 @@ class CommandAction extends Action { } } -export class UpdateContribution implements IGlobalActivity { +export class UpdateContribution extends Disposable implements IGlobalActivity { private static readonly showCommandsId = 'workbench.action.showCommands'; private static readonly openSettingsId = 'workbench.action.openSettings'; @@ -286,7 +286,6 @@ export class UpdateContribution implements IGlobalActivity { private state: UpdateState; private badgeDisposable: IDisposable = Disposable.None; - private disposables: IDisposable[] = []; constructor( @IStorageService private readonly storageService: IStorageService, @@ -298,9 +297,10 @@ export class UpdateContribution implements IGlobalActivity { @IActivityService private readonly activityService: IActivityService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { + super(); this.state = updateService.state; - updateService.onStateChange(this.onUpdateStateChange, this, this.disposables); + this._register(updateService.onStateChange(this.onUpdateStateChange, this)); this.onUpdateStateChange(this.updateService.state); /* @@ -575,8 +575,4 @@ export class UpdateContribution implements IGlobalActivity { this.updateService.quitAndInstall()); } } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index b63e923abc6..7788f6e36a0 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./watermark'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { isMacintosh, OS } from 'vs/base/common/platform'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -102,9 +102,8 @@ const folderEntries = [ const WORKBENCH_TIPS_ENABLED_KEY = 'workbench.tips.enabled'; -export class WatermarkContribution implements IWorkbenchContribution { +export class WatermarkContribution extends Disposable implements IWorkbenchContribution { - private toDispose: IDisposable[] = []; private watermark: HTMLElement; private enabled: boolean; private workbenchState: WorkbenchState; @@ -117,6 +116,7 @@ export class WatermarkContribution implements IWorkbenchContribution { @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService ) { + super(); this.workbenchState = contextService.getWorkbenchState(); lifecycleService.onShutdown(this.dispose, this); @@ -124,7 +124,7 @@ export class WatermarkContribution implements IWorkbenchContribution { if (this.enabled) { this.create(); } - this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { + this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(WORKBENCH_TIPS_ENABLED_KEY)) { const enabled = this.configurationService.getValue(WORKBENCH_TIPS_ENABLED_KEY); if (enabled !== this.enabled) { @@ -137,7 +137,7 @@ export class WatermarkContribution implements IWorkbenchContribution { } } })); - this.toDispose.push(this.contextService.onDidChangeWorkbenchState(e => { + this._register(this.contextService.onDidChangeWorkbenchState(e => { const previousWorkbenchState = this.workbenchState; this.workbenchState = this.contextService.getWorkbenchState(); @@ -171,8 +171,8 @@ export class WatermarkContribution implements IWorkbenchContribution { }; update(); dom.prepend(container.firstElementChild as HTMLElement, this.watermark); - this.toDispose.push(this.keybindingService.onDidUpdateKeybindings(update)); - this.toDispose.push(this.editorGroupsService.onDidLayout(dimension => this.handleEditorPartSize(container, dimension))); + this._register(this.keybindingService.onDidUpdateKeybindings(update)); + this._register(this.editorGroupsService.onDidLayout(dimension => this.handleEditorPartSize(container, dimension))); this.handleEditorPartSize(container, this.editorGroupsService.dimension); } @@ -197,10 +197,6 @@ export class WatermarkContribution implements IWorkbenchContribution { this.destroy(); this.create(); } - - public dispose(): void { - this.toDispose = dispose(this.toDispose); - } } Registry.as(WorkbenchExtensions.Workbench) diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index 079afa85d16..f8384d60d4e 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -6,7 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -33,7 +33,7 @@ export class WebviewEditor extends BaseEditor { private _content?: HTMLElement; private _webviewContent: HTMLElement | undefined; - private _webviewFocusTrackerDisposables: IDisposable[] = []; + private _webviewFocusTrackerDisposables = new DisposableStore(); private _onFocusWindowHandler?: IDisposable; private readonly _onDidFocusWebview = this._register(new Emitter()); @@ -89,7 +89,7 @@ export class WebviewEditor extends BaseEditor { this._content = undefined; } - this._webviewFocusTrackerDisposables = dispose(this._webviewFocusTrackerDisposables); + this._webviewFocusTrackerDisposables.dispose(); if (this._onFocusWindowHandler) { this._onFocusWindowHandler.dispose(); @@ -304,7 +304,8 @@ export class WebviewEditor extends BaseEditor { } private trackFocus() { - this._webviewFocusTrackerDisposables = dispose(this._webviewFocusTrackerDisposables); + this._webviewFocusTrackerDisposables.dispose(); + this._webviewFocusTrackerDisposables = new DisposableStore(); // Track focus in webview content const webviewContentFocusTracker = DOM.trackFocus(this._webviewContent!); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 265f11a583f..371611936cc 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -509,7 +509,7 @@ export class WebviewElement extends Disposable implements Webview { } this.style(themeService.getTheme()); - themeService.onThemeChange(this.style, this, this._toDispose); + this._register(themeService.onThemeChange(this.style, this)); } public mountTo(parent: HTMLElement) { diff --git a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts index 84b1219c443..036cedba11f 100644 --- a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts +++ b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts @@ -15,7 +15,7 @@ import { Action } from 'vs/base/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -150,9 +150,8 @@ export class HideWelcomeOverlayAction extends Action { } } -class WelcomeOverlay { +class WelcomeOverlay extends Disposable { - private _toDispose: IDisposable[] = []; private _overlayVisible: IContextKey; private _overlay: HTMLElement; @@ -163,6 +162,7 @@ class WelcomeOverlay { @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IKeybindingService private readonly keybindingService: IKeybindingService ) { + super(); this._overlayVisible = OVERLAY_VISIBLE.bindTo(this._contextKeyService); this.create(); } @@ -177,7 +177,7 @@ class WelcomeOverlay { this._overlay.style.display = 'none'; this._overlay.tabIndex = -1; - this._toDispose.push(dom.addStandardDisposableListener(this._overlay, 'click', () => this.hide())); + this._register(dom.addStandardDisposableListener(this._overlay, 'click', () => this.hide())); this.commandService.onWillExecuteCommand(() => this.hide()); dom.append(this._overlay, $('.commandPalettePlaceholder')); @@ -237,10 +237,6 @@ class WelcomeOverlay { this._overlayVisible.reset(); } } - - dispose() { - this._toDispose = dispose(this._toDispose); - } } Registry.as(Extensions.WorkbenchActions) diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index b26ccbb854f..57ec2510560 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -25,7 +25,7 @@ import { getInstalledExtensions, IExtensionStatus, onExtensionChanged, isKeymapE import { IExtensionEnablementService, IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, EnablementState, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { used } from 'vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page'; import { ILifecycleService, StartupKind } from 'vs/platform/lifecycle/common/lifecycle'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { splitName } from 'vs/base/common/labels'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerColor, focusBorder, textLinkForeground, textLinkActiveForeground, foreground, descriptionForeground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -245,9 +245,7 @@ const keymapStrings: Strings = { const welcomeInputTypeId = 'workbench.editors.welcomePageInput'; -class WelcomePage { - - private disposables: IDisposable[] = []; +class WelcomePage extends Disposable { readonly editorInput: WalkThroughInput; @@ -267,7 +265,8 @@ class WelcomePage { @ILifecycleService lifecycleService: ILifecycleService, @ITelemetryService private readonly telemetryService: ITelemetryService ) { - this.disposables.push(lifecycleService.onShutdown(() => this.dispose())); + super(); + this._register(lifecycleService.onShutdown(() => this.dispose())); const recentlyOpened = this.windowService.getRecentlyOpened(); const installedExtensions = this.instantiationService.invokeFunction(getInstalledExtensions); @@ -321,14 +320,14 @@ class WelcomePage { ul.append(...listEntries, moreRecent); }; updateEntries(); - this.disposables.push(this.labelService.onDidChangeFormatters(updateEntries)); + this._register(this.labelService.onDidChangeFormatters(updateEntries)); }).then(undefined, onUnexpectedError); this.addExtensionList(container, '.extensionPackList', extensionPacks, extensionPackStrings); this.addExtensionList(container, '.keymapList', keymapExtensions, keymapStrings); this.updateInstalledExtensions(container, installedExtensions); - this.disposables.push(this.instantiationService.invokeFunction(onExtensionChanged)(ids => { + this._register(this.instantiationService.invokeFunction(onExtensionChanged)(ids => { for (const id of ids) { if (container.querySelector(`.installExtension[data-extension="${id.id}"], .enabledExtension[data-extension="${id.id}"]`)) { const installedExtensions = this.instantiationService.invokeFunction(getInstalledExtensions); @@ -593,10 +592,6 @@ class WelcomePage { }); }).then(undefined, onUnexpectedError); } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } export class WelcomeInputFactory implements IEditorInputFactory { diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index a51b73abb56..0c84c54382e 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -8,7 +8,7 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { EditorOptions, IEditorMemento } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -56,7 +56,7 @@ export class WalkThroughPart extends BaseEditor { static readonly ID: string = 'workbench.editor.walkThroughPart'; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); private contentDisposables: IDisposable[] = []; private content: HTMLDivElement; private scrollbar: DomScrollableElement; @@ -514,7 +514,7 @@ export class WalkThroughPart extends BaseEditor { dispose(): void { this.editorFocus.reset(); this.contentDisposables = dispose(this.contentDisposables); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } diff --git a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts index 42feb610289..6d69a2758f1 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts @@ -6,7 +6,7 @@ import * as strings from 'vs/base/common/strings'; import { EditorInput, EditorModel, ITextEditorModel } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { IReference, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IReference } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import * as marked from 'vs/base/common/marked/marked'; import { Schemas } from 'vs/base/common/network'; @@ -46,8 +46,6 @@ export interface WalkThroughInputOptions { export class WalkThroughInput extends EditorInput { - private disposables: IDisposable[] = []; - private promise: Promise | null = null; private maxTopScroll = 0; @@ -139,8 +137,6 @@ export class WalkThroughInput extends EditorInput { } dispose(): void { - this.disposables = dispose(this.disposables); - if (this.promise) { this.promise.then(model => model.dispose()); this.promise = null; diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 40b009039a2..54eb96b7bc3 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -230,7 +230,7 @@ class CodeRendererMain extends Disposable { // Multi-root workspace if (this.configuration.workspace) { - return Promise.resolve(this.configuration.workspace); + return this.configuration.workspace; } // Single-folder workspace @@ -260,7 +260,7 @@ class CodeRendererMain extends Disposable { // Return early the folder is not local if (folderUri.scheme !== Schemas.file) { - return Promise.resolve({ id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri }); + return { id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri }; } function computeLocalDiskFolderId(folder: URI, stat: fs.Stats): string { diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/node/backupFileService.ts index 850fe4b103a..624ec71cad0 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/node/backupFileService.ts @@ -352,7 +352,7 @@ class BackupFileServiceImpl implements IBackupFileService { export class InMemoryBackupFileService implements IBackupFileService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private backups: Map = new Map(); diff --git a/src/vs/workbench/services/configuration/common/jsonEditingService.ts b/src/vs/workbench/services/configuration/common/jsonEditingService.ts index baeccb314dd..9313c53ccb9 100644 --- a/src/vs/workbench/services/configuration/common/jsonEditingService.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditingService.ts @@ -39,10 +39,11 @@ export class JSONEditingService implements IJSONEditingService { return Promise.resolve(this.queue.queue(() => this.doWriteConfiguration(resource, value, save))); // queue up writes to prevent race conditions } - private doWriteConfiguration(resource: URI, value: IJSONValue, save: boolean): Promise { - return this.resolveAndValidate(resource, save) - .then(reference => this.writeToBuffer(reference.object.textEditorModel, value) - .then(() => reference.dispose())); + private async doWriteConfiguration(resource: URI, value: IJSONValue, save: boolean): Promise { + const reference = await this.resolveAndValidate(resource, save); + await this.writeToBuffer(reference.object.textEditorModel, value); + + reference.dispose(); } private async writeToBuffer(model: ITextModel, value: IJSONValue): Promise { @@ -97,21 +98,21 @@ export class JSONEditingService implements IJSONEditingService { return parseErrors.length > 0; } - private resolveAndValidate(resource: URI, checkDirty: boolean): Promise> { - return this.resolveModelReference(resource) - .then(reference => { - const model = reference.object.textEditorModel; + private async resolveAndValidate(resource: URI, checkDirty: boolean): Promise> { + const reference = await this.resolveModelReference(resource); - if (this.hasParseErrors(model)) { - return this.reject>(JSONEditingErrorCode.ERROR_INVALID_FILE); - } + const model = reference.object.textEditorModel; - // Target cannot be dirty if not writing into buffer - if (checkDirty && this.textFileService.isDirty(resource)) { - return this.reject>(JSONEditingErrorCode.ERROR_FILE_DIRTY); - } - return reference; - }); + if (this.hasParseErrors(model)) { + return this.reject>(JSONEditingErrorCode.ERROR_INVALID_FILE); + } + + // Target cannot be dirty if not writing into buffer + if (checkDirty && this.textFileService.isDirty(resource)) { + return this.reject>(JSONEditingErrorCode.ERROR_FILE_DIRTY); + } + + return reference; } private reject(code: JSONEditingErrorCode): Promise { diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index 28e5a9f5fe7..b183a2629d8 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { IDecorationsService, IDecoration, IResourceDecorationChangeEvent, IDecorationsProvider, IDecorationData } from './decorations'; import { TernarySearchTree } from 'vs/base/common/map'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { isThenable } from 'vs/base/common/async'; import { LinkedList } from 'vs/base/common/linkedList'; import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; @@ -95,20 +95,20 @@ class DecorationRule { } } -class DecorationStyles { +class DecorationStyles extends Disposable { - private readonly _disposables: IDisposable[]; private readonly _styleElement = createStyleSheet(); private readonly _decorationRules = new Map(); constructor( private _themeService: IThemeService, ) { - this._disposables = [this._themeService.onThemeChange(this._onThemeChange, this)]; + super(); + this._register(this._themeService.onThemeChange(this._onThemeChange, this)); } dispose(): void { - dispose(this._disposables); + super.dispose(); const parent = this._styleElement.parentElement; if (parent) { diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index 9ff5db4854a..c5792b4a904 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -86,6 +86,7 @@ export class FileDialogService implements IFileDialogService { private shouldUseSimplified(schema: string): boolean { const setting = this.configurationService.getValue('files.simpleDialog.enable'); + return (schema !== Schemas.file) || (setting === true); } @@ -93,7 +94,7 @@ export class FileDialogService implements IFileDialogService { return schema !== Schemas.file ? [schema, Schemas.file] : [schema]; } - pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { + async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { @@ -103,21 +104,23 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder'); const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well - return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => { - if (uri) { - return (this.fileService.resolve(uri)).then(stat => { - const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; - return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); - }); - } - return undefined; - }); + + const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + + if (uri) { + const stat = await this.fileService.resolve(uri); + + const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; + return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); + } + + return; } return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options)); } - pickFileAndOpen(options: IPickAndOpenOptions): Promise { + async pickFileAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { @@ -127,18 +130,19 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFile.title', 'Open File'); const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well - return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => { - if (uri) { - return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - return undefined; - }); + + const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + + return; } return this.windowService.pickFileAndOpen(this.toNativeOpenDialogOptions(options)); } - pickFolderAndOpen(options: IPickAndOpenOptions): Promise { + async pickFolderAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { @@ -148,18 +152,19 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFolder.title', 'Open Folder'); const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well - return this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => { - if (uri) { - return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - return undefined; - }); + + const uri = await this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + + return; } return this.windowService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options)); } - pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { + async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { @@ -170,12 +175,13 @@ export class FileDialogService implements IFileDialogService { const title = nls.localize('openWorkspace.title', 'Open Workspace'); const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }]; const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well - return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }).then(uri => { - if (uri) { - return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - return undefined; - }); + + const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + + return; } return this.windowService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options)); @@ -190,33 +196,34 @@ export class FileDialogService implements IFileDialogService { }; } - showSaveDialog(options: ISaveDialogOptions): Promise { + async showSaveDialog(options: ISaveDialogOptions): Promise { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { options.availableFileSystems = [schema]; // by default only allow saving in the own file system } + return this.saveRemoteResource(options); } - return this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)).then(result => { - if (result) { - return URI.file(result); - } + const result = await this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)); + if (result) { + return URI.file(result); + } - return undefined; - }); + return; } - showOpenDialog(options: IOpenDialogOptions): Promise { + async showOpenDialog(options: IOpenDialogOptions): Promise { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { options.availableFileSystems = [schema]; // by default only allow loading in the own file system } - return this.pickRemoteResource(options).then(uri => { - return uri ? [uri] : undefined; - }); + + const uri = await this.pickRemoteResource(options); + + return uri ? [uri] : undefined; } const defaultUri = options.defaultUri; @@ -243,16 +250,20 @@ export class FileDialogService implements IFileDialogService { newOptions.properties!.push('multiSelections'); } - return this.windowService.showOpenDialog(newOptions).then(result => result ? result.map(URI.file) : undefined); + const result = await this.windowService.showOpenDialog(newOptions); + + return result ? result.map(URI.file) : undefined; } private pickRemoteResource(options: IOpenDialogOptions): Promise { const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); + return remoteFileDialog.showOpenDialog(options); } private saveRemoteResource(options: ISaveDialogOptions): Promise { const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); + return remoteFileDialog.showSaveDialog(options); } @@ -263,7 +274,6 @@ export class FileDialogService implements IFileDialogService { private getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string { return options.availableFileSystems && options.availableFileSystems[0] || options.defaultUri && options.defaultUri.scheme || this.getSchemeFilterForWindow(); } - } function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean { diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 584d5ce4031..683b344b9dd 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -650,18 +650,17 @@ export class DelegatingEditorService extends EditorService { this.editorOpenHandler = handler; } - protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { + protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { if (!this.editorOpenHandler) { return super.doOpenEditor(group, editor, options); } - return this.editorOpenHandler(group, editor, options).then(control => { - if (control) { - return control; // the opening was handled, so return early - } + const control = await this.editorOpenHandler(group, editor, options); + if (control) { + return control; // the opening was handled, so return early + } - return super.doOpenEditor(group, editor, options); - }); + return super.doOpenEditor(group, editor, options); } } diff --git a/src/vs/workbench/services/extensionManagement/node/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/node/extensionEnablementService.ts index 2cc63fe6305..4e1a3788010 100644 --- a/src/vs/workbench/services/extensionManagement/node/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/node/extensionEnablementService.ts @@ -14,8 +14,9 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { isUndefinedOrNull } from 'vs/base/common/types'; import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; +import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IProductService } from 'vs/platform/product/common/product'; const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled'; const ENABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/enabled'; @@ -36,6 +37,7 @@ export class ExtensionEnablementService extends Disposable implements IExtension @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IConfigurationService private readonly configurationService: IConfigurationService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IProductService private readonly productService: IProductService, ) { super(); this.storageManger = this._register(new StorageManager(storageService)); @@ -137,7 +139,7 @@ export class ExtensionEnablementService extends Disposable implements IExtension return disabledExtensions.some(id => areSameExtensions({ id }, extension.identifier)); } if (this.environmentService.configuration.remoteAuthority) { - const server = isUIExtension(extension.manifest, this.configurationService) ? this.extensionManagementServerService.localExtensionManagementServer : this.extensionManagementServerService.remoteExtensionManagementServer; + const server = isUIExtension(extension.manifest, this.productService, this.configurationService) ? this.extensionManagementServerService.localExtensionManagementServer : this.extensionManagementServerService.remoteExtensionManagementServer; return this.extensionManagementServerService.getExtensionManagementServer(extension.location) !== server; } return false; diff --git a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts index 9faa4c1c7ba..cdb4509a617 100644 --- a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts @@ -15,6 +15,7 @@ import { IExtensionContributions, ExtensionType, IExtension } from 'vs/platform/ import { isUndefinedOrNull } from 'vs/base/common/types'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ProductService } from 'vs/platform/product/node/productService'; function storageService(instantiationService: TestInstantiationService): IStorageService { let service = instantiationService.get(IStorageService); @@ -38,7 +39,9 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { instantiationService.get(IWorkbenchEnvironmentService) || instantiationService.stub(IWorkbenchEnvironmentService, { configuration: Object.create(null) } as IWorkbenchEnvironmentService), instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, { onDidInstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event } as IExtensionManagementService), - instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService)); + instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService), + new ProductService() + ); } public reset(): void { diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts new file mode 100644 index 00000000000..23fa0b21c4a --- /dev/null +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IProductService } from 'vs/platform/product/common/product'; +import { AbstractExtensionService } from 'vs/workbench/services/extensions/common/abstractExtensionService'; +import { browserWebSocketFactory } from 'vs/platform/remote/browser/browserWebSocketFactory'; +import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager'; +import { RemoteExtensionHostClient, IInitDataProvider } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient'; +import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; + +export class ExtensionService extends AbstractExtensionService implements IExtensionService { + + private _remoteExtensionsEnvironmentData: IRemoteAgentEnvironment | null; + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @INotificationService notificationService: INotificationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ITelemetryService telemetryService: ITelemetryService, + @IExtensionEnablementService extensionEnablementService: IExtensionEnablementService, + @IFileService fileService: IFileService, + @IProductService productService: IProductService, + @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, + ) { + super( + instantiationService, + notificationService, + environmentService, + telemetryService, + extensionEnablementService, + fileService, + productService, + ); + + this._remoteExtensionsEnvironmentData = null; + this._initialize(); + } + + private _createProvider(remoteAuthority: string): IInitDataProvider { + return { + remoteAuthority: remoteAuthority, + getInitData: () => { + return this.whenInstalledExtensionsRegistered().then(() => { + return this._remoteExtensionsEnvironmentData!; + }); + } + }; + } + + protected _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[] { + const result: ExtensionHostProcessManager[] = []; + + const remoteAgentConnection = this._remoteAgentService.getConnection()!; + const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority), browserWebSocketFactory); + const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); + result.push(remoteExtHostProcessManager); + + return result; + } + + protected async _scanAndHandleExtensions(): Promise { + // fetch the remote environment + const remoteEnv = (await this._remoteAgentService.getEnvironment())!; + + // enable or disable proposed API per extension + this._checkEnableProposedApi(remoteEnv.extensions); + + // remove disabled extensions + remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension)); + + // save for remote extension's init data + this._remoteExtensionsEnvironmentData = remoteEnv; + + // this._handleExtensionPoints(([]).concat(remoteEnv.extensions).concat(localExtensions)); + const result = this._registry.deltaExtensions(remoteEnv.extensions, []); + if (result.removedDueToLooping.length > 0) { + this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', '))); + } + + this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions()); + } + + public _onExtensionHostExit(code: number): void { + console.log(`vscode:exit`, code); + // ipc.send('vscode:exit', code); + } +} + +registerSingleton(IExtensionService, ExtensionService); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts new file mode 100644 index 00000000000..80418f5fad8 --- /dev/null +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -0,0 +1,498 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { Barrier } from 'vs/base/common/async'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import * as perf from 'vs/base/common/performance'; +import { isEqualOrParent } from 'vs/base/common/resources'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { BetterMergeId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; +import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; +import { IProductService } from 'vs/platform/product/common/product'; + +const hasOwnProperty = Object.hasOwnProperty; +const NO_OP_VOID_PROMISE = Promise.resolve(undefined); + +export abstract class AbstractExtensionService extends Disposable implements IExtensionService { + + public _serviceBrand: any; + + protected readonly _onDidRegisterExtensions: Emitter = this._register(new Emitter()); + public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event; + + protected readonly _onDidChangeExtensionsStatus: Emitter = this._register(new Emitter()); + public readonly onDidChangeExtensionsStatus: Event = this._onDidChangeExtensionsStatus.event; + + protected readonly _onDidChangeExtensions: Emitter = this._register(new Emitter()); + public readonly onDidChangeExtensions: Event = this._onDidChangeExtensions.event; + + protected readonly _onWillActivateByEvent = this._register(new Emitter()); + public readonly onWillActivateByEvent: Event = this._onWillActivateByEvent.event; + + protected readonly _onDidChangeResponsiveChange = this._register(new Emitter()); + public readonly onDidChangeResponsiveChange: Event = this._onDidChangeResponsiveChange.event; + + protected readonly _registry: ExtensionDescriptionRegistry; + private readonly _installedExtensionsReady: Barrier; + protected readonly _isDev: boolean; + private readonly _extensionsMessages: Map; + protected readonly _allRequestedActivateEvents: { [activationEvent: string]: boolean; }; + private readonly _proposedApiController: ProposedApiController; + private readonly _isExtensionDevHost: boolean; + protected readonly _isExtensionDevTestFromCli: boolean; + + // --- Members used per extension host process + protected _extensionHostProcessManagers: ExtensionHostProcessManager[]; + protected _extensionHostActiveExtensions: Map; + private _extensionHostProcessActivationTimes: Map; + private _extensionHostExtensionRuntimeErrors: Map; + + constructor( + @IInstantiationService protected readonly _instantiationService: IInstantiationService, + @INotificationService protected readonly _notificationService: INotificationService, + @IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService, + @ITelemetryService protected readonly _telemetryService: ITelemetryService, + @IExtensionEnablementService protected readonly _extensionEnablementService: IExtensionEnablementService, + @IFileService protected readonly _fileService: IFileService, + @IProductService protected readonly _productService: IProductService + ) { + super(); + + // help the file service to activate providers by activating extensions by file system event + this._register(this._fileService.onWillActivateFileSystemProvider(e => { + e.join(this.activateByEvent(`onFileSystem:${e.scheme}`)); + })); + + this._registry = new ExtensionDescriptionRegistry([]); + this._installedExtensionsReady = new Barrier(); + this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment; + this._extensionsMessages = new Map(); + this._allRequestedActivateEvents = Object.create(null); + this._proposedApiController = new ProposedApiController(this._environmentService, this._productService); + + this._extensionHostProcessManagers = []; + this._extensionHostActiveExtensions = new Map(); + this._extensionHostProcessActivationTimes = new Map(); + this._extensionHostExtensionRuntimeErrors = new Map(); + + const devOpts = parseExtensionDevOptions(this._environmentService); + this._isExtensionDevHost = devOpts.isExtensionDevHost; + this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli; + } + + protected async _initialize(): Promise { + perf.mark('willLoadExtensions'); + this._startExtensionHostProcess(true, []); + this.whenInstalledExtensionsRegistered().then(() => perf.mark('didLoadExtensions')); + await this._scanAndHandleExtensions(); + this._releaseBarrier(); + } + + private _releaseBarrier(): void { + perf.mark('extensionHostReady'); + this._installedExtensionsReady.open(); + this._onDidRegisterExtensions.fire(undefined); + this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier)); + } + + private _stopExtensionHostProcess(): void { + let previouslyActivatedExtensionIds: ExtensionIdentifier[] = []; + this._extensionHostActiveExtensions.forEach((value) => { + previouslyActivatedExtensionIds.push(value); + }); + + for (const manager of this._extensionHostProcessManagers) { + manager.dispose(); + } + this._extensionHostProcessManagers = []; + this._extensionHostActiveExtensions = new Map(); + this._extensionHostProcessActivationTimes = new Map(); + this._extensionHostExtensionRuntimeErrors = new Map(); + + if (previouslyActivatedExtensionIds.length > 0) { + this._onDidChangeExtensionsStatus.fire(previouslyActivatedExtensionIds); + } + } + + private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void { + this._stopExtensionHostProcess(); + + const processManagers = this._createExtensionHosts(isInitialStart, initialActivationEvents); + processManagers.forEach((processManager) => { + processManager.onDidExit(([code, signal]) => this._onExtensionHostCrashOrExit(processManager, code, signal)); + processManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); }); + this._extensionHostProcessManagers.push(processManager); + }); + } + + private _onExtensionHostCrashOrExit(extensionHost: ExtensionHostProcessManager, code: number, signal: string | null): void { + + // Unexpected termination + if (!this._isExtensionDevHost) { + this._onExtensionHostCrashed(extensionHost, code, signal); + return; + } + + this._onExtensionHostExit(code); + } + + protected _onExtensionHostCrashed(extensionHost: ExtensionHostProcessManager, code: number, signal: string | null): void { + console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal); + this._stopExtensionHostProcess(); + } + + //#region IExtensionService + + public canAddExtension(extension: IExtensionDescription): boolean { + return false; + } + + public canRemoveExtension(extension: IExtensionDescription): boolean { + return false; + } + + public restartExtensionHost(): void { + this._stopExtensionHostProcess(); + this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)); + } + + public startExtensionHost(): void { + this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)); + } + + public stopExtensionHost(): void { + this._stopExtensionHostProcess(); + } + + public activateByEvent(activationEvent: string): Promise { + if (this._installedExtensionsReady.isOpen()) { + // Extensions have been scanned and interpreted + + // Record the fact that this activationEvent was requested (in case of a restart) + this._allRequestedActivateEvents[activationEvent] = true; + + if (!this._registry.containsActivationEvent(activationEvent)) { + // There is no extension that is interested in this activation event + return NO_OP_VOID_PROMISE; + } + + return this._activateByEvent(activationEvent); + } else { + // Extensions have not been scanned yet. + + // Record the fact that this activationEvent was requested (in case of a restart) + this._allRequestedActivateEvents[activationEvent] = true; + + return this._installedExtensionsReady.wait().then(() => this._activateByEvent(activationEvent)); + } + } + + private _activateByEvent(activationEvent: string): Promise { + const result = Promise.all( + this._extensionHostProcessManagers.map(extHostManager => extHostManager.activateByEvent(activationEvent)) + ).then(() => { }); + this._onWillActivateByEvent.fire({ + event: activationEvent, + activation: result + }); + return result; + } + + public whenInstalledExtensionsRegistered(): Promise { + return this._installedExtensionsReady.wait(); + } + + public getExtensions(): Promise { + return this._installedExtensionsReady.wait().then(() => { + return this._registry.getAllExtensionDescriptions(); + }); + } + + public getExtension(id: string): Promise { + return this._installedExtensionsReady.wait().then(() => { + return this._registry.getExtensionDescription(id); + }); + } + + public readExtensionPointContributions(extPoint: IExtensionPoint): Promise[]> { + return this._installedExtensionsReady.wait().then(() => { + let availableExtensions = this._registry.getAllExtensionDescriptions(); + + let result: ExtensionPointContribution[] = [], resultLen = 0; + for (let i = 0, len = availableExtensions.length; i < len; i++) { + let desc = availableExtensions[i]; + + if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) { + result[resultLen++] = new ExtensionPointContribution(desc, desc.contributes[extPoint.name]); + } + } + + return result; + }); + } + + public getExtensionsStatus(): { [id: string]: IExtensionsStatus; } { + let result: { [id: string]: IExtensionsStatus; } = Object.create(null); + if (this._registry) { + const extensions = this._registry.getAllExtensionDescriptions(); + for (const extension of extensions) { + const extensionKey = ExtensionIdentifier.toKey(extension.identifier); + result[extension.identifier.value] = { + messages: this._extensionsMessages.get(extensionKey) || [], + activationTimes: this._extensionHostProcessActivationTimes.get(extensionKey), + runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey) || [], + }; + } + } + return result; + } + + public getInspectPort(): number { + return 0; + } + + //#endregion + + // --- impl + + protected _checkEnableProposedApi(extensions: IExtensionDescription[]): void { + for (let extension of extensions) { + this._proposedApiController.updateEnableProposedApi(extension); + } + } + + private _isExtensionUnderDevelopment(extension: IExtensionDescription): boolean { + if (this._environmentService.isExtensionDevelopment) { + const extDevLocs = this._environmentService.extensionDevelopmentLocationURI; + if (extDevLocs) { + const extLocation = extension.extensionLocation; + for (let p of extDevLocs) { + if (isEqualOrParent(extLocation, p)) { + return true; + } + } + } + } + return false; + } + + protected _isEnabled(extension: IExtensionDescription): boolean { + if (this._isExtensionUnderDevelopment(extension)) { + // Never disable extensions under development + return true; + } + + if (ExtensionIdentifier.equals(extension.identifier, BetterMergeId)) { + // Check if this is the better merge extension which was migrated to a built-in extension + return false; + } + + return this._extensionEnablementService.isEnabled(toExtension(extension)); + } + + protected _doHandleExtensionPoints(affectedExtensions: IExtensionDescription[]): void { + const affectedExtensionPoints: { [extPointName: string]: boolean; } = Object.create(null); + for (let extensionDescription of affectedExtensions) { + if (extensionDescription.contributes) { + for (let extPointName in extensionDescription.contributes) { + if (hasOwnProperty.call(extensionDescription.contributes, extPointName)) { + affectedExtensionPoints[extPointName] = true; + } + } + } + } + + const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); + const availableExtensions = this._registry.getAllExtensionDescriptions(); + const extensionPoints = ExtensionsRegistry.getExtensionPoints(); + for (let i = 0, len = extensionPoints.length; i < len; i++) { + if (affectedExtensionPoints[extensionPoints[i].name]) { + AbstractExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler); + } + } + } + + private _handleExtensionPointMessage(msg: IMessage) { + const extensionKey = ExtensionIdentifier.toKey(msg.extensionId); + + if (!this._extensionsMessages.has(extensionKey)) { + this._extensionsMessages.set(extensionKey, []); + } + this._extensionsMessages.get(extensionKey)!.push(msg); + + const extension = this._registry.getExtensionDescription(msg.extensionId); + const strMsg = `[${msg.extensionId.value}]: ${msg.message}`; + if (extension && extension.isUnderDevelopment) { + // This message is about the extension currently being developed + this._showMessageToUser(msg.type, strMsg); + } else { + this._logMessageInConsole(msg.type, strMsg); + } + + if (!this._isDev && msg.extensionId) { + const { type, extensionId, extensionPointId, message } = msg; + /* __GDPR__ + "extensionsMessage" : { + "type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "extensionId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "extensionPointId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "message": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } + */ + this._telemetryService.publicLog('extensionsMessage', { + type, extensionId: extensionId.value, extensionPointId, message + }); + } + } + + private static _handleExtensionPoint(extensionPoint: ExtensionPoint, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void { + let users: IExtensionPointUser[] = [], usersLen = 0; + for (let i = 0, len = availableExtensions.length; i < len; i++) { + let desc = availableExtensions[i]; + + if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) { + users[usersLen++] = { + description: desc, + value: desc.contributes[extensionPoint.name], + collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name) + }; + } + } + + extensionPoint.acceptUsers(users); + } + + private _showMessageToUser(severity: Severity, msg: string): void { + if (severity === Severity.Error || severity === Severity.Warning) { + this._notificationService.notify({ severity, message: msg }); + } else { + this._logMessageInConsole(severity, msg); + } + } + + private _logMessageInConsole(severity: Severity, msg: string): void { + if (severity === Severity.Error) { + console.error(msg); + } else if (severity === Severity.Warning) { + console.warn(msg); + } else { + console.log(msg); + } + } + + //#region Called by extension host + + public _logOrShowMessage(severity: Severity, msg: string): void { + if (this._isDev) { + this._showMessageToUser(severity, msg); + } else { + this._logMessageInConsole(severity, msg); + } + } + + public async _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise { + const results = await Promise.all( + this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, activationEvent)) + ); + const activated = results.some(e => e); + if (!activated) { + throw new Error(`Unknown extension ${extensionId.value}`); + } + } + + public _onWillActivateExtension(extensionId: ExtensionIdentifier): void { + this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId); + } + + public _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void { + this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent)); + this._onDidChangeExtensionsStatus.fire([extensionId]); + } + + public _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void { + const extensionKey = ExtensionIdentifier.toKey(extensionId); + if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) { + this._extensionHostExtensionRuntimeErrors.set(extensionKey, []); + } + this._extensionHostExtensionRuntimeErrors.get(extensionKey)!.push(err); + this._onDidChangeExtensionsStatus.fire([extensionId]); + } + + //#endregion + + protected abstract _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[]; + protected abstract _scanAndHandleExtensions(): Promise; + public abstract _onExtensionHostExit(code: number): void; +} + +class ProposedApiController { + + private readonly enableProposedApiFor: string | string[]; + private readonly enableProposedApiForAll: boolean; + private readonly productAllowProposedApi: Set; + + constructor( + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IProductService productService: IProductService + ) { + this.enableProposedApiFor = environmentService.args['enable-proposed-api'] || []; + if (this.enableProposedApiFor.length) { + // Make enabled proposed API be lowercase for case insensitive comparison + if (Array.isArray(this.enableProposedApiFor)) { + this.enableProposedApiFor = this.enableProposedApiFor.map(id => id.toLowerCase()); + } else { + this.enableProposedApiFor = this.enableProposedApiFor.toLowerCase(); + } + } + + this.enableProposedApiForAll = !environmentService.isBuilt || + (!!environmentService.extensionDevelopmentLocationURI && productService.nameLong !== 'Visual Studio Code') || + (this.enableProposedApiFor.length === 0 && 'enable-proposed-api' in environmentService.args); + + this.productAllowProposedApi = new Set(); + if (isNonEmptyArray(productService.extensionAllowedProposedApi)) { + productService.extensionAllowedProposedApi.forEach((id) => this.productAllowProposedApi.add(ExtensionIdentifier.toKey(id))); + } + } + + public updateEnableProposedApi(extension: IExtensionDescription): void { + if (this._allowProposedApiFromProduct(extension.identifier)) { + // fast lane -> proposed api is available to all extensions + // that are listed in product.json-files + extension.enableProposedApi = true; + + } else if (extension.enableProposedApi && !extension.isBuiltin) { + if ( + !this.enableProposedApiForAll && + this.enableProposedApiFor.indexOf(extension.identifier.value.toLowerCase()) < 0 + ) { + extension.enableProposedApi = false; + console.error(`Extension '${extension.identifier.value} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`); + + } else { + // proposed api is available when developing or when an extension was explicitly + // spelled out via a command line argument + console.warn(`Extension '${extension.identifier.value}' uses PROPOSED API which is subject to change and removal without notice.`); + } + } + } + + private _allowProposedApiFromProduct(id: ExtensionIdentifier): boolean { + return this.productAllowProposedApi.has(ExtensionIdentifier.toKey(id)); + } +} diff --git a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts index 4e63fa92494..c3f651cd23f 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts @@ -35,7 +35,7 @@ const NO_OP_VOID_PROMISE = Promise.resolve(undefined); export class ExtensionHostProcessManager extends Disposable { - public readonly onDidCrash: Event<[number, string | null]>; + public readonly onDidExit: Event<[number, string | null]>; private readonly _onDidChangeResponsiveState: Emitter = this._register(new Emitter()); public readonly onDidChangeResponsiveState: Event = this._onDidChangeResponsiveState.event; @@ -54,6 +54,7 @@ export class ExtensionHostProcessManager extends Disposable { private _resolveAuthorityAttempt: number; constructor( + public readonly isLocal: boolean, extensionHostProcessWorker: IExtensionHostStarter, private readonly _remoteAuthority: string, initialActivationEvents: string[], @@ -66,7 +67,7 @@ export class ExtensionHostProcessManager extends Disposable { this._extensionHostProcessCustomers = []; this._extensionHostProcessWorker = extensionHostProcessWorker; - this.onDidCrash = this._extensionHostProcessWorker.onCrashed; + this.onDidExit = this._extensionHostProcessWorker.onExit; this._extensionHostProcessProxy = this._extensionHostProcessWorker.start()!.then( (protocol) => { return { value: this._createExtensionHostCustomers(protocol) }; diff --git a/src/vs/workbench/services/extensions/common/extensionPoints.ts b/src/vs/workbench/services/extensions/common/extensionPoints.ts new file mode 100644 index 00000000000..58a5a483575 --- /dev/null +++ b/src/vs/workbench/services/extensions/common/extensionPoints.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Severity } from 'vs/platform/notification/common/notification'; + +export interface Translations { + [id: string]: string; +} + +export namespace Translations { + export function equals(a: Translations, b: Translations): boolean { + if (a === b) { + return true; + } + let aKeys = Object.keys(a); + let bKeys: Set = new Set(); + for (let key of Object.keys(b)) { + bKeys.add(key); + } + if (aKeys.length !== bKeys.size) { + return false; + } + + for (let key of aKeys) { + if (a[key] !== b[key]) { + return false; + } + bKeys.delete(key); + } + return bKeys.size === 0; + } +} + +export interface ILog { + error(source: string, message: string): void; + warn(source: string, message: string): void; + info(source: string, message: string): void; +} + +export class Logger implements ILog { + + private readonly _messageHandler: (severity: Severity, source: string, message: string) => void; + + constructor( + messageHandler: (severity: Severity, source: string, message: string) => void + ) { + this._messageHandler = messageHandler; + } + + public error(source: string, message: string): void { + this._messageHandler(Severity.Error, source, message); + } + + public warn(source: string, message: string): void { + this._messageHandler(Severity.Warning, source, message); + } + + public info(source: string, message: string): void { + this._messageHandler(Severity.Info, source, message); + } +} diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 2daf6549b81..1bd329231b6 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -84,7 +84,8 @@ export interface IExtensionHostProfile { } export interface IExtensionHostStarter { - readonly onCrashed: Event<[number, string | null]>; + readonly onExit: Event<[number, string | null]>; + start(): Promise | null; getInspectPort(): number | undefined; dispose(): void; diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 6fc035817de..577cc633360 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -341,6 +341,19 @@ export const schema = { pattern: EXTENSION_IDENTIFIER_PATTERN } }, + extensionKind: { + description: nls.localize('extensionKind', "Define the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote."), + type: 'string', + enum: [ + 'ui', + 'workspace' + ], + enumDescriptions: [ + nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), + nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") + ], + default: 'workspace' + }, scripts: { type: 'object', properties: { diff --git a/src/vs/workbench/services/extensions/node/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts similarity index 86% rename from src/vs/workbench/services/extensions/node/extensionsUtil.ts rename to src/vs/workbench/services/extensions/common/extensionsUtil.ts index d213b315db6..e03ddb10239 100644 --- a/src/vs/workbench/services/extensions/node/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -8,9 +8,9 @@ import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { getGalleryExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import product from 'vs/platform/product/node/product'; +import { IProductService } from 'vs/platform/product/common/product'; -export function isUIExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean { +export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { const uiContributions = ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').map(e => e.name); const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); const extensionKind = getExtensionKind(manifest, configurationService); @@ -19,7 +19,7 @@ export function isUIExtension(manifest: IExtensionManifest, configurationService case 'workspace': return false; default: { // Tagged as UI extension in product - if (isNonEmptyArray(product.uiExtensions) && product.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) { + if (isNonEmptyArray(productService.uiExtensions) && productService.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) { return true; } // Not an UI extension if it has main diff --git a/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts b/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts index 263a45a9821..351bbe5ddc9 100644 --- a/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { EnablementState, IExtensionEnablementService, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -70,10 +70,10 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { this.handleURL(URI.revive(JSON.parse(urlToHandleValue)), true); } - this.disposable = combinedDisposable([ + this.disposable = combinedDisposable( urlService.registerHandler(this), toDisposable(() => clearInterval(interval)) - ]); + ); } async handleURL(uri: URI, confirmed?: boolean): Promise { diff --git a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts similarity index 86% rename from src/vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient.ts rename to src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index 659c7ac2e15..d848799f873 100644 --- a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -3,17 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ipcRenderer as ipc } from 'electron'; import { Emitter, Event } from 'vs/base/common/event'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService } from 'vs/platform/log/common/log'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; -import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection'; +import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions, IWebSocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; @@ -28,8 +24,8 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { VSBuffer } from 'vs/base/common/buffer'; -import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory'; import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug'; +import { IProductService } from 'vs/platform/product/common/product'; export interface IInitDataProvider { readonly remoteAuthority: string; @@ -38,28 +34,28 @@ export interface IInitDataProvider { export class RemoteExtensionHostClient extends Disposable implements IExtensionHostStarter { - private _onCrashed: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>()); - public readonly onCrashed: Event<[number, string | null]> = this._onCrashed.event; + private _onExit: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>()); + public readonly onExit: Event<[number, string | null]> = this._onExit.event; private _protocol: PersistentProtocol | null; private readonly _isExtensionDevHost: boolean; - private readonly _isExtensionDevTestFromCli: boolean; private _terminating: boolean; constructor( private readonly _allExtensions: Promise, private readonly _initDataProvider: IInitDataProvider, + private readonly _webSocketFactory: IWebSocketFactory, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IWindowService private readonly _windowService: IWindowService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @ILogService private readonly _logService: ILogService, @ILabelService private readonly _labelService: ILabelService, @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService + @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService, + @IProductService private readonly _productService: IProductService ) { super(); this._protocol = null; @@ -69,14 +65,13 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH const devOpts = parseExtensionDevOptions(this._environmentService); this._isExtensionDevHost = devOpts.isExtensionDevHost; - this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli; } public start(): Promise { const options: IConnectionOptions = { isBuilt: this._environmentService.isBuilt, - commit: product.commit, - webSocketFactory: nodeWebSocketFactory, + commit: this._productService.commit, + webSocketFactory: this._webSocketFactory, addressProvider: { getAddress: async () => { const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority); @@ -168,20 +163,7 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH return; } - // Unexpected termination - if (!this._isExtensionDevHost) { - this._onCrashed.fire([0, null]); - } - - // Expected development extension termination: When the extension host goes down we also shutdown the window - else if (!this._isExtensionDevTestFromCli) { - this._windowService.closeWindow(); - } - - // When CLI testing make sure to exit with proper exit code - else { - ipc.send('vscode:exit', 0); - } + this._onExit.fire([0, null]); } private _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise { @@ -191,15 +173,15 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH const hostExtensions = allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier); const workspace = this._contextService.getWorkspace(); const r: IInitData = { - commit: product.commit, - version: pkg.version, + commit: this._productService.commit, + version: this._productService.version, parentPid: remoteExtensionHostData.pid, environment: { isExtensionDevelopmentDebug, appRoot: remoteExtensionHostData.appRoot, appSettingsHome: remoteExtensionHostData.appSettingsHome, - appName: product.nameLong, - appUriScheme: product.urlProtocol, + appName: this._productService.nameLong, + appUriScheme: this._productService.urlProtocol, appLanguage: platform.language, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index 40a8c8363ee..f24f320674b 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -21,7 +21,8 @@ import pkg from 'vs/platform/product/node/package'; import product from 'vs/platform/product/node/product'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, ILog, IRelaxedExtensionDescription, Translations } from 'vs/workbench/services/extensions/node/extensionPoints'; +import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, IRelaxedExtensionDescription } from 'vs/workbench/services/extensions/node/extensionPoints'; +import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints'; interface IExtensionCacheData { input: ExtensionScannerInput; @@ -387,26 +388,3 @@ class NullLogger implements ILog { public info(source: string, message: string): void { } } - -export class Logger implements ILog { - - private readonly _messageHandler: (severity: Severity, source: string, message: string) => void; - - constructor( - messageHandler: (severity: Severity, source: string, message: string) => void - ) { - this._messageHandler = messageHandler; - } - - public error(source: string, message: string): void { - this._messageHandler(Severity.Error, source, message); - } - - public warn(source: string, message: string): void { - this._messageHandler(Severity.Warning, source, message); - } - - public info(source: string, message: string): void { - this._messageHandler(Severity.Info, source, message); - } -} diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 1707b523b1a..7a3ca58c0ca 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -5,13 +5,12 @@ import * as nls from 'vs/nls'; import { ChildProcess, fork } from 'child_process'; -import { ipcRenderer as ipc } from 'electron'; import { Server, Socket, createServer } from 'net'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { timeout } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import pkg from 'vs/platform/product/node/package'; @@ -42,10 +41,10 @@ import { isEqualOrParent } from 'vs/base/common/resources'; export class ExtensionHostProcessWorker implements IExtensionHostStarter { - private readonly _onCrashed: Emitter<[number, string]> = new Emitter<[number, string]>(); - public readonly onCrashed: Event<[number, string]> = this._onCrashed.event; + private readonly _onExit: Emitter<[number, string]> = new Emitter<[number, string]>(); + public readonly onExit: Event<[number, string]> = this._onExit.event; - private readonly _toDispose: IDisposable[]; + private readonly _toDispose = new DisposableStore(); private readonly _isExtensionDevHost: boolean; private readonly _isExtensionDevDebug: boolean; @@ -92,8 +91,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._extensionHostConnection = null; this._messageProtocol = null; - this._toDispose = []; - this._toDispose.push(this._onCrashed); + this._toDispose.push(this._onExit); this._toDispose.push(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e))); this._toDispose.push(this._lifecycleService.onShutdown(reason => this.terminate())); this._toDispose.push(this._extensionHostDebugService.onClose(event => { @@ -451,20 +449,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { return; } - // Unexpected termination - if (!this._isExtensionDevHost) { - this._onCrashed.fire([code, signal]); - } - - // Expected development extension termination: When the extension host goes down we also shutdown the window - else if (!this._isExtensionDevTestFromCli) { - this._windowService.closeWindow(); - } - - // When CLI testing make sure to exit with proper exit code - else { - ipc.send('vscode:exit', code); - } + this._onExit.fire([code, signal]); } public enableInspector(): Promise { @@ -489,7 +474,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { } this._terminating = true; - dispose(this._toDispose); + this._toDispose.dispose(); if (!this._messageProtocol) { // .start() was not called diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index f053eb65854..a714a03fc97 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -3,62 +3,37 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ipcRenderer as ipc } from 'electron'; +import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; +import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory'; +import { AbstractExtensionService } from 'vs/workbench/services/extensions/common/abstractExtensionService'; import * as nls from 'vs/nls'; import * as path from 'vs/base/common/path'; -import { ipcRenderer as ipc } from 'electron'; -import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { Barrier, runWhenIdle } from 'vs/base/common/async'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import * as perf from 'vs/base/common/performance'; -import { isEqualOrParent } from 'vs/base/common/resources'; +import { runWhenIdle } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IExtensionEnablementService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient'; +import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService, ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; +import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import pkg from 'vs/platform/product/node/package'; -import product from 'vs/platform/product/node/product'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser, schema } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; -import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; -import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; -import { CachedExtensionScanner, Logger } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager'; import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Schemas } from 'vs/base/common/network'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; -import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; - -const hasOwnProperty = Object.hasOwnProperty; -const NO_OP_VOID_PROMISE = Promise.resolve(undefined); - -schema.properties.engines.properties.vscode.default = `^${pkg.version}`; - -let productAllowProposedApi: Set | null = null; -function allowProposedApiFromProduct(id: ExtensionIdentifier): boolean { - // create set if needed - if (!productAllowProposedApi) { - productAllowProposedApi = new Set(); - if (isNonEmptyArray(product.extensionAllowedProposedApi)) { - product.extensionAllowedProposedApi.forEach((id) => productAllowProposedApi!.add(ExtensionIdentifier.toKey(id))); - } - } - return productAllowProposedApi.has(ExtensionIdentifier.toKey(id)); -} +import { IProductService } from 'vs/platform/product/common/product'; +import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints'; class DeltaExtensionsQueueItem { constructor( @@ -67,80 +42,38 @@ class DeltaExtensionsQueueItem { ) { } } -export class ExtensionService extends Disposable implements IExtensionService { +export class ExtensionService extends AbstractExtensionService implements IExtensionService { - public _serviceBrand: any; - - private _remoteExtensionsEnvironmentData: Map; + private readonly _remoteExtensionsEnvironmentData: Map; private readonly _extensionHostLogsLocation: URI; - private readonly _registry: ExtensionDescriptionRegistry; - private readonly _installedExtensionsReady: Barrier; - private readonly _isDev: boolean; - private readonly _extensionsMessages: Map; - private _allRequestedActivateEvents: { [activationEvent: string]: boolean; }; private readonly _extensionScanner: CachedExtensionScanner; private _deltaExtensionsQueue: DeltaExtensionsQueueItem[]; - private readonly _onDidRegisterExtensions: Emitter = this._register(new Emitter()); - public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event; - - private readonly _onDidChangeExtensionsStatus: Emitter = this._register(new Emitter()); - public readonly onDidChangeExtensionsStatus: Event = this._onDidChangeExtensionsStatus.event; - - private readonly _onDidChangeExtensions: Emitter = this._register(new Emitter()); - public readonly onDidChangeExtensions: Event = this._onDidChangeExtensions.event; - - private readonly _onWillActivateByEvent = this._register(new Emitter()); - public readonly onWillActivateByEvent: Event = this._onWillActivateByEvent.event; - - private readonly _onDidChangeResponsiveChange = this._register(new Emitter()); - public readonly onDidChangeResponsiveChange: Event = this._onDidChangeResponsiveChange.event; - - // --- Members used per extension host process - private _extensionHostProcessManagers: ExtensionHostProcessManager[]; - private _extensionHostActiveExtensions: Map; - private _extensionHostProcessActivationTimes: Map; - private _extensionHostExtensionRuntimeErrors: Map; - constructor( - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @INotificationService private readonly _notificationService: INotificationService, - @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService, + @IInstantiationService instantiationService: IInstantiationService, + @INotificationService notificationService: INotificationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ITelemetryService telemetryService: ITelemetryService, + @IExtensionEnablementService extensionEnablementService: IExtensionEnablementService, + @IFileService fileService: IFileService, + @IProductService productService: IProductService, @IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService, - @IWindowService private readonly _windowService: IWindowService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, - @IFileService fileService: IFileService + @IWindowService protected readonly _windowService: IWindowService, ) { - super(); - - // help the file service to activate providers by activating extensions by file system event - this._register(fileService.onWillActivateFileSystemProvider(e => { - e.join(this.activateByEvent(`onFileSystem:${e.scheme}`)); - })); - - this._remoteExtensionsEnvironmentData = new Map(); - - this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${_windowService.windowId}`)); - this._registry = new ExtensionDescriptionRegistry([]); - this._installedExtensionsReady = new Barrier(); - this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment; - this._extensionsMessages = new Map(); - this._allRequestedActivateEvents = Object.create(null); - this._extensionScanner = this._instantiationService.createInstance(CachedExtensionScanner); - this._deltaExtensionsQueue = []; - - this._extensionHostProcessManagers = []; - this._extensionHostActiveExtensions = new Map(); - this._extensionHostProcessActivationTimes = new Map(); - this._extensionHostExtensionRuntimeErrors = new Map(); - - this._startDelayed(this._lifecycleService); + super( + instantiationService, + notificationService, + environmentService, + telemetryService, + extensionEnablementService, + fileService, + productService, + ); if (this._extensionEnablementService.allUserExtensionsDisabled) { this._notificationService.prompt(Severity.Info, nls.localize('extensionsDisabled', "All installed extensions are temporarily disabled. Reload the window to return to the previous state."), [{ @@ -151,6 +84,12 @@ export class ExtensionService extends Disposable implements IExtensionService { }]); } + this._remoteExtensionsEnvironmentData = new Map(); + + this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.windowId}`)); + this._extensionScanner = instantiationService.createInstance(CachedExtensionScanner); + this._deltaExtensionsQueue = []; + this._register(this._extensionEnablementService.onEnablementChanged((extensions) => { let toAdd: IExtension[] = []; let toRemove: string[] = []; @@ -181,8 +120,24 @@ export class ExtensionService extends Disposable implements IExtensionService { this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id])); } })); + + // delay extension host creation and extension scanning + // until the workbench is running. we cannot defer the + // extension host more (LifecyclePhase.Restored) because + // some editors require the extension host to restore + // and this would result in a deadlock + // see https://github.com/Microsoft/vscode/issues/41322 + this._lifecycleService.when(LifecyclePhase.Ready).then(() => { + // reschedule to ensure this runs after restoring viewlets, panels, and editors + runWhenIdle(async () => { + this._initialize(); + }, 50 /*max delay*/); + }); } + + //#region deltaExtensions + private _inHandleDeltaExtensions = false; private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise { this._deltaExtensionsQueue.push(item); @@ -274,26 +229,7 @@ export class ExtensionService extends Disposable implements IExtensionService { } private _rehandleExtensionPoints(extensionDescriptions: IExtensionDescription[]): void { - const affectedExtensionPoints: { [extPointName: string]: boolean; } = Object.create(null); - for (let extensionDescription of extensionDescriptions) { - if (extensionDescription.contributes) { - for (let extPointName in extensionDescription.contributes) { - if (hasOwnProperty.call(extensionDescription.contributes, extPointName)) { - affectedExtensionPoints[extPointName] = true; - } - } - } - } - - const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); - - const availableExtensions = this._registry.getAllExtensionDescriptions(); - const extensionPoints = ExtensionsRegistry.getExtensionPoints(); - for (let i = 0, len = extensionPoints.length; i < len; i++) { - if (affectedExtensionPoints[extensionPoints[i].name]) { - ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler); - } - } + this._doHandleExtensionPoints(extensionDescriptions); } public canAddExtension(extension: IExtensionDescription): boolean { @@ -383,76 +319,20 @@ export class ExtensionService extends Disposable implements IExtensionService { } } - private _startDelayed(lifecycleService: ILifecycleService): void { - // delay extension host creation and extension scanning - // until the workbench is running. we cannot defer the - // extension host more (LifecyclePhase.Restored) because - // some editors require the extension host to restore - // and this would result in a deadlock - // see https://github.com/Microsoft/vscode/issues/41322 - lifecycleService.when(LifecyclePhase.Ready).then(() => { - // reschedule to ensure this runs after restoring viewlets, panels, and editors - runWhenIdle(() => { - perf.mark('willLoadExtensions'); - this._startExtensionHostProcess(true, []); - this._scanAndHandleExtensions(); - this.whenInstalledExtensionsRegistered().then(() => perf.mark('didLoadExtensions')); - }, 50 /*max delay*/); - }); - } - - public dispose(): void { - super.dispose(); - this._onWillActivateByEvent.dispose(); - this._onDidChangeResponsiveChange.dispose(); - } - - public restartExtensionHost(): void { - this._stopExtensionHostProcess(); - this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)); - } - - public startExtensionHost(): void { - this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)); - } - - public stopExtensionHost(): void { - this._stopExtensionHostProcess(); - } - - private _stopExtensionHostProcess(): void { - let previouslyActivatedExtensionIds: ExtensionIdentifier[] = []; - this._extensionHostActiveExtensions.forEach((value) => { - previouslyActivatedExtensionIds.push(value); - }); - - for (const manager of this._extensionHostProcessManagers) { - manager.dispose(); - } - this._extensionHostProcessManagers = []; - this._extensionHostActiveExtensions = new Map(); - this._extensionHostProcessActivationTimes = new Map(); - this._extensionHostExtensionRuntimeErrors = new Map(); - - if (previouslyActivatedExtensionIds.length > 0) { - this._onDidChangeExtensionsStatus.fire(previouslyActivatedExtensionIds); - } - } + //#endregion private _createProvider(remoteAuthority: string): IInitDataProvider { return { remoteAuthority: remoteAuthority, getInitData: () => { - return this._installedExtensionsReady.wait().then(() => { + return this.whenInstalledExtensionsRegistered().then(() => { return this._remoteExtensionsEnvironmentData.get(remoteAuthority)!; }); } }; } - private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void { - this._stopExtensionHostProcess(); - + protected _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[] { let autoStart: boolean; let extensions: Promise; if (isInitialStart) { @@ -464,27 +344,25 @@ export class ExtensionService extends Disposable implements IExtensionService { extensions = this.getExtensions().then((extensions) => extensions.filter(ext => ext.extensionLocation.scheme === Schemas.file)); } + const result: ExtensionHostProcessManager[] = []; const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation); - const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, null, initialActivationEvents); - extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, true)); - extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); }); - this._extensionHostProcessManagers.push(extHostProcessManager); + const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, true, extHostProcessWorker, null, initialActivationEvents); + result.push(extHostProcessManager); const remoteAgentConnection = this._remoteAgentService.getConnection(); if (remoteAgentConnection) { - const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority)); - const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); - remoteExtHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, false)); - remoteExtHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); }); - this._extensionHostProcessManagers.push(remoteExtHostProcessManager); + const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority), nodeWebSocketFactory); + const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); + result.push(remoteExtHostProcessManager); } + + return result; } - private _onExtensionHostCrashed(code: number, signal: string | null, showNotification: boolean): void { - console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal); - this._stopExtensionHostProcess(); + protected _onExtensionHostCrashed(extensionHost: ExtensionHostProcessManager, code: number, signal: string | null): void { + super._onExtensionHostCrashed(extensionHost, code, signal); - if (showNotification) { + if (extensionHost.isLocal) { if (code === 55) { this._notificationService.prompt( Severity.Error, @@ -502,118 +380,19 @@ export class ExtensionService extends Disposable implements IExtensionService { return; } - let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly."); - if (code === 87) { - message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive."); - } - - this._notificationService.prompt(Severity.Error, message, + this._notificationService.prompt(Severity.Error, nls.localize('extensionService.crash', "Extension host terminated unexpectedly."), [{ label: nls.localize('devTools', "Open Developer Tools"), run: () => this._windowService.openDevTools() }, { label: nls.localize('restart', "Restart Extension Host"), - run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)) + run: () => this.startExtensionHost() }] ); } } - // ---- begin IExtensionService - - public activateByEvent(activationEvent: string): Promise { - if (this._installedExtensionsReady.isOpen()) { - // Extensions have been scanned and interpreted - - // Record the fact that this activationEvent was requested (in case of a restart) - this._allRequestedActivateEvents[activationEvent] = true; - - if (!this._registry.containsActivationEvent(activationEvent)) { - // There is no extension that is interested in this activation event - return NO_OP_VOID_PROMISE; - } - - return this._activateByEvent(activationEvent); - } else { - // Extensions have not been scanned yet. - - // Record the fact that this activationEvent was requested (in case of a restart) - this._allRequestedActivateEvents[activationEvent] = true; - - return this._installedExtensionsReady.wait().then(() => this._activateByEvent(activationEvent)); - } - } - - private _activateByEvent(activationEvent: string): Promise { - const result = Promise.all( - this._extensionHostProcessManagers.map(extHostManager => extHostManager.activateByEvent(activationEvent)) - ).then(() => { }); - this._onWillActivateByEvent.fire({ - event: activationEvent, - activation: result - }); - return result; - } - - public whenInstalledExtensionsRegistered(): Promise { - return this._installedExtensionsReady.wait(); - } - - public getExtensions(): Promise { - return this._installedExtensionsReady.wait().then(() => { - return this._registry.getAllExtensionDescriptions(); - }); - } - - public getExtension(id: string): Promise { - return this._installedExtensionsReady.wait().then(() => { - return this._registry.getExtensionDescription(id); - }); - } - - public readExtensionPointContributions(extPoint: IExtensionPoint): Promise[]> { - return this._installedExtensionsReady.wait().then(() => { - let availableExtensions = this._registry.getAllExtensionDescriptions(); - - let result: ExtensionPointContribution[] = [], resultLen = 0; - for (let i = 0, len = availableExtensions.length; i < len; i++) { - let desc = availableExtensions[i]; - - if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) { - result[resultLen++] = new ExtensionPointContribution(desc, desc.contributes[extPoint.name]); - } - } - - return result; - }); - } - - public getExtensionsStatus(): { [id: string]: IExtensionsStatus; } { - let result: { [id: string]: IExtensionsStatus; } = Object.create(null); - if (this._registry) { - const extensions = this._registry.getAllExtensionDescriptions(); - for (const extension of extensions) { - const extensionKey = ExtensionIdentifier.toKey(extension.identifier); - result[extension.identifier.value] = { - messages: this._extensionsMessages.get(extensionKey) || [], - activationTimes: this._extensionHostProcessActivationTimes.get(extensionKey), - runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey) || [], - }; - } - } - return result; - } - - public getInspectPort(): number { - if (this._extensionHostProcessManagers.length > 0) { - return this._extensionHostProcessManagers[0].getInspectPort(); - } - return 0; - } - - // ---- end IExtensionService - // --- impl private createLogger(): Logger { @@ -642,7 +421,7 @@ export class ExtensionService extends Disposable implements IExtensionService { } } - private async _scanAndHandleExtensions(): Promise { + protected async _scanAndHandleExtensions(): Promise { this._extensionScanner.startScanningExtensions(this.createLogger()); const remoteAuthority = this._environmentService.configuration.remoteAuthority; @@ -650,6 +429,12 @@ export class ExtensionService extends Disposable implements IExtensionService { let localExtensions = await this._extensionScanner.scannedExtensions; + // enable or disable proposed API per extension + this._checkEnableProposedApi(localExtensions); + + // remove disabled extensions + localExtensions = localExtensions.filter(extension => this._isEnabled(extension)); + if (remoteAuthority) { let resolvedAuthority: ResolvedAuthority; @@ -693,37 +478,28 @@ export class ExtensionService extends Disposable implements IExtensionService { // fetch the remote environment const remoteEnv = (await this._remoteAgentService.getEnvironment())!; - // revive URIs - remoteEnv.extensions.forEach((extension) => { - (extension).extensionLocation = URI.revive(extension.extensionLocation); - }); + // enable or disable proposed API per extension + this._checkEnableProposedApi(remoteEnv.extensions); + + // remove disabled extensions + remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension)); // remove UI extensions from the remote extensions - remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._configurationService)); + remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._productService, this._configurationService)); // remove non-UI extensions from the local extensions - localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._configurationService)); + localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._productService, this._configurationService)); // in case of overlap, the remote wins const isRemoteExtension = new Set(); remoteEnv.extensions.forEach(extension => isRemoteExtension.add(ExtensionIdentifier.toKey(extension.identifier))); localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier))); - // compute enabled extensions - const enabledExtensions = await this._getRuntimeExtensions(([]).concat(remoteEnv.extensions).concat(localExtensions)); - - // remove disabled extensions - const isEnabled = new Set(); - enabledExtensions.forEach(extension => isEnabled.add(ExtensionIdentifier.toKey(extension.identifier))); - remoteEnv.extensions = remoteEnv.extensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier))); - localExtensions = localExtensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier))); - // save for remote extension's init data this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv); - this._handleExtensionPoints(enabledExtensions); + this._handleExtensionPoints(([]).concat(remoteEnv.extensions).concat(localExtensions)); extensionHost.start(localExtensions.map(extension => extension.identifier)); - this._releaseBarrier(); } else { await this._startLocalExtensionHost(extensionHost, localExtensions); @@ -731,11 +507,8 @@ export class ExtensionService extends Disposable implements IExtensionService { } private async _startLocalExtensionHost(extensionHost: ExtensionHostProcessManager, localExtensions: IExtensionDescription[]): Promise { - const enabledExtensions = await this._getRuntimeExtensions(localExtensions); - - this._handleExtensionPoints(enabledExtensions); - extensionHost.start(enabledExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id))); - this._releaseBarrier(); + this._handleExtensionPoints(localExtensions); + extensionHost.start(localExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id))); } private _handleExtensionPoints(allExtensions: IExtensionDescription[]): void { @@ -744,234 +517,19 @@ export class ExtensionService extends Disposable implements IExtensionService { this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', '))); } - let availableExtensions = this._registry.getAllExtensionDescriptions(); - let extensionPoints = ExtensionsRegistry.getExtensionPoints(); - - let messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); - - for (let i = 0, len = extensionPoints.length; i < len; i++) { - ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler); - } + this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions()); } - private _releaseBarrier(): void { - perf.mark('extensionHostReady'); - this._installedExtensionsReady.open(); - this._onDidRegisterExtensions.fire(undefined); - this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier)); - } - - private isExtensionUnderDevelopment(extension: IExtensionDescription): boolean { - if (this._environmentService.isExtensionDevelopment) { - const extDevLocs = this._environmentService.extensionDevelopmentLocationURI; - if (extDevLocs) { - const extLocation = extension.extensionLocation; - for (let p of extDevLocs) { - if (isEqualOrParent(extLocation, p)) { - return true; - } - } - } + public getInspectPort(): number { + if (this._extensionHostProcessManagers.length > 0) { + return this._extensionHostProcessManagers[0].getInspectPort(); } - return false; - } - - private async _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise { - - const runtimeExtensions: IExtensionDescription[] = []; - const extensionsToDisable: IExtensionDescription[] = []; - const userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }]; - - let enableProposedApiFor: string | string[] = this._environmentService.args['enable-proposed-api'] || []; - - const notFound = (id: string) => nls.localize('notFound', "Extension \`{0}\` cannot use PROPOSED API as it cannot be found", id); - - if (enableProposedApiFor.length) { - let allProposed = (enableProposedApiFor instanceof Array ? enableProposedApiFor : [enableProposedApiFor]); - allProposed.forEach(id => { - if (!allExtensions.some(description => ExtensionIdentifier.equals(description.identifier, id))) { - console.error(notFound(id)); - } - }); - // Make enabled proposed API be lowercase for case insensitive comparison - if (Array.isArray(enableProposedApiFor)) { - enableProposedApiFor = enableProposedApiFor.map(id => id.toLowerCase()); - } else { - enableProposedApiFor = enableProposedApiFor.toLowerCase(); - } - } - - const enableProposedApiForAll = !this._environmentService.isBuilt || - (!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong !== 'Visual Studio Code') || - (enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args); - - - for (const extension of allExtensions) { - - // Do not disable extensions under development - if (!this.isExtensionUnderDevelopment(extension)) { - if (!this._extensionEnablementService.isEnabled(toExtension(extension))) { - continue; - } - } - - if (!extension.isBuiltin) { - // Check if the extension is changed to system extension - const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.identifier.value }))[0]; - if (userMigratedSystemExtension) { - extensionsToDisable.push(extension); - continue; - } - } - runtimeExtensions.push(this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor)); - } - - this._telemetryService.publicLog('extensionsScanned', { - totalCount: runtimeExtensions.length, - disabledCount: allExtensions.length - runtimeExtensions.length - }); - - if (extensionsToDisable.length) { - return this._extensionEnablementService.setEnablement(extensionsToDisable.map(e => toExtension(e)), EnablementState.Disabled) - .then(() => runtimeExtensions); - } else { - return runtimeExtensions; - } - } - - private _updateEnableProposedApi(extension: IExtensionDescription, enableProposedApiForAll: boolean, enableProposedApiFor: string | string[]): IExtensionDescription { - if (allowProposedApiFromProduct(extension.identifier)) { - // fast lane -> proposed api is available to all extensions - // that are listed in product.json-files - extension.enableProposedApi = true; - - } else if (extension.enableProposedApi && !extension.isBuiltin) { - if ( - !enableProposedApiForAll && - enableProposedApiFor.indexOf(extension.identifier.value.toLowerCase()) < 0 - ) { - extension.enableProposedApi = false; - console.error(`Extension '${extension.identifier.value} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`); - - } else { - // proposed api is available when developing or when an extension was explicitly - // spelled out via a command line argument - console.warn(`Extension '${extension.identifier.value}' uses PROPOSED API which is subject to change and removal without notice.`); - } - } - return extension; - } - - private _handleExtensionPointMessage(msg: IMessage) { - const extensionKey = ExtensionIdentifier.toKey(msg.extensionId); - - if (!this._extensionsMessages.has(extensionKey)) { - this._extensionsMessages.set(extensionKey, []); - } - this._extensionsMessages.get(extensionKey)!.push(msg); - - const extension = this._registry.getExtensionDescription(msg.extensionId); - const strMsg = `[${msg.extensionId.value}]: ${msg.message}`; - if (extension && extension.isUnderDevelopment) { - // This message is about the extension currently being developed - this._showMessageToUser(msg.type, strMsg); - } else { - this._logMessageInConsole(msg.type, strMsg); - } - - if (!this._isDev && msg.extensionId) { - const { type, extensionId, extensionPointId, message } = msg; - /* __GDPR__ - "extensionsMessage" : { - "type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "extensionId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "extensionPointId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "message": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } - } - */ - this._telemetryService.publicLog('extensionsMessage', { - type, extensionId: extensionId.value, extensionPointId, message - }); - } - } - - private static _handleExtensionPoint(extensionPoint: ExtensionPoint, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void { - let users: IExtensionPointUser[] = [], usersLen = 0; - for (let i = 0, len = availableExtensions.length; i < len; i++) { - let desc = availableExtensions[i]; - - if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) { - users[usersLen++] = { - description: desc, - value: desc.contributes[extensionPoint.name], - collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name) - }; - } - } - - extensionPoint.acceptUsers(users); - } - - private _showMessageToUser(severity: Severity, msg: string): void { - if (severity === Severity.Error || severity === Severity.Warning) { - this._notificationService.notify({ severity, message: msg }); - } else { - this._logMessageInConsole(severity, msg); - } - } - - private _logMessageInConsole(severity: Severity, msg: string): void { - if (severity === Severity.Error) { - console.error(msg); - } else if (severity === Severity.Warning) { - console.warn(msg); - } else { - console.log(msg); - } - } - - // -- called by extension host - - public _logOrShowMessage(severity: Severity, msg: string): void { - if (this._isDev) { - this._showMessageToUser(severity, msg); - } else { - this._logMessageInConsole(severity, msg); - } - } - - public async _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise { - const results = await Promise.all( - this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, activationEvent)) - ); - const activated = results.some(e => e); - if (!activated) { - throw new Error(`Unknown extension ${extensionId.value}`); - } - } - - public _onWillActivateExtension(extensionId: ExtensionIdentifier): void { - this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId); - } - - public _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void { - this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent)); - this._onDidChangeExtensionsStatus.fire([extensionId]); - } - - public _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void { - const extensionKey = ExtensionIdentifier.toKey(extensionId); - if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) { - this._extensionHostExtensionRuntimeErrors.set(extensionKey, []); - } - this._extensionHostExtensionRuntimeErrors.get(extensionKey)!.push(err); - this._onDidChangeExtensionsStatus.fire([extensionId]); + return 0; } public _onExtensionHostExit(code: number): void { // Expected development extension termination: When the extension host goes down we also shutdown the window - const devOpts = parseExtensionDevOptions(this._environmentService); - if (!devOpts.isExtensionDevTestFromCli) { + if (!this._isExtensionDevTestFromCli) { this._windowService.closeWindow(); } diff --git a/src/vs/workbench/services/extensions/node/extensionHostMain.ts b/src/vs/workbench/services/extensions/node/extensionHostMain.ts index 67d2ba56ada..9e58237b634 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostMain.ts @@ -19,7 +19,6 @@ import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ILogService } from 'vs/platform/log/common/log'; -import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures'; // we don't (yet) throw when extensions parse // uris that have no scheme @@ -53,9 +52,7 @@ export class ExtensionHostMain { hostUtils: IHostUtils, consolePatchFn: IConsolePatchFn, logServiceFn: ILogServiceFn, - uriTransformer: IURITransformer | null, - schemeTransformer: ISchemeTransformer | null, - outputChannelName: string, + uriTransformer: IURITransformer | null ) { this._isTerminating = false; this._hostUtils = hostUtils; @@ -86,8 +83,7 @@ export class ExtensionHostMain { extHostConfiguraiton, initData.environment, this._extHostLogService, - schemeTransformer, - outputChannelName + uriTransformer ); // error forwarding and stack trace scanning diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcess.ts b/src/vs/workbench/services/extensions/node/extensionHostProcess.ts index c7227dcdd98..4c890beb60d 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcess.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcess.ts @@ -3,11 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { startExtensionHostProcess } from 'vs/workbench/services/extensions/node/extensionHostProcessSetup'; -startExtensionHostProcess( - _ => null, - _ => null, - _ => nls.localize('extension host Log', "Extension Host") -).catch((err) => console.log(err)); +startExtensionHostProcess().catch((err) => console.log(err)); diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 7530a965ac7..533755d7f44 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -5,6 +5,7 @@ import * as nativeWatchdog from 'native-watchdog'; import * as net from 'net'; +import * as minimist from 'minimist'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; @@ -17,12 +18,21 @@ import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services import { VSBuffer } from 'vs/base/common/buffer'; import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService'; import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; -import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures'; -import { IURITransformer } from 'vs/base/common/uriIpc'; +import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc'; import { exists } from 'vs/base/node/pfs'; import { realpath } from 'vs/base/node/extpath'; import { IHostUtils } from 'vs/workbench/api/node/extHostExtensionService'; +interface ParsedExtHostArgs { + uriTransformerPath?: string; +} + +const args = minimist(process.argv.slice(2), { + string: [ + 'uriTransformerPath' + ] +}) as ParsedExtHostArgs; + // With Electron 2.x and node.js 8.x the "natives" module // can cause a native crash (see https://github.com/nodejs/node/issues/19891 and // https://github.com/electron/electron/issues/10905). To prevent this from @@ -272,11 +282,7 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise IURITransformer | null, - schemeTransformerFn: (initData: IInitData) => ISchemeTransformer | null, - outputChannelNameFn: (initData: IInitData) => string, -): Promise { +export async function startExtensionHostProcess(): Promise { const protocol = await createExtHostProtocol(); const renderer = await connectToRenderer(protocol); @@ -291,6 +297,17 @@ export async function startExtensionHostProcess( realpath(path: string) { return realpath(path); } }; + // Attempt to load uri transformer + let uriTransformer: IURITransformer | null = null; + if (initData.remoteAuthority && args.uriTransformerPath) { + try { + const rawURITransformerFactory = require.__$__nodeRequire(args.uriTransformerPath); + const rawURITransformer = rawURITransformerFactory(initData.remoteAuthority); + uriTransformer = new URITransformer(rawURITransformer); + } catch (e) { + console.error(e); + } + } const extensionHostMain = new ExtensionHostMain( renderer.protocol, @@ -298,9 +315,7 @@ export async function startExtensionHostProcess( hostUtils, patchPatchedConsole, createLogService, - uriTransformerFn(initData), - schemeTransformerFn(initData), - outputChannelNameFn(initData) + uriTransformer ); // rewrite onTerminate-function to be a proper shutdown diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index 6a450c7a9f4..6a1083c003b 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -12,40 +12,13 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; -import { getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getGalleryExtensionId, groupByExtension, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator'; -import { ExtensionIdentifier, ExtensionIdentifierWithVersion, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints'; const MANIFEST_FILE = 'package.json'; -export interface Translations { - [id: string]: string; -} - -namespace Translations { - export function equals(a: Translations, b: Translations): boolean { - if (a === b) { - return true; - } - let aKeys = Object.keys(a); - let bKeys: Set = new Set(); - for (let key of Object.keys(b)) { - bKeys.add(key); - } - if (aKeys.length !== bKeys.size) { - return false; - } - - for (let key of aKeys) { - if (a[key] !== b[key]) { - return false; - } - bKeys.delete(key); - } - return bKeys.size === 0; - } -} - export interface NlsConfiguration { readonly devMode: boolean; readonly locale: string | undefined; @@ -53,12 +26,6 @@ export interface NlsConfiguration { readonly translations: Translations; } -export interface ILog { - error(source: string, message: string): void; - warn(source: string, message: string): void; - info(source: string, message: string): void; -} - abstract class ExtensionManifestHandler { protected readonly _ourVersion: string; diff --git a/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts b/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts index 02d2f0e0116..e27a2a7ed46 100644 --- a/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts +++ b/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts @@ -16,10 +16,11 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localize } from 'vs/nls'; -import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; +import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/map'; +import { IProductService } from 'vs/platform/product/common/product'; export class MultiExtensionManagementService extends Disposable implements IExtensionManagementService { @@ -35,7 +36,8 @@ export class MultiExtensionManagementService extends Disposable implements IExte constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, ) { super(); this.servers = this.extensionManagementServerService.remoteExtensionManagementServer ? [this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer] : [this.extensionManagementServerService.localExtensionManagementServer]; @@ -85,7 +87,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte private async uninstallInServer(extension: ILocalExtension, server: IExtensionManagementServer, force?: boolean): Promise { if (server === this.extensionManagementServerService.localExtensionManagementServer) { const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getInstalled(ExtensionType.User); - const dependentNonUIExtensions = installedExtensions.filter(i => !isUIExtension(i.manifest, this.configurationService) + const dependentNonUIExtensions = installedExtensions.filter(i => !isUIExtension(i.manifest, this.productService, this.configurationService) && i.manifest.extensionDependencies && i.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier))); if (dependentNonUIExtensions.length) { return Promise.reject(new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions))); @@ -140,7 +142,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte const [extensionIdentifier] = await Promise.all(this.servers.map(server => server.extensionManagementService.install(vsix))); return extensionIdentifier; } - if (isUIExtension(manifest, this.configurationService)) { + if (isUIExtension(manifest, this.productService, this.configurationService)) { // Install only on local server return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix); } @@ -161,7 +163,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte // Install on both servers return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(() => undefined); } - if (isUIExtension(manifest, this.configurationService)) { + if (isUIExtension(manifest, this.productService, this.configurationService)) { // Install only on local server return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } @@ -210,7 +212,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte for (let idx = 0; idx < extensions.length; idx++) { const extension = extensions[idx]; const manifest = manifests[idx]; - if (manifest && isUIExtension(manifest, this.configurationService)) { + if (manifest && isUIExtension(manifest, this.productService, this.configurationService)) { result.set(extension.identifier.id.toLowerCase(), extension); uiExtensionsManifests.push(manifest); } diff --git a/src/vs/workbench/services/files/common/fileService.ts b/src/vs/workbench/services/files/common/fileService.ts index 7e06caa3de1..8631b019133 100644 --- a/src/vs/workbench/services/files/common/fileService.ts +++ b/src/vs/workbench/services/files/common/fileService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, IDisposable, toDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; @@ -55,14 +55,12 @@ export class FileService extends Disposable implements IFileService { providerDisposables.push(provider.onDidErrorOccur(error => this._onError.fire(error))); } - return combinedDisposable([ - toDisposable(() => { - this._onDidChangeFileSystemProviderRegistrations.fire({ added: false, scheme, provider }); - this.provider.delete(scheme); + return toDisposable(() => { + this._onDidChangeFileSystemProviderRegistrations.fire({ added: false, scheme, provider }); + this.provider.delete(scheme); - dispose(providerDisposables); - }) - ]); + dispose(providerDisposables); + }); } async activateProvider(scheme: string): Promise { @@ -80,7 +78,7 @@ export class FileService extends Disposable implements IFileService { }); if (this.provider.has(scheme)) { - return Promise.resolve(); // provider is already here so we can return directly + return; // provider is already here so we can return directly } // If the provider is not yet there, make sure to join on the listeners assuming @@ -237,7 +235,7 @@ export class FileService extends Disposable implements IFileService { return fileStat; } - return Promise.resolve(fileStat); + return fileStat; } async resolveAll(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise; diff --git a/src/vs/workbench/services/files/test/node/diskFileService.test.ts b/src/vs/workbench/services/files/test/node/diskFileService.test.ts index 8068df82ed6..a3f44904c3d 100644 --- a/src/vs/workbench/services/files/test/node/diskFileService.test.ts +++ b/src/vs/workbench/services/files/test/node/diskFileService.test.ts @@ -18,7 +18,7 @@ import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameS import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag, IStat } from 'vs/platform/files/common/files'; import { NullLogService } from 'vs/platform/log/common/log'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; @@ -125,7 +125,7 @@ suite('Disk File Service', () => { let testProvider: TestDiskFileSystemProvider; let testDir: string; - let disposables: IDisposable[] = []; + let disposables = new DisposableStore(); setup(async () => { const logService = new NullLogService(); @@ -149,7 +149,8 @@ suite('Disk File Service', () => { }); teardown(async () => { - disposables = dispose(disposables); + disposables.dispose(); + disposables = new DisposableStore(); await rimraf(parentDir, RimRafMode.MOVE); }); @@ -446,8 +447,7 @@ suite('Disk File Service', () => { await service.del(source.resource); return Promise.reject(new Error('Unexpected')); - } - catch (error) { + } catch (error) { return Promise.resolve(true); } }); diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 0861420e86f..e5e95771b4c 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -5,7 +5,7 @@ import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification'; import { INotificationsModel, NotificationsModel, ChoiceAction } from 'vs/workbench/common/notifications'; -import { dispose, Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -54,7 +54,7 @@ export class NotificationService extends Disposable implements INotificationServ } prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle { - const toDispose: IDisposable[] = []; + const toDispose = new DisposableStore(); let choiceClicked = false; let handle: INotificationHandle; @@ -94,7 +94,7 @@ export class NotificationService extends Disposable implements INotificationServ Event.once(handle.onDidClose)(() => { // Cleanup when notification gets disposed - dispose(toDispose); + toDispose.dispose(); // Indicate cancellation to the outside if no action was executed if (options && typeof options.onCancel === 'function' && !choiceClicked) { diff --git a/src/vs/workbench/services/output/common/outputChannelModel.ts b/src/vs/workbench/services/output/common/outputChannelModel.ts index 007ead80d15..6be8813a35c 100644 --- a/src/vs/workbench/services/output/common/outputChannelModel.ts +++ b/src/vs/workbench/services/output/common/outputChannelModel.ts @@ -96,12 +96,11 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen } else { this.model = this.modelService.createModel(content, this.modeService.create(this.mimeType), this.modelUri); this.onModelCreated(this.model); - const disposables: IDisposable[] = []; - disposables.push(this.model.onWillDispose(() => { + const disposable = this.model.onWillDispose(() => { this.onModelWillDispose(this.model); this.model = null; - dispose(disposables); - })); + dispose(disposable); + }); } return this.model; } @@ -340,11 +339,10 @@ export class BufferredOutputChannel extends Disposable implements IOutputChannel private createModel(content: string): ITextModel { const model = this.modelService.createModel(content, this.modeService.create(this.mimeType), this.modelUri); - const disposables: IDisposable[] = []; - disposables.push(model.onWillDispose(() => { + const disposable = model.onWillDispose(() => { this.model = null; - dispose(disposables); - })); + dispose(disposable); + }); return model; } diff --git a/src/vs/workbench/services/progress/browser/localProgressService.ts b/src/vs/workbench/services/progress/browser/localProgressService.ts new file mode 100644 index 00000000000..2f2932873d7 --- /dev/null +++ b/src/vs/workbench/services/progress/browser/localProgressService.ts @@ -0,0 +1,306 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import * as types from 'vs/base/common/types'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { ILocalProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; + +namespace ProgressState { + + export const enum Type { + None, + Done, + Infinite, + While, + Work + } + + export const None = new class { readonly type = Type.None; }; + export const Done = new class { readonly type = Type.Done; }; + export const Infinite = new class { readonly type = Type.Infinite; }; + + export class While { + readonly type = Type.While; + + constructor( + readonly whilePromise: Promise, + readonly whileStart: number, + readonly whileDelay: number, + ) { } + } + + export class Work { + readonly type = Type.Work; + + constructor( + readonly total: number | undefined, + readonly worked: number | undefined + ) { } + } + + export type State = + typeof None + | typeof Done + | typeof Infinite + | While + | Work; +} + +export abstract class ScopedService extends Disposable { + + constructor( + private viewletService: IViewletService, + private panelService: IPanelService, + private scopeId: string + ) { + super(); + + this.registerListeners(); + } + + registerListeners(): void { + this._register(this.viewletService.onDidViewletOpen(viewlet => this.onScopeOpened(viewlet.getId()))); + this._register(this.panelService.onDidPanelOpen(({ panel }) => this.onScopeOpened(panel.getId()))); + + this._register(this.viewletService.onDidViewletClose(viewlet => this.onScopeClosed(viewlet.getId()))); + this._register(this.panelService.onDidPanelClose(panel => this.onScopeClosed(panel.getId()))); + } + + private onScopeClosed(scopeId: string) { + if (scopeId === this.scopeId) { + this.onScopeDeactivated(); + } + } + + private onScopeOpened(scopeId: string) { + if (scopeId === this.scopeId) { + this.onScopeActivated(); + } + } + + abstract onScopeActivated(): void; + + abstract onScopeDeactivated(): void; +} + +export class ScopedProgressService extends ScopedService implements ILocalProgressService { + + _serviceBrand: ServiceIdentifier; + + private isActive: boolean; + private progressbar: ProgressBar; + private progressState: ProgressState.State = ProgressState.None; + + constructor( + progressbar: ProgressBar, + scopeId: string, + isActive: boolean, + @IViewletService viewletService: IViewletService, + @IPanelService panelService: IPanelService + ) { + super(viewletService, panelService, scopeId); + + this.progressbar = progressbar; + this.isActive = isActive || types.isUndefinedOrNull(scopeId); // If service is unscoped, enable by default + } + + onScopeDeactivated(): void { + this.isActive = false; + } + + onScopeActivated(): void { + this.isActive = true; + + // Return early if progress state indicates that progress is done + if (this.progressState.type === ProgressState.Done.type) { + return; + } + + // Replay Infinite Progress from Promise + if (this.progressState.type === ProgressState.Type.While) { + let delay: number | undefined; + if (this.progressState.whileDelay > 0) { + const remainingDelay = this.progressState.whileDelay - (Date.now() - this.progressState.whileStart); + if (remainingDelay > 0) { + delay = remainingDelay; + } + } + + this.doShowWhile(delay); + } + + // Replay Infinite Progress + else if (this.progressState.type === ProgressState.Type.Infinite) { + this.progressbar.infinite().show(); + } + + // Replay Finite Progress (Total & Worked) + else if (this.progressState.type === ProgressState.Type.Work) { + if (this.progressState.total) { + this.progressbar.total(this.progressState.total).show(); + } + + if (this.progressState.worked) { + this.progressbar.worked(this.progressState.worked).show(); + } + } + } + + show(infinite: true, delay?: number): IProgressRunner; + show(total: number, delay?: number): IProgressRunner; + show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { + + // Sort out Arguments + if (typeof infiniteOrTotal === 'boolean') { + this.progressState = ProgressState.Infinite; + } else { + this.progressState = new ProgressState.Work(infiniteOrTotal, undefined); + } + + // Active: Show Progress + if (this.isActive) { + + // Infinite: Start Progressbar and Show after Delay + if (this.progressState.type === ProgressState.Type.Infinite) { + this.progressbar.infinite().show(delay); + } + + // Finite: Start Progressbar and Show after Delay + else if (this.progressState.type === ProgressState.Type.Work && typeof this.progressState.total === 'number') { + this.progressbar.total(this.progressState.total).show(delay); + } + } + + return { + total: (total: number) => { + this.progressState = new ProgressState.Work( + total, + this.progressState.type === ProgressState.Type.Work ? this.progressState.worked : undefined); + + if (this.isActive) { + this.progressbar.total(total); + } + }, + + worked: (worked: number) => { + + // Verify first that we are either not active or the progressbar has a total set + if (!this.isActive || this.progressbar.hasTotal()) { + this.progressState = new ProgressState.Work( + this.progressState.type === ProgressState.Type.Work ? this.progressState.total : undefined, + this.progressState.type === ProgressState.Type.Work && typeof this.progressState.worked === 'number' ? this.progressState.worked + worked : worked); + + if (this.isActive) { + this.progressbar.worked(worked); + } + } + + // Otherwise the progress bar does not support worked(), we fallback to infinite() progress + else { + this.progressState = ProgressState.Infinite; + this.progressbar.infinite().show(); + } + }, + + done: () => { + this.progressState = ProgressState.Done; + + if (this.isActive) { + this.progressbar.stop().hide(); + } + } + }; + } + + async showWhile(promise: Promise, delay?: number): Promise { + + // Join with existing running promise to ensure progress is accurate + if (this.progressState.type === ProgressState.Type.While) { + promise = Promise.all([promise, this.progressState.whilePromise]); + } + + // Keep Promise in State + this.progressState = new ProgressState.While(promise, delay || 0, Date.now()); + + try { + this.doShowWhile(delay); + + await promise; + } catch (error) { + // ignore + } finally { + + // If this is not the last promise in the list of joined promises, skip this + if (this.progressState.type !== ProgressState.Type.While || this.progressState.whilePromise === promise) { + + // The while promise is either null or equal the promise we last hooked on + this.progressState = ProgressState.None; + + if (this.isActive) { + this.progressbar.stop().hide(); + } + } + } + } + + private doShowWhile(delay?: number): void { + + // Show Progress when active + if (this.isActive) { + this.progressbar.infinite().show(delay); + } + } +} + +export class LocalProgressService implements ILocalProgressService { + + _serviceBrand: ServiceIdentifier; + + constructor(private progressbar: ProgressBar) { } + + show(infinite: true, delay?: number): IProgressRunner; + show(total: number, delay?: number): IProgressRunner; + show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { + if (typeof infiniteOrTotal === 'boolean') { + this.progressbar.infinite().show(delay); + } else { + this.progressbar.total(infiniteOrTotal).show(delay); + } + + return { + total: (total: number) => { + this.progressbar.total(total); + }, + + worked: (worked: number) => { + if (this.progressbar.hasTotal()) { + this.progressbar.worked(worked); + } else { + this.progressbar.infinite().show(); + } + }, + + done: () => { + this.progressbar.stop().hide(); + } + }; + } + + async showWhile(promise: Promise, delay?: number): Promise { + try { + this.progressbar.infinite().show(delay); + + await promise; + } catch (error) { + // ignore + } finally { + this.progressbar.stop().hide(); + } + } +} diff --git a/src/vs/workbench/services/progress/browser/media/progressService2.css b/src/vs/workbench/services/progress/browser/media/progressService.css similarity index 100% rename from src/vs/workbench/services/progress/browser/media/progressService2.css rename to src/vs/workbench/services/progress/browser/media/progressService.css diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 5a37e806e67..088d60cf3cb 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -3,294 +3,339 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; -import * as types from 'vs/base/common/types'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import 'vs/css!./media/progressService'; + +import { localize } from 'vs/nls'; +import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; +import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, emptyProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions } from 'vs/platform/progress/common/progress'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; - - -namespace ProgressState { - export const enum Type { - None, - Done, - Infinite, - While, - Work - } - - export const None = new class { readonly type = Type.None; }; - export const Done = new class { readonly type = Type.Done; }; - export const Infinite = new class { readonly type = Type.Infinite; }; - - export class While { - public readonly type = Type.While; - constructor( - public readonly whilePromise: Promise, - public readonly whileStart: number, - public readonly whileDelay: number, - ) { } - } - - export class Work { - public readonly type = Type.Work; - constructor( - public readonly total: number | undefined, - public readonly worked: number | undefined - ) { } - } - - export type State = - typeof None - | typeof Done - | typeof Infinite - | While - | Work; -} - -export abstract class ScopedService extends Disposable { - - constructor(private viewletService: IViewletService, private panelService: IPanelService, private scopeId: string) { - super(); - - this.registerListeners(); - } - - registerListeners(): void { - this._register(this.viewletService.onDidViewletOpen(viewlet => this.onScopeOpened(viewlet.getId()))); - this._register(this.panelService.onDidPanelOpen(({ panel }) => this.onScopeOpened(panel.getId()))); - - this._register(this.viewletService.onDidViewletClose(viewlet => this.onScopeClosed(viewlet.getId()))); - this._register(this.panelService.onDidPanelClose(panel => this.onScopeClosed(panel.getId()))); - } - - private onScopeClosed(scopeId: string) { - if (scopeId === this.scopeId) { - this.onScopeDeactivated(); - } - } - - private onScopeOpened(scopeId: string) { - if (scopeId === this.scopeId) { - this.onScopeActivated(); - } - } - - abstract onScopeActivated(): void; - - abstract onScopeDeactivated(): void; -} - -export class ScopedProgressService extends ScopedService implements IProgressService { - _serviceBrand: any; - private isActive: boolean; - private progressbar: ProgressBar; - private progressState: ProgressState.State = ProgressState.None; - - constructor( - progressbar: ProgressBar, - scopeId: string, - isActive: boolean, - @IViewletService viewletService: IViewletService, - @IPanelService panelService: IPanelService - ) { - super(viewletService, panelService, scopeId); - - this.progressbar = progressbar; - this.isActive = isActive || types.isUndefinedOrNull(scopeId); // If service is unscoped, enable by default - } - - onScopeDeactivated(): void { - this.isActive = false; - } - - onScopeActivated(): void { - this.isActive = true; - - // Return early if progress state indicates that progress is done - if (this.progressState.type === ProgressState.Done.type) { - return; - } - - // Replay Infinite Progress from Promise - if (this.progressState.type === ProgressState.Type.While) { - let delay: number | undefined; - if (this.progressState.whileDelay > 0) { - const remainingDelay = this.progressState.whileDelay - (Date.now() - this.progressState.whileStart); - if (remainingDelay > 0) { - delay = remainingDelay; - } - } - - this.doShowWhile(delay); - } - - // Replay Infinite Progress - else if (this.progressState.type === ProgressState.Type.Infinite) { - this.progressbar.infinite().show(); - } - - // Replay Finite Progress (Total & Worked) - else if (this.progressState.type === ProgressState.Type.Work) { - if (this.progressState.total) { - this.progressbar.total(this.progressState.total).show(); - } - - if (this.progressState.worked) { - this.progressbar.worked(this.progressState.worked).show(); - } - } - } - - show(infinite: true, delay?: number): IProgressRunner; - show(total: number, delay?: number): IProgressRunner; - show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { - // Sort out Arguments - if (typeof infiniteOrTotal === 'boolean') { - this.progressState = ProgressState.Infinite; - } else { - this.progressState = new ProgressState.Work(infiniteOrTotal, undefined); - } - - // Active: Show Progress - if (this.isActive) { - - // Infinite: Start Progressbar and Show after Delay - if (this.progressState.type === ProgressState.Type.Infinite) { - this.progressbar.infinite().show(delay); - } - - // Finite: Start Progressbar and Show after Delay - else if (this.progressState.type === ProgressState.Type.Work && typeof this.progressState.total === 'number') { - this.progressbar.total(this.progressState.total).show(delay); - } - } - - return { - total: (total: number) => { - this.progressState = new ProgressState.Work( - total, - this.progressState.type === ProgressState.Type.Work ? this.progressState.worked : undefined); - - if (this.isActive) { - this.progressbar.total(total); - } - }, - - worked: (worked: number) => { - - // Verify first that we are either not active or the progressbar has a total set - if (!this.isActive || this.progressbar.hasTotal()) { - this.progressState = new ProgressState.Work( - this.progressState.type === ProgressState.Type.Work ? this.progressState.total : undefined, - this.progressState.type === ProgressState.Type.Work && typeof this.progressState.worked === 'number' ? this.progressState.worked + worked : worked); - - if (this.isActive) { - this.progressbar.worked(worked); - } - } - - // Otherwise the progress bar does not support worked(), we fallback to infinite() progress - else { - this.progressState = ProgressState.Infinite; - this.progressbar.infinite().show(); - } - }, - - done: () => { - this.progressState = ProgressState.Done; - - if (this.isActive) { - this.progressbar.stop().hide(); - } - } - }; - } - - async showWhile(promise: Promise, delay?: number): Promise { - - // Join with existing running promise to ensure progress is accurate - if (this.progressState.type === ProgressState.Type.While) { - promise = Promise.all([promise, this.progressState.whilePromise]); - } - - // Keep Promise in State - this.progressState = new ProgressState.While(promise, delay || 0, Date.now()); - - try { - this.doShowWhile(delay); - - await promise; - } catch (error) { - // ignore - } finally { - - // If this is not the last promise in the list of joined promises, skip this - if (this.progressState.type !== ProgressState.Type.While || this.progressState.whilePromise === promise) { - - // The while promise is either null or equal the promise we last hooked on - this.progressState = ProgressState.None; - - if (this.isActive) { - this.progressbar.stop().hide(); - } - } - } - } - - private doShowWhile(delay?: number): void { - - // Show Progress when active - if (this.isActive) { - this.progressbar.infinite().show(delay); - } - } -} +import { StatusbarAlignment, IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; +import { timeout } from 'vs/base/common/async'; +import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; +import { INotificationService, Severity, INotificationHandle, INotificationActions } from 'vs/platform/notification/common/notification'; +import { Action } from 'vs/base/common/actions'; +import { Event } from 'vs/base/common/event'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { Dialog } from 'vs/base/browser/ui/dialog/dialog'; +import { attachDialogStyler } from 'vs/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { EventHelper } from 'vs/base/browser/dom'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class ProgressService implements IProgressService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; - constructor(private progressbar: ProgressBar) { } + private readonly _stack: [IProgressOptions, Progress][] = []; + private _globalStatusEntry: IDisposable; - show(infinite: true, delay?: number): IProgressRunner; - show(total: number, delay?: number): IProgressRunner; - show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { - if (typeof infiniteOrTotal === 'boolean') { - this.progressbar.infinite().show(delay); - } else { - this.progressbar.total(infiniteOrTotal).show(delay); + constructor( + @IActivityService private readonly _activityBar: IActivityService, + @IViewletService private readonly _viewletService: IViewletService, + @INotificationService private readonly _notificationService: INotificationService, + @IStatusbarService private readonly _statusbarService: IStatusbarService, + @ILayoutService private readonly _layoutService: ILayoutService, + @IThemeService private readonly _themeService: IThemeService, + @IKeybindingService private readonly _keybindingService: IKeybindingService + ) { } + + withProgress(options: IProgressOptions, task: (progress: IProgress) => Promise, onDidCancel?: () => void): Promise { + const { location } = options; + if (typeof location === 'string') { + const viewlet = this._viewletService.getViewlet(location); + if (viewlet) { + return this._withViewletProgress(location, task, { ...options, location }); + } + + return Promise.reject(new Error(`Bad progress location: ${location}`)); } - return { - total: (total: number) => { - this.progressbar.total(total); - }, + switch (location) { + case ProgressLocation.Notification: + return this._withNotificationProgress({ ...options, location }, task, onDidCancel); + case ProgressLocation.Window: + return this._withWindowProgress(options, task); + case ProgressLocation.Explorer: + return this._withViewletProgress('workbench.view.explorer', task, { ...options, location }); + case ProgressLocation.Scm: + return this._withViewletProgress('workbench.view.scm', task, { ...options, location }); + case ProgressLocation.Extensions: + return this._withViewletProgress('workbench.view.extensions', task, { ...options, location }); + case ProgressLocation.Dialog: + return this._withDialogProgress(options, task, onDidCancel); + default: + return Promise.reject(new Error(`Bad progress location: ${location}`)); + } + } - worked: (worked: number) => { - if (this.progressbar.hasTotal()) { - this.progressbar.worked(worked); - } else { - this.progressbar.infinite().show(); - } - }, + private _withWindowProgress(options: IProgressOptions, callback: (progress: IProgress<{ message?: string }>) => Promise): Promise { + const task: [IProgressOptions, Progress] = [options, new Progress(() => this._updateWindowProgress())]; - done: () => { - this.progressbar.stop().hide(); + const promise = callback(task[1]); + + let delayHandle: any = setTimeout(() => { + delayHandle = undefined; + this._stack.unshift(task); + this._updateWindowProgress(); + + // show progress for at least 150ms + Promise.all([ + timeout(150), + promise + ]).finally(() => { + const idx = this._stack.indexOf(task); + this._stack.splice(idx, 1); + this._updateWindowProgress(); + }); + }, 150); + + // cancel delay if promise finishes below 150ms + return promise.finally(() => clearTimeout(delayHandle)); + } + + private _updateWindowProgress(idx: number = 0) { + dispose(this._globalStatusEntry); + + if (idx < this._stack.length) { + const [options, progress] = this._stack[idx]; + + let progressTitle = options.title; + let progressMessage = progress.value && progress.value.message; + let text: string; + let title: string; + + if (progressTitle && progressMessage) { + // : <message> + text = localize('progress.text2', "{0}: {1}", progressTitle, progressMessage); + title = options.source ? localize('progress.title3', "[{0}] {1}: {2}", options.source, progressTitle, progressMessage) : text; + + } else if (progressTitle) { + // <title> + text = progressTitle; + title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressTitle) : text; + + } else if (progressMessage) { + // <message> + text = progressMessage; + title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressMessage) : text; + + } else { + // no title, no message -> no progress. try with next on stack + this._updateWindowProgress(idx + 1); + return; + } + + this._globalStatusEntry = this._statusbarService.addEntry({ + text: `$(sync~spin) ${text}`, + tooltip: title + }, StatusbarAlignment.LEFT); + } + } + + private _withNotificationProgress<P extends Promise<R>, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { + const toDispose = new DisposableStore(); + + const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => { + if (!message) { + return undefined; // we need a message at least + } + + const actions: INotificationActions = { primary: options.primaryActions || [], secondary: options.secondaryActions || [] }; + if (options.cancellable) { + const cancelAction = new class extends Action { + constructor() { + super('progress.cancel', localize('cancel', "Cancel"), undefined, true); + } + + run(): Promise<any> { + if (typeof onDidCancel === 'function') { + onDidCancel(); + } + + return Promise.resolve(undefined); + } + }; + toDispose.push(cancelAction); + + actions.primary!.push(cancelAction); + } + + const handle = this._notificationService.notify({ + severity: Severity.Info, + message, + source: options.source, + actions + }); + + updateProgress(handle, increment); + + Event.once(handle.onDidClose)(() => { + toDispose.dispose(); + }); + + return handle; + }; + + const updateProgress = (notification: INotificationHandle, increment?: number): void => { + if (typeof increment === 'number' && increment >= 0) { + notification.progress.total(100); // always percentage based + notification.progress.worked(increment); + } else { + notification.progress.infinite(); } }; + + let handle: INotificationHandle | undefined; + const updateNotification = (message?: string, increment?: number): void => { + if (!handle) { + handle = createNotification(message, increment); + } else { + if (typeof message === 'string') { + let newMessage: string; + if (typeof options.title === 'string') { + newMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932) + } else { + newMessage = message; + } + + handle.updateMessage(newMessage); + } + + if (typeof increment === 'number') { + updateProgress(handle, increment); + } + } + }; + + // Show initially + updateNotification(options.title); + + // Update based on progress + const promise = callback({ + report: progress => { + updateNotification(progress.message, progress.increment); + } + }); + + // Show progress for at least 800ms and then hide once done or canceled + Promise.all([timeout(800), promise]).finally(() => { + if (handle) { + handle.close(); + } + }); + + return promise; } - async showWhile(promise: Promise<any>, delay?: number): Promise<void> { - try { - this.progressbar.infinite().show(delay); + private _withViewletProgress<P extends Promise<R>, R = unknown>(viewletId: string, task: (progress: IProgress<{ message?: string }>) => P, options: IProgressCompositeOptions): P { + const promise = task(emptyProgress); - await promise; - } catch (error) { - // ignore - } finally { - this.progressbar.stop().hide(); + // show in viewlet + const viewletProgress = this._viewletService.getProgressIndicator(viewletId); + if (viewletProgress) { + viewletProgress.showWhile(promise, options.delay); } + + // show activity bar + let activityProgress: IDisposable; + let delayHandle: any = setTimeout(() => { + delayHandle = undefined; + + const handle = this._activityBar.showActivity( + viewletId, + new ProgressBadge(() => ''), + 'progress-badge', + 100 + ); + + const startTimeVisible = Date.now(); + const minTimeVisible = 300; + activityProgress = { + dispose() { + const d = Date.now() - startTimeVisible; + if (d < minTimeVisible) { + // should at least show for Nms + setTimeout(() => handle.dispose(), minTimeVisible - d); + } else { + // shown long enough + handle.dispose(); + } + } + }; + }, options.delay || 300); + + promise.finally(() => { + clearTimeout(delayHandle); + dispose(activityProgress); + }); + + return promise; + } + + private _withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressOptions, task: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { + const disposables = new DisposableStore(); + const allowableCommands = [ + 'workbench.action.quit', + 'workbench.action.reloadWindow' + ]; + + let dialog: Dialog; + + const createDialog = (message: string) => { + dialog = new Dialog( + this._layoutService.container, + message, + [options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")], + { + type: 'pending', + keyEventProcessor: (event: StandardKeyboardEvent) => { + const resolved = this._keybindingService.softDispatch(event, this._layoutService.container); + if (resolved && resolved.commandId) { + if (allowableCommands.indexOf(resolved.commandId) === -1) { + EventHelper.stop(event, true); + } + } + } + } + ); + + disposables.push(dialog); + disposables.push(attachDialogStyler(dialog, this._themeService)); + + dialog.show().then(() => { + if (typeof onDidCancel === 'function') { + onDidCancel(); + } + + dispose(dialog); + }); + + return dialog; + }; + + const updateDialog = (message?: string) => { + if (message && !dialog) { + dialog = createDialog(message); + } else if (message) { + dialog.updateMessage(message); + } + }; + + const promise = task({ + report: progress => { + updateDialog(progress.message); + } + }); + + promise.finally(() => { + dispose(disposables); + }); + + return promise; } } + +registerSingleton(IProgressService, ProgressService, true); diff --git a/src/vs/workbench/services/progress/browser/progressService2.ts b/src/vs/workbench/services/progress/browser/progressService2.ts deleted file mode 100644 index d2e4eee1263..00000000000 --- a/src/vs/workbench/services/progress/browser/progressService2.ts +++ /dev/null @@ -1,344 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./media/progressService2'; - -import { localize } from 'vs/nls'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IProgressService2, IProgressOptions, IProgressStep, ProgressLocation, IProgress, emptyProgress, Progress, IProgressNotificationOptions } from 'vs/platform/progress/common/progress'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { StatusbarAlignment, IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; -import { timeout } from 'vs/base/common/async'; -import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; -import { INotificationService, Severity, INotificationHandle, INotificationActions } from 'vs/platform/notification/common/notification'; -import { Action } from 'vs/base/common/actions'; -import { Event } from 'vs/base/common/event'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { Dialog } from 'vs/base/browser/ui/dialog/dialog'; -import { attachDialogStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { EventHelper } from 'vs/base/browser/dom'; - -export class ProgressService2 implements IProgressService2 { - - _serviceBrand: any; - - private readonly _stack: [IProgressOptions, Progress<IProgressStep>][] = []; - private _globalStatusEntry: IDisposable; - - constructor( - @IActivityService private readonly _activityBar: IActivityService, - @IViewletService private readonly _viewletService: IViewletService, - @INotificationService private readonly _notificationService: INotificationService, - @IStatusbarService private readonly _statusbarService: IStatusbarService, - @ILayoutService private readonly _layoutService: ILayoutService, - @IThemeService private readonly _themeService: IThemeService, - @IKeybindingService private readonly _keybindingService: IKeybindingService - ) { } - - withProgress<R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: () => void): Promise<R> { - - const { location } = options; - if (typeof location === 'string') { - const viewlet = this._viewletService.getViewlet(location); - if (viewlet) { - return this._withViewletProgress(location, task); - } - return Promise.reject(new Error(`Bad progress location: ${location}`)); - } - - switch (location) { - case ProgressLocation.Notification: - return this._withNotificationProgress({ ...options, location: ProgressLocation.Notification }, task, onDidCancel); - case ProgressLocation.Window: - return this._withWindowProgress(options, task); - case ProgressLocation.Explorer: - return this._withViewletProgress('workbench.view.explorer', task); - case ProgressLocation.Scm: - return this._withViewletProgress('workbench.view.scm', task); - case ProgressLocation.Extensions: - return this._withViewletProgress('workbench.view.extensions', task); - case ProgressLocation.Dialog: - return this._withDialogProgress(options, task, onDidCancel); - default: - return Promise.reject(new Error(`Bad progress location: ${location}`)); - } - } - - private _withWindowProgress<R = unknown>(options: IProgressOptions, callback: (progress: IProgress<{ message?: string }>) => Promise<R>): Promise<R> { - - const task: [IProgressOptions, Progress<IProgressStep>] = [options, new Progress<IProgressStep>(() => this._updateWindowProgress())]; - - const promise = callback(task[1]); - - let delayHandle: any = setTimeout(() => { - delayHandle = undefined; - this._stack.unshift(task); - this._updateWindowProgress(); - - // show progress for at least 150ms - Promise.all([ - timeout(150), - promise - ]).finally(() => { - const idx = this._stack.indexOf(task); - this._stack.splice(idx, 1); - this._updateWindowProgress(); - }); - - }, 150); - - // cancel delay if promise finishes below 150ms - return promise.finally(() => clearTimeout(delayHandle)); - } - - private _updateWindowProgress(idx: number = 0) { - - dispose(this._globalStatusEntry); - - if (idx < this._stack.length) { - - const [options, progress] = this._stack[idx]; - - let progressTitle = options.title; - let progressMessage = progress.value && progress.value.message; - let text: string; - let title: string; - - if (progressTitle && progressMessage) { - // <title>: <message> - text = localize('progress.text2', "{0}: {1}", progressTitle, progressMessage); - title = options.source ? localize('progress.title3', "[{0}] {1}: {2}", options.source, progressTitle, progressMessage) : text; - - } else if (progressTitle) { - // <title> - text = progressTitle; - title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressTitle) : text; - - } else if (progressMessage) { - // <message> - text = progressMessage; - title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressMessage) : text; - - } else { - // no title, no message -> no progress. try with next on stack - this._updateWindowProgress(idx + 1); - return; - } - - this._globalStatusEntry = this._statusbarService.addEntry({ - text: `$(sync~spin) ${text}`, - tooltip: title - }, StatusbarAlignment.LEFT); - } - } - - private _withNotificationProgress<P extends Promise<R>, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { - const toDispose: IDisposable[] = []; - - const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => { - if (!message) { - return undefined; // we need a message at least - } - - const actions: INotificationActions = { primary: options.primaryActions || [], secondary: options.secondaryActions || [] }; - if (options.cancellable) { - const cancelAction = new class extends Action { - constructor() { - super('progress.cancel', localize('cancel', "Cancel"), undefined, true); - } - - run(): Promise<any> { - if (typeof onDidCancel === 'function') { - onDidCancel(); - } - - return Promise.resolve(undefined); - } - }; - toDispose.push(cancelAction); - - actions.primary!.push(cancelAction); - } - - const handle = this._notificationService.notify({ - severity: Severity.Info, - message, - source: options.source, - actions - }); - - updateProgress(handle, increment); - - Event.once(handle.onDidClose)(() => { - dispose(toDispose); - }); - - return handle; - }; - - const updateProgress = (notification: INotificationHandle, increment?: number): void => { - if (typeof increment === 'number' && increment >= 0) { - notification.progress.total(100); // always percentage based - notification.progress.worked(increment); - } else { - notification.progress.infinite(); - } - }; - - let handle: INotificationHandle | undefined; - const updateNotification = (message?: string, increment?: number): void => { - if (!handle) { - handle = createNotification(message, increment); - } else { - if (typeof message === 'string') { - let newMessage: string; - if (typeof options.title === 'string') { - newMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932) - } else { - newMessage = message; - } - - handle.updateMessage(newMessage); - } - - if (typeof increment === 'number') { - updateProgress(handle, increment); - } - } - }; - - // Show initially - updateNotification(options.title); - - // Update based on progress - const p = callback({ - report: progress => { - updateNotification(progress.message, progress.increment); - } - }); - - // Show progress for at least 800ms and then hide once done or canceled - Promise.all([timeout(800), p]).finally(() => { - if (handle) { - handle.close(); - } - }); - - return p; - } - - private _withViewletProgress<P extends Promise<R>, R = unknown>(viewletId: string, task: (progress: IProgress<{ message?: string }>) => P): P { - - const promise = task(emptyProgress); - - // show in viewlet - const viewletProgress = this._viewletService.getProgressIndicator(viewletId); - if (viewletProgress) { - viewletProgress.showWhile(promise); - } - - // show activity bar - let activityProgress: IDisposable; - let delayHandle: any = setTimeout(() => { - delayHandle = undefined; - const handle = this._activityBar.showActivity( - viewletId, - new ProgressBadge(() => ''), - 'progress-badge', - 100 - ); - const startTimeVisible = Date.now(); - const minTimeVisible = 300; - activityProgress = { - dispose() { - const d = Date.now() - startTimeVisible; - if (d < minTimeVisible) { - // should at least show for Nms - setTimeout(() => handle.dispose(), minTimeVisible - d); - } else { - // shown long enough - handle.dispose(); - } - } - }; - }, 300); - - const onDone = () => { - clearTimeout(delayHandle); - dispose(activityProgress); - }; - - promise.then(onDone, onDone); - return promise; - } - - private _withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressOptions, task: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { - const disposables: IDisposable[] = []; - const allowableCommands = [ - 'workbench.action.quit', - 'workbench.action.reloadWindow' - ]; - - let dialog: Dialog; - - const createDialog = (message: string) => { - dialog = new Dialog( - this._layoutService.container, - message, - [options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")], - { - type: 'pending', - keyEventProcessor: (event: StandardKeyboardEvent) => { - const resolved = this._keybindingService.softDispatch(event, this._layoutService.container); - if (resolved && resolved.commandId) { - if (allowableCommands.indexOf(resolved.commandId) === -1) { - EventHelper.stop(event, true); - } - } - } - } - ); - - disposables.push(dialog); - disposables.push(attachDialogStyler(dialog, this._themeService)); - - dialog.show().then(() => { - if (typeof onDidCancel === 'function') { - onDidCancel(); - } - - dispose(dialog); - }); - - return dialog; - }; - - const updateDialog = (message?: string) => { - if (message && !dialog) { - dialog = createDialog(message); - } else if (message) { - dialog.updateMessage(message); - } - }; - - const p = task({ - report: progress => { - updateDialog(progress.message); - } - }); - - p.finally(() => { - dispose(disposables); - }); - - return p; - } -} - -registerSingleton(IProgressService2, ProgressService2, true); diff --git a/src/vs/workbench/services/progress/test/progressService.test.ts b/src/vs/workbench/services/progress/test/localProgressService.test.ts similarity index 99% rename from src/vs/workbench/services/progress/test/progressService.test.ts rename to src/vs/workbench/services/progress/test/localProgressService.test.ts index 205b49e082e..dad585ef4f0 100644 --- a/src/vs/workbench/services/progress/test/progressService.test.ts +++ b/src/vs/workbench/services/progress/test/localProgressService.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { IAction, IActionViewItem } from 'vs/base/common/actions'; import { IEditorControl } from 'vs/workbench/common/editor'; -import { ScopedProgressService, ScopedService } from 'vs/workbench/services/progress/browser/progressService'; +import { ScopedProgressService, ScopedService } from 'vs/workbench/services/progress/browser/localProgressService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewlet } from 'vs/workbench/common/viewlet'; diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index c114ccbe3b9..4da4619b9e9 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -144,7 +144,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - private onFileChanges(e: FileChangesEvent): void { + private async onFileChanges(e: FileChangesEvent): Promise<void> { let fileEventImpactsModel = false; let newInOrphanModeGuess: boolean | undefined; @@ -167,28 +167,25 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } if (fileEventImpactsModel && this.inOrphanMode !== newInOrphanModeGuess) { - let checkOrphanedPromise: Promise<boolean>; + let newInOrphanModeValidated: boolean = false; if (newInOrphanModeGuess) { // We have received reports of users seeing delete events even though the file still // exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665). // Since we do not want to mark the model as orphaned, we have to check if the // file is really gone and not just a faulty file event. - checkOrphanedPromise = timeout(100).then(() => { - if (this.disposed) { - return true; - } + await timeout(100); - return this.fileService.exists(this.resource).then(exists => !exists); - }); - } else { - checkOrphanedPromise = Promise.resolve(false); + if (this.disposed) { + newInOrphanModeValidated = true; + } else { + const exists = await this.fileService.exists(this.resource); + newInOrphanModeValidated = !exists; + } } - checkOrphanedPromise.then(newInOrphanModeValidated => { - if (this.inOrphanMode !== newInOrphanModeValidated && !this.disposed) { - this.setOrphaned(newInOrphanModeValidated); - } - }); + if (this.inOrphanMode !== newInOrphanModeValidated && !this.disposed) { + this.setOrphaned(newInOrphanModeValidated); + } } } @@ -239,13 +236,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.backupFileService.backupResource<IBackupMetaData>(target, this.createSnapshot(), this.versionId, meta); } - - return Promise.resolve(); } async revert(soft?: boolean): Promise<void> { if (!this.isResolved()) { - return Promise.resolve(undefined); + return; } // Cancel any running auto-save @@ -254,25 +249,21 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Unset flags const undo = this.setDirty(false); - let loadPromise: Promise<unknown>; - if (soft) { - loadPromise = Promise.resolve(); - } else { - loadPromise = this.load({ forceReadFromDisk: true }); + // Force read from disk unless reverting soft + if (!soft) { + try { + await this.load({ forceReadFromDisk: true }); + } catch (error) { + + // Set flags back to previous values, we are still dirty if revert failed + undo(); + + throw error; + } } - try { - await loadPromise; - - // Emit file change event - this._onDidStateChange.fire(StateChange.REVERTED); - } catch (error) { - - // Set flags back to previous values, we are still dirty if revert failed - undo(); - - return Promise.reject(error); - } + // Emit file change event + this._onDidStateChange.fire(StateChange.REVERTED); } async load(options?: ILoadOptions): Promise<ITextFileEditorModel> { @@ -600,7 +591,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil save(options: ISaveOptions = Object.create(null)): Promise<void> { if (!this.isResolved()) { - return Promise.resolve(undefined); + return Promise.resolve(); } this.logService.trace('save() - enter', this.resource); @@ -626,7 +617,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (this.saveSequentializer.hasPendingSave(versionId)) { this.logService.trace(`doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource); - return this.saveSequentializer.pendingSave || Promise.resolve(undefined); + return this.saveSequentializer.pendingSave || Promise.resolve(); } // Return early if not dirty (unless forced) or version changed meanwhile @@ -639,7 +630,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if ((!options.force && !this.dirty) || versionId !== this.versionId) { this.logService.trace(`doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource); - return Promise.resolve(undefined); + return Promise.resolve(); } // Return if currently saving by storing this save request as the next save that should happen. diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index ade71f748cd..a2f3269ecd0 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -151,10 +151,10 @@ export class TextModelResolverService implements ITextModelService { const cachedModel = this.modelService.getModel(resource); if (!cachedModel) { - return Promise.reject(new Error('Cant resolve inmemory resource')); + throw new Error('Cant resolve inmemory resource'); } - return Promise.resolve(new ImmortalReference(this.instantiationService.createInstance(ResourceEditorModel, resource) as IResolvedTextEditorModel)); + return new ImmortalReference(this.instantiationService.createInstance(ResourceEditorModel, resource) as IResolvedTextEditorModel); } const ref = this.resourceModelCollection.acquire(resource.toString()); diff --git a/src/vs/workbench/services/viewlet/browser/viewlet.ts b/src/vs/workbench/services/viewlet/browser/viewlet.ts index 3883013c4a6..2567f4cacea 100644 --- a/src/vs/workbench/services/viewlet/browser/viewlet.ts +++ b/src/vs/workbench/services/viewlet/browser/viewlet.ts @@ -7,7 +7,7 @@ import { IViewlet } from 'vs/workbench/common/viewlet'; import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; export const IViewletService = createDecorator<IViewletService>('viewletService'); @@ -47,7 +47,7 @@ export interface IViewletService { /** * Returns the progress indicator for the side bar. */ - getProgressIndicator(id: string): IProgressService | null; + getProgressIndicator(id: string): ILocalProgressService | null; /** * Hide the active viewlet. diff --git a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts index b1c60ab4252..f7324b042a1 100644 --- a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts @@ -67,74 +67,81 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } - private saveUntitedBeforeShutdown(reason: ShutdownReason): Promise<boolean> | undefined { + private async saveUntitedBeforeShutdown(reason: ShutdownReason): Promise<boolean> { if (reason !== ShutdownReason.LOAD && reason !== ShutdownReason.CLOSE) { - return undefined; // only interested when window is closing or loading + return false; // only interested when window is closing or loading } const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); if (!workspaceIdentifier || !isEqualOrParent(workspaceIdentifier.configPath, this.environmentService.untitledWorkspacesHome)) { - return undefined; // only care about untitled workspaces to ask for saving + return false; // only care about untitled workspaces to ask for saving } - return this.windowsService.getWindowCount().then(windowCount => { - if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) { - return false; // Windows/Linux: quits when last window is closed, so do not ask then - } - enum ConfirmResult { - SAVE, - DONT_SAVE, - CANCEL - } + const windowCount = await this.windowsService.getWindowCount(); - const save = { label: mnemonicButtonLabel(nls.localize('save', "Save")), result: ConfirmResult.SAVE }; - const dontSave = { label: mnemonicButtonLabel(nls.localize('doNotSave', "Don't Save")), result: ConfirmResult.DONT_SAVE }; - const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL }; + if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) { + return false; // Windows/Linux: quits when last window is closed, so do not ask then + } - const buttons: { label: string; result: ConfirmResult; }[] = []; - if (isWindows) { - buttons.push(save, dontSave, cancel); - } else if (isLinux) { - buttons.push(dontSave, cancel, save); - } else { - buttons.push(save, cancel, dontSave); - } + enum ConfirmResult { + SAVE, + DONT_SAVE, + CANCEL + } - const message = nls.localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"); - const detail = nls.localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."); - const cancelId = buttons.indexOf(cancel); + const save = { label: mnemonicButtonLabel(nls.localize('save', "Save")), result: ConfirmResult.SAVE }; + const dontSave = { label: mnemonicButtonLabel(nls.localize('doNotSave', "Don't Save")), result: ConfirmResult.DONT_SAVE }; + const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL }; - return this.dialogService.show(Severity.Warning, message, buttons.map(button => button.label), { detail, cancelId }).then(res => { - switch (buttons[res].result) { + const buttons: { label: string; result: ConfirmResult; }[] = []; + if (isWindows) { + buttons.push(save, dontSave, cancel); + } else if (isLinux) { + buttons.push(dontSave, cancel, save); + } else { + buttons.push(save, cancel, dontSave); + } - // Cancel: veto unload - case ConfirmResult.CANCEL: - return true; + const message = nls.localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"); + const detail = nls.localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."); + const cancelId = buttons.indexOf(cancel); - // Don't Save: delete workspace - case ConfirmResult.DONT_SAVE: - this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); - return false; + const res = await this.dialogService.show(Severity.Warning, message, buttons.map(button => button.label), { detail, cancelId }); - // Save: save workspace, but do not veto unload - case ConfirmResult.SAVE: { - return this.pickNewWorkspacePath().then(newWorkspacePath => { - if (newWorkspacePath) { - return this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath).then(_ => { - return this.workspaceService.getWorkspaceIdentifier(newWorkspacePath).then(newWorkspaceIdentifier => { - const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }); - this.windowsService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]); - this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); - return false; - }); - }, () => false); - } - return true; // keep veto if no target was provided - }); - } + switch (buttons[res].result) { + + // Cancel: veto unload + case ConfirmResult.CANCEL: + return true; + + // Don't Save: delete workspace + case ConfirmResult.DONT_SAVE: + this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); + return false; + + // Save: save workspace, but do not veto unload if path provided + case ConfirmResult.SAVE: { + const newWorkspacePath = await this.pickNewWorkspacePath(); + if (!newWorkspacePath) { + return true; // keep veto if no target was provided } - }); - }); + + try { + await this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath); + + const newWorkspaceIdentifier = await this.workspaceService.getWorkspaceIdentifier(newWorkspacePath); + + const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }); + this.windowsService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]); + + this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); + } catch (error) { + // ignore + } + + return false; + } + } } pickNewWorkspacePath(): Promise<URI | undefined> { @@ -218,7 +225,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { newWorkspaceFolders = distinct(newWorkspaceFolders, folder => getComparisonKey(folder.uri)); if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) { - return Promise.resolve(); // return if the operation is a no-op for the current state + return; // return if the operation is a no-op for the current state } return this.createAndEnterWorkspace(newWorkspaceFolders); @@ -267,7 +274,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { async createAndEnterWorkspace(folders: IWorkspaceFolderCreationData[], path?: URI): Promise<void> { if (path && !await this.isValidTargetWorkspacePath(path)) { - return Promise.reject(null); + return; } const remoteAuthority = this.environmentService.configuration.remoteAuthority; const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders, remoteAuthority); @@ -281,11 +288,11 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { async saveAndEnterWorkspace(path: URI): Promise<void> { if (!await this.isValidTargetWorkspacePath(path)) { - return Promise.reject(null); + return; } const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); if (!workspaceIdentifier) { - return Promise.reject(null); + return; } await this.saveWorkspaceAs(workspaceIdentifier, path); @@ -318,7 +325,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { // Return early if target is same as source if (isEqual(configPathURI, targetConfigPathURI)) { - return Promise.resolve(null); + return; } // Read the contents of the workspace file, update it to new location and save it. @@ -327,18 +334,17 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { await this.textFileService.create(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true }); } - private handleWorkspaceConfigurationEditingError(error: JSONEditingError): Promise<void> { + private handleWorkspaceConfigurationEditingError(error: JSONEditingError): void { switch (error.code) { case JSONEditingErrorCode.ERROR_INVALID_FILE: this.onInvalidWorkspaceConfigurationFileError(); - return Promise.resolve(); + break; case JSONEditingErrorCode.ERROR_FILE_DIRTY: this.onWorkspaceConfigurationFileDirtyError(); - return Promise.resolve(); + break; + default: + this.notificationService.error(error.message); } - this.notificationService.error(error.message); - - return Promise.resolve(); } private onInvalidWorkspaceConfigurationFileError(): void { @@ -362,7 +368,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { async enterWorkspace(path: URI): Promise<void> { if (!!this.environmentService.extensionTestsLocationURI) { - return Promise.reject(new Error('Entering a new workspace is not possible in tests.')); + throw new Error('Entering a new workspace is not possible in tests.'); } const workspace = await this.workspaceService.getWorkspaceIdentifier(path); diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index a31180c8d3b..d73ba1096ac 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -1041,7 +1041,9 @@ suite('ExtHostLanguageFeatures', function () { disposables.push(extHost.registerDocumentLinkProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentLinkProvider { provideDocumentLinks() { - return [new types.DocumentLink(new types.Range(0, 0, 1, 1), URI.parse('foo:bar#3'))]; + const link = new types.DocumentLink(new types.Range(0, 0, 1, 1), URI.parse('foo:bar#3')); + link.tooltip = 'tooltip'; + return [link]; } })); @@ -1051,6 +1053,7 @@ suite('ExtHostLanguageFeatures', function () { let [first] = links; assert.equal(first.url, 'foo:bar#3'); assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 2, endColumn: 2 }); + assert.equal(first.tooltip, 'tooltip'); }); test('Links, evil provider', async () => { diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index f7540b8a6b1..3b71d71b1fd 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -102,7 +102,7 @@ import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService import 'vs/workbench/services/extensions/common/inactiveExtensionUrlHandler'; import 'vs/workbench/services/decorations/browser/decorationsService'; import 'vs/workbench/services/search/node/searchService'; -import 'vs/workbench/services/progress/browser/progressService2'; +import 'vs/workbench/services/progress/browser/progressService'; import 'vs/workbench/services/editor/browser/codeEditorService'; import 'vs/workbench/services/broadcast/electron-browser/broadcastService'; import 'vs/workbench/services/extensions/electron-browser/extensionHostDebugService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 3d23a2ecde7..4dc252c0c3f 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -106,7 +106,7 @@ import 'vs/workbench/services/keybinding/common/keybindingEditing'; // import 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; import 'vs/workbench/services/decorations/browser/decorationsService'; // import 'vs/workbench/services/search/node/searchService'; -import 'vs/workbench/services/progress/browser/progressService2'; +import 'vs/workbench/services/progress/browser/progressService'; import 'vs/workbench/services/editor/browser/codeEditorService'; // import 'vs/workbench/services/broadcast/electron-browser/broadcastService'; import 'vs/workbench/services/preferences/browser/preferencesService'; @@ -128,7 +128,7 @@ import 'vs/workbench/services/mode/common/workbenchModeService'; import 'vs/workbench/services/commands/common/commandService'; import 'vs/workbench/services/themes/browser/workbenchThemeService'; // import 'vs/workbench/services/extensionManagement/node/extensionEnablementService'; -// import 'vs/workbench/services/extensions/electron-browser/extensionService'; +import 'vs/workbench/services/extensions/browser/extensionService'; // import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; // import 'vs/workbench/services/extensionManagement/node/multiExtensionManagement'; import 'vs/workbench/services/label/common/labelService';