From 4a0b22cf62265482f892eee9142c37b64eee209a Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 17 Aug 2021 15:40:28 +0200 Subject: [PATCH] Have a test view and a drag and drop test view --- tree-view-sample/package.json | 5 + tree-view-sample/src/extension.ts | 4 + tree-view-sample/src/testView.ts | 312 +++++++------------- tree-view-sample/src/testViewDragAndDrop.ts | 198 +++++++++++++ 4 files changed, 310 insertions(+), 209 deletions(-) create mode 100644 tree-view-sample/src/testViewDragAndDrop.ts diff --git a/tree-view-sample/package.json b/tree-view-sample/package.json index 7d351a69..1e0aabb1 100644 --- a/tree-view-sample/package.json +++ b/tree-view-sample/package.json @@ -23,6 +23,7 @@ "onView:jsonOutline", "onView:fileExplorer", "onView:testView", + "onView:testViewDragAndDrop", "onLanguage:json", "onLanguage:jsonc", "onCommand:testView.reveal" @@ -64,6 +65,10 @@ { "id": "testView", "name": "Test View" + }, + { + "id": "testViewDragAndDrop", + "name": "Test View Drag and Drop" } ] }, diff --git a/tree-view-sample/src/extension.ts b/tree-view-sample/src/extension.ts index 142cf138..83350e6f 100644 --- a/tree-view-sample/src/extension.ts +++ b/tree-view-sample/src/extension.ts @@ -6,6 +6,7 @@ import { DepNodeProvider, Dependency } from './nodeDependencies'; import { JsonOutlineProvider } from './jsonOutline'; import { FtpExplorer } from './ftpExplorer'; import { FileExplorer } from './fileExplorer'; +import { TestViewDragAndDrop } from './testViewDragAndDrop'; import { TestView } from './testView'; export function activate(context: vscode.ExtensionContext) { @@ -34,4 +35,7 @@ export function activate(context: vscode.ExtensionContext) { // Test View new TestView(context); + + // Drag and Drop sample + new TestViewDragAndDrop(context); } \ No newline at end of file diff --git a/tree-view-sample/src/testView.ts b/tree-view-sample/src/testView.ts index 6f587ed9..cf8874be 100644 --- a/tree-view-sample/src/testView.ts +++ b/tree-view-sample/src/testView.ts @@ -1,210 +1,104 @@ -import * as vscode from 'vscode'; - -export class TestView implements vscode.TreeDataProvider, vscode.DragAndDropController { - supportedTypes = ['text/treeitems']; - private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); - // We want to use an array as the event type, so we use the proposed onDidChangeTreeData2. - public onDidChangeTreeData2: vscode.Event = this._onDidChangeTreeData.event; - public tree = { - 'a': { - 'aa': { - 'aaa': { - 'aaaa': { - 'aaaaa': { - 'aaaaaa': { - - } - } - } - } - }, - 'ab': {} - }, - 'b': { - 'ba': {}, - 'bb': {} - } - }; - // Keep track of any nodes we create so that we can re-use the same objects. - private nodes = {}; - - constructor(context: vscode.ExtensionContext) { - const view = vscode.window.createTreeView('testView', { treeDataProvider: this, showCollapseAll: true, canSelectMany: true, dragAndDropController: this }); - context.subscriptions.push(view); - vscode.commands.registerCommand('testView.reveal', async () => { - const key = await vscode.window.showInputBox({ placeHolder: 'Type the label of the item to reveal' }); - if (key) { - await view.reveal({ key }, { focus: true, select: false, expand: true }); - } - }); - vscode.commands.registerCommand('testView.changeTitle', async () => { - const title = await vscode.window.showInputBox({ prompt: 'Type the new title for the Test View', placeHolder: view.title }); - if (title) { - view.title = title; - } - }); - } - - // Tree data provider - - public getChildren(element: Node): Node[] { - return this._getChildren(element ? element.key : undefined).map(key => this._getNode(key)); - } - - public getTreeItem(element: Node): vscode.TreeItem { - const treeItem = this._getTreeItem(element.key); - treeItem.id = element.key; - return treeItem; - } - public getParent(element: Node): Node { - return this._getParent(element.key); - } - - dispose(): void { - // nothing to dispose - } - - // Drag and drop controller - - public async onDrop(sources: vscode.TreeDataTransfer, target: Node): Promise { - const treeItems = JSON.parse(await sources.items.get('text/treeitems')!.asString()); - let roots = this._getLocalRoots(treeItems); - // Remove nodes that are already target's parent nodes - roots = roots.filter(r => !this._isChild(this._getTreeElement(r.key), target)); - if (roots.length > 0) { - // Reload parents of the moving elements - const parents = roots.map(r => this.getParent(r)); - roots.forEach(r => this._reparentNode(r, target)); - this._onDidChangeTreeData.fire([...parents, target]); - } - } - - // Helper methods - - _isChild(node: Node, child: Node): boolean { - for (const prop in node) { - if (prop === child.key) { - return true; - } else { - const isChild = this._isChild(node[prop], child); - if (isChild) { - return isChild; - } - } - } - return false; - } - - // From the given nodes, filter out all nodes who's parent is already in the the array of Nodes. - _getLocalRoots(nodes: Node[]): Node[] { - const localRoots = []; - for (let i = 0; i < nodes.length; i++) { - const parent = this.getParent(nodes[i]); - if (parent) { - const isInList = nodes.find(n => n.key === parent.key); - if (isInList === undefined) { - localRoots.push(nodes[i]); - } - } else { - localRoots.push(nodes[i]); - } - } - return localRoots; - } - - // Remove node from current position and add node to new target element - _reparentNode(node: Node, target: Node): void { - const element = {}; - element[node.key] = this._getTreeElement(node.key); - const elementCopy = { ...element }; - this._removeNode(node); - const targetElement = this._getTreeElement(target.key); - if (Object.keys(element).length === 0) { - targetElement[node.key] = {}; - } else { - Object.assign(targetElement, elementCopy); - } - } - - // Remove node from tree - _removeNode(element: Node, tree?: any): void { - const subTree = tree ? tree : this.tree; - for (const prop in subTree) { - if (prop === element.key) { - const parent = this.getParent(element); - if (parent) { - const parentObject = this._getTreeElement(parent.key); - delete parentObject[prop]; - } else { - delete this.tree[prop]; - } - } else { - this._removeNode(element, subTree[prop]); - } - } - } - - _getChildren(key: string): string[] { - if (!key) { - return Object.keys(this.tree); - } - const treeElement = this._getTreeElement(key); - if (treeElement) { - return Object.keys(treeElement); - } - return []; - } - - _getTreeItem(key: string): vscode.TreeItem { - const treeElement = this._getTreeElement(key); - // An example of how to use codicons in a MarkdownString in a tree item tooltip. - const tooltip = new vscode.MarkdownString(`$(zap) Tooltip for ${key}`, true); - return { - label: /**vscode.TreeItemLabel**/{ label: key, highlights: key.length > 1 ? [[key.length - 2, key.length - 1]] : void 0 }, - tooltip, - collapsibleState: treeElement && Object.keys(treeElement).length ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None, - resourceUri: vscode.Uri.parse(`/tmp/${key}`), - }; - } - - _getTreeElement(element: string, tree?: any): Node { - const currentNode = tree ?? this.tree; - for (const prop in currentNode) { - if (prop === element) { - return currentNode[prop]; - } else { - const treeElement = this._getTreeElement(element, currentNode[prop]); - if (treeElement) { - return treeElement; - } - } - } - } - - _getParent(element: string, parent?: string, tree?): any { - const currentNode = tree ?? this.tree; - for (const prop in currentNode) { - if (prop === element && parent) { - return this._getNode(parent); - } else { - const parent = this._getParent(element, prop, currentNode[prop]); - if (parent) { - return parent; - } - } - } - } - - _getNode(key: string): Node { - if (!this.nodes[key]) { - this.nodes[key] = new Key(key); - } - return this.nodes[key]; - } -} - -type Node = { key: string }; - -class Key { - constructor(readonly key: string) { } +import * as vscode from 'vscode'; + +export class TestView { + + constructor(context: vscode.ExtensionContext) { + const view = vscode.window.createTreeView('testView', { treeDataProvider: aNodeWithIdTreeDataProvider(), showCollapseAll: true }); + context.subscriptions.push(view); + vscode.commands.registerCommand('testView.reveal', async () => { + const key = await vscode.window.showInputBox({ placeHolder: 'Type the label of the item to reveal' }); + if (key) { + await view.reveal({ key }, { focus: true, select: false, expand: true }); + } + }); + vscode.commands.registerCommand('testView.changeTitle', async () => { + const title = await vscode.window.showInputBox({ prompt: 'Type the new title for the Test View', placeHolder: view.title }); + if (title) { + view.title = title; + } + }); + } +} + +const tree = { + 'a': { + 'aa': { + 'aaa': { + 'aaaa': { + 'aaaaa': { + 'aaaaaa': { + + } + } + } + } + }, + 'ab': {} + }, + 'b': { + 'ba': {}, + 'bb': {} + } +}; +const nodes = {}; + +function aNodeWithIdTreeDataProvider(): vscode.TreeDataProvider<{ key: string }> { + return { + getChildren: (element: { key: string }): { key: string }[] => { + return getChildren(element ? element.key : undefined).map(key => getNode(key)); + }, + getTreeItem: (element: { key: string }): vscode.TreeItem => { + const treeItem = getTreeItem(element.key); + treeItem.id = element.key; + return treeItem; + }, + getParent: ({ key }: { key: string }): { key: string } => { + const parentKey = key.substring(0, key.length - 1); + return parentKey ? new Key(parentKey) : void 0; + } + }; +} + +function getChildren(key: string): string[] { + if (!key) { + return Object.keys(tree); + } + const treeElement = getTreeElement(key); + if (treeElement) { + return Object.keys(treeElement); + } + return []; +} + +function getTreeItem(key: string): vscode.TreeItem { + const treeElement = getTreeElement(key); + // An example of how to use codicons in a MarkdownString in a tree item tooltip. + const tooltip = new vscode.MarkdownString(`$(zap) Tooltip for ${key}`, true); + return { + label: /**vscode.TreeItemLabel**/{ label: key, highlights: key.length > 1 ? [[key.length - 2, key.length - 1]] : void 0 }, + tooltip, + collapsibleState: treeElement && Object.keys(treeElement).length ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None + }; +} + +function getTreeElement(element): any { + let parent = tree; + for (let i = 0; i < element.length; i++) { + parent = parent[element.substring(0, i + 1)]; + if (!parent) { + return null; + } + } + return parent; +} + +function getNode(key: string): { key: string } { + if (!nodes[key]) { + nodes[key] = new Key(key); + } + return nodes[key]; +} + +class Key { + constructor(readonly key: string) { } } \ No newline at end of file diff --git a/tree-view-sample/src/testViewDragAndDrop.ts b/tree-view-sample/src/testViewDragAndDrop.ts new file mode 100644 index 00000000..a44610cc --- /dev/null +++ b/tree-view-sample/src/testViewDragAndDrop.ts @@ -0,0 +1,198 @@ +import * as vscode from 'vscode'; + +export class TestViewDragAndDrop implements vscode.TreeDataProvider, vscode.DragAndDropController { + supportedTypes = ['text/treeitems']; + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + // We want to use an array as the event type, so we use the proposed onDidChangeTreeData2. + public onDidChangeTreeData2: vscode.Event = this._onDidChangeTreeData.event; + public tree = { + 'a': { + 'aa': { + 'aaa': { + 'aaaa': { + 'aaaaa': { + 'aaaaaa': { + + } + } + } + } + }, + 'ab': {} + }, + 'b': { + 'ba': {}, + 'bb': {} + } + }; + // Keep track of any nodes we create so that we can re-use the same objects. + private nodes = {}; + + constructor(context: vscode.ExtensionContext) { + const view = vscode.window.createTreeView('testViewDragAndDrop', { treeDataProvider: this, showCollapseAll: true, canSelectMany: true, dragAndDropController: this }); + context.subscriptions.push(view); + } + + // Tree data provider + + public getChildren(element: Node): Node[] { + return this._getChildren(element ? element.key : undefined).map(key => this._getNode(key)); + } + + public getTreeItem(element: Node): vscode.TreeItem { + const treeItem = this._getTreeItem(element.key); + treeItem.id = element.key; + return treeItem; + } + public getParent(element: Node): Node { + return this._getParent(element.key); + } + + dispose(): void { + // nothing to dispose + } + + // Drag and drop controller + + public async onDrop(sources: vscode.TreeDataTransfer, target: Node): Promise { + const treeItems = JSON.parse(await sources.items.get('text/treeitems')!.asString()); + let roots = this._getLocalRoots(treeItems); + // Remove nodes that are already target's parent nodes + roots = roots.filter(r => !this._isChild(this._getTreeElement(r.key), target)); + if (roots.length > 0) { + // Reload parents of the moving elements + const parents = roots.map(r => this.getParent(r)); + roots.forEach(r => this._reparentNode(r, target)); + this._onDidChangeTreeData.fire([...parents, target]); + } + } + + // Helper methods + + _isChild(node: Node, child: Node): boolean { + for (const prop in node) { + if (prop === child.key) { + return true; + } else { + const isChild = this._isChild(node[prop], child); + if (isChild) { + return isChild; + } + } + } + return false; + } + + // From the given nodes, filter out all nodes who's parent is already in the the array of Nodes. + _getLocalRoots(nodes: Node[]): Node[] { + const localRoots = []; + for (let i = 0; i < nodes.length; i++) { + const parent = this.getParent(nodes[i]); + if (parent) { + const isInList = nodes.find(n => n.key === parent.key); + if (isInList === undefined) { + localRoots.push(nodes[i]); + } + } else { + localRoots.push(nodes[i]); + } + } + return localRoots; + } + + // Remove node from current position and add node to new target element + _reparentNode(node: Node, target: Node): void { + const element = {}; + element[node.key] = this._getTreeElement(node.key); + const elementCopy = { ...element }; + this._removeNode(node); + const targetElement = this._getTreeElement(target.key); + if (Object.keys(element).length === 0) { + targetElement[node.key] = {}; + } else { + Object.assign(targetElement, elementCopy); + } + } + + // Remove node from tree + _removeNode(element: Node, tree?: any): void { + const subTree = tree ? tree : this.tree; + for (const prop in subTree) { + if (prop === element.key) { + const parent = this.getParent(element); + if (parent) { + const parentObject = this._getTreeElement(parent.key); + delete parentObject[prop]; + } else { + delete this.tree[prop]; + } + } else { + this._removeNode(element, subTree[prop]); + } + } + } + + _getChildren(key: string): string[] { + if (!key) { + return Object.keys(this.tree); + } + const treeElement = this._getTreeElement(key); + if (treeElement) { + return Object.keys(treeElement); + } + return []; + } + + _getTreeItem(key: string): vscode.TreeItem { + const treeElement = this._getTreeElement(key); + // An example of how to use codicons in a MarkdownString in a tree item tooltip. + const tooltip = new vscode.MarkdownString(`$(zap) Tooltip for ${key}`, true); + return { + label: /**vscode.TreeItemLabel**/{ label: key, highlights: key.length > 1 ? [[key.length - 2, key.length - 1]] : void 0 }, + tooltip, + collapsibleState: treeElement && Object.keys(treeElement).length ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None, + resourceUri: vscode.Uri.parse(`/tmp/${key}`), + }; + } + + _getTreeElement(element: string, tree?: any): Node { + const currentNode = tree ?? this.tree; + for (const prop in currentNode) { + if (prop === element) { + return currentNode[prop]; + } else { + const treeElement = this._getTreeElement(element, currentNode[prop]); + if (treeElement) { + return treeElement; + } + } + } + } + + _getParent(element: string, parent?: string, tree?): any { + const currentNode = tree ?? this.tree; + for (const prop in currentNode) { + if (prop === element && parent) { + return this._getNode(parent); + } else { + const parent = this._getParent(element, prop, currentNode[prop]); + if (parent) { + return parent; + } + } + } + } + + _getNode(key: string): Node { + if (!this.nodes[key]) { + this.nodes[key] = new Key(key); + } + return this.nodes[key]; + } +} + +type Node = { key: string }; + +class Key { + constructor(readonly key: string) { } +} \ No newline at end of file