From b1e6a681ed1453c7cd8fcdc0c36b504d0879628a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 22 Oct 2018 13:47:10 +0200 Subject: [PATCH 1/4] Fix #61313 --- src/vs/vscode.d.ts | 23 +++- src/vs/workbench/api/node/extHostTreeViews.ts | 28 +++- src/vs/workbench/api/node/extHostTypes.ts | 6 +- .../browser/parts/views/customView.ts | 14 +- src/vs/workbench/common/views.ts | 10 +- .../api/extHostTreeViews.test.ts | 121 ++++++++++++++---- 6 files changed, 158 insertions(+), 44 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 16f4b7a73fa..54163f498f6 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6538,11 +6538,28 @@ declare module 'vscode' { getParent?(element: T): ProviderResult; } + /** + * Label describing the [Tree item](#TreeItem) + */ + export interface TreeItemLabel { + + /** + * A human-readable string describing the [Tree item](#TreeItem). + */ + label: string; + + /** + * Ranges in the label to highlight. + */ + highlights?: { start: number, end: number }[]; + + } + export class TreeItem { /** - * A human-readable string describing this item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri). + * Label describing this item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri). */ - label?: string; + label?: string | TreeItemLabel; /** * Optional id for the tree item that has to be unique across tree. The id is used to preserve the selection and expansion state of the tree item. @@ -6605,7 +6622,7 @@ declare module 'vscode' { * @param label A human-readable string describing this item * @param collapsibleState [TreeItemCollapsibleState](#TreeItemCollapsibleState) of the tree item. Default is [TreeItemCollapsibleState.None](#TreeItemCollapsibleState.None) */ - constructor(label: string, collapsibleState?: TreeItemCollapsibleState); + constructor(label: string | TreeItemLabel, collapsibleState?: TreeItemCollapsibleState); /** * @param resourceUri The [uri](#Uri) of the resource representing this item. diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index a8333e2f958..7e1b11546f8 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -10,16 +10,35 @@ import { URI } from 'vs/base/common/uri'; import { debounceEvent, Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol'; -import { ITreeItem, TreeViewItemHandleArg } from 'vs/workbench/common/views'; +import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel } from 'vs/workbench/common/views'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; import { asThenable } from 'vs/base/common/async'; import { TreeItemCollapsibleState, ThemeIcon } from 'vs/workbench/api/node/extHostTypes'; -import { isUndefinedOrNull } from 'vs/base/common/types'; +import { isUndefinedOrNull, isString } from 'vs/base/common/types'; import { equals } from 'vs/base/common/arrays'; import { ILogService } from 'vs/platform/log/common/log'; type TreeItemHandle = string; +function toTreeItemLabel(label: any): ITreeItemLabel { + if (isString(label)) { + return { label }; + } + + if (label + && typeof label === 'object' + && typeof label.label === 'string') { + if (Array.isArray(label.highlights) && label.highlights.every(highlight => typeof highlight === 'object' && typeof highlight.start === 'number' && typeof highlight.end === 'number')) { + return label; + } else { + return { label: label.label }; + } + } + + return void 0; +} + + export class ExtHostTreeViews implements ExtHostTreeViewsShape { private treeViews: Map> = new Map>(); @@ -383,7 +402,7 @@ class ExtHostTreeView extends Disposable { const item = { handle, parentHandle: parent ? parent.item.handle : void 0, - label: extensionTreeItem.label, + label: toTreeItemLabel(extensionTreeItem.label), resourceUri: extensionTreeItem.resourceUri, tooltip: typeof extensionTreeItem.tooltip === 'string' ? extensionTreeItem.tooltip : void 0, command: extensionTreeItem.command ? this.commands.toInternal(extensionTreeItem.command) : void 0, @@ -402,8 +421,9 @@ class ExtHostTreeView extends Disposable { return `${ExtHostTreeView.ID_HANDLE_PREFIX}/${id}`; } + const treeItemLabel = toTreeItemLabel(label); const prefix: string = parent ? parent.item.handle : ExtHostTreeView.LABEL_HANDLE_PREFIX; - let elementId = label ? label : resourceUri ? basename(resourceUri.path) : ''; + let elementId = treeItemLabel ? treeItemLabel.label : resourceUri ? basename(resourceUri.path) : ''; elementId = elementId.indexOf('/') !== -1 ? elementId.replace('/', '//') : elementId; const existingHandle = this.nodes.has(element) ? this.nodes.get(element).item.handle : void 0; const childrenNodes = (this.getChildrenNodes(parent) || []); diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 555c76511c7..3aee6feeb14 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1791,16 +1791,16 @@ export enum ProgressLocation { export class TreeItem { - label?: string; + label?: string | vscode.TreeItemLabel; resourceUri?: URI; iconPath?: string | URI | { light: string | URI; dark: string | URI }; command?: vscode.Command; contextValue?: string; tooltip?: string; - constructor(label: string, collapsibleState?: vscode.TreeItemCollapsibleState) + constructor(label: string | vscode.TreeItemLabel, collapsibleState?: vscode.TreeItemCollapsibleState) constructor(resourceUri: URI, collapsibleState?: vscode.TreeItemCollapsibleState) - constructor(arg1: string | URI, public collapsibleState: vscode.TreeItemCollapsibleState = TreeItemCollapsibleState.None) { + constructor(arg1: string | vscode.TreeItemLabel | URI, public collapsibleState: vscode.TreeItemCollapsibleState = TreeItemCollapsibleState.None) { if (arg1 instanceof URI) { this.resourceUri = arg1; } else { diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index b4abbb8fc87..5f0b24f7c81 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -14,7 +14,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuItemActionItem, fillInActionBarActions, fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewsService, ITreeViewer, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ICustomViewDescriptor, ViewsRegistry, ViewContainer } from 'vs/workbench/common/views'; +import { IViewsService, ITreeViewer, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ICustomViewDescriptor, ViewsRegistry, ViewContainer, ITreeItemLabel } from 'vs/workbench/common/views'; import { IViewletViewOptions, FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -164,7 +164,7 @@ class TitleMenus implements IDisposable { } class Root implements ITreeItem { - label = 'root'; + label = { label: 'root' }; handle = '0'; parentHandle = null; collapsibleState = TreeItemCollapsibleState.Expanded; @@ -513,7 +513,7 @@ class TreeRenderer implements IRenderer { DOM.addClass(container, 'custom-view-tree-node-item'); const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon')); - const resourceLabel = this.instantiationService.createInstance(ResourceLabel, container, {}); + const resourceLabel = this.instantiationService.createInstance(ResourceLabel, container, { supportHighlights: true }); DOM.addClass(resourceLabel.element, 'custom-view-tree-node-item-resourceLabel'); const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions')); const actionBar = new ActionBar(actionsContainer, { @@ -526,7 +526,9 @@ class TreeRenderer implements IRenderer { renderElement(tree: ITree, node: ITreeItem, templateId: string, templateData: ITreeExplorerTemplateData): void { const resource = node.resourceUri ? URI.revive(node.resourceUri) : null; - const label = node.label ? node.label : resource ? basename(resource.path) : ''; + const treeItemLabel: ITreeItemLabel = node.label ? node.label : resource ? { label: basename(resource.path) } : void 0; + const label = treeItemLabel ? treeItemLabel.label : void 0; + const matches = treeItemLabel ? treeItemLabel.highlights : void 0; const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark; const iconUrl = icon ? URI.revive(icon) : null; const title = node.tooltip ? node.tooltip : resource ? void 0 : label; @@ -537,9 +539,9 @@ class TreeRenderer implements IRenderer { if (resource || node.themeIcon) { const fileDecorations = this.configurationService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations'); - templateData.resourceLabel.setLabel({ name: label, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'] }); + templateData.resourceLabel.setLabel({ name: label, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches }); } else { - templateData.resourceLabel.setLabel({ name: label }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'] }); + templateData.resourceLabel.setLabel({ name: label }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches }); } templateData.icon.style.backgroundImage = iconUrl ? `url('${iconUrl.toString(true)}')` : ''; diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 7d168a3455f..57e89de5c0c 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -280,6 +280,14 @@ export enum TreeItemCollapsibleState { Expanded = 2 } +export interface ITreeItemLabel { + + label: string; + + highlights?: { start: number, end: number }[]; + +} + export interface ITreeItem { handle: string; @@ -288,7 +296,7 @@ export interface ITreeItem { collapsibleState: TreeItemCollapsibleState; - label?: string; + label?: ITreeItemLabel; icon?: UriComponents; diff --git a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts index 6e43ee0967c..0fbfb0ec341 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts @@ -74,6 +74,7 @@ suite('ExtHostTreeView', function () { onDidChangeTreeNodeWithId = new Emitter<{ key: string }>(); testObject.createTreeView('testNodeTreeProvider', { treeDataProvider: aNodeTreeDataProvider() }); testObject.createTreeView('testNodeWithIdTreeProvider', { treeDataProvider: aNodeWithIdTreeDataProvider() }); + testObject.createTreeView('testNodeWithHighlightsTreeProvider', { treeDataProvider: aNodeWithHighlightedLabelTreeDataProvider() }); return loadCompleteTree('testNodeTreeProvider'); }); @@ -134,6 +135,51 @@ suite('ExtHostTreeView', function () { }); }); + test('construct highlights tree', () => { + return testObject.$getChildren('testNodeWithHighlightsTreeProvider') + .then(elements => { + assert.deepEqual(removeUnsetKeys(elements), [{ + handle: '1/a', + label: { label: 'a', highlights: [{ start: 0, end: 2 }, { start: 3, end: 5 }] }, + collapsibleState: TreeItemCollapsibleState.Collapsed + }, { + handle: '1/b', + label: { label: 'b', highlights: [{ start: 0, end: 2 }, { start: 3, end: 5 }] }, + collapsibleState: TreeItemCollapsibleState.Collapsed + }]); + return Promise.all([ + testObject.$getChildren('testNodeWithHighlightsTreeProvider', '1/a') + .then(children => { + assert.deepEqual(removeUnsetKeys(children), [{ + handle: '1/aa', + parentHandle: '1/a', + label: { label: 'aa', highlights: [{ start: 0, end: 2 }, { start: 3, end: 5 }] }, + collapsibleState: TreeItemCollapsibleState.None + }, { + handle: '1/ab', + parentHandle: '1/a', + label: { label: 'ab', highlights: [{ start: 0, end: 2 }, { start: 3, end: 5 }] }, + collapsibleState: TreeItemCollapsibleState.None + }]); + }), + testObject.$getChildren('testNodeWithHighlightsTreeProvider', '1/b') + .then(children => { + assert.deepEqual(removeUnsetKeys(children), [{ + handle: '1/ba', + parentHandle: '1/b', + label: { label: 'ba', highlights: [{ start: 0, end: 2 }, { start: 3, end: 5 }] }, + collapsibleState: TreeItemCollapsibleState.None + }, { + handle: '1/bb', + parentHandle: '1/b', + label: { label: 'bb', highlights: [{ start: 0, end: 2 }, { start: 3, end: 5 }] }, + collapsibleState: TreeItemCollapsibleState.None + }]); + }) + ]); + }); + }); + test('error is thrown if id is not unique', (done) => { tree['a'] = { 'aa': {}, @@ -169,7 +215,7 @@ suite('ExtHostTreeView', function () { assert.deepEqual(['0/0:b'], Object.keys(actuals)); assert.deepEqual(removeUnsetKeys(actuals['0/0:b']), { handle: '0/0:b', - label: 'b', + label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed }); c(null); @@ -184,7 +230,7 @@ suite('ExtHostTreeView', function () { assert.deepEqual(removeUnsetKeys(actuals['0/0:b/0:bb']), { handle: '0/0:b/0:bb', parentHandle: '0/0:b', - label: 'bb', + label: { label: 'bb' }, collapsibleState: TreeItemCollapsibleState.None }); done(); @@ -197,13 +243,13 @@ suite('ExtHostTreeView', function () { assert.deepEqual(['0/0:b', '0/0:a/0:aa'], Object.keys(actuals)); assert.deepEqual(removeUnsetKeys(actuals['0/0:b']), { handle: '0/0:b', - label: 'b', + label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed }); assert.deepEqual(removeUnsetKeys(actuals['0/0:a/0:aa']), { handle: '0/0:a/0:aa', parentHandle: '0/0:a', - label: 'aa', + label: { label: 'aa' }, collapsibleState: TreeItemCollapsibleState.None }); done(); @@ -218,13 +264,13 @@ suite('ExtHostTreeView', function () { assert.deepEqual(['0/0:a/0:aa', '0/0:b'], Object.keys(actuals)); assert.deepEqual(removeUnsetKeys(actuals['0/0:b']), { handle: '0/0:b', - label: 'b', + label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed }); assert.deepEqual(removeUnsetKeys(actuals['0/0:a/0:aa']), { handle: '0/0:a/0:aa', parentHandle: '0/0:a', - label: 'aa', + label: { label: 'aa' }, collapsibleState: TreeItemCollapsibleState.None }); done(); @@ -240,7 +286,7 @@ suite('ExtHostTreeView', function () { assert.deepEqual(['0/0:a'], Object.keys(actuals)); assert.deepEqual(removeUnsetKeys(actuals['0/0:a']), { handle: '0/0:aa', - label: 'aa', + label: { label: 'aa' }, collapsibleState: TreeItemCollapsibleState.Collapsed }); done(); @@ -411,7 +457,7 @@ suite('ExtHostTreeView', function () { .then(() => { assert.ok(revealTarget.calledOnce); assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); - assert.deepEqual({ handle: '0/0:a', label: 'a', collapsibleState: TreeItemCollapsibleState.Collapsed }, removeUnsetKeys(revealTarget.args[0][1])); + assert.deepEqual({ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }, removeUnsetKeys(revealTarget.args[0][1])); assert.deepEqual([], revealTarget.args[0][2]); assert.deepEqual({ select: true, focus: false }, revealTarget.args[0][3]); }); @@ -424,8 +470,8 @@ suite('ExtHostTreeView', function () { .then(() => { assert.ok(revealTarget.calledOnce); assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); - assert.deepEqual({ handle: '0/0:a/0:aa', label: 'aa', collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1])); - assert.deepEqual([{ handle: '0/0:a', label: 'a', collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); + assert.deepEqual({ handle: '0/0:a/0:aa', label: { label: 'aa' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1])); + assert.deepEqual([{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); assert.deepEqual({ select: true, focus: false }, revealTarget.args[0][3]); }); }); @@ -439,8 +485,8 @@ suite('ExtHostTreeView', function () { .then(() => { assert.ok(revealTarget.calledOnce); assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); - assert.deepEqual({ handle: '0/0:a/0:aa', label: 'aa', collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1])); - assert.deepEqual([{ handle: '0/0:a', label: 'a', collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); + assert.deepEqual({ handle: '0/0:a/0:aa', label: { label: 'aa' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1])); + assert.deepEqual([{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); assert.deepEqual({ select: true, focus: false }, revealTarget.args[0][3]); })); }); @@ -459,10 +505,10 @@ suite('ExtHostTreeView', function () { .then(() => { assert.ok(revealTarget.calledOnce); assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); - assert.deepEqual({ handle: '0/0:b/0:ba/0:bac', label: 'bac', collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b/0:ba' }, removeUnsetKeys(revealTarget.args[0][1])); + assert.deepEqual({ handle: '0/0:b/0:ba/0:bac', label: { label: 'bac' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b/0:ba' }, removeUnsetKeys(revealTarget.args[0][1])); assert.deepEqual([ - { handle: '0/0:b', label: 'b', collapsibleState: TreeItemCollapsibleState.Collapsed }, - { handle: '0/0:b/0:ba', label: 'ba', collapsibleState: TreeItemCollapsibleState.Collapsed, parentHandle: '0/0:b' } + { handle: '0/0:b', label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed }, + { handle: '0/0:b/0:ba', label: { label: 'ba' }, collapsibleState: TreeItemCollapsibleState.Collapsed, parentHandle: '0/0:b' } ], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); assert.deepEqual({ select: false, focus: false }, revealTarget.args[0][3]); }); @@ -489,8 +535,8 @@ suite('ExtHostTreeView', function () { .then(() => { assert.ok(revealTarget.calledOnce); assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); - assert.deepEqual({ handle: '0/0:a/0:ac', label: 'ac', collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1])); - assert.deepEqual([{ handle: '0/0:a', label: 'a', collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); + assert.deepEqual({ handle: '0/0:a/0:ac', label: { label: 'ac' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:a' }, removeUnsetKeys(revealTarget.args[0][1])); + assert.deepEqual([{ handle: '0/0:a', label: { label: 'a' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); assert.deepEqual({ select: true, focus: false }, revealTarget.args[0][3]); }); }); @@ -528,8 +574,8 @@ suite('ExtHostTreeView', function () { .then(() => { assert.ok(revealTarget.calledOnce); assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); - assert.deepEqual({ handle: '0/0:b/0:bc', label: 'bc', collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b' }, removeUnsetKeys(revealTarget.args[0][1])); - assert.deepEqual([{ handle: '0/0:b', label: 'b', collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); + assert.deepEqual({ handle: '0/0:b/0:bc', label: { label: 'bc' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b' }, removeUnsetKeys(revealTarget.args[0][1])); + assert.deepEqual([{ handle: '0/0:b', label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); assert.deepEqual({ select: true, focus: false }, revealTarget.args[0][3]); }); }); @@ -542,13 +588,20 @@ suite('ExtHostTreeView', function () { } function removeUnsetKeys(obj: any): any { - const result = {}; - for (const key of Object.keys(obj)) { - if (obj[key] !== void 0) { - result[key] = obj[key]; - } + if (Array.isArray(obj)) { + return obj.map(o => removeUnsetKeys(o)); } - return result; + + if (typeof obj === 'object') { + const result = {}; + for (const key of Object.keys(obj)) { + if (obj[key] !== void 0) { + result[key] = removeUnsetKeys(obj[key]); + } + } + return result; + } + return obj; } function aNodeTreeDataProvider(): TreeDataProvider<{ key: string }> { @@ -593,6 +646,20 @@ suite('ExtHostTreeView', function () { }; } + function aNodeWithHighlightedLabelTreeDataProvider(): TreeDataProvider<{ key: string }> { + return { + getChildren: (element: { key: string }): { key: string }[] => { + return getChildren(element ? element.key : undefined).map(key => getNode(key)); + }, + getTreeItem: (element: { key: string }): TreeItem => { + const treeItem = getTreeItem(element.key, [{ start: 0, end: 2 }, { start: 3, end: 5 }]); + treeItem.id = element.key; + return treeItem; + }, + onDidChangeTreeData: onDidChangeTreeNodeWithId.event + }; + } + function getTreeElement(element): any { let parent = tree; for (let i = 0; i < element.length; i++) { @@ -615,10 +682,10 @@ suite('ExtHostTreeView', function () { return []; } - function getTreeItem(key: string): TreeItem { + function getTreeItem(key: string, highlights?: { start: number, end: number }[]): TreeItem { const treeElement = getTreeElement(key); return { - label: labels[key] || key, + label: { label: labels[key] || key, highlights }, collapsibleState: treeElement && Object.keys(treeElement).length ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None }; } From 90052ff8c47ee4a6986f40e897578617294794b9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 22 Oct 2018 16:50:37 +0200 Subject: [PATCH 2/4] code review - change from object array to number tuples. Move to proposed api --- src/vs/vscode.d.ts | 23 ++------------ src/vs/vscode.proposed.d.ts | 31 +++++++++++++++++++ src/vs/workbench/api/node/extHost.api.impl.ts | 1 + src/vs/workbench/api/node/extHostTreeViews.ts | 9 +++--- .../browser/parts/views/customView.ts | 2 +- src/vs/workbench/common/views.ts | 2 +- 6 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 54163f498f6..16f4b7a73fa 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -6538,28 +6538,11 @@ declare module 'vscode' { getParent?(element: T): ProviderResult; } - /** - * Label describing the [Tree item](#TreeItem) - */ - export interface TreeItemLabel { - - /** - * A human-readable string describing the [Tree item](#TreeItem). - */ - label: string; - - /** - * Ranges in the label to highlight. - */ - highlights?: { start: number, end: number }[]; - - } - export class TreeItem { /** - * Label describing this item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri). + * A human-readable string describing this item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri). */ - label?: string | TreeItemLabel; + label?: string; /** * Optional id for the tree item that has to be unique across tree. The id is used to preserve the selection and expansion state of the tree item. @@ -6622,7 +6605,7 @@ declare module 'vscode' { * @param label A human-readable string describing this item * @param collapsibleState [TreeItemCollapsibleState](#TreeItemCollapsibleState) of the tree item. Default is [TreeItemCollapsibleState.None](#TreeItemCollapsibleState.None) */ - constructor(label: string | TreeItemLabel, collapsibleState?: TreeItemCollapsibleState); + constructor(label: string, collapsibleState?: TreeItemCollapsibleState); /** * @param resourceUri The [uri](#Uri) of the resource representing this item. diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 7382f0dd54c..e1dba34467e 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1141,4 +1141,35 @@ declare module 'vscode' { alwaysShow?: boolean; } //#endregion + + //#region Tree Item Label Highlights + /** + * Label describing the [Tree item](#TreeItem) + */ + export interface TreeItemLabel { + + /** + * A human-readable string describing the [Tree item](#TreeItem). + */ + label: string; + + /** + * Ranges in the label to highlight. + */ + highlights?: [number][number][]; + + } + + export class TreeItem2 extends TreeItem { + /** + * Label describing this item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri). + */ + label?: string | TreeItemLabel | /* for compilation */ any; + + /** + * @param label Label describing this item + * @param collapsibleState [TreeItemCollapsibleState](#TreeItemCollapsibleState) of the tree item. Default is [TreeItemCollapsibleState.None](#TreeItemCollapsibleState.None) + */ + constructor(label: TreeItemLabel, collapsibleState?: TreeItemCollapsibleState); + } } diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 8484f983781..dff2e339147 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -794,6 +794,7 @@ export function createApiFactory( ThemeColor: extHostTypes.ThemeColor, ThemeIcon: extHostTypes.ThemeIcon, TreeItem: extHostTypes.TreeItem, + TreeItem2: extHostTypes.TreeItem, TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState, Uri: URI, ViewColumn: extHostTypes.ViewColumn, diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index 7e1b11546f8..487b76fa9a7 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -28,11 +28,12 @@ function toTreeItemLabel(label: any): ITreeItemLabel { if (label && typeof label === 'object' && typeof label.label === 'string') { - if (Array.isArray(label.highlights) && label.highlights.every(highlight => typeof highlight === 'object' && typeof highlight.start === 'number' && typeof highlight.end === 'number')) { - return label; - } else { - return { label: label.label }; + let highlights: [number, number][] = void 0; + if (Array.isArray(label.highlights)) { + highlights = (<[number, number][]>label.highlights).filter((highlight => highlight.length === 2 && typeof highlight[0] === 'number' && typeof highlight[1] === 'number')); + highlights = highlights.length ? highlights : void 0; } + return { label: label.label, highlights }; } return void 0; diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 5f0b24f7c81..7fe21ac0b13 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -528,7 +528,7 @@ class TreeRenderer implements IRenderer { const resource = node.resourceUri ? URI.revive(node.resourceUri) : null; const treeItemLabel: ITreeItemLabel = node.label ? node.label : resource ? { label: basename(resource.path) } : void 0; const label = treeItemLabel ? treeItemLabel.label : void 0; - const matches = treeItemLabel ? treeItemLabel.highlights : void 0; + const matches = treeItemLabel ? treeItemLabel.highlights.map(([start, end]) => ({ start, end })) : void 0; const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark; const iconUrl = icon ? URI.revive(icon) : null; const title = node.tooltip ? node.tooltip : resource ? void 0 : label; diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 57e89de5c0c..ab8d9857d80 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -284,7 +284,7 @@ export interface ITreeItemLabel { label: string; - highlights?: { start: number, end: number }[]; + highlights?: [number, number][]; } From e3d1d1d1186e671391cf7cf7cdf89d2fb4f1f6e6 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 22 Oct 2018 17:25:43 +0200 Subject: [PATCH 3/4] Fix tests --- .../api/extHostTreeViews.test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts index 0fbfb0ec341..ee9704fd064 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts @@ -140,11 +140,11 @@ suite('ExtHostTreeView', function () { .then(elements => { assert.deepEqual(removeUnsetKeys(elements), [{ handle: '1/a', - label: { label: 'a', highlights: [{ start: 0, end: 2 }, { start: 3, end: 5 }] }, + label: { label: 'a', highlights: [[0, 2], [3, 5]] }, collapsibleState: TreeItemCollapsibleState.Collapsed }, { handle: '1/b', - label: { label: 'b', highlights: [{ start: 0, end: 2 }, { start: 3, end: 5 }] }, + label: { label: 'b', highlights: [[0, 2], [3, 5]] }, collapsibleState: TreeItemCollapsibleState.Collapsed }]); return Promise.all([ @@ -153,12 +153,12 @@ suite('ExtHostTreeView', function () { assert.deepEqual(removeUnsetKeys(children), [{ handle: '1/aa', parentHandle: '1/a', - label: { label: 'aa', highlights: [{ start: 0, end: 2 }, { start: 3, end: 5 }] }, + label: { label: 'aa', highlights: [[0, 2], [3, 5]] }, collapsibleState: TreeItemCollapsibleState.None }, { handle: '1/ab', parentHandle: '1/a', - label: { label: 'ab', highlights: [{ start: 0, end: 2 }, { start: 3, end: 5 }] }, + label: { label: 'ab', highlights: [[0, 2], [3, 5]] }, collapsibleState: TreeItemCollapsibleState.None }]); }), @@ -167,12 +167,12 @@ suite('ExtHostTreeView', function () { assert.deepEqual(removeUnsetKeys(children), [{ handle: '1/ba', parentHandle: '1/b', - label: { label: 'ba', highlights: [{ start: 0, end: 2 }, { start: 3, end: 5 }] }, + label: { label: 'ba', highlights: [[0, 2], [3, 5]] }, collapsibleState: TreeItemCollapsibleState.None }, { handle: '1/bb', parentHandle: '1/b', - label: { label: 'bb', highlights: [{ start: 0, end: 2 }, { start: 3, end: 5 }] }, + label: { label: 'bb', highlights: [[0, 2], [3, 5]] }, collapsibleState: TreeItemCollapsibleState.None }]); }) @@ -652,7 +652,7 @@ suite('ExtHostTreeView', function () { return getChildren(element ? element.key : undefined).map(key => getNode(key)); }, getTreeItem: (element: { key: string }): TreeItem => { - const treeItem = getTreeItem(element.key, [{ start: 0, end: 2 }, { start: 3, end: 5 }]); + const treeItem = getTreeItem(element.key, [[0, 2], [3, 5]]); treeItem.id = element.key; return treeItem; }, @@ -682,10 +682,10 @@ suite('ExtHostTreeView', function () { return []; } - function getTreeItem(key: string, highlights?: { start: number, end: number }[]): TreeItem { + function getTreeItem(key: string, highlights?: [number, number][]): TreeItem { const treeElement = getTreeElement(key); return { - label: { label: labels[key] || key, highlights }, + label: { label: labels[key] || key, highlights }, collapsibleState: treeElement && Object.keys(treeElement).length ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None }; } From d0849b76e98284df352cb699518243865acf8f74 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 22 Oct 2018 19:28:12 +0200 Subject: [PATCH 4/4] Fix typo --- src/vs/vscode.proposed.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e1dba34467e..42bb8c80ba3 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1156,7 +1156,7 @@ declare module 'vscode' { /** * Ranges in the label to highlight. */ - highlights?: [number][number][]; + highlights?: [number, number][]; }