完成 iverilog 的诊断

This commit is contained in:
锦恢 2024-12-09 23:07:18 +08:00
parent 19fed383b0
commit b1b302bbfb
8 changed files with 156 additions and 43 deletions

View File

@ -5,6 +5,8 @@ use serde::{Deserialize, Serialize};
use ropey::Rope; use ropey::Rope;
use tower_lsp::lsp_types::{Diagnostic, Url}; use tower_lsp::lsp_types::{Diagnostic, Url};
use crate::server::LspServer;
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct LinterStatus { pub struct LinterStatus {
@ -31,7 +33,7 @@ pub trait AbstractLinterConfiguration {
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self; fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self;
/// 提供一次诊断目前基于 uri 中的地址读取真实路径来获取诊断结果 /// 提供一次诊断目前基于 uri 中的地址读取真实路径来获取诊断结果
fn provide_diagnostics(&self, uri: &Url, rope: &Rope) -> Vec<Diagnostic>; fn provide_diagnostics(&self, uri: &Url, rope: &Rope, server: &LspServer) -> Option<Vec<Diagnostic>>;
/// 获取工具在 Windows 下的后缀,比如 iverilog 就是 exe xvlog 就是 bat /// 获取工具在 Windows 下的后缀,比如 iverilog 就是 exe xvlog 就是 bat
fn get_windows_extension(&self) -> &str; fn get_windows_extension(&self) -> &str;
@ -68,4 +70,23 @@ pub trait AbstractLinterConfiguration {
/// ///
/// 参考链接https://kirigaya.cn/blog/article?seq=284 /// 参考链接https://kirigaya.cn/blog/article?seq=284
fn linter_status(&self) -> LinterStatus; fn linter_status(&self) -> LinterStatus;
}
/// rope 的第 line_index 行文字中,第一个和最后一个非空字符在本行的索引
pub fn find_non_whitespace_indices(rope: &Rope, line_index: usize) -> Option<(usize, usize)> {
let line_text = rope.line(line_index);
let mut first_non_whitespace_index = None;
let mut last_non_whitespace_index = None;
for (i, c) in line_text.chars().enumerate() {
if !c.is_whitespace() {
if first_non_whitespace_index.is_none() {
first_non_whitespace_index = Some(i);
}
last_non_whitespace_index = Some(i);
}
}
match (first_non_whitespace_index, last_non_whitespace_index) {
(Some(first), Some(last)) => Some((first, last)),
_ => None,
}
} }

View File

@ -1,10 +1,12 @@
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use log::info;
use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ropey::Rope; use ropey::Rope;
use tower_lsp::lsp_types::{Diagnostic, Url}; use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url};
use crate::utils::command::is_command_valid; use crate::{diagnostics::find_non_whitespace_indices, server::LspServer, sources::LSPSupport, utils::command::is_command_valid};
use super::{AbstractLinterConfiguration, LinterStatus}; use super::{AbstractLinterConfiguration, LinterStatus};
@ -47,25 +49,88 @@ impl AbstractLinterConfiguration for IverilogConfiguration {
fn provide_diagnostics( fn provide_diagnostics(
&self, &self,
uri: &Url, uri: &Url,
#[allow(unused)] rope: &Rope,
rope: &Rope server: &LspServer
) -> Vec<Diagnostic> { ) -> Option<Vec<Diagnostic>> {
let mut diagnostics = Vec::<Diagnostic>::new(); let mut diagnostics = Vec::<Diagnostic>::new();
let invoke_name = self.get_invoke_name("iverilog"); let invoke_name = self.get_invoke_name("iverilog");
let pathbuf = uri.to_file_path().unwrap(); let pathbuf = uri.to_file_path().unwrap();
let path_string = pathbuf.to_str().unwrap(); let path_string = pathbuf.to_str().unwrap();
let mut process = Command::new(invoke_name) let child = Command::new(&invoke_name)
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.arg("-t null") .args(&self.linter.args)
.arg(path_string) .arg(path_string)
.spawn(); .spawn()
.ok()?;
let output = child.wait_with_output().ok()?;
diagnostics let output_string = String::from_utf8(output.stderr).ok()?;
info!("iverilog linter: {:?}, output:\n{}", path_string, output_string);
// 初始化一个正则捕获器
static REGEX_STORE: std::sync::OnceLock<Regex> = 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::<usize>() {
// 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.srcs.contains_module(unknown_module_name));
if server.srcs.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_windows_extension(&self) -> &str { fn get_windows_extension(&self) -> &str {

View File

@ -27,12 +27,18 @@ pub use modelsim::*;
/// - provide_diagnostics: 提供一次诊断 /// - provide_diagnostics: 提供一次诊断
/// - /// -
/// 获取诊断核心函数 /// 获取诊断核心函数
/// - uri: 当前正在诊断的文件的 uri
/// - rope: 当前正在诊断的文件在后端增量更新的文本内容
/// - files: 所有 hdl 文件,方便后续进行联合诊断使用
/// - configuration: 前端关于 lsp 相关的配置
/// - server: 服务实例
pub fn provide_diagnostics( pub fn provide_diagnostics(
uri: Url, uri: Url,
rope: &Rope, rope: &Rope,
#[allow(unused_variables)] #[allow(unused_variables)]
files: Vec<Url>, files: Vec<Url>,
configuration: &LspConfiguration, configuration: &LspConfiguration,
server: &LspServer
) -> PublishDiagnosticsParams { ) -> PublishDiagnosticsParams {
let mut diagnostics = Vec::<Diagnostic>::new(); let mut diagnostics = Vec::<Diagnostic>::new();
let language_id = get_language_id_by_uri(&uri); let language_id = get_language_id_by_uri(&uri);
@ -60,33 +66,33 @@ pub fn provide_diagnostics(
match linter_configuration { match linter_configuration {
config if config.iverilog.linter.enabled => { config if config.iverilog.linter.enabled => {
info!("iverilog linter enter"); info!("iverilog linter enter");
diagnostics.append( if let Some(diag) = &mut config.iverilog.provide_diagnostics(&uri, rope, server) {
&mut config.iverilog.provide_diagnostics(&uri, rope) diagnostics.append(diag);
); }
} }
config if config.verilator.linter.enabled => { config if config.verilator.linter.enabled => {
info!("verilator linter enter"); info!("verilator linter enter");
diagnostics.append( if let Some(diag) = &mut config.verilator.provide_diagnostics(&uri, rope, server) {
&mut config.verilator.provide_diagnostics(&uri, rope) diagnostics.append(diag);
); }
} }
config if config.verible.linter.enabled => { config if config.verible.linter.enabled => {
info!("verible linter enter"); info!("verible linter enter");
diagnostics.append( if let Some(diag) = &mut config.verible.provide_diagnostics(&uri, rope, server) {
&mut config.verible.provide_diagnostics(&uri, rope) diagnostics.append(diag);
); }
} }
config if config.modelsim.linter.enabled => { config if config.modelsim.linter.enabled => {
info!("modelsim linter enter"); info!("modelsim linter enter");
diagnostics.append( if let Some(diag) = &mut config.modelsim.provide_diagnostics(&uri, rope, server) {
&mut config.modelsim.provide_diagnostics(&uri, rope) diagnostics.append(diag);
); }
} }
config if config.vivado.linter.enabled => { config if config.vivado.linter.enabled => {
info!("vivado linter enter"); info!("vivado linter enter");
diagnostics.append( if let Some(diag) = &mut config.vivado.provide_diagnostics(&uri, rope, server) {
&mut config.vivado.provide_diagnostics(&uri, rope) diagnostics.append(diag);
); }
} }
_ => {} _ => {}
} }
@ -123,6 +129,7 @@ pub fn update_diagnostics_configuration(
let linter_configuration = linter_configuration.unwrap(); let linter_configuration = linter_configuration.unwrap();
linter_configuration.iverilog.linter.enabled = false;
linter_configuration.verilator.linter.enabled = false; linter_configuration.verilator.linter.enabled = false;
linter_configuration.verible.linter.enabled = false; linter_configuration.verible.linter.enabled = false;
linter_configuration.modelsim.linter.enabled = false; linter_configuration.modelsim.linter.enabled = false;

View File

@ -1,7 +1,7 @@
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::utils::command::is_command_valid; use crate::{server::LspServer, utils::command::is_command_valid};
use super::{AbstractLinterConfiguration, LinterStatus}; use super::{AbstractLinterConfiguration, LinterStatus};
use ropey::Rope; use ropey::Rope;
use tower_lsp::lsp_types::{Diagnostic, Url}; use tower_lsp::lsp_types::{Diagnostic, Url};
@ -48,12 +48,12 @@ impl AbstractLinterConfiguration for ModelsimConfiguration {
fn provide_diagnostics( fn provide_diagnostics(
&self, &self,
uri: &Url, uri: &Url,
#[allow(unused)] rope: &Rope,
rope: &Rope server: &LspServer
) -> Vec<Diagnostic> { ) -> Option<Vec<Diagnostic>> {
let mut diagnostics = Vec::<Diagnostic>::new(); let mut diagnostics = Vec::<Diagnostic>::new();
diagnostics Some(diagnostics)
} }
fn get_windows_extension(&self) -> &str { fn get_windows_extension(&self) -> &str {

View File

@ -4,7 +4,7 @@ use ropey::Rope;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use crate::utils::command::is_command_valid; use crate::{server::LspServer, utils::command::is_command_valid};
use super::{AbstractLinterConfiguration, LinterStatus}; use super::{AbstractLinterConfiguration, LinterStatus};
@ -130,11 +130,12 @@ impl AbstractLinterConfiguration for VeribleConfiguration {
&self, &self,
uri: &Url, uri: &Url,
#[allow(unused)] #[allow(unused)]
rope: &Rope rope: &Rope,
) -> Vec<Diagnostic> { server: &LspServer
) -> Option<Vec<Diagnostic>> {
let mut diagnostics = Vec::<Diagnostic>::new(); let mut diagnostics = Vec::<Diagnostic>::new();
diagnostics Some(diagnostics)
} }
fn get_linter_path(&self) -> &str { fn get_linter_path(&self) -> &str {

View File

@ -5,6 +5,7 @@ use std::path::PathBuf;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use crate::server::LspServer;
use crate::utils::command::is_command_valid; use crate::utils::command::is_command_valid;
use super::{AbstractLinterConfiguration, LinterStatus}; use super::{AbstractLinterConfiguration, LinterStatus};
@ -134,11 +135,12 @@ impl AbstractLinterConfiguration for VerilatorConfiguration {
&self, &self,
uri: &Url, uri: &Url,
#[allow(unused)] #[allow(unused)]
rope: &Rope rope: &Rope,
) -> Vec<Diagnostic> { server: &LspServer
) -> Option<Vec<Diagnostic>> {
let mut diagnostics = Vec::<Diagnostic>::new(); let mut diagnostics = Vec::<Diagnostic>::new();
diagnostics Some(diagnostics)
} }
fn get_linter_path(&self) -> &str { fn get_linter_path(&self) -> &str {

View File

@ -1,7 +1,7 @@
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::utils::command::is_command_valid; use crate::{server::LspServer, utils::command::is_command_valid};
use ropey::Rope; use ropey::Rope;
use tower_lsp::lsp_types::{Diagnostic, Url}; use tower_lsp::lsp_types::{Diagnostic, Url};
@ -47,11 +47,12 @@ impl AbstractLinterConfiguration for VivadoConfiguration {
&self, &self,
uri: &Url, uri: &Url,
#[allow(unused)] #[allow(unused)]
rope: &Rope rope: &Rope,
) -> Vec<Diagnostic> { server: &LspServer
) -> Option<Vec<Diagnostic>> {
let mut diagnostics = Vec::<Diagnostic>::new(); let mut diagnostics = Vec::<Diagnostic>::new();
diagnostics Some(diagnostics)
} }
fn get_linter_path(&self) -> &str { fn get_linter_path(&self) -> &str {

View File

@ -61,7 +61,8 @@ impl LspServer {
uri, uri,
&file.text, &file.text,
urls, urls,
&self.configuration.read().unwrap() &self.configuration.read().unwrap(),
&self
); );
diagnostics diagnostics
} else { } else {
@ -113,6 +114,7 @@ impl LspServer {
&file.text, &file.text,
urls, urls,
&self.configuration.read().unwrap(), &self.configuration.read().unwrap(),
&self
) )
} else { } else {
PublishDiagnosticsParams { PublishDiagnosticsParams {
@ -491,6 +493,20 @@ impl Sources {
} }
None None
} }
/// 同时从 fast 和 primitives 里面找 module
/// 在内部判断一个 module 是否存在时会用到
/// - name: 模块的名字
pub fn contains_module(&self, name: &str) -> bool {
if let Some(_) = self.hdl_param.find_module_by_name(name) {
return true;
}
// 从 primitives 里面寻找
let primitive_text = self.primitive_text.clone();
let primitive_map = primitive_text.name_to_text.read().unwrap();
primitive_map.contains_key(name)
}
} }