diff --git a/src/diagnostics/common.rs b/src/diagnostics/common.rs index 6def168..197c729 100644 --- a/src/diagnostics/common.rs +++ b/src/diagnostics/common.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use ropey::Rope; use tower_lsp::lsp_types::{Diagnostic, Url}; -use crate::server::LspServer; +use crate::{server::LspServer, utils::command::is_command_valid}; #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] @@ -35,32 +35,28 @@ pub trait AbstractLinterConfiguration { /// 提供一次诊断目前基于 uri 中的地址读取真实路径来获取诊断结果 fn provide_diagnostics(&self, uri: &Url, rope: &Rope, server: &LspServer) -> Option>; - /// 获取工具在 Windows 下的后缀,比如 iverilog 就是 exe , xvlog 就是 bat - fn get_windows_extension(&self) -> &str; - /// 获取 linter 安装路径,内部实现,内部调用 fn get_linter_path(&self) -> &str; + /// 获取 可执行文件 的名字,比如 + /// - iverilog.exe + /// - iverilog + /// - xvlog.bat + fn get_exe_name(&self) -> String; + /// 获取真实调用路径,比如 /// iverilog.exe 或者 /path/to/verilog.exe - fn get_invoke_name(&self, execute_name: &str) -> String { - let suffix = { - let os = std::env::consts::OS; - if os == "windows" { - self.get_windows_extension() - } else { - "" - } - }; + fn get_invoke_name(&self) -> String { + let exe_name = self.get_exe_name(); let linter_path = self.get_linter_path(); let pathbuf = PathBuf::from_str(linter_path); if linter_path.len() == 0 || !pathbuf.as_ref().unwrap().exists() { - format!("{}{}", execute_name, suffix) + exe_name.to_string() } else { // 路径存在 let pathbuf = pathbuf.unwrap(); - let pathbuf = pathbuf.join(format!("{}{}", execute_name, suffix)); + let pathbuf = pathbuf.join(exe_name); let path_string = pathbuf.to_str().unwrap(); path_string.to_string() } @@ -69,7 +65,15 @@ pub trait AbstractLinterConfiguration { /// 获取与该工具匹配的诊断器名字与调用函数名。并检查它们是否有效 /// /// 参考链接:https://kirigaya.cn/blog/article?seq=284 - fn linter_status(&self) -> LinterStatus; + fn linter_status(&self) -> LinterStatus { + let invoke_name = self.get_invoke_name(); + let available = is_command_valid(&invoke_name); + LinterStatus { + tool_name: self.get_exe_name(), + available, + invoke_name + } + } } /// rope 的第 line_index 行文字中,第一个和最后一个非空字符在本行的索引 diff --git a/src/diagnostics/iverilog.rs b/src/diagnostics/iverilog.rs index 936bac3..17715cb 100644 --- a/src/diagnostics/iverilog.rs +++ b/src/diagnostics/iverilog.rs @@ -54,7 +54,7 @@ impl AbstractLinterConfiguration for IverilogConfiguration { ) -> Option> { let mut diagnostics = Vec::::new(); - let invoke_name = self.get_invoke_name("iverilog"); + let invoke_name = self.get_invoke_name(); let pathbuf = uri.to_file_path().unwrap(); let path_string = pathbuf.to_str().unwrap(); @@ -133,31 +133,15 @@ impl AbstractLinterConfiguration for IverilogConfiguration { Some(diagnostics) } - fn get_windows_extension(&self) -> &str { - "exe" - } - fn get_linter_path(&self) -> &str { &self.linter.path } - fn linter_status(&self) -> LinterStatus { - match self.language_id.as_str() { - "verilog" => { - let invoke_name = self.get_invoke_name("iverilog"); - let available = is_command_valid(&invoke_name); - LinterStatus { - tool_name: "iverilog".to_string(), - available, - invoke_name - } - } - - // 不支持 svlog - // 不支持 vhdl - _ => { - LinterStatus::default() - } + fn get_exe_name(&self) -> String { + if std::env::consts::OS == "windows" { + format!("{}.exe", self.linter.exe_name) + } else { + self.linter.exe_name.to_string() } } } diff --git a/src/diagnostics/mod.rs b/src/diagnostics/mod.rs index 3d5c98f..399794b 100644 --- a/src/diagnostics/mod.rs +++ b/src/diagnostics/mod.rs @@ -139,19 +139,19 @@ pub fn update_diagnostics_configuration( "iverilog" => { linter_configuration.iverilog.linter.enabled = true; linter_configuration.iverilog.linter.path = linter_path.to_string(); - let invoke_name = linter_configuration.iverilog.get_invoke_name("iverilog"); + let invoke_name = linter_configuration.iverilog.get_invoke_name(); info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); } "verilator" => { linter_configuration.verilator.linter.enabled = true; linter_configuration.verilator.linter.path = linter_path.to_string(); - let invoke_name = linter_configuration.verilator.get_invoke_name("verilator"); + let invoke_name = linter_configuration.verilator.get_invoke_name(); info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); } "verible" => { linter_configuration.verible.linter.enabled = true; linter_configuration.verible.linter.path = linter_path.to_string(); - let invoke_name = linter_configuration.verible.get_invoke_name("verible-verilog-syntax"); + let invoke_name = linter_configuration.verible.get_invoke_name(); info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); } "modelsim" => { @@ -159,9 +159,9 @@ pub fn update_diagnostics_configuration( linter_configuration.modelsim.linter.path = linter_path.to_string(); let invoke_name = { if language_id == "vhdl" { - linter_configuration.modelsim.get_invoke_name("vcom") + linter_configuration.modelsim.get_invoke_name() } else { - linter_configuration.modelsim.get_invoke_name("vlog") + linter_configuration.modelsim.get_invoke_name() } }; info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); @@ -171,9 +171,9 @@ pub fn update_diagnostics_configuration( linter_configuration.vivado.linter.path = linter_path.to_string(); let invoke_name = { if language_id == "vhdl" { - linter_configuration.vivado.get_invoke_name("xvhdl") + linter_configuration.vivado.get_invoke_name() } else { - linter_configuration.vivado.get_invoke_name("xvlog") + linter_configuration.vivado.get_invoke_name() } }; info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); diff --git a/src/diagnostics/modelsim.rs b/src/diagnostics/modelsim.rs index 1bd5876..8b692e1 100644 --- a/src/diagnostics/modelsim.rs +++ b/src/diagnostics/modelsim.rs @@ -56,49 +56,15 @@ impl AbstractLinterConfiguration for ModelsimConfiguration { Some(diagnostics) } - fn get_windows_extension(&self) -> &str { - "exe" - } - fn get_linter_path(&self) -> &str { &self.linter.path } - fn linter_status(&self) -> LinterStatus { - match self.language_id.as_str() { - "verilog" => { - let invoke_name = self.get_invoke_name("vlog"); - let available = is_command_valid(&invoke_name); - LinterStatus { - tool_name: "vlog".to_string(), - available, - invoke_name - } - } - - "systemverilog" => { - let invoke_name = self.get_invoke_name("vlog"); - let available = is_command_valid(&invoke_name); - LinterStatus { - tool_name: "vlog".to_string(), - available, - invoke_name - } - } - - "vhdl" => { - let invoke_name = self.get_invoke_name("vcom"); - let available = is_command_valid(&invoke_name); - LinterStatus { - tool_name: "vcom".to_string(), - available, - invoke_name - } - } - - _ => { - LinterStatus::default() - } + fn get_exe_name(&self) -> String { + if std::env::consts::OS == "windows" { + format!("{}.exe", self.linter.exe_name) + } else { + self.linter.exe_name.to_string() } } } \ No newline at end of file diff --git a/src/diagnostics/verible.rs b/src/diagnostics/verible.rs index 0b00ece..44d7fa6 100644 --- a/src/diagnostics/verible.rs +++ b/src/diagnostics/verible.rs @@ -142,36 +142,11 @@ impl AbstractLinterConfiguration for VeribleConfiguration { &self.linter.path } - fn get_windows_extension(&self) -> &str { - "exe" - } - - fn linter_status(&self) -> LinterStatus { - match self.language_id.as_str() { - "verilog" => { - let invoke_name = self.get_invoke_name("verible-verilog-syntax"); - let available = is_command_valid(&invoke_name); - LinterStatus { - tool_name: "verible-verilog-syntax".to_string(), - available, - invoke_name - } - } - - "systemverilog" => { - let invoke_name = self.get_invoke_name("verible-verilog-syntax"); - let available = is_command_valid(&invoke_name); - LinterStatus { - tool_name: "verible-verilog-syntax".to_string(), - available, - invoke_name - } - } - - // verible 不支持 vhdl - _ => { - LinterStatus::default() - } + fn get_exe_name(&self) -> String { + if std::env::consts::OS == "windows" { + format!("{}.exe", self.linter.exe_name) + } else { + self.linter.exe_name.to_string() } } } \ No newline at end of file diff --git a/src/diagnostics/verilator.rs b/src/diagnostics/verilator.rs index 563c2a3..c09cae3 100644 --- a/src/diagnostics/verilator.rs +++ b/src/diagnostics/verilator.rs @@ -1,3 +1,4 @@ +use log::info; use serde::{Deserialize, Serialize}; use regex::Regex; use ropey::Rope; @@ -139,6 +140,97 @@ impl AbstractLinterConfiguration for VerilatorConfiguration { server: &LspServer ) -> Option> { let mut diagnostics = Vec::::new(); + + let invoke_name = self.get_invoke_name(); + let pathbuf = uri.to_file_path().unwrap(); + let path_string = pathbuf.to_str().unwrap(); + + let child = Command::new(&invoke_name) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .args(&self.linter.args) + .arg(path_string) + .spawn() + .ok()?; + + let output = child.wait_with_output().ok()?; + let output_string = String::from_utf8(output.stderr).ok()?; + + info!("verilator linter: {:?}, output:\n{}", path_string, output_string); + + static REGEX_STORE: std::sync::OnceLock = std::sync::OnceLock::new(); + let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"%(?PError|Warning)(-(?P[A-Z0-9_]+))?: (?P[^:]+):(?P\d+):((?P\d+):)? ?(?P.*)").unwrap() }); + + // verilator 错误输出样例 + // %Warning-IMPLICIT: library/Apply/Comm/FDE/AGC/AGC.v:23:12: Signal definition not found, creating implicitly: 'r_rms' + // : ... Suggested alternative: 'ref_rms' + // 23 | assign r_rms = (reference * reference); + // | ^~~~~ + // ... Use "/* verilator lint_off IMPLICIT */" and lint_on around source to disable this message. + // %Error: Exiting due to 5 warning(s) + + // 先将上面的输出处理为一整块一整块的数组,每块分解为两个部分:第一行(用于解析行号和列号)和后面几行(具体错误地址) + let mut error_tuples = Vec::<(String, String)>::new(); + let mut current_error_tuple: Option<(&str, Vec<&str>)> = None; + for error_line in output_string.lines() { + if error_line.starts_with("%") { + if let Some((first, ref description)) = current_error_tuple { + error_tuples.push((first.to_string(), description.join("\n"))); + } + current_error_tuple = Some((error_line, Vec::<&str>::new())); + } else { + if let Some((_, ref mut description)) = current_error_tuple { + description.push(error_line); + } else { + continue; + } + } + } + + for error_tuple in error_tuples { + let captures = match regex.captures(error_tuple.0.as_str()) { + Some(caps) => caps, + None => continue + }; + // 因为 verilator 会递归检查,因此报错信息中可能会出现非本文件内的信息 + if captures.name("filepath")?.as_str().replace("\\", "/") != path_string { + continue; + } + + let line: u32 = captures.name("line")?.as_str().to_string().parse().ok()?; + let col: u32 = captures.name("col").map_or("1", |m| m.as_str()).parse().ok()?; + + // verilator 的诊断索引都是 one index 的 + let pos = Position::new(line - 1, col - 1); + let severity = verilator_severity(captures.name("severity")?.as_str()); + + let message = match severity { + Some(DiagnosticSeverity::ERROR) => captures.name("message")?.as_str().to_string(), + Some(DiagnosticSeverity::WARNING) => format!( + "{}: {}", + captures.name("warning_type")?.as_str(), + captures.name("message")?.as_str() + ), + _ => "".to_string(), + }; + + let message = format!("{}\n\n---\n\n{}", message, error_tuple.1); + + let diagnostic = Diagnostic { + range: Range::new(pos, pos), + code: None, + severity, + source: Some("Digital IDE: verilator".to_string()), + message, + related_information: None, + tags: None, + code_description: None, + data: None + }; + + diagnostics.push(diagnostic); + } Some(diagnostics) } @@ -147,36 +239,11 @@ impl AbstractLinterConfiguration for VerilatorConfiguration { &self.linter.path } - fn get_windows_extension(&self) -> &str { - "exe" - } - - fn linter_status(&self) -> LinterStatus { - match self.language_id.as_str() { - "verilog" => { - let invoke_name = self.get_invoke_name("verilator"); - let available = is_command_valid(&invoke_name); - LinterStatus { - tool_name: "verilator".to_string(), - available, - invoke_name - } - } - - "systemverilog" => { - let invoke_name = self.get_invoke_name("verilator"); - let available = is_command_valid(&invoke_name); - LinterStatus { - tool_name: "verilator".to_string(), - available, - invoke_name - } - } - - // verilator 不支持 vhdl - _ => { - LinterStatus::default() - } + fn get_exe_name(&self) -> String { + if std::env::consts::OS == "windows" { + format!("{}.exe", self.linter.exe_name) + } else { + self.linter.exe_name.to_string() } } } \ No newline at end of file diff --git a/src/diagnostics/vivado.rs b/src/diagnostics/vivado.rs index 256c65c..79e6699 100644 --- a/src/diagnostics/vivado.rs +++ b/src/diagnostics/vivado.rs @@ -59,45 +59,11 @@ impl AbstractLinterConfiguration for VivadoConfiguration { &self.linter.path } - fn get_windows_extension(&self) -> &str { - "exe" - } - - fn linter_status(&self) -> LinterStatus { - match self.language_id.as_str() { - "verilog" => { - let invoke_name = self.get_invoke_name("xvlog"); - let available = is_command_valid(&invoke_name); - LinterStatus { - tool_name: "xvlog".to_string(), - available, - invoke_name - } - } - - "systemverilog" => { - let invoke_name = self.get_invoke_name("xvlog"); - let available = is_command_valid(&invoke_name); - LinterStatus { - tool_name: "xvlog".to_string(), - available, - invoke_name - } - } - - "vhdl" => { - let invoke_name = self.get_invoke_name("xvhdl"); - let available = is_command_valid(&invoke_name); - LinterStatus { - tool_name: "xvhdl".to_string(), - available, - invoke_name - } - } - - _ => { - LinterStatus::default() - } + fn get_exe_name(&self) -> String { + if std::env::consts::OS == "windows" { + format!("{}.bat", self.linter.exe_name) + } else { + self.linter.exe_name.to_string() } } } \ No newline at end of file diff --git a/src/inlay_hint/sv.rs b/src/inlay_hint/sv.rs index bdaec9b..889ea28 100644 --- a/src/inlay_hint/sv.rs +++ b/src/inlay_hint/sv.rs @@ -17,7 +17,12 @@ pub fn inlay_hint(server: &LspServer, params: &InlayHintParams) -> Option