diff --git a/src/completion/feature.rs b/src/completion/feature.rs index 07e15a2..8e2c6e4 100644 --- a/src/completion/feature.rs +++ b/src/completion/feature.rs @@ -1,10 +1,11 @@ use std::{fs, path::PathBuf, str::FromStr}; use log::info; +use regex::Regex; use ropey::RopeSlice; -use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, CompletionList, Position, Url}; +use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionList, Position, Url}; -use crate::utils::{resolve_path, to_escape_path}; +use crate::{server::LSPServer, utils::{get_word_range_at_position, 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(""); @@ -85,4 +86,173 @@ pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Op } } None -} \ No newline at end of file +} + +pub fn get_dot_completion( + server: &LSPServer, + line: &RopeSlice, + url: &Url, + pos: &Position, + language_id: &str +) -> Option { + // 判断点模式,v 中,点的模式一共两种 + // 一种是 port & param position 赋值 + // 一种是结构体中的属性访问 + info!("current line: {:?}, pos: {:?}", line, pos); + + if is_port_completion(line, pos) { + return get_position_port_param_completion(server, line, url, pos, language_id); + } + + // TODO: 加入结构体的补全 + + None +} + +fn is_port_completion(line: &RopeSlice, pos: &Position) -> bool { + let character = pos.character as usize; + if character == 0 { + match line.get_char(character) { + Some(char) => { + let char_string = char.to_string(); + return char_string == "."; + }, + None => return false + } + } else { + let pre_char = line.get_char(character - 1); + let cur_char = line.get_char(character); + match pre_char { + Some(pre_char) => { + let pre_char = pre_char.to_string(); + if pre_char == "." { + return true; + } + match cur_char { + Some(cur_char) => { + let cur_char = cur_char.to_string(); + return cur_char == "."; + }, + None => {} + } + }, + None => {} + } + } + false +} + +fn get_position_port_param_completion( + server: &LSPServer, + line: &RopeSlice, + url: &Url, + pos: &Position, + language_id: &str +) -> Option { + // 判断在不在一个模块内,并获取这个模块 + let hdl_param = &server.srcs.hdl_param; + let fast_map = hdl_param.path_to_hdl_file.read().unwrap(); + let path = PathBuf::from_str(url.path()).unwrap(); + let path = to_escape_path(&path); + let path_string = path.to_str().unwrap(); + if let Some(hdl_file) = fast_map.get(path_string) { + // 在当前文件的 fast 中寻找 + for module in &hdl_file.fast.content { + for instance in &module.instances { + if let Some(param_range) = &instance.instparams { + if param_range.contains(pos) { + // 补全当前 module 的所有 param + let inst_module = hdl_param.find_module_by_name(&instance.inst_type); + if inst_module.is_some() { + let inst_module = inst_module.unwrap(); + let mut completion_items = Vec::::new(); + for param in inst_module.params { + let label_details = CompletionItemLabelDetails { + detail: Some("parameter".to_string()), + ..CompletionItemLabelDetails::default() + }; + + let param_desc = make_param_desc(¶m); + let c_item = CompletionItem { + label: param.name, + detail: Some(param_desc), + label_details: Some(label_details), + kind: Some(CompletionItemKind::CONSTANT), + ..CompletionItem::default() + }; + completion_items.push(c_item); + } + return Some(CompletionList { + is_incomplete: false, + items: completion_items + }); + } + } + } + + if let Some(port_range) = &instance.instports { + if port_range.contains(pos) { + let inst_module = hdl_param.find_module_by_name(&instance.inst_type); + if inst_module.is_some() { + let inst_module = inst_module.unwrap(); + let mut completion_items = Vec::::new(); + for port in inst_module.ports { + let label_details = CompletionItemLabelDetails { + detail: Some("port".to_string()), + ..CompletionItemLabelDetails::default() + }; + + let param_desc = make_port_desc(&port); + let c_item = CompletionItem { + label: port.name, + detail: Some(param_desc), + label_details: Some(label_details), + kind: Some(CompletionItemKind::CONSTANT), + ..CompletionItem::default() + }; + completion_items.push(c_item); + } + return Some(CompletionList { + is_incomplete: false, + items: completion_items + }); + } + } + } + } + } + } + None +} + +fn make_port_desc(port: &crate::core::fast_hdlparam::Port) -> String { + let mut port_desc_array = Vec::::new(); + port_desc_array.push(port.dir_type.to_string()); + if port.net_type != "unknown" { + port_desc_array.push(port.net_type.to_string()); + } + + if port.signed != "unsigned" { + port_desc_array.push("signed".to_string()); + } + + if port.width != "1" { + port_desc_array.push(port.width.to_string()); + } + + port_desc_array.push(port.name.to_string()); + let port_desc = port_desc_array.join(" "); + port_desc +} + +fn make_param_desc(param: &crate::core::fast_hdlparam::Parameter) -> String { + let mut param_desc_array = Vec::::new(); + param_desc_array.push(format!("parameter {}", param.name)); + + if param.init != "unknown" { + param_desc_array.push(param.init.to_string()); + } + + let param_desc = param_desc_array.join(" "); + param_desc +} diff --git a/src/completion/mod.rs b/src/completion/mod.rs index a055bcc..95ebbe3 100644 --- a/src/completion/mod.rs +++ b/src/completion/mod.rs @@ -1,187 +1,19 @@ -use crate::server::LSPServer; -use crate::sources::LSPSupport; -use feature::include_path_completion; -use log::info; -use ropey::{Rope, RopeSlice}; +use crate::{server::LSPServer, utils::get_language_id_by_uri}; use tower_lsp::lsp_types::*; pub mod keyword; pub mod feature; -pub mod vhdl; +mod vhdl; +mod sv; impl LSPServer { - pub fn completion(&self, params: CompletionParams) -> Option { - let doc = params.text_document_position; - let pos = doc.position; - - let file_id = self.srcs.get_id(&doc.text_document.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 token = get_completion_token( - &file.text, - file.text.line(doc.position.line as usize), - doc.position, - ); - - // info!("trigger completion token: {}", token); - let line_text = file.text.line(pos.line as usize); - - let response = match params.context { - Some(context) => match context.trigger_kind { - CompletionTriggerKind::TRIGGER_CHARACTER => { - match context.trigger_character?.as_str() { - "." => Some(self.srcs.get_dot_completions( - token.trim_end_matches('.'), - file.text.pos_to_byte(&doc.position), - &doc.text_document.uri, - )?), - "$" => Some(CompletionList { - is_incomplete: false, - items: self.sys_tasks.clone(), - }), - "`" => Some(CompletionList { - is_incomplete: false, - items: self.directives.clone(), - }), - "/" => { - info!("trigger include"); - include_path_completion(&doc.text_document.uri, &line_text, pos) - }, - "\"" => { - info!("trigger include"); - include_path_completion(&doc.text_document.uri, &line_text, pos) - } - _ => None, - } - } - CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None, - CompletionTriggerKind::INVOKED => { - let mut comps = self.srcs.get_completions( - &token, - file.text.pos_to_byte(&doc.position), - &doc.text_document.uri, - )?; - - // complete keywords - comps.items.extend::>( - self.key_comps - .iter() - .filter(|x| x.label.starts_with(&token)) - .cloned() - .collect(), - ); - Some(comps) - } - _ => None, - }, - None => { - let trigger = prev_char(&file.text, &doc.position); - match trigger { - '.' => Some(self.srcs.get_dot_completions( - token.trim_end_matches('.'), - file.text.pos_to_byte(&doc.position), - &doc.text_document.uri, - )?), - '$' => Some(CompletionList { - is_incomplete: false, - items: self.sys_tasks.clone(), - }), - '`' => Some(CompletionList { - is_incomplete: false, - items: self.directives.clone(), - }), - _ => { - let mut comps = self.srcs.get_completions( - &token, - file.text.pos_to_byte(&doc.position), - &doc.text_document.uri, - )?; - comps.items.extend::>( - self.key_comps - .iter() - .filter(|x| x.label.starts_with(&token)) - .cloned() - .collect(), - ); - Some(comps) - } - } - } - }; - // eprintln!("comp response: {}", now.elapsed().as_millis()); - Some(CompletionResponse::List(response?)) - } -} - - - -/// get the previous non-whitespace character -fn prev_char(text: &Rope, pos: &Position) -> char { - let char_idx = text.pos_to_char(pos); - if char_idx > 0 { - for i in (0..char_idx).rev() { - let res = text.char(i); - if !res.is_whitespace() { - return res; - } - } - ' ' - } else { - ' ' - } -} - -/// attempt to get the token the user was trying to complete, by -/// filtering out characters unneeded for name resolution -fn get_completion_token(text: &Rope, 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(); - //TODO: make this a regex - while c.is_some() - && (c.unwrap().is_alphanumeric() - || c.unwrap() == '_' - || c.unwrap() == '.' - || c.unwrap() == '[' - || c.unwrap() == ']') - { - token.push(c.unwrap()); - c = line_iter.prev(); - } - let mut result: String = token.chars().rev().collect(); - if result.contains('[') { - let l_bracket_offset = result.find('[').unwrap_or(result.len()); - result.replace_range(l_bracket_offset.., ""); - } - if &result == "." { - // probably a instantiation, the token should be what we're instatiating - let mut char_iter = text.chars(); - let mut token = String::new(); - for _ in 0..text.pos_to_char(&pos) { - char_iter.next(); - } - let mut c = char_iter.prev(); - - // go to the last semicolon - while c.is_some() && (c.unwrap() != ';') { - c = char_iter.prev(); - } - // go the the start of the next symbol - while c.is_some() && !(c.unwrap().is_alphanumeric() || c.unwrap() == '_') { - c = char_iter.next(); - } - // then extract the next symbol - while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') { - token.push(c.unwrap()); - c = char_iter.next(); - } - token - } else { - result + 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() { + "vhdl" => vhdl::completion(self, ¶ms), + "verilog" | "systemverilog" => sv::completion(self, ¶ms), + _ => None + } } } diff --git a/src/completion/sv.rs b/src/completion/sv.rs new file mode 100644 index 0000000..489362f --- /dev/null +++ b/src/completion/sv.rs @@ -0,0 +1,182 @@ +use crate::{completion::feature::{get_dot_completion, include_path_completion}, 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 { + let doc = ¶ms.text_document_position; + let uri = ¶ms.text_document_position.text_document.uri; + let pos = doc.position; + let language_id = get_language_id_by_uri(uri); + + let file_id = server.srcs.get_id(uri).to_owned(); + server.srcs.wait_parse_ready(file_id, false); + let file = server.srcs.get_file(file_id)?; + let file = file.read().ok()?; + let line_text = file.text.line(doc.position.line as usize); + let token = get_completion_token( + &file.text, + line_text.clone(), + doc.position, + ); + + // info!("trigger completion token: {}", token); + let line_text = file.text.line(pos.line as usize); + + let response = match ¶ms.context { + Some(context) => match context.trigger_kind { + CompletionTriggerKind::TRIGGER_CHARACTER => { + let trigger_character = context.trigger_character.clone().unwrap(); + match trigger_character.as_str() { + "." => { + info!("trigger dot completion"); + get_dot_completion(server, &line_text, uri, &pos, &language_id) + }, + "$" => Some(CompletionList { + is_incomplete: false, + items: server.sys_tasks.clone(), + }), + "`" => Some(CompletionList { + is_incomplete: false, + items: server.directives.clone(), + }), + "/" => { + info!("trigger include"); + include_path_completion(&doc.text_document.uri, &line_text, pos) + }, + "\"" => { + info!("trigger include"); + include_path_completion(&doc.text_document.uri, &line_text, pos) + } + _ => None, + } + } + CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None, + CompletionTriggerKind::INVOKED => { + let mut comps = server.srcs.get_completions( + &token, + file.text.pos_to_byte(&doc.position), + &doc.text_document.uri, + )?; + + // complete keywords + comps.items.extend::>( + server.key_comps + .iter() + .filter(|x| x.label.starts_with(&token)) + .cloned() + .collect(), + ); + Some(comps) + } + _ => None, + }, + None => { + let trigger = prev_char(&file.text, &doc.position); + match trigger { + '.' => Some(server.srcs.get_dot_completions( + token.trim_end_matches('.'), + file.text.pos_to_byte(&doc.position), + &doc.text_document.uri, + )?), + '$' => Some(CompletionList { + is_incomplete: false, + items: server.sys_tasks.clone(), + }), + '`' => Some(CompletionList { + is_incomplete: false, + items: server.directives.clone(), + }), + _ => { + let mut comps = server.srcs.get_completions( + &token, + file.text.pos_to_byte(&doc.position), + &doc.text_document.uri, + )?; + comps.items.extend::>( + server.key_comps + .iter() + .filter(|x| x.label.starts_with(&token)) + .cloned() + .collect(), + ); + Some(comps) + } + } + } + }; + // eprintln!("comp response: {}", now.elapsed().as_millis()); + Some(CompletionResponse::List(response?)) +} + + +/// get the previous non-whitespace character +fn prev_char(text: &Rope, pos: &Position) -> char { + let char_idx = text.pos_to_char(pos); + if char_idx > 0 { + for i in (0..char_idx).rev() { + let res = text.char(i); + if !res.is_whitespace() { + return res; + } + } + ' ' + } else { + ' ' + } +} + +/// attempt to get the token the user was trying to complete, by +/// filtering out characters unneeded for name resolution +fn get_completion_token(text: &Rope, 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(); + //TODO: make this a regex + while c.is_some() + && (c.unwrap().is_alphanumeric() + || c.unwrap() == '_' + || c.unwrap() == '.' + || c.unwrap() == '[' + || c.unwrap() == ']') + { + token.push(c.unwrap()); + c = line_iter.prev(); + } + let mut result: String = token.chars().rev().collect(); + if result.contains('[') { + let l_bracket_offset = result.find('[').unwrap_or(result.len()); + result.replace_range(l_bracket_offset.., ""); + } + if &result == "." { + // probably a instantiation, the token should be what we're instatiating + let mut char_iter = text.chars(); + let mut token = String::new(); + for _ in 0..text.pos_to_char(&pos) { + char_iter.next(); + } + let mut c = char_iter.prev(); + + // go to the last semicolon + while c.is_some() && (c.unwrap() != ';') { + c = char_iter.prev(); + } + // go the the start of the next symbol + while c.is_some() && !(c.unwrap().is_alphanumeric() || c.unwrap() == '_') { + c = char_iter.next(); + } + // then extract the next symbol + while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') { + token.push(c.unwrap()); + c = char_iter.next(); + } + token + } else { + result + } +} + diff --git a/src/completion/vhdl.rs b/src/completion/vhdl.rs index c24f3af..e09367a 100644 --- a/src/completion/vhdl.rs +++ b/src/completion/vhdl.rs @@ -7,7 +7,7 @@ use crate::{server::LSPServer, utils::{from_lsp_pos, to_escape_path}}; /// Called when the client requests a completion. /// This function looks in the source code to find suitable options and then returns them -pub fn request_completion(server: &LSPServer, params: &CompletionParams) -> Option { +pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option { let doc = ¶ms.text_document_position; // let pos = doc.position; @@ -33,16 +33,17 @@ pub fn request_completion(server: &LSPServer, params: &CompletionParams) -> Opti None => return None }; - let source = project.get_source(&escape_path)?; + // let source = project.get_source(&escape_path)?; let binding = escape_path; let file = binding.as_path(); // 1) get source position, and source file let Some(source) = project.get_source(file) else { // Do not enable completions for files that are not part of the project - return Some(CompletionList { + + return Some(CompletionResponse::List(CompletionList { ..Default::default() - }); + })); }; let cursor = from_lsp_pos(params.text_document_position.position); // 2) Optimization chance: go to last recognizable token before the cursor. For example: @@ -57,11 +58,10 @@ pub fn request_completion(server: &LSPServer, params: &CompletionParams) -> Opti .into_iter() .map(|item| completion_item_to_lsp_item(server, item)) .collect(); - - Some(CompletionList { + Some(CompletionResponse::List(CompletionList { items: options, is_incomplete: true, - }) + })) } fn completion_item_to_lsp_item( diff --git a/src/definition/mod.rs b/src/definition/mod.rs index e177553..f6583ef 100644 --- a/src/definition/mod.rs +++ b/src/definition/mod.rs @@ -1,6 +1,5 @@ -use crate::utils::get_definition_token; +use crate::utils::get_language_id_by_uri; use crate::server::LSPServer; -use crate::sources::LSPSupport; #[allow(unused)] use log::info; @@ -16,58 +15,21 @@ pub use feature::*; pub mod extract_defs; pub use extract_defs::*; -pub mod vhdl; +mod sv; +mod vhdl; impl LSPServer { pub fn goto_definition(&self, params: GotoDefinitionParams) -> Option { - let doc = params.text_document_position_params.text_document.uri; - let pos = params.text_document_position_params.position; - let file_id = self.srcs.get_id(&doc).to_owned(); - self.srcs.wait_parse_ready(file_id, false); - let file = self.srcs.get_file(file_id)?; - let file = file.read().ok()?; - - let line_text = file.text.line(pos.line as usize); - let token: String = get_definition_token(&line_text, pos); - - // match include - if let Some(definition) = goto_include_definition(&doc, &line_text, pos) { - return Some(definition); + let language_id = get_language_id_by_uri(¶ms.text_document_position_params.text_document.uri); + match language_id.as_str() { + "vhdl" => vhdl::goto_vhdl_definition(self, ¶ms), + "verilog" | "systemverilog" => sv::goto_definition(self, ¶ms), + _ => None } - - // match macro - if let Some(definition) = goto_macro_definition(self, &line_text, pos) { - return Some(definition); - } - - // match instance - - let scope_tree = self.srcs.scope_tree.read().ok()?; - - // match position port & param - if let Some(definition) = goto_position_port_param_definition(self, &line_text, &doc, pos) { - return Some(definition); - } - - // match module name - if let Some(definition) = goto_module_declaration_definition(self, &token) { - return Some(definition); - } - - let byte_idx = file.text.pos_to_byte(&pos); - let global_scope = scope_tree.as_ref()?; - let def = global_scope.get_definition(&token, byte_idx, &doc)?; - - let def_pos = file.text.byte_to_pos(def.byte_idx()); - Some(GotoDefinitionResponse::Scalar(Location::new( - def.url(), - Range::new(def_pos, def_pos), - ))) } } - type ScopesAndDefs = Option<(Vec>, Vec>)>; /// Take a given syntax node from a sv-parser syntax tree and extract out the definition/scope at diff --git a/src/definition/sv.rs b/src/definition/sv.rs new file mode 100644 index 0000000..056267c --- /dev/null +++ b/src/definition/sv.rs @@ -0,0 +1,56 @@ +use crate::utils::get_definition_token; +use crate::server::LSPServer; +use crate::sources::LSPSupport; + +#[allow(unused)] +use log::info; +use tower_lsp::lsp_types::*; + +use super::{feature::*, Definition, Scope}; + +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(); + server.srcs.wait_parse_ready(file_id, false); + let file = server.srcs.get_file(file_id)?; + let file = file.read().ok()?; + + let line_text = file.text.line(pos.line as usize); + let token: String = get_definition_token(&line_text, pos); + + // match include + if let Some(definition) = goto_include_definition(doc, &line_text, pos) { + return Some(definition); + } + + // match macro + if let Some(definition) = goto_macro_definition(server, &line_text, pos) { + return Some(definition); + } + + // match instance + + let scope_tree = server.srcs.scope_tree.read().ok()?; + + // match position port & param + if let Some(definition) = goto_position_port_param_definition(server, &line_text, doc, pos) { + return Some(definition); + } + + // match module name + if let Some(definition) = goto_module_declaration_definition(server, &token) { + return Some(definition); + } + + let byte_idx = file.text.pos_to_byte(&pos); + let global_scope = scope_tree.as_ref()?; + let def = global_scope.get_definition(&token, byte_idx, doc)?; + + let def_pos = file.text.byte_to_pos(def.byte_idx()); + Some(GotoDefinitionResponse::Scalar(Location::new( + def.url(), + Range::new(def_pos, def_pos), + ))) +} + diff --git a/src/definition/vhdl.rs b/src/definition/vhdl.rs index 06a423c..196c0a5 100644 --- a/src/definition/vhdl.rs +++ b/src/definition/vhdl.rs @@ -4,8 +4,9 @@ use log::info; use tower_lsp::lsp_types::*; use crate::{server::LSPServer, utils::{from_lsp_pos, srcpos_to_location, to_escape_path}}; -pub fn goto_vhdl_definition(server: &LSPServer, params: TextDocumentPositionParams) -> Option { - let uri = ¶ms.text_document.uri; +pub fn goto_vhdl_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Option { + let uri = ¶ms.text_document_position_params.text_document.uri; + let pos = params.text_document_position_params.position; let file_id = server.srcs.get_id(&uri).to_owned(); server.srcs.wait_parse_ready(file_id, false); let projects = server.srcs.design_file_map.read().ok()?; @@ -30,6 +31,7 @@ pub fn goto_vhdl_definition(server: &LSPServer, params: TextDocumentPositionPara let source = project.get_source(&escape_path)?; - let ent = project.find_definition(&source, from_lsp_pos(params.position))?; - Some(srcpos_to_location(ent.decl_pos()?)) + let ent = project.find_definition(&source, from_lsp_pos(pos))?; + let location = srcpos_to_location(ent.decl_pos()?); + Some(GotoDefinitionResponse::Scalar(location)) } \ No newline at end of file diff --git a/src/hover/feature.rs b/src/hover/feature.rs index 5ae15f8..1fbc935 100644 --- a/src/hover/feature.rs +++ b/src/hover/feature.rs @@ -3,11 +3,11 @@ use std::{path::PathBuf, str::FromStr}; use log::info; use regex::Regex; use ropey::RopeSlice; -use tower_lsp::lsp_types::{Hover, HoverContents, LanguageString, MarkedString, MarkupContent, MarkupKind, Position, Range, Url}; +use tower_lsp::lsp_types::{Hover, HoverContents, LanguageString, MarkedString, Position, Range, Url}; use crate::{core::fast_hdlparam::{Define, FastHdlparam}, server::LSPServer}; -use super::{file::get_range_text, get_word_range_at_position, resolve_path, to_escape_path}; +use super::{get_word_range_at_position, resolve_path, to_escape_path}; /// 将 4'b0011 分解为 ("b", "0011") fn parse_digit_string(digit_string: &str) -> Option<(&str, &str)> { diff --git a/src/hover/mod.rs b/src/hover/mod.rs index b492af6..ac20a77 100644 --- a/src/hover/mod.rs +++ b/src/hover/mod.rs @@ -1,225 +1,21 @@ -use std::sync::RwLockReadGuard; - -use crate::definition::*; use crate::server::LSPServer; -use crate::sources::LSPSupport; use crate::utils::*; #[allow(unused)] use log::info; -use regex::Regex; -use ropey::Rope; use tower_lsp::lsp_types::*; pub mod feature; -use feature::*; -pub mod vhdl; +mod sv; +mod vhdl; 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) = hover_include(&doc, &line_text, pos, &language_id) { - return Some(hover); - } - - // match macro - if let Some(hover) = hover_macro(self, &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(); - - - // match positional port param - if let Some(hover) = hover_position_port_param(self, &line_text, &doc, pos, &language_id) { - return Some(hover); - } - - // match module name - if let Some(hover) = hover_module_declaration(self, &token, &language_id) { - return Some(hover); - } - - if let Some(global_scope) = global_scope { - - // match 正常 symbol - if let Some(hover) = hover_common_symbol(self, global_scope, &token, &file, &doc, pos, &language_id) { - return Some(hover); - } - } - - // match digit 5'b00110 - if let Some(hover) = hover_format_digit(&line_text, pos, &language_id) { - return Some(hover); - } - - None - } - -} - - - -fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str) -> Option { - if line == 0 { - let language_string = LanguageString { - language: language_id.to_string(), - value: doc.line(line).to_string() - }; - let markdown = MarkedString::LanguageString(language_string); - - return Some(Hover { - contents: HoverContents::Scalar(markdown), - range: None, - }); - } - 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; - - // 寻找周围的注释 - 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; - } + let language_id = get_language_id_by_uri(¶ms.text_document_position_params.text_document.uri); + match language_id.as_str() { + "vhdl" => vhdl::hover(self, ¶ms), + "verilog" | "systemverilog" => sv::hover(self, ¶ms), + _ => None } } - hover.reverse(); - - let multi_space_regex = Regex::new(r"\s+").unwrap(); - let line_handler = |line: &str| -> String { - let line = multi_space_regex.replace_all(line.trim(), " "); - let mut line = line.into_owned(); - - if line.starts_with("/*") { - line = line.strip_prefix("/*").unwrap().trim().to_string(); - } - - if line.ends_with("*/") { - line = line.strip_suffix("*/").unwrap().trim().to_string(); - } - - return format!("{}\n", line); - }; - - let line_comment_extractor = |line: &str| -> Option<(String, String)> { - let line = line.trim(); - if line.contains("//") { - let comment_start = line.split_once("//"); - match comment_start { - Some((code, comment)) => { - let code = code.trim().to_string(); - let comment = comment.trim().to_string(); - Some((code, comment)) - }, - None => None - } - } else if line.contains("/*") { - let comment_start = line.split_once("/*"); - match comment_start { - Some((code, comment)) => { - let code = code.trim().to_string(); - let comment = comment.trim().strip_suffix("*/").unwrap_or("").to_string(); - Some((code, comment)) - }, - None => None - } - } else { - return None; - } - }; - - // 所有的 markdown - let mut comment_markdowns = Vec::::new(); - - for (line_no, line_text) in hover.iter().enumerate() { - let line_hover = if let Some(stripped) = line_text.strip_prefix(<rim) { - line_handler(stripped) - } else { - line_handler(&line_text) - }; - - if line_no == hover.len() - 1 { - // 最后一个为定义所在的一行 - if let Some((code, comment)) = line_comment_extractor(&line_text) { - comment_markdowns.push(MarkedString::LanguageString(LanguageString { - language: language_id.to_string(), - value: code - })); - comment_markdowns.insert(0, MarkedString::String(comment)); - } else { - // 这行只有代码,没有注释 - comment_markdowns.push(MarkedString::LanguageString(LanguageString { - language: language_id.to_string(), - value: line_hover - })); - } - } else { - // 否则,都是上方的注释 - comment_markdowns.push(MarkedString::String(line_hover)); - } - } - - if comment_markdowns.len() > 0 { - return Some(Hover { - contents: HoverContents::Array(comment_markdowns), - range: None - }); - } - - None -} - - - -/// 计算正常 symbol 的 hover -fn hover_common_symbol( - server: &LSPServer, - scope_tree: &GenericScope, - token: &String, - file: &RwLockReadGuard<'_, crate::sources::Source>, - doc: &Url, - pos: Position, - language_id: &String -) -> Option { - - let symbol_definition: GenericDec = scope_tree - .get_definition(token, file.text.pos_to_byte(&pos), doc)?; - - // 根据 symbol 的类别进行额外的判断 - - let def_line = file.text.byte_to_line(symbol_definition.byte_idx()); - make_hover_with_comment(&file.text, def_line, &language_id) -} - +} \ No newline at end of file diff --git a/src/hover/sv.rs b/src/hover/sv.rs new file mode 100644 index 0000000..994aa3c --- /dev/null +++ b/src/hover/sv.rs @@ -0,0 +1,214 @@ +use regex::Regex; +use ropey::Rope; +use tower_lsp::lsp_types::*; +use crate::{server::LSPServer, sources::LSPSupport}; +use super::feature::*; +use std::sync::RwLockReadGuard; + +use crate::definition::*; + +use super::{get_definition_token, get_language_id_by_uri}; + +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(); + server.srcs.wait_parse_ready(file_id, false); + let file: std::sync::Arc> = server.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) = hover_include(doc, &line_text, pos, &language_id) { + return Some(hover); + } + + // match macro + if let Some(hover) = hover_macro(server, &line_text, pos, &language_id) { + return Some(hover); + } + + let scope_tree = server.srcs.scope_tree.read().ok()?; + let global_scope = scope_tree.as_ref(); + + // match positional port param + if let Some(hover) = hover_position_port_param(server, &line_text, doc, pos, &language_id) { + return Some(hover); + } + + // match module name + if let Some(hover) = hover_module_declaration(server, &token, &language_id) { + return Some(hover); + } + + if let Some(global_scope) = global_scope { + + // match 正常 symbol + if let Some(hover) = hover_common_symbol(server, global_scope, &token, &file, doc, pos, &language_id) { + return Some(hover); + } + } + + // match digit 5'b00110 + if let Some(hover) = hover_format_digit(&line_text, pos, &language_id) { + return Some(hover); + } + + None +} + +fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str) -> Option { + if line == 0 { + let language_string = LanguageString { + language: language_id.to_string(), + value: doc.line(line).to_string() + }; + let markdown = MarkedString::LanguageString(language_string); + + return Some(Hover { + contents: HoverContents::Scalar(markdown), + range: None, + }); + } + 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; + + // 寻找周围的注释 + 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 multi_space_regex = Regex::new(r"\s+").unwrap(); + let line_handler = |line: &str| -> String { + let line = multi_space_regex.replace_all(line.trim(), " "); + let mut line = line.into_owned(); + + if line.starts_with("/*") { + line = line.strip_prefix("/*").unwrap().trim().to_string(); + } + + if line.ends_with("*/") { + line = line.strip_suffix("*/").unwrap().trim().to_string(); + } + + return format!("{}\n", line); + }; + + let line_comment_extractor = |line: &str| -> Option<(String, String)> { + let line = line.trim(); + if line.contains("//") { + let comment_start = line.split_once("//"); + match comment_start { + Some((code, comment)) => { + let code = code.trim().to_string(); + let comment = comment.trim().to_string(); + Some((code, comment)) + }, + None => None + } + } else if line.contains("/*") { + let comment_start = line.split_once("/*"); + match comment_start { + Some((code, comment)) => { + let code = code.trim().to_string(); + let comment = comment.trim().strip_suffix("*/").unwrap_or("").to_string(); + Some((code, comment)) + }, + None => None + } + } else { + return None; + } + }; + + // 所有的 markdown + let mut comment_markdowns = Vec::::new(); + + for (line_no, line_text) in hover.iter().enumerate() { + let line_hover = if let Some(stripped) = line_text.strip_prefix(<rim) { + line_handler(stripped) + } else { + line_handler(&line_text) + }; + + if line_no == hover.len() - 1 { + // 最后一个为定义所在的一行 + if let Some((code, comment)) = line_comment_extractor(&line_text) { + comment_markdowns.push(MarkedString::LanguageString(LanguageString { + language: language_id.to_string(), + value: code + })); + comment_markdowns.insert(0, MarkedString::String(comment)); + } else { + // 这行只有代码,没有注释 + comment_markdowns.push(MarkedString::LanguageString(LanguageString { + language: language_id.to_string(), + value: line_hover + })); + } + } else { + // 否则,都是上方的注释 + comment_markdowns.push(MarkedString::String(line_hover)); + } + } + + if comment_markdowns.len() > 0 { + return Some(Hover { + contents: HoverContents::Array(comment_markdowns), + range: None + }); + } + + None +} + + + +/// 计算正常 symbol 的 hover +fn hover_common_symbol( + #[allow(unused)] + server: &LSPServer, + scope_tree: &GenericScope, + token: &String, + file: &RwLockReadGuard<'_, crate::sources::Source>, + doc: &Url, + pos: Position, + language_id: &String +) -> Option { + + let symbol_definition: GenericDec = scope_tree + .get_definition(token, file.text.pos_to_byte(&pos), doc)?; + + // 根据 symbol 的类别进行额外的判断 + + let def_line = file.text.byte_to_line(symbol_definition.byte_idx()); + make_hover_with_comment(&file.text, def_line, &language_id) +} + diff --git a/src/hover/vhdl.rs b/src/hover/vhdl.rs index 3144ebf..24269b5 100644 --- a/src/hover/vhdl.rs +++ b/src/hover/vhdl.rs @@ -1,7 +1,7 @@ use tower_lsp::lsp_types::*; use crate::server::LSPServer; -pub fn hover_vhdl(server: &LSPServer, param: &DocumentSymbolParams) -> Option { +pub fn hover(server: &LSPServer, params: &HoverParams) -> Option { let design_file = server.srcs.design_file_map.write().unwrap();