diff --git a/package-lock.json b/package-lock.json index de9fb1d..7eb4b34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ccls", - "version": "0.1.17", + "version": "0.1.18", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2345,7 +2345,7 @@ }, "tunnel": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz", + "resolved": "http://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz", "integrity": "sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM=", "dev": true }, diff --git a/package.json b/package.json index dfe2027..c552c60 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "C/C++/ObjC language server supporting cross references, hierarchies, completion and semantic highlighting", "author": "ccls-project", "license": "MIT", - "version": "0.1.17", + "version": "0.1.18", "publisher": "ccls-project", "preview": true, "engines": { @@ -33,6 +33,11 @@ "id": "ccls.callHierarchy", "name": "Call Hierarchy", "when": "extension.ccls.callHierarchyVisible" + }, + { + "id": "ccls.dataFlowInto", + "name": "Data Flow Hierarchy", + "when": "extension.ccls.dataFlowHierarchyVisible" } ] }, @@ -49,14 +54,19 @@ "group": "navigation@1.32" }, { - "command": "ccls.vars", + "command": "ccls.dataFlowInto", "when": "resourceLangId == cpp", "group": "navigation@1.33" }, { - "command": "ccls.base", + "command": "ccls.vars", "when": "resourceLangId == cpp", "group": "navigation@1.34" + }, + { + "command": "ccls.base", + "when": "resourceLangId == cpp", + "group": "navigation@1.35" } ], "view/title": [ @@ -69,6 +79,11 @@ "command": "ccls.closeCallHierarchy", "when": "view == ccls.callHierarchy", "group": "navigation" + }, + { + "command": "ccls.closeDataFlowHierarchy", + "when": "view == ccls.dataFlowInto", + "group": "navigation" } ], "view/item/context": [ @@ -86,6 +101,10 @@ "command": "ccls.closeCallHierarchy", "when": "false" }, + { + "command": "ccls.closeDataFlowHierarchy", + "when": "false" + }, { "command": "ccls.gotoForTreeView", "when": "false" @@ -126,6 +145,15 @@ "category": "ccls", "command": "ccls.vars" }, + { + "title": "Show Data Flow Into", + "category": "ccls", + "command": "ccls.dataFlowInto" + }, + { + "title": "Close", + "command": "ccls.closeDataFlowHierarchy" + }, { "title": "Show Cross References", "category": "ccls", diff --git a/src/dataFlowHierarchy.ts b/src/dataFlowHierarchy.ts new file mode 100644 index 0000000..244be7f --- /dev/null +++ b/src/dataFlowHierarchy.ts @@ -0,0 +1,63 @@ +import { Event, EventEmitter, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Range, Position, workspace } from 'vscode'; +import { LanguageClient } from 'vscode-languageclient/lib/main'; +import * as ls from 'vscode-languageserver-types'; +import { parseUri } from './extension'; + +export class DataFlowHierarchyNode { + // These properties come directly from the language server. + id: number; + location: ls.Location; + children: DataFlowHierarchyNode[]; +} + +export class DataFlowHierarchyProvider implements TreeDataProvider { + root: DataFlowHierarchyNode; + readonly onDidChangeEmitter: EventEmitter = new EventEmitter(); + readonly onDidChangeTreeData: Event = this.onDidChangeEmitter.event; + + constructor( + readonly languageClient: LanguageClient, readonly baseDark: string, readonly baseLight: string) { } + + async getTreeItem(element: DataFlowHierarchyNode): Promise { + let collapseState = TreeItemCollapsibleState.None; + if (element.children.length > 0) + collapseState = TreeItemCollapsibleState.Expanded; + + let light = this.baseLight; + let dark = this.baseDark; + + let parentFile = await workspace.openTextDocument(parseUri(element.location.uri)); + let label = parentFile.getText( + new Range( + new Position(element.location.range.start.line, element.location.range.start.character), + new Position(element.location.range.end.line, element.location.range.end.character), + ) + ); + //let label = element.location.uri.toString(); + if (element.location) { + const path = parseUri(element.location.uri).path; + const name = path.substr(path.lastIndexOf('/') + 1); + label += ` (${name}:${element.location.range.start.line + 1})`; + } + + return { + collapsibleState: collapseState, + command: { + arguments: [element, element.children.length > 0], + command: 'ccls.hackGotoForTreeView', + title: 'Goto', + }, + contextValue: 'cclsGoto', + iconPath: { light, dark }, + label, + }; + } + + getChildren(element?: DataFlowHierarchyNode): DataFlowHierarchyNode[] | Thenable { + if (!this.root) + return []; + if (!element) + return [this.root]; + return element.children; + } +} diff --git a/src/extension.ts b/src/extension.ts index 4625fcd..6f36934 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -36,6 +36,7 @@ import * as ls from "vscode-languageserver-types"; import { CallHierarchyNode, CallHierarchyProvider } from "./callHierarchy"; import { CclsErrorHandler } from "./cclsErrorHandler"; +import { DataFlowHierarchyNode, DataFlowHierarchyProvider } from "./dataFlowHierarchy"; import { InheritanceHierarchyNode, InheritanceHierarchyProvider } from "./inheritanceHierarchy"; import { jumpToUriAtPosition } from "./vscodeUtils"; @@ -711,6 +712,36 @@ export function activate(context: ExtensionContext) { }); })(); + // DataFlow Hierarchy + (() => { + const baseDark = context.asAbsolutePath(path.join('resources', 'base-dark.svg')); + const baseLight = context.asAbsolutePath(path.join('resources', 'base-light.svg')); + const dataFlowHierarchyProvider = new DataFlowHierarchyProvider( + languageClient, baseDark, baseLight); + window.registerTreeDataProvider('ccls.dataFlowInto', dataFlowHierarchyProvider); + commands.registerTextEditorCommand('ccls.dataFlowInto', (editor) => { + setContext('extension.ccls.dataFlowHierarchyVisible', true); + const position = editor.selection.active; + const uri = editor.document.uri; + languageClient + .sendRequest('$ccls/dataFlowInto', { + position, + textDocument: { + uri: uri.toString(), + }, + }) + .then((callNode: DataFlowHierarchyNode) => { + dataFlowHierarchyProvider.root = callNode; + dataFlowHierarchyProvider.onDidChangeEmitter.fire(); + }); + }); + commands.registerCommand('ccls.closeDataFlowHierarchy', (e) => { + setContext('extension.ccls.dataFlowHierarchyVisible', false); + dataFlowHierarchyProvider.root = undefined; + dataFlowHierarchyProvider.onDidChangeEmitter.fire(); + }); + })(); + // Common between tree views. (() => { commands.registerCommand( @@ -729,7 +760,7 @@ export function activate(context: ExtensionContext) { let lastGotoClickTime: number; commands.registerCommand( 'ccls.hackGotoForTreeView', - (node: InheritanceHierarchyNode|CallHierarchyNode, + (node: InheritanceHierarchyNode|CallHierarchyNode|DataFlowHierarchyNode, hasChildren: boolean) => { if (!node.location) return;