From 0fb86a70a1f5a8d7c653069dc1aa6a744ad66ae1 Mon Sep 17 00:00:00 2001 From: Kirigaya <1193466151@qq.com> Date: Tue, 11 Apr 2023 22:56:09 +0800 Subject: [PATCH] finish treeView partly --- src/function/index.ts | 23 ++- src/function/treeView/command.ts | 195 ++++++++++++++++++++++ src/function/treeView/common.ts | 11 +- src/function/treeView/index.ts | 77 ++++----- src/function/treeView/tree.ts | 278 +++++++++++++++++++++++++++++++ src/global/prjInfo.ts | 5 +- src/hdlFs/path.ts | 11 +- src/hdlParser/core.ts | 45 ++++- 8 files changed, 588 insertions(+), 57 deletions(-) create mode 100644 src/function/treeView/command.ts create mode 100644 src/function/treeView/tree.ts diff --git a/src/function/index.ts b/src/function/index.ts index e1b47e3..e67bc7a 100644 --- a/src/function/index.ts +++ b/src/function/index.ts @@ -1,7 +1,8 @@ import * as vscode from 'vscode'; import * as hdlDoc from './hdlDoc'; -import * as Sim from './sim'; +import * as sim from './sim'; +import * as treeView from './treeView'; function registerDocumentation(context: vscode.ExtensionContext) { vscode.commands.registerCommand('digital-ide.hdlDoc.showWebview', hdlDoc.showDocWebview); @@ -11,14 +12,28 @@ function registerDocumentation(context: vscode.ExtensionContext) { function registerSimulation(context: vscode.ExtensionContext) { - vscode.commands.registerCommand('digital-ide.tool.instance', Sim.instantiation); - vscode.commands.registerCommand('digital-ide.tool.testbench', Sim.testbench); - vscode.commands.registerCommand('digital-ide.tool.icarus.simulateFile', Sim.Icarus.simulateFile); + vscode.commands.registerCommand('digital-ide.tool.instance', sim.instantiation); + vscode.commands.registerCommand('digital-ide.tool.testbench', sim.testbench); + vscode.commands.registerCommand('digital-ide.tool.icarus.simulateFile', sim.Icarus.simulateFile); } function registerAllCommands(context: vscode.ExtensionContext) { registerDocumentation(context); registerSimulation(context); + registerTreeView(context); +} + +function registerTreeView(context: vscode.ExtensionContext) { + // register normal tree + vscode.window.registerTreeDataProvider('digital-ide.treeView.arch', treeView.moduleTreeProvider); + vscode.window.registerTreeDataProvider('digital-ide.treeView.tool', treeView.toolTreeProvider); + vscode.window.registerTreeDataProvider('digital-ide.treeView.hardware', treeView.hardwareTreeProvider); + vscode.window.registerTreeDataProvider('digital-ide.treeView.software', treeView.softwareTreeProvider); + + // constant used in tree + vscode.commands.executeCommand('setContext', 'TOOL-tree-expand', false); + + } diff --git a/src/function/treeView/command.ts b/src/function/treeView/command.ts new file mode 100644 index 0000000..3280c50 --- /dev/null +++ b/src/function/treeView/command.ts @@ -0,0 +1,195 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import * as vscode from 'vscode'; +import { getIconConfig } from '../../hdlFs/icons'; + +interface CommandDataItem { + name: string, + cmd: string, + icon: string, + tip: string, + children: any[] +} + +type CommandConfig = Record; + +class BaseCommandTreeProvider implements vscode.TreeDataProvider { + config: CommandConfig; + contextValue: 'HARD' | 'SOFT' | 'TOOL'; + + constructor(config: CommandConfig, contextValue: 'HARD' | 'SOFT' | 'TOOL') { + this.config = config; + this.contextValue = contextValue; + } + + // 根据对象遍历属性,返回CommandDataItem数组 + public makeCommandDataItem(object: any): CommandDataItem[] { + const childDataItemList = []; + for (const key of Object.keys(object)) { + const el = object[key]; + const dataItem: CommandDataItem = { name: key, cmd: el.cmd, icon: el.icon, tip: el.tip, children: el.children }; + childDataItemList.push(dataItem); + } + return childDataItemList; + } + + public getChildren(element: CommandDataItem): CommandDataItem[] { + if (element) { + if (element.children) { + return this.makeCommandDataItem(element.children); + } else { + return []; + } + } else { // 第一层 + return this.makeCommandDataItem(this.config); + } + } + + + // 根据输入的CommandDataItem转化为vscode.TreeItem + getTreeItem(element: CommandDataItem): vscode.TreeItem | Thenable { + const childNum = Object.keys(element.children).length; + const treeItem = new vscode.TreeItem( + element.name, + childNum === 0 ? + vscode.TreeItemCollapsibleState.None : + vscode.TreeItemCollapsibleState.Collapsed + ); + treeItem.contextValue = this.contextValue; + treeItem.command = { + title: element.cmd, + command: element.cmd, + }; + + treeItem.tooltip = element.tip; + + treeItem.iconPath = getIconConfig(element.icon); + + return treeItem; + } +}; + +class HardwareTreeProvider extends BaseCommandTreeProvider { + constructor() { + const config: CommandConfig = { + Launch: { + cmd: 'HARD.Launch', + icon: 'cmd', + tip: 'Launch FPGA development assist function' + }, + Simulate: { + cmd: 'HARD.Simulate', + icon: 'toolBox', + tip: 'Launch the manufacturer Simulation', + children: { + CLI: { + cmd: 'HARD.simCLI', + icon: 'branch', + tip: 'Launch the manufacturer Simulation in CLI' + }, + GUI: { + cmd: 'HARD.simGUI', + icon: 'branch', + tip: 'Launch the manufacturer Simulation in GUI' + }, + } + }, + Refresh: { + cmd: 'HARD.Refresh', + icon: 'cmd', + tip: 'Refresh the current project file' + }, + Build: { + cmd: 'HARD.Build', + icon: 'toolBox', + tip: 'Build the current fpga project', + children: { + Synth: { + cmd: 'HARD.Synth', + icon: 'branch', + tip: 'Synth the current project' + }, + Impl: { + cmd: 'HARD.Impl', + icon: 'branch', + tip: 'Impl the current project' + }, + BitStream: { + cmd: 'HARD.Bit', + icon: 'branch', + tip: 'Generate the BIT File' + }, + } + }, + Program: { + cmd: 'HARD.Program', + icon: 'cmd', + tip: 'Download the bit file into the device' + }, + GUI: { + cmd: 'HARD.GUI', + icon: 'cmd', + tip: 'Open the GUI' + }, + Exit: { + cmd: 'HARD.Exit', + icon: 'cmd', + tip: 'Exit the current project' + } + }; + + super(config, 'HARD'); + } +}; + +class SoftwareTreeProvider extends BaseCommandTreeProvider { + constructor() { + const config: CommandConfig = { + Launch: { + cmd: 'SOFT.Launch', + icon: 'cmd', + tip: 'Launch SDK development assist function' + }, + Build: { + cmd: 'SOFT.Launch', + icon: 'cmd', + tip: 'Build the current SDK project' + }, + Download: { + cmd: 'SOFT.Launch', + icon: 'cmd', + tip: 'Download the boot file into the device' + }, + }; + + super(config, 'SOFT'); + } +} + +class ToolTreeProvider extends BaseCommandTreeProvider { + constructor() { + const config: CommandConfig = { + Clean: { + cmd: 'TOOL.Clean', + icon: 'clean', + tip: 'Clean the current project' + } + }; + super(config, 'TOOL'); + } +} + + +const hardwareTreeProvider = new HardwareTreeProvider(); +const softwareTreeProvider = new SoftwareTreeProvider(); +const toolTreeProvider = new ToolTreeProvider(); + +export { + hardwareTreeProvider, + softwareTreeProvider, + toolTreeProvider, +}; \ No newline at end of file diff --git a/src/function/treeView/common.ts b/src/function/treeView/common.ts index 022da96..27744da 100644 --- a/src/function/treeView/common.ts +++ b/src/function/treeView/common.ts @@ -43,9 +43,16 @@ const xilinx = new Set([ ]); -enum ItemMode { vhdl, systemverilog, verilog, remote, cells }; +const itemModes = new Set([ + 'vhdl', 'systemverilog', 'verilog', 'remote', 'cells' +]); +const otherModes = new Set([ + 'src', 'sim', 'File Error', 'cells' +]); export { - xilinx + xilinx, + itemModes, + otherModes }; \ No newline at end of file diff --git a/src/function/treeView/index.ts b/src/function/treeView/index.ts index 91428c8..a012b96 100644 --- a/src/function/treeView/index.ts +++ b/src/function/treeView/index.ts @@ -1,53 +1,38 @@ import * as vscode from 'vscode'; -import { AbsPath } from '../../global'; +import { hdlPath } from '../../hdlFs'; -interface ModuleDataItem { - icon: string, // 图标 - name: string, // module name - type: string, - path: AbsPath, // path of the file - parent: ModuleDataItem | null // parent file +import { hardwareTreeProvider, softwareTreeProvider, toolTreeProvider } from './command'; +import { moduleTreeProvider, ModuleDataItem } from './tree'; + + +function openFileByUri(uri: string) { + if (hdlPath.exist(uri)) { + vscode.window.showTextDocument(vscode.Uri.file(uri)); + } } -interface SelectTop { - src: any, - sim: any +function refreshArchTree(element: ModuleDataItem) { + // TODO : diff and optimize + moduleTreeProvider.refresh(element); +} + +function expandTreeView() { + vscode.commands.executeCommand('setContext', 'TOOL-tree-expand', false); +} + +function collapseTreeView() { + vscode.commands.executeCommand('workbench.actions.treeView.TOOL-tree-arch.collapseAll'); + vscode.commands.executeCommand('setContext', 'TOOL-tree-expand', true); } -class ModuleTreeProvider implements vscode.TreeDataProvider { - treeEventEmitter: vscode.EventEmitter; - treeEvent: vscode.Event; - - constructor() { - this.treeEventEmitter = new vscode.EventEmitter(); - this.treeEvent = this.treeEventEmitter.event; - - - } - - - getTreeItem(element: ModuleDataItem): vscode.TreeItem | Thenable { - - } - - getChildren(element?: ModuleDataItem | undefined): vscode.ProviderResult { - if (element) { - const name = element.name; - if (name === 'sim' || name === 'src') { - element.parent = null; - - } - } else { - - } - } - - getParent(element: ModuleDataItem): vscode.ProviderResult { - - } - - getTopModuleItemList(element: ModuleDataItem): ModuleDataItem[] { - - } -} \ No newline at end of file +export { + hardwareTreeProvider, + softwareTreeProvider, + toolTreeProvider, + moduleTreeProvider, + expandTreeView, + collapseTreeView, + openFileByUri, + refreshArchTree +}; \ No newline at end of file diff --git a/src/function/treeView/tree.ts b/src/function/treeView/tree.ts new file mode 100644 index 0000000..15ef0fd --- /dev/null +++ b/src/function/treeView/tree.ts @@ -0,0 +1,278 @@ +import * as vscode from 'vscode'; + +import { AbsPath, MainOutput, opeParam, ReportType } from '../../global'; +import { SimPath, SrcPath } from '../../global/prjInfo'; +import { HdlInstance, hdlParam } from '../../hdlParser/core'; +import { HdlFileType } from '../../hdlParser/common'; +import { hdlFile, hdlPath } from '../../hdlFs'; +import { xilinx, itemModes, otherModes } from './common'; +import { getIconConfig } from '../../hdlFs/icons'; + +let needExpand = true; + +interface ModuleDataItem { + icon: string, // 图标 + name: string, // module name + type: string, + path: AbsPath | undefined, // path of the file + parent: ModuleDataItem | null // parent file +} + +interface FirstTopItem { + name: string, + path: AbsPath | undefined +} + +interface FirstTop { + src: FirstTopItem | null, + sim: FirstTopItem | null +} + +function canExpandable(element: ModuleDataItem) { + if (element.icon === 'src' || element.icon === 'sim') { // src and sim can expand anytime + return true; + } else { + const modulePath = element.path; + if (!modulePath) { // unsolved module cannot expand + return false; + } + const moduleName = element.name; + if (!hdlParam.hasHdlModule(modulePath, moduleName)) { // test or bug + return false; + } + const module = hdlParam.getHdlModule(modulePath, moduleName); + if (module) { + return module.getInstanceNum() > 0; + } else { + return false; + } + } +} + + +class ModuleTreeProvider implements vscode.TreeDataProvider { + treeEventEmitter: vscode.EventEmitter; + treeEvent: vscode.Event; + firstTop: FirstTop; + srcRootItem: ModuleDataItem; + simRootItem: ModuleDataItem; + + constructor() { + this.treeEventEmitter = new vscode.EventEmitter(); + this.treeEvent = this.treeEventEmitter.event; + this.firstTop = { + src: null, + sim: null, + }; + this.srcRootItem = {icon: 'src', type: HdlFileType.Src, name: 'src', path: '', parent: null}; + this.simRootItem = {icon: 'sim', type: HdlFileType.Sim, name: 'sim', path: '', parent: null}; + + } + + public refresh(element?: ModuleDataItem) { + if (element) { + this.treeEventEmitter.fire(element); + } else { + // refresh all the root in default + this.refreshSim(); + this.refreshSrc(); + } + } + + public refreshSrc() { + this.treeEventEmitter.fire(this.srcRootItem); + } + + public refreshSim() { + this.treeEventEmitter.fire(this.simRootItem); + } + + + public getTreeItem(element: ModuleDataItem): vscode.TreeItem | Thenable { + let itemName = element.name; + if (itemModes.has(element.icon)) { + itemName = `${element.type}(${itemName})`; + } + + const expandable = canExpandable(element); + let collapsibleState; + if (!expandable) { + collapsibleState = vscode.TreeItemCollapsibleState.None; + } else if (needExpand) { + collapsibleState = vscode.TreeItemCollapsibleState.Expanded; + } else { + collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; + } + + const treeItem = new vscode.TreeItem(itemName, collapsibleState); + // set contextValue file -> simulate / netlist + if (otherModes.has(element.icon)) { + treeItem.contextValue = 'other'; + } else { + treeItem.contextValue = 'file'; + } + + // set tooltip + treeItem.tooltip = element.path; + if (!treeItem.tooltip) { + treeItem.tooltip = "can't find the module of this instance"; + } + + // set iconPath + treeItem.iconPath = getIconConfig(element.icon); + + // set command + treeItem.command = { + title: "Open this HDL File", + // TODO : 修改这里的指令前缀 + command: 'TOOL.tree.arch.openFile', + arguments: [element.path], + }; + + return treeItem; + } + + public getChildren(element?: ModuleDataItem | undefined): vscode.ProviderResult { + if (element) { + const name = element.name; + if (name === 'sim' || name === 'src') { + element.parent = null; + return this.getTopModuleItemList(element); + } else { + return this.getInstanceItemList(element); + } + } else { + // use roots in default + return [ + this.srcRootItem, + this.simRootItem, + ]; + } + } + + public getParent(element: ModuleDataItem): vscode.ProviderResult { + return element.parent; + } + + public getTopModuleItemList(element: ModuleDataItem): ModuleDataItem[] { + // src or sim + const hardwarePath = opeParam.prjInfo.arch.hardware; + const moduleType = element.name as keyof (SrcPath & SimPath); + + const topModules = hdlParam.getTopModulesByType(moduleType); + const topModuleItemList = topModules.map(module => ({ + icon: 'top', + type: moduleType, + name: module.name, + path: module.path, + parent: element, + })); + + if (topModuleItemList.length > 0) { + const type = moduleType as keyof FirstTop; + + const firstTop = topModuleItemList[0]; + if (!this.firstTop[type]) { + this.setFirstTop(type, firstTop.name, firstTop.path); + } + const name = this.firstTop[type]!.name; + const path = this.firstTop[type]!.path; + const icon = this.makeFirstTopIconName(type); + const parent = element; + + const tops = topModuleItemList.filter(item => item.path === path && item.name === name); + const adjustItemList = []; + if (tops.length > 0 || !hdlParam.hasHdlModule(path, name)) { + // mean that the seleted top is an original top module + // push it to the top of the *topModuleItemList* + const headItem = tops[0] ? tops[0] : topModuleItemList[0]; + + headItem.icon = icon; + adjustItemList.push(headItem); + for (const item of topModuleItemList) { + if (item !== headItem) { + adjustItemList.push(item); + } + } + } else { + // mean the selected top is not an original top module + // create it and add it to the head of *topModuleItemList* + const selectedTopItem: ModuleDataItem = {icon, type, name, path, parent}; + adjustItemList.push(selectedTopItem); + adjustItemList.push(...topModuleItemList); + } + return adjustItemList; + } + + return topModuleItemList; + } + + // 获取当前模块下的子模块 + public getInstanceItemList(element: ModuleDataItem): ModuleDataItem[] { + if (!element.path) { + return []; + } + + const moduleDataItemList: ModuleDataItem[] = []; + const targetModule = hdlParam.getHdlModule(element.path, element.name); + + if (targetModule) { + for (const instance of targetModule.getAllInstances()) { + const item: ModuleDataItem = { + icon: 'file', + type: instance.name, + name: instance.type, + path: instance.instModPath, + parent: element + }; + + if (item.type === element.type && // 防止递归 + item.name === element.name && + item.path === element.path) { + continue; + } + item.icon = this.judgeIcon(item, instance); + + moduleDataItemList.push(item); + } + } else { + MainOutput.report(`cannot find ${element} in hdlParam when constructing treeView`, ReportType.Error); + } + + return moduleDataItemList; + } + + public setFirstTop(type: keyof FirstTop, name: string, path: AbsPath | undefined) { + this.firstTop[type] = {name, path}; + } + + + private makeFirstTopIconName(type: string): string { + return 'current-' + type + '-top'; + } + + private judgeIcon(item: ModuleDataItem, instance: HdlInstance): string { + const workspacePath = opeParam.workspacePath; + if (hdlPath.exist(item.path)) { + if (!item.path?.includes(workspacePath)) { + return 'remote'; + } else { + const langID = hdlFile.getLanguageId(item.path); + return langID; + } + } else { + if (xilinx.has(instance.type)) { + return 'cells'; + } else { + return 'File Error'; + } + } + } +} + +const moduleTreeProvider = new ModuleTreeProvider(); + +export { + moduleTreeProvider, + ModuleDataItem +}; \ No newline at end of file diff --git a/src/global/prjInfo.ts b/src/global/prjInfo.ts index 8c8d7a6..7b972d2 100644 --- a/src/global/prjInfo.ts +++ b/src/global/prjInfo.ts @@ -466,5 +466,8 @@ export { Library, RawPrjInfo, toSlash, - resolve + resolve, + SimPath, + SrcPath, + DataPath }; \ No newline at end of file diff --git a/src/hdlFs/path.ts b/src/hdlFs/path.ts index 1cd29ee..db46279 100644 --- a/src/hdlFs/path.ts +++ b/src/hdlFs/path.ts @@ -1,4 +1,5 @@ import * as fspath from 'path'; +import * as fs from 'fs'; import { AbsPath, RelPath } from '../global'; @@ -76,6 +77,13 @@ function filename(path: AbsPath | RelPath): string { return fspath.basename(path, ext); } +function exist(path: AbsPath | undefined): boolean { + if (!path) { + return false; + } + return fs.existsSync(path); +} + export { toSlash, rel2abs, @@ -83,5 +91,6 @@ export { resolve, filename, extname, - basename + basename, + exist }; \ No newline at end of file diff --git a/src/hdlParser/core.ts b/src/hdlParser/core.ts index afb2104..3154dfa 100644 --- a/src/hdlParser/core.ts +++ b/src/hdlParser/core.ts @@ -1,4 +1,4 @@ -import { AbsPath } from '../global'; +import { AbsPath, opeParam } from '../global'; import { HdlLangID } from '../global/enum'; import { MainOutput, ReportType } from '../global/outputChannel'; @@ -39,7 +39,10 @@ class HdlParam { this.pathToHdlFiles.set(path, hdlFile); } - public hasHdlModule(path: AbsPath, name: string): boolean { + public hasHdlModule(path: AbsPath | undefined, name: string): boolean { + if (!path) { + return false; + } const hdlFile = this.getHdlFile(path); if (!hdlFile) { return false; @@ -208,7 +211,43 @@ class HdlParam { hdlFile.makeInstance(); } } - + + public getTopModulesByType(type: string): HdlModule[] { + const hardware = opeParam.prjInfo.arch.hardware; + if (hardware.sim === hardware.src) { + return this.getAllTopModules(); + } + + switch (type) { + case common.HdlFileType.Src: return this.getSrcTopModules(); + case common.HdlFileType.Sim: return this.getSimTopModules(); + default: return []; + } + } + + public getSrcTopModules(): HdlModule[] { + const srcTopModules = this.srcTopModules; + if (!srcTopModules) { + return []; + } + const moduleFiles = []; + for (const module of srcTopModules) { + moduleFiles.push(module); + } + return moduleFiles; + } + + public getSimTopModules(): HdlModule[] { + const simTopModules = this.simTopModules; + if (!simTopModules) { + return []; + } + const moduleFiles = []; + for (const module of simTopModules) { + moduleFiles.push(module); + } + return moduleFiles; + } }; const hdlParam = new HdlParam();