use std::process::{Command, Stdio}; use log::info; use regex::Regex; use serde::{Deserialize, Serialize}; use ropey::Rope; use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url}; use crate::{diagnostics::find_non_whitespace_indices, server::LspServer}; use super::AbstractLinterConfiguration; #[derive(Debug, Serialize, Deserialize)] pub struct IverilogConfiguration { pub language_id: String, pub linter: IverilogLinter } #[derive(Debug, Serialize, Deserialize)] pub struct IverilogLinter { pub name: String, pub exe_name: String, /// 目前是否启动 pub enabled: bool, pub path: String, pub args: Vec, } impl IverilogLinter { fn new(invoker: &str, args: Vec) -> Self { IverilogLinter { name: "iverilog".to_string(), exe_name: invoker.to_string(), enabled: false, path: invoker.to_string(), args } } } impl AbstractLinterConfiguration for IverilogConfiguration { fn new(language_id: &str, invoker: &str, args: Vec) -> Self { IverilogConfiguration { language_id: language_id.to_string(), linter: IverilogLinter::new(invoker, args) } } fn provide_diagnostics( &self, uri: &Url, rope: &Rope, 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!("iverilog linter: {:?}, output:\n{}", path_string, output_string); // 初始化一个正则捕获器 static REGEX_STORE: std::sync::OnceLock = std::sync::OnceLock::new(); // {filename}:{errorno}: {error tag}(:{error description}) let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"^(.*?)\s*:\s*(\d+)\s*:\s*(\w+)\s*(?::\s*(.*))?$").unwrap() }); for error_line in output_string.lines() { let captures = match regex.captures(error_line) { Some(cap) => cap, None => continue }; let error_no = captures.get(2).unwrap().as_str().trim(); let error_tag = captures.get(3).unwrap().as_str().trim(); let error_description = captures.get(4).map_or("", |m| m.as_str()).trim(); let error_no = match error_no.parse::() { // Icarus Verilog 在报告错误和警告时,行数是从 1 开始计数的。 Ok(no) => no - 1, Err(_) => 0 }; if let Some((start_char, end_char)) = find_non_whitespace_indices(rope, error_no) { let range = Range { start: Position { line: error_no as u32, character: start_char as u32 }, end: Position { line: error_no as u32, character: end_char as u32 } }; // 特殊处理错误信息,比如 // /home/dide/project/Digital-Test/DIDEtemp/user/sim/FFT/FFT_IFFT_tb.v:81: error: Unknown module type: FFT_IFFT // 找不到模块 XXX,此时 error_description 就是 Unknown module type: 找不到的模块名,去 fast 里寻找 if error_description.starts_with("Unknown module type") { let mut groups = error_description.split(":"); if let Some(unknown_module_name) = groups.nth(1) { let unknown_module_name = unknown_module_name.trim(); info!("包含 {} ? {}", unknown_module_name, server.db.contains_module(unknown_module_name)); if server.db.contains_module(unknown_module_name) { continue; } } } let diagnostic = Diagnostic { range, code: None, severity: Some(DiagnosticSeverity::ERROR), source: Some("Digital IDE: iverilog".to_string()), message: format!("{} {}", error_tag, error_description), related_information: None, tags: None, code_description: None, data: None }; diagnostics.push(diagnostic); } } Some(diagnostics) } fn get_linter_path(&self) -> &str { &self.linter.path } 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() } } }