143 lines
4.5 KiB
Rust
143 lines
4.5 KiB
Rust
use std::process::{Command, Stdio};
|
|
|
|
#[allow(unused)]
|
|
use log::info;
|
|
use regex::Regex;
|
|
use serde::{Deserialize, Serialize};
|
|
use crate::{diagnostics::find_non_whitespace_indices, server::LspServer};
|
|
use super::AbstractLinterConfiguration;
|
|
use ropey::Rope;
|
|
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url};
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct ModelsimConfiguration {
|
|
pub language_id: String,
|
|
pub linter: ModelsimLinter
|
|
}
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct ModelsimLinter {
|
|
pub name: String,
|
|
pub exe_name: String,
|
|
/// 目前是否启动
|
|
pub enabled: bool,
|
|
pub path: String,
|
|
pub args: Vec<String>,
|
|
}
|
|
|
|
|
|
impl ModelsimLinter {
|
|
fn new(invoker: &str, args: Vec<String>) -> Self {
|
|
Self {
|
|
name: "modelsim".to_string(),
|
|
exe_name: invoker.to_string(),
|
|
enabled: false,
|
|
path: invoker.to_string(),
|
|
args,
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
impl AbstractLinterConfiguration for ModelsimConfiguration {
|
|
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
|
|
ModelsimConfiguration {
|
|
language_id: language_id.to_string(),
|
|
linter: ModelsimLinter::new(invoker, args)
|
|
}
|
|
}
|
|
|
|
fn provide_diagnostics(
|
|
&self,
|
|
uri: &Url,
|
|
#[allow(unused)]
|
|
rope: &Rope,
|
|
#[allow(unused)]
|
|
server: &LspServer
|
|
) -> Option<Vec<Diagnostic>> {
|
|
let mut diagnostics = Vec::<Diagnostic>::new();
|
|
let invoke_name = self.get_invoke_name();
|
|
let pathbuf = uri.to_file_path().unwrap();
|
|
let path_string = pathbuf.to_str().unwrap();
|
|
|
|
let cache_info = server.cache.cache_info.read().unwrap();
|
|
|
|
let cwd = match &cache_info.linter_cache {
|
|
Some(pc) => pc,
|
|
None => {
|
|
info!("缓存系统尚未完成初始化,本次诊断取消");
|
|
return None;
|
|
}
|
|
};
|
|
|
|
let child = Command::new(&invoke_name)
|
|
.current_dir(cwd)
|
|
.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!("modelsim linter: {:?}, output:\n{}", path_string, output_string);
|
|
|
|
static REGEX_STORE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
|
|
let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"\*\* (Error|Warning): \(\S+\) (?P<file>.*?)(\((?P<line>\d+)\))?: (?P<description>.+)").unwrap() });
|
|
|
|
for error_line in output_string.lines() {
|
|
let caps = match regex.captures(error_line) {
|
|
Some(caps) => caps,
|
|
None => continue
|
|
};
|
|
|
|
info!("get caps: {:?}", caps);
|
|
|
|
let error_description = caps.name("description").unwrap().as_str();
|
|
let error_no = caps.name("line").unwrap().as_str();
|
|
let error_no = match error_no.parse::<usize>() {
|
|
// Mentor Modelsim vlog 在报告错误和警告时,行数是从 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 }
|
|
};
|
|
|
|
let diagnostic = Diagnostic {
|
|
range,
|
|
code: None,
|
|
severity: Some(DiagnosticSeverity::ERROR),
|
|
source: Some("Digital IDE: modelsim".to_string()),
|
|
message: error_description.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()
|
|
}
|
|
}
|
|
} |