From 3a649060296e2d3ac6ce9a10815370d12eae0082 Mon Sep 17 00:00:00 2001 From: LSTM-Kirigaya <1193466151@qq.com> Date: Thu, 26 Sep 2024 20:09:42 +0800 Subject: [PATCH] update --- Cargo.lock | 13 - Cargo.toml | 1 - src/{completion.rs => completion/mod.rs} | 4 +- src/{core.rs => core/mod.rs} | 0 src/core/sv_parser.rs | 20 +- src/custom_request.rs | 15 +- src/definition/extract_defs.rs | 1 - src/definition/feature.rs | 16 +- src/{definition.rs => definition/mod.rs} | 268 +-------------------- src/{diagnostics.rs => diagnostics/mod.rs} | 0 src/{format.rs => format/mod.rs} | 0 src/hover/feature.rs | 20 ++ src/hover/mod.rs | 158 ++++++++++++ src/lib.rs | 19 ++ src/main.rs | 2 + src/sources.rs | 78 +++--- src/test/mod.rs | 58 ++++- src/utils/mod.rs | 93 +++++++ 18 files changed, 413 insertions(+), 353 deletions(-) rename src/{completion.rs => completion/mod.rs} (99%) rename src/{core.rs => core/mod.rs} (100%) rename src/{definition.rs => definition/mod.rs} (53%) rename src/{diagnostics.rs => diagnostics/mod.rs} (100%) rename src/{format.rs => format/mod.rs} (100%) create mode 100644 src/hover/feature.rs create mode 100644 src/hover/mod.rs create mode 100644 src/utils/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 253dd7f..a581889 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,7 +205,6 @@ version = "0.0.1" dependencies = [ "anyhow", "flexi_logger", - "futures", "log", "once_cell", "path-clean", @@ -286,7 +285,6 @@ checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", - "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -309,17 +307,6 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" -[[package]] -name = "futures-executor" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - [[package]] name = "futures-io" version = "0.3.28" diff --git a/Cargo.toml b/Cargo.toml index 619fbc7..fceb979 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" [dependencies] sv-parser = { version = "0.13.3", path = "sv-parser/sv-parser"} once_cell = "1.8" -futures = "0.3" percent-encoding = "2.1.0" log = "0.4.19" tower-lsp = "0.20.0" diff --git a/src/completion.rs b/src/completion/mod.rs similarity index 99% rename from src/completion.rs rename to src/completion/mod.rs index 7426abe..b46f980 100644 --- a/src/completion.rs +++ b/src/completion/mod.rs @@ -174,7 +174,7 @@ mod tests { use super::*; use crate::definition::def_types::Scope; use crate::definition::get_scopes; - use crate::sources::{parse, LSPSupport}; + use crate::sources::{recovery_sv_parse, LSPSupport}; use crate::support::test_init; use ropey::Rope; @@ -651,7 +651,7 @@ endinterface let doc = Rope::from_str(&text); let url = Url::parse("file:///test.sv").unwrap(); - let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap(); + let syntax_tree = recovery_sv_parse(&doc, &url, &None, &Vec::new()).unwrap(); let scope_tree = get_scopes(&syntax_tree, &url).unwrap(); let pos = Position::new(8, 9); let token = get_completion_token(&doc, doc.line(pos.line as usize), pos); diff --git a/src/core.rs b/src/core/mod.rs similarity index 100% rename from src/core.rs rename to src/core/mod.rs diff --git a/src/core/sv_parser.rs b/src/core/sv_parser.rs index a8f70a7..7753da3 100644 --- a/src/core/sv_parser.rs +++ b/src/core/sv_parser.rs @@ -1,25 +1,35 @@ -use std::fs::File; +use std::fs::{self, File}; use std::io::BufRead; use std::{collections::HashMap, io::BufReader}; use std::path::PathBuf; use anyhow::Error; use percent_encoding::percent_decode_str; +use ropey::Rope; +use tower_lsp::lsp_types::Url; use std::env::consts::OS; use sv_parser::{parse_sv, unwrap_node, Locate, RefNode, SyntaxTree}; +use crate::sources::recovery_sv_parse; + use super::fast_hdlparam::{FastHdlparam, Macro}; #[allow(unused)] pub fn sv_parser(path: &str) -> Option { // The path of SystemVerilog source file let path = PathBuf::from(path); - // The list of defined macros - let defines = HashMap::new(); // The list of include paths let includes: Vec = Vec::new(); - let result = parse_sv(&path, &defines, &includes, false, true); - if let Ok((syntax_tree, _)) = result { + let text = match fs::read_to_string(&path) { + Ok(text) => text, + Err(_) => return None + }; + + let doc = Rope::from_str(&text); + let uri = Url::from_file_path(&path).unwrap(); + let result = recovery_sv_parse(&doc, &uri, &None, &includes); + + if let Some(syntax_tree) = result { if let Ok(hdlparam) = make_fast_from_syntaxtree(&syntax_tree, &path) { return Some(hdlparam); } diff --git a/src/custom_request.rs b/src/custom_request.rs index c7071f9..8b9f60a 100644 --- a/src/custom_request.rs +++ b/src/custom_request.rs @@ -12,9 +12,9 @@ use tower_lsp::lsp_types::*; use crate::core::fast_hdlparam::FastHdlparam; use crate::core::sv_parser::make_fast_from_syntaxtree; -use crate::definition::get_language_id_by_uri; +use crate::utils::*; use crate::server::{Backend, GLOBAL_BACKEND}; -use crate::sources::parse; +use crate::sources::recovery_sv_parse; #[derive(Clone)] @@ -85,7 +85,8 @@ fn make_textdocumenitem_from_path(path_buf: &PathBuf) -> Option Result { - info!("parse hdl path: {:?}", path); + info!("parse fast {}", path); + let path_buf = PathBuf::from(&path); let doc = match make_textdocumenitem_from_path(&path_buf) { @@ -105,8 +106,11 @@ pub fn do_fast(path: String) -> Result { let text = Rope::from(doc.text); // fast 解析不需要 include let includes: Vec = Vec::new(); + + + info!("before parse {}", path); - let parse_result = parse( + let parse_result = recovery_sv_parse( &text, &uri, &None, @@ -115,6 +119,7 @@ pub fn do_fast(path: String) -> Result { if let Some(syntax_tree) = parse_result { if let Ok(hdlparam) = make_fast_from_syntaxtree(&syntax_tree, &path_buf) { + info!("after parse {}, get hdlparam {:?}", path, hdlparam); return Ok(hdlparam); } } @@ -154,7 +159,7 @@ impl Notification for UpdateFastNotification { type Params = Self; } - +#[allow(unused)] pub async fn update_fast_to_client(fast: FastHdlparam, path: &PathBuf) { // info!("send fast to foreend {:?}", fast); diff --git a/src/definition/extract_defs.rs b/src/definition/extract_defs.rs index 478edd7..bc68377 100644 --- a/src/definition/extract_defs.rs +++ b/src/definition/extract_defs.rs @@ -1,6 +1,5 @@ use crate::definition::def_types::*; use crate::definition::match_definitions; -use log::info; use sv_parser::*; use tower_lsp::lsp_types::*; diff --git a/src/definition/feature.rs b/src/definition/feature.rs index cd4ad61..ecb46ad 100644 --- a/src/definition/feature.rs +++ b/src/definition/feature.rs @@ -1,8 +1,8 @@ -use std::path::{Path, PathBuf}; - use ropey::RopeSlice; use tower_lsp::lsp_types::{GotoDefinitionResponse, LocationLink, Position, Range, Url}; +use crate::utils::resolve_path; + /// 跳转到定义 pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Option { let line_text = line.as_str().unwrap_or(""); @@ -22,17 +22,8 @@ pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Op if path_string.starts_with("./") || path_string.starts_with(".\\") { path_string = &path_string[2 ..]; } - // 此处得到路径,根据是否为绝对路径进行判断 - let path = Path::new(path_string); - let mut abs_path: PathBuf; - if path.is_absolute() { - abs_path = PathBuf::from(path); - } else { - let base = Path::new(uri.path()).parent()?; - abs_path = base.join(path); - } - if abs_path.exists() { + if let Some(abs_path) = resolve_path(uri.path(), path_string) { let target_uri = match Url::from_file_path(abs_path.as_path()) { Ok(uri) => uri, Err(_) => return None @@ -55,6 +46,7 @@ pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Op let links = GotoDefinitionResponse::Link(link); return Some(links); } + } } diff --git a/src/definition.rs b/src/definition/mod.rs similarity index 53% rename from src/definition.rs rename to src/definition/mod.rs index cb8cb2c..5c432c2 100644 --- a/src/definition.rs +++ b/src/definition/mod.rs @@ -1,9 +1,8 @@ -use crate::definition::extract_defs::get_ident; +use crate::{definition::extract_defs::get_ident, utils::get_definition_token}; use crate::server::LSPServer; use crate::sources::LSPSupport; use log::info; -use ropey::{Rope, RopeSlice}; use sv_parser::*; use tower_lsp::lsp_types::*; @@ -29,7 +28,6 @@ impl LSPServer { let line_text = file.text.line(pos.line as usize); let token: String = get_definition_token(&line_text, pos); - info!("definition token: {:?}", token); let include_definition = goto_include_definition(&doc, &line_text, pos); if include_definition.is_some() { return include_definition; @@ -49,35 +47,6 @@ impl LSPServer { ))) } - pub fn hover(&self, params: HoverParams) -> Option { - let doc: Url = params.text_document_position_params.text_document.uri; - let pos: Position = params.text_document_position_params.position; - let file_id: usize = self.srcs.get_id(&doc).to_owned(); - self.srcs.wait_parse_ready(file_id, false); - let file: std::sync::Arc> = self.srcs.get_file(file_id)?; - let file: std::sync::RwLockReadGuard<'_, crate::sources::Source> = file.read().ok()?; - - let line_text = file.text.line(pos.line as usize); - let token: String = get_definition_token(&line_text, pos); - - let scope_tree: std::sync::RwLockReadGuard<'_, Option> = self.srcs.scope_tree.read().ok()?; - - let def: GenericDec = scope_tree - .as_ref()? - .get_definition(&token, file.text.pos_to_byte(&pos), &doc)?; - - let def_line = file.text.byte_to_line(def.byte_idx()); - let language_id = get_language_id_by_uri(&doc); - - Some(Hover { - contents: HoverContents::Scalar(MarkedString::LanguageString(LanguageString { - language: language_id, - value: get_hover(&file.text, def_line), - })), - range: None, - }) - } - pub fn document_symbol(&self, params: DocumentSymbolParams) -> Option { let uri = params.text_document.uri; let file_id = self.srcs.get_id(&uri).to_owned(); @@ -137,47 +106,6 @@ fn all_identifiers(syntax_tree: &SyntaxTree, token: &str) -> Vec<(String, usize) idents } -/// retrieve the token the user invoked goto definition or hover on -fn get_definition_token(line: &RopeSlice, pos: Position) -> String { - let mut token = String::new(); - let mut line_iter = line.chars(); - for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) { - line_iter.next(); - } - let mut c = line_iter.prev(); - while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') { - token.push(c.unwrap()); - c = line_iter.prev(); - } - token = token.chars().rev().collect(); - line_iter = line.chars(); - for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) { - line_iter.next(); - } - let mut c = line_iter.next(); - while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') { - token.push(c.unwrap()); - c = line_iter.next(); - } - token -} - - -pub fn get_language_id_by_uri(uri: &Url) -> String { - let path = uri.path(); - let ext_name = std::path::Path::new(path) - .extension() - .and_then(std::ffi::OsStr::to_str) - .unwrap_or(""); - - match ext_name { - "vhd" | "vhdl" | "vho" | "vht" => "vhdl".to_string(), - "v" | "V" | "vh" | "vl" => "verilog".to_string(), - "sv" | "svh" => "systemverilog".to_string(), - _ => "plaintext".to_string() - } -} - type ScopesAndDefs = Option<(Vec>, Vec>)>; @@ -340,197 +268,3 @@ pub fn get_scopes(syntax_tree: &SyntaxTree, url: &Url) -> Option { global_scope.scopes.append(&mut scopes); Some(global_scope) } - -/// get the hover information -fn get_hover(doc: &Rope, line: usize) -> String { - if line == 0 { - return doc.line(line).to_string(); - } - let mut hover: Vec = Vec::new(); - let mut multiline: bool = false; - let mut valid: bool = true; - let mut current: String = doc.line(line).to_string(); - let ltrim: String = " ".repeat(current.len() - current.trim_start().len()); - let mut line_idx = line; - - // iterate upwards from the definition, and grab the comments - while valid { - hover.push(current.clone()); - line_idx -= 1; - valid = false; - if line_idx > 0 { - current = doc.line(line_idx).to_string(); - let currentl = current.clone().trim_start().to_owned(); - let currentr = current.clone().trim_end().to_owned(); - if currentl.starts_with("/*") && currentr.ends_with("*/") { - valid = true; - } else if currentr.ends_with("*/") { - multiline = true; - valid = true; - } else if currentl.starts_with("/*") { - multiline = false; - valid = true; - } else { - valid = currentl.starts_with("//") || multiline; - } - } - } - hover.reverse(); - let mut result: Vec = Vec::new(); - for i in hover { - if let Some(stripped) = i.strip_prefix(<rim) { - result.push(stripped.to_owned()); - } else { - result.push(i); - } - } - result.join("").trim_end().to_owned() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::sources::{parse, LSPSupport}; - use crate::support::test_init; - use ropey::Rope; - use std::fs::read_to_string; - use std::path::PathBuf; - - #[test] - fn test_definition_token() { - test_init(); - let line = Rope::from_str("assign ab_c[2:0] = 3'b000;"); - let line_text = line.line(0); - let token = get_definition_token(&line_text, Position::new(0, 10)); - assert_eq!(token, "ab_c".to_owned()); - } - - #[test] - fn test_get_definition() { - test_init(); - let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - d.push("test_data/definition_test.sv"); - let text = read_to_string(d).unwrap(); - let doc = Rope::from_str(&text); - let url = Url::parse("file:///test_data/definition_test.sv").unwrap(); - let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap(); - let scope_tree = get_scopes(&syntax_tree, &url).unwrap(); - - let line_text = doc.line(3); - let token = get_definition_token(&line_text, Position::new(3, 13)); - for def in scope_tree.defs { - if token == def.ident() { - assert_eq!(doc.byte_to_pos(def.byte_idx()), Position::new(3, 9)) - } - } - } - - #[test] - fn test_hover() { - test_init(); - let text = r#" -// module test -// test module -module test; - /* a */ - logic a; - /** - * b - */ - logic b; - endmodule"#; - let doc = Rope::from_str(text); - eprintln!("{}", get_hover(&doc, 2)); - assert_eq!( - get_hover(&doc, 3), - r#"// module test -// test module -module test;"# - .to_owned() - ); - assert_eq!( - get_hover(&doc, 9), - r#"/** - * b -*/ -logic b;"# - .to_owned() - ); - } - - #[test] - fn test_symbols() { - test_init(); - let text = r#" -module test; - logic a; - logic b; -endmodule"#; - let doc = Rope::from_str(&text); - let url = Url::parse("file:///test.sv").unwrap(); - let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap(); - let scope_tree = get_scopes(&syntax_tree, &url).unwrap(); - let symbol = scope_tree.document_symbols(&url, &doc); - let symbol = symbol.get(0).unwrap(); - assert_eq!(&symbol.name, "test"); - let names: Vec = symbol - .children - .as_ref() - .unwrap() - .iter() - .map(|x| x.name.clone()) - .collect(); - assert!(names.contains(&"a".to_string())); - assert!(names.contains(&"b".to_string())); - } - - #[test] - fn test_highlight() { - test_init(); - let text = r#" -module test; - logic clk; - assign clk = 1'b1; -endmodule"#; - let doc = Rope::from_str(&text); - let url = Url::parse("file:///test.sv").unwrap(); - let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap(); - let scope_tree = get_scopes(&syntax_tree, &url).unwrap(); - let references = all_identifiers(&syntax_tree, "clk"); - let highlights = scope_tree.document_highlights( - &url, - &doc, - references, - doc.pos_to_byte(&Position::new(2, 8)), - ); - let expected = vec![ - DocumentHighlight { - range: Range { - start: Position { - line: 2, - character: 8, - }, - end: Position { - line: 2, - character: 11, - }, - }, - kind: None, - }, - DocumentHighlight { - range: Range { - start: Position { - line: 3, - character: 9, - }, - end: Position { - line: 3, - character: 12, - }, - }, - kind: None, - }, - ]; - assert_eq!(highlights, expected) - } -} diff --git a/src/diagnostics.rs b/src/diagnostics/mod.rs similarity index 100% rename from src/diagnostics.rs rename to src/diagnostics/mod.rs diff --git a/src/format.rs b/src/format/mod.rs similarity index 100% rename from src/format.rs rename to src/format/mod.rs diff --git a/src/hover/feature.rs b/src/hover/feature.rs new file mode 100644 index 0000000..71e0abd --- /dev/null +++ b/src/hover/feature.rs @@ -0,0 +1,20 @@ +use log::info; +use regex::Regex; +use ropey::RopeSlice; +use tower_lsp::lsp_types::{Hover, Position}; + +use super::get_word_range_at_position; + + + +/// 将 1'b1 翻译成 10进制 +pub fn match_format_digit(line: &RopeSlice, pos: Position) -> Option { + let line_text = line.as_str().unwrap_or(""); + let regex = Regex::new(r"[0-9'bho]").unwrap(); + let digit_string = get_word_range_at_position(line, pos, regex); + if digit_string.len() > 0 { + info!("current digit: {}", digit_string); + } + + None +} \ No newline at end of file diff --git a/src/hover/mod.rs b/src/hover/mod.rs new file mode 100644 index 0000000..a8216a4 --- /dev/null +++ b/src/hover/mod.rs @@ -0,0 +1,158 @@ +use std::{fmt::format, sync::RwLockReadGuard}; + +use crate::definition::*; +use crate::server::LSPServer; +use crate::sources::LSPSupport; +use crate::utils::*; +use ropey::{Rope, RopeSlice}; +use tower_lsp::lsp_types::*; + +pub mod feature; +use feature::*; + +impl LSPServer { + pub fn hover(&self, params: HoverParams) -> Option { + let doc: Url = params.text_document_position_params.text_document.uri; + let pos: Position = params.text_document_position_params.position; + let file_id: usize = self.srcs.get_id(&doc).to_owned(); + self.srcs.wait_parse_ready(file_id, false); + let file: std::sync::Arc> = self.srcs.get_file(file_id)?; + let file: std::sync::RwLockReadGuard<'_, crate::sources::Source> = file.read().ok()?; + + let line_text = file.text.line(pos.line as usize); + let token: String = get_definition_token(&line_text, pos); + let language_id = get_language_id_by_uri(&doc); + + // match `include + if let Some(hover) = match_include(&doc, &line_text, pos, &language_id) { + return Some(hover); + } + + + let scope_tree: RwLockReadGuard<'_, Option> = self.srcs.scope_tree.read().ok()?; + let global_scope = scope_tree.as_ref(); + + if let Some(global_scope) = global_scope { + // match 正常 symbol + if let Some(hover) = match_common_symbol(global_scope, &token, &file, &doc, pos, &language_id) { + return Some(hover); + } + } + + // match digit 5'b00110 + if let Some(hover) = match_format_digit(&line_text, pos) { + return Some(hover); + } + + None + } + +} + + + +/// get the hover information +fn get_hover(doc: &Rope, line: usize) -> String { + if line == 0 { + return doc.line(line).to_string(); + } + let mut hover: Vec = Vec::new(); + let mut multiline: bool = false; + let mut valid: bool = true; + let mut current: String = doc.line(line).to_string(); + let ltrim: String = " ".repeat(current.len() - current.trim_start().len()); + let mut line_idx = line; + + // iterate upwards from the definition, and grab the comments + while valid { + hover.push(current.clone()); + line_idx -= 1; + valid = false; + if line_idx > 0 { + current = doc.line(line_idx).to_string(); + let currentl = current.clone().trim_start().to_owned(); + let currentr = current.clone().trim_end().to_owned(); + if currentl.starts_with("/*") && currentr.ends_with("*/") { + valid = true; + } else if currentr.ends_with("*/") { + multiline = true; + valid = true; + } else if currentl.starts_with("/*") { + multiline = false; + valid = true; + } else { + valid = currentl.starts_with("//") || multiline; + } + } + } + hover.reverse(); + let mut result: Vec = Vec::new(); + for i in hover { + if let Some(stripped) = i.strip_prefix(<rim) { + result.push(stripped.to_owned()); + } else { + result.push(i); + } + } + result.join("").trim_end().to_owned() +} + + +fn match_include(uri: &Url, line: &RopeSlice, pos: Position, language_id: &String) -> Option { + let line_text = line.as_str().unwrap_or(""); + if line_text.trim().starts_with("`include") { + let character = pos.character as usize; + let first_quote_idx = line_text.find("\""); + let last_quote_idx = line_text.rfind("\""); + + if first_quote_idx.is_none() || last_quote_idx.is_none() { + return None; + } + + let first_quote_idx = first_quote_idx.unwrap(); + let last_quote_idx = last_quote_idx.unwrap(); + if character >= first_quote_idx && character <= last_quote_idx { + let mut path_string = &line_text[(first_quote_idx + 1) .. last_quote_idx]; + if path_string.starts_with("./") || path_string.starts_with(".\\") { + path_string = &path_string[2 ..]; + } + + if let Some(abs_path) = resolve_path(uri.path(), path_string) { + let content = format!("{:?}", abs_path); + let language_string = LanguageString { + language: language_id.to_string(), + value: content + }; + let markdown = MarkedString::LanguageString(language_string); + return Some(Hover { contents: HoverContents::Scalar(markdown), range: None }) + } + } + } + + None +} + +fn match_common_symbol( + scope_tree: &GenericScope, + token: &String, + file: &RwLockReadGuard<'_, crate::sources::Source>, + doc: &Url, + pos: Position, + language_id: &String +) -> Option { + + let def: GenericDec = scope_tree + .get_definition(token, file.text.pos_to_byte(&pos), doc)?; + + let def_line = file.text.byte_to_line(def.byte_idx()); + let language_string = LanguageString { + language: language_id.to_string(), + value: get_hover(&file.text, def_line) + }; + let markdown = MarkedString::LanguageString(language_string); + + Some(Hover { + contents: HoverContents::Scalar(markdown), + range: None, + }) +} diff --git a/src/lib.rs b/src/lib.rs index 00ca923..73cb85f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,31 @@ #![recursion_limit = "256"] pub mod core; + +// 自动补全 pub mod completion; + +// 定义跳转 pub mod definition; + +// 悬停提示 +pub mod hover; + +// 诊断 pub mod diagnostics; + +// 格式化 pub mod format; + +// 基础工具 +pub mod utils; + pub mod server; pub mod sources; pub mod support; + +// 自定义发送请求 pub mod custom_request; + +// 测试模块 pub mod test; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 3fbcb9c..370d98c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,8 @@ use tower_lsp::{LspService, Server}; mod core; mod completion; mod definition; +mod hover; +mod utils; mod diagnostics; mod format; mod server; diff --git a/src/sources.rs b/src/sources.rs index f42d233..b4c9c06 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -19,7 +19,6 @@ use std::str::FromStr; use std::sync::{Arc, Condvar, Mutex, RwLock}; use std::thread; use sv_parser::*; -use sv_parser::sv_parser_pp_range; use thread::JoinHandle; use tower_lsp::lsp_types::*; use walkdir::WalkDir; @@ -33,11 +32,6 @@ macro_rules! unwrap_result { }; } -struct CustomRange { - pub begin: usize, - pub end: usize, -} - impl LSPServer { pub fn did_open(&self, params: DidOpenTextDocumentParams) -> PublishDiagnosticsParams { info!("[LSPServer] did open"); @@ -68,11 +62,12 @@ impl LSPServer { } pub fn did_change(&self, params: DidChangeTextDocumentParams) { - info!("[LSPServer] did change"); + info!("[LSPServer] did change, change content: {:?}", params.content_changes); let file_id = self.srcs.get_id(¶ms.text_document.uri); let file = self.srcs.get_file(file_id).unwrap(); let mut file = file.write().unwrap(); + // loop through changes and apply for change in params.content_changes { if change.range.is_none() { @@ -161,6 +156,7 @@ pub struct Sources { // file metadata pub meta: Arc>>>>, // all source files are indexed into this tree, which can then + // be used for completion, name resolution pub scope_tree: Arc>>, // include directories, passed to parser to resolve `include @@ -231,7 +227,7 @@ impl Sources { let scope_handle = self.scope_tree.clone(); let inc_dirs = self.include_dirs.clone(); - // let backend = Arc::new(backend); + info!("launch worker to parse {:?}", doc.uri.to_string()); // spawn parse thread let parse_handle = thread::spawn(move || { @@ -245,12 +241,15 @@ impl Sources { info!("do parse in {:?}", uri.to_string()); - let syntax_tree = parse(&text, uri, range, &inc_dirs.read().unwrap()); + let syntax_tree = recovery_sv_parse(&text, uri, range, &inc_dirs.read().unwrap()); let mut scope_tree = match &syntax_tree { Some(tree) => get_scopes(tree, uri), None => None, }; + + info!("finish parse {:?}", uri.to_string()); + // 计算 fast // if let Some(syntax_tree) = &syntax_tree { // let path = PathBuf::from(uri.path().to_string()); @@ -406,9 +405,9 @@ impl Sources { } } -//TODO: show all unrecoverable parse errors to user -/// parse the file using sv-parser, attempt to recover if the parse fails -pub fn parse( +/// 更加稳定地解析 sv 和 v +/// 支持遇到错误进行自动修复,然后再解析 +pub fn recovery_sv_parse( doc: &Rope, uri: &Url, last_change_range: &Option, @@ -429,39 +428,48 @@ pub fn parse( &defines, &includes, true, - true + false ) { Ok((syntax_tree, _)) => { return Some(syntax_tree); } Err(err) => { + println!("find err : {:?}", err); + info!("find err : {:?}", err); match err { // 语法错误 - sv_parser::Error::Parse(trace) => match trace { - Some((_, bpos)) => { - let mut line_start = text.byte_to_line(bpos); - let mut line_end = text.byte_to_line(bpos) + 1; - if !reverted_change { - if let Some(range) = last_change_range { - line_start = range.start.line as usize; - line_end = range.end.line as usize + 1; - reverted_change = true; - } - } - for line_idx in line_start..line_end { - let line = text.line(line_idx); - let start_char = text.line_to_char(line_idx); - let line_length = line.len_chars(); - text.remove(start_char..(start_char + line_length - 1)); - text.insert(start_char, &" ".to_owned().repeat(line_length)); - } - parse_iterations += 1; + sv_parser::Error::Parse(trace) => { + if trace.is_none() { + return None; } - None => return None, + + let (_, bpos) = trace.unwrap(); + let mut line_start = text.byte_to_line(bpos); + let mut line_end = text.byte_to_line(bpos) + 1; + // println!("enter Error::Parse, start: {}, end: {}", line_start, line_end); + + if !reverted_change { + if let Some(range) = last_change_range { + line_start = range.start.line as usize; + line_end = range.end.line as usize + 1; + reverted_change = true; + } + } + + // 把 last_change_range 中的涉及到的所有行内容用等长的空格替代 + for line_idx in line_start..line_end { + let line = text.line(line_idx); + let start_char = text.line_to_char(line_idx); + let line_length = line.len_chars(); + text.remove(start_char..(start_char + line_length - 1)); + text.insert(start_char, &" ".to_owned().repeat(line_length)); + } + parse_iterations += 1; }, // 遇到 include 错误,那就把提示中的 include 加入解析中再次解析 sv_parser::Error::Include { source: x } => { + info!("Error::Include"); if let sv_parser::Error::File { source: _, path: z } = *x { // Include paths have to be relative to the working directory // so we have to convert a source file relative path to a working directory @@ -484,6 +492,7 @@ pub fn parse( // 宏定义不存在的错误 sv_parser::Error::DefineNotFound(not_found_macro_name) => { + info!("Error::DefineNotFound"); let range = sv_parser::sv_parser_pp_range::Range { begin: 0, end: 0 }; let pathbuf = PathBuf::from_str(uri.as_str()).unwrap(); let origin = Some((pathbuf, range)); @@ -494,6 +503,7 @@ pub fn parse( }; defines.insert(not_found_macro_name, Some(com_define)); parse_iterations += 1; + info!("parse iteration plus one"); } _ => error!("parse error, {:?}", err), }; @@ -687,7 +697,7 @@ endmodule"#; d.push("test_data/top_inc.sv"); let text = read_to_string(&d).unwrap(); let doc = Rope::from_str(&text); - assert!(parse(&doc, &Url::from_file_path(d).unwrap(), &None, &Vec::new()).is_some(),); + assert!(recovery_sv_parse(&doc, &Url::from_file_path(d).unwrap(), &None, &Vec::new()).is_some(),); // TODO: add missing header test } } diff --git a/src/test/mod.rs b/src/test/mod.rs index f442a47..3385c08 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -5,7 +5,7 @@ const TESTFILES_DIR: &str = "testfiles"; const DIGTIAL_IDE_TEST: &str = "/home/dide/project/Digital-Test/Digital-IDE-test/user"; #[allow(unused)] -const TEST_FILE: &str = "/home/dide/project/Digital-Test/Digital-IDE-test/user/src/svlog/tools/ivtest/br_gh330.sv"; +const TEST_FILE: &str = "/home/dide/project/Digital-Test/MipsDesign/src/MyCpu.v"; #[allow(unused)] const INCOMPLETE_EXAMPLE: &str = "/home/dide/project/Digital-Test/Digital-IDE-test/user/src/incomplete-example"; @@ -37,6 +37,13 @@ mod test_fast { use crate::core::sv_parser::sv_parser; use super::*; + #[test] + fn test_mycpu() { + let hdlparam = sv_parser(TEST_FILE); + println!("{hdlparam:?}"); + assert!(hdlparam.is_some()); + } + #[test] fn test_testfiles() { let entries = unwrap_result!(fs::read_dir(TESTFILES_DIR)); @@ -122,11 +129,12 @@ mod test_fast { #[cfg(test)] mod test_svparse { - use std::{fs, path::{Path, PathBuf}}; + use std::{collections::HashMap, fs, path::{Path, PathBuf}}; use ropey::Rope; - use tower_lsp::lsp_types::Url; - use crate::sources::parse; + use sv_parser::parse_sv_str; + use tower_lsp::lsp_types::{Position, Range, Url}; + use crate::sources::recovery_sv_parse; use super::{INCOMPLETE_EXAMPLE, TEST_FILE}; @@ -143,10 +151,15 @@ mod test_svparse { let doc = Rope::from_str(&text); let uri = Url::from_file_path(&path).unwrap(); - let result = parse(&doc, &uri, &None, &includes); + let last_change_range = Range::new( + Position { line: 0, character: 0 }, + Position { line: 0, character: 0 }, + ); + let result = recovery_sv_parse(&doc, &uri, &Some(last_change_range), &includes); match result { - Some(_) => { + Some(syntax_tree) => { println!("success"); + println!("{:?}", syntax_tree); }, None => { eprintln!("gen None"); @@ -204,8 +217,10 @@ mod test_svparse { let includes: Vec = Vec::new(); let doc = Rope::from_str(&text); let uri = Url::from_file_path(file_path).unwrap(); - let result = parse(&doc, &uri, &None, &includes); + let result = recovery_sv_parse(&doc, &uri, &None, &includes); + assert!(result.is_some()); + } } } @@ -217,19 +232,26 @@ mod test_svparse { #[cfg(test)] mod test_scope_tree { - use std::{collections::HashMap, fs, path::{Path, PathBuf}}; - use crate::definition::{get_scopes, GenericScope}; - use sv_parser::parse_sv; + use std::{fs, path::{Path, PathBuf}}; + use crate::{definition::{get_scopes, GenericScope}, sources::recovery_sv_parse}; + use ropey::Rope; use tower_lsp::lsp_types::Url; use super::*; fn get_scope_tree(file_path: &str) -> Option { - let defines = HashMap::new(); let includes: Vec = Vec::new(); - let result = parse_sv(file_path, &defines, &includes, true, true); - if let Ok((syntax_tree, _)) = result { + let text = match fs::read_to_string(file_path) { + Ok(text) => text, + Err(_) => return None + }; + + let doc = Rope::from_str(&text); + let uri = Url::from_file_path(&file_path).unwrap(); + let result = recovery_sv_parse(&doc, &uri, &None, &includes); + + if let Some(syntax_tree) = result { let file_url = format!("file://{}", file_path); let uri = Url::parse(&file_url); if let Ok(uri) = uri { @@ -242,6 +264,16 @@ mod test_scope_tree { None } + #[test] + fn test_mycpu() { + let result = get_scope_tree(TEST_FILE); + if let Some(scope) = result { + println!("{:?}", scope); + } else { + panic!("error parsing"); + } + } + #[test] fn test_testfiles() { let entries = unwrap_result!(fs::read_dir(TESTFILES_DIR)); diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..5e1ef0b --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,93 @@ +use std::path::{Path, PathBuf}; + +use regex::Regex; +use ropey::RopeSlice; +use tower_lsp::lsp_types::*; + +/// 根据 pos 获取到当前光标所在的字符串,相当于 getWordRangeAtPosition +pub fn get_definition_token(line: &RopeSlice, pos: Position) -> String { + let mut token = String::new(); + let mut line_iter = line.chars(); + for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) { + line_iter.next(); + } + let mut c = line_iter.prev(); + while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') { + token.push(c.unwrap()); + c = line_iter.prev(); + } + token = token.chars().rev().collect(); + line_iter = line.chars(); + for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) { + line_iter.next(); + } + let mut c = line_iter.next(); + while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') { + token.push(c.unwrap()); + c = line_iter.next(); + } + token +} + +pub fn get_word_range_at_position(line: &RopeSlice, pos: Position, regex: Regex) -> String { + let mut token = String::new(); + let mut line_iter = line.chars(); + for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) { + line_iter.next(); + } + let mut c = line_iter.prev(); + + while c.is_some() && (regex.is_match(&c.unwrap().to_string())) { + token.push(c.unwrap()); + c = line_iter.prev(); + } + token = token.chars().rev().collect(); + line_iter = line.chars(); + for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) { + line_iter.next(); + } + let mut c = line_iter.next(); + while c.is_some() && (regex.is_match(&c.unwrap().to_string())) { + token.push(c.unwrap()); + c = line_iter.next(); + } + token +} + +/// 根据 uri 获取 hdl 的 language id +pub fn get_language_id_by_uri(uri: &Url) -> String { + let path = uri.path(); + let ext_name = std::path::Path::new(path) + .extension() + .and_then(std::ffi::OsStr::to_str) + .unwrap_or(""); + + match ext_name { + "vhd" | "vhdl" | "vho" | "vht" => "vhdl".to_string(), + "v" | "V" | "vh" | "vl" => "verilog".to_string(), + "sv" | "svh" => "systemverilog".to_string(), + _ => "plaintext".to_string() + } +} + +/// 根据基础路径和给出的 path 路径,计算出绝对路径 +/// 如果 path 是绝对路径直接返回 +/// 会进行存在性检查,不存在的路径直接返回 None +/// 例子: +/// resolve_path("/home/ubuntu/hello.v", "./control.v") -> Some("/home/ubuntu/control.v") +/// resolve_path("/home/ubuntu/hello.v", "/opt/control.v") -> Some("/opt/control.v") +pub fn resolve_path(base: &str, path_string: &str) -> Option { + let path = Path::new(path_string); + let abs_path: PathBuf; + if path.is_absolute() { + abs_path = PathBuf::from(path); + } else { + let base = Path::new(base).parent()?; + abs_path = base.join(path); + } + if abs_path.exists() { + let path = abs_path.as_path().to_owned(); + return Some(path); + } + None +} \ No newline at end of file