use log::info; use serde::{Deserialize, Serialize}; use regex::Regex; use ropey::Rope; use std::process::{Command, Stdio}; use tower_lsp::lsp_types::*; use crate::server::LspServer; use super::AbstractLinterConfiguration; #[derive(Debug, Serialize, Deserialize)] pub struct VeribleConfiguration { pub language_id: String, pub linter: VeribleLinter, pub format: VeribleFormat, } #[derive(Debug, Serialize, Deserialize)] pub struct VeribleLinter { pub name: String, pub exe_name: String, /// 目前是否启动 pub enabled: bool, pub path: String, pub args: Vec, } #[derive(Debug, Serialize, Deserialize)] pub struct VeribleFormat { pub enabled: bool, pub path: String, pub args: Vec, } impl VeribleLinter { fn new(invoker: &str, args: Vec) -> Self { Self { name: "verible".to_string(), exe_name: invoker.to_string(), enabled: false, path: invoker.to_string(), args, } } } impl Default for VeribleFormat { fn default() -> Self { Self { enabled: false, path: "verible-verilog-format".to_string(), args: Vec::new(), } } } impl AbstractLinterConfiguration for VeribleConfiguration { fn new(language_id: &str, invoker: &str, args: Vec) -> Self { VeribleConfiguration { language_id: language_id.to_string(), linter: VeribleLinter::new(invoker, args), format: VeribleFormat::default() } } fn provide_diagnostics( &self, uri: &Url, #[allow(unused)] rope: &Rope, #[allow(unused)] 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.stdout).ok()?; info!("verible 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"^.+:(?P\d*):(?P\d*)(?:-(?P\d*))?:\s(?P.*)\s.*$").unwrap() }); for error_line in output_string.lines() { let caps = regex.captures(error_line)?; let line: u32 = caps.name("line")?.as_str().parse().ok()?; let startcol: u32 = caps.name("startcol")?.as_str().parse().ok()?; let endcol: Option = match caps.name("endcol").map(|e| e.as_str().parse()) { Some(Ok(e)) => Some(e), None => None, Some(Err(_)) => return None, }; let start_pos = Position::new(line - 1, startcol - 1); let end_pos = Position::new(line - 1, endcol.unwrap_or(startcol) - 1); let diagnostic = Diagnostic { range: Range::new(start_pos, end_pos), severity: Some(DiagnosticSeverity::ERROR), code: None, source: Some("Digital IDE: verible".to_string()), message: caps.name("message")?.as_str().to_string(), 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() } } }