From 7824b74c9a0c1324a294169ca21e850e705217e0 Mon Sep 17 00:00:00 2001 From: LSTM-Kirigaya <1193466151@qq.com> Date: Thu, 5 Dec 2024 23:53:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20linter=20=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E8=AF=B7=E6=B1=82=E6=8E=A5=E5=8F=A3=E5=92=8C=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/code_lens/mod.rs | 4 +- src/code_lens/sv.rs | 4 +- src/code_lens/vhdl.rs | 4 +- src/completion/feature.rs | 6 +- src/completion/mod.rs | 4 +- src/completion/sv.rs | 8 +- src/completion/vhdl.rs | 6 +- src/definition/feature.rs | 16 +- src/definition/mod.rs | 4 +- src/definition/sv.rs | 4 +- src/definition/vhdl.rs | 4 +- src/diagnostics/mod.rs | 314 ++++++++++++++------------------- src/diagnostics/modelsim.rs | 50 +++++- src/diagnostics/verible.rs | 73 +++++++- src/diagnostics/verilator.rs | 100 ++++++++++- src/diagnostics/vivado.rs | 9 +- src/document_highlight/mod.rs | 4 +- src/document_highlight/sv.rs | 4 +- src/document_highlight/vhdl.rs | 4 +- src/document_symbol/mod.rs | 4 +- src/document_symbol/sv.rs | 4 +- src/document_symbol/vhdl.rs | 4 +- src/format/mod.rs | 92 +++++----- src/hover/feature.rs | 16 +- src/hover/mod.rs | 4 +- src/hover/sv.rs | 14 +- src/hover/vhdl.rs | 6 +- src/inlay_hint/mod.rs | 4 +- src/inlay_hint/sv.rs | 8 +- src/inlay_hint/vhdl.rs | 4 +- src/request/README.md | 9 + src/request/config.rs | 66 ++++++- src/server.rs | 51 +++--- src/sources.rs | 36 +--- src/utils/fast.rs | 4 +- 35 files changed, 576 insertions(+), 372 deletions(-) create mode 100644 src/request/README.md diff --git a/src/code_lens/mod.rs b/src/code_lens/mod.rs index 189f4e5..7e58e8e 100644 --- a/src/code_lens/mod.rs +++ b/src/code_lens/mod.rs @@ -1,4 +1,4 @@ -use crate::server::LSPServer; +use crate::server::LspServer; use crate::utils::*; #[allow(unused)] use log::info; @@ -7,7 +7,7 @@ use tower_lsp::lsp_types::*; mod sv; mod vhdl; -impl LSPServer { +impl LspServer { pub fn code_lens(&self, params: CodeLensParams) -> Option> { let language_id = get_language_id_by_uri(¶ms.text_document.uri); match language_id.as_str() { diff --git a/src/code_lens/sv.rs b/src/code_lens/sv.rs index 2bca5d8..39f9365 100644 --- a/src/code_lens/sv.rs +++ b/src/code_lens/sv.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, str::FromStr}; -use crate::{core, server::LSPServer}; +use crate::{core, server::LspServer}; #[allow(unused)] use log::info; use tower_lsp::lsp_types::*; @@ -9,7 +9,7 @@ use super::to_escape_path; pub fn code_lens( #[allow(unused)] - server: &LSPServer, + server: &LspServer, #[allow(unused)] params: &CodeLensParams ) -> Option> { diff --git a/src/code_lens/vhdl.rs b/src/code_lens/vhdl.rs index bf97612..756507d 100644 --- a/src/code_lens/vhdl.rs +++ b/src/code_lens/vhdl.rs @@ -1,12 +1,12 @@ -use crate::server::LSPServer; +use crate::server::LspServer; #[allow(unused)] use log::info; use tower_lsp::lsp_types::*; pub fn code_lens( #[allow(unused)] - server: &LSPServer, + server: &LspServer, #[allow(unused)] params: &CodeLensParams ) -> Option> { diff --git a/src/completion/feature.rs b/src/completion/feature.rs index 0d19782..1e29f03 100644 --- a/src/completion/feature.rs +++ b/src/completion/feature.rs @@ -4,7 +4,7 @@ use log::info; use ropey::RopeSlice; use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionList, Position, Url}; -use crate::{server::LSPServer, utils::{resolve_path, to_escape_path}}; +use crate::{server::LspServer, utils::{resolve_path, to_escape_path}}; pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Option { let line_text = line.as_str().unwrap_or(""); @@ -88,7 +88,7 @@ pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Op } pub fn get_dot_completion( - server: &LSPServer, + server: &LspServer, line: &RopeSlice, url: &Url, pos: &Position, @@ -142,7 +142,7 @@ fn is_port_completion(line: &RopeSlice, pos: &Position) -> bool { } fn get_position_port_param_completion( - server: &LSPServer, + server: &LspServer, #[allow(unused)] line: &RopeSlice, url: &Url, diff --git a/src/completion/mod.rs b/src/completion/mod.rs index 7fc105d..4e31c71 100644 --- a/src/completion/mod.rs +++ b/src/completion/mod.rs @@ -1,4 +1,4 @@ -use crate::{server::LSPServer, utils::get_language_id_by_uri}; +use crate::{server::LspServer, utils::get_language_id_by_uri}; use tower_lsp::lsp_types::*; pub mod keyword; @@ -10,7 +10,7 @@ pub use sys_tasks::provide_vlog_sys_tasks_completions; mod vhdl; mod sv; -impl LSPServer { +impl LspServer { pub fn completion(&self, params: CompletionParams) -> Option { let language_id = get_language_id_by_uri(¶ms.text_document_position.text_document.uri); match language_id.as_str() { diff --git a/src/completion/sv.rs b/src/completion/sv.rs index 67dd633..9049246 100644 --- a/src/completion/sv.rs +++ b/src/completion/sv.rs @@ -1,12 +1,10 @@ -use std::collections::HashSet; - -use crate::{completion::feature::{get_dot_completion, include_path_completion}, core, hover::feature::make_module_profile_code, server::LSPServer, sources::LSPSupport, utils::get_language_id_by_uri}; +use crate::{completion::feature::{get_dot_completion, include_path_completion}, core, hover::feature::make_module_profile_code, server::LspServer, sources::LSPSupport, utils::get_language_id_by_uri}; use log::info; use ropey::{Rope, RopeSlice}; use tower_lsp::lsp_types::*; -pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option { +pub fn completion(server: &LspServer, params: &CompletionParams) -> Option { let doc = ¶ms.text_document_position; let uri = ¶ms.text_document_position.text_document.uri; let pos = doc.position; @@ -305,7 +303,7 @@ fn make_instantiation_code(module: &crate::core::hdlparam::Module) -> String { /// 自动补全例化模块 fn make_module_completions( - server: &LSPServer, + server: &LspServer, token: &str, language_id: &str ) -> Vec { diff --git a/src/completion/vhdl.rs b/src/completion/vhdl.rs index e74a366..6989915 100644 --- a/src/completion/vhdl.rs +++ b/src/completion/vhdl.rs @@ -6,13 +6,13 @@ use ropey::{Rope, RopeSlice}; use tower_lsp::lsp_types::*; use crate::{hover::feature::make_vhdl_module_profile_code, utils::{from_lsp_pos, to_escape_path}}; #[allow(unused)] -use crate::{server::LSPServer, sources::LSPSupport, utils::get_language_id_by_uri}; +use crate::{server::LspServer, sources::LSPSupport, utils::get_language_id_by_uri}; /// Called when the client requests a completion. /// This function looks in the source code to find suitable options and then returns them -pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option { +pub fn completion(server: &LspServer, params: &CompletionParams) -> Option { let doc = ¶ms.text_document_position; let uri = ¶ms.text_document_position.text_document.uri; let language_id = get_language_id_by_uri(uri); @@ -214,7 +214,7 @@ fn get_completion_token(text: &Rope, line: RopeSlice, pos: Position) -> String { } fn make_module_completions( - server: &LSPServer, + server: &LspServer, token: &str, language_id: &str ) -> Vec { diff --git a/src/definition/feature.rs b/src/definition/feature.rs index 3a18a49..776416b 100644 --- a/src/definition/feature.rs +++ b/src/definition/feature.rs @@ -5,7 +5,7 @@ use regex::Regex; use ropey::RopeSlice; use tower_lsp::lsp_types::{GotoDefinitionResponse, LocationLink, Position, Range, Url}; -use crate::{core::{self, hdlparam::{self, FastHdlparam}}, server::LSPServer, utils::{get_word_range_at_position, resolve_path, to_escape_path}}; +use crate::{core::{self, hdlparam::{self, FastHdlparam}}, server::LspServer, utils::{get_word_range_at_position, resolve_path, to_escape_path}}; /// 跳转到 include 的文件 pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Option { @@ -73,7 +73,7 @@ pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Op /// 跳转到宏定义 -pub fn goto_macro_definition(server: &LSPServer, line: &RopeSlice, pos: Position) -> Option { +pub fn goto_macro_definition(server: &LspServer, line: &RopeSlice, pos: Position) -> Option { let macro_text_regex = Regex::new(r"[`0-9a-zA-Z]").unwrap(); if let Some((macro_text, range)) = get_word_range_at_position(line, pos, macro_text_regex) { if macro_text.starts_with("`") { @@ -103,7 +103,7 @@ pub fn goto_macro_definition(server: &LSPServer, line: &RopeSlice, pos: Position fn goto_instantiation<'a>( - server: &LSPServer, + server: &LspServer, fast: &'a FastHdlparam, token_name: &str, pos: &Position, @@ -207,7 +207,7 @@ fn goto_instantiation<'a>( } pub fn goto_position_port_param_definition( - server: &LSPServer, + server: &LspServer, line: &RopeSlice, url: &Url, pos: Position @@ -235,7 +235,7 @@ pub fn goto_position_port_param_definition( } pub fn goto_module_declaration_definition( - server: &LSPServer, + server: &LspServer, token_name: &str ) -> Option { let hdl_param = server.srcs.hdl_param.clone(); @@ -276,7 +276,7 @@ pub fn goto_module_declaration_definition( fn goto_common_module_declaration_definition( #[allow(unused)] - server: &LSPServer, + server: &LspServer, #[allow(unused)] token_name: &str, #[allow(unused)] @@ -301,7 +301,7 @@ fn goto_common_module_declaration_definition( fn goto_ip_module_declaration_definition( #[allow(unused)] - server: &LSPServer, + server: &LspServer, #[allow(unused)] token_name: &str, #[allow(unused)] @@ -331,7 +331,7 @@ fn goto_ip_module_declaration_definition( fn goto_primitives_module_declaration_definition( #[allow(unused)] - server: &LSPServer, + server: &LspServer, #[allow(unused)] token_name: &str, #[allow(unused)] diff --git a/src/definition/mod.rs b/src/definition/mod.rs index fff058f..cae2457 100644 --- a/src/definition/mod.rs +++ b/src/definition/mod.rs @@ -1,6 +1,6 @@ use crate::core::hdlparam::{self, FastHdlparam}; use crate::utils::get_language_id_by_uri; -use crate::server::LSPServer; +use crate::server::LspServer; #[allow(unused)] use log::info; @@ -19,7 +19,7 @@ pub use extract_defs::*; mod sv; mod vhdl; -impl LSPServer { +impl LspServer { pub fn goto_definition(&self, params: GotoDefinitionParams) -> Option { let language_id = get_language_id_by_uri(¶ms.text_document_position_params.text_document.uri); match language_id.as_str() { diff --git a/src/definition/sv.rs b/src/definition/sv.rs index 929e629..96c7ed8 100644 --- a/src/definition/sv.rs +++ b/src/definition/sv.rs @@ -1,5 +1,5 @@ use crate::utils::get_definition_token; -use crate::server::LSPServer; +use crate::server::LspServer; use crate::sources::LSPSupport; #[allow(unused)] @@ -8,7 +8,7 @@ use tower_lsp::lsp_types::*; use super::{feature::*, Definition, Scope}; -pub fn goto_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Option { +pub fn goto_definition(server: &LspServer, params: &GotoDefinitionParams) -> Option { let doc = ¶ms.text_document_position_params.text_document.uri; let pos = params.text_document_position_params.position; let file_id = server.srcs.get_id(doc).to_owned(); diff --git a/src/definition/vhdl.rs b/src/definition/vhdl.rs index 0bb1d83..f43ab1d 100644 --- a/src/definition/vhdl.rs +++ b/src/definition/vhdl.rs @@ -3,11 +3,11 @@ use std::{path::PathBuf, str::FromStr}; #[allow(unused)] use log::info; use tower_lsp::lsp_types::*; -use crate::{server::LSPServer, utils::{from_lsp_pos, get_definition_token, srcpos_to_location, to_escape_path}}; +use crate::{server::LspServer, utils::{from_lsp_pos, get_definition_token, srcpos_to_location, to_escape_path}}; use super::{Definition, Scope}; -pub fn goto_vhdl_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Option { +pub fn goto_vhdl_definition(server: &LspServer, params: &GotoDefinitionParams) -> Option { let doc = ¶ms.text_document_position_params.text_document.uri; let pos = params.text_document_position_params.position; let file_id = server.srcs.get_id(doc).to_owned(); diff --git a/src/diagnostics/mod.rs b/src/diagnostics/mod.rs index 3477bc7..811adb0 100644 --- a/src/diagnostics/mod.rs +++ b/src/diagnostics/mod.rs @@ -1,10 +1,10 @@ -use crate::server::ProjectConfig; -use regex::Regex; +use std::fs::OpenOptions; + +use crate::{server::{LspConfiguration, LspServer}, utils::get_language_id_by_uri}; +use log::info; use ropey::Rope; -use std::path::PathBuf; -use std::process::{Command, Stdio}; +use serde::Deserialize; use tower_lsp::lsp_types::*; -use walkdir::DirEntry; pub mod verible; pub mod verilator; @@ -16,202 +16,148 @@ pub use verilator::*; pub use vivado::*; pub use modelsim::*; +/// description +/// 诊断功能需要提供两套函数,一套函数用于从给定路径读取文件并给出诊断结果;一套用于从 lsp 的文件缓冲区直接读取文本然后给出诊断结果。 +/// 前者用于扫描整个项目使用,后者在用户实时修改代码时,给出实时的诊断信息。 /// 获取诊断核心函数 pub fn provide_diagnostics( uri: Url, rope: &Rope, - #[allow(unused_variables)] files: Vec, - configuration: &ProjectConfig, + #[allow(unused_variables)] + files: Vec, + configuration: &LspConfiguration, ) -> PublishDiagnosticsParams { + let mut diagnostics = Vec::::new(); + let language_id = get_language_id_by_uri(&uri); + + // 选择对应语言的 lsp + let linter_configuration = match language_id.as_str() { + "vhdl" => Some(&configuration.vhdl_linter_configuration), + "verilog" => Some(&configuration.vlog_linter_configuration), + "systemverilog" => Some(&configuration.svlog_linter_configuration), + _ => None + }; + + if linter_configuration.is_none() { + info!("未知语言 {} 试图发起诊断", language_id); + return PublishDiagnosticsParams { + uri, diagnostics, + version: None + }; + } + + let linter_configuration = linter_configuration.unwrap(); // 根据配置决定使用哪一个诊断器 + // 外层代码需要保证只有一个 linter.enable 为 true + if linter_configuration.verilator.linter.enabled { + info!("verilator linter enter"); + + } else if linter_configuration.verible.linter.enabled { + info!("verilator linter enter"); - + } else if linter_configuration.modelsim.linter.enabled { + info!("modelsim linter enter"); + + } else if linter_configuration.vivado.linter.enabled { + info!("vivado linter enter"); + + } + + PublishDiagnosticsParams { + uri, diagnostics, + version: None + } +} + +/// 根据输入的名字选择诊断器 +/// - `linter_name` 为 `"vivado" | "modelsim" | "verilator" | "verible" | "iverilog"` +/// - `language_id` 为 `"vhdl" | "verilog" | "systemverilog"` +/// - `linter_path` 为第三方的可执行文件的路径 +pub fn update_diagnostics_configuration( + server: &LspServer, + linter_name: &str, + language_id: &str, + linter_path: &str +) { + let mut configuration = server.configuration.write().unwrap(); + + // 选择对应语言的 lsp + let linter_configuration = match language_id { + "vhdl" => Some(&mut configuration.vhdl_linter_configuration), + "verilog" => Some(&mut configuration.vlog_linter_configuration), + "systemverilog" => Some(&mut configuration.svlog_linter_configuration), + _ => None + }; + + if linter_configuration.is_none() { + info!("未知语言 {} 试图配置诊断器", language_id); + return; + } + + let linter_configuration = linter_configuration.unwrap(); + + linter_configuration.verilator.linter.enabled = false; + linter_configuration.verible.linter.enabled = false; + linter_configuration.modelsim.linter.enabled = false; + linter_configuration.vivado.linter.enabled = false; + + if linter_configuration.verilator.linter.name == linter_name { + linter_configuration.verilator.linter.enabled = true; + linter_configuration.verilator.linter.path = linter_path.to_string(); + info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.verilator.linter.path); + + } else if linter_configuration.verible.linter.name == linter_name { + linter_configuration.verible.linter.enabled = true; + linter_configuration.verible.linter.path = linter_path.to_string(); + info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.verible.linter.path); + + } else if linter_configuration.modelsim.linter.name == linter_name { + linter_configuration.modelsim.linter.enabled = true; + linter_configuration.modelsim.linter.path = linter_path.to_string(); + info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.modelsim.linter.path); + + } else if linter_configuration.vivado.linter.name == linter_name { + linter_configuration.vivado.linter.enabled = true; + linter_configuration.vivado.linter.path = linter_path.to_string(); + info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.vivado.linter.path); - if !(cfg!(test) && (uri.to_string().starts_with("file:///test"))) { - let diagnostics = { - if configuration.verilator.syntax.enabled { - if let Ok(path) = uri.to_file_path() { - match verilator_syntax( - rope, - path, - &configuration.verilator.syntax.path, - &configuration.verilator.syntax.args, - ) { - Some(diags) => diags, - None => Vec::new(), - } - } else { - Vec::new() - } - } else if configuration.verible.syntax.enabled { - match verible_syntax(rope, &configuration.verible.syntax.path, &configuration.verible.syntax.args) { - Some(diags) => diags, - None => Vec::new(), - } - } else { - Vec::new() - } - }; - PublishDiagnosticsParams { - uri, - diagnostics, - version: None, - } - } else { - PublishDiagnosticsParams { - uri, - diagnostics: Vec::new(), - version: None, - } } } -pub fn is_hidden(entry: &DirEntry) -> bool { - entry - .file_name() - .to_str() - .map(|s| s.starts_with('.')) - .unwrap_or(false) +#[derive(Debug, Deserialize)] +#[derive(serde::Serialize)] +pub enum DigitalLinterMode { + /// 全局诊断,将所有设计源直接进行诊断,并报错,无论文件是否打开 + FULL, + /// 单文件关闭时,对应报错去除,打开哪个文件就对哪个文件进行诊断 + SINGLE, + /// 全局关闭,即整个工程都不进行工程报错 + NONE } - -/// convert captured severity string to DiagnosticSeverity -fn verilator_severity(severity: &str) -> Option { - match severity { - "Error" => Some(DiagnosticSeverity::ERROR), - s if s.starts_with("Warning") => Some(DiagnosticSeverity::WARNING), - // NOTE: afaik, verilator doesn't have an info or hint severity - _ => Some(DiagnosticSeverity::INFORMATION), - } +#[derive(Debug, Deserialize)] +#[derive(serde::Serialize)] +pub struct DigitalLinterConfiguration { + // verible 相关的工具配置 + pub verible: VeribleConfiguration, + // verilator 相关的工具配置 + pub verilator: VerilatorConfiguration, + // modelsim 相关的工具配置 + pub modelsim: ModelsimConfiguration, + // vivado 相关的工具配置 + pub vivado: VivadoConfiguration } -/// syntax checking using verilator --lint-only -fn verilator_syntax( - rope: &Rope, - file_path: PathBuf, - verilator_syntax_path: &str, - verilator_syntax_args: &[String], -) -> Option> { - let mut child = Command::new(verilator_syntax_path) - .stdin(Stdio::piped()) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .args(verilator_syntax_args) - .arg(file_path.to_str()?) - .spawn() - .ok()?; - - static RE: std::sync::OnceLock = std::sync::OnceLock::new(); - let re = RE.get_or_init(|| { - Regex::new( - r"%(?PError|Warning)(-(?P[A-Z0-9_]+))?: (?P[^:]+):(?P\d+):((?P\d+):)? ?(?P.*)", - ) - .unwrap() - }); - // write file to stdin, read output from stdout - rope.write_to(child.stdin.as_mut()?).ok()?; - let output = child.wait_with_output().ok()?; - if !output.status.success() { - let mut diags: Vec = Vec::new(); - let raw_output = String::from_utf8(output.stderr).ok()?; - let filtered_output = raw_output - .lines() - .filter(|line| line.starts_with('%')) - .collect::>(); - for error in filtered_output { - let caps = match re.captures(error) { - Some(caps) => caps, - None => continue, - }; - - // check if diagnostic is for this file, since verilator can provide diagnostics for - // included files - if caps.name("filepath")?.as_str() != file_path.to_str().unwrap_or("") { - continue; - } - let severity = verilator_severity(caps.name("severity")?.as_str()); - let line: u32 = caps.name("line")?.as_str().to_string().parse().ok()?; - let col: u32 = caps.name("col").map_or("1", |m| m.as_str()).parse().ok()?; - let pos = Position::new(line - 1, col - 1); - let msg = match severity { - Some(DiagnosticSeverity::ERROR) => caps.name("message")?.as_str().to_string(), - Some(DiagnosticSeverity::WARNING) => format!( - "{}: {}", - caps.name("warning_type")?.as_str(), - caps.name("message")?.as_str() - ), - _ => "".to_string(), - }; - diags.push(Diagnostic::new( - Range::new(pos, pos), - severity, - None, - Some("verilator".to_string()), - msg, - None, - None, - )); +impl Default for DigitalLinterConfiguration { + fn default() -> Self { + DigitalLinterConfiguration { + verible: VeribleConfiguration::default(), + verilator: VerilatorConfiguration::default(), + modelsim: ModelsimConfiguration::default(), + vivado: VivadoConfiguration::default() } - Some(diags) - } else { - None } -} - -/// syntax checking using verible-verilog-syntax -fn verible_syntax( - rope: &Rope, - verible_syntax_path: &str, - verible_syntax_args: &[String], -) -> Option> { - let mut child = Command::new(verible_syntax_path) - .stdin(Stdio::piped()) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .args(verible_syntax_args) - .arg("-") - .spawn() - .ok()?; - - static RE: std::sync::OnceLock = std::sync::OnceLock::new(); - let re = RE.get_or_init(|| { - Regex::new( - r"^.+:(?P\d*):(?P\d*)(?:-(?P\d*))?:\s(?P.*)\s.*$", - ) - .unwrap() - }); - // write file to stdin, read output from stdout - rope.write_to(child.stdin.as_mut()?).ok()?; - let output = child.wait_with_output().ok()?; - if !output.status.success() { - let mut diags: Vec = Vec::new(); - let raw_output = String::from_utf8(output.stdout).ok()?; - for error in raw_output.lines() { - let caps = re.captures(error)?; - 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); - diags.push(Diagnostic::new( - Range::new(start_pos, end_pos), - Some(DiagnosticSeverity::ERROR), - None, - Some("verible".to_string()), - caps.name("message")?.as_str().to_string(), - None, - None, - )); - } - Some(diags) - } else { - None - } -} +} \ No newline at end of file diff --git a/src/diagnostics/modelsim.rs b/src/diagnostics/modelsim.rs index 2d6b365..ec8e4af 100644 --- a/src/diagnostics/modelsim.rs +++ b/src/diagnostics/modelsim.rs @@ -1,5 +1,53 @@ use serde::{Deserialize, Serialize}; +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct ModelsimConfiguration { + pub linter: ModelsimLinter, + pub format: ModelsimFormat, +} + + +#[derive(Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct ModelsimLinter { + pub name: String, + /// 目前是否启动 + pub enabled: bool, + pub path: String, + pub args: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(default)] +pub struct ModelsimFormat { + pub enabled: bool, + pub path: String, + pub args: Vec, +} + +impl Default for ModelsimLinter { + fn default() -> Self { + Self { + name: "modelsim".to_string(), + enabled: true, + path: "Modelsim-verilog-syntax".to_string(), + args: Vec::new(), + } + } +} + + +impl Default for ModelsimFormat { + fn default() -> Self { + Self { + enabled: true, + path: "Modelsim-verilog-format".to_string(), + args: Vec::new(), + } + } +} + pub fn provide_diagnostics() { - + } \ No newline at end of file diff --git a/src/diagnostics/verible.rs b/src/diagnostics/verible.rs index 98ebe7e..f9477c9 100644 --- a/src/diagnostics/verible.rs +++ b/src/diagnostics/verible.rs @@ -1,15 +1,22 @@ use serde::{Deserialize, Serialize}; +use regex::Regex; +use ropey::Rope; +use std::process::{Command, Stdio}; +use tower_lsp::lsp_types::*; + #[derive(Default, Debug, Serialize, Deserialize)] #[serde(default)] pub struct VeribleConfiguration { - pub syntax: VeribleSyntax, + pub linter: VeribleLinter, pub format: VeribleFormat, } #[derive(Debug, Serialize, Deserialize)] #[serde(default)] -pub struct VeribleSyntax { +pub struct VeribleLinter { + pub name: String, + /// 目前是否启动 pub enabled: bool, pub path: String, pub args: Vec, @@ -23,9 +30,10 @@ pub struct VeribleFormat { pub args: Vec, } -impl Default for VeribleSyntax { +impl Default for VeribleLinter { fn default() -> Self { Self { + name: "verible".to_string(), enabled: true, path: "verible-verilog-syntax".to_string(), args: Vec::new(), @@ -46,4 +54,61 @@ impl Default for VeribleFormat { pub fn provide_diagnostics() { -} \ No newline at end of file +} + + +/// syntax checking using verible-verilog-syntax +fn verible_syntax( + rope: &Rope, + verible_syntax_path: &str, + verible_syntax_args: &[String], +) -> Option> { + let mut child = Command::new(verible_syntax_path) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .args(verible_syntax_args) + .arg("-") + .spawn() + .ok()?; + + static RE: std::sync::OnceLock = std::sync::OnceLock::new(); + let re = RE.get_or_init(|| { + Regex::new( + r"^.+:(?P\d*):(?P\d*)(?:-(?P\d*))?:\s(?P.*)\s.*$", + ) + .unwrap() + }); + // write file to stdin, read output from stdout + rope.write_to(child.stdin.as_mut()?).ok()?; + + let output = child.wait_with_output().ok()?; + if !output.status.success() { + let mut diags: Vec = Vec::new(); + let raw_output = String::from_utf8(output.stdout).ok()?; + for error in raw_output.lines() { + let caps = re.captures(error)?; + 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); + diags.push(Diagnostic::new( + Range::new(start_pos, end_pos), + Some(DiagnosticSeverity::ERROR), + None, + Some("verible".to_string()), + caps.name("message")?.as_str().to_string(), + None, + None, + )); + } + Some(diags) + } else { + None + } +} diff --git a/src/diagnostics/verilator.rs b/src/diagnostics/verilator.rs index 7dc5787..5322250 100644 --- a/src/diagnostics/verilator.rs +++ b/src/diagnostics/verilator.rs @@ -1,22 +1,30 @@ use serde::{Deserialize, Serialize}; +use regex::Regex; +use ropey::Rope; +use std::path::PathBuf; +use std::process::{Command, Stdio}; +use tower_lsp::lsp_types::*; #[derive(Debug, Default, Serialize, Deserialize)] #[serde(default)] pub struct VerilatorConfiguration { - pub syntax: VerilatorSyntax, + pub linter: VerilatorLinter, } #[derive(Debug, Serialize, Deserialize)] #[serde(default)] -pub struct VerilatorSyntax { +pub struct VerilatorLinter { + pub name: String, + /// 目前是否启动 pub enabled: bool, pub path: String, pub args: Vec, } -impl Default for VerilatorSyntax { +impl Default for VerilatorLinter { fn default() -> Self { Self { + name: "verilator".to_string(), enabled: true, path: "verilator".to_string(), args: vec![ @@ -30,4 +38,88 @@ impl Default for VerilatorSyntax { pub fn provide_diagnostics() { -} \ No newline at end of file +} + + +/// convert captured severity string to DiagnosticSeverity +fn verilator_severity(severity: &str) -> Option { + match severity { + "Error" => Some(DiagnosticSeverity::ERROR), + s if s.starts_with("Warning") => Some(DiagnosticSeverity::WARNING), + // NOTE: afaik, verilator doesn't have an info or hint severity + _ => Some(DiagnosticSeverity::INFORMATION), + } +} + +/// syntax checking using verilator --lint-only +fn verilator_syntax( + rope: &Rope, + file_path: PathBuf, + verilator_syntax_path: &str, + verilator_syntax_args: &[String], +) -> Option> { + let mut child = Command::new(verilator_syntax_path) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .args(verilator_syntax_args) + .arg(file_path.to_str()?) + .spawn() + .ok()?; + + static RE: std::sync::OnceLock = std::sync::OnceLock::new(); + let re = RE.get_or_init(|| { + Regex::new( + r"%(?PError|Warning)(-(?P[A-Z0-9_]+))?: (?P[^:]+):(?P\d+):((?P\d+):)? ?(?P.*)", + ) + .unwrap() + }); + // write file to stdin, read output from stdout + rope.write_to(child.stdin.as_mut()?).ok()?; + let output = child.wait_with_output().ok()?; + if !output.status.success() { + let mut diags: Vec = Vec::new(); + let raw_output = String::from_utf8(output.stderr).ok()?; + let filtered_output = raw_output + .lines() + .filter(|line| line.starts_with('%')) + .collect::>(); + for error in filtered_output { + let caps = match re.captures(error) { + Some(caps) => caps, + None => continue, + }; + + // check if diagnostic is for this file, since verilator can provide diagnostics for + // included files + if caps.name("filepath")?.as_str() != file_path.to_str().unwrap_or("") { + continue; + } + let severity = verilator_severity(caps.name("severity")?.as_str()); + let line: u32 = caps.name("line")?.as_str().to_string().parse().ok()?; + let col: u32 = caps.name("col").map_or("1", |m| m.as_str()).parse().ok()?; + let pos = Position::new(line - 1, col - 1); + let msg = match severity { + Some(DiagnosticSeverity::ERROR) => caps.name("message")?.as_str().to_string(), + Some(DiagnosticSeverity::WARNING) => format!( + "{}: {}", + caps.name("warning_type")?.as_str(), + caps.name("message")?.as_str() + ), + _ => "".to_string(), + }; + diags.push(Diagnostic::new( + Range::new(pos, pos), + severity, + None, + Some("verilator".to_string()), + msg, + None, + None, + )); + } + Some(diags) + } else { + None + } +} diff --git a/src/diagnostics/vivado.rs b/src/diagnostics/vivado.rs index 8229784..ee7f528 100644 --- a/src/diagnostics/vivado.rs +++ b/src/diagnostics/vivado.rs @@ -3,20 +3,23 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Serialize, Deserialize)] #[serde(default)] pub struct VivadoConfiguration { - pub syntax: VivadoSyntax, + pub linter: VivadoLinter, } #[derive(Debug, Serialize, Deserialize)] #[serde(default)] -pub struct VivadoSyntax { +pub struct VivadoLinter { + pub name: String, + /// 目前是否启动 pub enabled: bool, pub path: String, pub args: Vec, } -impl Default for VivadoSyntax { +impl Default for VivadoLinter { fn default() -> Self { Self { + name: "vivado".to_string(), enabled: true, path: "Vivado".to_string(), args: vec![ diff --git a/src/document_highlight/mod.rs b/src/document_highlight/mod.rs index 3823bd8..ff46154 100644 --- a/src/document_highlight/mod.rs +++ b/src/document_highlight/mod.rs @@ -1,10 +1,10 @@ -use crate::{server::LSPServer, utils::{get_definition_token, get_language_id_by_uri}}; +use crate::{server::LspServer, utils::{get_definition_token, get_language_id_by_uri}}; use tower_lsp::lsp_types::*; mod sv; mod vhdl; -impl LSPServer { +impl LspServer { pub fn document_highlight( &self, params: DocumentHighlightParams, diff --git a/src/document_highlight/sv.rs b/src/document_highlight/sv.rs index 999b938..7bf9796 100644 --- a/src/document_highlight/sv.rs +++ b/src/document_highlight/sv.rs @@ -3,10 +3,10 @@ use log::info; use sv_parser::{RefNode, SyntaxTree}; use tower_lsp::lsp_types::*; -use crate::{definition::{get_ident, Scope}, server::LSPServer, sources::{LSPSupport, ParseIR, Source}}; +use crate::{definition::{get_ident, Scope}, server::LspServer, sources::{LSPSupport, ParseIR, Source}}; pub fn document_highlight( - server: &LSPServer, + server: &LspServer, token: &str, file: &Source, pos: Position, diff --git a/src/document_highlight/vhdl.rs b/src/document_highlight/vhdl.rs index d863bbf..9a644a5 100644 --- a/src/document_highlight/vhdl.rs +++ b/src/document_highlight/vhdl.rs @@ -2,10 +2,10 @@ use log::info; use sv_parser::{RefNode, SyntaxTree}; use tower_lsp::lsp_types::*; -use crate::{definition::{get_ident, Scope}, server::LSPServer, sources::{LSPSupport, ParseIR, Source}}; +use crate::{definition::{get_ident, Scope}, server::LspServer, sources::{LSPSupport, ParseIR, Source}}; pub fn document_highlight( - server: &LSPServer, + server: &LspServer, token: &str, file: &Source, pos: Position, diff --git a/src/document_symbol/mod.rs b/src/document_symbol/mod.rs index 3f46f83..3d3ac79 100644 --- a/src/document_symbol/mod.rs +++ b/src/document_symbol/mod.rs @@ -1,11 +1,11 @@ use tower_lsp::lsp_types::*; -use crate::{server::LSPServer, utils::get_language_id_by_uri}; +use crate::{server::LspServer, utils::get_language_id_by_uri}; mod sv; mod vhdl; -impl LSPServer { +impl LspServer { pub fn document_symbol(&self, params: DocumentSymbolParams) -> Option { let uri = ¶ms.text_document.uri; let language_id = get_language_id_by_uri(uri); diff --git a/src/document_symbol/sv.rs b/src/document_symbol/sv.rs index 9bbae29..f12547b 100644 --- a/src/document_symbol/sv.rs +++ b/src/document_symbol/sv.rs @@ -1,8 +1,8 @@ -use crate::{definition::Scope, server::LSPServer}; +use crate::{definition::Scope, server::LspServer}; use tower_lsp::lsp_types::*; pub fn document_symbol( - server: &LSPServer, + server: &LspServer, params: &DocumentSymbolParams ) -> Option { let uri = ¶ms.text_document.uri; diff --git a/src/document_symbol/vhdl.rs b/src/document_symbol/vhdl.rs index f02b226..0b98922 100644 --- a/src/document_symbol/vhdl.rs +++ b/src/document_symbol/vhdl.rs @@ -4,9 +4,9 @@ use std::{path::PathBuf, str::FromStr}; use log::info; use tower_lsp::lsp_types::*; use vhdl_lang::{EntHierarchy, Token}; -use crate::{definition::Scope, server::LSPServer, utils::{to_escape_path, to_lsp_range, to_symbol_kind}}; +use crate::{definition::Scope, server::LspServer, utils::{to_escape_path, to_lsp_range, to_symbol_kind}}; -pub fn document_symbol(server: &LSPServer, params: &DocumentSymbolParams) -> Option { +pub fn document_symbol(server: &LspServer, params: &DocumentSymbolParams) -> Option { // info!("enter document symbol"); let uri = ¶ms.text_document.uri; diff --git a/src/format/mod.rs b/src/format/mod.rs index 9a70298..a13b880 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -1,60 +1,62 @@ -use crate::server::LSPServer; +use crate::server::LspServer; use crate::sources::LSPSupport; use log::info; use ropey::Rope; use std::process::{Command, Stdio}; use tower_lsp::lsp_types::*; -impl LSPServer { +impl LspServer { pub fn formatting(&self, params: DocumentFormattingParams) -> Option> { - let uri = params.text_document.uri; - info!("formatting {}", &uri); - let file_id = self.srcs.get_id(&uri).to_owned(); - self.srcs.wait_parse_ready(file_id, false); - let file = self.srcs.get_file(file_id)?; - let file = file.read().ok()?; + None + // let uri = params.text_document.uri; + // info!("formatting {}", &uri); + // let file_id = self.srcs.get_id(&uri).to_owned(); + // self.srcs.wait_parse_ready(file_id, false); + // let file = self.srcs.get_file(file_id)?; + // let file = file.read().ok()?; - let conf = self.configuration.read().unwrap(); - if conf.verible.format.enabled { - Some(vec![TextEdit::new( - Range::new( - file.text.char_to_pos(0), - file.text.char_to_pos(file.text.len_chars()), - ), - format_document( - &file.text, - None, - &conf.verible.format.path, - &conf.verible.format.args, - )?, - )]) - } else { - None - } + // let conf = self.configuration.read().unwrap(); + // if conf.verible.format.enabled { + // Some(vec![TextEdit::new( + // Range::new( + // file.text.char_to_pos(0), + // file.text.char_to_pos(file.text.len_chars()), + // ), + // format_document( + // &file.text, + // None, + // &conf.verible.format.path, + // &conf.verible.format.args, + // )?, + // )]) + // } else { + // None + // } } pub fn range_formatting(&self, params: DocumentRangeFormattingParams) -> Option> { - let uri = params.text_document.uri; - info!("range formatting {}", &uri); - let file_id = self.srcs.get_id(&uri).to_owned(); - self.srcs.wait_parse_ready(file_id, false); - let file = self.srcs.get_file(file_id)?; - let file = file.read().ok()?; + None + // let uri = params.text_document.uri; + // info!("range formatting {}", &uri); + // let file_id = self.srcs.get_id(&uri).to_owned(); + // self.srcs.wait_parse_ready(file_id, false); + // let file = self.srcs.get_file(file_id)?; + // let file = file.read().ok()?; - let conf = self.configuration.read().unwrap(); - if conf.verible.format.enabled { - Some(vec![TextEdit::new( - file.text.char_range_to_range(0..file.text.len_chars()), - format_document( - &file.text, - Some(params.range), - &conf.verible.format.path, - &conf.verible.format.args, - )?, - )]) - } else { - None - } + // let conf = self.configuration.read().unwrap(); + // if conf.verible.format.enabled { + // Some(vec![TextEdit::new( + // file.text.char_range_to_range(0..file.text.len_chars()), + // format_document( + // &file.text, + // Some(params.range), + // &conf.verible.format.path, + // &conf.verible.format.args, + // )?, + // )]) + // } else { + // None + // } } } diff --git a/src/hover/feature.rs b/src/hover/feature.rs index b5bc1ae..e946066 100644 --- a/src/hover/feature.rs +++ b/src/hover/feature.rs @@ -5,7 +5,7 @@ use regex::Regex; use ropey::RopeSlice; use tower_lsp::lsp_types::{Hover, HoverContents, LanguageString, MarkedString, Position, Range, Url}; -use crate::{core::{self, hdlparam::{Define, FastHdlparam}, primitive_parser::PrimitiveText}, server::LSPServer}; +use crate::{core::{self, hdlparam::{Define, FastHdlparam}, primitive_parser::PrimitiveText}, server::LspServer}; use super::{get_language_id_by_path_str, get_word_range_at_position, resolve_path, to_escape_path}; @@ -198,7 +198,7 @@ fn make_macro_define_content(macro_define: &Define) -> String { } } -pub fn hover_macro(server: &LSPServer, line: &RopeSlice, pos: Position, language_id: &str) -> Option { +pub fn hover_macro(server: &LspServer, line: &RopeSlice, pos: Position, language_id: &str) -> Option { let macro_text_regex = Regex::new(r"[`_0-9a-zA-Z]").unwrap(); if let Some((macro_text, range)) = get_word_range_at_position(line, pos, macro_text_regex) { if macro_text.starts_with("`") { @@ -222,7 +222,7 @@ pub fn hover_macro(server: &LSPServer, line: &RopeSlice, pos: Position, language fn goto_instantiation<'a>( - server: &LSPServer, + server: &LspServer, fast: &'a FastHdlparam, token_name: &str, pos: &Position, @@ -313,7 +313,7 @@ fn goto_instantiation<'a>( /// 计算 position 赋值的 port 或者 param /// 比如 .clk ( clk ) 中的 .clk pub fn hover_position_port_param( - server: &LSPServer, + server: &LspServer, line: &RopeSlice, url: &Url, pos: Position, @@ -441,7 +441,7 @@ fn make_param_desc_hover(file_type: &str, param: &crate::core::hdlparam::Paramet } pub fn hover_module_declaration( - server: &LSPServer, + server: &LspServer, token_name: &str, #[allow(unused)] language_id: &str @@ -486,7 +486,7 @@ pub fn hover_module_declaration( fn hover_common_module_declaration( #[allow(unused)] - server: &LSPServer, + server: &LspServer, #[allow(unused)] token_name: &str, #[allow(unused)] @@ -545,7 +545,7 @@ fn hover_common_module_declaration( fn hover_ip_module_declaration( #[allow(unused)] - server: &LSPServer, + server: &LspServer, #[allow(unused)] token_name: &str, #[allow(unused)] @@ -613,7 +613,7 @@ fn hover_ip_module_declaration( fn hover_primitives_module_declaration( #[allow(unused)] - server: &LSPServer, + server: &LspServer, #[allow(unused)] token_name: &str, #[allow(unused)] diff --git a/src/hover/mod.rs b/src/hover/mod.rs index 47f4681..88179fb 100644 --- a/src/hover/mod.rs +++ b/src/hover/mod.rs @@ -1,4 +1,4 @@ -use crate::server::LSPServer; +use crate::server::LspServer; use crate::utils::*; #[allow(unused)] use log::info; @@ -9,7 +9,7 @@ pub mod feature; mod sv; mod vhdl; -impl LSPServer { +impl LspServer { pub fn hover(&self, params: HoverParams) -> Option { let language_id = get_language_id_by_uri(¶ms.text_document_position_params.text_document.uri); match language_id.as_str() { diff --git a/src/hover/sv.rs b/src/hover/sv.rs index d43937a..4fb22e8 100644 --- a/src/hover/sv.rs +++ b/src/hover/sv.rs @@ -3,7 +3,7 @@ use log::info; use regex::Regex; use ropey::Rope; use tower_lsp::lsp_types::*; -use crate::{core::hdlparam::{Instance, Module}, hover::{to_escape_path, BracketMatchResult, BracketMatcher}, server::LSPServer, sources::LSPSupport}; +use crate::{core::hdlparam::{Instance, Module}, hover::{to_escape_path, BracketMatchResult, BracketMatcher}, server::LspServer, sources::LSPSupport}; use super::feature::*; use std::{path::PathBuf, str::FromStr, sync::RwLockReadGuard}; @@ -11,7 +11,7 @@ use crate::definition::*; use super::{get_definition_token, get_language_id_by_uri}; -pub fn hover(server: &LSPServer, params: &HoverParams) -> Option { +pub fn hover(server: &LspServer, params: &HoverParams) -> Option { let doc = ¶ms.text_document_position_params.text_document.uri; let pos: Position = params.text_document_position_params.position; let file_id: usize = server.srcs.get_id(doc).to_owned(); @@ -224,7 +224,7 @@ fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_c /// 计算正常 symbol 的 hover fn hover_common_symbol( #[allow(unused)] - server: &LSPServer, + server: &LspServer, #[allow(unused)] token: &String, symbol_definition: &GenericDec, @@ -294,7 +294,7 @@ fn hover_common_symbol( make_hover_with_comment(&file.text, def_line, &language_id, false) } -fn hover_for_module(server: &LSPServer, pos: Position, doc: &Url) -> bool { +fn hover_for_module(server: &LspServer, pos: Position, doc: &Url) -> bool { let pathbuf = PathBuf::from_str(doc.path()).unwrap(); let pathbuf = to_escape_path(&pathbuf); let path_string = pathbuf.to_str().unwrap().replace("\\", "/"); @@ -309,13 +309,13 @@ fn hover_for_module(server: &LSPServer, pos: Position, doc: &Url) -> bool { }; if let Some(_) = hdlparam.walk_module(&path_string, find_module_range) { - // info!("[LSPServer] in hover: it is module"); + // info!("[LspServer] in hover: it is module"); true } else if let Some(_) = hdlparam.walk_instantiation(&path_string, find_instance_range) { - // info!("[LSPServer] in hover: it is instance"); + // info!("[LspServer] in hover: it is instance"); true } else { - // info!("[LSPServer] in hover: it is not instance"); + // info!("[LspServer] in hover: it is not instance"); false } } diff --git a/src/hover/vhdl.rs b/src/hover/vhdl.rs index c9749a0..13f29f8 100644 --- a/src/hover/vhdl.rs +++ b/src/hover/vhdl.rs @@ -4,11 +4,11 @@ use log::info; use regex::Regex; use ropey::Rope; use tower_lsp::lsp_types::*; -use crate::{core::hdlparam::{Instance, Module}, definition::{Definition, DefinitionType, GenericDec, Scope}, hover::{BracketMatchResult, BracketMatcher}, server::LSPServer, sources::LSPSupport}; +use crate::{core::hdlparam::{Instance, Module}, definition::{Definition, DefinitionType, GenericDec, Scope}, hover::{BracketMatchResult, BracketMatcher}, server::LspServer, sources::LSPSupport}; use super::{from_lsp_pos, get_definition_token, get_language_id_by_uri, to_escape_path}; -pub fn hover(server: &LSPServer, params: &HoverParams) -> Option { +pub fn hover(server: &LspServer, params: &HoverParams) -> Option { let doc = ¶ms.text_document_position_params.text_document.uri; let pos: Position = params.text_document_position_params.position; let file_id: usize = server.srcs.get_id(doc).to_owned(); @@ -239,7 +239,7 @@ fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_c fn hover_common_symbol( #[allow(unused)] - server: &LSPServer, + server: &LspServer, #[allow(unused)] token: &String, symbol_definition: &GenericDec, diff --git a/src/inlay_hint/mod.rs b/src/inlay_hint/mod.rs index 9ddbfb7..f16880e 100644 --- a/src/inlay_hint/mod.rs +++ b/src/inlay_hint/mod.rs @@ -1,4 +1,4 @@ -use crate::server::LSPServer; +use crate::server::LspServer; use crate::utils::*; #[allow(unused)] use log::info; @@ -7,7 +7,7 @@ use tower_lsp::lsp_types::*; mod sv; mod vhdl; -impl LSPServer { +impl LspServer { pub fn inlay_hint(&self, params: InlayHintParams) -> Option> { let language_id = get_language_id_by_uri(¶ms.text_document.uri); match language_id.as_str() { diff --git a/src/inlay_hint/sv.rs b/src/inlay_hint/sv.rs index b4e1969..bdaec9b 100644 --- a/src/inlay_hint/sv.rs +++ b/src/inlay_hint/sv.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr}; -use crate::{core, server::LSPServer}; +use crate::{core, server::LspServer}; #[allow(unused)] use log::info; use ropey::Rope; @@ -8,7 +8,7 @@ use tower_lsp::lsp_types::*; use super::{get_language_id_by_path_str, to_escape_path}; -pub fn inlay_hint(server: &LSPServer, params: &InlayHintParams) -> Option> { +pub fn inlay_hint(server: &LspServer, params: &InlayHintParams) -> Option> { let uri = ¶ms.text_document.uri; let path = PathBuf::from_str(uri.path()).unwrap(); let path = to_escape_path(&path); @@ -58,7 +58,7 @@ fn is_visible_range( } fn make_instparam_hints( - server: &LSPServer, + server: &LspServer, params: &InlayHintParams, instance: &core::hdlparam::Instance, rope: &Rope @@ -106,7 +106,7 @@ fn find_instport_inlay_hints_position( } fn make_instport_hints( - server: &LSPServer, + server: &LspServer, params: &InlayHintParams, instance: &core::hdlparam::Instance, rope: &Rope diff --git a/src/inlay_hint/vhdl.rs b/src/inlay_hint/vhdl.rs index 625ca03..11a6181 100644 --- a/src/inlay_hint/vhdl.rs +++ b/src/inlay_hint/vhdl.rs @@ -1,8 +1,8 @@ -use crate::server::LSPServer; +use crate::server::LspServer; #[allow(unused)] use log::info; use tower_lsp::lsp_types::*; -pub fn inlay_hint(server: &LSPServer, params: &InlayHintParams) -> Option> { +pub fn inlay_hint(server: &LspServer, params: &InlayHintParams) -> Option> { None } \ No newline at end of file diff --git a/src/request/README.md b/src/request/README.md new file mode 100644 index 0000000..03e6620 --- /dev/null +++ b/src/request/README.md @@ -0,0 +1,9 @@ +request 负责实现前后端非 LSP 协议的通信,比如前端把配置文件同步到后端,前端获取 AST 的一部分数据用于前端的界面功能。 + + +request 是单向的,只能由前端主动发起。 + +```mermaid +graph TB +前端 --编码发送--> request.rs --解码处理--> 后端 +``` \ No newline at end of file diff --git a/src/request/config.rs b/src/request/config.rs index 75bcc94..f6eb28f 100644 --- a/src/request/config.rs +++ b/src/request/config.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use tower_lsp::jsonrpc::Result; -use crate::server::Backend; +use crate::{diagnostics::update_diagnostics_configuration, server::Backend}; #[derive(Clone)] pub struct UpdateConfigurationApi; @@ -33,21 +33,75 @@ impl <'a>tower_lsp::jsonrpc::Method<&'a Arc, (UpdateConfigurationParams let configs = request_param.configs; // 用于未来进行配置分区 - #[allow(unused)] let config_type = request_param.config_type; - update_configuration(configs, &_server); + update_configuration(configs, config_type, &_server); future::ready(Ok(())) } } +/// 前端配置文件的更新 fn update_configuration( configs: Vec, + config_type: String, backend: &Arc ) { let mut lsp_configuration = backend.server.srcs.lsp_configuration.write().unwrap(); - for config in configs { - info!("name: {}, value: {}", config.name, config.value); - lsp_configuration.insert(config.name, config.value); + + match config_type.as_str() { + // 所有配置同步到 lsp_configuration 中 + "lsp" => { + for config in configs { + info!("update config, name: {}, value: {}", config.name, config.value); + lsp_configuration.insert(config.name, config.value); + } + }, + + // 针对当前项目选择的诊断器的更新 + // 此时 configs 的长度必然为 2 + // configs.0 含有当前选择的诊断器的名字的信息,比如 "digital-ide.function.lsp.linter.vlog.diagnostor": "vivado" + // configs.1 含有第三方诊断器的合法路径相关的信息,比如 "path": "/opt/xilinx/Vivado/2022.2/bin/xvlog" + "linter" => { + if configs.len() < 2 { + info!("update_configuration, type : {}, 发生错误,原因:configs 数量不为 2", config_type); + return; + } + + let linter_name_configuration = &configs[0]; + let linter_path_configuration = &configs[1]; + + // something like digital-ide.function.lsp.linter.vlog.diagnostor + let linter_name_config_name = &linter_name_configuration.name; + + lsp_configuration.insert( + linter_name_config_name.to_string(), + linter_name_configuration.value.clone() + ); + + // 从 linter_name_configuration.name 解析出 language_id + let language_id = { + // linter_name_config_name 形如 digital-ide.function.lsp.linter.vlog.diagnostor + let mut cookies = linter_name_config_name.split("."); + let name = cookies.nth(4).unwrap(); + match name { + "vlog" => "verilog", + "vhdl" => "vhdl", + "svlog" => "systemverilog", + _ => return + } + }; + + let linter_name = linter_name_configuration.value.as_str().unwrap(); + let linter_path = linter_path_configuration.value.as_str().unwrap(); + + update_diagnostics_configuration( + &backend.server, + linter_name, + language_id, + linter_path + ); + }, + + _ => {} } } \ No newline at end of file diff --git a/src/server.rs b/src/server.rs index adfef95..423c929 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,5 +1,5 @@ use crate::core::cache_storage::CacheManager; -use crate::diagnostics::{VeribleConfiguration, VerilatorConfiguration}; +use crate::diagnostics::{DigitalLinterConfiguration, DigitalLinterMode, ModelsimConfiguration, VeribleConfiguration, VerilatorConfiguration, VivadoConfiguration}; use crate::sources::*; use crate::completion::{keyword::*, provide_vlog_sys_tasks_completions}; use flexi_logger::LoggerHandle; @@ -11,7 +11,7 @@ use std::sync::{Arc, Mutex, RwLock}; use tower_lsp::jsonrpc::Result; use tower_lsp::lsp_types::*; use tower_lsp::{Client, LanguageServer}; -pub struct LSPServer { +pub struct LspServer { /// 文件和 ast 相关的 pub srcs: Sources, /// 缓存 @@ -25,23 +25,24 @@ pub struct LSPServer { /// vhdl 关键词的自动补全 pub vhdl_keyword_completiom_items: Vec, /// 相关的配置项目 - pub configuration: Arc>, + pub configuration: Arc>, + #[allow(unused)] pub log_handle: Mutex>, } -impl LSPServer { - pub fn new(log_handle: Option) -> LSPServer { +impl LspServer { + pub fn new(log_handle: Option) -> LspServer { let user_home = dirs_next::home_dir().unwrap(); let dide_home = user_home.join(".digital-ide"); - LSPServer { + LspServer { srcs: Sources::new(), cache: CacheManager::new(dide_home), vlog_keyword_completion_items: keyword_completions(VLOG_KEYWORDS), vhdl_keyword_completiom_items: keyword_completions(VHDL_KEYWORDS), vlog_sys_tasks_completion_items: provide_vlog_sys_tasks_completions(), vlog_directives: other_completions(DIRECTIVES), - configuration: Arc::new(RwLock::new(ProjectConfig::default())), + configuration: Arc::new(RwLock::new(LspConfiguration::default())), log_handle: Mutex::new(log_handle), } } @@ -49,7 +50,7 @@ impl LSPServer { pub struct Backend { pub client: Client, - pub server: LSPServer + pub server: LspServer } @@ -57,7 +58,7 @@ impl Backend { pub fn new(client: Client, log_handle: LoggerHandle) -> Backend { Backend { client, - server: LSPServer::new(Some(log_handle)), + server: LspServer::new(Some(log_handle)), } } } @@ -78,7 +79,7 @@ pub enum LogLevel { #[derive(Debug, Serialize, Deserialize)] #[serde(default)] -pub struct ProjectConfig { +pub struct LspConfiguration { // 用户工作目录的路径 pub workspace_folder: Option, // 插件安装的根路径 @@ -93,29 +94,33 @@ pub struct ProjectConfig { // list of directories to recursively search for SystemVerilog/Verilog sources pub source_dirs: Vec, - // verible 相关的工具配置 - pub verible: VeribleConfiguration, - // verilator 相关的工具配置 - pub verilator: VerilatorConfiguration, - - // pub modelsim: String, - // pub vivado: String, + // 下方是和 linter 相关的配置 + // 诊断模式 + pub linter_mode: DigitalLinterMode, + // vlog 的诊断配置 + pub vlog_linter_configuration: DigitalLinterConfiguration, + // vhdl 的诊断配置 + pub vhdl_linter_configuration: DigitalLinterConfiguration, + // svlog 的诊断配置 + pub svlog_linter_configuration: DigitalLinterConfiguration, // log level pub log_level: LogLevel } -impl Default for ProjectConfig { +impl Default for LspConfiguration { fn default() -> Self { - ProjectConfig { + LspConfiguration { workspace_folder: None, extension_path: "".to_string(), tool_chain: "xilinx".to_string(), auto_search_workdir: true, include_dirs: Vec::new(), source_dirs: Vec::new(), - verible: VeribleConfiguration::default(), - verilator: VerilatorConfiguration::default(), + linter_mode: DigitalLinterMode::FULL, + vlog_linter_configuration: DigitalLinterConfiguration::default(), + vhdl_linter_configuration: DigitalLinterConfiguration::default(), + svlog_linter_configuration: DigitalLinterConfiguration::default(), log_level: LogLevel::Info, } } @@ -148,11 +153,15 @@ impl LanguageServer for Backend { info!("extensionPath: {:?}", configure.extension_path); info!("toolChain: {:?}", configure.tool_chain); + // 初始化原语系统 self.server.srcs.init_primitive( &configure.tool_chain, &configure.extension_path ); + // 初始化诊断器 + + let text_document_sync = TextDocumentSyncCapability::Options( TextDocumentSyncOptions { open_close: Some(true), diff --git a/src/sources.rs b/src/sources.rs index 4ae564e..1f95a15 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -6,9 +6,9 @@ use crate::core::vhdl_parser::vhdl_parse_str; use crate::definition::def_types::*; use crate::definition::get_scopes_from_syntax_tree; use crate::definition::get_scopes_from_vhdl_fast; -use crate::diagnostics::{provide_diagnostics, is_hidden}; -use crate::server::LSPServer; -use crate::server::ProjectConfig; +use crate::diagnostics::provide_diagnostics; +use crate::server::LspServer; +use crate::server::LspConfiguration; use crate::utils::to_escape_path; #[allow(unused)] use log::info; @@ -33,7 +33,7 @@ use thread::JoinHandle; use tower_lsp::lsp_types::*; use walkdir::WalkDir; -impl LSPServer { +impl LspServer { pub fn did_open(&self, params: DidOpenTextDocumentParams) -> PublishDiagnosticsParams { let document: TextDocumentItem = params.text_document; let uri = document.uri.clone(); @@ -216,28 +216,6 @@ pub struct SourceMeta { pub parse_handle: JoinHandle<()>, } -/// find SystemVerilog/Verilog sources recursively from opened files -fn find_src_paths(dirs: &[PathBuf]) -> Vec { - let mut paths: Vec = Vec::new(); - - for dir in dirs { - let walker = WalkDir::new(dir).into_iter(); - for entry in walker.filter_entry(|e| !is_hidden(e)) { - let entry = entry.unwrap(); - if entry.file_type().is_file() && entry.path().extension().is_some() { - let extension = entry.path().extension().unwrap(); - - if extension == "sv" || extension == "svh" || extension == "v" || extension == "vh" { - let entry_path = entry.path().to_path_buf(); - if !paths.contains(&entry_path) { - paths.push(entry_path); - } - } - } - } - } - paths -} /// The Sources struct manages all source files pub struct Sources { @@ -302,7 +280,7 @@ impl Sources { } /// 增加一个 hdl 文件,并为该文件添加单独的解析线程 - pub fn add(&self, server: &LSPServer, doc: TextDocumentItem) { + pub fn add(&self, server: &LspServer, doc: TextDocumentItem) { // 对于当前的文件增加一个解析线程,不断进行解析和同步 #[allow(clippy::mutex_atomic)] // https://github.com/rust-lang/rust-clippy/issues/1516 let valid_parse = Arc::new((Mutex::new(false), Condvar::new())); @@ -662,7 +640,7 @@ pub fn recovery_sv_parse( pub fn sv_parser_pipeline( #[allow(unused)] - conf: &Arc>, + conf: &Arc>, source_handle: &Arc>, scope_handle: &Arc>>, hdl_param_handle: &Arc, @@ -744,7 +722,7 @@ pub fn sv_parser_pipeline( } pub fn vhdl_parser_pipeline( - conf: &Arc>, + conf: &Arc>, source_handle: &Arc>, scope_handle: &Arc>>, project_handle: &Arc>>, diff --git a/src/utils/fast.rs b/src/utils/fast.rs index 3fc8326..80e4926 100644 --- a/src/utils/fast.rs +++ b/src/utils/fast.rs @@ -1,9 +1,9 @@ #[allow(unused)] use log::info; -use crate::{core::hdlparam::Define, server::LSPServer}; +use crate::{core::hdlparam::Define, server::LspServer}; -impl LSPServer { +impl LspServer { /// 根据输入的 macro 名字,寻找 fast 中存在的第一个 macro /// macro 可以以 ` 开头 pub fn find_macros(&self, macro_name: &str) -> Option<(Define, String)> {