From 509df3b4f44b51b763419cb6d81ff3e8a21b9236 Mon Sep 17 00:00:00 2001 From: Andrew Nolte Date: Tue, 9 Apr 2024 18:26:50 -0400 Subject: [PATCH] index include paths, do macros, refactor module inst --- package.json | 9 ++ src/commands/ModuleInstantiation.ts | 195 +++++------------------- src/config.ts | 10 ++ src/ctags.ts | 48 +++++- src/extension.ts | 14 +- src/parsers/ctagsParser.ts | 108 ++++++++++++- src/providers/CompletionItemProvider.ts | 24 +-- 7 files changed, 230 insertions(+), 178 deletions(-) create mode 100644 src/config.ts diff --git a/package.json b/package.json index c555fcd..312f1cb 100644 --- a/package.json +++ b/package.json @@ -227,6 +227,15 @@ "configuration": { "title": "Verilog configuration", "properties": { + "verilog.includes": { + "scope": "machine-overridable", + "type": "array", + "items": { + "type": "string" + }, + "default": "", + "description": "verilog include directories, applied to each tool" + }, "verilog.subdir": { "scope": "window", "type": "string", diff --git a/src/commands/ModuleInstantiation.ts b/src/commands/ModuleInstantiation.ts index 49e6787..27127f3 100644 --- a/src/commands/ModuleInstantiation.ts +++ b/src/commands/ModuleInstantiation.ts @@ -5,146 +5,57 @@ import * as vscode from 'vscode'; import { logger } from '../extension'; import { CtagsParser, Symbol } from '../parsers/ctagsParser'; import { getWorkspaceFolder } from '../utils'; +import { CtagsManager } from '../ctags'; +import { Logger } from '../logger'; -export async function instantiateModuleInteract() { - if (vscode.window.activeTextEditor === undefined) { - return; - } - let filePath = path.dirname(vscode.window.activeTextEditor.document.fileName); - let srcPath = await selectFile(filePath); - if (srcPath === undefined) { - return; - } - let snippet = await instantiateModule(srcPath); - if (snippet === undefined) { - return; - } - vscode.window.activeTextEditor?.insertSnippet(snippet); -} -export async function instantiateModule( - srcpath: string -): Promise { - // Using Ctags to get all the modules in the file - let moduleName: string | undefined = undefined; - let file: vscode.TextDocument | undefined = vscode.window.activeTextEditor?.document; - if (file === undefined) { - logger.warn('file undefined... returning'); - return; +export class CommandExcecutor { + private logger: Logger; + private ctagsManager: CtagsManager; + constructor(logger: Logger, ctagsManager: CtagsManager) { + this.logger = logger; + this.ctagsManager = ctagsManager; } - let ctags: ModuleTags = new ModuleTags(logger, file); - logger.info('Executing ctags for module instantiation'); - let output = await ctags.execCtags(srcpath); - await ctags.buildSymbolsList(output); + - let modules: Symbol[] = ctags.symbols.filter((tag) => tag.type === 'module' || tag.type === 'interface'); - // No modules found - if (modules.length <= 0) { - vscode.window.showErrorMessage('Verilog-HDL/SystemVerilog: No modules found in the file'); - return undefined; - } - let module: Symbol = modules[0]; - if (modules.length > 1) { - // many modules found - moduleName = await vscode.window.showQuickPick( - ctags.symbols.filter((tag) => tag.type === 'module').map((tag) => tag.name), - { - placeHolder: 'Choose a module to instantiate', - } - ); - if (moduleName === undefined) { - return undefined; + async instantiateModuleInteract() { + if (vscode.window.activeTextEditor === undefined) { + return; } - module = modules.filter((tag) => tag.name === moduleName)[0]; - } - - return moduleSnippet(ctags, module, true); -} - -export async function moduleFromFile(srcpath: string): Promise { - let file: vscode.TextDocument | undefined = vscode.window.activeTextEditor?.document; - if (file === undefined) { - logger.warn('file undefined... returning'); - return; - } - let ctags: ModuleTags = new ModuleTags(logger, file); - logger.info('Executing ctags for module instantiation'); - let output = await ctags.execCtags(srcpath); - await ctags.buildSymbolsList(output); - - let modules: Symbol[] = ctags.symbols.filter((tag) => tag.type === 'module' || tag.type === 'interface'); - // No modules found - if (modules.length <= 0) { - vscode.window.showErrorMessage('Verilog-HDL/SystemVerilog: No modules found in the file'); - return undefined; - } - - return moduleSnippet(ctags, modules[0], false); -} - -export async function moduleSnippet(ctags: CtagsParser, module: Symbol, fullModule: boolean) { - let portsName: string[] = []; - let parametersName: string[] = []; - - let scope = module.parentScope !== '' ? module.parentScope + '.' + module.name : module.name; - let ports: Symbol[] = ctags.symbols.filter( - (tag) => tag.type === 'port' && tag.parentScope === scope - ); - portsName = ports.map((tag) => tag.name); - let params: Symbol[] = ctags.symbols.filter( - (tag) => tag.type === 'parameter' && tag.parentScope === scope - ); - parametersName = params.map((tag) => tag.name); - logger.info('Module name: ' + module.name); - let paramString = ``; - if (parametersName.length > 0) { - paramString = `\n${instantiatePort(parametersName)}`; - } - logger.info('portsName: ' + portsName.toString()); - - if (fullModule) { - return new vscode.SnippetString() - .appendText(module.name + ' ') - .appendText('#(') - .appendText(paramString) - .appendText(') ') - .appendPlaceholder('u_') - .appendPlaceholder(`${module.name} (\n`) - .appendText(instantiatePort(portsName)) - .appendText(');\n'); - } - return ( - new vscode.SnippetString() - // .appendText('#(') - .appendText(paramString) - .appendText(') ') - .appendPlaceholder('u_') - .appendPlaceholder(`${module.name} (\n`) - .appendText(instantiatePort(portsName)) - ); -} + let srcPath = await selectFile(path.dirname(vscode.window.activeTextEditor.document.fileName)); + if (srcPath === undefined) { + return; + } + let doc = await vscode.workspace.openTextDocument(srcPath); + let ctags = this.ctagsManager.getCtags(doc); -function instantiatePort(ports: string[]): string { - let port = ''; - let maxLen = 0; - for (let i = 0; i < ports.length; i++) { - if (ports[i].length > maxLen) { - maxLen = ports[i].length; + let modules: Symbol[] = await ctags.getModules(); + // No modules found + if (modules.length <= 0) { + vscode.window.showErrorMessage('Verilog-HDL/SystemVerilog: No modules found in the file'); + return undefined; } - } - // .NAME(NAME) - for (let i = 0; i < ports.length; i++) { - let element = ports[i]; - let padding = maxLen - element.length; - element = element + ' '.repeat(padding); - port += `\t.${element}(${ports[i]})`; - if (i !== ports.length - 1) { - port += ','; + let module: Symbol = modules[0]; + if (modules.length > 1) { + let moduleName = await vscode.window.showQuickPick( + modules.map((sym) => sym.name), + { + placeHolder: 'Choose a module to instantiate', + } + ); + if (moduleName === undefined) { + return undefined; + } + module = modules.filter((tag) => tag.name === moduleName)[0]; + } + let snippet = ctags.getModuleSnippet(module, true); + if (snippet === undefined) { + return; } - port += '\n'; + vscode.window.activeTextEditor?.insertSnippet(snippet); } - return port; + } async function selectFile(currentDir?: string): Promise { @@ -202,27 +113,3 @@ function getDirectories(srcpath: string): string[] { function getFiles(srcpath: string): string[] { return fs.readdirSync(srcpath).filter((file) => fs.statSync(path.join(srcpath, file)).isFile()); } - -class ModuleTags extends CtagsParser { - buildSymbolsList(tags: string): Promise { - if (tags === '') { - return Promise.resolve(); - } - // Parse ctags output - let lines: string[] = tags.split(/\r?\n/); - lines.forEach((line) => { - if (line !== '') { - let tag: Symbol | undefined = this.parseTagLine(line); - if (tag === undefined) { - return; - } - // add only modules and ports - if (tag.type === 'interface' || tag.type === 'module' || tag.type === 'port' || tag.type === 'parameter') { - this.symbols.push(tag); - } - } - }); - // skip finding end tags - return Promise.resolve(); - } -} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..25cfdfc --- /dev/null +++ b/src/config.ts @@ -0,0 +1,10 @@ +import { getVSCodeDownloadUrl } from "@vscode/test-electron/out/util"; + +import * as vscode from 'vscode'; + + +export class Config { + static getIncludePaths(): string[] { + return vscode.workspace.getConfiguration().get('verilog.includes', []); + } +} \ No newline at end of file diff --git a/src/ctags.ts b/src/ctags.ts index a0d49b1..e144c58 100644 --- a/src/ctags.ts +++ b/src/ctags.ts @@ -3,6 +3,7 @@ import * as vscode from 'vscode'; import { Logger } from './logger'; import { CtagsParser, Symbol } from './parsers/ctagsParser'; import { getParentText, getPrevChar, getWorkspaceFolder } from './utils'; +import { Config } from './config'; // Internal representation of a symbol @@ -11,6 +12,9 @@ export class CtagsManager { private logger: Logger; private searchPrefix: string; + // top level + private symbolMap: Map = new Map(); + constructor(logger: Logger, hdlDir: string) { this.logger = logger; this.logger.info('ctags manager configure'); @@ -22,6 +26,34 @@ export class CtagsManager { vscode.workspace.onDidSaveTextDocument(this.onSave.bind(this)); vscode.workspace.onDidCloseTextDocument(this.onClose.bind(this)); + this.indexIncludes(); + vscode.workspace.onDidChangeConfiguration(async (e) => { + if (e.affectsConfiguration('verilog.includes')) { + this.indexIncludes(); + } + }); + + } + + async indexIncludes(): Promise { + this.logger.info('indexIncludes'); + Config.getIncludePaths().forEach(async (path: string) => { + let files: vscode.Uri[] = await this.findFiles(`${path}/*.{sv,svh}`); + + files.forEach(async (file: vscode.Uri) => { + this.logger.info(`indexing ${file}`); + let doc = await vscode.workspace.openTextDocument(file); + let syms = await this.getCtags(doc).getSymbols(); + syms.forEach((element: Symbol) => { + // this.logger.info(`adding ${element.name}`); + if(this.symbolMap.has(element.name)) { + this.symbolMap.get(element.name)?.push(element); + } else { + this.symbolMap.set(element.name, [element]); + } + }); + }); + }); } getCtags(doc: vscode.TextDocument): CtagsParser { @@ -42,13 +74,17 @@ export class CtagsManager { ctags.clearSymbols(); } - async findModule(moduleName: string): Promise { + async findFiles(pattern: string): Promise { let ws = getWorkspaceFolder(); if (ws === undefined) { - return undefined; + return []; } - let searchPattern = new vscode.RelativePattern(ws, `${this.searchPrefix}/${moduleName}.{sv,v}`); - let files = await vscode.workspace.findFiles(searchPattern); + let searchPattern = new vscode.RelativePattern(ws, pattern); + return await vscode.workspace.findFiles(searchPattern); + } + + async findModule(moduleName: string): Promise { + let files = await this.findFiles(`${this.searchPrefix}/${moduleName}.{sv,v}`); if (files.length === 0) { return undefined; } @@ -95,6 +131,10 @@ export class CtagsManager { } } + if (this.symbolMap.has(targetText)){ + return this.symbolMap.get(targetText) ?? []; + } + const results: Symbol[][] = await Promise.all([ // find def in current document parser.getSymbols({ name: targetText }), diff --git a/src/extension.ts b/src/extension.ts index 967cdab..b9c2bb1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,6 +12,7 @@ import * as DefinitionProvider from './providers/DefinitionProvider'; import * as DocumentSymbolProvider from './providers/DocumentSymbolProvider'; import * as FormatProvider from './providers/FormatPrivider'; import * as HoverProvider from './providers/HoverProvider'; +import { CommandExcecutor } from './commands/ModuleInstantiation'; export var logger: Logger; // Global logger var ctagsManager: CtagsManager; @@ -113,18 +114,21 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( vscode.workspace.onDidCloseTextDocument(lintManager.removeFileDiagnostics, lintManager) ); - context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(lintManager.configLinter, lintManager)); - - + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration(lintManager.configLinter, lintManager) + ); ///////////////////////////////////////////// // Register Commands ///////////////////////////////////////////// + + let commandExcecutor = new CommandExcecutor(logger.getChild('CommandExcecutor'), ctagsManager); vscode.commands.registerCommand( 'verilog.instantiateModule', - ModuleInstantiation.instantiateModuleInteract + commandExcecutor.instantiateModuleInteract, + commandExcecutor ); - vscode.commands.registerCommand('verilog.lint', lintManager.runLintTool); + vscode.commands.registerCommand('verilog.lint', lintManager.runLintTool, lintManager); ///////////////////////////////////////////// // Language Servers diff --git a/src/parsers/ctagsParser.ts b/src/parsers/ctagsParser.ts index b14a2fe..f0adc42 100644 --- a/src/parsers/ctagsParser.ts +++ b/src/parsers/ctagsParser.ts @@ -1,5 +1,4 @@ -import * as child_process from 'child_process'; -import { ExecException } from 'child_process'; +import * as child from 'child_process'; import * as vscode from 'vscode'; import { Logger } from '../logger'; @@ -76,7 +75,33 @@ export class Symbol { ); } + isMacro(): boolean { + let charnum = this.getIdRange().start.character; + if (charnum === undefined) { + return false; + } + let firstchar = this.doc.lineAt(this.line).firstNonWhitespaceCharacterIndex; + if(this.doc.getText(new vscode.Range(this.line, firstchar, this.line, firstchar+7)) === '`define'){ + return true; + } + return false; + } + getHoverText(): string { + if (this.isMacro()){ + // macro definitions should show the whole macro + let code = ''; + for (let i = this.line; i < this.doc.lineCount; i++) { + const lineText = this.doc.lineAt(i).text; + code += lineText; + if (!lineText.endsWith('\\')) { + break; + } + code += '\n'; + } + return code; + } + let code = this.doc .lineAt(this.line) .text.trim() @@ -87,6 +112,7 @@ export class Symbol { return code; } + getDefinitionLink(): vscode.DefinitionLink { return { targetUri: this.doc.uri, @@ -222,6 +248,11 @@ export class CtagsParser { return this.symbols; } + async getModules(): Promise { + let syms = await this.getSymbols(); + return syms.filter((tag) => tag.type === 'module' || tag.type === 'interface'); + } + async execCtags(filepath: string): Promise { let command: string = this.binPath + @@ -230,9 +261,9 @@ export class CtagsParser { '"'; this.logger.info('Executing Command: ' + command); return new Promise((resolve, _reject) => { - child_process.exec( + child.exec( command, - (_error: ExecException | null, stdout: string, _stderr: string) => { + (_error: child.ExecException | null, stdout: string, _stderr: string) => { resolve(stdout); } ); @@ -319,11 +350,11 @@ export class CtagsParser { } endPosition = this.doc.positionAt(match.index + match[0].length - 1); - let m_type: string = match[1]; + let type: string = match[1]; // get the starting symbols of the same type // doesn't check for begin...end blocks let s = this.symbols.filter((sym) => { - return sym.type === m_type && sym.startPosition.isBefore(endPosition) && !sym.isValid; + return sym.type === type && sym.startPosition.isBefore(endPosition) && !sym.isValid; }); if (s.length === 0) { continue; @@ -352,4 +383,69 @@ export class CtagsParser { let output = await this.execCtags(this.doc.uri.fsPath); await this.buildSymbolsList(output); } + + + getModuleSnippet(module: Symbol, fullModule: boolean) { + let portsName: string[] = []; + let parametersName: string[] = []; + + let scope = module.parentScope !== '' ? module.parentScope + '.' + module.name : module.name; + let ports: Symbol[] = this.symbols.filter( + (tag) => tag.type === 'port' && tag.parentScope === scope + ); + portsName = ports.map((tag) => tag.name); + let params: Symbol[] = this.symbols.filter( + (tag) => tag.type === 'parameter' && tag.parentScope === scope + ); + parametersName = params.map((tag) => tag.name); + let paramString = ``; + if (parametersName.length > 0) { + paramString = `\n${instantiatePort(parametersName)}`; + } + + if (fullModule) { + return new vscode.SnippetString() + .appendText(module.name + ' ') + .appendText('#(') + .appendText(paramString) + .appendText(') ') + .appendPlaceholder('u_') + .appendPlaceholder(`${module.name} (\n`) + .appendText(instantiatePort(portsName)) + .appendText(');\n'); + } + return ( + new vscode.SnippetString() + // .appendText('#(') + .appendText(paramString) + .appendText(') ') + .appendPlaceholder('u_') + .appendPlaceholder(`${module.name} (\n`) + .appendText(instantiatePort(portsName)) + ); + } } + + + +function instantiatePort(ports: string[]): string { + let port = ''; + let maxLen = 0; + for (let i = 0; i < ports.length; i++) { + if (ports[i].length > maxLen) { + maxLen = ports[i].length; + } + } + // .NAME(NAME) + for (let i = 0; i < ports.length; i++) { + let element = ports[i]; + let padding = maxLen - element.length; + element = element + ' '.repeat(padding); + port += `\t.${element}(${ports[i]})`; + if (i !== ports.length - 1) { + port += ','; + } + port += '\n'; + } + return port; +} \ No newline at end of file diff --git a/src/providers/CompletionItemProvider.ts b/src/providers/CompletionItemProvider.ts index 8ffdeca..9509048 100644 --- a/src/providers/CompletionItemProvider.ts +++ b/src/providers/CompletionItemProvider.ts @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT import * as vscode from 'vscode'; -import { moduleFromFile } from '../commands/ModuleInstantiation'; import { CtagsManager } from '../ctags'; import { Logger } from '../logger'; import { Symbol } from '../parsers/ctagsParser'; + export class VerilogCompletionItemProvider implements vscode.CompletionItemProvider { private logger: Logger; private ctagsManager: CtagsManager; @@ -22,20 +22,26 @@ export class VerilogCompletionItemProvider implements vscode.CompletionItemProvi this.logger.info('Module Completion requested'); let items: vscode.CompletionItem[] = []; - let newItem: vscode.CompletionItem = new vscode.CompletionItem( - 'module label', - vscode.CompletionItemKind.Module - ); - + let moduleRange = document.getWordRangeAtPosition(_position.translate(0, -3)); let moduleName = document.getText(moduleRange); - + let moduleDoc = await this.ctagsManager.findModule(moduleName); if (moduleDoc === undefined) { return []; } - let snippet = await moduleFromFile(moduleDoc.fileName); - newItem.insertText = snippet; + + let ctags = this.ctagsManager.getCtags(moduleDoc); + let modules = await ctags.getModules(); + if (modules.length === 0) { + return []; + } + let module = modules[0]; + let newItem: vscode.CompletionItem = new vscode.CompletionItem( + module.getHoverText(), + vscode.CompletionItemKind.Module + ); + newItem.insertText = ctags.getModuleSnippet(module, false); items.push(newItem); return items;