diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b8fb88..03ab503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,20 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ## [0.3.2] - 2023-11-01 Bug 修复 -- 修复文档化input, output处注释无法正常显示到文档的 bug +- 修复文档化 input, output 处注释无法正常显示到文档的 bug - 修复 iverilog 仿真功能中,将重复的路径作为编译参数编译的 bug -- 修复 iverilog 仿真功能中,将 `include 加入或去除后,无法通过仿真编译的 bug (没有更新 instance 的 instModPathStatus 属性) +- 修复 iverilog 仿真功能中,将 `include 加入或去除后,无法通过仿真编译的 bug (没有更新 instance 的 instModPathStatus 属性) +- 修复其他已知 bug -Feat +Change +- 将插件的工作状态显示在 vscode 下侧的状态栏上,利于用户了解目前的设置状态 +- 优化项目配置目录 + +Feature - 增加对 XDC,TCL 等脚本的 LSP 支持 -- 增加 verilog, vhdl, xdc, tcl 等语言的图标 -- 增加对于 vivado 的支持,用户可以通过添加 vivado 路径的方式(或者将 bin 文件夹添加到环境变量,默认路径为 C:\Xilinx\Vivado\2018.3\bin)来使用 vivado 的仿真和自动纠错 -- 增加对于 modelsim 的支持,用户可以通过添加 modelsim 安装路径(或者将 bin 文件夹添加到环境变量,默认路径为 C:\modeltech64_10.4\win64)来使用 vivado 的仿真和自动纠错 +- 增加 verilog, vhdl, xdc, tcl, vvp 等语言的工作区图标 +- 增加对于 vivado, modelsim, verilator 的支持,用户可以通过设置 `function.lsp.linter.vhdl.diagnostor`(设置 vhdl) 和 `function.lsp.linter.vlog.diagnostor`(设置 verilog) 来使用这些第三方工具的仿真和自动纠错。 +- 增加对于 TCL, XDC, VVP 等脚本的 LSP 和 语法高亮 支持 ## [0.1.23] - 2022-12-24 - Finish the css of documentation, see `./css/documentation.css` for detail. diff --git a/config/vvp.configuration.json b/config/vvp.configuration.json new file mode 100644 index 0000000..ad4745f --- /dev/null +++ b/config/vvp.configuration.json @@ -0,0 +1,17 @@ +{ + "comments": { + "lineComment": "#" + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + { "open": "(", "close": ")" }, + { "open": "[", "close": "]" }, + { "open": "'", "close": "'", "notIn": ["string"] }, + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "/*", "close": " */", "notIn": ["string"] } + ] +} \ No newline at end of file diff --git a/images/svg/dark/vlang.svg b/images/svg/dark/vlang.svg new file mode 100644 index 0000000..450afcb --- /dev/null +++ b/images/svg/dark/vlang.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/svg/dark/vvp.svg b/images/svg/dark/vvp.svg new file mode 100644 index 0000000..6059c47 --- /dev/null +++ b/images/svg/dark/vvp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/svg/light/vvp.svg b/images/svg/light/vvp.svg new file mode 100644 index 0000000..6059c47 --- /dev/null +++ b/images/svg/light/vvp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/package.json b/package.json index 7a1b401..539dfa7 100644 --- a/package.json +++ b/package.json @@ -33,11 +33,6 @@ "Snippets" ], "activationEvents": [ - "onLanguage:verilog", - "onLanguage:vhdl", - "onLanguage:systemverilog", - "onCommand:digital-ide.property-json.generate", - "onCommand:digital-ide.property-json.overwrite", "workspaceContains:.vscode/property.json" ], "contributes": { @@ -831,6 +826,21 @@ ], "configuration": "./config/link.configuration.json" }, + { + "id": "vvp", + "aliases": [ + "VivadoVerificationPlan" + ], + "extensions": [ + ".vvp", + ".VVP" + ], + "configuration": "./config/vvp.configuration.json", + "icon": { + "dark": "./images/svg/dark/vvp.svg", + "light": "./images/svg/light/vvp.svg" + } + }, { "id": "digital-ide-output", "mimetypes": [ @@ -863,7 +873,7 @@ { "language": "vhdl", "scopeName": "source.vhdl", - "path": "./syntaxes/vhdl.json" + "path": "./syntaxes/vhdl.tmLanguage.json" }, { "language": "verilog", @@ -875,6 +885,11 @@ "scopeName": "source.systemverilog", "path": "./syntaxes/systemverilog.json" }, + { + "language": "vvp", + "scopeName": "source.vvp", + "path": "./syntaxes/vvp.tmLanguage.json" + }, { "language": "digital-ide-output", "scopeName": "digital-ide.output", diff --git a/src/function/index.ts b/src/function/index.ts index c1ca646..0c0f8c7 100644 --- a/src/function/index.ts +++ b/src/function/index.ts @@ -78,16 +78,23 @@ function registerLsp(context: vscode.ExtensionContext) { vscode.languages.registerCompletionItemProvider(vlogSelector, lspCompletion.vlogMacroCompletionProvider, '`'); vscode.languages.registerCompletionItemProvider(vlogSelector, lspCompletion.vlogPositionPortProvider, '.'); vscode.languages.registerCompletionItemProvider(vlogSelector, lspCompletion.vlogCompletionProvider); - vscode.languages.registerDocumentSemanticTokensProvider(vlogSelector, lspDocSemantic.vlogDocSenmanticProvider, lspDocSemantic.vlogLegend); + // vscode.languages.registerDocumentSemanticTokensProvider(vlogSelector, lspDocSemantic.vlogDocSenmanticProvider, lspDocSemantic.vlogLegend); + + + + // vhdl lsp + + vscode.languages.registerCompletionItemProvider(vhdlSelector, lspCompletion.vhdlCompletionProvider); + // tcl lsp vscode.languages.registerCompletionItemProvider(tclSelector, lspCompletion.tclCompletionProvider); - lspLinter.vlogLinter.initialise(); + // lsp linter + // make first symbols in workspace lspCore.hdlSymbolStorage.initialise(); - - // vhdl lsp - + lspLinter.vlogLinterManager.initialise(); + lspLinter.vhdlLinterManager.initialise(); } diff --git a/src/function/lsp/completion/index.ts b/src/function/lsp/completion/index.ts index f6ccdb7..36bf3e4 100644 --- a/src/function/lsp/completion/index.ts +++ b/src/function/lsp/completion/index.ts @@ -1,4 +1,5 @@ import { vlogCompletionProvider, vlogIncludeCompletionProvider, vlogMacroCompletionProvider, vlogPositionPortProvider } from './vlog'; +import { vhdlCompletionProvider } from './vhdl'; import { tclCompletionProvider } from './tcl'; export { @@ -6,5 +7,6 @@ export { vlogIncludeCompletionProvider, vlogMacroCompletionProvider, vlogPositionPortProvider, + vhdlCompletionProvider, tclCompletionProvider }; \ No newline at end of file diff --git a/src/function/lsp/completion/vhdl.ts b/src/function/lsp/completion/vhdl.ts new file mode 100644 index 0000000..cf01522 --- /dev/null +++ b/src/function/lsp/completion/vhdl.ts @@ -0,0 +1,212 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; + +import * as util from '../util'; +import { hdlParam } from '../../../hdlParser'; +import { AbsPath, MainOutput, RelPath, ReportType } from '../../../global'; +import { Define, Include, RawSymbol } from '../../../hdlParser/common'; +import { HdlInstance, HdlModule } from '../../../hdlParser/core'; +import { vhdlKeyword } from '../util/keyword'; +import { hdlPath } from '../../../hdlFs'; +import { hdlSymbolStorage } from '../core'; + +class VhdlCompletionProvider implements vscode.CompletionItemProvider { + public async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext): Promise | null | undefined> { + // console.log('VhdlCompletionProvider'); + + try { + const filePath = hdlPath.toSlash(document.fileName); + + // 1. provide keyword + const completions = this.makeKeywordItems(document, position); + completions.push(...this.makeCompilerKeywordItems(document, position)); + completions.push(...this.makeSystemKeywordItems(document, position)); + + const symbolResult = await hdlSymbolStorage.getSymbol(filePath); + if (!symbolResult) { + return completions; + } + + console.log('vhdl symbol result'); + + + // locate at one module + const scopeSymbols = util.filterSymbolScope(position, symbolResult.content); + if (!scopeSymbols || + !scopeSymbols.module || + !hdlParam.hasHdlModule(filePath, scopeSymbols.module.name)) { + // MainOutput.report('Fail to get HdlModule ' + filePath + ' ' + scopeSymbols?.module.name, ReportType.Debug); + return completions; + } + + // find wrapper module + const currentModule = hdlParam.getHdlModule(filePath, scopeSymbols.module.name); + if (!currentModule) { + return completions; + } + + // 3. provide modules + const suggestModulesPromise = this.provideModules(document, position, filePath, symbolResult.macro.includes); + + // 4. provide params and ports of wrapper module + const suggestParamsPortsPromise = this.provideParamsPorts(currentModule); + + // 5. provide nets + const suggestNetsPromise = this.provideNets(scopeSymbols.symbols); + + // collect + completions.push(...await suggestModulesPromise); + completions.push(...await suggestParamsPortsPromise); + completions.push(...await suggestNetsPromise); + + return completions; + + } catch (err) { + console.log(err); + } + } + + private makeKeywordItems(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] { + const vhdlKeywordItem = []; + for (const keyword of vhdlKeyword.keys()) { + const clItem = this.makekeywordCompletionItem(keyword); + vhdlKeywordItem.push(clItem); + } + + return vhdlKeywordItem; + } + + private makeCompilerKeywordItems(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] { + const items = []; + const targetRange = document.getWordRangeAtPosition(position, /[`_0-9a-zA-Z]+/); + const targetWord = document.getText(targetRange); + const prefix = targetWord.startsWith('`') ? '' : '`'; + for (const keyword of vhdlKeyword.compilerKeys()) { + const clItem = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword); + clItem.insertText = new vscode.SnippetString(prefix + keyword); + clItem.detail = 'compiler directive'; + items.push(clItem); + } + return items; + } + + private makeSystemKeywordItems(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] { + const items = []; + for (const keyword of vhdlKeyword.systemKeys()) { + const clItem = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Method); + clItem.insertText = new vscode.SnippetString('\\$' + keyword + '($1);'); + clItem.detail = 'system task'; + items.push(clItem); + } + return items; + } + + + private makekeywordCompletionItem(keyword: string): vscode.CompletionItem { + const clItem = new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword); + clItem.detail = 'keyword'; + + switch (keyword) { + case 'begin': clItem.insertText = new vscode.SnippetString("begin$1\nend"); break; + case 'function': clItem.insertText = new vscode.SnippetString("function ${1:name}\n\nendfunction"); break; + default: break; + } + return clItem; + } + + private async provideModules(document: vscode.TextDocument, position: vscode.Position, filePath: AbsPath, includes: Include[]): Promise { + const suggestModules: vscode.CompletionItem[] = []; + + const lspVhdlConfig = vscode.workspace.getConfiguration('function.lsp.completion.vhdl'); + const autoAddInclude: boolean = lspVhdlConfig.get('autoAddInclude', true); + const completeWholeInstante: boolean = lspVhdlConfig.get('completeWholeInstante', true); + + const includePaths = new Set(); + let lastIncludeLine = 0; + for (const include of includes) { + const absIncludePath = hdlPath.rel2abs(filePath, include.path); + includePaths.add(absIncludePath); + lastIncludeLine = Math.max(include.range.end.line, lastIncludeLine); + } + const insertPosition = new vscode.Position(lastIncludeLine, 0); + const insertRange = new vscode.Range(insertPosition, insertPosition); + const fileFolder = hdlPath.resolve(filePath, '..'); + + // used only when completeWholeInstante is true + let completePrefix = ''; + if (completeWholeInstante) { + const wordRange = document.getWordRangeAtPosition(position); + const countStart = wordRange ? wordRange.start.character : position.character; + const spaceNumber = Math.floor(countStart / 4) * 4; + console.log(wordRange, countStart, spaceNumber); + + completePrefix = ' '.repeat(spaceNumber); + } + + + // for (const module of hdlParam.getAllHdlModules()) { + // const clItem = new vscode.CompletionItem(module.name, vscode.CompletionItemKind.Class); + + // // feature 1 : auto add include path if there's no corresponding include path + // if (autoAddInclude && !includePaths.has(module.path)) { + // const relPath: RelPath = hdlPath.relative(fileFolder, module.path); + // const includeString = '`include "' + relPath + '"\n'; + // const textEdit = new vscode.TextEdit(insertRange, includeString); + // clItem.additionalTextEdits = [textEdit]; + // } + + // // feature 2 : auto complete instance + // if (completeWholeInstante) { + // const snippetString = instanceVhdlCode(module, '', true); + // clItem.insertText = new vscode.SnippetString(snippetString); + // } + + // clItem.detail = 'module'; + // suggestModules.push(clItem); + // } + + return suggestModules; + } + + private async provideParamsPorts(module: HdlModule): Promise { + if (!module) { + return []; + } + const suggestParamsPorts = []; + for (const param of module.params) { + const clItem = new vscode.CompletionItem(param.name, vscode.CompletionItemKind.Constant); + clItem.detail = 'param'; + suggestParamsPorts.push(clItem); + } + + for (const port of module.ports) { + const clItem = new vscode.CompletionItem(port.name, vscode.CompletionItemKind.Interface); + clItem.detail = 'port'; + suggestParamsPorts.push(clItem); + } + + return suggestParamsPorts; + } + + private async provideNets(symbols: RawSymbol[]): Promise { + if (!symbols) { + return []; + } + const suggestNets = []; + for (const symbol of symbols) { + if (symbol.type === 'wire' || symbol.type === 'reg') { + const clItem = new vscode.CompletionItem(symbol.name, vscode.CompletionItemKind.Variable); + clItem.sortText = ''; + clItem.detail = symbol.type; + suggestNets.push(clItem); + } + } + return suggestNets; + } +}; + +const vhdlCompletionProvider = new VhdlCompletionProvider(); + +export { + vhdlCompletionProvider +}; \ No newline at end of file diff --git a/src/function/lsp/linter/base.ts b/src/function/lsp/linter/base.ts index 3e3771e..1b87c1d 100644 --- a/src/function/lsp/linter/base.ts +++ b/src/function/lsp/linter/base.ts @@ -1,12 +1,16 @@ import * as vscode from 'vscode'; +import { HdlLangID } from '../../../global/enum'; interface BaseLinter { diagnostic: vscode.DiagnosticCollection; lint(document: vscode.TextDocument): Promise; + remove(uri: vscode.Uri): Promise; + initialise(langID: HdlLangID): Promise; } interface BaseManager { initialise(): Promise; + updateLinter(): Promise; } diff --git a/src/function/lsp/linter/default.ts b/src/function/lsp/linter/default.ts index efb9505..63d4131 100644 --- a/src/function/lsp/linter/default.ts +++ b/src/function/lsp/linter/default.ts @@ -73,13 +73,8 @@ class DefaultVlogLinter implements BaseLinter { } public async initialise() { - for (const doc of vscode.workspace.textDocuments) { - if (isVerilogFile(doc.fileName)) { - // TODO : check performance - await this.lint(doc); - } - } - LspOutput.report('finish initialization of default vlog linter', ReportType.Launch); + // move code to outer layer + return true; } } @@ -149,13 +144,8 @@ class DefaultVHDLLinter implements BaseLinter { } public async initialise() { - for (const doc of vscode.workspace.textDocuments) { - if (isVhdlFile(doc.fileName)) { - // TODO : check performance - await this.lint(doc); - } - } - LspOutput.report('finish initialization of default vlog linter', ReportType.Launch); + // move code to outer layer + return true; } } diff --git a/src/function/lsp/linter/modelsim.ts b/src/function/lsp/linter/modelsim.ts index a4510cf..36aaa99 100644 --- a/src/function/lsp/linter/modelsim.ts +++ b/src/function/lsp/linter/modelsim.ts @@ -51,10 +51,14 @@ class ModelsimLinter implements BaseLinter { this.diagnostic.set(document.uri, diagnostics); } } else { - LspOutput.report('linter is not available, please check prj.modelsim.install.path in your setting', ReportType.Error); + LspOutput.report('modelsim linter is not available, please check prj.modelsim.install.path in your setting!', ReportType.Error, true); } } + async remove(uri: vscode.Uri) { + this.diagnostic.delete(uri); + } + /** * @param document * @param stdout stdout from xvlog @@ -62,21 +66,24 @@ class ModelsimLinter implements BaseLinter { */ private provideDiagnostics(document: vscode.TextDocument, stdout: string): vscode.Diagnostic[] { const diagnostics = []; - for (const line of stdout.split('\n')) { + for (const line of stdout.split(/\r?\n/g)) { const tokens = line.split(/(Error|Warning).+?(?: *?(?:.+?(?:\\|\/))+.+?\((\d+?)\):|)(?: *?near "(.+?)":|)(?: *?\((.+?)\)|) +?(.+)/gm); - - const headerInfo = tokens[0]; + const headerInfo = tokens[1]; if (headerInfo === 'Error') { const errorLine = parseInt(tokens[2]) - 1; const syntaxInfo = tokens[5]; - const range = this.makeCorrectRange(document, errorLine); + LspOutput.report(` line: ${errorLine}, info: ${syntaxInfo}`, ReportType.Run); + + const range = this.makeCorrectRange(document, errorLine, syntaxInfo); const diag = new vscode.Diagnostic(range, syntaxInfo, vscode.DiagnosticSeverity.Error); diagnostics.push(diag); - } else if (headerInfo == 'Warning') { + } else if (headerInfo === 'Warning') { const errorLine = parseInt(tokens[2]) - 1; const syntaxInfo = tokens[5]; - const range = this.makeCorrectRange(document, errorLine); + LspOutput.report(` line: ${errorLine}, info: ${syntaxInfo}`, ReportType.Run); + + const range = this.makeCorrectRange(document, errorLine, syntaxInfo); const diag = new vscode.Diagnostic(range, syntaxInfo, vscode.DiagnosticSeverity.Warning); diagnostics.push(diag); } @@ -84,8 +91,32 @@ class ModelsimLinter implements BaseLinter { return diagnostics; } - private makeCorrectRange(document: vscode.TextDocument, line: number): vscode.Range { + private makeCorrectRange(document: vscode.TextDocument, line: number, syntaxInfo: string): vscode.Range { + // extract all the words like 'adawwd' in a syntax info + const singleQuoteWords = syntaxInfo.match(/'([^']*)'/g); + if (singleQuoteWords && singleQuoteWords.length > 0) { + const targetWord = singleQuoteWords.map(val => val.replace(/'/g, ''))[0]; + // find range of target word + const textLine = document.lineAt(line); + const text = textLine.text; + const startCharacter = text.indexOf(targetWord); + if (startCharacter > -1) { + const endCharacter = startCharacter + targetWord.length; + const range = new vscode.Range( + new vscode.Position(line, startCharacter), + new vscode.Position(line, endCharacter) + ); + return range; + } + } + + // else target the first word in the line + return this.makeCommonRange(document, line, syntaxInfo); + } + + private makeCommonRange(document: vscode.TextDocument, line: number, syntaxInfo: string): vscode.Range { const startPosition = new vscode.Position(line, 0); + const wordRange = document.getWordRangeAtPosition(startPosition, /[`_0-9a-zA-Z]+/); if (wordRange) { return wordRange; @@ -107,11 +138,11 @@ class ModelsimLinter implements BaseLinter { const fullExecutorName = opeParam.os === 'win32' ? executorName + '.exe' : executorName; if (modelsimInstallPath.trim() === '' || !fs.existsSync(modelsimInstallPath)) { - LspOutput.report(`User's modelsim Install Path ${modelsimInstallPath}, which is invalid. Use ${executorName} in default.`, ReportType.Warn); + LspOutput.report(`User's modelsim Install Path "${modelsimInstallPath}", which is invalid. Use ${executorName} in default.`, ReportType.Warn); LspOutput.report('If you have doubts, check prj.modelsim.install.path in setting', ReportType.Warn); return executorName; } else { - LspOutput.report(`User's modelsim Install Path ${modelsimInstallPath}, which is invalid`); + LspOutput.report(`User's modelsim Install Path "${modelsimInstallPath}", which is invalid`); const executorPath = hdlPath.join( hdlPath.toSlash(modelsimInstallPath), @@ -131,23 +162,23 @@ class ModelsimLinter implements BaseLinter { } const { stderr } = await easyExec(executorPath, []); if (stderr.length === 0) { - this.executableInvokeNameMap.set(langID, undefined); - LspOutput.report(`fail to execute ${executorPath}! Reason: ${stderr}`, ReportType.Error); - return false; - } else { this.executableInvokeNameMap.set(langID, executorPath); LspOutput.report(`success to verify ${executorPath}, linter from modelsim is ready to go!`, ReportType.Launch); return true; + } else { + this.executableInvokeNameMap.set(langID, undefined); + LspOutput.report(`fail to execute ${executorPath}! Reason: ${stderr}`, ReportType.Error, true); + return false; } } - public initialise(langID: HdlLangID) { + public async initialise(langID: HdlLangID): Promise { const executorPath = this.getExecutableFilePath(langID); - this.setExecutableFilePath(executorPath, langID); + return this.setExecutableFilePath(executorPath, langID); } } -const modelsimLinter = new ModelsimLinter() +const modelsimLinter = new ModelsimLinter(); export { modelsimLinter, diff --git a/src/function/lsp/linter/verilator.ts b/src/function/lsp/linter/verilator.ts index b51a953..656af79 100644 --- a/src/function/lsp/linter/verilator.ts +++ b/src/function/lsp/linter/verilator.ts @@ -43,47 +43,59 @@ class VerilatorLinter implements BaseLinter { const args = [filePath, ...linterArgs]; const executor = this.executableInvokeNameMap.get(langID); if (executor !== undefined) { - const { stdout } = await easyExec(executor, args); - if (stdout.length > 0) { - const diagnostics = this.provideDiagnostics(document, stdout); + const { stderr } = await easyExec(executor, args); + if (stderr.length > 0) { + const diagnostics = this.provideDiagnostics(document, stderr); this.diagnostic.set(document.uri, diagnostics); } } else { - LspOutput.report('linter is not available, please check prj.verilator.install.path in your setting', ReportType.Error); + LspOutput.report('verilator linter is not available, please check prj.verilator.install.path in your setting', ReportType.Error, true); } } + async remove(uri: vscode.Uri) { + this.diagnostic.delete(uri); + } + /** * @param document * @param stdout stdout from xvlog * @returns { vscode.Diagnostic[] } linter info */ - private provideDiagnostics(document: vscode.TextDocument, stdout: string): vscode.Diagnostic[] { + private provideDiagnostics(document: vscode.TextDocument, stderr: string): vscode.Diagnostic[] { const diagnostics = []; - for (const line of stdout.split('\n')) { - const tokens = line.split(/(Error|Warning).+?(?: *?(?:.+?(?:\\|\/))+.+?\((\d+?)\):|)(?: *?near "(.+?)":|)(?: *?\((.+?)\)|) +?(.+)/gm); - // TODO : make parser of output info from verilator + for (let line of stderr.split(/\r?\n/g)) { + if (!line.startsWith('%')) { + continue; + } else { + line = line.substring(1); + } + + const tokens = line.split(':'); + if (tokens.length < 3) { + continue; + } + const header = tokens[0].toLowerCase(); + const fileName = tokens[1]; + const lineNo = parseInt(tokens[2]) - 1; + const characterNo = parseInt(tokens[3]) - 1; + const syntaxInfo = tokens[4]; - const headerInfo = tokens[0]; - if (headerInfo === 'Error') { - const errorLine = parseInt(tokens[2]) - 1; - const syntaxInfo = tokens[5]; - const range = this.makeCorrectRange(document, errorLine); + if (header.startsWith('warning')) { + const range = this.makeCorrectRange(document, lineNo, characterNo); + const diag = new vscode.Diagnostic(range, syntaxInfo, vscode.DiagnosticSeverity.Warning); + diagnostics.push(diag); + } else if (header.startsWith('error')) { + const range = this.makeCorrectRange(document, lineNo, characterNo); const diag = new vscode.Diagnostic(range, syntaxInfo, vscode.DiagnosticSeverity.Error); diagnostics.push(diag); - } else if (headerInfo == 'Warning') { - const errorLine = parseInt(tokens[2]) - 1; - const syntaxInfo = tokens[5]; - const range = this.makeCorrectRange(document, errorLine); - const diag = new vscode.Diagnostic(range, syntaxInfo, vscode.DiagnosticSeverity.Warning); - diagnostics.push(diag); } } return diagnostics; } - private makeCorrectRange(document: vscode.TextDocument, line: number): vscode.Range { - const startPosition = new vscode.Position(line, 0); + private makeCorrectRange(document: vscode.TextDocument, line: number, character: number): vscode.Range { + const startPosition = new vscode.Position(line, character); const wordRange = document.getWordRangeAtPosition(startPosition, /[`_0-9a-zA-Z]+/); if (wordRange) { return wordRange; @@ -129,19 +141,20 @@ class VerilatorLinter implements BaseLinter { } const { stderr } = await easyExec(executorPath, []); if (stderr.length === 0) { - this.executableInvokeNameMap.set(langID, undefined); - LspOutput.report(`fail to execute ${executorPath}! Reason: ${stderr}`, ReportType.Error); - return false; - } else { this.executableInvokeNameMap.set(langID, executorPath); LspOutput.report(`success to verify ${executorPath}, linter from verilator is ready to go!`, ReportType.Launch); return true; + } else { + this.executableInvokeNameMap.set(langID, undefined); + LspOutput.report(`fail to execute ${executorPath}! Reason: ${stderr}`, ReportType.Error, true); + + return false; } } - public initialise(langID: HdlLangID) { + public async initialise(langID: HdlLangID): Promise { const executorPath = this.getExecutableFilePath(langID); - this.setExecutableFilePath(executorPath, langID); + return this.setExecutableFilePath(executorPath, langID); } } diff --git a/src/function/lsp/linter/vhdl.ts b/src/function/lsp/linter/vhdl.ts index ede90f5..1bec2c2 100644 --- a/src/function/lsp/linter/vhdl.ts +++ b/src/function/lsp/linter/vhdl.ts @@ -5,6 +5,7 @@ import { BaseLinter, BaseManager } from './base'; import { defaultVlogLinter } from './default'; import { modelsimLinter } from './modelsim'; import { vivadoLinter } from './vivado'; +import { hdlFile, hdlPath } from '../../../hdlFs'; class VhdlLinterManager implements BaseManager { currentLinter: BaseLinter | undefined; @@ -17,21 +18,37 @@ class VhdlLinterManager implements BaseManager { this.activateList.set('default', false); this.activateLinterName = 'default'; - this.updateLinter(); - // update when user's config is changed vscode.workspace.onDidChangeConfiguration(() => { const diagnostor = this.getUserDiagnostorSelection(); const lastDiagnostor = this.activateLinterName; if (diagnostor !== lastDiagnostor) { - LspOutput.report(`[vhdl lsp manager] detect linter setting changes, switch ${lastDiagnostor} to ${diagnostor}.`, ); + LspOutput.report(` detect linter setting changes, switch ${lastDiagnostor} to ${diagnostor}.`, ); this.updateLinter(); } }); } async initialise(): Promise { - + const success = await this.updateLinter(); + if (!success) { + return; + } + for (const doc of vscode.workspace.textDocuments) { + const fileName = hdlPath.toSlash(doc.fileName); + if (hdlFile.isVhdlFile(fileName)) { + await this.lint(doc); + } + } + LspOutput.report(' finish initialization of vhdl linter. Linter name: ' + this.activateLinterName, ReportType.Launch); + } + + async lint(document: vscode.TextDocument) { + await this.currentLinter?.lint(document); + } + + async remove(uri: vscode.Uri): Promise { + this.currentLinter?.remove(uri); } public getUserDiagnostorSelection() { @@ -40,53 +57,59 @@ class VhdlLinterManager implements BaseManager { return diagnostor; } - public updateLinter() { + public async updateLinter(): Promise { const diagnostor = this.getUserDiagnostorSelection(); switch (diagnostor) { - case 'vivado': this.activateVivado(); break; - case 'modelsim': this.activateModelsim(); break; - case 'default': this.activateDefault(); break; - case default: this.activateDefault(); break; + case 'vivado': return this.activateVivado(); + case 'modelsim': return this.activateModelsim(); + case 'default': return this.activateDefault(); + default: return this.activateDefault(); } } - public activateVivado() { + public async activateVivado(): Promise { const selectedLinter = vivadoLinter; + let launch = true; if (this.activateList.get('vivado') === false) { - selectedLinter.initialise(HdlLangID.Verilog); + launch = await selectedLinter.initialise(HdlLangID.Verilog); this.activateList.set('vivado', true); - LspOutput.report('[vhdl lsp manager] vivado linter has been activated', ReportType.Info); + LspOutput.report(' vivado linter has been activated', ReportType.Info); } this.currentLinter = selectedLinter; this.activateLinterName = 'vivado'; + return launch; } - public activateModelsim() { + public async activateModelsim(): Promise { const selectedLinter = modelsimLinter; + let launch = true; if (this.activateList.get('modelsim') === false) { - selectedLinter.initialise(HdlLangID.Verilog); + launch = await selectedLinter.initialise(HdlLangID.Verilog); this.activateList.set('modelsim', true); - LspOutput.report('[vhdl lsp manager] modelsim linter has been activated', ReportType.Info); + LspOutput.report(' modelsim linter has been activated', ReportType.Info); } this.currentLinter = selectedLinter; this.activateLinterName = 'modelsim'; + return launch; } - public activateDefault() { + public async activateDefault(): Promise { const selectedLinter = defaultVlogLinter; + let launch = true; if (this.activateList.get('default') === false) { this.activateList.set('default', true); - LspOutput.report('[vhdl lsp manager] default build-in linter has been activated', ReportType.Info); + LspOutput.report(' default build-in linter has been activated', ReportType.Info); } this.currentLinter = selectedLinter; this.activateLinterName = 'default'; + return launch; } } diff --git a/src/function/lsp/linter/vivado.ts b/src/function/lsp/linter/vivado.ts index f48cf9a..fda808c 100644 --- a/src/function/lsp/linter/vivado.ts +++ b/src/function/lsp/linter/vivado.ts @@ -54,10 +54,14 @@ class VivadoLinter implements BaseLinter { this.diagnostic.set(document.uri, diagnostics); } } else { - LspOutput.report('linter is not available, please check prj.vivado.install.path in your setting', ReportType.Error); + LspOutput.report('vivado linter is not available, please check prj.vivado.install.path in your setting', ReportType.Error, true); } } + async remove(uri: vscode.Uri) { + this.diagnostic.delete(uri); + } + /** * @param document * @param stdout stdout from xvlog @@ -65,7 +69,7 @@ class VivadoLinter implements BaseLinter { */ private provideDiagnostics(document: vscode.TextDocument, stdout: string): vscode.Diagnostic[] { const diagnostics = []; - for (const line of stdout.split('\n')) { + for (const line of stdout.split(/\r?\n/g)) { const tokens = line.split(/:?\s*(?:\[|\])\s*/); const headerInfo = tokens[0]; // const standardInfo = tokens[1]; @@ -73,8 +77,10 @@ class VivadoLinter implements BaseLinter { const parsedPath = tokens[3]; if (headerInfo === 'ERROR') { const errorInfos = parsedPath.split(':'); - const errorLine = parseInt(errorInfos[errorInfos.length - 1]); - const range = this.makeCorrectRange(document, errorLine); + const errorLine = Math.max(parseInt(errorInfos[errorInfos.length - 1]) - 1, 0); + LspOutput.report(` line: ${errorLine}, info: ${syntaxInfo}`, ReportType.Run); + + const range = this.makeCorrectRange(document, errorLine, syntaxInfo); const diag = new vscode.Diagnostic(range, syntaxInfo, vscode.DiagnosticSeverity.Error); diagnostics.push(diag); } @@ -82,8 +88,32 @@ class VivadoLinter implements BaseLinter { return diagnostics; } - private makeCorrectRange(document: vscode.TextDocument, line: number): vscode.Range { + private makeCorrectRange(document: vscode.TextDocument, line: number, syntaxInfo: string): vscode.Range { + // extract all the words like 'adawwd' in a syntax info + const singleQuoteWords = syntaxInfo.match(/'([^']*)'/g); + if (singleQuoteWords && singleQuoteWords.length > 0) { + const targetWord = singleQuoteWords.map(val => val.replace(/'/g, ''))[0]; + // find range of target word + const textLine = document.lineAt(line); + const text = textLine.text; + const startCharacter = text.indexOf(targetWord); + if (startCharacter > -1) { + const endCharacter = startCharacter + targetWord.length; + const range = new vscode.Range( + new vscode.Position(line, startCharacter), + new vscode.Position(line, endCharacter) + ); + return range; + } + } + + // else target the first word in the line + return this.makeCommonRange(document, line, syntaxInfo); + } + + private makeCommonRange(document: vscode.TextDocument, line: number, syntaxInfo: string): vscode.Range { const startPosition = new vscode.Position(line, 0); + const wordRange = document.getWordRangeAtPosition(startPosition, /[`_0-9a-zA-Z]+/); if (wordRange) { return wordRange; @@ -105,11 +135,11 @@ class VivadoLinter implements BaseLinter { const fullExecutorName = opeParam.os === 'win32' ? executorName + '.bat' : executorName; if (vivadoInstallPath.trim() === '' || !fs.existsSync(vivadoInstallPath)) { - LspOutput.report(`User's Vivado Install Path ${vivadoInstallPath}, which is invalid. Use ${executorName} in default.`, ReportType.Warn); + LspOutput.report(`User's Vivado Install Path "${vivadoInstallPath}", which is invalid. Use ${executorName} in default.`, ReportType.Warn); LspOutput.report('If you have doubts, check prj.vivado.install.path in setting', ReportType.Warn); return executorName; } else { - LspOutput.report(`User's Vivado Install Path ${vivadoInstallPath}, which is invalid`); + LspOutput.report(`User's Vivado Install Path "${vivadoInstallPath}", which is invalid`); const executorPath = hdlPath.join( hdlPath.toSlash(vivadoInstallPath), @@ -129,19 +159,19 @@ class VivadoLinter implements BaseLinter { } const { stderr } = await easyExec(executorPath, []); if (stderr.length === 0) { - this.executableInvokeNameMap.set(langID, undefined); - LspOutput.report(`fail to execute ${executorPath}! Reason: ${stderr}`, ReportType.Error); - return false; - } else { this.executableInvokeNameMap.set(langID, executorPath); LspOutput.report(`success to verify ${executorPath}, linter from vivado is ready to go!`, ReportType.Launch); return true; + } else { + this.executableInvokeNameMap.set(langID, undefined); + LspOutput.report(`fail to execute ${executorPath}! Reason: ${stderr}`, ReportType.Error, true); + return false; } } - public initialise(langID: HdlLangID) { + public async initialise(langID: HdlLangID): Promise { const executorPath = this.getExecutableFilePath(langID); - this.setExecutableFilePath(executorPath, langID); + return this.setExecutableFilePath(executorPath, langID); } } diff --git a/src/function/lsp/linter/vlog.ts b/src/function/lsp/linter/vlog.ts index 4142176..5c61e42 100644 --- a/src/function/lsp/linter/vlog.ts +++ b/src/function/lsp/linter/vlog.ts @@ -5,6 +5,7 @@ import { BaseLinter, BaseManager } from './base'; import { defaultVlogLinter } from './default'; import { modelsimLinter } from './modelsim'; import { vivadoLinter } from './vivado'; +import { hdlFile, hdlPath } from '../../../hdlFs'; class VlogLinterManager implements BaseManager { currentLinter: BaseLinter | undefined; @@ -17,21 +18,39 @@ class VlogLinterManager implements BaseManager { this.activateList.set('default', false); this.activateLinterName = 'default'; - this.updateLinter(); - // update when user's config is changed vscode.workspace.onDidChangeConfiguration(() => { const diagnostor = this.getUserDiagnostorSelection(); const lastDiagnostor = this.activateLinterName; if (diagnostor !== lastDiagnostor) { - LspOutput.report(`detect linter setting changes, switch ${lastDiagnostor} to ${diagnostor}.`, ); + LspOutput.report(` detect linter setting changes, switch ${lastDiagnostor} to ${diagnostor}.`, ); this.updateLinter(); } }); + } async initialise(): Promise { - + const success = await this.updateLinter(); + if (!success) { + return; + } + + for (const doc of vscode.workspace.textDocuments) { + const fileName = hdlPath.toSlash(doc.fileName); + if (hdlFile.isVerilogFile(fileName)) { + await this.lint(doc); + } + } + LspOutput.report(' finish initialization of vlog linter. Linter name: ' + this.activateLinterName, ReportType.Launch); + } + + async lint(document: vscode.TextDocument) { + await this.currentLinter?.lint(document); + } + + async remove(uri: vscode.Uri): Promise { + this.currentLinter?.remove(uri); } public getUserDiagnostorSelection() { @@ -40,53 +59,59 @@ class VlogLinterManager implements BaseManager { return diagnostor; } - public updateLinter() { + public async updateLinter() { const diagnostor = this.getUserDiagnostorSelection(); switch (diagnostor) { - case 'vivado': this.activateVivado(); break; - case 'modelsim': this.activateModelsim(); break; - case 'default': this.activateDefault(); break; - case default: this.activateDefault(); break; - } + case 'vivado': return this.activateVivado(); + case 'modelsim': return this.activateModelsim(); + case 'default': return this.activateDefault(); + default: return this.activateDefault(); + } } - public activateVivado() { + public async activateVivado(): Promise { const selectedLinter = vivadoLinter; + let launch = true; if (this.activateList.get('vivado') === false) { - selectedLinter.initialise(HdlLangID.Verilog); + launch = await selectedLinter.initialise(HdlLangID.Verilog); this.activateList.set('vivado', true); - LspOutput.report('vivado linter has been activated', ReportType.Info); + LspOutput.report(' vivado linter has been activated', ReportType.Info); } this.currentLinter = selectedLinter; this.activateLinterName = 'vivado'; + return launch; } - public activateModelsim() { + public async activateModelsim(): Promise { const selectedLinter = modelsimLinter; + let launch = true; if (this.activateList.get('modelsim') === false) { - selectedLinter.initialise(HdlLangID.Verilog); + launch = await selectedLinter.initialise(HdlLangID.Verilog); this.activateList.set('modelsim', true); - LspOutput.report('modelsim linter has been activated', ReportType.Info); + LspOutput.report(' modelsim linter has been activated', ReportType.Info); } this.currentLinter = selectedLinter; this.activateLinterName = 'modelsim'; + return launch; } - public activateDefault() { + public async activateDefault(): Promise { const selectedLinter = defaultVlogLinter; + let launch = true; if (this.activateList.get('default') === false) { this.activateList.set('default', true); - LspOutput.report('default build-in linter has been activated', ReportType.Info); + LspOutput.report(' default build-in linter has been activated', ReportType.Info); } this.currentLinter = selectedLinter; this.activateLinterName = 'default'; + return launch; } } diff --git a/src/monitor/event.ts b/src/monitor/event.ts index 4f93d3f..28b397f 100644 --- a/src/monitor/event.ts +++ b/src/monitor/event.ts @@ -12,9 +12,9 @@ import { hdlParam, HdlSymbol } from '../hdlParser'; import { prjManage } from '../manager'; import { libManage } from '../manager/lib'; import type { HdlMonitor } from './index'; -import { ToolChainType } from '../global/enum'; +import { HdlLangID, ToolChainType } from '../global/enum'; import { hdlSymbolStorage } from '../function/lsp/core'; -import { vlogLinter } from '../function/lsp/linter'; +import { vlogLinterManager, vhdlLinterManager } from '../function/lsp/linter'; import { isVerilogFile } from '../hdlFs/file'; enum Event { @@ -98,7 +98,12 @@ class HdlAction extends BaseAction { } const uri = vscode.Uri.file(path); - vlogLinter.remove(uri); + const langID = hdlFile.getLanguageId(path); + if (langID === HdlLangID.Verilog) { + vlogLinterManager.remove(uri); + } else if (langID === HdlLangID.Vhdl) { + vhdlLinterManager.remove(uri); + } } async unlinkDir(path: string, m: HdlMonitor): Promise { @@ -128,10 +133,16 @@ class HdlAction extends BaseAction { } async updateLinter(path: string) { - if (isVerilogFile(path)) { - const uri = vscode.Uri.file(path); - const document = await vscode.workspace.openTextDocument(uri); - vlogLinter.lint(document); + const uri = vscode.Uri.file(path); + const document = await vscode.workspace.openTextDocument(uri); + const langID = hdlFile.getLanguageId(path); + + if (langID === HdlLangID.Verilog) { + vlogLinterManager.lint(document); + } else if (langID === HdlLangID.Vhdl) { + vhdlLinterManager.lint(document); + } else if (langID === HdlLangID.SystemVerilog) { + // TODO } } diff --git a/syntaxes/digital-ide-output.json b/syntaxes/digital-ide-output.json index 2631569..7945dea 100644 --- a/syntaxes/digital-ide-output.json +++ b/syntaxes/digital-ide-output.json @@ -71,6 +71,18 @@ } } }, + { + "name": "digital-ide.Launch", + "match": "(\\[Launch - (.*)\\])([\\s\\S]*)", + "captures": { + "1": { + "name": "keyword.launch-token" + }, + "2": { + "name": "string" + } + } + }, { "name": "string.quoted.double", "begin": "\"", diff --git a/syntaxes/vhdl.json b/syntaxes/vhdl.tmLanguage.json similarity index 100% rename from syntaxes/vhdl.json rename to syntaxes/vhdl.tmLanguage.json diff --git a/syntaxes/vvp.tmLanguage.json b/syntaxes/vvp.tmLanguage.json new file mode 100644 index 0000000..8f2d125 --- /dev/null +++ b/syntaxes/vvp.tmLanguage.json @@ -0,0 +1,361 @@ +{ + "fileTypes": [ + "v", + "vh" + ], + "keyEquivalent": "^~V", + "name": "Verilog", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#module_pattern" + }, + { + "include": "#keywords" + }, + { + "include": "#constants" + }, + { + "include": "#strings" + }, + { + "include": "#operators" + }, + { + "include": "#macro_definition" + }, + { + "include": "#macro_quote" + }, + { + "include": "#common_variable" + } + ], + "repository": { + "comments": { + "patterns": [ + { + "begin": "(^[ \\t]+)?(?=#)", + "beginCaptures": { + "1": { + "name": "punctuation.whitespace.comment.leading.verilog" + } + }, + "end": "(?!\\G)", + "patterns": [ + { + "begin": "#", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.verilog" + } + }, + "end": "\\n", + "name": "comment.line.double-slash.verilog" + } + ] + }, + { + "begin": "/\\*", + "end": "\\*/", + "name": "comment.block.c-style.verilog" + } + ] + }, + "constants": { + "patterns": [ + { + "match": "\\b[0-9]+'[bBoOdDhH][a-fA-F0-9_xXzZ]+\\b", + "name": "constant.numeric.sized_integer.verilog" + }, + { + "captures": { + "1": { + "name": "constant.numeric.integer.verilog" + }, + "2": { + "name": "punctuation.separator.range.verilog" + }, + "3": { + "name": "constant.numeric.integer.verilog" + } + }, + "match": "\\b(\\d+)(:)(\\d+)\\b", + "name": "meta.block.numeric.range.verilog" + }, + { + "match": "\\b\\d+(?i:e\\d+)?\\b", + "name": "constant.numeric.integer.verilog" + }, + { + "match": "\\b\\d+\\.\\d+(?i:e\\d+)?\\b", + "name": "constant.numeric.real.verilog" + }, + { + "match": "#\\d+", + "name": "constant.numeric.delay.verilog" + }, + { + "match": "\\b[01xXzZ]+\\b", + "name": "constant.numeric.logic.verilog" + } + ] + }, + "instantiation_patterns": { + "patterns": [ + { + "include": "#keywords" + }, + { + "begin": "^\\s*([a-zA-Z][a-zA-Z0-9_]*)\\s+([a-zA-Z][a-zA-Z0-9_]*)(?)=?|(!|=)?==?|!|&&?|\\|\\|?|\\^?~|~\\^?", + "name": "keyword.operator.verilog" + } + ] + }, + "parenthetical_list": { + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.section.list.verilog" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.section.list.verilog" + } + }, + "name": "support.block.parenthetical_list.verilog", + "patterns": [ + { + "include": "#parenthetical_list" + }, + { + "include": "#comments" + }, + { + "include": "#keywords" + }, + { + "include": "#constants" + }, + { + "include": "#strings" + }, + { + "include": "#position_ports" + }, + { + "include": "#macro_quote" + }, + { + "include": "#common_variable" + } + ] + } + ] + }, + "position_ports": { + "patterns": [ + { + "match": "\\.[a-zA-Z_][a-zA-Z_0-9]*", + "name": "variable.inst.port.verilog" + } + ] + }, + "strings": { + "patterns": [ + { + "begin": "\"", + "end": "\"", + "name": "string.quoted.double.verilog", + "patterns": [ + { + "match": "\\\\.", + "name": "constant.character.escape.verilog" + } + ] + } + ] + }, + "macro_definition": { + "patterns": [ + { + "match": "(?<=`define\\s)\\w+", + "name": "support.function.macro.definition.verilog" + } + ] + }, + "macro_quote": { + "patterns": [ + { + "match": "`[a-zA-Z_][a-zA-Z_0-9]*", + "name": "support.function.macro.quote.verilog" + } + ] + }, + "common_variable": { + "patterns": [ + { + "match": "[a-zA-Z_][a-zA-Z_0-9]*", + "name": "variable.other.constant.verilog" + } + ] + } + }, + "scopeName": "source.vvp", + "uuid": "7F4396B3-A33E-44F0-8502-98CA6C25971F" +} \ No newline at end of file diff --git a/test/vlogFast.js b/test/vlogFast.js new file mode 100644 index 0000000..8c53606 --- /dev/null +++ b/test/vlogFast.js @@ -0,0 +1,8 @@ +const { vlogFast } = require('../resources/hdlParser'); + +const testFile = 'c:/Users/11934/Project/Digital-IDE/Digital-Test/Verilog/dependence_test/parent.v'; + +(async () => { + const fast = vlogFast(testFile); + console.log(JSON.stringify(fast, null, ' ')); +})(); \ No newline at end of file