From e5a93f37b1d8318bce296632fc87f3b8c3e7f3a6 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 28 Nov 2018 15:54:57 +0100 Subject: [PATCH 01/14] AsyncDataTree --- src/tsconfig.strictNullChecks.json | 4 +- .../ui/tree/{dataTree.ts => asyncDataTree.ts} | 94 +++++++++---------- .../contrib/referenceSearch/referencesTree.ts | 2 +- .../referenceSearch/referencesWidget.ts | 8 +- src/vs/platform/list/browser/listService.ts | 8 +- src/vs/workbench/electron-browser/commands.ts | 30 +++--- test/tree/public/index.html | 8 +- 7 files changed, 77 insertions(+), 77 deletions(-) rename src/vs/base/browser/ui/tree/{dataTree.ts => asyncDataTree.ts} (75%) diff --git a/src/tsconfig.strictNullChecks.json b/src/tsconfig.strictNullChecks.json index 05b17d45c7e..51484b3be16 100644 --- a/src/tsconfig.strictNullChecks.json +++ b/src/tsconfig.strictNullChecks.json @@ -58,7 +58,7 @@ "./vs/base/browser/ui/splitview/panelview.ts", "./vs/base/browser/ui/splitview/splitview.ts", "./vs/base/browser/ui/tree/abstractTree.ts", - "./vs/base/browser/ui/tree/dataTree.ts", + "./vs/base/browser/ui/tree/asyncDataTree.ts", "./vs/base/browser/ui/tree/indexTree.ts", "./vs/base/browser/ui/tree/indexTreeModel.ts", "./vs/base/browser/ui/tree/objectTree.ts", @@ -725,4 +725,4 @@ "exclude": [ "./typings/require-monaco.d.ts" ] -} +} \ No newline at end of file diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts similarity index 75% rename from src/vs/base/browser/ui/tree/dataTree.ts rename to src/vs/base/browser/ui/tree/asyncDataTree.ts index 5ed334b53f2..5ffdc63bf7b 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -19,46 +19,46 @@ export interface IDataSource> { getChildren(element: T | null): Thenable; } -enum DataTreeNodeState { +enum AsyncDataTreeNodeState { Uninitialized, Loaded, Loading, Slow } -interface IDataTreeNode> { +interface IAsyncDataTreeNode> { readonly element: T | null; - readonly parent: IDataTreeNode | null; - state: DataTreeNodeState; + readonly parent: IAsyncDataTreeNode | null; + state: AsyncDataTreeNodeState; } interface IDataTreeListTemplateData { templateData: T; } -class DataTreeNodeWrapper implements ITreeNode { +class AsyncDataTreeNodeWrapper implements ITreeNode { get element(): T { return this.node.element!.element!; } - get parent(): ITreeNode | undefined { return this.node.parent && new DataTreeNodeWrapper(this.node.parent); } - get children(): ITreeNode[] { return this.node.children.map(node => new DataTreeNodeWrapper(node)); } + get parent(): ITreeNode | undefined { return this.node.parent && new AsyncDataTreeNodeWrapper(this.node.parent); } + get children(): ITreeNode[] { return this.node.children.map(node => new AsyncDataTreeNodeWrapper(node)); } get depth(): number { return this.node.depth; } get collapsible(): boolean { return this.node.collapsible; } get collapsed(): boolean { return this.node.collapsed; } get visible(): boolean { return this.node.visible; } get filterData(): TFilterData | undefined { return this.node.filterData; } - constructor(private node: ITreeNode | null, TFilterData>) { } + constructor(private node: ITreeNode | null, TFilterData>) { } } -class DataTreeRenderer implements ITreeRenderer, TFilterData, IDataTreeListTemplateData> { +class DataTreeRenderer implements ITreeRenderer, TFilterData, IDataTreeListTemplateData> { readonly templateId: string; - private renderedNodes = new Map, IDataTreeListTemplateData>(); + private renderedNodes = new Map, IDataTreeListTemplateData>(); private disposables: IDisposable[] = []; constructor( private renderer: ITreeRenderer, - readonly onDidChangeTwistieState: Event> + readonly onDidChangeTwistieState: Event> ) { this.templateId = renderer.templateId; } @@ -68,17 +68,17 @@ class DataTreeRenderer implements ITreeRenderer, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { - this.renderer.renderElement(new DataTreeNodeWrapper(node), index, templateData.templateData); + renderElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { + this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData); } - renderTwistie(element: IDataTreeNode, twistieElement: HTMLElement): boolean { - toggleClass(twistieElement, 'loading', element.state === DataTreeNodeState.Slow); + renderTwistie(element: IAsyncDataTreeNode, twistieElement: HTMLElement): boolean { + toggleClass(twistieElement, 'loading', element.state === AsyncDataTreeNodeState.Slow); return false; } - disposeElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { - this.renderer.disposeElement(new DataTreeNodeWrapper(node), index, templateData.templateData); + disposeElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { + this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData); } disposeTemplate(templateData: IDataTreeListTemplateData): void { @@ -91,21 +91,21 @@ class DataTreeRenderer implements ITreeRenderer(e: ITreeEvent>): ITreeEvent { +function asTreeEvent(e: ITreeEvent>): ITreeEvent { return { browserEvent: e.browserEvent, elements: e.elements.map(e => e.element!) }; } -function asTreeMouseEvent(e: ITreeMouseEvent>): ITreeMouseEvent { +function asTreeMouseEvent(e: ITreeMouseEvent>): ITreeMouseEvent { return { browserEvent: e.browserEvent, element: e.element && e.element.element! }; } -function asTreeContextMenuEvent(e: ITreeContextMenuEvent>): ITreeContextMenuEvent { +function asTreeContextMenuEvent(e: ITreeContextMenuEvent>): ITreeContextMenuEvent { return { browserEvent: e.browserEvent, element: e.element && e.element.element!, @@ -123,14 +123,14 @@ export interface IChildrenResolutionEvent { readonly reason: ChildrenResolutionReason; } -export class DataTree, TFilterData = void> implements IDisposable { +export class AsyncDataTree, TFilterData = void> implements IDisposable { - private tree: ObjectTree, TFilterData>; - private root: IDataTreeNode; - private nodes = new Map>(); - private refreshPromises = new Map, Thenable>(); + private tree: ObjectTree, TFilterData>; + private root: IAsyncDataTreeNode; + private nodes = new Map>(); + private refreshPromises = new Map, Thenable>(); - private _onDidChangeNodeState = new Emitter>(); + private _onDidChangeNodeState = new Emitter>(); protected disposables: IDisposable[] = []; @@ -156,15 +156,15 @@ export class DataTree, TFilterData = void> implements private dataSource: IDataSource, options?: ITreeOptions ) { - const objectTreeDelegate = new ComposedTreeDelegate>(delegate); + const objectTreeDelegate = new ComposedTreeDelegate>(delegate); const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeState.event)); - const objectTreeOptions = createComposedTreeListOptions>(options); + const objectTreeOptions = createComposedTreeListOptions>(options); this.tree = new ObjectTree(container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions); this.root = { element: null, parent: null, - state: DataTreeNodeState.Uninitialized, + state: AsyncDataTreeNodeState.Uninitialized, }; this.nodes.set(null, this.root); @@ -213,7 +213,7 @@ export class DataTree, TFilterData = void> implements getNode(element: T | null): ITreeNode { const dataNode = this.getDataNode(element); const node = this.tree.getNode(dataNode === this.root ? null : dataNode); - return new DataTreeNodeWrapper(node); + return new AsyncDataTreeNodeWrapper(node); } collapse(element: T): boolean { @@ -227,7 +227,7 @@ export class DataTree, TFilterData = void> implements return Promise.resolve(false); } - if (node.element!.state === DataTreeNodeState.Uninitialized) { + if (node.element!.state === AsyncDataTreeNodeState.Uninitialized) { const result = this.refreshNode(node, ChildrenResolutionReason.Expand); this.tree.expand(node); return result.then(() => true); @@ -341,8 +341,8 @@ export class DataTree, TFilterData = void> implements // Implementation - private getDataNode(element: T | null): IDataTreeNode { - const node: IDataTreeNode = this.nodes.get(element); + private getDataNode(element: T | null): IAsyncDataTreeNode { + const node: IAsyncDataTreeNode = this.nodes.get(element); if (typeof node === 'undefined') { throw new Error(`Data tree node not found: ${element}`); @@ -351,7 +351,7 @@ export class DataTree, TFilterData = void> implements return node; } - private refreshNode(node: IDataTreeNode, reason: ChildrenResolutionReason): Thenable { + private refreshNode(node: IAsyncDataTreeNode, reason: ChildrenResolutionReason): Thenable { let result = this.refreshPromises.get(node); if (result) { @@ -363,36 +363,36 @@ export class DataTree, TFilterData = void> implements return always(result, () => this.refreshPromises.delete(node)); } - private doRefresh(node: IDataTreeNode, reason: ChildrenResolutionReason): Thenable { + private doRefresh(node: IAsyncDataTreeNode, reason: ChildrenResolutionReason): Thenable { const hasChildren = this.dataSource.hasChildren(node.element); if (!hasChildren) { this.setChildren(node === this.root ? null : node); return Promise.resolve(); } else { - node.state = DataTreeNodeState.Loading; + node.state = AsyncDataTreeNodeState.Loading; this._onDidChangeNodeState.fire(node); const slowTimeout = timeout(800); slowTimeout.then(() => { - node.state = DataTreeNodeState.Slow; + node.state = AsyncDataTreeNodeState.Slow; this._onDidChangeNodeState.fire(node); }, _ => null); return this.dataSource.getChildren(node.element) .then(children => { slowTimeout.cancel(); - node.state = DataTreeNodeState.Loaded; + node.state = AsyncDataTreeNodeState.Loaded; this._onDidChangeNodeState.fire(node); - const createTreeElement = (element: T): ITreeElement> => { + const createTreeElement = (element: T): ITreeElement> => { const collapsible = this.dataSource.hasChildren(element); return { element: { element: element, - state: DataTreeNodeState.Uninitialized, + state: AsyncDataTreeNodeState.Uninitialized, parent: node }, collapsible, @@ -400,12 +400,12 @@ export class DataTree, TFilterData = void> implements }; }; - const nodeChildren = children.map>>(createTreeElement); + const nodeChildren = children.map>>(createTreeElement); this.setChildren(node === this.root ? null : node, nodeChildren); this._onDidResolveChildren.fire({ element: node.element, reason }); }, err => { slowTimeout.cancel(); - node.state = DataTreeNodeState.Uninitialized; + node.state = AsyncDataTreeNodeState.Uninitialized; this._onDidChangeNodeState.fire(node); if (node !== this.root) { @@ -417,23 +417,23 @@ export class DataTree, TFilterData = void> implements } } - private _onDidChangeCollapseState(treeNode: ITreeNode, any>): void { - if (!treeNode.collapsed && treeNode.element.state === DataTreeNodeState.Uninitialized) { + private _onDidChangeCollapseState(treeNode: ITreeNode, any>): void { + if (!treeNode.collapsed && treeNode.element.state === AsyncDataTreeNodeState.Uninitialized) { this.refreshNode(treeNode.element, ChildrenResolutionReason.Expand); } } - private setChildren(element: IDataTreeNode | null, children?: ISequence>>): void { + private setChildren(element: IAsyncDataTreeNode | null, children?: ISequence>>): void { const insertedElements = new Set(); - const onDidCreateNode = (node: ITreeNode, TFilterData>) => { + const onDidCreateNode = (node: ITreeNode, TFilterData>) => { if (node.element.element) { insertedElements.add(node.element.element); this.nodes.set(node.element.element, node.element); } }; - const onDidDeleteNode = (node: ITreeNode, TFilterData>) => { + const onDidDeleteNode = (node: ITreeNode, TFilterData>) => { if (node.element.element) { if (!insertedElements.has(node.element.element)) { this.nodes.delete(node.element.element); diff --git a/src/vs/editor/contrib/referenceSearch/referencesTree.ts b/src/vs/editor/contrib/referenceSearch/referencesTree.ts index 7200367bc5b..febecc28958 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesTree.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesTree.ts @@ -5,7 +5,7 @@ import { ReferencesModel, FileReferences, OneReference } from './referencesModel'; -import { IDataSource } from 'vs/base/browser/ui/tree/dataTree'; +import { IDataSource } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index cb95293d778..5831c290152 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -26,7 +26,7 @@ import * as nls from 'vs/nls'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { WorkbenchDataTree } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { activeContrastBorder, contrastBorder, registerColor } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PeekViewWidget } from './peekViewWidget'; @@ -236,7 +236,7 @@ export class ReferenceWidget extends PeekViewWidget { private _onDidSelectReference = new Emitter(); private _treeDataSource: DataSource; - private _tree: WorkbenchDataTree; + private _tree: WorkbenchAsyncDataTree; private _treeContainer: HTMLElement; private _sash: VSash; private _preview: ICodeEditor; @@ -358,11 +358,11 @@ export class ReferenceWidget extends PeekViewWidget { this._treeDataSource = this._instantiationService.createInstance(DataSource); this._tree = this._instantiationService.createInstance( - WorkbenchDataTree, this._treeContainer, new Delegate(), + WorkbenchAsyncDataTree, this._treeContainer, new Delegate(), renderer as any, this._treeDataSource, treeOptions - ) as any as WorkbenchDataTree; + ) as any as WorkbenchAsyncDataTree; ctxReferenceWidgetSearchTreeFocused.bindTo(this._tree.contextKeyService); diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index d76afcc22e2..8fc73944ffe 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -35,9 +35,9 @@ import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { ITreeOptions as ITreeOptions2, ITreeEvent } from 'vs/base/browser/ui/tree/abstractTree'; import { ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; -import { DataTree, IDataSource } from 'vs/base/browser/ui/tree/dataTree'; +import { AsyncDataTree, IDataSource } from 'vs/base/browser/ui/tree/asyncDataTree'; -export type ListWidget = List | PagedList | ITree | ObjectTree | DataTree; +export type ListWidget = List | PagedList | ITree | ObjectTree | AsyncDataTree; export const IListService = createDecorator('listService'); @@ -581,7 +581,7 @@ export class TreeResourceNavigator2 extends Disposable { private readonly _openResource: Emitter> = new Emitter>(); readonly openResource: Event> = this._openResource.event; - constructor(private tree: WorkbenchObjectTree | WorkbenchDataTree, private options?: IResourceResultsNavigationOptions) { + constructor(private tree: WorkbenchObjectTree | WorkbenchAsyncDataTree, private options?: IResourceResultsNavigationOptions) { super(); this.registerListeners(); @@ -932,7 +932,7 @@ export class WorkbenchObjectTree, TFilterData = void> } } -export class WorkbenchDataTree, TFilterData = void> extends DataTree { +export class WorkbenchAsyncDataTree, TFilterData = void> extends AsyncDataTree { readonly contextKeyService: IContextKeyService; diff --git a/src/vs/workbench/electron-browser/commands.ts b/src/vs/workbench/electron-browser/commands.ts index c636b7740b3..d0c893ec3fe 100644 --- a/src/vs/workbench/electron-browser/commands.ts +++ b/src/vs/workbench/electron-browser/commands.ts @@ -23,7 +23,7 @@ import { URI } from 'vs/base/common/uri'; import { IDownloadService } from 'vs/platform/download/common/download'; import { generateUuid } from 'vs/base/common/uuid'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; -import { DataTree } from 'vs/base/browser/ui/tree/dataTree'; +import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; // --- List Commands @@ -59,7 +59,7 @@ export function registerCommands(): void { } // ObjectTree - else if (focused instanceof ObjectTree || focused instanceof DataTree) { + else if (focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const list = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); @@ -92,7 +92,7 @@ export function registerCommands(): void { handler: (accessor, arg2) => focusDown(accessor, arg2) }); - function expandMultiSelection(focused: List | PagedList | ITree | ObjectTree | DataTree, previousFocus: any): void { + function expandMultiSelection(focused: List | PagedList | ITree | ObjectTree | AsyncDataTree, previousFocus: any): void { // List if (focused instanceof List || focused instanceof PagedList) { @@ -108,7 +108,7 @@ export function registerCommands(): void { } // ObjectTree - else if (focused instanceof ObjectTree || focused instanceof DataTree) { + else if (focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const list = focused; const focus = list.getFocus() ? list.getFocus()[0] : void 0; @@ -145,7 +145,7 @@ export function registerCommands(): void { const focused = accessor.get(IListService).lastFocusedList; // List - if (focused instanceof List || focused instanceof PagedList || focused instanceof ObjectTree || focused instanceof DataTree) { + if (focused instanceof List || focused instanceof PagedList || focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const list = focused; // Focus down first @@ -189,7 +189,7 @@ export function registerCommands(): void { } // ObjectTree - else if (focused instanceof ObjectTree || focused instanceof DataTree) { + else if (focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const list = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); @@ -270,7 +270,7 @@ export function registerCommands(): void { // Tree only if (focused && !(focused instanceof List || focused instanceof PagedList)) { - if (focused instanceof ObjectTree || focused instanceof DataTree) { + if (focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const tree = focused; const focusedElements = tree.getFocus(); @@ -317,7 +317,7 @@ export function registerCommands(): void { // Tree only if (focused && !(focused instanceof List || focused instanceof PagedList)) { - if (focused instanceof ObjectTree || focused instanceof DataTree) { + if (focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const tree = focused; const focusedElements = tree.getFocus(); @@ -374,7 +374,7 @@ export function registerCommands(): void { } // ObjectTree - else if (focused instanceof ObjectTree || focused instanceof DataTree) { + else if (focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const list = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); @@ -412,7 +412,7 @@ export function registerCommands(): void { } // ObjectTree - else if (focused instanceof ObjectTree || focused instanceof DataTree) { + else if (focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const list = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); @@ -461,7 +461,7 @@ export function registerCommands(): void { } // ObjectTree - else if (focused instanceof ObjectTree || focused instanceof DataTree) { + else if (focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const list = focused; const first = list.getFirstElementChild(null); @@ -514,7 +514,7 @@ export function registerCommands(): void { } // ObjectTree - else if (focused instanceof ObjectTree || focused instanceof DataTree) { + else if (focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const list = focused; const last = list.getLastElementAncestor(); @@ -556,7 +556,7 @@ export function registerCommands(): void { } // ObjectTree - else if (focused instanceof ObjectTree || focused instanceof DataTree) { + else if (focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const list = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); list.setSelection(list.getFocus(), fakeKeyboardEvent); @@ -601,7 +601,7 @@ export function registerCommands(): void { // Tree only if (focused && !(focused instanceof List || focused instanceof PagedList)) { - if (focused instanceof ObjectTree || focused instanceof DataTree) { + if (focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const tree = focused; const focus = tree.getFocus(); @@ -642,7 +642,7 @@ export function registerCommands(): void { } // ObjectTree - else if (focused instanceof ObjectTree || focused instanceof DataTree) { + else if (focused instanceof ObjectTree || focused instanceof AsyncDataTree) { const list = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); diff --git a/test/tree/public/index.html b/test/tree/public/index.html index b8fc79f2d2d..2a419b85578 100644 --- a/test/tree/public/index.html +++ b/test/tree/public/index.html @@ -42,7 +42,7 @@ require.config({ baseUrl: '/static' }); - require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/dataTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { DataTree }, { TreeVisibility }, { iter }) => { + require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/asyncDataTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { AsyncDataTree }, { TreeVisibility }, { iter }) => { function createIndexTree(opts) { opts = opts || {}; @@ -98,7 +98,7 @@ return { tree, treeFilter }; } - function createDataTree() { + function createAsyncDataTree() { const delegate = { getHeight() { return 22; }, getTemplateId() { return 'template'; } @@ -163,7 +163,7 @@ } } - const tree = new DataTree(container, delegate, [renderer], dataSource, { filter: treeFilter }); + const tree = new AsyncDataTree(container, delegate, [renderer], dataSource, { filter: treeFilter }); return { tree, treeFilter }; } @@ -190,7 +190,7 @@ break; } case '?data': { - const { tree, treeFilter } = createDataTree(); + const { tree, treeFilter } = createAsyncDataTree(); collapseall.onclick = () => perf('collapse all', () => tree.collapseAll()); renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random())); From 04bd1cb2550ed9890b834af29a3514964903ade9 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 28 Nov 2018 16:03:41 +0100 Subject: [PATCH 02/14] tree: :lipstick: move types around --- src/vs/base/browser/ui/tree/abstractTree.ts | 20 +----------------- src/vs/base/browser/ui/tree/asyncDataTree.ts | 4 ++-- src/vs/base/browser/ui/tree/indexTree.ts | 4 ++-- src/vs/base/browser/ui/tree/indexTreeModel.ts | 8 +++++-- src/vs/base/browser/ui/tree/objectTree.ts | 4 ++-- .../base/browser/ui/tree/objectTreeModel.ts | 6 +++--- src/vs/base/browser/ui/tree/tree.ts | 21 +++++++++++++++++-- src/vs/platform/list/browser/listService.ts | 3 +-- .../markers/electron-browser/markersPanel.ts | 3 +-- 9 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 9cdde9f2631..4a89819e322 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -11,7 +11,7 @@ import { append, $, toggleClass } from 'vs/base/browser/dom'; import { Event, Relay, chain, mapEvent } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { ITreeModel, ITreeNode, ITreeRenderer, ITreeModelOptions } from 'vs/base/browser/ui/tree/tree'; +import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeOptions } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; export function createComposedTreeListOptions(options?: IListOptions): IListOptions | undefined { @@ -174,24 +174,6 @@ function isInputElement(e: HTMLElement): boolean { return e.tagName === 'INPUT' || e.tagName === 'TEXTAREA'; } -export interface ITreeOptions extends IListOptions, ITreeModelOptions { } - -export interface ITreeEvent { - elements: T[]; - browserEvent?: UIEvent; -} - -export interface ITreeMouseEvent { - browserEvent: MouseEvent; - element: T | null; -} - -export interface ITreeContextMenuEvent { - browserEvent: UIEvent; - element: T | null; - anchor: HTMLElement | { x: number; y: number; } | undefined; -} - function asTreeEvent(event: IListEvent>): ITreeEvent { return { elements: event.elements.map(node => node.element), diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 5ffdc63bf7b..9098a24e167 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITreeOptions, ComposedTreeDelegate, createComposedTreeListOptions, ITreeEvent, ITreeContextMenuEvent, ITreeMouseEvent } from 'vs/base/browser/ui/tree/abstractTree'; +import { ComposedTreeDelegate, createComposedTreeListOptions } from 'vs/base/browser/ui/tree/abstractTree'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeOptions } from 'vs/base/browser/ui/tree/tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Emitter, Event, mapEvent } from 'vs/base/common/event'; import { timeout, always } from 'vs/base/common/async'; diff --git a/src/vs/base/browser/ui/tree/indexTree.ts b/src/vs/base/browser/ui/tree/indexTree.ts index 02e9b4492bb..42b4a030fef 100644 --- a/src/vs/base/browser/ui/tree/indexTree.ts +++ b/src/vs/base/browser/ui/tree/indexTree.ts @@ -5,10 +5,10 @@ import 'vs/css!./media/tree'; import { Iterator, ISequence } from 'vs/base/common/iterator'; -import { AbstractTree, ITreeOptions } from 'vs/base/browser/ui/tree/abstractTree'; +import { AbstractTree } from 'vs/base/browser/ui/tree/abstractTree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel'; -import { ITreeElement, ITreeModel, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { ITreeElement, ITreeModel, ITreeNode, ITreeOptions } from 'vs/base/browser/ui/tree/tree'; export class IndexTree extends AbstractTree { diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index 57c8430c4d8..413ceae1bab 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -7,7 +7,7 @@ import { ISpliceable } from 'vs/base/common/sequence'; import { Iterator, ISequence } from 'vs/base/common/iterator'; import { Emitter, Event, EventBufferer } from 'vs/base/common/event'; import { tail2 } from 'vs/base/common/arrays'; -import { ITreeFilterDataResult, TreeVisibility, ITreeFilter, ITreeModel, ITreeNode, ITreeElement, ITreeModelOptions } from 'vs/base/browser/ui/tree/tree'; +import { ITreeFilterDataResult, TreeVisibility, ITreeFilter, ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree'; interface IMutableTreeNode extends ITreeNode { readonly parent: IMutableTreeNode | undefined; @@ -38,6 +38,10 @@ function getVisibleState(visibility: boolean | TreeVisibility): TreeVisibility { } } +export interface IIndexTreeModelOptions { + filter?: ITreeFilter; +} + export class IndexTreeModel implements ITreeModel { private root: IMutableTreeNode = { @@ -62,7 +66,7 @@ export class IndexTreeModel implements ITreeModel; - constructor(private list: ISpliceable>, options: ITreeModelOptions = {}) { + constructor(private list: ISpliceable>, options: IIndexTreeModelOptions = {}) { this.filter = options.filter; } diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index 0a8d0c6142c..b45f128893b 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { Iterator, ISequence } from 'vs/base/common/iterator'; -import { AbstractTree, ITreeOptions } from 'vs/base/browser/ui/tree/abstractTree'; +import { AbstractTree } from 'vs/base/browser/ui/tree/abstractTree'; import { ISpliceable } from 'vs/base/common/sequence'; -import { ITreeNode, ITreeModel, ITreeElement } from 'vs/base/browser/ui/tree/tree'; +import { ITreeNode, ITreeModel, ITreeElement, ITreeOptions } from 'vs/base/browser/ui/tree/tree'; import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; export class ObjectTree, TFilterData = void> extends AbstractTree { diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index db1ecf451ce..50130e3cfcf 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -5,9 +5,9 @@ import { ISpliceable } from 'vs/base/common/sequence'; import { Iterator, ISequence } from 'vs/base/common/iterator'; -import { IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel'; +import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel'; import { Event } from 'vs/base/common/event'; -import { ITreeModel, ITreeNode, ITreeElement, ITreeModelOptions } from 'vs/base/browser/ui/tree/tree'; +import { ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree'; export class ObjectTreeModel, TFilterData = void> implements ITreeModel { @@ -19,7 +19,7 @@ export class ObjectTreeModel, TFilterData = void> imp get size(): number { return this.nodes.size; } - constructor(list: ISpliceable>, options: ITreeModelOptions = {}) { + constructor(list: ISpliceable>, options: IIndexTreeModelOptions = {}) { this.model = new IndexTreeModel(list, options); this.onDidChangeCollapseState = this.model.onDidChangeCollapseState; this.onDidChangeRenderNodeCount = this.model.onDidChangeRenderNodeCount; diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 4b4a50df0d9..c59dc69e299 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -6,6 +6,7 @@ import { Event } from 'vs/base/common/event'; import { Iterator } from 'vs/base/common/iterator'; import { IListRenderer } from 'vs/base/browser/ui/list/list'; +import { IListOptions } from 'vs/base/browser/ui/list/listWidget'; export const enum TreeVisibility { @@ -111,6 +112,22 @@ export interface ITreeRenderer extends IListRende onDidChangeTwistieState?: Event; } -export interface ITreeModelOptions { +export interface ITreeOptions extends IListOptions { filter?: ITreeFilter; -} \ No newline at end of file +} + +export interface ITreeEvent { + elements: T[]; + browserEvent?: UIEvent; +} + +export interface ITreeMouseEvent { + browserEvent: MouseEvent; + element: T | null; +} + +export interface ITreeContextMenuEvent { + browserEvent: UIEvent; + element: T | null; + anchor: HTMLElement | { x: number; y: number; } | undefined; +} diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 8fc73944ffe..c61bb809bbd 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -33,8 +33,7 @@ import { attachInputBoxStyler, attachListStyler, computeStyles, defaultListStyle import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; -import { ITreeOptions as ITreeOptions2, ITreeEvent } from 'vs/base/browser/ui/tree/abstractTree'; -import { ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { ITreeOptions as ITreeOptions2, ITreeEvent, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { AsyncDataTree, IDataSource } from 'vs/base/browser/ui/tree/asyncDataTree'; export type ListWidget = List | PagedList | ITree | ObjectTree | AsyncDataTree; diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 2be484dbca8..6c950152f3d 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -25,7 +25,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Iterator } from 'vs/base/common/iterator'; -import { ITreeElement, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { ITreeElement, ITreeNode, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { debounceEvent, Relay, Event, Emitter } from 'vs/base/common/event'; import { WorkbenchObjectTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; @@ -33,7 +33,6 @@ import { IExpression, getEmptyExpression } from 'vs/base/common/glob'; import { mixin, deepClone } from 'vs/base/common/objects'; import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isAbsolute, join } from 'vs/base/common/paths'; -import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/abstractTree'; import { FilterData, FileResourceMarkersRenderer, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider } from 'vs/workbench/parts/markers/electron-browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Separator, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; From c083f2113a33150b668f1912c1f93fbd7c103ad9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 28 Nov 2018 16:22:21 +0100 Subject: [PATCH 03/14] Fix tests --- .../extensions/node/extensionsWorkbenchService.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 873434ebca7..1a6373d0b36 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -672,7 +672,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, install(extension: string | IExtension): Promise { if (typeof extension === 'string') { - return this.installWithProgress(this.extensionService.install(URI.file(extension)).then(extensionIdentifier => this.checkAndEnableDisabledDependencies(extensionIdentifier))); + return this.installWithProgress(() => this.extensionService.install(URI.file(extension)).then(extensionIdentifier => this.checkAndEnableDisabledDependencies(extensionIdentifier))); } if (!(extension instanceof Extension)) { @@ -691,7 +691,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, } return this.installWithProgress( - this.extensionService.installFromGallery(gallery) + () => this.extensionService.installFromGallery(gallery) .then(() => this.checkAndEnableDisabledDependencies(gallery.identifier)) , gallery.displayName); } @@ -736,7 +736,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, return null; } return this.installWithProgress( - this.extensionService.installFromGallery(gallery) + () => this.extensionService.installFromGallery(gallery) .then(() => this.ignoreAutoUpdate(gallery.identifier.id, version)) , gallery.displayName); }); @@ -760,12 +760,12 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, }, () => Promise.all(toReinstall.map(local => this.extensionService.reinstallFromGallery(local))).then(() => null)); } - private installWithProgress(installTask: Promise, extensionName?: string): Promise { + private installWithProgress(installTask: () => Promise, extensionName?: string): Promise { const title = extensionName ? nls.localize('installing named extension', "Installing '{0}' extension....", extensionName) : nls.localize('installing extension', 'Installing extension....'); return this.progressService.withProgress({ location: ProgressLocation.Extensions, title - }, () => installTask).then(() => null); + }, () => installTask()); } private checkAndEnableDisabledDependencies(extensionIdentifier: IExtensionIdentifier): Promise { From 37037eac8a15aeba535aec23c72287c5e9871585 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 28 Nov 2018 16:33:59 +0100 Subject: [PATCH 04/14] #23251 Expose global storage path in proposed API --- src/vs/vscode.proposed.d.ts | 14 ++++++++++++++ src/vs/workbench/api/node/extHost.protocol.ts | 1 + .../api/node/extHostExtensionActivator.ts | 1 + .../workbench/api/node/extHostExtensionService.ts | 9 +++++++-- .../extensions/electron-browser/extensionHost.ts | 3 ++- 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index cd6139652b3..9e749114312 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1147,4 +1147,18 @@ declare module 'vscode' { } //#endregion + //#region Extension Context + export interface ExtensionContext { + + /** + * An absolute file path in which the extension can store gloabal state. + * The directory might not exist on disk and creation is + * up to the extension. However, the parent directory is guaranteed to be existent. + * + * Use [`globalState`](#ExtensionContext.globalState) to store key value data. + */ + globalStoragePath: string; + + } + //#endregion } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 87c45e90ebd..6029a4eeedc 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -50,6 +50,7 @@ export interface IEnvironment { appSettingsHome: URI; extensionDevelopmentLocationURI: URI; extensionTestsPath: string; + globalStorageHome: string; } export interface IWorkspaceData { diff --git a/src/vs/workbench/api/node/extHostExtensionActivator.ts b/src/vs/workbench/api/node/extHostExtensionActivator.ts index 48475d388f8..e875258fcb7 100644 --- a/src/vs/workbench/api/node/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/node/extHostExtensionActivator.ts @@ -23,6 +23,7 @@ export interface IExtensionContext { globalState: IExtensionMemento; extensionPath: string; storagePath: string; + globalStoragePath: string; asAbsolutePath(relativePath: string): string; readonly logPath: string; } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index b3783a67413..45b80a26efd 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -91,13 +91,17 @@ class ExtensionStoragePath { return this._ready; } - value(extension: IExtensionDescription): string { + workspaceValue(extension: IExtensionDescription): string { if (this._value) { return join(this._value, extension.id); } return undefined; } + globalValue(extension: IExtensionDescription): string { + return join(this._environment.globalStorageHome, extension.id); + } + private async _getOrCreateWorkspaceStoragePath(): Promise { if (!this._workspace) { return Promise.resolve(undefined); @@ -383,7 +387,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { workspaceState, subscriptions: [], get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, - storagePath: this._storagePath.value(extensionDescription), + storagePath: this._storagePath.workspaceValue(extensionDescription), + globalStoragePath: this._storagePath.globalValue(extensionDescription), asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionLocation.fsPath, relativePath); }, logPath: that._extHostLogService.getLogDirectory(extensionDescription.id) }); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index c62c562a2ca..28a41e9029c 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -424,7 +424,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : void 0, appSettingsHome: this._environmentService.appSettingsHome ? URI.file(this._environmentService.appSettingsHome) : void 0, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, - extensionTestsPath: this._environmentService.extensionTestsPath + extensionTestsPath: this._environmentService.extensionTestsPath, + globalStorageHome: this._environmentService.globalStorageHome }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : { configuration: workspace.configuration, From e5094daf8cfb74a515cfc1bfb9087cf8d2e18c08 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 28 Nov 2018 15:34:02 +0100 Subject: [PATCH 05/14] don't resort extensions after profiling --- .../runtimeExtensionsEditor.ts | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts index 4227a53a04d..9903bb630f8 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -219,21 +219,17 @@ export class RuntimeExtensionsEditor extends BaseEditor { }; } - result = result.filter((element) => element.status.activationTimes); + result = result.filter(element => element.status.activationTimes); - if (this._profileInfo) { - // sort descending by time spent in the profiler - result = result.sort((a, b) => { - if (a.unresponsiveProfile === this._profileInfo && !b.unresponsiveProfile) { - return -1; - } else if (!a.unresponsiveProfile && b.unresponsiveProfile === this._profileInfo) { - return 1; - } else if (a.profileInfo.totalTime === b.profileInfo.totalTime) { - return a.originalIndex - b.originalIndex; - } - return b.profileInfo.totalTime - a.profileInfo.totalTime; - }); - } + // bubble up extensions that have caused slowness + result = result.sort((a, b) => { + if (a.unresponsiveProfile === this._profileInfo && !b.unresponsiveProfile) { + return -1; + } else if (!a.unresponsiveProfile && b.unresponsiveProfile === this._profileInfo) { + return 1; + } + return a.originalIndex - b.originalIndex; + }); return result; } From 701ccb5d9c74175a04a7dbf7a06a24bd7f6e6b58 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 28 Nov 2018 16:25:12 +0100 Subject: [PATCH 06/14] adjust wording, add report issue from silent perf notification --- .../extensionsAutoProfiler.ts | 125 +++++++++++------- .../runtimeExtensionsEditor.ts | 16 ++- 2 files changed, 87 insertions(+), 54 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsAutoProfiler.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsAutoProfiler.ts index dd2df4cf616..bf281f548fe 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsAutoProfiler.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsAutoProfiler.ts @@ -13,12 +13,13 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { tmpdir } from 'os'; import { join } from 'path'; import { writeFile } from 'vs/base/node/pfs'; -import { IExtensionHostProfileService } from 'vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor'; +import { IExtensionHostProfileService, ReportExtensionIssueAction } from 'vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { RuntimeExtensionsInput } from 'vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput'; import { generateUuid } from 'vs/base/common/uuid'; +import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchContribution { @@ -27,6 +28,7 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont constructor( @IExtensionService private _extensionService: IExtensionService, @IExtensionHostProfileService private readonly _extensionProfileService: IExtensionHostProfileService, + @IExtensionsWorkbenchService private readonly _anotherExtensionService: IExtensionsWorkbenchService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILogService private readonly _logService: ILogService, @INotificationService private readonly _notificationService: INotificationService, @@ -80,7 +82,7 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont } } - private _processCpuProfile(profile: IExtensionHostProfile) { + private async _processCpuProfile(profile: IExtensionHostProfile) { interface NamedSlice { id: string; @@ -108,7 +110,6 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont } data = data.slice(0, anchor + 1); - const id = generateUuid(); const duration = profile.endTime - profile.startTime; const percentage = duration / 100; let top: NamedSlice | undefined; @@ -123,55 +124,77 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont return; } - this._extensionService.getExtension(top.id).then(async extension => { - if (!extension) { - return; + const extension = await this._extensionService.getExtension(top.id); + if (!extension) { + // not an extension => idle, gc, self? + return; + } + + // add to running extensions view + this._extensionProfileService.setUnresponsiveProfile(extension.id, profile); + + // print message to log + const path = join(tmpdir(), `exthost-${Math.random().toString(16).slice(2, 8)}.cpuprofile`); + await writeFile(path, JSON.stringify(profile.data)); + this._logService.warn(`UNRESPONSIVE extension host, '${top.id}' took ${top!.percentage}% of ${duration / 1e3}ms, saved PROFILE here: '${path}'`, data); + + // send telemetry + const id = generateUuid(); + + /* __GDPR__ + "exthostunresponsive" : { + "id" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "data": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } } - - // add to running extensions view - this._extensionProfileService.setUnresponsiveProfile(extension.id, profile); - - // user-facing message when very bad... - const prompt = top.percentage >= 99 && top.total >= 5e6; - if (prompt) { - this._notificationService.prompt( - Severity.Info, - localize('unresponsive-exthost', "Extension '{0}' froze the extension host for more than {1} seconds.", extension.displayName || extension.name, Math.round(duration / 1e6)), - [{ - label: localize('show', 'Show Extensions'), - run: () => { - this._editorService.openEditor(new RuntimeExtensionsInput()); - /* __GDPR__ - "exthostunresponsive-more" : { - "id" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } - } - */ - this._telemetryService.publicLog('exthostunresponsive-more', { id }); - } - }], - { silent: true } - ); - } - - const path = join(tmpdir(), `exthost-${Math.random().toString(16).slice(2, 8)}.cpuprofile`); - await writeFile(path, JSON.stringify(profile.data)); - - this._logService.warn(`UNRESPONSIVE extension host, '${top.id}' took ${top!.percentage}% of ${duration / 1e3}ms, saved PROFILE here: '${path}'`, data); - - /* __GDPR__ - "exthostunresponsive" : { - "id" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "data": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } - "prompt" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - } - */ - this._telemetryService.publicLog('exthostunresponsive', { - id, - duration, - data, - prompt - }); + */ + this._telemetryService.publicLog('exthostunresponsive', { + id, + duration, + data, }); + + // prompt: when really slow/greedy + if (!(top.percentage >= 99 && top.total >= 5e6)) { + return; + } + + // prompt: only when you can file an issue + const reportAction = new ReportExtensionIssueAction({ + marketplaceInfo: this._anotherExtensionService.local.filter(value => value.id === extension.id)[0], + description: extension, + unresponsiveProfile: profile, + status: undefined, + }); + if (!reportAction.enabled) { + return; + } + + // user-facing message when very bad... + this._notificationService.prompt( + Severity.Info, + localize( + 'unresponsive-exthost', + "The extension '{0}' took a very long time to complete its last task and it has prevented other extensions from running.", + extension.displayName || extension.name + ), + [{ + label: localize('show', 'Show Extensions'), + run: () => this._editorService.openEditor(new RuntimeExtensionsInput()) + }, + { + label: localize('report', "Report Issue"), + run: () => { + /* __GDPR__ + "exthostunresponsive/report" : { + "id" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + } + */ + this._telemetryService.publicLog('exthostunresponsive/report', { id }); + return reportAction.run(); + } + }], + { silent: true } + ); } } diff --git a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts index 9903bb630f8..84463d6fcb9 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -463,14 +463,19 @@ export class ShowRuntimeExtensionsAction extends Action { } } -class ReportExtensionIssueAction extends Action { +export class ReportExtensionIssueAction extends Action { private static readonly _id = 'workbench.extensions.action.reportExtensionIssue'; private static _label = nls.localize('reportExtensionIssue', "Report Issue"); private readonly _url: string; - constructor(extension: IRuntimeExtension) { + constructor(extension: { + description: IExtensionDescription; + marketplaceInfo: IExtension; + status: IExtensionsStatus; + unresponsiveProfile?: IExtensionHostProfile + }) { super(ReportExtensionIssueAction._id, ReportExtensionIssueAction._label, 'extension-action report-issue'); this.enabled = extension.marketplaceInfo && extension.marketplaceInfo.type === LocalExtensionType.User @@ -484,7 +489,12 @@ class ReportExtensionIssueAction extends Action { return Promise.resolve(null); } - private static _generateNewIssueUrl(extension: IRuntimeExtension): string { + private static _generateNewIssueUrl(extension: { + description: IExtensionDescription; + marketplaceInfo: IExtension; + status: IExtensionsStatus; + unresponsiveProfile?: IExtensionHostProfile + }): string { let baseUrl = extension.marketplaceInfo && extension.marketplaceInfo.type === LocalExtensionType.User && extension.description.repository ? extension.description.repository.url : undefined; if (!!baseUrl) { baseUrl = `${baseUrl.indexOf('.git') !== -1 ? baseUrl.substr(0, baseUrl.length - 4) : baseUrl}/issues/new/`; From 0e0fbf9880c090bfc5bcb3cbccb453471f7ae077 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 28 Nov 2018 16:34:24 +0100 Subject: [PATCH 07/14] don't write profile too early, apply pii massage --- .../runtimeExtensionsEditor.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts index 84463d6fcb9..ee32ea2855a 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -469,6 +469,7 @@ export class ReportExtensionIssueAction extends Action { private static _label = nls.localize('reportExtensionIssue', "Report Issue"); private readonly _url: string; + private readonly _task: () => Promise; constructor(extension: { description: IExtensionDescription; @@ -481,12 +482,16 @@ export class ReportExtensionIssueAction extends Action { && extension.marketplaceInfo.type === LocalExtensionType.User && Boolean(extension.description.repository) && Boolean(extension.description.repository.url); - this._url = ReportExtensionIssueAction._generateNewIssueUrl(extension); + const { url, task } = ReportExtensionIssueAction._generateNewIssueUrl(extension); + this._url = url; + this._task = task; } - run(): Promise { + async run(): Promise { + if (this._task) { + await this._task(); + } window.open(this._url); - return Promise.resolve(null); } private static _generateNewIssueUrl(extension: { @@ -494,7 +499,9 @@ export class ReportExtensionIssueAction extends Action { marketplaceInfo: IExtension; status: IExtensionsStatus; unresponsiveProfile?: IExtensionHostProfile - }): string { + }): { url: string, task?: () => Promise } { + + let task: () => Promise; let baseUrl = extension.marketplaceInfo && extension.marketplaceInfo.type === LocalExtensionType.User && extension.description.repository ? extension.description.repository.url : undefined; if (!!baseUrl) { baseUrl = `${baseUrl.indexOf('.git') !== -1 ? baseUrl.substr(0, baseUrl.length - 4) : baseUrl}/issues/new/`; @@ -508,7 +515,11 @@ export class ReportExtensionIssueAction extends Action { // unresponsive extension host caused reason = 'Performance'; let path = join(os.homedir(), `${extension.description.id}-unresponsive.cpuprofile.txt`); - writeFile(path, JSON.stringify(extension.unresponsiveProfile.data)).catch(onUnexpectedError); + task = async () => { + const profiler = await import('v8-inspect-profiler'); + const data = profiler.rewriteAbsolutePaths({ profile: extension.unresponsiveProfile.data }, 'pii_removed'); + writeFile(path, JSON.stringify(data)).catch(onUnexpectedError); + }; message = `:warning: Make sure to **attach** this file from your *home*-directory: \`${path}\` :warning:`; } else { @@ -528,7 +539,10 @@ export class ReportExtensionIssueAction extends Action { - VSCode version: \`${pkg.version}\`\n\n${message}` ); - return `${baseUrl}${queryStringPrefix}body=${body}`; + return { + url: `${baseUrl}${queryStringPrefix}body=${body}`, + task + }; } } From d462d332a1b80fae15746e925e3463faa30b1a53 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 28 Nov 2018 16:27:44 +0100 Subject: [PATCH 08/14] tree options: collapse by default --- src/vs/base/browser/ui/tree/abstractTree.ts | 14 ++++++++----- src/vs/base/browser/ui/tree/asyncDataTree.ts | 16 ++++++++------- src/vs/base/browser/ui/tree/indexTree.ts | 20 ++++++++++++++++--- src/vs/base/browser/ui/tree/indexTreeModel.ts | 7 +++++-- src/vs/base/browser/ui/tree/objectTree.ts | 20 ++++++++++++++++--- .../base/browser/ui/tree/objectTreeModel.ts | 4 +++- src/vs/base/browser/ui/tree/tree.ts | 5 ----- src/vs/platform/list/browser/listService.ts | 10 +++++----- 8 files changed, 65 insertions(+), 31 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 4a89819e322..1bd1a933136 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -11,10 +11,10 @@ import { append, $, toggleClass } from 'vs/base/browser/dom'; import { Event, Relay, chain, mapEvent } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeOptions } from 'vs/base/browser/ui/tree/tree'; +import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; -export function createComposedTreeListOptions(options?: IListOptions): IListOptions | undefined { +export function createComposedTreeListOptions>(options?: IListOptions): O | undefined { if (!options) { return undefined; } @@ -60,7 +60,7 @@ export function createComposedTreeListOptions(optio identityProvider, multipleSelectionController, accessibilityProvider - }; + } as O; } export class ComposedTreeDelegate implements IListVirtualDelegate { @@ -196,6 +196,10 @@ function asTreeContextMenuEvent(event: IListContextMenuEvent extends IListOptions { + filter?: ITreeFilter; +} + export abstract class AbstractTree implements IDisposable { private view: List>; @@ -220,7 +224,7 @@ export abstract class AbstractTree implements IDisposable container: HTMLElement, delegate: IListVirtualDelegate, renderers: ITreeRenderer[], - options: ITreeOptions = {} + options: IAbstractTreeOptions = {} ) { const treeDelegate = new ComposedTreeDelegate>(delegate); @@ -480,7 +484,7 @@ export abstract class AbstractTree implements IDisposable this.model.toggleCollapsed(location); } - protected abstract createModel(view: ISpliceable>, options: ITreeOptions): ITreeModel; + protected abstract createModel(view: ISpliceable>, options: IAbstractTreeOptions): ITreeModel; dispose(): void { this.disposables = dispose(this.disposables); diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 9098a24e167..fb20e184c7e 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ComposedTreeDelegate, createComposedTreeListOptions } from 'vs/base/browser/ui/tree/abstractTree'; -import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; +import { ComposedTreeDelegate, createComposedTreeListOptions, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree'; +import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeOptions } from 'vs/base/browser/ui/tree/tree'; +import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Emitter, Event, mapEvent } from 'vs/base/common/event'; import { timeout, always } from 'vs/base/common/async'; @@ -123,6 +123,8 @@ export interface IChildrenResolutionEvent { readonly reason: ChildrenResolutionReason; } +export interface IAsyncDataTreeOptions extends IAbstractTreeOptions { } + export class AsyncDataTree, TFilterData = void> implements IDisposable { private tree: ObjectTree, TFilterData>; @@ -154,11 +156,12 @@ export class AsyncDataTree, TFilterData = void> imple delegate: IListVirtualDelegate, renderers: ITreeRenderer[], private dataSource: IDataSource, - options?: ITreeOptions + options?: IAsyncDataTreeOptions ) { const objectTreeDelegate = new ComposedTreeDelegate>(delegate); const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeState.event)); - const objectTreeOptions = createComposedTreeListOptions>(options); + const objectTreeOptions = createComposedTreeListOptions, IObjectTreeOptions, TFilterData>>(options); + objectTreeOptions.collapseByDefault = true; this.tree = new ObjectTree(container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions); this.root = { @@ -395,8 +398,7 @@ export class AsyncDataTree, TFilterData = void> imple state: AsyncDataTreeNodeState.Uninitialized, parent: node }, - collapsible, - collapsed: true + collapsible }; }; diff --git a/src/vs/base/browser/ui/tree/indexTree.ts b/src/vs/base/browser/ui/tree/indexTree.ts index 42b4a030fef..fa6a3ad5e9e 100644 --- a/src/vs/base/browser/ui/tree/indexTree.ts +++ b/src/vs/base/browser/ui/tree/indexTree.ts @@ -5,20 +5,34 @@ import 'vs/css!./media/tree'; import { Iterator, ISequence } from 'vs/base/common/iterator'; -import { AbstractTree } from 'vs/base/browser/ui/tree/abstractTree'; +import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel'; -import { ITreeElement, ITreeModel, ITreeNode, ITreeOptions } from 'vs/base/browser/ui/tree/tree'; +import { ITreeElement, ITreeModel, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; + +export interface IIndexTreeOptions extends IAbstractTreeOptions { + collapseByDefault?: boolean; // defaults to false +} export class IndexTree extends AbstractTree { protected model: IndexTreeModel; + constructor( + container: HTMLElement, + delegate: IListVirtualDelegate, + renderers: ITreeRenderer[], + options: IIndexTreeOptions = {} + ) { + super(container, delegate, renderers, options); + } + splice(location: number[], deleteCount: number, toInsert: ISequence> = Iterator.empty()): Iterator> { return this.model.splice(location, deleteCount, toInsert); } - protected createModel(view: ISpliceable>, options: ITreeOptions): ITreeModel { + protected createModel(view: ISpliceable>, options: IIndexTreeOptions): ITreeModel { return new IndexTreeModel(view, options); } } \ No newline at end of file diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index 413ceae1bab..d57e98846cb 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -39,6 +39,7 @@ function getVisibleState(visibility: boolean | TreeVisibility): TreeVisibility { } export interface IIndexTreeModelOptions { + collapseByDefault?: boolean; // defaults to false filter?: ITreeFilter; } @@ -64,9 +65,11 @@ export class IndexTreeModel implements ITreeModel>(); readonly onDidChangeRenderNodeCount: Event> = this.eventBufferer.wrapEvent(this._onDidChangeRenderNodeCount.event); + private collapseByDefault: boolean; private filter?: ITreeFilter; constructor(private list: ISpliceable>, options: IIndexTreeModelOptions = {}) { + this.collapseByDefault = typeof options.collapseByDefault === 'undefined' ? false : options.collapseByDefault; this.filter = options.filter; } @@ -194,8 +197,8 @@ export class IndexTreeModel implements ITreeModel extends IAbstractTreeOptions { + collapseByDefault?: boolean; // defaults to false +} export class ObjectTree, TFilterData = void> extends AbstractTree { protected model: ObjectTreeModel; + constructor( + container: HTMLElement, + delegate: IListVirtualDelegate, + renderers: ITreeRenderer[], + options: IObjectTreeOptions = {} + ) { + super(container, delegate, renderers, options); + } + setChildren( element: T | null, children?: ISequence>, @@ -22,7 +36,7 @@ export class ObjectTree, TFilterData = void> extends return this.model.setChildren(element, children, onDidCreateNode, onDidDeleteNode); } - protected createModel(view: ISpliceable>, options: ITreeOptions): ITreeModel { + protected createModel(view: ISpliceable>, options: IObjectTreeOptions): ITreeModel { return new ObjectTreeModel(view, options); } } \ No newline at end of file diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index 50130e3cfcf..673dd245000 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -9,6 +9,8 @@ import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/ import { Event } from 'vs/base/common/event'; import { ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree'; +export interface IObjectTreeModelOptions extends IIndexTreeModelOptions { } + export class ObjectTreeModel, TFilterData = void> implements ITreeModel { private model: IndexTreeModel; @@ -19,7 +21,7 @@ export class ObjectTreeModel, TFilterData = void> imp get size(): number { return this.nodes.size; } - constructor(list: ISpliceable>, options: IIndexTreeModelOptions = {}) { + constructor(list: ISpliceable>, options: IObjectTreeModelOptions = {}) { this.model = new IndexTreeModel(list, options); this.onDidChangeCollapseState = this.model.onDidChangeCollapseState; this.onDidChangeRenderNodeCount = this.model.onDidChangeRenderNodeCount; diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index c59dc69e299..e60d3794f3e 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -6,7 +6,6 @@ import { Event } from 'vs/base/common/event'; import { Iterator } from 'vs/base/common/iterator'; import { IListRenderer } from 'vs/base/browser/ui/list/list'; -import { IListOptions } from 'vs/base/browser/ui/list/listWidget'; export const enum TreeVisibility { @@ -112,10 +111,6 @@ export interface ITreeRenderer extends IListRende onDidChangeTwistieState?: Event; } -export interface ITreeOptions extends IListOptions { - filter?: ITreeFilter; -} - export interface ITreeEvent { elements: T[]; browserEvent?: UIEvent; diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index c61bb809bbd..e01c4c7611c 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -32,9 +32,9 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { attachInputBoxStyler, attachListStyler, computeStyles, defaultListStyles } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys'; -import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; -import { ITreeOptions as ITreeOptions2, ITreeEvent, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; -import { AsyncDataTree, IDataSource } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; +import { ITreeEvent, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { AsyncDataTree, IDataSource, IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree'; export type ListWidget = List | PagedList | ITree | ObjectTree | AsyncDataTree; @@ -881,7 +881,7 @@ export class WorkbenchObjectTree, TFilterData = void> container: HTMLElement, delegate: IListVirtualDelegate, renderers: ITreeRenderer[], - options: ITreeOptions2, + options: IObjectTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @@ -944,7 +944,7 @@ export class WorkbenchAsyncDataTree, TFilterData = vo delegate: IListVirtualDelegate, renderers: ITreeRenderer[], dataSource: IDataSource, - options: ITreeOptions2, + options: IAsyncDataTreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, From deaaa5fc7594f4aae25472159350b2d68c801ea6 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 28 Nov 2018 16:29:06 +0100 Subject: [PATCH 09/14] fix strict null checks --- src/vs/base/browser/ui/tree/asyncDataTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index fb20e184c7e..d93c5ce6c9f 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -160,7 +160,7 @@ export class AsyncDataTree, TFilterData = void> imple ) { const objectTreeDelegate = new ComposedTreeDelegate>(delegate); const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeState.event)); - const objectTreeOptions = createComposedTreeListOptions, IObjectTreeOptions, TFilterData>>(options); + const objectTreeOptions = createComposedTreeListOptions, IObjectTreeOptions, TFilterData>>(options) || {}; objectTreeOptions.collapseByDefault = true; this.tree = new ObjectTree(container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions); From 8cd83722d5a06ffaf8ad95495b7abeb78529aa6a Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 28 Nov 2018 16:50:58 +0100 Subject: [PATCH 10/14] async data tree: proper options mapping --- src/vs/base/browser/ui/tree/abstractTree.ts | 12 ++-- src/vs/base/browser/ui/tree/asyncDataTree.ts | 72 ++++++++++++++++++-- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 1bd1a933136..39a7a0a2cd1 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -14,12 +14,12 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; -export function createComposedTreeListOptions>(options?: IListOptions): O | undefined { +function asListOptions(options?: IAbstractTreeOptions): IListOptions> | undefined { if (!options) { return undefined; } - let identityProvider: IIdentityProvider | undefined = undefined; + let identityProvider: IIdentityProvider> | undefined = undefined; if (options.identityProvider) { const ip = options.identityProvider; @@ -30,7 +30,7 @@ export function createComposedTreeListOptions | undefined = undefined; + let multipleSelectionController: IMultipleSelectionController> | undefined = undefined; if (options.multipleSelectionController) { const msc = options.multipleSelectionController; @@ -44,7 +44,7 @@ export function createComposedTreeListOptions | undefined = undefined; + let accessibilityProvider: IAccessibilityProvider> | undefined = undefined; if (options.accessibilityProvider) { const ap = options.accessibilityProvider; @@ -60,7 +60,7 @@ export function createComposedTreeListOptions implements IListVirtualDelegate { @@ -232,7 +232,7 @@ export abstract class AbstractTree implements IDisposable const treeRenderers = renderers.map(r => new TreeRenderer(r, onDidChangeCollapseStateRelay.event)); this.disposables.push(...treeRenderers); - this.view = new List(container, treeDelegate, treeRenderers, createComposedTreeListOptions(options)); + this.view = new List(container, treeDelegate, treeRenderers, asListOptions(options)); this.model = this.createModel(this.view, options); onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState; diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index d93c5ce6c9f..63a01d1b0ac 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -3,15 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ComposedTreeDelegate, createComposedTreeListOptions, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree'; +import { ComposedTreeDelegate, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree'; import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; -import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; +import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter } from 'vs/base/browser/ui/tree/tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Emitter, Event, mapEvent } from 'vs/base/common/event'; import { timeout, always } from 'vs/base/common/async'; import { ISequence } from 'vs/base/common/iterator'; -import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; +import { IListStyles, IMultipleSelectionController, IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { toggleClass } from 'vs/base/browser/dom'; export interface IDataSource> { @@ -125,6 +125,68 @@ export interface IChildrenResolutionEvent { export interface IAsyncDataTreeOptions extends IAbstractTreeOptions { } +function asObjectTreeOptions(options?: IAsyncDataTreeOptions): IObjectTreeOptions, TFilterData> | undefined { + if (!options) { + return undefined; + } + + let identityProvider: IIdentityProvider> | undefined = undefined; + + if (options.identityProvider) { + const ip = options.identityProvider; + identityProvider = { + getId(el) { + return ip.getId(el.element!); + } + }; + } + + let multipleSelectionController: IMultipleSelectionController> | undefined = undefined; + + if (options.multipleSelectionController) { + const msc = options.multipleSelectionController; + multipleSelectionController = { + isSelectionSingleChangeEvent(e) { + return msc.isSelectionSingleChangeEvent({ ...e, element: e.element } as any); + }, + isSelectionRangeChangeEvent(e) { + return msc.isSelectionRangeChangeEvent({ ...e, element: e.element } as any); + } + }; + } + + let accessibilityProvider: IAccessibilityProvider> | undefined = undefined; + + if (options.accessibilityProvider) { + const ap = options.accessibilityProvider; + accessibilityProvider = { + getAriaLabel(e) { + return ap.getAriaLabel(e.element!); + } + }; + } + + let filter: ITreeFilter, TFilterData> | undefined = undefined; + + if (options.filter) { + const f = options.filter; + filter = { + filter(element, parentVisibility) { + return f.filter(element.element!, parentVisibility); + } + }; + } + + return { + ...options, + identityProvider, + multipleSelectionController, + accessibilityProvider, + filter + }; +} + + export class AsyncDataTree, TFilterData = void> implements IDisposable { private tree: ObjectTree, TFilterData>; @@ -160,7 +222,7 @@ export class AsyncDataTree, TFilterData = void> imple ) { const objectTreeDelegate = new ComposedTreeDelegate>(delegate); const objectTreeRenderers = renderers.map(r => new DataTreeRenderer(r, this._onDidChangeNodeState.event)); - const objectTreeOptions = createComposedTreeListOptions, IObjectTreeOptions, TFilterData>>(options) || {}; + const objectTreeOptions = asObjectTreeOptions(options) || {}; objectTreeOptions.collapseByDefault = true; this.tree = new ObjectTree(container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions); From c8d9db0a26b8d06f2f19c7e1c056408c77feb571 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 28 Nov 2018 17:22:15 +0100 Subject: [PATCH 11/14] Fix #62370 --- .../common/services/modelServiceImpl.ts | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index f5b8c26b983..2fd6b08b644 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -28,7 +28,6 @@ import { overviewRulerError, overviewRulerInfo, overviewRulerWarning } from 'vs/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IMarker, IMarkerService, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers'; import { ThemeColor, themeColorFromId } from 'vs/platform/theme/common/themeService'; -import { localize } from 'vs/nls'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -199,8 +198,16 @@ class ModelMarkerHandler { // Hence, escape all input strings hoverMessage.sanitize = false; - hoverMessage.appendMarkdown(`
`); - hoverMessage.appendMarkdown(`${escape(message.trim())}`); + hoverMessage.appendMarkdown(`
`); + hoverMessage.appendMarkdown(`${escape(message.trim())}`); + if (source) { + hoverMessage.appendMarkdown(`${escape(source)}`); + if (code) { + hoverMessage.appendMarkdown(`(${escape(code)})`); + } + } else if (code) { + hoverMessage.appendMarkdown(`(${escape(code)})`); + } hoverMessage.appendMarkdown(`
`); if (isNonEmptyArray(relatedInformation)) { @@ -213,20 +220,6 @@ class ModelMarkerHandler { } hoverMessage.appendMarkdown(``); } - - if (source || code) { - hoverMessage.appendMarkdown(`
`); - if (source) { - hoverMessage.appendMarkdown(`${localize('source', "Source")}:${escape(source)}`); - if (code) { - hoverMessage.appendMarkdown(`,`); - } - } - if (code) { - hoverMessage.appendMarkdown(`${localize('code', "Code")}:${escape(code)}`); - } - hoverMessage.appendMarkdown(`
`); - } } return { From 8bcb65bb7f33f42710929b6eb2f3d89979cc8fe7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 28 Nov 2018 17:29:05 +0100 Subject: [PATCH 12/14] #61312 Revert to proposed --- src/vs/vscode.d.ts | 5 ----- src/vs/vscode.proposed.d.ts | 9 +++++++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 32e34faf85e..592f8a035c7 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6580,11 +6580,6 @@ declare module 'vscode' { */ readonly onDidChangeVisibility: Event; - /** - * An optional human-readable message that will be rendered in the view. - */ - message?: string | MarkdownString; - /** * Reveals the given element in the tree view. * If the tree view is not visible then the tree view is shown and element is revealed. diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 9e749114312..8a585190a9e 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1090,6 +1090,15 @@ declare module 'vscode' { //#region Tree View + export interface TreeView { + + /** + * An optional human-readable message that will be rendered in the view. + */ + message?: string | MarkdownString; + + } + /** * Label describing the [Tree item](#TreeItem) */ From 067f46af2aea9affe459e26332a07e9f621c5bbe Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 28 Nov 2018 17:42:44 +0100 Subject: [PATCH 13/14] Support for automatic tasks (#63695) Fixes #17147 --- .../parts/tasks/common/taskService.ts | 15 ++++++- src/vs/workbench/parts/tasks/common/tasks.ts | 21 +++------ .../tasks/electron-browser/jsonSchema_v2.ts | 18 +++++--- .../electron-browser/runAutomaticTasks.ts | 41 +++++++++++++++++ .../electron-browser/task.contribution.ts | 23 ++++------ .../parts/tasks/node/taskConfiguration.ts | 44 ++++++++++++++++++- 6 files changed, 122 insertions(+), 40 deletions(-) create mode 100644 src/vs/workbench/parts/tasks/electron-browser/runAutomaticTasks.ts diff --git a/src/vs/workbench/parts/tasks/common/taskService.ts b/src/vs/workbench/parts/tasks/common/taskService.ts index 52b4996ee4e..f8572a0a16f 100644 --- a/src/vs/workbench/parts/tasks/common/taskService.ts +++ b/src/vs/workbench/parts/tasks/common/taskService.ts @@ -11,7 +11,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IDisposable } from 'vs/base/common/lifecycle'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { Task, ContributedTask, CustomTask, TaskSet, TaskSorter, TaskEvent, TaskIdentifier } from 'vs/workbench/parts/tasks/common/tasks'; +import { Task, ContributedTask, CustomTask, TaskSet, TaskSorter, TaskEvent, TaskIdentifier, ConfiguringTask } from 'vs/workbench/parts/tasks/common/tasks'; import { ITaskSummary, TaskTerminateResponse, TaskSystemInfo } from 'vs/workbench/parts/tasks/common/taskSystem'; import { IStringDictionary } from 'vs/base/common/collections'; @@ -38,6 +38,18 @@ export interface TaskFilter { type?: string; } +interface WorkspaceTaskResult { + set: TaskSet; + configurations: { + byIdentifier: IStringDictionary; + }; + hasErrors: boolean; +} + +export interface WorkspaceFolderTaskResult extends WorkspaceTaskResult { + workspaceFolder: IWorkspaceFolder; +} + export interface ITaskService { _serviceBrand: any; onDidStateChange: Event; @@ -54,6 +66,7 @@ export interface ITaskService { terminate(task: Task): TPromise; terminateAll(): TPromise; tasks(filter?: TaskFilter): TPromise; + getWorkspaceTasks(): TPromise>; /** * @param alias The task's name, label or defined identifier. */ diff --git a/src/vs/workbench/parts/tasks/common/tasks.ts b/src/vs/workbench/parts/tasks/common/tasks.ts index 000ba0e110f..419e2953f0e 100644 --- a/src/vs/workbench/parts/tasks/common/tasks.ts +++ b/src/vs/workbench/parts/tasks/common/tasks.ts @@ -176,21 +176,6 @@ export namespace PanelKind { } } -export namespace RerunBehavior { - export function fromString(value: string | undefined): RerunBehavior { - if (!value) { - return RerunBehavior.reevaluate; - } - switch (value.toLowerCase()) { - case 'useevaluated': - return RerunBehavior.useEvaluated; - case 'reevaulate': - default: - return RerunBehavior.reevaluate; - } - } -} - export interface PresentationOptions { /** * Controls whether the task output is reveal in the user interface. @@ -441,8 +426,14 @@ export enum RerunBehavior { useEvaluated = 2, } +export enum RunOnOptions { + default = 1, + folderOpen = 2 +} + export interface RunOptions { rerunBehavior?: RerunBehavior; + runOn?: RunOnOptions; } export interface CommonTask { diff --git a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts b/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts index fdbd984c73c..fac4e684be5 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts @@ -281,16 +281,20 @@ const identifier: IJSONSchema = { deprecationMessage: nls.localize('JsonSchema.tasks.identifier.deprecated', 'User defined identifiers are deprecated. For custom task use the name as a reference and for tasks provided by extensions use their defined task identifier.') }; -const rerunBehavior: IJSONSchema = { - type: 'string', - enum: ['reevauate', 'useEvaluated'], - description: nls.localize('JsonSchema.tasks.rerunBehavior', 'The task\'s behavior on rerun') -}; - const runOptions: IJSONSchema = { type: 'object', properties: { - rerunBehavior: Objects.deepClone(rerunBehavior), + rerunBehavior: { + type: 'string', + enum: ['reevauate', 'useEvaluated'], + description: nls.localize('JsonSchema.tasks.rerunBehavior', 'The task\'s behavior on rerun') + }, + runOn: { + type: 'string', + enum: ['default', 'folderOpen'], + description: nls.localize('JsonSchema.tasks.runOn', 'Configures when the task should be run. If set to folderOpen, then the task will be run automatically when the folder is opened.'), + default: 'default' + }, }, description: nls.localize('JsonSchema.tasks.runOptions', 'The task\'s run related options') }; diff --git a/src/vs/workbench/parts/tasks/electron-browser/runAutomaticTasks.ts b/src/vs/workbench/parts/tasks/electron-browser/runAutomaticTasks.ts new file mode 100644 index 00000000000..d639ebc506a --- /dev/null +++ b/src/vs/workbench/parts/tasks/electron-browser/runAutomaticTasks.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService'; +import { forEach } from 'vs/base/common/collections'; +import { RunOnOptions } from 'vs/workbench/parts/tasks/common/tasks'; + +export class RunAutomaticTasks extends Disposable implements IWorkbenchContribution { + constructor( + @ITaskService taskService: ITaskService) { + super(); + + taskService.getWorkspaceTasks().then(workspaceTaskResult => { + if (workspaceTaskResult) { + workspaceTaskResult.forEach(resultElement => { + resultElement.set.tasks.forEach(task => { + if (task.runOptions.runOn === RunOnOptions.folderOpen) { + taskService.run(task); + } + }); + if (resultElement.configurations) { + forEach(resultElement.configurations.byIdentifier, (configedTask) => { + if (configedTask.value.runOptions.runOn === RunOnOptions.folderOpen) { + taskService.getTask(resultElement.workspaceFolder, configedTask.value._id, true).then(task => { + if (task) { + taskService.run(task); + } + }); + } + }); + } + }); + } + }); + } + +} \ No newline at end of file diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index bcee66cb830..e2b77e20e40 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -29,7 +29,7 @@ import { LinkedMap, Touch } from 'vs/base/common/map'; import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; @@ -76,7 +76,7 @@ import { TaskEventKind, TaskSet, TaskGroup, GroupType, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, TaskSorter, TaskIdentifier, KeyedTaskIdentifier, TASK_RUNNING_STATE, RerunBehavior } from 'vs/workbench/parts/tasks/common/tasks'; -import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter } from 'vs/workbench/parts/tasks/common/taskService'; +import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult } from 'vs/workbench/parts/tasks/common/taskService'; import { getTemplates as getTaskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates'; import { KeyedTaskIdentifier as NKeyedTaskIdentifier, TaskDefinition } from 'vs/workbench/parts/tasks/node/tasks'; @@ -93,9 +93,14 @@ import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/ import { TaskDefinitionRegistry } from 'vs/workbench/parts/tasks/common/taskDefinitionRegistry'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { RunAutomaticTasks } from 'vs/workbench/parts/tasks/electron-browser/runAutomaticTasks'; let tasksCategory = nls.localize('tasksCategory', "Tasks"); +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); + namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; export const TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task"); @@ -370,18 +375,6 @@ class ProblemReporter implements TaskConfig.IProblemReporter { } } -interface WorkspaceTaskResult { - set: TaskSet; - configurations: { - byIdentifier: IStringDictionary; - }; - hasErrors: boolean; -} - -interface WorkspaceFolderTaskResult extends WorkspaceTaskResult { - workspaceFolder: IWorkspaceFolder; -} - interface WorkspaceFolderConfigurationResult { workspaceFolder: IWorkspaceFolder; config: TaskConfig.ExternalTaskRunnerConfiguration; @@ -1493,7 +1486,7 @@ class TaskService extends Disposable implements ITaskService { return result; } - private getWorkspaceTasks(): TPromise> { + public getWorkspaceTasks(): TPromise> { if (this._workspaceTasksPromise) { return this._workspaceTasksPromise; } diff --git a/src/vs/workbench/parts/tasks/node/taskConfiguration.ts b/src/vs/workbench/parts/tasks/node/taskConfiguration.ts index dfb8376e378..49c96a2b75e 100644 --- a/src/vs/workbench/parts/tasks/node/taskConfiguration.ts +++ b/src/vs/workbench/parts/tasks/node/taskConfiguration.ts @@ -121,6 +121,7 @@ export interface PresentationOptionsConfig { export interface RunOptionsConfig { rerunBehavior?: string; + runOn?: string; } export interface TaskIdentifier { @@ -633,6 +634,45 @@ function _freeze(this: void, target: T, properties: MetaData[]): Read return target; } +export namespace RerunBehavior { + export function fromString(value: string | undefined): Tasks.RerunBehavior { + if (!value) { + return Tasks.RerunBehavior.reevaluate; + } + switch (value.toLowerCase()) { + case 'useevaluated': + return Tasks.RerunBehavior.useEvaluated; + case 'reevaulate': + default: + return Tasks.RerunBehavior.reevaluate; + } + } +} + +export namespace RunOnOptions { + export function fromString(value: string | undefined): Tasks.RunOnOptions { + if (!value) { + return Tasks.RunOnOptions.default; + } + switch (value.toLowerCase()) { + case 'folderopen': + return Tasks.RunOnOptions.folderOpen; + case 'default': + default: + return Tasks.RunOnOptions.default; + } + } +} + +export namespace RunOptions { + export function fromConfiguration(value: RunOptionsConfig | undefined): Tasks.RunOptions { + return { + rerunBehavior: value ? RerunBehavior.fromString(value.rerunBehavior) : Tasks.RerunBehavior.reevaluate, + runOn: value ? RunOnOptions.fromString(value.runOn) : Tasks.RunOnOptions.default + }; + } +} + interface ParseContext { workspaceFolder: IWorkspaceFolder; problemReporter: IProblemReporter; @@ -1295,7 +1335,7 @@ namespace ConfiguringTask { _id: `${typeDeclaration.extensionId}.${taskIdentifier._key}`, _source: Objects.assign({}, source, { config: configElement }), _label: undefined, - runOptions: { rerunBehavior: external.runOptions ? Tasks.RerunBehavior.fromString(external.runOptions.rerunBehavior) : Tasks.RerunBehavior.reevaluate }, + runOptions: RunOptions.fromConfiguration(external.runOptions) }; let configuration = ConfigurationProperties.from(external, context, true); if (configuration) { @@ -1355,7 +1395,7 @@ namespace CustomTask { identifier: taskName, hasDefinedMatchers: false, command: undefined, - runOptions: { rerunBehavior: external.runOptions ? Tasks.RerunBehavior.fromString(external.runOptions.rerunBehavior) : Tasks.RerunBehavior.reevaluate } + runOptions: RunOptions.fromConfiguration(external.runOptions) }; let configuration = ConfigurationProperties.from(external, context, false); if (configuration) { From b10fc8fc9c9cdd54c746cba210c368304119dbd1 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 28 Nov 2018 17:46:56 +0100 Subject: [PATCH 14/14] object tree model: preserve collapse with identity provider fixes #63149 --- .../base/browser/ui/tree/objectTreeModel.ts | 58 ++++++++++++++++++- .../browser/ui/tree/objectTreeModel.test.ts | 31 ++++++++++ 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index 673dd245000..f466b2534ce 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -4,18 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { ISpliceable } from 'vs/base/common/sequence'; -import { Iterator, ISequence } from 'vs/base/common/iterator'; +import { Iterator, ISequence, getSequenceIterator } from 'vs/base/common/iterator'; import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel'; import { Event } from 'vs/base/common/event'; import { ITreeModel, ITreeNode, ITreeElement } from 'vs/base/browser/ui/tree/tree'; +import { IIdentityProvider } from 'vs/base/browser/ui/list/list'; -export interface IObjectTreeModelOptions extends IIndexTreeModelOptions { } +export interface IObjectTreeModelOptions extends IIndexTreeModelOptions { + identityProvider?: IIdentityProvider; +} export class ObjectTreeModel, TFilterData = void> implements ITreeModel { private model: IndexTreeModel; private nodes = new Map>(); + private identityProvider: IIdentityProvider | undefined = undefined; + private nodesByIdentity: Map> | undefined = undefined; + readonly onDidChangeCollapseState: Event>; readonly onDidChangeRenderNodeCount: Event>; @@ -23,6 +29,12 @@ export class ObjectTreeModel, TFilterData = void> imp constructor(list: ISpliceable>, options: IObjectTreeModelOptions = {}) { this.model = new IndexTreeModel(list, options); + this.identityProvider = options.identityProvider; + + if (this.identityProvider) { + this.nodesByIdentity = new Map(); + } + this.onDidChangeCollapseState = this.model.onDidChangeCollapseState; this.onDidChangeRenderNodeCount = this.model.onDidChangeRenderNodeCount; } @@ -35,11 +47,20 @@ export class ObjectTreeModel, TFilterData = void> imp ): Iterator> { const location = this.getElementLocation(element); const insertedElements = new Set(); + let createdNodesByIdentity: Map> | undefined; + + if (this.identityProvider) { + createdNodesByIdentity = new Map(); + } const _onDidCreateNode = (node: ITreeNode) => { insertedElements.add(node.element); this.nodes.set(node.element, node); + if (createdNodesByIdentity) { + createdNodesByIdentity.set(this.identityProvider!.getId(node.element).toString(), node); + } + if (onDidCreateNode) { onDidCreateNode(node); } @@ -50,12 +71,43 @@ export class ObjectTreeModel, TFilterData = void> imp this.nodes.delete(node.element); } + if (this.nodesByIdentity) { + this.nodesByIdentity.delete(this.identityProvider!.getId(node.element).toString()); + } + if (onDidDeleteNode) { onDidDeleteNode(node); } }; - return this.model.splice([...location, 0], Number.MAX_VALUE, children, _onDidCreateNode, _onDidDeleteNode); + const preserveCollapseState = (elements: ISequence>): ISequence> => { + const iterator = getSequenceIterator(elements); + + return Iterator.map(iterator, treeElement => { + const identity = this.identityProvider!.getId(treeElement.element).toString(); + const node = this.nodesByIdentity!.get(identity); + const collapsible = typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : (typeof treeElement.collapsed !== 'undefined'); + + return { + ...treeElement, + collapsible, + collapsed: typeof treeElement.collapsed === 'undefined' ? (node ? (collapsible && node.collapsed) : undefined) : treeElement.collapsed, + children: treeElement.children && preserveCollapseState(treeElement.children) + }; + }); + }; + + if (children && this.identityProvider) { + children = preserveCollapseState(children); + } + + const result = this.model.splice([...location, 0], Number.MAX_VALUE, children, _onDidCreateNode, _onDidDeleteNode); + + if (createdNodesByIdentity) { + createdNodesByIdentity.forEach((node, identity) => this.nodesByIdentity!.set(identity, node)); + } + + return result; } getParentElement(ref: T | null = null): T | null { diff --git a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts index 6aac4a6ad68..7b7a3647a64 100644 --- a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts @@ -140,4 +140,35 @@ suite('ObjectTreeModel', function () { model.setCollapsed(1, false); assert.deepEqual(toArray(list), [1, 11, 111, 112, 2]); }); + + test('collapse state is lost without an identity provider', () => { + const list: ITreeNode[] = []; + const model = new ObjectTreeModel(toSpliceable(list), { collapseByDefault: true }); + const data = [{ element: 'father', children: [{ element: 'child' }] }]; + + model.setChildren(null, data); + assert.deepEqual(toArray(list), ['father']); + + model.setCollapsed('father', false); + assert.deepEqual(toArray(list), ['father', 'child']); + + model.setChildren(null, data); + assert.deepEqual(toArray(list), ['father']); + }); + + test('collapse state is preserved with an identity provider', () => { + const list: ITreeNode[] = []; + const identityProvider = { getId: (name: string) => name }; + const model = new ObjectTreeModel(toSpliceable(list), { collapseByDefault: true, identityProvider }); + const data = [{ element: 'father', children: [{ element: 'child' }] }]; + + model.setChildren(null, data); + assert.deepEqual(toArray(list), ['father']); + + model.setCollapsed('father', false); + assert.deepEqual(toArray(list), ['father', 'child']); + + model.setChildren(null, data); + assert.deepEqual(toArray(list), ['father', 'child']); + }); }); \ No newline at end of file