diff --git a/extensions/git/package.json b/extensions/git/package.json index ffa34ca6ef..66b8731c6d 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1354,6 +1354,15 @@ "scope": "resource", "default": false, "description": "%config.supportCancellation%" + }, + "git.branchSortOrder": { + "type": "string", + "enum": [ + "committerdate", + "alphabetically" + ], + "default": "committerdate", + "description": "%config.branchSortOrder%" } } }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 367544c6cc..7d90b4b15f 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -127,6 +127,7 @@ "config.confirmForcePush": "Controls whether to ask for confirmation before force-pushing.", "config.openDiffOnClick": "Controls whether the diff editor should be opened when clicking a change. Otherwise the regular editor will be opened.", "config.supportCancellation": "Controls whether a notification comes up when running the Sync action, which allows the user to cancel the operation.", + "config.branchSortOrder": "Controls the sort order for branches.", "colors.added": "Color for added resources.", "colors.modified": "Color for modified resources.", "colors.deleted": "Color for deleted resources.", diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index fc3141d689..01877f4362 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1624,8 +1624,14 @@ export class Repository { .map(([ref]) => ({ name: ref, type: RefType.Head } as Branch)); } - async getRefs(): Promise { - const result = await this.run(['for-each-ref', '--format', '%(refname) %(objectname)', '--sort', '-committerdate']); + async getRefs(opts?: { sort?: 'alphabetically' | 'committerdate' }): Promise { + const args = ['for-each-ref', '--format', '%(refname) %(objectname)']; + + if (opts && opts.sort && opts.sort !== 'alphabetically') { + args.push('--sort', opts.sort); + } + + const result = await this.run(args); const fn = (line: string): Ref | null => { let match: RegExpExecArray | null; diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 517258f74f..0660c83f45 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -701,6 +701,9 @@ export class Repository implements Disposable { onConfigListener(updateIndexGroupVisibility, this, this.disposables); updateIndexGroupVisibility(); + const onConfigListenerForBranchSortOrder = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchSortOrder', root)); + onConfigListenerForBranchSortOrder(this.updateModelState, this, this.disposables); + this.mergeGroup.hideWhenEmpty = true; this.disposables.push(this.mergeGroup); @@ -1405,7 +1408,6 @@ export class Repository implements Disposable { const config = workspace.getConfiguration('git'); const shouldIgnore = config.get('ignoreLimitWarning') === true; const useIcons = !config.get('decorations.enabled', true); - this.isRepositoryHuge = didHitLimit; if (didHitLimit && !shouldIgnore && !this.didWarnAboutLimit) { @@ -1455,7 +1457,8 @@ export class Repository implements Disposable { // noop } - const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]); + const sort = config.get<'alphabetically' | 'committerdate'>('branchSortOrder') || 'alphabetically'; + const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs({ sort }), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]); this._HEAD = HEAD; this._refs = refs; diff --git a/package.json b/package.json index 1713ab7423..94213a79a4 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "vscode-ripgrep": "^1.5.6", "vscode-sqlite3": "4.0.8", "vscode-textmate": "^4.2.2", - "xterm": "3.15.0-beta98", + "xterm": "3.15.0-beta99", "xterm-addon-search": "0.2.0-beta3", "xterm-addon-web-links": "0.1.0-beta10", "yauzl": "^2.9.2", diff --git a/remote/package.json b/remote/package.json index d3ad3afaed..ae754c1ae6 100644 --- a/remote/package.json +++ b/remote/package.json @@ -21,7 +21,7 @@ "vscode-proxy-agent": "0.4.0", "vscode-ripgrep": "^1.5.6", "vscode-textmate": "^4.2.2", - "xterm": "3.15.0-beta98", + "xterm": "3.15.0-beta99", "xterm-addon-search": "0.2.0-beta3", "xterm-addon-web-links": "0.1.0-beta10", "yauzl": "^2.9.2", diff --git a/remote/web/.yarnrc b/remote/web/.yarnrc deleted file mode 100644 index b28191e6ba..0000000000 --- a/remote/web/.yarnrc +++ /dev/null @@ -1,3 +0,0 @@ -disturl "http://nodejs.org/dist" -target "10.11.0" -runtime "node" diff --git a/remote/yarn.lock b/remote/yarn.lock index b271a9fd5c..5d578d3130 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -1227,10 +1227,10 @@ xterm-addon-web-links@0.1.0-beta10: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta10.tgz#610fa9773a2a5ccd41c1c83ba0e2dd2c9eb66a23" integrity sha512-xfpjy0V6bB4BR44qIgZQPoCMVakxb65gMscPkHpO//QxvUxKzabV3dxOsIbeZRFkUGsWTFlvz2OoaBLoNtv5gg== -xterm@3.15.0-beta98: - version "3.15.0-beta98" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta98.tgz#37f37c35577422880e7ef673cc37f9d2a45dd40c" - integrity sha512-vZbg2LcRvoiJOgr1MyeLFM9mF4uib3BWUWDHyFc+vZ58CTuK0iczOvFXgk/ySo23ZLqwmHQSigLgmWvZ8J5G0Q== +xterm@3.15.0-beta99: + version "3.15.0-beta99" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta99.tgz#0010a7ea5d56cbb08a1e3a525b353c96a158e7a0" + integrity sha512-Vm0ZWToWwO4uk/28Kqvqt9L92h5EU2z4WR9I6xcQaPIBmkJPINIARU4LWQnvaOfgFhRbpwBMveTfh8/jM97lPg== yauzl@^2.9.2: version "2.10.0" diff --git a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts index 77f96832f2..0bbf2ed514 100644 --- a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts @@ -22,7 +22,6 @@ import { ExtHostModelView } from 'sql/workbench/api/common/extHostModelView'; import { ExtHostConnectionManagement } from 'sql/workbench/api/common/extHostConnectionManagement'; import { ExtHostDashboard } from 'sql/workbench/api/common/extHostDashboard'; import { ExtHostObjectExplorer } from 'sql/workbench/api/common/extHostObjectExplorer'; -import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; import { ExtHostModelViewDialog } from 'sql/workbench/api/common/extHostModelViewDialog'; import { ExtHostModelViewTreeViews } from 'sql/workbench/api/common/extHostModelViewTree'; import { ExtHostQueryEditor } from 'sql/workbench/api/common/extHostQueryEditor'; @@ -56,7 +55,7 @@ export function createApiFactory(accessor: ServicesAccessor): ISqlExtensionApiFa const instaServer = accessor.get(IInstantiationService); const uriTransformer = accessor.get(IURITransformerService); const rpcProtocol = accessor.get(IExtHostRpcService); - const extHostLogService = accessor.get(ILogService); + const extHostLogService = accessor.get(ILogService); let vsCodeFactory = instaServer.invokeFunction(extHostApi.createApiFactoryAndRegisterActors); // Addressable instances diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 3298350372..aa0af48b34 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -14,7 +14,7 @@ }, "include": [ "typings/require.d.ts", - "./typings/require-monaco.d.ts", + "typings/require-monaco.d.ts", "typings/thenable.d.ts", "typings/es6-promise.d.ts", "typings/lib.es2018.promise.d.ts", diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 75ef1241fc..26d7b77aa6 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -7,7 +7,7 @@ import 'vs/css!./gridview'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { Disposable } from 'vs/base/common/lifecycle'; import { tail2 as tail, equals } from 'vs/base/common/arrays'; -import { orthogonal, IView as IGridViewView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize, ILayoutController, LayoutController } from './gridview'; +import { orthogonal, IView as IGridViewView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize, LayoutController, IGridViewOptions } from './gridview'; import { Event } from 'vs/base/common/event'; import { InvisibleSizing } from 'vs/base/browser/ui/splitview/splitview'; @@ -193,11 +193,8 @@ export namespace Sizing { export interface IGridStyles extends IGridViewStyles { } -export interface IGridOptions { - readonly styles?: IGridStyles; - readonly proportionalLayout?: boolean; +export interface IGridOptions extends IGridViewOptions { readonly firstViewVisibleCachedSize?: number; - readonly layoutController?: ILayoutController; } export class Grid extends Disposable { diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 930347184b..bcdc9f3c53 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -76,6 +76,11 @@ export class LayoutController implements ILayoutController { constructor(public isLayoutEnabled: boolean) { } } +export class MultiplexLayoutController implements ILayoutController { + get isLayoutEnabled(): boolean { return this.layoutControllers.every(l => l.isLayoutEnabled); } + constructor(private layoutControllers: ILayoutController[]) { } +} + export interface IGridViewOptions { readonly styles?: IGridViewStyles; readonly proportionalLayout?: boolean; // default true @@ -170,6 +175,7 @@ class BranchNode implements ISplitView, IDisposable { constructor( readonly orientation: Orientation, + readonly layoutController: ILayoutController, styles: IGridViewStyles, readonly proportionalLayout: boolean, size: number = 0, @@ -181,7 +187,7 @@ class BranchNode implements ISplitView, IDisposable { this.element = $('.monaco-grid-branch-node'); this.splitview = new SplitView(this.element, { orientation, styles, proportionalLayout }); - this.splitview.layout(size); + this.splitview.layout(size, orthogonalSize); const onDidSashReset = Event.map(this.splitview.onDidSashReset, i => [i]); this.splitviewSashResetDisposable = onDidSashReset(this._onDidSashReset.fire, this._onDidSashReset); @@ -198,12 +204,20 @@ class BranchNode implements ISplitView, IDisposable { } } - layout(size: number): void { + layout(size: number, orthogonalSize: number | undefined): void { + if (!this.layoutController.isLayoutEnabled) { + return; + } + + if (typeof orthogonalSize !== 'number') { + throw new Error('Invalid state'); + } + + // branch nodes should flip the normal/orthogonal directions + this._size = orthogonalSize; this._orthogonalSize = size; - for (const child of this.children) { - child.orthogonalLayout(size); - } + this.splitview.layout(orthogonalSize, size); } setVisible(visible: boolean): void { @@ -212,11 +226,6 @@ class BranchNode implements ISplitView, IDisposable { } } - orthogonalLayout(size: number): void { - this._size = size; - this.splitview.layout(size); - } - addChild(node: Node, size: number | Sizing, index: number): void { if (index < 0 || index > this.children.length) { throw new Error('Invalid index'); @@ -347,6 +356,10 @@ class BranchNode implements ISplitView, IDisposable { throw new Error('Invalid index'); } + if (this.splitview.isViewVisible(index) === visible) { + return; + } + this.splitview.setViewVisible(index, visible); this._onDidChange.fire(undefined); } @@ -539,12 +552,18 @@ class LeafNode implements ISplitView, IDisposable { // noop } - layout(size: number): void { - this._size = size; - - if (this.layoutController.isLayoutEnabled) { - this.view.layout(this.width, this.height, orthogonal(this.orientation)); + layout(size: number, orthogonalSize: number | undefined): void { + if (!this.layoutController.isLayoutEnabled) { + return; } + + if (typeof orthogonalSize !== 'number') { + throw new Error('Invalid state'); + } + + this._size = size; + this._orthogonalSize = orthogonalSize; + this.view.layout(this.width, this.height, orthogonal(this.orientation)); } setVisible(visible: boolean): void { @@ -553,14 +572,6 @@ class LeafNode implements ISplitView, IDisposable { } } - orthogonalLayout(size: number): void { - this._orthogonalSize = size; - - if (this.layoutController.isLayoutEnabled) { - this.view.layout(this.width, this.height, orthogonal(this.orientation)); - } - } - dispose(): void { } } @@ -568,7 +579,7 @@ type Node = BranchNode | LeafNode; function flipNode(node: T, size: number, orthogonalSize: number): T { if (node instanceof BranchNode) { - const result = new BranchNode(orthogonal(node.orientation), node.styles, node.proportionalLayout, size, orthogonalSize); + const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.proportionalLayout, size, orthogonalSize); let totalSize = 0; @@ -589,7 +600,7 @@ function flipNode(node: T, size: number, orthogonalSize: number) return result as T; } else { - return new LeafNode((node as LeafNode).view, orthogonal(node.orientation), (node as LeafNode).layoutController, orthogonalSize) as T; + return new LeafNode((node as LeafNode).view, orthogonal(node.orientation), node.layoutController, orthogonalSize) as T; } } @@ -634,8 +645,7 @@ export class GridView implements IDisposable { const { size, orthogonalSize } = this._root; this.root = flipNode(this._root, orthogonalSize, size); - this.root.layout(size); - this.root.orthogonalLayout(orthogonalSize); + this.root.layout(size, orthogonalSize); } get width(): number { return this.root.width; } @@ -649,14 +659,25 @@ export class GridView implements IDisposable { private _onDidChange = new Relay(); readonly onDidChange = this._onDidChange.event; + /** + * The first layout controller makes sure layout only propagates + * to the views after the very first call to gridview.layout() + */ + private firstLayoutController: LayoutController; private layoutController: LayoutController; constructor(options: IGridViewOptions = {}) { this.element = $('.monaco-grid-view'); this.styles = options.styles || defaultStyles; this.proportionalLayout = typeof options.proportionalLayout !== 'undefined' ? !!options.proportionalLayout : true; - this.root = new BranchNode(Orientation.VERTICAL, this.styles, this.proportionalLayout); - this.layoutController = options.layoutController || new LayoutController(true); + + this.firstLayoutController = new LayoutController(false); + this.layoutController = new MultiplexLayoutController([ + this.firstLayoutController, + ...(options.layoutController ? [options.layoutController] : []) + ]); + + this.root = new BranchNode(Orientation.VERTICAL, this.layoutController, this.styles, this.proportionalLayout); } style(styles: IGridViewStyles): void { @@ -665,9 +686,10 @@ export class GridView implements IDisposable { } layout(width: number, height: number): void { + this.firstLayoutController.isLayoutEnabled = true; + const [size, orthogonalSize] = this.root.orientation === Orientation.HORIZONTAL ? [height, width] : [width, height]; - this.root.layout(size); - this.root.orthogonalLayout(orthogonalSize); + this.root.layout(size, orthogonalSize); } addView(view: IView, size: number | Sizing, location: number[]): void { @@ -694,9 +716,8 @@ export class GridView implements IDisposable { grandParent.removeChild(parentIndex); - const newParent = new BranchNode(parent.orientation, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize); + const newParent = new BranchNode(parent.orientation, parent.layoutController, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize); grandParent.addChild(newParent, parent.size, parentIndex); - newParent.orthogonalLayout(parent.orthogonalSize); const newSibling = new LeafNode(parent.view, grandParent.orientation, this.layoutController, parent.size); newParent.addChild(newSibling, newSiblingSize, 0); @@ -827,9 +848,6 @@ export class GridView implements IDisposable { fromParent.addChild(toNode, fromSize, fromIndex); toParent.addChild(fromNode, toSize, toIndex); - - fromParent.layout(fromParent.orthogonalSize); - toParent.layout(toParent.orthogonalSize); } } diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index b541d32941..8c3cf3cdc3 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -24,12 +24,12 @@ const defaultStyles: ISplitViewStyles = { }; export interface ISplitViewOptions { - orientation?: Orientation; // default Orientation.VERTICAL - styles?: ISplitViewStyles; - orthogonalStartSash?: Sash; - orthogonalEndSash?: Sash; - inverseAltBehavior?: boolean; - proportionalLayout?: boolean; // default true + readonly orientation?: Orientation; // default Orientation.VERTICAL + readonly styles?: ISplitViewStyles; + readonly orthogonalStartSash?: Sash; + readonly orthogonalEndSash?: Sash; + readonly inverseAltBehavior?: boolean; + readonly proportionalLayout?: boolean; // default true } /** @@ -48,7 +48,7 @@ export interface IView { readonly onDidChange: Event; readonly priority?: LayoutPriority; readonly snap?: boolean; - layout(size: number, orientation: Orientation): void; + layout(size: number, orthogonalSize: number | undefined): void; setVisible?(visible: boolean): void; } @@ -125,13 +125,13 @@ abstract class ViewItem { dom.addClass(container, 'visible'); } - layout(): void { + layout(_orthogonalSize: number | undefined): void { this.container.scrollTop = 0; this.container.scrollLeft = 0; } - layoutView(orientation: Orientation): void { - this.view.layout(this.size, orientation); + layoutView(orthogonalSize: number | undefined): void { + this.view.layout(this.size, orthogonalSize); } dispose(): IView { @@ -142,19 +142,19 @@ abstract class ViewItem { class VerticalViewItem extends ViewItem { - layout(): void { - super.layout(); + layout(orthogonalSize: number | undefined): void { + super.layout(orthogonalSize); this.container.style.height = `${this.size}px`; - this.layoutView(Orientation.VERTICAL); + this.layoutView(orthogonalSize); } } class HorizontalViewItem extends ViewItem { - layout(): void { - super.layout(); + layout(orthogonalSize: number | undefined): void { + super.layout(orthogonalSize); this.container.style.width = `${this.size}px`; - this.layoutView(Orientation.HORIZONTAL); + this.layoutView(orthogonalSize); } } @@ -205,6 +205,7 @@ export class SplitView extends Disposable { private sashContainer: HTMLElement; private viewContainer: HTMLElement; private size = 0; + private orthogonalSize: number | undefined; private contentSize = 0; private proportions: undefined | number[] = undefined; private viewItems: ViewItem[] = []; @@ -475,9 +476,10 @@ export class SplitView extends Disposable { return viewItem.cachedVisibleSize; } - layout(size: number): void { + layout(size: number, orthogonalSize?: number): void { const previousSize = Math.max(this.size, this.contentSize); this.size = size; + this.orthogonalSize = orthogonalSize; if (!this.proportions) { const indexes = range(this.viewItems.length); @@ -820,7 +822,7 @@ export class SplitView extends Disposable { this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); // Layout views - this.viewItems.forEach(item => item.layout()); + this.viewItems.forEach(item => item.layout(this.orthogonalSize)); // Layout sashes this.sashItems.forEach(item => item.sash.layout()); diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 6ffeef4ab6..1debaa2fbc 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -16,7 +16,7 @@ export interface IWorker extends IDisposable { } export interface IWorkerCallback { - (message: string): void; + (message: any): void; } export interface IWorkerFactory { diff --git a/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/src/vs/base/test/browser/ui/splitview/splitview.test.ts index a6cfa772dd..f657a0bb58 100644 --- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Emitter } from 'vs/base/common/event'; -import { SplitView, IView, Orientation, Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview'; +import { SplitView, IView, Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview'; import { Sash, SashState } from 'vs/base/browser/ui/sash/sash'; class TestView implements IView { @@ -27,7 +27,9 @@ class TestView implements IView { private _size = 0; get size(): number { return this._size; } - private _onDidLayout = new Emitter<{ size: number; orientation: Orientation }>(); + private _orthogonalSize: number | undefined = 0; + get orthogonalSize(): number | undefined { return this._orthogonalSize; } + private _onDidLayout = new Emitter<{ size: number; orthogonalSize: number | undefined }>(); readonly onDidLayout = this._onDidLayout.event; private _onDidFocus = new Emitter(); @@ -41,9 +43,10 @@ class TestView implements IView { assert(_minimumSize <= _maximumSize, 'splitview view minimum size must be <= maximum size'); } - layout(size: number, orientation: Orientation): void { + layout(size: number, orthogonalSize: number | undefined): void { this._size = size; - this._onDidLayout.fire({ size, orientation }); + this._orthogonalSize = orthogonalSize; + this._onDidLayout.fire({ size, orthogonalSize }); } focus(): void { @@ -523,4 +526,24 @@ suite('Splitview', () => { view2.dispose(); view1.dispose(); }); -}); \ No newline at end of file + + test('orthogonal size propagates to views', () => { + const view1 = new TestView(20, Number.POSITIVE_INFINITY); + const view2 = new TestView(20, Number.POSITIVE_INFINITY); + const view3 = new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.Low); + const splitview = new SplitView(container, { proportionalLayout: false }); + splitview.layout(200); + + splitview.addView(view1, Sizing.Distribute); + splitview.addView(view2, Sizing.Distribute); + splitview.addView(view3, Sizing.Distribute); + + splitview.layout(200, 100); + assert.deepEqual([view1.orthogonalSize, view2.orthogonalSize, view3.orthogonalSize], [100, 100, 100]); + + splitview.dispose(); + view3.dispose(); + view2.dispose(); + view1.dispose(); + }); +}); diff --git a/src/vs/base/worker/workerMain.ts b/src/vs/base/worker/workerMain.ts index 1020d68b65..b0769949ba 100644 --- a/src/vs/base/worker/workerMain.ts +++ b/src/vs/base/worker/workerMain.ts @@ -20,8 +20,8 @@ let loadCode = function (moduleId: string) { require([moduleId], function (ws) { setTimeout(function () { - let messageHandler = ws.create((msg: any) => { - (self).postMessage(msg); + let messageHandler = ws.create((msg: any, transfer?: Transferable[]) => { + (self).postMessage(msg, transfer); }, null); self.onmessage = (e) => messageHandler.onmessage(e.data); diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 790834d2a3..8c9da62f99 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -27,7 +27,7 @@ bootstrapWindow.load([ perf.mark('main/startup'); // @ts-ignore - return require('vs/workbench/electron-browser/main').main(configuration); + return require('vs/workbench/electron-browser/desktop.main').main(configuration); }); }, { removeDeveloperKeybindingsAfterLoad: true, diff --git a/src/vs/platform/markers/common/markers.ts b/src/vs/platform/markers/common/markers.ts index 60ef4306c7..d095685d72 100644 --- a/src/vs/platform/markers/common/markers.ts +++ b/src/vs/platform/markers/common/markers.ts @@ -129,6 +129,10 @@ export interface MarkerStatistics { export namespace IMarkerData { const emptyString = ''; export function makeKey(markerData: IMarkerData): string { + return makeKeyOptionalMessage(markerData, true); + } + + export function makeKeyOptionalMessage(markerData: IMarkerData, useMessage: boolean): string { let result: string[] = [emptyString]; if (markerData.source) { result.push(markerData.source.replace('¦', '\¦')); @@ -145,7 +149,10 @@ export namespace IMarkerData { } else { result.push(emptyString); } - if (markerData.message) { + + // Modifed to not include the message as part of the marker key to work around + // https://github.com/microsoft/vscode/issues/77475 + if (markerData.message && useMessage) { result.push(markerData.message.replace('¦', '\¦')); } else { result.push(emptyString); diff --git a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts index 7c7ba03bd3..2683d577a4 100644 --- a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts @@ -12,6 +12,7 @@ export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; import * as Platform from 'vs/base/common/platform'; import * as uuid from 'vs/base/common/uuid'; +import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; export async function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string | undefined, version: string | undefined, machineId: string, remoteAuthority?: string): Promise<{ [name: string]: string | undefined }> { const result: { [name: string]: string | undefined; } = Object.create(null); @@ -69,18 +70,3 @@ export async function resolveWorkbenchCommonProperties(storageService: IStorageS return result; } -function cleanRemoteAuthority(remoteAuthority?: string): string { - if (!remoteAuthority) { - return 'none'; - } - - let ret = 'other'; - // Whitelisted remote authorities - ['ssh-remote', 'dev-container', 'wsl'].forEach((res: string) => { - if (remoteAuthority!.indexOf(`${res}+`) === 0) { - ret = res; - } - }); - - return ret; -} diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index a948ff65ee..42843b6ab8 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -288,6 +288,22 @@ export function validateTelemetryData(data?: any): { properties: Properties, mea }; } +export function cleanRemoteAuthority(remoteAuthority?: string): string { + if (!remoteAuthority) { + return 'none'; + } + + let ret = 'other'; + // Whitelisted remote authorities + ['ssh-remote', 'dev-container', 'attached-container', 'wsl'].forEach((res: string) => { + if (remoteAuthority!.indexOf(`${res}+`) === 0) { + ret = res; + } + }); + + return ret; +} + function flatten(obj: any, result: { [key: string]: any }, order: number = 0, prefix?: string): void { if (!obj) { return; diff --git a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts index 1797217f20..f8e2f0877d 100644 --- a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts @@ -6,6 +6,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; +import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; // {{ SQL CARBON EDIT }} import product from 'vs/platform/product/node/product'; @@ -57,19 +58,3 @@ function setUsageDates(storageService: IStorageService): void { const monthlyLastUseDate = storageService.get('telemetry.monthlyLastUseDate', StorageScope.GLOBAL, appStartDate.toUTCString()); storageService.store('telemetry.monthlyLastUseDate', monthlyLastUseDate, StorageScope.GLOBAL); } - -function cleanRemoteAuthority(remoteAuthority?: string): string { - if (!remoteAuthority) { - return 'none'; - } - - let ret = 'other'; - // Whitelisted remote authorities - ['ssh-remote', 'dev-container', 'wsl'].forEach((res: string) => { - if (remoteAuthority!.indexOf(`${res}+`) === 0) { - ret = res; - } - }); - - return ret; -} diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 126fed8622..ca8ecc9bf2 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5208,7 +5208,7 @@ declare module 'vscode' { */ export enum TaskScope { /** - * The task is a global task + * The task is a global task. Global tasks are currrently not supported. */ Global = 1, @@ -5237,7 +5237,7 @@ declare module 'vscode' { * Creates a new task. * * @param definition The task definition as defined in the taskDefinitions extension point. - * @param scope Specifies the task's scope. It is either a global or a workspace task or a task for a specific workspace folder. + * @param scope Specifies the task's scope. It is either a global or a workspace task or a task for a specific workspace folder. Global tasks are currently not supported. * @param name The task's name. Is presented in the user interface. * @param source The task's source (e.g. 'gulp', 'npm', ...). Is presented in the user interface. * @param execution The process or shell execution. diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 71cff6c3bf..0337069629 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -124,7 +124,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie this._dataProviders.forEach((dataProvider, treeViewId) => { const treeView = this.getTreeView(treeViewId); if (treeView) { - treeView.dataProvider = null; + treeView.dataProvider = undefined; } }); this._dataProviders.clear(); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index bbf7378881..588563d01f 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -15,7 +15,7 @@ import { OverviewRulerLane } from 'vs/editor/common/model'; import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration'; import { score } from 'vs/editor/common/modes/languageSelector'; import * as files from 'vs/platform/files/common/files'; -import { ExtHostContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, MainContext, ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -33,7 +33,6 @@ import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem'; import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService'; 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'; import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; import { ExtHostProgress } from 'vs/workbench/api/common/extHostProgress'; @@ -95,10 +94,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const uriTransformer = accessor.get(IURITransformerService); const rpcProtocol = accessor.get(IExtHostRpcService); const extHostStorage = accessor.get(IExtHostStorage); - const extHostLogService = accessor.get(ILogService); + const extHostLogService = accessor.get(ILogService); // register addressable instances - rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService); + rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService); rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); @@ -147,10 +146,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostStatusBar = new ExtHostStatusBar(rpcProtocol); const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments); - // Register an output channel for exthost log - const outputChannelName = initData.remote.isRemote ? 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 ExtHostApiCommands.register(extHostCommands); diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 12dfb21ea4..3ffbd8b2f6 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -14,7 +14,6 @@ import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostExtensionServiceShape, IInitData, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape, IResolveAuthorityResult } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator'; -import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; import { ExtHostStorage, IExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; @@ -75,7 +74,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio protected readonly _instaService: IInstantiationService; protected readonly _extHostWorkspace: ExtHostWorkspace; protected readonly _extHostConfiguration: ExtHostConfiguration; - protected readonly _extHostLogService: ExtHostLogService; + protected readonly _logService: ILogService; protected readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape; protected readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; @@ -102,7 +101,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio @IExtHostRpcService extHostContext: IExtHostRpcService, @IExtHostWorkspace extHostWorkspace: IExtHostWorkspace, @IExtHostConfiguration extHostConfiguration: IExtHostConfiguration, - @ILogService extHostLogService: ExtHostLogService, + @ILogService logService: ILogService, @IExtHostInitDataService initData: IExtHostInitDataService, @IExtensionStoragePaths storagePath: IExtensionStoragePaths ) { @@ -112,7 +111,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio this._extHostWorkspace = extHostWorkspace; this._extHostConfiguration = extHostConfiguration; - this._extHostLogService = extHostLogService; + this._logService = logService; this._disposables = new DisposableStore(); this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace); @@ -329,14 +328,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE)); } - this._extHostLogService.info(`ExtensionService#_doActivateExtension ${extensionDescription.identifier.value} ${JSON.stringify(reason)}`); + this._logService.info(`ExtensionService#_doActivateExtension ${extensionDescription.identifier.value} ${JSON.stringify(reason)}`); const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup); return Promise.all([ this._loadCommonJSModule(extensionDescription.main, activationTimesBuilder), this._loadExtensionContext(extensionDescription) ]).then(values => { - return AbstractExtHostExtensionService._callActivate(this._extHostLogService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); + return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); }); } @@ -347,7 +346,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio const globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage); const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage); - this._extHostLogService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`); + this._logService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`); return Promise.all([ globalState.whenReady, workspaceState.whenReady, @@ -359,10 +358,10 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio workspaceState, subscriptions: [], get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, - storagePath: this._storagePath.workspaceValue(extensionDescription), - globalStoragePath: this._storagePath.globalValue(extensionDescription), + get storagePath() { return that._storagePath.workspaceValue(extensionDescription); }, + get globalStoragePath() { return that._storagePath.globalValue(extensionDescription); }, asAbsolutePath: (relativePath: string) => { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, - logPath: that._extHostLogService.getLogDirectory(extensionDescription.identifier), + get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); }, executionContext: this._initData.remote.isRemote ? ExtensionExecutionContext.Remote : ExtensionExecutionContext.Local, }); }); @@ -385,7 +384,8 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio try { activationTimesBuilder.activateCallStart(); logService.trace(`ExtensionService#_callActivateOptional ${extensionId.value}`); - const activateResult: Promise = extensionModule.activate.apply(global, [context]); + const scope = typeof global === 'object' ? global : self; // `global` is nodejs while `self` is for workers + const activateResult: Promise = extensionModule.activate.apply(scope, [context]); activationTimesBuilder.activateCallStop(); activationTimesBuilder.activateResolveStart(); @@ -478,7 +478,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio } private async _activateIfGlobPatterns(folders: ReadonlyArray, extensionId: ExtensionIdentifier, globPatterns: string[]): Promise { - this._extHostLogService.trace(`extensionHostMain#activateIfGlobPatterns: fileSearch, extension: ${extensionId.value}, entryPoint: workspaceContains`); + this._logService.trace(`extensionHostMain#activateIfGlobPatterns: fileSearch, extension: ${extensionId.value}, entryPoint: workspaceContains`); if (globPatterns.length === 0) { return Promise.resolve(undefined); @@ -525,7 +525,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio } private async _doHandleExtensionTests(): Promise { - const { extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment; + const { extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment; if (!(extensionDevelopmentLocationURI && extensionTestsLocationURI && extensionTestsLocationURI.scheme === Schemas.file)) { return Promise.resolve(undefined); } @@ -605,7 +605,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio .then(() => this._handleEagerExtensions()) .then(() => this._handleExtensionTests()) .then(() => { - this._extHostLogService.info(`eager extensions activated`); + this._logService.info(`eager extensions activated`); }); } diff --git a/src/vs/workbench/api/common/extHostLogService.ts b/src/vs/workbench/api/common/extHostLogService.ts deleted file mode 100644 index 64f865a3f8..0000000000 --- a/src/vs/workbench/api/common/extHostLogService.ts +++ /dev/null @@ -1,34 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { join } from 'vs/base/common/path'; -import { ILogService, DelegatedLogService, LogLevel } from 'vs/platform/log/common/log'; -import { ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; -import { URI } from 'vs/base/common/uri'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; - -export class ExtHostLogService extends DelegatedLogService implements ILogService, ExtHostLogServiceShape { - - private _logsPath: string; - readonly logFile: URI; - - constructor( - delegate: ILogService, - logsPath: string, - ) { - super(delegate); - this._logsPath = logsPath; - this.logFile = URI.file(join(logsPath, `${ExtensionHostLogFileName}.log`)); - } - - $setLevel(level: LogLevel): void { - this.setLevel(level); - } - - getLogDirectory(extensionID: ExtensionIdentifier): string { - return join(this._logsPath, extensionID.value); - } -} diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts index 9071ae1d91..571d770311 100644 --- a/src/vs/workbench/api/node/extHost.services.ts +++ b/src/vs/workbench/api/node/extHost.services.ts @@ -24,8 +24,11 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; // register singleton services +registerSingleton(ILogService, ExtHostLogService); registerSingleton(IExtHostOutputService, ExtHostOutputService2); registerSingleton(IExtHostWorkspace, ExtHostWorkspace); registerSingleton(IExtHostDecorations, ExtHostDecorations); diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 651d958532..eaaa3ba225 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -44,14 +44,24 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { } // Do this when extension service exists, but extensions are not being activated yet. - await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._extHostLogService, this._mainThreadTelemetryProxy); + await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy); + // Use IPC messages to forward console-calls, note that the console is + // already patched to use`process.send()` + const nativeProcessSend = process.send!; + const mainThreadConsole = this._extHostContext.getProxy(MainContext.MainThreadConsole); + process.send = (...args: any[]) => { + if (args.length === 0 || !args[0] || args[0].type !== '__$console') { + return nativeProcessSend.apply(process, args); + } + mainThreadConsole.$logExtensionHostMessage(args[0]); + }; } protected _loadCommonJSModule(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { let r: T | null = null; activationTimesBuilder.codeLoadingStart(); - this._extHostLogService.info(`ExtensionService#loadCommonJSModule ${modulePath}`); + this._logService.info(`ExtensionService#loadCommonJSModule ${modulePath}`); try { r = require.__$__nodeRequire(modulePath); } catch (e) { diff --git a/src/vs/workbench/api/node/extHostLogService.ts b/src/vs/workbench/api/node/extHostLogService.ts new file mode 100644 index 0000000000..e71d0284ac --- /dev/null +++ b/src/vs/workbench/api/node/extHostLogService.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { join } from 'vs/base/common/path'; +import { ILogService, DelegatedLogService, LogLevel } from 'vs/platform/log/common/log'; +import { ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; +import { URI } from 'vs/base/common/uri'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { Schemas } from 'vs/base/common/network'; +import { SpdLogService } from 'vs/platform/log/node/spdlogService'; +import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; + +export class ExtHostLogService extends DelegatedLogService implements ILogService, ExtHostLogServiceShape { + + constructor( + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtHostOutputService extHostOutputService: IExtHostOutputService + ) { + if (initData.logsLocation.scheme !== Schemas.file) { throw new Error('Only file-logging supported'); } + super(new SpdLogService(ExtensionHostLogFileName, initData.logsLocation.fsPath, initData.logLevel)); + + // Register an output channel for exthost log + extHostOutputService.createOutputChannelFromLogFile( + initData.remote.isRemote ? localize('remote extension host Log', "Remote Extension Host") : localize('extension host Log', "Extension Host"), + URI.file(join(initData.logsLocation.fsPath, `${ExtensionHostLogFileName}.log`)) + ); + } + + $setLevel(level: LogLevel): void { + this.setLevel(level); + } +} diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts new file mode 100644 index 0000000000..f2667aeea4 --- /dev/null +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createApiFactoryAndRegisterActors, IExtensionApiFactory } from 'vs/workbench/api/common/extHost.api.impl'; +import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHostExtensionActivator'; +import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { endsWith } from 'vs/base/common/strings'; +import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; +import * as vscode from 'vscode'; +import { TernarySearchTree } from 'vs/base/common/map'; +import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; + +class ApiInstances { + + private readonly _apiInstances = new Map(); + + constructor( + private readonly _apiFactory: IExtensionApiFactory, + private readonly _extensionPaths: TernarySearchTree, + private readonly _extensionRegistry: ExtensionDescriptionRegistry, + private readonly _configProvider: ExtHostConfigProvider, + ) { + // + } + + get(modulePath: string): typeof vscode { + const extension = this._extensionPaths.findSubstr(modulePath) || nullExtensionDescription; + const id = ExtensionIdentifier.toKey(extension.identifier); + + let apiInstance = this._apiInstances.get(id); + if (!apiInstance) { + apiInstance = this._apiFactory(extension, this._extensionRegistry, this._configProvider); + this._apiInstances.set(id, apiInstance); + } + return apiInstance; + } +} + +export class ExtHostExtensionService extends AbstractExtHostExtensionService { + + private _apiInstances?: ApiInstances; + + protected async _beforeAlmostReadyToRunExtensions(): Promise { + // initialize API and register actors + const apiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors); + const configProvider = await this._extHostConfiguration.getConfigProvider(); + const extensionPath = await this.getExtensionPathIndex(); + this._apiInstances = new ApiInstances(apiFactory, extensionPath, this._registry, configProvider); + } + + protected _loadCommonJSModule(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + + // make sure modulePath ends with `.js` + const suffix = '.js'; + modulePath = endsWith(modulePath, suffix) ? modulePath : modulePath + suffix; + + interface FakeCommonJSSelf { + module?: object; + exports?: object; + require?: (module: string) => any; + window?: object; + __dirname: never; + __filename: never; + } + + // FAKE commonjs world that only collects exports + const patchSelf: FakeCommonJSSelf = self; + const module = { exports: {} }; + patchSelf.module = module; + patchSelf.exports = module.exports; + patchSelf.window = self; // <- that's improper but might help extensions that aren't authored correctly + + // FAKE require function that only works for the vscode-module + patchSelf.require = (module: string) => { + if (module !== 'vscode') { + throw new Error(`Cannot load module '${module}'`); + } + return this._apiInstances!.get(modulePath); + }; + + try { + activationTimesBuilder.codeLoadingStart(); + importScripts(modulePath); + } finally { + activationTimesBuilder.codeLoadingStop(); + } + + return Promise.resolve(module.exports as T); + } + + async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise { + throw new Error('Not supported'); + } +} diff --git a/src/vs/workbench/api/worker/extHostLogService.ts b/src/vs/workbench/api/worker/extHostLogService.ts new file mode 100644 index 0000000000..c8d9d26d5f --- /dev/null +++ b/src/vs/workbench/api/worker/extHostLogService.ts @@ -0,0 +1,84 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILogService, LogLevel, AbstractLogService } from 'vs/platform/log/common/log'; +import { ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; +import * as vscode from 'vscode'; + +export class ExtHostLogService extends AbstractLogService implements ILogService, ExtHostLogServiceShape { + + _serviceBrand: any; + + private readonly _logChannel: vscode.OutputChannel; + + constructor( + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtHostOutputService extHostOutputService: IExtHostOutputService + ) { + super(); + this.setLevel(initData.logLevel); + this._logChannel = extHostOutputService.createOutputChannel('Log (Worker Extension Host)'); + } + + $setLevel(level: LogLevel): void { + this.setLevel(level); + } + + trace(_message: string, ..._args: any[]): void { + if (this.getLevel() <= LogLevel.Trace) { + this._logChannel.appendLine(this._format(arguments)); + } + } + + debug(_message: string, ..._args: any[]): void { + if (this.getLevel() <= LogLevel.Debug) { + this._logChannel.appendLine(this._format(arguments)); + } + } + + info(_message: string, ..._args: any[]): void { + if (this.getLevel() <= LogLevel.Info) { + this._logChannel.appendLine(this._format(arguments)); + } + } + + warn(_message: string, ..._args: any[]): void { + if (this.getLevel() <= LogLevel.Warning) { + this._logChannel.appendLine(this._format(arguments)); + } + } + + error(_message: string | Error, ..._args: any[]): void { + if (this.getLevel() <= LogLevel.Error) { + this._logChannel.appendLine(this._format(arguments)); + } + } + + critical(_message: string | Error, ..._args: any[]): void { + if (this.getLevel() <= LogLevel.Critical) { + this._logChannel.appendLine(String(arguments)); + } + } + + private _format(args: any): string { + let result = ''; + + for (let i = 0; i < args.length; i++) { + let a = args[i]; + + if (typeof a === 'object') { + try { + a = JSON.stringify(a); + } catch (e) { } + } + + result += (i > 0 ? ' ' : '') + a; + } + + return result; + } +} diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index b00ef19f94..bebe9c295f 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -71,6 +71,7 @@ export abstract class BreadcrumbsConfig { static FilePath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.filePath'); static SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath'); static SymbolSortOrder = BreadcrumbsConfig._stub<'position' | 'name' | 'type'>('breadcrumbs.symbolSortOrder'); + static Icons = BreadcrumbsConfig._stub('breadcrumbs.icons'); static FileExcludes = BreadcrumbsConfig._stub('files.exclude'); @@ -160,6 +161,11 @@ Registry.as(Extensions.Configuration).registerConfigurat localize('symbolSortOrder.name', "Show symbol outline in alphabetical order."), localize('symbolSortOrder.type', "Show symbol outline in symbol type order."), ] + }, + 'breadcrumbs.icons': { + description: localize('icons', "Render breadcrumb items with icons."), + type: 'boolean', + default: true } } }); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index b7f03dcbb0..9805fc1952 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -69,7 +69,9 @@ class Item extends BreadcrumbsItem { return false; } if (this.element instanceof FileElement && other.element instanceof FileElement) { - return isEqual(this.element.uri, other.element.uri, false); + return (isEqual(this.element.uri, other.element.uri, false) && + this.options.showFileIcons === other.options.showFileIcons && + this.options.showSymbolIcons === other.options.showSymbolIcons); } if (this.element instanceof TreeElement && other.element instanceof TreeElement) { return this.element.id === other.element.id; @@ -143,6 +145,7 @@ export class BreadcrumbsControl { private readonly _ckBreadcrumbsActive: IContextKey; private readonly _cfUseQuickPick: BreadcrumbsConfig; + private readonly _cfShowIcons: BreadcrumbsConfig; readonly domNode: HTMLDivElement; private readonly _widget: BreadcrumbsWidget; @@ -185,6 +188,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService); + this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(_configurationService); this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); } @@ -196,6 +200,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible.reset(); this._ckBreadcrumbsActive.reset(); this._cfUseQuickPick.dispose(); + this._cfShowIcons.dispose(); this._widget.dispose(); this.domNode.remove(); } @@ -246,15 +251,23 @@ export class BreadcrumbsControl { dom.toggleClass(this.domNode, 'backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\'); const updateBreadcrumbs = () => { - const items = model.getElements().map(element => new Item(element, this._options, this._instantiationService)); + const showIcons = this._cfShowIcons.getValue(); + const options: IBreadcrumbsControlOptions = { + ...this._options, + showFileIcons: this._options.showFileIcons && showIcons, + showSymbolIcons: this._options.showSymbolIcons && showIcons + }; + const items = model.getElements().map(element => new Item(element, options, this._instantiationService)); this._widget.setItems(items); this._widget.reveal(items[items.length - 1]); }; const listener = model.onDidUpdate(updateBreadcrumbs); + const configListener = this._cfShowIcons.onDidChange(updateBreadcrumbs); updateBreadcrumbs(); this._breadcrumbsDisposables.clear(); this._breadcrumbsDisposables.add(model); this._breadcrumbsDisposables.add(listener); + this._breadcrumbsDisposables.add(configListener); // close picker on hide/update this._breadcrumbsDisposables.add({ diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 5f95de1de7..41777fafa2 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -806,7 +806,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region openEditor() - openEditor(editor: EditorInput, options?: EditorOptions): Promise { + async openEditor(editor: EditorInput, options?: EditorOptions): Promise { // Guard against invalid inputs if (!editor) { @@ -822,7 +822,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Proceed with opening - return this.doOpenEditor(editor, options).then(withUndefinedAsNull); + return withUndefinedAsNull(await this.doOpenEditor(editor, options)); } private doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise { diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index f45d78c124..9f8e0a7df9 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -63,7 +63,7 @@ export class SidebarPart extends CompositePart implements IViewletServi return undefined; // {{SQL CARBON EDIT}} strict-null-check } - return width; + return Math.max(width, 300); } //#endregion diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index a3ea19220b..7d1ab39a99 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -161,12 +161,12 @@ export class CustomTreeView extends Disposable implements ITreeView { private _showCollapseAllAction = false; private focused: boolean = false; - private domNode: HTMLElement; - private treeContainer: HTMLElement; + private domNode!: HTMLElement; + private treeContainer!: HTMLElement; private _messageValue: string | undefined; - private messageElement: HTMLDivElement; - private tree: WorkbenchAsyncDataTree; - private treeLabels: ResourceLabels; + private messageElement!: HTMLDivElement; + private tree: WorkbenchAsyncDataTree | undefined; + private treeLabels: ResourceLabels | undefined; private root: ITreeItem; private elementsToRefresh: ITreeItem[] = []; private menus: TitleMenus; @@ -218,12 +218,12 @@ export class CustomTreeView extends Disposable implements ITreeView { this.create(); } - private _dataProvider: ITreeViewDataProvider | null; - get dataProvider(): ITreeViewDataProvider | null { + private _dataProvider: ITreeViewDataProvider | undefined; + get dataProvider(): ITreeViewDataProvider | undefined { return this._dataProvider; } - set dataProvider(dataProvider: ITreeViewDataProvider | null) { + set dataProvider(dataProvider: ITreeViewDataProvider | undefined) { if (dataProvider) { this._dataProvider = new class implements ITreeViewDataProvider { async getChildren(node: ITreeItem): Promise { @@ -238,7 +238,7 @@ export class CustomTreeView extends Disposable implements ITreeView { this.updateMessage(); this.refresh(); } else { - this._dataProvider = null; + this._dataProvider = undefined; this.updateMessage(); } } @@ -399,7 +399,7 @@ export class CustomTreeView extends Disposable implements ITreeView { if (!e.browserEvent) { return; } - const selection = this.tree.getSelection(); + const selection = this.tree!.getSelection(); if ((selection.length === 1) && selection[0].command) { this.commandService.executeCommand(selection[0].command.id, ...(selection[0].command.arguments || [])); } @@ -416,7 +416,7 @@ export class CustomTreeView extends Disposable implements ITreeView { event.preventDefault(); event.stopPropagation(); - this.tree.setFocus([node]); + this.tree!.setFocus([node]); const actions = treeMenus.getResourceContextActions(node); if (!actions.length) { return; @@ -436,13 +436,13 @@ export class CustomTreeView extends Disposable implements ITreeView { onHide: (wasCancelled?: boolean) => { if (wasCancelled) { - this.tree.domFocus(); + this.tree!.domFocus(); } }, getActionsContext: () => ({ $treeViewId: this.id, $treeItemHandle: node.handle }), - actionRunner: new MultipleSelectionActionRunner(() => this.tree.getSelection()) + actionRunner: new MultipleSelectionActionRunner(() => this.tree!.getSelection()) }); } @@ -479,8 +479,8 @@ export class CustomTreeView extends Disposable implements ITreeView { DOM.clearNode(this.messageElement); } - private _height: number; - private _width: number; + private _height: number = 0; + private _width: number = 0; layout(height: number, width: number) { if (height && width) { this._height = height; @@ -532,10 +532,11 @@ export class CustomTreeView extends Disposable implements ITreeView { } async expand(itemOrItems: ITreeItem | ITreeItem[]): Promise { - if (this.tree) { + const tree = this.tree; + if (tree) { itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems]; await Promise.all(itemOrItems.map(element => { - return this.tree.expand(element, false); + return tree.expand(element, false); })); } return Promise.resolve(undefined); @@ -575,18 +576,19 @@ export class CustomTreeView extends Disposable implements ITreeView { private refreshing: boolean = false; private async doRefresh(elements: ITreeItem[]): Promise { - if (this.tree) { + const tree = this.tree; + if (tree) { this.refreshing = true; const parents: Set = new Set(); elements.forEach(element => { if (element !== this.root) { - const parent = this.tree.getParentElement(element); + const parent = tree.getParentElement(element); parents.add(parent); } else { parents.add(element); } }); - await Promise.all(Array.from(parents.values()).map(element => this.tree.updateChildren(element, true))); + await Promise.all(Array.from(parents.values()).map(element => tree.updateChildren(element, true))); this.refreshing = false; this.updateContentAreas(); if (this.focused) { @@ -772,7 +774,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer; + private _tree: WorkbenchAsyncDataTree | undefined; constructor(private themeService: IWorkbenchThemeService) { super(); @@ -790,11 +792,15 @@ class Aligner extends Disposable { return false; } - const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput(); - if (this.hasIcon(parent)) { + if (this._tree) { + const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput(); + if (this.hasIcon(parent)) { + return false; + } + return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c)); + } else { return false; } - return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c)); } private hasIcon(node: ITreeItem): boolean { diff --git a/src/vs/workbench/browser/parts/views/media/panelviewlet.css b/src/vs/workbench/browser/parts/views/media/panelviewlet.css index 069c3225d9..cedd92bc02 100644 --- a/src/vs/workbench/browser/parts/views/media/panelviewlet.css +++ b/src/vs/workbench/browser/parts/views/media/panelviewlet.css @@ -7,6 +7,10 @@ border-top: none !important; /* less clutter: do not show any border for first views in a panel */ } +.monaco-panel-view .panel > .panel-header > .actions.show { + display: initial; +} + .monaco-panel-view .panel > .panel-header h3.title { white-space: nowrap; text-overflow: ellipsis; diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index 6c7b953b75..c00f152635 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -41,6 +41,7 @@ export interface IViewletPanelOptions extends IPanelOptions { actionRunner?: IActionRunner; id: string; title: string; + showActionsAlways?: boolean; } export abstract class ViewletPanel extends Panel implements IView { @@ -67,6 +68,7 @@ export abstract class ViewletPanel extends Panel implements IView { protected actionRunner?: IActionRunner; protected toolbar: ToolBar; + private readonly showActionsAlways: boolean = false; private headerContainer: HTMLElement; private titleContainer: HTMLElement; @@ -82,6 +84,7 @@ export abstract class ViewletPanel extends Panel implements IView { this.id = options.id; this.title = options.title; this.actionRunner = options.actionRunner; + this.showActionsAlways = !!options.showActionsAlways; this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService); } @@ -133,6 +136,7 @@ export abstract class ViewletPanel extends Panel implements IView { this.renderHeaderTitle(container, this.title); const actions = append(container, $('.actions')); + toggleClass(actions, 'show', this.showActionsAlways); this.toolbar = new ToolBar(actions, this.contextMenuService, { orientation: ActionsOrientation.HORIZONTAL, actionViewItemProvider: action => this.getActionViewItem(action), diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index a8889821ab..af235b633b 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -8,9 +8,8 @@ import * as browser from 'vs/base/browser/browser'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IGalleryExtension, IExtensionIdentifier, IReportedExtension, IExtensionManagementService, ILocalExtension, IGalleryMetadata } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionTipsService, ExtensionRecommendationReason, IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionType, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; import { ConsoleLogService, ILogService } from 'vs/platform/log/common/log'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -24,7 +23,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common import { ITunnelService } from 'vs/platform/remote/common/tunnel'; // tslint:disable-next-line: import-patterns import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, windowOpenNoOpener } from 'vs/base/browser/dom'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { pathsToEditors } from 'vs/workbench/common/editor'; import { IFileService } from 'vs/platform/files/common/files'; @@ -90,63 +89,6 @@ registerSingleton(IExtensionTipsService, SimpleExtensionTipsService, true); //#endregion -export class SimpleExtensionManagementService implements IExtensionManagementService { - - _serviceBrand: any; - - onInstallExtension = Event.None; - onDidInstallExtension = Event.None; - onUninstallExtension = Event.None; - onDidUninstallExtension = Event.None; - - zip(extension: ILocalExtension): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - unzip(zipLocation: URI, type: ExtensionType): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - install(vsix: URI): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - installFromGallery(extension: IGalleryExtension): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - uninstall(extension: ILocalExtension, force?: boolean): Promise { - return Promise.resolve(undefined); - } - - reinstallFromGallery(extension: ILocalExtension): Promise { - return Promise.resolve(undefined); - } - - getInstalled(type?: ExtensionType): Promise { - // @ts-ignore - return Promise.resolve([]); - } - - getExtensionsReport(): Promise { - // @ts-ignore - return Promise.resolve([]); - } - - updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { - // @ts-ignore - return Promise.resolve(local); - } -} - -registerSingleton(IExtensionManagementService, SimpleExtensionManagementService); - -//#endregion - //#region Extension URL Handler export const IExtensionUrlHandler = createDecorator('inactiveExtensionUrlHandler'); @@ -784,6 +726,8 @@ export class SimpleWindowsService implements IWindowsService { // This needs to be handled from browser process to prevent // foreground ordering issues on Windows openExternal(_url: string): Promise { + windowOpenNoOpener(_url); + return Promise.resolve(true); } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 8f45495f44..4716abf4b4 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -300,7 +300,7 @@ export interface IViewsService { export interface ITreeView extends IDisposable { - dataProvider: ITreeViewDataProvider | null; + dataProvider: ITreeViewDataProvider | undefined; showCollapseAllAction: boolean; diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index b1d16682e1..47822abd71 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -607,11 +607,9 @@ class CallStackDataSource implements IAsyncDataSourcethreads[0]) : Promise.resolve(threads); } else if (isDebugSession(element)) { const childSessions = this.debugService.getModel().getSessions().filter(s => s.parentSession === element); - if (childSessions.length) { - return Promise.resolve(childSessions); - } + const threads: CallStackItem[] = element.getAllThreads(); - return Promise.resolve(element.getAllThreads()); + return Promise.resolve(threads.concat(childSessions)); } else { return this.getThreadChildren(element); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index cfb92f0ff2..14ff396630 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -134,29 +134,33 @@ interface IActiveElement { focus(): void; } +interface IExtensionEditorTemplate { + iconContainer: HTMLElement; + icon: HTMLImageElement; + name: HTMLElement; + identifier: HTMLElement; + preview: HTMLElement; + builtin: HTMLElement; + license: HTMLElement; + publisher: HTMLElement; + installCount: HTMLElement; + rating: HTMLElement; + repository: HTMLElement; + description: HTMLElement; + extensionActionBar: ActionBar; + navbar: NavBar; + content: HTMLElement; + subtextContainer: HTMLElement; + subtext: HTMLElement; + ignoreActionbar: ActionBar; + header: HTMLElement; +} + export class ExtensionEditor extends BaseEditor { static readonly ID: string = 'workbench.editor.extension'; - private iconContainer: HTMLElement; - private icon: HTMLImageElement; - private name: HTMLElement; - private identifier: HTMLElement; - private preview: HTMLElement; - private builtin: HTMLElement; - private license: HTMLElement; - private publisher: HTMLElement; - private installCount: HTMLElement; - private rating: HTMLElement; - private repository: HTMLElement; - private description: HTMLElement; - private extensionActionBar: ActionBar; - private navbar: NavBar; - private content: HTMLElement; - private subtextContainer: HTMLElement; - private subtext: HTMLElement; - private ignoreActionbar: ActionBar; - private header: HTMLElement; + private template: IExtensionEditorTemplate | undefined; private extensionReadme: Cache | null; private extensionChangelog: Cache | null; @@ -165,7 +169,7 @@ export class ExtensionEditor extends BaseEditor { private layoutParticipants: ILayoutParticipant[] = []; private readonly contentDisposables = this._register(new DisposableStore()); private readonly transientDisposables = this._register(new DisposableStore()); - private activeElement: IActiveElement | null; + private activeElement: IActiveElement | null = null; private editorLoadComplete: boolean = false; constructor( @@ -193,43 +197,43 @@ export class ExtensionEditor extends BaseEditor { const root = append(parent, $('.extension-editor')); root.tabIndex = 0; // this is required for the focus tracker on the editor root.style.outline = 'none'; - this.header = append(root, $('.header')); + const header = append(root, $('.header')); - this.iconContainer = append(this.header, $('.icon-container')); - this.icon = append(this.iconContainer, $('img.icon', { draggable: false })); + const iconContainer = append(header, $('.icon-container')); + const icon = append(iconContainer, $('img.icon', { draggable: false })); - const details = append(this.header, $('.details')); + const details = append(header, $('.details')); const title = append(details, $('.title')); - this.name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") })); - this.identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") })); + const name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") })); + const identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") })); - this.preview = append(title, $('span.preview', { title: localize('preview', "Preview") })); - this.preview.textContent = localize('preview', "Preview"); + const preview = append(title, $('span.preview', { title: localize('preview', "Preview") })); + preview.textContent = localize('preview', "Preview"); - this.builtin = append(title, $('span.builtin')); - this.builtin.textContent = localize('builtin', "Built-in"); + const builtin = append(title, $('span.builtin')); + builtin.textContent = localize('builtin', "Built-in"); const subtitle = append(details, $('.subtitle')); - this.publisher = append(subtitle, $('span.publisher.clickable', { title: localize('publisher', "Publisher name"), tabIndex: 0 })); + const publisher = append(subtitle, $('span.publisher.clickable', { title: localize('publisher', "Publisher name"), tabIndex: 0 })); - this.installCount = append(subtitle, $('span.install', { title: localize('install count', "Install count"), tabIndex: 0 })); + const installCount = append(subtitle, $('span.install', { title: localize('install count', "Install count"), tabIndex: 0 })); - this.rating = append(subtitle, $('span.rating.clickable', { title: localize('rating', "Rating"), tabIndex: 0 })); + const rating = append(subtitle, $('span.rating.clickable', { title: localize('rating', "Rating"), tabIndex: 0 })); - this.repository = append(subtitle, $('span.repository.clickable')); - this.repository.textContent = localize('repository', 'Repository'); - this.repository.style.display = 'none'; - this.repository.tabIndex = 0; + const repository = append(subtitle, $('span.repository.clickable')); + repository.textContent = localize('repository', 'Repository'); + repository.style.display = 'none'; + repository.tabIndex = 0; - this.license = append(subtitle, $('span.license.clickable')); - this.license.textContent = localize('license', 'License'); - this.license.style.display = 'none'; - this.license.tabIndex = 0; + const license = append(subtitle, $('span.license.clickable')); + license.textContent = localize('license', 'License'); + license.style.display = 'none'; + license.tabIndex = 0; - this.description = append(details, $('.description')); + const description = append(details, $('.description')); const extensionActions = append(details, $('.actions')); - this.extensionActionBar = new ActionBar(extensionActions, { + const extensionActionBar = this._register(new ActionBar(extensionActions, { animated: false, actionViewItemProvider: (action: Action) => { if (action instanceof ExtensionEditorDropDownAction) { @@ -237,29 +241,48 @@ export class ExtensionEditor extends BaseEditor { } return undefined; } - }); + })); - this.subtextContainer = append(details, $('.subtext-container')); - this.subtext = append(this.subtextContainer, $('.subtext')); - this.ignoreActionbar = new ActionBar(this.subtextContainer, { animated: false }); + const subtextContainer = append(details, $('.subtext-container')); + const subtext = append(subtextContainer, $('.subtext')); + const ignoreActionbar = this._register(new ActionBar(subtextContainer, { animated: false })); - this._register(this.extensionActionBar); - this._register(this.ignoreActionbar); - - this._register(Event.chain(this.extensionActionBar.onDidRun) + this._register(Event.chain(extensionActionBar.onDidRun) .map(({ error }) => error) .filter(error => !!error) .on(this.onError, this)); - this._register(Event.chain(this.ignoreActionbar.onDidRun) + this._register(Event.chain(ignoreActionbar.onDidRun) .map(({ error }) => error) .filter(error => !!error) .on(this.onError, this)); const body = append(root, $('.body')); - this.navbar = new NavBar(body); + const navbar = new NavBar(body); - this.content = append(body, $('.content')); + const content = append(body, $('.content')); + + this.template = { + builtin, + content, + description, + extensionActionBar, + header, + icon, + iconContainer, + identifier, + ignoreActionbar, + installCount, + license, + name, + navbar, + preview, + publisher, + rating, + repository, + subtext, + subtextContainer + }; } private onClick(element: HTMLElement, callback: () => void): IDisposable { @@ -277,6 +300,13 @@ export class ExtensionEditor extends BaseEditor { } async setInput(input: ExtensionsInput, options: EditorOptions, token: CancellationToken): Promise { + if (this.template) { + await this.updateTemplate(input, this.template); + } + return super.setInput(input, options, token); + } + + private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate): Promise { const runningExtensions = await this.extensionService.getExtensions(); const colorThemes = await this.workbenchThemeService.getColorThemes(); const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); @@ -291,18 +321,18 @@ export class ExtensionEditor extends BaseEditor { this.extensionChangelog = new Cache(() => createCancelablePromise(token => extension.getChangelog(token))); this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(token))); - const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, this.iconContainer, true); - const onError = Event.once(domEvent(this.icon, 'error')); - onError(() => this.icon.src = extension.iconUrlFallback, null, this.transientDisposables); - this.icon.src = extension.iconUrl; + const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, template.iconContainer, true); + const onError = Event.once(domEvent(template.icon, 'error')); + onError(() => template.icon.src = extension.iconUrlFallback, null, this.transientDisposables); + template.icon.src = extension.iconUrl; - this.name.textContent = extension.displayName; - this.identifier.textContent = extension.identifier.id; - this.preview.style.display = extension.preview ? 'inherit' : 'none'; - this.builtin.style.display = extension.type === ExtensionType.System ? 'inherit' : 'none'; + template.name.textContent = extension.displayName; + template.identifier.textContent = extension.identifier.id; + template.preview.style.display = extension.preview ? 'inherit' : 'none'; + template.builtin.style.display = extension.type === ExtensionType.System ? 'inherit' : 'none'; - this.publisher.textContent = extension.publisherDisplayName; - this.description.textContent = extension.description; + template.publisher.textContent = extension.publisherDisplayName; + template.description.textContent = extension.description; const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); let recommendationsData = {}; @@ -320,51 +350,50 @@ export class ExtensionEditor extends BaseEditor { */ this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData)); - toggleClass(this.name, 'clickable', !!extension.url); - toggleClass(this.publisher, 'clickable', !!extension.url); - toggleClass(this.rating, 'clickable', !!extension.url); + toggleClass(template.name, 'clickable', !!extension.url); + toggleClass(template.publisher, 'clickable', !!extension.url); + toggleClass(template.rating, 'clickable', !!extension.url); if (extension.url) { - this.transientDisposables.add(this.onClick(this.name, () => window.open(extension.url))); - this.transientDisposables.add(this.onClick(this.rating, () => window.open(`${extension.url}#review-details`))); - this.transientDisposables.add(this.onClick(this.publisher, () => { + this.transientDisposables.add(this.onClick(template.name, () => window.open(extension.url))); + this.transientDisposables.add(this.onClick(template.rating, () => window.open(`${extension.url}#review-details`))); + this.transientDisposables.add(this.onClick(template.publisher, () => { this.viewletService.openViewlet(VIEWLET_ID, true) .then(viewlet => viewlet as IExtensionsViewlet) .then(viewlet => viewlet.search(`publisher:"${extension.publisherDisplayName}"`)); })); if (extension.licenseUrl) { - this.transientDisposables.add(this.onClick(this.license, () => window.open(extension.licenseUrl))); - this.license.style.display = 'initial'; + this.transientDisposables.add(this.onClick(template.license, () => window.open(extension.licenseUrl))); + template.license.style.display = 'initial'; } else { - this.license.style.display = 'none'; + template.license.style.display = 'none'; } } else { - this.license.style.display = 'none'; + template.license.style.display = 'none'; } // {{SQL CARBON EDIT}} add license url if (extension.licenseUrl) { - this.license.onclick = finalHandler(() => window.open(extension.licenseUrl)); - this.license.style.display = 'initial'; + template.license.onclick = finalHandler(() => window.open(extension.licenseUrl)); + template.license.style.display = 'initial'; } else { - this.license.onclick = null; - this.license.style.display = 'none'; + template.license.onclick = null; + template.license.style.display = 'none'; } // {{SQL CARBON EDIT}} - End if (extension.repository) { - this.transientDisposables.add(this.onClick(this.repository, () => window.open(extension.repository))); - this.repository.style.display = 'initial'; + this.transientDisposables.add(this.onClick(template.repository, () => window.open(extension.repository))); + template.repository.style.display = 'initial'; } else { - this.repository.style.display = 'none'; + template.repository.style.display = 'none'; } const widgets = [ remoteBadge, - // {{SQL CARBON EDIT}} Remove the widgets - // this.instantiationService.createInstance(InstallCountWidget, this.installCount, false), - // this.instantiationService.createInstance(RatingsWidget, this.rating, false) + // this.instantiationService.createInstance(InstallCountWidget, template.installCount, false), {{SQL CARBON EDIT}} Remove the widgets + // this.instantiationService.createInstance(RatingsWidget, template.rating, false) {{SQL CARBON EDIT}} Remove the widgets ]; const reloadAction = this.instantiationService.createInstance(ReloadAction); const combinedInstallAction = this.instantiationService.createInstance(CombinedInstallAction); @@ -387,20 +416,20 @@ export class ExtensionEditor extends BaseEditor { const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]); extensionContainers.extension = extension; - this.extensionActionBar.clear(); - this.extensionActionBar.push(actions, { icon: true, label: true }); + template.extensionActionBar.clear(); + template.extensionActionBar.push(actions, { icon: true, label: true }); for (const disposable of [...actions, ...widgets, extensionContainers]) { this.transientDisposables.add(disposable); } - this.setSubText(extension, reloadAction); - this.content.innerHTML = ''; // Clear content before setting navbar actions. + this.setSubText(extension, reloadAction, template); + template.content.innerHTML = ''; // Clear content before setting navbar actions. - this.navbar.clear(); - this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables); + template.navbar.clear(); + template.navbar.onChange(e => this.onNavbarChange(extension, e, template), this, this.transientDisposables); if (extension.hasReadme()) { - this.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file")); + template.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file")); } this.extensionManifest.get() .promise @@ -409,25 +438,23 @@ export class ExtensionEditor extends BaseEditor { combinedInstallAction.manifest = manifest; } if (extension.extensionPack.length) { - this.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together")); + template.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together")); } if (manifest && manifest.contributes) { - this.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension")); + template.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension")); } if (extension.hasChangelog()) { - this.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file")); + template.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file")); } if (extension.dependencies.length) { - this.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on")); + template.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on")); } this.editorLoadComplete = true; }); - - return super.setInput(input, options, token); } - private setSubText(extension: IExtension, reloadAction: ReloadAction): void { - hide(this.subtextContainer); + private setSubText(extension: IExtension, reloadAction: ReloadAction, template: IExtensionEditorTemplate): void { + hide(template.subtextContainer); const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction); const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction); @@ -436,23 +463,23 @@ export class ExtensionEditor extends BaseEditor { ignoreAction.enabled = false; undoIgnoreAction.enabled = false; - this.ignoreActionbar.clear(); - this.ignoreActionbar.push([ignoreAction, undoIgnoreAction], { icon: true, label: true }); + template.ignoreActionbar.clear(); + template.ignoreActionbar.push([ignoreAction, undoIgnoreAction], { icon: true, label: true }); this.transientDisposables.add(ignoreAction); this.transientDisposables.add(undoIgnoreAction); const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); if (extRecommendations[extension.identifier.id.toLowerCase()]) { ignoreAction.enabled = true; - this.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; - show(this.subtextContainer); + template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; + show(template.subtextContainer); } else if (this.extensionTipsService.getAllIgnoredRecommendations().global.indexOf(extension.identifier.id.toLowerCase()) !== -1) { undoIgnoreAction.enabled = true; - this.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); - show(this.subtextContainer); + template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); + show(template.subtextContainer); } else { - this.subtext.textContent = ''; + template.subtext.textContent = ''; } this.extensionTipsService.onRecommendationChange(change => { @@ -462,28 +489,28 @@ export class ExtensionEditor extends BaseEditor { const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); if (extRecommendations[extension.identifier.id.toLowerCase()]) { ignoreAction.enabled = true; - this.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; + template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; } } else { undoIgnoreAction.enabled = true; ignoreAction.enabled = false; - this.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); + template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); } } }); this.transientDisposables.add(reloadAction.onDidChange(e => { if (e.tooltip) { - this.subtext.textContent = reloadAction.tooltip; - show(this.subtextContainer); + template.subtext.textContent = reloadAction.tooltip; + show(template.subtextContainer); ignoreAction.enabled = false; undoIgnoreAction.enabled = false; } if (e.enabled === true) { - show(this.subtextContainer); + show(template.subtextContainer); } if (e.enabled === false) { - hide(this.subtextContainer); + hide(template.subtextContainer); } })); } @@ -507,7 +534,7 @@ export class ExtensionEditor extends BaseEditor { } } - private onNavbarChange(extension: IExtension, { id, focus }: { id: string, focus: boolean }): void { + private onNavbarChange(extension: IExtension, { id, focus }: { id: string | null, focus: boolean }, template: IExtensionEditorTemplate): void { if (this.editorLoadComplete) { /* __GDPR__ "extensionEditor:navbarChange" : { @@ -521,30 +548,32 @@ export class ExtensionEditor extends BaseEditor { } this.contentDisposables.clear(); - this.content.innerHTML = ''; + template.content.innerHTML = ''; this.activeElement = null; - this.open(id, extension) - .then(activeElement => { - this.activeElement = activeElement; - if (focus) { - this.focus(); - } - }); + if (id) { + this.open(id, extension, template) + .then(activeElement => { + this.activeElement = activeElement; + if (focus) { + this.focus(); + } + }); + } } - private open(id: string, extension: IExtension): Promise { + private open(id: string, extension: IExtension, template: IExtensionEditorTemplate): Promise { switch (id) { - case NavbarSection.Readme: return this.openReadme(); - case NavbarSection.Contributions: return this.openContributions(); - case NavbarSection.Changelog: return this.openChangelog(); - case NavbarSection.Dependencies: return this.openDependencies(extension); - case NavbarSection.ExtensionPack: return this.openExtensionPack(extension); + case NavbarSection.Readme: return this.openReadme(template); + case NavbarSection.Contributions: return this.openContributions(template); + case NavbarSection.Changelog: return this.openChangelog(template); + case NavbarSection.Dependencies: return this.openDependencies(extension, template); + case NavbarSection.ExtensionPack: return this.openExtensionPack(extension, template); } return Promise.resolve(null); } - private openMarkdown(cacheResult: CacheResult, noContentCopy: string): Promise { - return this.loadContents(() => cacheResult) + private openMarkdown(cacheResult: CacheResult, noContentCopy: string, template: IExtensionEditorTemplate): Promise { + return this.loadContents(() => cacheResult, template) .then(marked.parse) .then(content => this.renderBody(content)) .then(removeEmbeddedSVGs) @@ -556,7 +585,7 @@ export class ExtensionEditor extends BaseEditor { { svgWhiteList: this.extensionsWorkbenchService.allowedBadgeProviders, }); - webviewElement.mountTo(this.content); + webviewElement.mountTo(template.content); this.contentDisposables.add(webviewElement.onDidFocus(() => this.fireOnDidFocus())); const removeLayoutParticipant = arrays.insert(this.layoutParticipants, webviewElement); this.contentDisposables.add(toDisposable(removeLayoutParticipant)); @@ -575,7 +604,7 @@ export class ExtensionEditor extends BaseEditor { return webviewElement; }) .then(undefined, () => { - const p = append(this.content, $('p.nocontent')); + const p = append(template.content, $('p.nocontent')); p.textContent = noContentCopy; return p; }); @@ -769,17 +798,17 @@ export class ExtensionEditor extends BaseEditor { `; } - private openReadme(): Promise { - return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available.")); + private openReadme(template: IExtensionEditorTemplate): Promise { + return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), template); } - private openChangelog(): Promise { - return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available.")); + private openChangelog(template: IExtensionEditorTemplate): Promise { + return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available."), template); } - private openContributions(): Promise { + private openContributions(template: IExtensionEditorTemplate): Promise { const content = $('div', { class: 'subcontent', tabindex: '0' }); - return this.loadContents(() => this.extensionManifest!.get()) + return this.loadContents(() => this.extensionManifest!.get(), template) .then(manifest => { if (!manifest) { return content; @@ -811,28 +840,28 @@ export class ExtensionEditor extends BaseEditor { const isEmpty = !renders.some(x => x); if (isEmpty) { append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions"); - append(this.content, content); + append(template.content, content); } else { - append(this.content, scrollableContent.getDomNode()); + append(template.content, scrollableContent.getDomNode()); this.contentDisposables.add(scrollableContent); } return content; }, () => { append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions"); - append(this.content, content); + append(template.content, content); return content; }); } - private openDependencies(extension: IExtension): Promise { + private openDependencies(extension: IExtension, template: IExtensionEditorTemplate): Promise { if (arrays.isFalsyOrEmpty(extension.dependencies)) { - append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies"); - return Promise.resolve(this.content); + append(template.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies"); + return Promise.resolve(template.content); } const content = $('div', { class: 'subcontent' }); const scrollableContent = new DomScrollableElement(content, {}); - append(this.content, scrollableContent.getDomNode()); + append(template.content, scrollableContent.getDomNode()); this.contentDisposables.add(scrollableContent); const dependenciesTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.dependencies || [], this.extensionsWorkbenchService), content); @@ -849,10 +878,10 @@ export class ExtensionEditor extends BaseEditor { return Promise.resolve({ focus() { dependenciesTree.domFocus(); } }); } - private openExtensionPack(extension: IExtension): Promise { + private openExtensionPack(extension: IExtension, template: IExtensionEditorTemplate): Promise { const content = $('div', { class: 'subcontent' }); const scrollableContent = new DomScrollableElement(content, {}); - append(this.content, scrollableContent.getDomNode()); + append(template.content, scrollableContent.getDomNode()); this.contentDisposables.add(scrollableContent); const extensionsPackTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.extensionPack || [], this.extensionsWorkbenchService), content); @@ -1272,11 +1301,11 @@ export class ExtensionEditor extends BaseEditor { return null; } - private loadContents(loadingTask: () => CacheResult): Promise { - addClass(this.content, 'loading'); + private loadContents(loadingTask: () => CacheResult, template: IExtensionEditorTemplate): Promise { + addClass(template.content, 'loading'); const result = loadingTask(); - const onDone = () => removeClass(this.content, 'loading'); + const onDone = () => removeClass(template.content, 'loading'); result.promise.then(onDone, onDone); this.contentDisposables.add(toDisposable(() => result.dispose())); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 71707992e3..c03daece76 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -3097,7 +3097,10 @@ interface IExtensionPickItem extends IQuickPickItem { export class InstallLocalExtensionsInRemoteAction extends Action { + private extensions: IExtension[] | undefined = undefined; + constructor( + private readonly selectAndInstall: boolean, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @@ -3109,32 +3112,56 @@ export class InstallLocalExtensionsInRemoteAction extends Action { ) { super('workbench.extensions.actions.installLocalExtensionsInRemote'); this.update(); - this._register(this.extensionsWorkbenchService.onChange(() => this.update())); + this.extensionsWorkbenchService.queryLocal().then(() => this.updateExtensions()); + this._register(this.extensionsWorkbenchService.onChange(() => { + if (this.extensions) { + this.updateExtensions(); + } + })); } get label(): string { - return this.extensionManagementServerService.remoteExtensionManagementServer ? - localize('install local extensions', "Install Local Extensions in {0}...", this.extensionManagementServerService.remoteExtensionManagementServer.label) : ''; + if (this.extensionManagementServerService.remoteExtensionManagementServer) { + return this.selectAndInstall ? + localize('select and install local extensions', "Install Local Extensions in {0}...", this.extensionManagementServerService.remoteExtensionManagementServer.label) + : localize('install local extensions', "Install Local Extensions in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label); + } + return ''; + } + + private updateExtensions(): void { + this.extensions = this.extensionsWorkbenchService.local; + this.update(); } private update(): void { - this.enabled = this.getLocalExtensionsToInstall().length > 0; + this.enabled = !!this.extensions && this.getExtensionsToInstall(this.extensions).length > 0; + this.tooltip = this.label; } - private getLocalExtensionsToInstall(): IExtension[] { - return this.extensionsWorkbenchService.local.filter(extension => { + async run(): Promise { + if (this.selectAndInstall) { + return this.selectAndInstallLocalExtensions(); + } else { + const extensionsToInstall = await this.queryExtensionsToInstall(); + return this.installLocalExtensions(extensionsToInstall); + } + } + + private async queryExtensionsToInstall(): Promise { + const local = await this.extensionsWorkbenchService.queryLocal(); + return this.getExtensionsToInstall(local); + } + + private getExtensionsToInstall(local: IExtension[]): IExtension[] { + return local.filter(extension => { const action = this.instantiationService.createInstance(RemoteInstallAction); action.extension = extension; return action.enabled; }); } - async run(): Promise { - this.selectAndInstallLocalExtensions(); - return Promise.resolve(); - } - - private selectAndInstallLocalExtensions(): void { + private async selectAndInstallLocalExtensions(): Promise { const quickPick = this.quickInputService.createQuickPick(); quickPick.busy = true; const disposable = quickPick.onDidAccept(() => { @@ -3144,7 +3171,7 @@ export class InstallLocalExtensionsInRemoteAction extends Action { this.onDidAccept(quickPick.selectedItems); }); quickPick.show(); - const localExtensionsToInstall = this.getLocalExtensionsToInstall(); + const localExtensionsToInstall = await this.queryExtensionsToInstall(); quickPick.busy = false; if (localExtensionsToInstall.length) { quickPick.title = localize('install local extensions title', "Install Local Extensions in {0}", this.extensionManagementServerService.remoteExtensionManagementServer!.label); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index b6f33dbede..b9f50fa295 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -156,7 +156,7 @@ export class UnknownExtensionRenderer implements IListRenderer { - return this.extensionsWorkdbenchService.open(this.extensionData.extension, sideByside); + if (this._extensionData) { + return this.extensionsWorkdbenchService.open(this._extensionData.extension, sideByside); + } + return Promise.resolve(); } } @@ -263,4 +262,4 @@ export class ExtensionData implements IExtensionData { } return null; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 1db4bd5db8..7614dc012c 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -22,7 +22,7 @@ import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, AutoUpdate import { ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction, ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction, - EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, InstallLocalExtensionsInRemoteAction + EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -323,7 +323,8 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensionsViewlet { - private onSearchChange: EventOf; + private readonly _onSearchChange: Emitter = this._register(new Emitter()); + private readonly onSearchChange: EventOf = this._onSearchChange.event; private nonEmptyWorkspaceContextKey: IContextKey; private defaultViewsContextKey: IContextKey; private searchMarketplaceExtensionsContextKey: IContextKey; @@ -337,12 +338,10 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio private defaultRecommendedExtensionsContextKey: IContextKey; private searchDelayer: Delayer; - private root: HTMLElement; - - private searchBox: SuggestEnabledInput; - private extensionsBox: HTMLElement; - private primaryActions: IAction[]; - private secondaryActions: IAction[] | null; + private root: HTMLElement | undefined; + private searchBox: SuggestEnabledInput | undefined; + private primaryActions: IAction[] | undefined; + private secondaryActions: IAction[] | null = null; private readonly searchViewletState: MementoObject; constructor( @@ -352,7 +351,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio @IInstantiationService instantiationService: IInstantiationService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @INotificationService private readonly notificationService: INotificationService, @IViewletService private readonly viewletService: IViewletService, @IThemeService themeService: IThemeService, @@ -422,32 +420,35 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this._register(attachSuggestEnabledInputBoxStyler(this.searchBox, this.themeService)); - const _searchChange = new Emitter(); - this.onSearchChange = _searchChange.event; this._register(this.searchBox.onInputDidChange(() => { this.triggerSearch(); - _searchChange.fire(this.searchBox.getValue()); + this._onSearchChange.fire(this.searchBox!.getValue()); }, this)); this._register(this.searchBox.onShouldFocusResults(() => this.focusListView(), this)); this._register(this.onDidChangeVisibility(visible => { if (visible) { - this.searchBox.focus(); + this.searchBox!.focus(); } })); - this.extensionsBox = append(this.root, $('.extensions')); - super.create(this.extensionsBox); + super.create(append(this.root, $('.extensions'))); } focus(): void { - this.searchBox.focus(); + if (this.searchBox) { + this.searchBox.focus(); + } } layout(dimension: Dimension): void { - toggleClass(this.root, 'narrow', dimension.width <= 300); - this.searchBox.layout({ height: 20, width: dimension.width - 34 }); + if (this.root) { + toggleClass(this.root, 'narrow', dimension.width <= 300); + } + if (this.searchBox) { + this.searchBox.layout({ height: 20, width: dimension.width - 34 }); + } super.layout(new Dimension(dimension.width, dimension.height - 38)); } @@ -458,7 +459,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio getActions(): IAction[] { if (!this.primaryActions) { this.primaryActions = [ - this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox.getValue()) + this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox ? this.searchBox.getValue() : '') ]; } return this.primaryActions; @@ -484,7 +485,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL), ...(this.configurationService.getValue(AutoUpdateConfigurationKey) ? [this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)] : [this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)]), this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL), - ...(this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer ? [this.instantiationService.createInstance(InstallLocalExtensionsInRemoteAction)] : []), new Separator(), this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL), this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL) @@ -495,22 +495,24 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio } search(value: string): void { - const event = new Event('input', { bubbles: true }) as SearchInputEvent; - event.immediate = true; + if (this.searchBox) { + const event = new Event('input', { bubbles: true }) as SearchInputEvent; + event.immediate = true; - this.searchBox.setValue(value); + this.searchBox.setValue(value); + } } - private triggerSearch(immediate = false): void { - this.searchDelayer.trigger(() => this.doSearch(), immediate || !this.searchBox.getValue() ? 0 : 500).then(undefined, err => this.onError(err)); + private triggerSearch(): void { + this.searchDelayer.trigger(() => this.doSearch(), this.searchBox && this.searchBox.getValue() ? 500 : 0).then(undefined, err => this.onError(err)); } private normalizedQuery(): string { - return this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:'); + return this.searchBox ? this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:') : ''; } protected saveState(): void { - const value = this.searchBox.getValue(); + const value = this.searchBox ? this.searchBox.getValue() : ''; if (ExtensionsListView.isLocalExtensionsQuery(value)) { this.searchViewletState['query.value'] = value; } else { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index ffa3d815f6..79cc19ddc6 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -14,11 +14,11 @@ import { IExtensionManagementServer, IExtensionManagementServerService, IExtensi import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { append, $, toggleClass } from 'vs/base/browser/dom'; +import { append, $, toggleClass, addClass } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Delegate, Renderer, IExtensionsViewState } from 'vs/workbench/contrib/extensions/browser/extensionsList'; -import { IExtension, IExtensionsWorkbenchService, ExtensionState } from '../common/extensions'; -import { Query } from '../common/extensionQuery'; +import { IExtension, IExtensionsWorkbenchService, ExtensionState } from 'vs/workbench/contrib/extensions/common/extensions'; +import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; @@ -27,8 +27,8 @@ import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/brows import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; -import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { WorkbenchPagedList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -74,15 +74,16 @@ class ExtensionListViewWarning extends Error { } export class ExtensionsListView extends ViewletPanel { - private readonly server: IExtensionManagementServer | undefined; - private messageContainer: HTMLElement; - private messageSeverityIcon: HTMLElement; - private messageBox: HTMLElement; - private extensionsList: HTMLElement; - private badge: CountBadge; - protected badgeContainer: HTMLElement; - private list: WorkbenchPagedList | null; - private queryRequest: { query: string, request: CancelablePromise> } | null; + protected readonly server: IExtensionManagementServer | undefined; + private bodyTemplate: { + messageContainer: HTMLElement; + messageSeverityIcon: HTMLElement; + messageBox: HTMLElement; + extensionsList: HTMLElement; + } | undefined; + private badge: CountBadge | undefined; + private list: WorkbenchPagedList | null = null; + private queryRequest: { query: string, request: CancelablePromise> } | null = null; constructor( options: ExtensionsListViewOptions, @@ -104,31 +105,27 @@ export class ExtensionsListView extends ViewletPanel { @IProductService protected readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService); this.server = options.server; } protected renderHeader(container: HTMLElement): void { - this.renderHeaderTitle(container); - } + addClass(container, 'extension-view-header'); + super.renderHeader(container); - renderHeaderTitle(container: HTMLElement): void { - super.renderHeaderTitle(container, this.title); - - this.badgeContainer = append(container, $('.count-badge-wrapper')); - this.badge = new CountBadge(this.badgeContainer); + this.badge = new CountBadge(append(container, $('.count-badge-wrapper'))); this._register(attachBadgeStyler(this.badge, this.themeService)); } renderBody(container: HTMLElement): void { - this.extensionsList = append(container, $('.extensions-list')); - this.messageContainer = append(container, $('.message-container')); - this.messageSeverityIcon = append(this.messageContainer, $('')); - this.messageBox = append(this.messageContainer, $('.message')); + const extensionsList = append(container, $('.extensions-list')); + const messageContainer = append(container, $('.message-container')); + const messageSeverityIcon = append(messageContainer, $('')); + const messageBox = append(messageContainer, $('.message')); const delegate = new Delegate(); const extensionsViewState = new ExtensionsViewState(); const renderer = this.instantiationService.createInstance(Renderer, extensionsViewState); - this.list = this.instantiationService.createInstance(WorkbenchPagedList, this.extensionsList, delegate, [renderer], { + this.list = this.instantiationService.createInstance(WorkbenchPagedList, extensionsList, delegate, [renderer], { ariaLabel: localize('extensions', "Extensions"), multipleSelectionSupport: false, setRowLineHeight: false, @@ -148,10 +145,19 @@ export class ExtensionsListView extends ViewletPanel { .map(e => e.elements[0]) .filter(e => !!e) .on(this.pin, this)); + + this.bodyTemplate = { + extensionsList, + messageBox, + messageContainer, + messageSeverityIcon + }; } protected layoutBody(height: number, width: number): void { - this.extensionsList.style.height = height + 'px'; + if (this.bodyTemplate) { + this.bodyTemplate.extensionsList.style.height = height + 'px'; + } if (this.list) { this.list.layout(height, width); } @@ -501,7 +507,7 @@ export class ExtensionsListView extends ViewletPanel { } - private _searchExperiments: Promise; + private _searchExperiments: Promise | undefined; private getSearchExperiments(): Promise { if (!this._searchExperiments) { this._searchExperiments = this.experimentService.getExperimentsByType(ExperimentActionType.ExtensionSearchResults); @@ -773,24 +779,27 @@ export class ExtensionsListView extends ViewletPanel { this.list.scrollTop = 0; const count = this.count(); - toggleClass(this.extensionsList, 'hidden', count === 0); - toggleClass(this.messageContainer, 'hidden', count > 0); - this.badge.setCount(count); + if (this.bodyTemplate && this.badge) { - if (count === 0 && this.isBodyVisible()) { - if (error) { - if (error instanceof ExtensionListViewWarning) { - this.messageSeverityIcon.className = SeverityIcon.className(Severity.Warning); - this.messageBox.textContent = getErrorMessage(error); + toggleClass(this.bodyTemplate.extensionsList, 'hidden', count === 0); + toggleClass(this.bodyTemplate.messageContainer, 'hidden', count > 0); + this.badge.setCount(count); + + if (count === 0 && this.isBodyVisible()) { + if (error) { + if (error instanceof ExtensionListViewWarning) { + this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Warning); + this.bodyTemplate.messageBox.textContent = getErrorMessage(error); + } else { + this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Error); + this.bodyTemplate.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error)); + } } else { - this.messageSeverityIcon.className = SeverityIcon.className(Severity.Error); - this.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error)); + this.bodyTemplate.messageSeverityIcon.className = ''; + this.bodyTemplate.messageBox.textContent = localize('no extensions found', "No extensions found."); } - } else { - this.messageSeverityIcon.className = ''; - this.messageBox.textContent = localize('no extensions found', "No extensions found."); + alert(this.bodyTemplate.messageBox.textContent); } - alert(this.messageBox.textContent); } } } @@ -949,6 +958,15 @@ export class ServerExtensionsView extends ExtensionsListView { } return super.show(query.trim()); } + + getActions(): IAction[] { + if (this.extensionManagementServerService.localExtensionManagementServer === this.server) { + const installLocalExtensionsInRemoteAction = this._register(this.instantiationService.createInstance(InstallLocalExtensionsInRemoteAction, false)); + installLocalExtensionsInRemoteAction.class = 'octicon octicon-cloud-download'; + return [installLocalExtensionsInRemoteAction]; + } + return []; + } } export class EnabledExtensionsView extends ExtensionsListView { @@ -1030,7 +1048,7 @@ export class RecommendedExtensionsView extends ExtensionsListView { export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { private readonly recommendedExtensionsQuery = '@recommended:workspace'; - private installAllAction: InstallWorkspaceRecommendedExtensionsAction; + private installAllAction: InstallWorkspaceRecommendedExtensionsAction | undefined; renderBody(container: HTMLElement): void { super.renderBody(container); @@ -1040,25 +1058,15 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { this._register(this.contextService.onDidChangeWorkbenchState(() => this.update())); } - renderHeader(container: HTMLElement): void { - super.renderHeader(container); + getActions(): IAction[] { + if (!this.installAllAction) { + this.installAllAction = this._register(this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, [])); + this.installAllAction.class = 'octicon octicon-cloud-download'; + } - const listActionBar = $('.list-actionbar-container'); - container.insertBefore(listActionBar, this.badgeContainer); - - const actionbar = this._register(new ActionBar(listActionBar, { - animated: false - })); - actionbar.onDidRun(({ error }) => error && this.notificationService.error(error)); - - this.installAllAction = this._register(this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, [])); const configureWorkspaceFolderAction = this._register(this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL)); - - this.installAllAction.class = 'octicon octicon-cloud-download'; configureWorkspaceFolderAction.class = 'octicon octicon-pencil'; - - actionbar.push([this.installAllAction], { icon: true, label: false }); - actionbar.push([configureWorkspaceFolderAction], { icon: true, label: false }); + return [this.installAllAction, configureWorkspaceFolderAction]; } async show(query: string): Promise> { @@ -1073,9 +1081,11 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { this.setRecommendationsToInstall(); } - private setRecommendationsToInstall(): Promise { - return this.getRecommendationsToInstall() - .then(recommendations => { this.installAllAction.recommendations = recommendations; }); + private async setRecommendationsToInstall(): Promise { + const recommendations = await this.getRecommendationsToInstall(); + if (this.installAllAction) { + this.installAllAction.recommendations = recommendations; + } } private getRecommendationsToInstall(): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 8d9caa2380..8d85f13468 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -18,9 +18,9 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export abstract class ExtensionWidget extends Disposable implements IExtensionContainer { - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } + private _extension: IExtension | null = null; + get extension(): IExtension | null { return this._extension; } + set extension(extension: IExtension | null) { this._extension = extension; this.update(); } update(): void { this.render(); } abstract render(): void; } @@ -183,7 +183,7 @@ export class RecommendationWidget extends ExtensionWidget { private element?: HTMLElement; private readonly disposables = this._register(new DisposableStore()); - private _tooltip: string; + private _tooltip: string = ''; get tooltip(): string { return this._tooltip; } set tooltip(tooltip: string) { if (this._tooltip !== tooltip) { @@ -314,4 +314,4 @@ class RemoteBadge extends Disposable { updateTitle(); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 035f810297..c72d62938f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -500,7 +500,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private readonly _onChange: Emitter = new Emitter(); get onChange(): Event { return this._onChange.event; } - private _extensionAllowedBadgeProviders: string[]; + private _extensionAllowedBadgeProviders: string[] | undefined; private installing: IExtension[] = []; constructor( @@ -1151,12 +1151,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } - private _ignoredAutoUpdateExtensions: string[]; + private _ignoredAutoUpdateExtensions: string[] | undefined; private get ignoredAutoUpdateExtensions(): string[] { if (!this._ignoredAutoUpdateExtensions) { this._ignoredAutoUpdateExtensions = JSON.parse(this.storageService.get('extensions.ignoredAutoUpdateExtension', StorageScope.GLOBAL, '[]') || '[]'); } - return this._ignoredAutoUpdateExtensions; + return this._ignoredAutoUpdateExtensions!; } private set ignoredAutoUpdateExtensions(extensionIds: string[]) { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensions.css b/src/vs/workbench/contrib/extensions/browser/media/extensions.css index 8033dd8d4d..d581b73aab 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensions.css @@ -6,8 +6,3 @@ .monaco-workbench .activitybar > .content .monaco-action-bar .action-label.extensions { -webkit-mask: url('extensions-activity-bar.svg') no-repeat 50% 50%; } - -.extensions .split-view-view .panel-header .count-badge-wrapper { - position: absolute; - right: 12px; -} \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index bda60c60e3..83206a788e 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -27,13 +27,16 @@ height: calc(100% - 38px); } -.extensions-viewlet > .extensions .list-actionbar-container .monaco-action-bar .action-item > .octicon { - font-size: 12px; - line-height: 1; - margin-right: 10px; +.extensions-viewlet > .extensions .extension-view-header .monaco-action-bar { + margin-right: 4px; } -.extensions-viewlet > .extensions .list-actionbar-container .monaco-action-bar .action-item.disabled { +.extensions-viewlet > .extensions .extension-view-header .monaco-action-bar .action-item > .action-label.icon.octicon { + vertical-align: middle; + line-height: 22px; +} + +.extensions-viewlet > .extensions .extension-view-header .monaco-action-bar .action-item.disabled { display: none; } @@ -44,7 +47,7 @@ } .extensions-viewlet > .extensions .panel-header { - padding-right: 28px; + padding-right: 6px; } .extensions-viewlet > .extensions .panel-header > .title { diff --git a/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts b/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts index 44595ecaf9..c750591e01 100644 --- a/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts +++ b/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts @@ -22,7 +22,7 @@ export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchC ) { super(); if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { - const installLocalExtensionsInRemoteAction = instantiationService.createInstance(InstallLocalExtensionsInRemoteAction); + const installLocalExtensionsInRemoteAction = instantiationService.createInstance(InstallLocalExtensionsInRemoteAction, true); CommandsRegistry.registerCommand('workbench.extensions.installLocalExtensions', () => installLocalExtensionsInRemoteAction.run()); let disposable = Disposable.None; const appendMenuItem = () => { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index b14de369fc..157395c93e 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -44,7 +44,7 @@ import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/e import { coalesce } from 'vs/base/common/arrays'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { onUnexpectedError, getErrorMessage } from 'vs/base/common/errors'; // {{SQL CARBON EDIT}} import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; @@ -1140,7 +1140,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { return await fileService.copy(fileToPaste, targetFile); } } catch (e) { - onError(notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile"))); + onError(notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile. {0}", getErrorMessage(e)))); return undefined; } })); diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index 12bd616a4a..6b39528ef4 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -14,14 +14,11 @@ import { EditorOptions } from 'vs/workbench/common/editor'; import { IOutputChannelDescriptor, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, LOG_SCHEME, CONTEXT_ACTIVE_LOG_OUTPUT, LOG_MIME, OUTPUT_MIME } from 'vs/workbench/contrib/output/common/output'; import { OutputPanel } from 'vs/workbench/contrib/output/browser/outputPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { OutputLinkProvider } from 'vs/workbench/contrib/output/common/outputLinkProvider'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { ITextModel } from 'vs/editor/common/model'; import { IPanel } from 'vs/workbench/common/panel'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { ILogService } from 'vs/platform/log/common/log'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -77,10 +74,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo @IStorageService private readonly storageService: IStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IPanelService private readonly panelService: IPanelService, - @IWorkspaceContextService contextService: IWorkspaceContextService, @ITextModelService textModelResolverService: ITextModelService, - @IEnvironmentService environmentService: IEnvironmentService, - @IWindowService windowService: IWindowService, @ILogService private readonly logService: ILogService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IContextKeyService private readonly contextKeyService: IContextKeyService, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index df671f4cab..4eead4fe9a 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -854,7 +854,7 @@ export class SettingsEditor2 extends BaseEditor { this._register(model.onDidChangeGroups(() => this.onConfigUpdate())); this.defaultSettingsEditorModel = model; - return this.onConfigUpdate(); + return this.onConfigUpdate(undefined, true); }); } return Promise.resolve(null); diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index d6e4ccece5..7be158d995 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -36,7 +36,8 @@ export class LabelContribution implements IWorkbenchContribution { formatting: { label: '${path}', separator: remoteEnvironment.os === OperatingSystem.Windows ? '\\' : '/', - tildify: remoteEnvironment.os !== OperatingSystem.Windows + tildify: remoteEnvironment.os !== OperatingSystem.Windows, + normalizeDriveLetter: remoteEnvironment.os === OperatingSystem.Windows } }); } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 21e422c2d1..b3f3fce8b0 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -179,10 +179,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private static nextHandle: number = 0; - private _schemaVersion: JsonSchemaVersion; - private _executionEngine: ExecutionEngine; - private _workspaceFolders: IWorkspaceFolder[]; - private _ignoredWorkspaceFolders: IWorkspaceFolder[]; + private _schemaVersion: JsonSchemaVersion | undefined; + private _executionEngine: ExecutionEngine | undefined; + private _workspaceFolders: IWorkspaceFolder[] | undefined; + private _ignoredWorkspaceFolders: IWorkspaceFolder[] | undefined; private _showIgnoreMessage?: boolean; private _providers: Map; private _providerTypes: Map; @@ -192,7 +192,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected _taskSystem?: ITaskSystem; protected _taskSystemListener?: IDisposable; - private _recentlyUsedTasks: LinkedMap; + private _recentlyUsedTasks: LinkedMap | undefined; protected _taskRunningState: IContextKey; @@ -375,28 +375,28 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!this._workspaceFolders) { this.updateSetup(); } - return this._workspaceFolders; + return this._workspaceFolders!; } private get ignoredWorkspaceFolders(): IWorkspaceFolder[] { if (!this._ignoredWorkspaceFolders) { this.updateSetup(); } - return this._ignoredWorkspaceFolders; + return this._ignoredWorkspaceFolders!; } protected get executionEngine(): ExecutionEngine { if (this._executionEngine === undefined) { this.updateSetup(); } - return this._executionEngine; + return this._executionEngine!; } private get schemaVersion(): JsonSchemaVersion { if (this._schemaVersion === undefined) { this.updateSetup(); } - return this._schemaVersion; + return this._schemaVersion!; } private get showIgnoreMessage(): boolean { diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index c06a81e08a..664a6a5caf 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -155,9 +155,10 @@ export class TerminalTaskSystem implements ITaskSystem { private idleTaskTerminals: LinkedMap; private sameTaskTerminals: IStringDictionary; private taskSystemInfoResolver: TaskSystemInfoResolver; - private lastTask: VerifiedTask; - private currentTask: VerifiedTask; - private isRerun: boolean; + private lastTask: VerifiedTask | undefined; + // Should always be set in run + private currentTask!: VerifiedTask; + private isRerun: boolean = false; private readonly _onDidStateChange: Emitter; @@ -485,28 +486,32 @@ export class TerminalTaskSystem implements ITaskSystem { } private reexecuteCommand(task: CustomTask | ContributedTask, trigger: string): Promise { - const workspaceFolder = this.currentTask.workspaceFolder = this.lastTask.workspaceFolder; + const lastTask = this.lastTask; + if (!lastTask) { + return Promise.reject(new Error('No task previously run')); + } + const workspaceFolder = this.currentTask.workspaceFolder = lastTask.workspaceFolder; let variables = new Set(); this.collectTaskVariables(variables, task); // Check that the task hasn't changed to include new variables let hasAllVariables = true; variables.forEach(value => { - if (value.substring(2, value.length - 1) in this.lastTask.getVerifiedTask().resolvedVariables) { + if (value.substring(2, value.length - 1) in lastTask.getVerifiedTask().resolvedVariables) { hasAllVariables = false; } }); if (!hasAllVariables) { - return this.resolveVariablesFromSet(this.lastTask.getVerifiedTask().systemInfo, this.lastTask.getVerifiedTask().workspaceFolder, task, variables).then((resolvedVariables) => { + return this.resolveVariablesFromSet(lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().workspaceFolder, task, variables).then((resolvedVariables) => { this.currentTask.resolvedVariables = resolvedVariables; - return this.executeInTerminal(task, trigger, new VariableResolver(this.lastTask.getVerifiedTask().workspaceFolder, this.lastTask.getVerifiedTask().systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder!); + return this.executeInTerminal(task, trigger, new VariableResolver(lastTask.getVerifiedTask().workspaceFolder, lastTask.getVerifiedTask().systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder!); }, reason => { return Promise.reject(reason); }); } else { - this.currentTask.resolvedVariables = this.lastTask.getVerifiedTask().resolvedVariables; - return this.executeInTerminal(task, trigger, new VariableResolver(this.lastTask.getVerifiedTask().workspaceFolder, this.lastTask.getVerifiedTask().systemInfo, this.lastTask.getVerifiedTask().resolvedVariables.variables, this.configurationResolverService), workspaceFolder!); + this.currentTask.resolvedVariables = lastTask.getVerifiedTask().resolvedVariables; + return this.executeInTerminal(task, trigger, new VariableResolver(lastTask.getVerifiedTask().workspaceFolder, lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().resolvedVariables.variables, this.configurationResolverService), workspaceFolder!); } } @@ -923,7 +928,7 @@ export class TerminalTaskSystem implements ITaskSystem { args = resolvedResult.args; commandExecutable = CommandString.value(command); - this.currentTask.shellLaunchConfig = launchConfigs = this.isRerun ? this.lastTask.getVerifiedTask().shellLaunchConfig : await this.createShellLaunchConfig(task, workspaceFolder, resolver, platform, options, command, args, waitOnExit); + this.currentTask.shellLaunchConfig = launchConfigs = (this.isRerun && this.lastTask) ? this.lastTask.getVerifiedTask().shellLaunchConfig : await this.createShellLaunchConfig(task, workspaceFolder, resolver, platform, options, command, args, waitOnExit); if (launchConfigs === undefined) { return [undefined, undefined, new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive using cmd.exe.'), TaskErrors.UnknownError)]; } diff --git a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts index 2398d450e2..cf1d3e370e 100644 --- a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts +++ b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts @@ -44,10 +44,10 @@ export abstract class AbstractProblemCollector implements IDisposable { private bufferLength: number; private openModels: IStringDictionary; private readonly modelListeners = new DisposableStore(); - private tail: Promise; + private tail: Promise | undefined; // [owner] -> ApplyToKind - private applyToByOwner: Map; + protected applyToByOwner: Map; // [owner] -> [resource] -> URI private resourcesToClean: Map>; // [owner] -> [resource] -> [markerkey] -> markerData @@ -278,9 +278,14 @@ export abstract class AbstractProblemCollector implements IDisposable { markersPerResource = new Map(); markersPerOwner.set(resourceAsString, markersPerResource); } - let key = IMarkerData.makeKey(marker); + let key = IMarkerData.makeKeyOptionalMessage(marker, false); + let existingMarker; if (!markersPerResource.has(key)) { markersPerResource.set(key, marker); + } else if (((existingMarker = markersPerResource.get(key)) !== undefined) && existingMarker.message.length < marker.message.length) { + // Most likely https://github.com/microsoft/vscode/issues/77475 + // Heuristic dictates that when the key is the same and message is smaller, we have hit this limitation. + markersPerResource.set(key, marker); } } @@ -344,8 +349,8 @@ export const enum ProblemHandlingStrategy { export class StartStopProblemCollector extends AbstractProblemCollector implements IProblemMatcher { private owners: string[]; - private currentOwner: string; - private currentResource: string; + private currentOwner: string | undefined; + private currentResource: string | undefined; constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService, _strategy: ProblemHandlingStrategy = ProblemHandlingStrategy.Clean, fileService?: IFileService) { super(problemMatchers, markerService, modelService, fileService); @@ -397,8 +402,8 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement private _activeBackgroundMatchers: Set; // Current State - private currentOwner: string | null; - private currentResource: string | null; + private currentOwner: string | undefined; + private currentResource: string | undefined; constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService, fileService?: IFileService) { super(problemMatchers, markerService, modelService, fileService); @@ -503,8 +508,8 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement private resetCurrentResource(): void { this.reportMarkersForCurrentResource(); - this.currentOwner = null; - this.currentResource = null; + this.currentOwner = undefined; + this.currentResource = undefined; } private reportMarkersForCurrentResource(): void { @@ -512,4 +517,11 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement this.deliverMarkersPerOwnerAndResource(this.currentOwner, this.currentResource); } } -} \ No newline at end of file + + public done(): void { + [...this.applyToByOwner.keys()].forEach(owner => { + this.recordResourcesToClean(owner); + }); + super.done(); + } +} diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index b17875dffb..e390f5af8c 100644 --- a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -264,7 +264,7 @@ abstract class AbstractLineMatcher implements ILineMatcher { public abstract get matchLength(): number; - protected fillProblemData(data: ProblemData | null, pattern: ProblemPattern, matches: RegExpExecArray): data is ProblemData { + protected fillProblemData(data: ProblemData | undefined, pattern: ProblemPattern, matches: RegExpExecArray): data is ProblemData { if (data) { this.fillProperty(data, 'file', pattern, matches, true); this.appendProperty(data, 'message', pattern, matches, true); @@ -449,7 +449,7 @@ class SingleLineMatcher extends AbstractLineMatcher { class MultiLineMatcher extends AbstractLineMatcher { private patterns: ProblemPattern[]; - private data: ProblemData | null; + private data: ProblemData | undefined; constructor(matcher: ProblemMatcher, fileService?: IFileService) { super(matcher, fileService); @@ -480,7 +480,7 @@ class MultiLineMatcher extends AbstractLineMatcher { } let loop = !!this.patterns[this.patterns.length - 1].loop; if (!loop) { - this.data = null; + this.data = undefined; } const markerMatch = data ? this.getMarkerMatch(data) : null; return { match: markerMatch ? markerMatch : null, continue: loop }; @@ -491,7 +491,7 @@ class MultiLineMatcher extends AbstractLineMatcher { Assert.ok(pattern.loop === true && this.data !== null); let matches = pattern.regexp.exec(line); if (!matches) { - this.data = null; + this.data = undefined; return null; } let data = Objects.deepClone(this.data); @@ -794,7 +794,7 @@ export namespace Config { fileLocation?: string | string[]; /** - * The name of a predefined problem pattern, the inline definintion + * The name of a predefined problem pattern, the inline definition * of a problem pattern or an array of problem patterns to match * problems spread over multiple lines. */ diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index d99691cffa..84b3d451bf 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -681,7 +681,7 @@ export namespace RunOptions { } } -class ParseContext { +interface ParseContext { workspaceFolder: IWorkspaceFolder; problemReporter: IProblemReporter; namedProblemMatchers: IStringDictionary; diff --git a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts index 2b604cd8c7..9cb242eaf0 100644 --- a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts +++ b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts @@ -89,7 +89,7 @@ class TaskDefinitionRegistryImpl implements ITaskDefinitionRegistry { private taskTypes: IStringDictionary; private readyPromise: Promise; - private _schema: IJSONSchema; + private _schema: IJSONSchema | undefined; constructor() { this.taskTypes = Object.create(null); diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 917fe50ade..8fe0f5723d 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -518,7 +518,7 @@ export abstract class CommonTask { /** * The cached label. */ - _label: string; + _label: string = ''; type?: string; @@ -614,7 +614,7 @@ export abstract class CommonTask { export class CustomTask extends CommonTask { - type: '$customized'; // CUSTOMIZED_TASK_TYPE + type!: '$customized'; // CUSTOMIZED_TASK_TYPE /** * Indicated the source of the task (e.g. tasks.json or extension) @@ -626,7 +626,7 @@ export class CustomTask extends CommonTask { /** * The command configuration */ - command: CommandConfiguration; + command: CommandConfiguration = {}; public constructor(id: string, source: WorkspaceTaskSource, label: string, type: string, command: CommandConfiguration | undefined, hasDefinedMatchers: boolean, runOptions: RunOptions, configurationProperties: ConfigurationProperties) { @@ -754,8 +754,9 @@ export class ContributedTask extends CommonTask { /** * Indicated the source of the task (e.g. tasks.json or extension) + * Set in the super constructor */ - _source: ExtensionTaskSource; + _source!: ExtensionTaskSource; defines: KeyedTaskIdentifier; @@ -824,7 +825,7 @@ export class InMemoryTask extends CommonTask { */ _source: InMemoryTaskSource; - type: 'inMemory'; + type!: 'inMemory'; public constructor(id: string, source: InMemoryTaskSource, label: string, type: string, runOptions: RunOptions, configurationProperties: ConfigurationProperties) { diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index 284e3bb593..b18168d511 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 { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } 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'; @@ -42,65 +42,21 @@ interface WatermarkEntry { } // {{SQL CARBON EDIT}} -const showServers: WatermarkEntry = { - text: nls.localize('watermark.showServers', "Show Servers"), - id: OpenDataExplorerViewletAction.ID -}; +const showServers: WatermarkEntry = { text: nls.localize('watermark.showServers', "Show Servers"), id: OpenDataExplorerViewletAction.ID }; +const newSqlFile: WatermarkEntry = { text: nls.localize('watermark.newSqlFile', "New SQL File"), id: GlobalNewUntitledFileAction.ID }; +const newNotebook: WatermarkEntry = { text: nls.localize('watermark.newNotebook', "New Notebook"), id: NewNotebookAction.ID }; -const newSqlFile: WatermarkEntry = { - text: nls.localize('watermark.newSqlFile', "New SQL File"), - id: GlobalNewUntitledFileAction.ID -}; -const newNotebook: WatermarkEntry = { - text: nls.localize('watermark.newNotebook', "New Notebook"), - id: NewNotebookAction.ID -}; - -const showCommands: WatermarkEntry = { - text: nls.localize('watermark.showCommands', "Show All Commands"), - id: ShowAllCommandsAction.ID -}; -const quickOpen: WatermarkEntry = { - text: nls.localize('watermark.quickOpen', "Go to File"), - id: QUICKOPEN_ACTION_ID -}; -const openFileNonMacOnly: WatermarkEntry = { - text: nls.localize('watermark.openFile', "Open File"), - id: OpenFileAction.ID, - mac: false -}; -const openFolderNonMacOnly: WatermarkEntry = { - text: nls.localize('watermark.openFolder', "Open Folder"), - id: OpenFolderAction.ID, - mac: false -}; -const openFileOrFolderMacOnly: WatermarkEntry = { - text: nls.localize('watermark.openFileFolder', "Open File or Folder"), - id: OpenFileFolderAction.ID, - mac: true -}; -const openRecent: WatermarkEntry = { - text: nls.localize('watermark.openRecent', "Open Recent"), - id: 'workbench.action.openRecent' -}; -const newUntitledFile: WatermarkEntry = { - text: nls.localize('watermark.newUntitledFile', "New Untitled File"), - id: GlobalNewUntitledFileAction.ID -}; +const showCommands: WatermarkEntry = { text: nls.localize('watermark.showCommands', "Show All Commands"), id: ShowAllCommandsAction.ID }; +const quickOpen: WatermarkEntry = { text: nls.localize('watermark.quickOpen', "Go to File"), id: QUICKOPEN_ACTION_ID }; +const openFileNonMacOnly: WatermarkEntry = { text: nls.localize('watermark.openFile', "Open File"), id: OpenFileAction.ID, mac: false }; +const openFolderNonMacOnly: WatermarkEntry = { text: nls.localize('watermark.openFolder', "Open Folder"), id: OpenFolderAction.ID, mac: false }; +const openFileOrFolderMacOnly: WatermarkEntry = { text: nls.localize('watermark.openFileFolder', "Open File or Folder"), id: OpenFileFolderAction.ID, mac: true }; +const openRecent: WatermarkEntry = { text: nls.localize('watermark.openRecent', "Open Recent"), id: 'workbench.action.openRecent' }; +const newUntitledFile: WatermarkEntry = { text: nls.localize('watermark.newUntitledFile', "New Untitled File"), id: GlobalNewUntitledFileAction.ID }; const newUntitledFileMacOnly: WatermarkEntry = assign({ mac: true }, newUntitledFile); -const toggleTerminal: WatermarkEntry = { - text: nls.localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), - id: TERMINAL_COMMAND_ID.TOGGLE -}; - -const findInFiles: WatermarkEntry = { - text: nls.localize('watermark.findInFiles', "Find in Files"), - id: FindInFilesActionId -}; -const startDebugging: WatermarkEntry = { - text: nls.localize('watermark.startDebugging', "Start Debugging"), - id: StartAction.ID -}; +const toggleTerminal: WatermarkEntry = { text: nls.localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: TERMINAL_COMMAND_ID.TOGGLE }; +const findInFiles: WatermarkEntry = { text: nls.localize('watermark.findInFiles', "Find in Files"), id: FindInFilesActionId }; +const startDebugging: WatermarkEntry = { text: nls.localize('watermark.startDebugging', "Start Debugging"), id: StartAction.ID }; // {{SQL CARBON EDIT}} - Replace noFolderEntries and folderEntries const noFolderEntries = [ @@ -121,13 +77,13 @@ const folderEntries = [ const WORKBENCH_TIPS_ENABLED_KEY = 'workbench.tips.enabled'; export class WatermarkContribution extends Disposable implements IWorkbenchContribution { - private watermark: HTMLElement; + private watermarkDisposable = this._register(new DisposableStore()); private enabled: boolean; private workbenchState: WorkbenchState; constructor( - @ILifecycleService lifecycleService: ILifecycleService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -135,13 +91,20 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService ) { super(); - this.workbenchState = contextService.getWorkbenchState(); - lifecycleService.onShutdown(this.dispose, this); + this.workbenchState = contextService.getWorkbenchState(); this.enabled = this.configurationService.getValue(WORKBENCH_TIPS_ENABLED_KEY); + + this.registerListeners(); + if (this.enabled) { this.create(); } + } + + private registerListeners(): void { + this.lifecycleService.onShutdown(this.dispose, this); + this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(WORKBENCH_TIPS_ENABLED_KEY)) { const enabled = this.configurationService.getValue(WORKBENCH_TIPS_ENABLED_KEY); @@ -155,6 +118,7 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr } } })); + this._register(this.contextService.onDidChangeWorkbenchState(e => { const previousWorkbenchState = this.workbenchState; this.workbenchState = this.contextService.getWorkbenchState(); @@ -175,6 +139,7 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr const selected = folder ? folderEntries : noFolderEntries .filter(entry => !('mac' in entry) || entry.mac === isMacintosh) .filter(entry => !!CommandsRegistry.getCommand(entry.id)); + const update = () => { dom.clearNode(box); selected.map(entry => { @@ -187,10 +152,14 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr dd.innerHTML = keybinding.element.outerHTML; }); }; + update(); + dom.prepend(container.firstElementChild as HTMLElement, this.watermark); - this._register(this.keybindingService.onDidUpdateKeybindings(update)); - this._register(this.editorGroupsService.onDidLayout(dimension => this.handleEditorPartSize(container, dimension))); + + this.watermarkDisposable.add(this.keybindingService.onDidUpdateKeybindings(update)); + this.watermarkDisposable.add(this.editorGroupsService.onDidLayout(dimension => this.handleEditorPartSize(container, dimension))); + this.handleEditorPartSize(container, this.editorGroupsService.contentDimension); } @@ -205,9 +174,11 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr private destroy(): void { if (this.watermark) { this.watermark.remove(); + const container = this.layoutService.getContainer(Parts.EDITOR_PART); container.classList.remove('has-watermark'); - this.dispose(); + + this.watermarkDisposable.clear(); } } diff --git a/src/vs/workbench/contrib/webview/common/portMapping.ts b/src/vs/workbench/contrib/webview/common/portMapping.ts index 60b401ad43..371837a259 100644 --- a/src/vs/workbench/contrib/webview/common/portMapping.ts +++ b/src/vs/workbench/contrib/webview/common/portMapping.ts @@ -47,16 +47,16 @@ export class WebviewPortMappingManager extends Disposable { if (this.extensionLocation && this.extensionLocation.scheme === REMOTE_HOST_SCHEME) { const tunnel = await this.getOrCreateTunnel(mapping.extensionHostPort); if (tunnel) { - return uri.with({ + return encodeURI(uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}`, - }).toString(); + }).toString(true)); } } if (mapping.webviewPort !== mapping.extensionHostPort) { - return uri.with({ + return encodeURI(uri.with({ authority: `${requestLocalHostInfo.address}:${mapping.extensionHostPort}` - }).toString(); + }).toString(true)); } } } @@ -84,4 +84,4 @@ export class WebviewPortMappingManager extends Disposable { } return tunnel; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts similarity index 100% rename from src/vs/workbench/electron-browser/main.contribution.ts rename to src/vs/workbench/electron-browser/desktop.contribution.ts diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/desktop.main.ts similarity index 100% rename from src/vs/workbench/electron-browser/main.ts rename to src/vs/workbench/electron-browser/desktop.main.ts diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 6ddf186f63..9e8e09f10b 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -18,6 +18,8 @@ import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/co 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'; +import { WebWorkerExtensionHostStarter } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter'; +import { URI } from 'vs/base/common/uri'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { @@ -62,6 +64,11 @@ export class ExtensionService extends AbstractExtensionService implements IExten const result: ExtensionHostProcessManager[] = []; const remoteAgentConnection = this._remoteAgentService.getConnection()!; + + const webHostProcessWorker = this._instantiationService.createInstance(WebWorkerExtensionHostStarter, true, Promise.resolve([]), URI.parse('empty:value')); //todo@joh + const webHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, webHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); + result.push(webHostProcessManager); + const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory); const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); result.push(remoteExtHostProcessManager); diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts new file mode 100644 index 0000000000..d3e2491589 --- /dev/null +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts @@ -0,0 +1,150 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DefaultWorkerFactory } from 'vs/base/worker/defaultWorkerFactory'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { createMessageOfType, MessageType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import * as platform from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; +import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions'; +import { IProductService } from 'vs/platform/product/common/product'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; + +export class WebWorkerExtensionHostStarter implements IExtensionHostStarter { + + private _toDispose = new DisposableStore(); + private _isTerminating: boolean = false; + private _protocol?: IMessagePassingProtocol; + + private readonly _onDidExit = new Emitter<[number, string | null]>(); + readonly onExit: Event<[number, string | null]> = this._onDidExit.event; + + constructor( + private readonly _autoStart: boolean, + private readonly _extensions: Promise, + private readonly _extensionHostLogsLocation: URI, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, + @ILabelService private readonly _labelService: ILabelService, + @ILogService private readonly _logService: ILogService, + @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @IProductService private readonly _productService: IProductService, + ) { + + } + + async start(): Promise { + + if (!this._protocol) { + + const emitter = new Emitter(); + const worker = new DefaultWorkerFactory('WorkerExtensionHost').create( + 'vs/workbench/services/extensions/worker/extensionHostWorker', data => { + if (data instanceof ArrayBuffer) { + emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength))); + } else { + console.warn('UNKNOWN data received', data); + this._onDidExit.fire([77, 'UNKNOWN data received']); + } + }, err => { + this._onDidExit.fire([81, err]); + console.error(err); + } + ); + + // keep for cleanup + this._toDispose.add(emitter); + this._toDispose.add(worker); + + const protocol: IMessagePassingProtocol = { + onMessage: emitter.event, + send: vsbuf => { + const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength); + worker.postMessage(data, [data]); + } + }; + + // extension host handshake happens below + // (1) <== wait for: Ready + // (2) ==> send: init data + // (3) <== wait for: Initialized + + await Event.toPromise(Event.filter(protocol.onMessage, msg => isMessageOfType(msg, MessageType.Ready))); + protocol.send(VSBuffer.fromString(JSON.stringify(await this._createExtHostInitData()))); + await Event.toPromise(Event.filter(protocol.onMessage, msg => isMessageOfType(msg, MessageType.Initialized))); + + this._protocol = protocol; + } + return this._protocol; + + } + + dispose(): void { + if (!this._protocol) { + this._toDispose.dispose(); + return; + } + if (this._isTerminating) { + return; + } + this._isTerminating = true; + this._protocol.send(createMessageOfType(MessageType.Terminate)); + setTimeout(() => this._toDispose.dispose(), 10 * 1000); + } + + getInspectPort(): number | undefined { + return undefined; + } + + private async _createExtHostInitData(): Promise { + const [telemetryInfo, extensionDescriptions] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._extensions]); + const workspace = this._contextService.getWorkspace(); + return { + commit: this._productService.commit, + version: this._productService.version, + vscodeVersion: this._productService.vscodeVersion, + parentPid: -1, + environment: { + isExtensionDevelopmentDebug: false, + appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined, + appSettingsHome: this._environmentService.appSettingsHome ? this._environmentService.appSettingsHome : undefined, + appName: this._productService.nameLong, + appUriScheme: this._productService.urlProtocol, + appLanguage: platform.language, + extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, + extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, + globalStorageHome: URI.parse('fake:globalStorageHome'), //todo@joh URI.file(this._environmentService.globalStorageHome), + userHome: URI.parse('fake:userHome'), //todo@joh URI.file(this._environmentService.userHome), + webviewResourceRoot: this._environmentService.webviewResourceRoot, + webviewCspSource: this._environmentService.webviewCspSource, + }, + workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : { + configuration: workspace.configuration || undefined, + id: workspace.id, + name: this._labelService.getWorkspaceLabel(workspace) + }, + resolvedExtensions: [], + hostExtensions: [], + extensions: extensionDescriptions, + telemetryInfo, + logLevel: this._logService.getLevel(), + logsLocation: this._extensionHostLogsLocation, + autoStart: this._autoStart, + remote: { + authority: this._environmentService.configuration.remoteAuthority, + isRemote: false + }, + }; + } +} diff --git a/src/vs/workbench/services/extensions/common/extensionHostMain.ts b/src/vs/workbench/services/extensions/common/extensionHostMain.ts index d93fa9b388..72c7a0ac42 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostMain.ts @@ -10,7 +10,6 @@ import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { IInitData, MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; @@ -35,10 +34,6 @@ export interface IConsolePatchFn { (mainThreadConsole: MainThreadConsoleShape): any; } -export interface ILogServiceFn { - (initData: IInitData): ILogService; -} - export class ExtensionHostMain { private _isTerminating: boolean; @@ -50,8 +45,6 @@ export class ExtensionHostMain { protocol: IMessagePassingProtocol, initData: IInitData, hostUtils: IHostUtils, - consolePatchFn: IConsolePatchFn, - logServiceFn: ILogServiceFn, uriTransformer: IURITransformer | null ) { this._isTerminating = false; @@ -61,27 +54,27 @@ export class ExtensionHostMain { // ensure URIs are transformed and revived initData = ExtensionHostMain._transform(initData, rpcProtocol); - // allow to patch console - consolePatchFn(rpcProtocol.getProxy(MainContext.MainThreadConsole)); - - // services - const extHostLogService = new ExtHostLogService(logServiceFn(initData), initData.logsLocation.fsPath); - this._disposables.add(extHostLogService); - // bootstrap services const services = new ServiceCollection(...getSingletonServiceDescriptors()); services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData }); services.set(IExtHostRpcService, new ExtHostRpcService(rpcProtocol)); - services.set(ILogService, extHostLogService); services.set(IURITransformerService, new URITransformerService(uriTransformer)); services.set(IHostUtils, hostUtils); const instaService: IInstantiationService = new InstantiationService(services, true); - extHostLogService.info('extension host started'); - extHostLogService.trace('initData', initData); + // todo@joh + // ugly self - inject + const logService = instaService.invokeFunction(accessor => accessor.get(ILogService)); + this._disposables.add(logService); - // todo@joh -> not soo nice... + logService.info('extension host started'); + logService.trace('initData', initData); + + // todo@joh + // ugly self - inject + // must call initialize *after* creating the extension service + // because `initialize` itself creates instances that depend on it this._extensionService = instaService.invokeFunction(accessor => accessor.get(IExtHostExtensionService)); this._extensionService.initialize(); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 801c3f802f..b345b439e2 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -354,6 +354,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } const result: ExtensionHostProcessManager[] = []; + const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation); const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, true, extHostProcessWorker, null, initialActivationEvents); result.push(extHostProcessManager); diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 80778382ef..a580361305 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -12,16 +12,14 @@ import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { PersistentProtocol, ProtocolConstants, createBufferedEvent } from 'vs/base/parts/ipc/common/ipc.net'; import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import product from 'vs/platform/product/node/product'; -import { IInitData, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; -import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/common/extensionHostMain'; +import { ExtensionHostMain, IExitFn } from 'vs/workbench/services/extensions/common/extensionHostMain'; import { VSBuffer } from 'vs/base/common/buffer'; -import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; 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/common/extHostExtensionService'; -import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import 'vs/workbench/api/node/extHost.services'; interface ParsedExtHostArgs { @@ -70,21 +68,6 @@ function patchProcess(allowExit: boolean) { }; } -// use IPC messages to forward console-calls -function patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void { - // The console is already patched to use `process.send()` - const nativeProcessSend = process.send!; - process.send = (...args: any[]) => { - if (args.length === 0 || !args[0] || args[0].type !== '__$console') { - return nativeProcessSend.apply(process, args); - } - - mainThreadConsole.$logExtensionHostMessage(args[0]); - }; -} - -const createLogService: ILogServiceFn = initData => new SpdLogService(ExtensionHostLogFileName, initData.logsLocation.fsPath, initData.logLevel); - interface IRendererConnection { protocol: IMessagePassingProtocol; initData: IInitData; @@ -206,7 +189,7 @@ async function createExtHostProtocol(): Promise { } function connectToRenderer(protocol: IMessagePassingProtocol): Promise { - return new Promise((c, e) => { + return new Promise((c) => { // Listen init data message const first = protocol.onMessage(raw => { @@ -335,8 +318,6 @@ export async function startExtensionHostProcess(): Promise { renderer.protocol, initData, hostUtils, - patchPatchedConsole, - createLogService, uriTransformer ); diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index ec0bec873a..1fa3ce99ce 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -17,11 +17,11 @@ import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorksp import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { ProxyAgent } from 'vscode-proxy-agent'; import { MainThreadTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { URI } from 'vs/base/common/uri'; import { promisify } from 'util'; +import { ILogService } from 'vs/platform/log/common/log'; interface ConnectionResult { proxy: string; @@ -34,7 +34,7 @@ export function connectProxyResolver( extHostWorkspace: IExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, extensionService: ExtHostExtensionService, - extHostLogService: ExtHostLogService, + extHostLogService: ILogService, mainThreadTelemetry: MainThreadTelemetryShape ) { const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry); @@ -47,7 +47,7 @@ const maxCacheEntries = 5000; // Cache can grow twice that much due to 'oldCache function setupProxyResolution( extHostWorkspace: IExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, - extHostLogService: ExtHostLogService, + extHostLogService: ILogService, mainThreadTelemetry: MainThreadTelemetryShape ) { const env = process.env; @@ -421,7 +421,7 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku }); } -function useSystemCertificates(extHostLogService: ExtHostLogService, useSystemCertificates: boolean, opts: http.RequestOptions, callback: () => void) { +function useSystemCertificates(extHostLogService: ILogService, useSystemCertificates: boolean, opts: http.RequestOptions, callback: () => void) { if (useSystemCertificates) { getCaCertificates(extHostLogService) .then(caCertificates => { @@ -443,7 +443,7 @@ function useSystemCertificates(extHostLogService: ExtHostLogService, useSystemCe } let _caCertificates: ReturnType | Promise; -async function getCaCertificates(extHostLogService: ExtHostLogService) { +async function getCaCertificates(extHostLogService: ILogService) { if (!_caCertificates) { _caCertificates = readCaCertificates() .then(res => res && res.certs.length ? res : undefined) @@ -469,24 +469,26 @@ async function readCaCertificates() { } async function readWindowsCaCertificates() { - const winCA = await import('vscode-windows-ca-certs'); + // Not using await to work around minifier bug (https://github.com/microsoft/vscode/issues/79044). + return import('vscode-windows-ca-certs') + .then(winCA => { + let ders: any[] = []; + const store = winCA(); + try { + let der: any; + while (der = store.next()) { + ders.push(der); + } + } finally { + store.done(); + } - let ders: any[] = []; - const store = winCA(); - try { - let der: any; - while (der = store.next()) { - ders.push(der); - } - } finally { - store.done(); - } - - const certs = new Set(ders.map(derToPem)); - return { - certs: Array.from(certs), - append: true - }; + const certs = new Set(ders.map(derToPem)); + return { + certs: Array.from(certs), + append: true + }; + }); } async function readMacCaCertificates() { diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts new file mode 100644 index 0000000000..e6e43bfda8 --- /dev/null +++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtHostOutputService, ExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; +import { IExtHostWorkspace, ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostDecorations, ExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; +import { IExtHostConfiguration, ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; +import { IExtHostCommands, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; +import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; +import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; +import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService'; + +// register singleton services +registerSingleton(ILogService, ExtHostLogService); +registerSingleton(IExtHostOutputService, ExtHostOutputService); +registerSingleton(IExtHostWorkspace, ExtHostWorkspace); +registerSingleton(IExtHostDecorations, ExtHostDecorations); +registerSingleton(IExtHostConfiguration, ExtHostConfiguration); +registerSingleton(IExtHostCommands, ExtHostCommands); +registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors); +registerSingleton(IExtHostStorage, ExtHostStorage); +registerSingleton(IExtHostExtensionService, ExtHostExtensionService); + +// register services that only throw errors +function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { + return class { + constructor() { + return new Proxy({}, { + get(target: any, prop: string | number) { + if (target[prop]) { + return target[prop]; + } + throw new Error(`Not Implemented: ${name}->${String(prop)}`); + } + }); + } + }; +} +registerSingleton(IExtHostTerminalService, class extends NotImplementedProxy(IExtHostTerminalService) { }); +registerSingleton(IExtHostTask, class extends NotImplementedProxy(IExtHostTask) { }); +registerSingleton(IExtHostDebugService, class extends NotImplementedProxy(IExtHostDebugService) { }); +registerSingleton(IExtHostSearch, class extends NotImplementedProxy(IExtHostSearch) { }); +registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { + whenReady = Promise.resolve(); +}); diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts new file mode 100644 index 0000000000..6906d62a21 --- /dev/null +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; +import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { Emitter } from 'vs/base/common/event'; +import { isMessageOfType, MessageType, createMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtensionHostMain } from 'vs/workbench/services/extensions/common/extensionHostMain'; +import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; +import 'vs/workbench/services/extensions/worker/extHost.services'; + +// worker-self +declare namespace self { + function close(): void; +} + +// do not allow extensions to call terminate +const nativeClose = self.close.bind(self); +self.close = () => console.trace('An extension called terminate and this was prevented'); +let onTerminate = nativeClose; + +const hostUtil = new class implements IHostUtils { + _serviceBrand: any; + exit(_code?: number | undefined): void { + nativeClose(); + } + async exists(_path: string): Promise { + return true; + } + async realpath(path: string): Promise { + return path; + } +}; + +//todo@joh do not allow extensions to call postMessage and other globals... + +class ExtensionWorker implements IRequestHandler { + + // worker-contract + readonly _requestHandlerBrand: any; + readonly onmessage: (data: any) => any; + + // protocol + readonly protocol: IMessagePassingProtocol; + + constructor(postMessage: (message: any, transfer?: Transferable[]) => any) { + + let emitter = new Emitter(); + let terminating = false; + + this.onmessage = data => { + if (!(data instanceof ArrayBuffer)) { + console.warn('UNKNOWN data received', data); + return; + } + + const msg = VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength)); + if (isMessageOfType(msg, MessageType.Terminate)) { + // handle terminate-message right here + terminating = true; + onTerminate(); + return; + } + + // emit non-terminate messages to the outside + emitter.fire(msg); + }; + + this.protocol = { + onMessage: emitter.event, + send: vsbuf => { + if (!terminating) { + const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength); + postMessage(data, [data]); + } + } + }; + } +} + +interface IRendererConnection { + protocol: IMessagePassingProtocol; + initData: IInitData; +} +function connectToRenderer(protocol: IMessagePassingProtocol): Promise { + return new Promise(resolve => { + const once = protocol.onMessage(raw => { + once.dispose(); + const initData = JSON.parse(raw.toString()); + protocol.send(createMessageOfType(MessageType.Initialized)); + resolve({ protocol, initData }); + }); + protocol.send(createMessageOfType(MessageType.Ready)); + }); +} + +export function create(postMessage: (message: any, transfer?: Transferable[]) => any): IRequestHandler { + const res = new ExtensionWorker(postMessage); + + connectToRenderer(res.protocol).then(data => { + + const extHostMain = new ExtensionHostMain( + data.protocol, + data.initData, + hostUtil, + null, + ); + + onTerminate = () => extHostMain.terminate(); + }); + + return res; +} diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 6e02c43b8f..f26571a796 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -21,8 +21,8 @@ import 'vs/workbench/workbench.common.main'; //#region --- workbench (desktop main) import 'sql/setup'; // {{SQL CARBON EDIT}} -import 'vs/workbench/electron-browser/main.contribution'; -import 'vs/workbench/electron-browser/main'; +import 'vs/workbench/electron-browser/desktop.contribution'; +import 'vs/workbench/electron-browser/desktop.main'; //#endregion @@ -33,7 +33,6 @@ import 'vs/workbench/services/textMate/electron-browser/textMateService'; import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService'; import 'vs/workbench/services/extensions/common/inactiveExtensionUrlHandler'; import 'vs/workbench/services/search/node/searchService'; -import 'vs/workbench/contrib/debug/electron-browser/extensionHostDebugService'; import 'vs/workbench/services/output/node/outputChannelModelService'; import 'vs/workbench/services/textfile/node/textFileService'; import 'vs/workbench/services/dialogs/electron-browser/dialogService'; @@ -49,7 +48,6 @@ import 'vs/workbench/services/configurationResolver/electron-browser/configurati import 'vs/workbench/services/extensionManagement/node/extensionManagementService'; import 'vs/workbench/services/accessibility/node/accessibilityService'; import 'vs/workbench/services/remote/node/tunnelService'; -import 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; import 'vs/workbench/services/backup/node/backupFileService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -214,6 +212,7 @@ import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; import 'vs/workbench/contrib/logs/electron-browser/logs.contribution'; // Stats +import 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; // Rapid Render Splash @@ -221,6 +220,7 @@ import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; // Debug // import 'vs/workbench/contrib/debug/node/debugHelperService'; {{SQL CARBON EDIT}} +import 'vs/workbench/contrib/debug/electron-browser/extensionHostDebugService'; // Webview import 'vs/workbench/contrib/webview/electron-browser/webview.contribution'; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 193d2fb2ba..0fc9ddb19f 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -48,6 +48,11 @@ export interface IWorkbenchConstructionOptions { * A factory for web sockets. */ webSocketFactory?: IWebSocketFactory; + + /** + * Experimental: Whether to enable the smoke test driver. + */ + driver?: boolean; } /** diff --git a/yarn.lock b/yarn.lock index 1026a4eed4..38deacd755 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10297,10 +10297,10 @@ xterm-addon-web-links@0.1.0-beta10: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta10.tgz#610fa9773a2a5ccd41c1c83ba0e2dd2c9eb66a23" integrity sha512-xfpjy0V6bB4BR44qIgZQPoCMVakxb65gMscPkHpO//QxvUxKzabV3dxOsIbeZRFkUGsWTFlvz2OoaBLoNtv5gg== -xterm@3.15.0-beta98: - version "3.15.0-beta98" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta98.tgz#37f37c35577422880e7ef673cc37f9d2a45dd40c" - integrity sha512-vZbg2LcRvoiJOgr1MyeLFM9mF4uib3BWUWDHyFc+vZ58CTuK0iczOvFXgk/ySo23ZLqwmHQSigLgmWvZ8J5G0Q== +xterm@3.15.0-beta99: + version "3.15.0-beta99" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta99.tgz#0010a7ea5d56cbb08a1e3a525b353c96a158e7a0" + integrity sha512-Vm0ZWToWwO4uk/28Kqvqt9L92h5EU2z4WR9I6xcQaPIBmkJPINIARU4LWQnvaOfgFhRbpwBMveTfh8/jM97lPg== y18n@^3.2.1: version "3.2.1"