完成 iverilog 的诊断
This commit is contained in:
parent
19fed383b0
commit
b1b302bbfb
@ -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;
|
||||||
@ -69,3 +71,22 @@ 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,
|
||||||
|
}
|
||||||
|
}
|
@ -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()?;
|
||||||
|
let output_string = String::from_utf8(output.stderr).ok()?;
|
||||||
|
|
||||||
diagnostics
|
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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user