From 758a108096ac57e8e3dbcd023e55cabd1c6d5634 Mon Sep 17 00:00:00 2001 From: LSTM-Kirigaya <1193466151@qq.com> Date: Tue, 17 Dec 2024 20:35:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=B8=89=E7=A7=8D=E8=AF=8A?= =?UTF-8?q?=E6=96=AD=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- l10n/bundle.l10n.de.json | 4 +- l10n/bundle.l10n.en.json | 4 +- l10n/bundle.l10n.ja.json | 4 +- l10n/bundle.l10n.zh-cn.json | 4 +- l10n/bundle.l10n.zh-tw.json | 4 +- package.json | 2 +- src/extension.ts | 16 ++- src/function/lsp-client/config.ts | 13 ++ src/function/lsp/linter/common.ts | 4 +- src/function/lsp/linter/index.ts | 21 ++- src/function/lsp/linter/manager.ts | 151 +++++++++++++++++--- src/function/lsp/linter/modelsim.ts | 200 --------------------------- src/function/lsp/linter/verilator.ts | 180 ------------------------ src/function/lsp/linter/vivado.ts | 198 -------------------------- src/global/lsp.ts | 4 +- src/hdlParser/core.ts | 2 +- src/manager/index.ts | 5 +- src/manager/prj.ts | 3 + 18 files changed, 199 insertions(+), 620 deletions(-) delete mode 100644 src/function/lsp/linter/modelsim.ts delete mode 100644 src/function/lsp/linter/verilator.ts delete mode 100644 src/function/lsp/linter/vivado.ts diff --git a/l10n/bundle.l10n.de.json b/l10n/bundle.l10n.de.json index 11a433f..1385094 100644 --- a/l10n/bundle.l10n.de.json +++ b/l10n/bundle.l10n.de.json @@ -94,5 +94,7 @@ "error.linter.status-bar.tooltip": "Kann Diagnose für {0} nicht abrufen", "info.linter.status-bar.tooltip": "Diagnosegerät {0} arbeitet", "warning.linter.cannot-get-valid-linter-invoker": "Die digitale IDE kann keinen Aufrufpfad für {0} abrufen. Bitte installieren Sie den entsprechenden Diagnose-Tool und konfigurieren Sie ihn entweder in der Umgebungsvariablen PATH oder im digitalen IDE-Diagnosetool-Installationspfad.", - "info.linter.config-linter-install-path": "Installationsverzeichnis konfigurieren" + "info.linter.config-linter-install-path": "Installationsverzeichnis konfigurieren", + "info.progress.doing-diagnostic": "Diagnostizierung", + "error.common.fail-to-launch-lsp": "Start des Sprachservers fehlgeschlagen!" } \ No newline at end of file diff --git a/l10n/bundle.l10n.en.json b/l10n/bundle.l10n.en.json index 7255ed1..a48a8f2 100644 --- a/l10n/bundle.l10n.en.json +++ b/l10n/bundle.l10n.en.json @@ -94,5 +94,7 @@ "error.linter.status-bar.tooltip": "Unable to get {0} diagnoser", "info.linter.status-bar.tooltip": "Diagnostics {0} is working", "warning.linter.cannot-get-valid-linter-invoker": "The Digital IDE cannot retrieve the call path for {0}. Please install the corresponding diagnostic tool and configure it either in the environment variable PATH or in the Digital IDE's diagnostic tool installation path.", - "info.linter.config-linter-install-path": "Configure installation directory" + "info.linter.config-linter-install-path": "Configure installation directory", + "info.progress.doing-diagnostic": "Diagnosing", + "error.common.fail-to-launch-lsp": "Language server startup failed!" } \ No newline at end of file diff --git a/l10n/bundle.l10n.ja.json b/l10n/bundle.l10n.ja.json index 26b172f..a5bf450 100644 --- a/l10n/bundle.l10n.ja.json +++ b/l10n/bundle.l10n.ja.json @@ -94,5 +94,7 @@ "error.linter.status-bar.tooltip": "{0} 診断機能を取得できません", "info.linter.status-bar.tooltip": "診断ツール {0} が作動中", "warning.linter.cannot-get-valid-linter-invoker": "デジタルIDEは{0}の呼び出しパスを取得できません。対応する診断ツールをインストールし、環境変数PATHに設定するか、デジタルIDEの診断ツールインストールパスを設定してください。", - "info.linter.config-linter-install-path": "インストールディレクトリを設定" + "info.linter.config-linter-install-path": "インストールディレクトリを設定", + "info.progress.doing-diagnostic": "診断中", + "error.common.fail-to-launch-lsp": "言語サーバーの起動に失敗しました!" } \ No newline at end of file diff --git a/l10n/bundle.l10n.zh-cn.json b/l10n/bundle.l10n.zh-cn.json index 37dc988..e063f47 100644 --- a/l10n/bundle.l10n.zh-cn.json +++ b/l10n/bundle.l10n.zh-cn.json @@ -94,5 +94,7 @@ "error.linter.status-bar.tooltip": "无法获取 {0} 诊断器", "info.linter.status-bar.tooltip": "诊断器 {0} 正在工作", "warning.linter.cannot-get-valid-linter-invoker": "Digital IDE 无法获取到关于 {0} 的调用路径,请安装对应诊断器后,配置到环境变量 PATH 或者配置 Digital IDE 对应的诊断工具安装路径", - "info.linter.config-linter-install-path": "配置安装目录" + "info.linter.config-linter-install-path": "配置安装目录", + "info.progress.doing-diagnostic": "诊断中", + "error.common.fail-to-launch-lsp": "语言服务器启动失败!" } \ No newline at end of file diff --git a/l10n/bundle.l10n.zh-tw.json b/l10n/bundle.l10n.zh-tw.json index 823e443..9ca7fe1 100644 --- a/l10n/bundle.l10n.zh-tw.json +++ b/l10n/bundle.l10n.zh-tw.json @@ -94,5 +94,7 @@ "error.linter.status-bar.tooltip": "無法取得 {0} 診斷器", "info.linter.status-bar.tooltip": "診斷器 {0} 正在運作", "warning.linter.cannot-get-valid-linter-invoker": "Digital IDE 無法取得關於 {0} 的呼叫路徑,請安裝對應診斷器後,配置到環境變數 PATH 或者配置 Digital IDE 對應的診斷工具安裝路徑。", - "info.linter.config-linter-install-path": "配置安裝目錄" + "info.linter.config-linter-install-path": "配置安裝目錄", + "info.progress.doing-diagnostic": "診斷中", + "error.common.fail-to-launch-lsp": "語言伺服器啟動失敗!" } \ No newline at end of file diff --git a/package.json b/package.json index 7a58c21..15ba99f 100644 --- a/package.json +++ b/package.json @@ -293,7 +293,7 @@ ], "enum": [ "full", - "single", + "common", "shutdown" ], "default": "full", diff --git a/src/extension.ts b/src/extension.ts index 3c71d64..130b7e6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,12 +4,12 @@ import * as fs from 'fs'; import { MainOutput, ReportType, IProgress } from './global'; import { hdlParam } from './hdlParser'; import * as manager from './manager'; +import * as lspLinter from './function/lsp/linter'; import * as func from './function'; import { hdlMonitor } from './monitor'; import * as lspClient from './function/lsp-client'; import { refreshArchTree } from './function/treeView'; -import { hdlFile } from './hdlFs'; import { initialiseI18n, t } from './i18n'; @@ -85,12 +85,12 @@ async function launch(context: vscode.ExtensionContext) { await lspClient.activate(context, packageJson); }); - await vscode.window.withProgress({ + const hdlFiles = await vscode.window.withProgress({ location: vscode.ProgressLocation.Window, title: t('info.progress.initialization') }, async (progress: vscode.Progress, token: vscode.CancellationToken) => { // 初始化解析 - await manager.prjManage.initialise(context, progress); + const hdlFiles = await manager.prjManage.initialise(context, progress); // 这里是因为 pl 对象在 initialise 完成初始化,此处再注册它的行为 manager.registerManagerCommands(context); @@ -100,9 +100,19 @@ async function launch(context: vscode.ExtensionContext) { // 启动监视器 hdlMonitor.start(); + + return hdlFiles; }); + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Window, + title: t('info.progress.doing-diagnostic') + }, async (progress: vscode.Progress, token: vscode.CancellationToken) => { + // 完成诊断器初始化 + await lspLinter.initialise(context, hdlFiles, progress); + }); + console.log(hdlParam); // show welcome information (if first install) diff --git a/src/function/lsp-client/config.ts b/src/function/lsp-client/config.ts index 74048de..b8c60db 100644 --- a/src/function/lsp-client/config.ts +++ b/src/function/lsp-client/config.ts @@ -4,6 +4,10 @@ import { UpdateConfigurationType } from '../../global/lsp'; import * as Linter from '../lsp/linter/common'; import { HdlLangID } from '../../global/enum'; import * as lspLinter from '../lsp/linter'; +import { t } from '../../i18n'; +import { IProgress } from '../../global'; +import { refreshWorkspaceDiagonastics } from '../lsp/linter/manager'; +import { prjManage } from '../../manager'; interface ConfigItem { name: string, @@ -124,6 +128,15 @@ export async function registerConfigurationUpdater(client: LanguageClient, packa } } } + + // 如果诊断模式发生变化,进行一次刷新 + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Window, + title: t('info.progress.doing-diagnostic') + }, async (progress: vscode.Progress, token: vscode.CancellationToken) => { + const hdlFiles = await prjManage.getPrjHardwareFiles(); + await refreshWorkspaceDiagonastics(client, hdlFiles, false, progress); + }); }); } diff --git a/src/function/lsp/linter/common.ts b/src/function/lsp/linter/common.ts index d6d9aa0..8ed79b7 100644 --- a/src/function/lsp/linter/common.ts +++ b/src/function/lsp/linter/common.ts @@ -1,8 +1,6 @@ import * as vscode from 'vscode'; import * as fs from 'fs'; -import { vivadoLinter } from './vivado'; -import { modelsimLinter } from './modelsim'; import { HdlLangID } from '../../../global/enum'; import { t } from '../../../i18n'; import { LspClient } from '../../../global'; @@ -114,7 +112,7 @@ export async function makeLinterOptions( export enum LinterMode { Full = 'full', - Single = 'single', + Common = 'common', Shutdown = 'shutdown' } diff --git a/src/function/lsp/linter/index.ts b/src/function/lsp/linter/index.ts index d9d5fc3..0b4dae6 100644 --- a/src/function/lsp/linter/index.ts +++ b/src/function/lsp/linter/index.ts @@ -1,10 +1,29 @@ +import * as vscode from 'vscode'; +import { AbsPath, IProgress, LspClient } from '../../../global'; + import { vlogLinterManager, vhdlLinterManager, svlogLinterManager, reserveLinterManager, - LinterManager + LinterManager, + refreshWorkspaceDiagonastics } from './manager'; +import { t } from '../../../i18n'; + +export async function initialise( + context: vscode.ExtensionContext, + hdlFiles: AbsPath[], + progress: vscode.Progress +) { + const client = LspClient.DigitalIDE; + if (!client) { + vscode.window.showErrorMessage(t('error.common.fail-to-launch-lsp')); + throw Error('初始化失败'); + } + + await refreshWorkspaceDiagonastics(client, hdlFiles, true, progress); +} export { vlogLinterManager, diff --git a/src/function/lsp/linter/manager.ts b/src/function/lsp/linter/manager.ts index aacc872..be7fc02 100644 --- a/src/function/lsp/linter/manager.ts +++ b/src/function/lsp/linter/manager.ts @@ -1,11 +1,14 @@ import * as vscode from 'vscode'; -import { LspClient, LinterOutput, ReportType } from '../../../global'; +import * as os from 'os'; + +import { LspClient, LinterOutput, ReportType, AbsPath, IProgress } from '../../../global'; import { HdlLangID } from '../../../global/enum'; import { hdlFile, hdlPath } from '../../../hdlFs'; import { t } from '../../../i18n'; import { getLinterConfigurationName, getLinterInstallConfigurationName, getLinterName, IConfigReminder, LinterItem, LinterMode, makeLinterNamePickItem, makeLinterOptions, SupportLinterName, updateLinterConfigurationName } from './common'; import { UpdateConfigurationType } from '../../../global/lsp'; import { LanguageClient } from 'vscode-languageclient/node'; +import { toEscapePath } from '../../../hdlFs/path'; export class LinterManager { /** @@ -92,9 +95,6 @@ export class LinterManager { // 根据配置选择对应的诊断器 await this.updateCurrentLinterItem(client); - // TODO: 根据当前的诊断模式进行选择 - - // 注册内部命令 if (!this.started) { const pickerCommand = this.getLinterPickCommand(); @@ -120,25 +120,6 @@ export class LinterManager { }); } - /** - * @description 刷新当前工作区所有文件的 linter 状态。仅仅在初始化和更新配置文件时需要使用。 - */ - public async refreshWorkspaceLinterResult(linterMode: LinterMode) { - switch (linterMode) { - case LinterMode.Full: - - break; - case LinterMode.Single: - - break; - case LinterMode.Shutdown: - - break; - default: - break; - } - } - /** * @description 更新右下角 status bar 的状态 */ @@ -264,6 +245,130 @@ export class LinterManager { } } +export async function publishDiagnostics( + client: LanguageClient, + path: string +) { + await client.sendRequest("workspace/executeCommand", { + command: 'publish-diagnostics', + arguments: [path] + }); +} + +export async function clearDiagnostics( + client: LanguageClient, + path: string +) { + await client.sendRequest("workspace/executeCommand", { + command: 'clear-diagnostics', + arguments: [path] + }); +} + +/** + * @description 异步方法的受限并发消费 + * @param arrays + * @param consumer + * @param poolNum + */ +export async function asyncConsumer( + arrays: T[], + consumer: (item: T) => Promise, + poolNum: number, + progress?: vscode.Progress +): Promise { + const rets: R[] = []; + const pools = []; + let i = 0; + + progress?.report({ message: `${1}/${arrays.length}`, increment: 0 }); + + while (i < arrays.length) { + const p = consumer(arrays[i]); + pools.push({ id: i + 1, promise: p }); + if (pools.length % poolNum === 0) { + for (const p of pools) { + const ret = await p.promise; + const increment = Math.floor(p.id / arrays.length * 100); + progress?.report({ message: `${p.id}/${arrays.length}`, increment }); + rets.push(ret); + } + pools.length = 0; + } + i ++; + } + + for (const p of pools) { + const ret = await p.promise; + const increment = Math.floor(p.id / arrays.length * 100); + progress?.report({ message: `${p.id}/${arrays.length}`, increment }); + rets.push(ret); + } + + return rets; +} + +/** + * @description 刷新当前工作区所有文件的 linter 状态。仅仅在初始化和更新配置文件时需要使用。 + */ +export async function refreshWorkspaceDiagonastics( + client: LanguageClient, + lintPaths: AbsPath[], + isInitialise: boolean, + progress: vscode.Progress +) { + const parallelChunk = Math.min(os.cpus().length, 32); + const configuration = vscode.workspace.getConfiguration(); + const linterMode = configuration.get('digital-ide.function.lsp.linter.linter-mode', LinterMode.Common); + + console.log('进入诊断,当前诊断模式:', linterMode, lintPaths); + + if (linterMode === LinterMode.Full) { + // full,对工作区所有文件进行诊断 + const consumer = async (path: string) => { + await publishDiagnostics(client, path); + } + await asyncConsumer(lintPaths, consumer, parallelChunk); + } else if (linterMode === LinterMode.Common) { + // common, 只对打开文件进行操作 + // 先清除所有的诊断结果 + const clearConsumer = async (path: string) => { + await clearDiagnostics(client, path); + } + await asyncConsumer(lintPaths, clearConsumer, parallelChunk); + + // 再对激活区域进行诊断 + const consumer = async (path: string) => { + await publishDiagnostics(client, path); + } + + const tabs = vscode.window.tabGroups.all; + const tabArray = tabs.flatMap(group => { + const files = []; + for (const tab of group.tabs) { + if (tab.input) { + const doc = tab.input as vscode.TabInputText; + if (doc.uri) { + const absPath = hdlPath.toEscapePath(doc.uri.fsPath); + files.push(absPath); + } + } + } + return files; + }); + + await asyncConsumer(tabArray, consumer, parallelChunk); + } else { + // shutdown, 如果是初始化阶段,什么都不需要做 + const consumer = async (path: string) => { + await clearDiagnostics(client, path); + }; + if (!isInitialise) { + await asyncConsumer(lintPaths, consumer, parallelChunk); + } + } +} + export const vlogLinterManager = new LinterManager(HdlLangID.Verilog, [ 'iverilog', 'modelsim', diff --git a/src/function/lsp/linter/modelsim.ts b/src/function/lsp/linter/modelsim.ts deleted file mode 100644 index 1562241..0000000 --- a/src/function/lsp/linter/modelsim.ts +++ /dev/null @@ -1,200 +0,0 @@ -import * as vscode from "vscode"; -import * as fs from 'fs'; - -import { LinterOutput, ReportType, opeParam } from "../../../global"; -import { hdlFile, hdlPath } from "../../../hdlFs"; -import { easyExec } from "../../../global/util"; -import { HdlLangID } from "../../../global/enum"; - -class ModelsimLinter { - diagnostic: vscode.DiagnosticCollection; - executableFileMap: Map = new Map(); - executableInvokeNameMap: Map = new Map(); - linterArgsMap: Map = new Map(); - - constructor() { - this.diagnostic = vscode.languages.createDiagnosticCollection(); - - // configure map for executable file name - this.executableFileMap.set(HdlLangID.Verilog, 'vlog'); - this.executableFileMap.set(HdlLangID.Vhdl, 'vcom'); - this.executableFileMap.set(HdlLangID.SystemVerilog, 'vlog'); - this.executableFileMap.set(HdlLangID.Unknown, undefined); - - // configure map for argruments when lintering - this.linterArgsMap.set(HdlLangID.Verilog, ['-quiet', '-nologo']); - this.linterArgsMap.set(HdlLangID.Vhdl, ['-quiet', '-nologo', '-2008']); - this.linterArgsMap.set(HdlLangID.SystemVerilog, ['-quiet', '-nolog', '-sv']); - this.linterArgsMap.set(HdlLangID.Unknown, []); - } - - - async lint(document: vscode.TextDocument) { - const filePath = hdlPath.toSlash(document.fileName); - const langID = hdlFile.getLanguageId(filePath); - - // acquire install path - const linterArgs = this.linterArgsMap.get(langID); - - if (linterArgs === undefined) { - return; - } - - 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); - this.diagnostic.set(document.uri, diagnostics); - } - } else { - LinterOutput.report('modelsim linter is not available, please check prj.modelsim.install.path in your setting!', { - level: ReportType.Error, - notify: 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[] { - const diagnostics = []; - for (const line of stdout.split(/\r?\n/g)) { - const tokens = line.split(/(Error|Warning).+?(?: *?(?:.+?(?:\\|\/))+.+?\((\d+?)\):|)(?: *?near "(.+?)":|)(?: *?\((.+?)\)|) +?(.+)/gm); - - const headerInfo = tokens[1]; - if (headerInfo === 'Error') { - const errorLine = parseInt(tokens[2]) - 1; - const syntaxInfo = tokens[5]; - LinterOutput.report(` line: ${errorLine}, info: ${syntaxInfo}`, { - level: 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') { - const errorLine = parseInt(tokens[2]) - 1; - const syntaxInfo = tokens[5]; - LinterOutput.report(` line: ${errorLine}, info: ${syntaxInfo}`, { - level: ReportType.Run - }); - - const range = this.makeCorrectRange(document, errorLine, syntaxInfo); - const diag = new vscode.Diagnostic(range, syntaxInfo, vscode.DiagnosticSeverity.Warning); - diagnostics.push(diag); - } - } - return diagnostics; - } - - 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; - } else { - return new vscode.Range(startPosition, startPosition); - } - } - - public getExecutableFilePath(langID: HdlLangID): string | undefined { - // modelsim install path stored in prj.modelsim.install.path - const modelsimConfig = vscode.workspace.getConfiguration('digital-ide.prj.modelsim'); - const modelsimInstallPath = modelsimConfig.get('install.path', ''); - const executorName = this.executableFileMap.get(langID); - if (executorName === undefined) { - return undefined; - } - - // e.g. vlog.exe in windows, vlog in linux - const fullExecutorName = opeParam.os === 'win32' ? executorName + '.exe' : executorName; - - if (modelsimInstallPath.trim() === '' || !fs.existsSync(modelsimInstallPath)) { - LinterOutput.report(`User's modelsim Install Path "${modelsimInstallPath}", which is invalid. Use ${executorName} in default.`, { - level: ReportType.Warn - }); - LinterOutput.report('If you have doubts, check prj.modelsim.install.path in setting', { - level: ReportType.Warn - }); - return executorName; - } else { - LinterOutput.report(`User's modelsim Install Path "${modelsimInstallPath}", which is invalid`); - - const executorPath = hdlPath.join( - hdlPath.toSlash(modelsimInstallPath), - fullExecutorName - ); - // prevent path like C://stupid name/xxx/xxx/bin - // blank space - const safeExecutorPath = '"' + executorPath + '"'; - return safeExecutorPath; - } - } - - - public async setExecutableFilePath(executorPath: string | undefined, langID: HdlLangID): Promise { - if (executorPath === undefined) { - return false; - } - const { stderr } = await easyExec(executorPath, []); - if (stderr.length === 0) { - this.executableInvokeNameMap.set(langID, executorPath); - LinterOutput.report(`success to verify ${executorPath}, linter from modelsim is ready to go!`, { - level: ReportType.Launch - }); - return true; - } else { - this.executableInvokeNameMap.set(langID, undefined); - LinterOutput.report(`Fail to execute ${executorPath}! Reason: ${stderr}`, { - level: ReportType.Error, - notify: true - }); - return false; - } - } - - public async initialise(langID: HdlLangID): Promise { - const executorPath = this.getExecutableFilePath(langID); - return this.setExecutableFilePath(executorPath, langID); - } -} - -const modelsimLinter = new ModelsimLinter(); - -export { - modelsimLinter, - ModelsimLinter -}; diff --git a/src/function/lsp/linter/verilator.ts b/src/function/lsp/linter/verilator.ts deleted file mode 100644 index 3b0ed76..0000000 --- a/src/function/lsp/linter/verilator.ts +++ /dev/null @@ -1,180 +0,0 @@ -import * as vscode from "vscode"; -import * as fs from 'fs'; - -import { LinterOutput, ReportType, opeParam } from "../../../global"; -import { hdlFile, hdlPath } from "../../../hdlFs"; -import { easyExec } from "../../../global/util"; -import { HdlLangID } from "../../../global/enum"; - -type Path = string; - -class VerilatorLinter { - diagnostic: vscode.DiagnosticCollection; - executableFileMap: Map = new Map(); - executableInvokeNameMap: Map = new Map(); - linterArgsMap: Map = new Map(); - - constructor() { - this.diagnostic = vscode.languages.createDiagnosticCollection(); - - // configure map for executable file name - this.executableFileMap.set(HdlLangID.Verilog, 'verilator'); - this.executableFileMap.set(HdlLangID.SystemVerilog, 'verilator'); - this.executableFileMap.set(HdlLangID.Unknown, undefined); - - // configure map for argruments when lintering - this.linterArgsMap.set(HdlLangID.Verilog, ['--lint-only', '-Wall', '-bbox-sys', '--bbox-unsup', '-DGLBL']); - this.linterArgsMap.set(HdlLangID.SystemVerilog, ['--lint-only', '-sv', '-Wall', '-bbox-sys', '--bbox-unsup', '-DGLBL']); - this.linterArgsMap.set(HdlLangID.Unknown, []); - } - - - async lint(document: vscode.TextDocument) { - const filePath = hdlPath.toSlash(document.fileName); - const langID = hdlFile.getLanguageId(filePath); - - // acquire install path - const linterArgs = this.linterArgsMap.get(langID); - - if (linterArgs === undefined) { - return; - } - - const args = [filePath, ...linterArgs]; - const executor = this.executableInvokeNameMap.get(langID); - if (executor !== undefined) { - const { stderr } = await easyExec(executor, args); - if (stderr.length > 0) { - const diagnostics = this.provideDiagnostics(document, stderr); - this.diagnostic.set(document.uri, diagnostics); - } - } else { - LinterOutput.report('verilator linter is not available, please check prj.verilator.install.path in your setting', { - level: ReportType.Error, - notify: 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, stderr: string): vscode.Diagnostic[] { - const diagnostics = []; - 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]; - - 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); - } - } - return diagnostics; - } - - 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; - } else { - return new vscode.Range(startPosition, startPosition); - } - } - - public getExecutableFilePath(langID: HdlLangID): string | Path | undefined { - // verilator install path stored in prj.verilator.install.path - const verilatorConfig = vscode.workspace.getConfiguration('digital-ide.prj.verilator'); - const verilatorInstallPath = verilatorConfig.get('install.path', ''); - const executorName = this.executableFileMap.get(langID); - if (executorName === undefined) { - return undefined; - } - - // e.g. vlog.exe in windows, vlog in linux - const fullExecutorName = opeParam.os === 'win32' ? executorName + '.exe' : executorName; - - if (verilatorInstallPath.trim() === '' || !fs.existsSync(verilatorInstallPath)) { - LinterOutput.report(`User's verilator Install Path ${verilatorInstallPath}, which is invalid. Use ${executorName} in default.`, { - level: ReportType.Warn - }); - LinterOutput.report('If you have doubts, check prj.verilator.install.path in setting', { - level: ReportType.Warn - }); - return executorName; - } else { - LinterOutput.report(`User's verilator Install Path ${verilatorInstallPath}, which is invalid`); - - const executorPath = hdlPath.join( - hdlPath.toSlash(verilatorInstallPath), - fullExecutorName - ); - // prevent path like C://stupid name/xxx/xxx/bin - // blank space - const safeExecutorPath = '"' + executorPath + '"'; - return safeExecutorPath; - } - } - - - public async setExecutableFilePath(executorPath: string | Path | undefined, langID: HdlLangID): Promise { - if (executorPath === undefined) { - return false; - } - const { stderr } = await easyExec(executorPath, []); - if (stderr.length === 0) { - this.executableInvokeNameMap.set(langID, executorPath); - LinterOutput.report(`success to verify ${executorPath}, linter from verilator is ready to go!`, { - level: ReportType.Launch - }); - return true; - } else { - this.executableInvokeNameMap.set(langID, undefined); - console.log(stderr); - - LinterOutput.report(`Fail to execute ${executorPath}! Reason: ${stderr}`, { - level: ReportType.Error, - notify: true - }); - - return false; - } - } - - public async initialise(langID: HdlLangID): Promise { - const executorPath = this.getExecutableFilePath(langID); - return this.setExecutableFilePath(executorPath, langID); - } -} - -const verilatorLinter = new VerilatorLinter(); - -export { - verilatorLinter, - VerilatorLinter -}; diff --git a/src/function/lsp/linter/vivado.ts b/src/function/lsp/linter/vivado.ts deleted file mode 100644 index 83f37e5..0000000 --- a/src/function/lsp/linter/vivado.ts +++ /dev/null @@ -1,198 +0,0 @@ -import * as vscode from "vscode"; -import * as fs from 'fs'; - -import { LinterOutput, ReportType, opeParam } from "../../../global"; -import { hdlFile, hdlPath } from "../../../hdlFs"; -import { easyExec } from "../../../global/util"; -import { HdlLangID } from "../../../global/enum"; - -type Path = string; - -class VivadoLinter { - diagnostic: vscode.DiagnosticCollection; - executableFileMap: Map = new Map(); - executableInvokeNameMap: Map = new Map(); - linterArgsMap: Map = new Map(); - - constructor() { - this.diagnostic = vscode.languages.createDiagnosticCollection(); - - // configure map for executable file name - this.executableFileMap.set(HdlLangID.Verilog, 'xvlog'); - this.executableFileMap.set(HdlLangID.Vhdl, 'xvhdl'); - this.executableFileMap.set(HdlLangID.SystemVerilog, 'xvlog'); - this.executableFileMap.set(HdlLangID.Unknown, undefined); - - // configure map for argruments when lintering - this.linterArgsMap.set(HdlLangID.Verilog, ['--nolog']); - this.linterArgsMap.set(HdlLangID.Vhdl, ['--nolog']); - this.linterArgsMap.set(HdlLangID.SystemVerilog, ['--sv', '--nolog']); - this.linterArgsMap.set(HdlLangID.Unknown, []); - - // this.initialise(HdlLangID.Verilog); - // this.initialise(HdlLangID.Vhdl); - // this.initialise(HdlLangID.SystemVerilog); - } - - - async lint(document: vscode.TextDocument) { - const filePath = hdlPath.toSlash(document.fileName); - const langID = hdlFile.getLanguageId(filePath); - - // acquire install path - const linterArgs = this.linterArgsMap.get(langID); - if (linterArgs === undefined) { - return; - } - - 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); - this.diagnostic.set(document.uri, diagnostics); - } - } else { - LinterOutput.report('vivado linter is not available, please check prj.vivado.install.path in your setting', { - level: ReportType.Error, - notify: 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[] { - const diagnostics = []; - for (const line of stdout.split(/\r?\n/g)) { - const tokens = line.split(/:?\s*(?:\[|\])\s*/); - const headerInfo = tokens[0]; - // const standardInfo = tokens[1]; - const syntaxInfo = tokens[2]; - const parsedPath = tokens[3]; - if (headerInfo === 'ERROR') { - const errorInfos = parsedPath.split(':'); - const errorLine = Math.max(parseInt(errorInfos[errorInfos.length - 1]) - 1, 0); - LinterOutput.report(` line: ${errorLine}, info: ${syntaxInfo}`, { - level: ReportType.Run - }); - - const range = this.makeCorrectRange(document, errorLine, syntaxInfo); - const diag = new vscode.Diagnostic(range, syntaxInfo, vscode.DiagnosticSeverity.Error); - diagnostics.push(diag); - } - } - return diagnostics; - } - - 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; - } else { - return new vscode.Range(startPosition, startPosition); - } - } - - public getExecutableFilePath(langID: HdlLangID): string | Path | undefined { - // vivado install path stored in prj.vivado.install.path - const vivadoConfig = vscode.workspace.getConfiguration('digital-ide.prj.vivado'); - const vivadoInstallPath = vivadoConfig.get('install.path', ''); - const executorName = this.executableFileMap.get(langID); - if (executorName === undefined) { - return undefined; - } - - // e.g. xvlog.bat in windows, xvlog in linux - const fullExecutorName = opeParam.os === 'win32' ? executorName + '.bat' : executorName; - - if (vivadoInstallPath.trim() === '' || !fs.existsSync(vivadoInstallPath)) { - LinterOutput.report(`User's Vivado Install Path "${vivadoInstallPath}", which is invalid. Use ${executorName} in default.`, { - level: ReportType.Warn - }); - LinterOutput.report('If you have doubts, check prj.vivado.install.path in setting', { - level: ReportType.Warn - }); - - return executorName; - } else { - LinterOutput.report(`User's Vivado Install Path "${vivadoInstallPath}", which is invalid`); - - const executorPath = hdlPath.join( - hdlPath.toSlash(vivadoInstallPath), - fullExecutorName - ); - // prevent path like C://stupid name/xxx/xxx/bin - // blank space - const safeExecutorPath = '"' + executorPath + '"'; - return safeExecutorPath; - } - } - - - public async setExecutableFilePath(executorPath: string | Path | undefined, langID: HdlLangID): Promise { - if (executorPath === undefined) { - return false; - } - const { stderr } = await easyExec(executorPath, []); - if (stderr.length === 0) { - this.executableInvokeNameMap.set(langID, executorPath); - LinterOutput.report(`success to verify ${executorPath}, linter from vivado is ready to go!`, { - level: ReportType.Launch - }); - return true; - } else { - this.executableInvokeNameMap.set(langID, undefined); - LinterOutput.report(`Fail to execute ${executorPath}! Reason: ${stderr}`, { - level: ReportType.Error, - notify: true - }); - return false; - } - } - - public async initialise(langID: HdlLangID): Promise { - const executorPath = this.getExecutableFilePath(langID); - return await this.setExecutableFilePath(executorPath, langID); - } -} - -const vivadoLinter = new VivadoLinter(); - -export { - vivadoLinter, - VivadoLinter -}; diff --git a/src/global/lsp.ts b/src/global/lsp.ts index 8b1461e..42227fb 100644 --- a/src/global/lsp.ts +++ b/src/global/lsp.ts @@ -29,7 +29,7 @@ export const DoFastRequestType = new RequestType('api/update-configuration'); export const DoPrimitivesJudgeType = new RequestType('api/do-primitives-judge'); export const SyncFastRequestType = new RequestType('api/sync-fast'); -export const LinterStatusRequestType = new RequestType('api/linter-status'); +export const LinterStatusRequestType = new RequestType('api/linter-status'); export interface ITextDocumentItem { uri: vscode.Uri, @@ -57,7 +57,7 @@ export interface IUpdateConfigurationParam { configType: string } -export interface ILinterStatusRequestType { +export interface ILinterStatusParam { languageId: string, linterName: string, linterPath: string diff --git a/src/hdlParser/core.ts b/src/hdlParser/core.ts index 82dcf36..260de88 100644 --- a/src/hdlParser/core.ts +++ b/src/hdlParser/core.ts @@ -332,7 +332,7 @@ class HdlParam { const increment = Math.floor(p.id / fileNum * 100); await p.promise; // console.log("handle id " + p.id + ' increment: ' + increment); - progress?.report({ message: reportTitle + ` ${p.id}/${fileNum}`, increment }); + progress.report({ message: reportTitle + ` ${p.id}/${fileNum}`, increment }); } pools.length = 0; } diff --git a/src/manager/index.ts b/src/manager/index.ts index 6a9e559..104668c 100644 --- a/src/manager/index.ts +++ b/src/manager/index.ts @@ -4,7 +4,7 @@ import * as assert from 'assert'; import { prjManage } from './prj'; import { pickLibrary } from './libPick'; -function registerManagerCommands(context: vscode.ExtensionContext) { +export function registerManagerCommands(context: vscode.ExtensionContext) { // make ps and ps have been prepared assert(prjManage.pl, 'pl is undefined'); // assert(prjManage.ps, 'ps is undefined'); @@ -45,6 +45,5 @@ function registerManagerCommands(context: vscode.ExtensionContext) { } export { - prjManage, - registerManagerCommands + prjManage }; \ No newline at end of file diff --git a/src/manager/prj.ts b/src/manager/prj.ts index f0d0a2c..d0af706 100644 --- a/src/manager/prj.ts +++ b/src/manager/prj.ts @@ -16,6 +16,7 @@ import { t } from '../i18n'; import { PpyAction } from '../monitor/propery'; import { refreshArchTree } from '../function/treeView'; import * as lspClient from '../function/lsp-client'; +import { refreshWorkspaceDiagonastics } from '../function/lsp/linter/manager'; interface RefreshPrjConfig { @@ -211,6 +212,8 @@ class PrjManage { if (countTimeCost) { console.timeLog('launch'); } + + return hdlFiles; } public async refreshPrjFolder(config?: RefreshPrjConfig) {