diff --git a/src/completion/feature.rs b/src/completion/feature.rs new file mode 100644 index 0000000..07e15a2 --- /dev/null +++ b/src/completion/feature.rs @@ -0,0 +1,88 @@ +use std::{fs, path::PathBuf, str::FromStr}; + +use log::info; +use ropey::RopeSlice; +use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, CompletionList, Position, Url}; + +use crate::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(""); + 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 ..]; + } + + // 路径转换 + let path = match PathBuf::from_str(uri.path()) { + Ok(path) => path, + Err(error) => { + info!("error happen in : {:?}", error); + return None; + } + }; + let escape_path = to_escape_path(&path); + let escape_path = escape_path.to_str().unwrap_or(""); + if escape_path.len() == 0 { + return None; + } + + if let Some(abs_path) = resolve_path(escape_path, path_string) { + // 遍历该路径下所有文件和文件夹,并构建新的自动补全列表 + info!("completion active in {:?}", abs_path); + if abs_path.is_dir() { + let mut completion_items = Vec::::new(); + for entry in fs::read_dir(abs_path).unwrap() { + let entry = entry.unwrap(); + let path: PathBuf = entry.path(); + let file_name = match path.file_name() { + Some(os_str) => os_str.to_str(), + None => continue + }; + + if file_name.is_none() { + continue; + } + let file_name = file_name.unwrap(); + + if path.is_dir() { + completion_items.push(CompletionItem { + label: file_name.to_string(), + kind: Some(CompletionItemKind::FOLDER), + ..CompletionItem::default() + }); + } + + if path.is_file() { + completion_items.push(CompletionItem { + label: file_name.to_string(), + kind: Some(CompletionItemKind::FILE), + ..CompletionItem::default() + }); + } + } + + if !completion_items.is_empty() { + return Some(CompletionList { + is_incomplete: false, + items: completion_items + }); + } + } + } + } + } + None +} \ No newline at end of file diff --git a/src/completion/keyword.rs b/src/completion/keyword.rs index b2baa95..945d6b1 100644 --- a/src/completion/keyword.rs +++ b/src/completion/keyword.rs @@ -133,7 +133,7 @@ pub const KEYWORDS: &[(&str, &str)] = &[ ("implies", ""), ("import", ""), ("incdir", ""), - ("include", ""), + ("include", "`include \"$1\""), ("initial", ""), ("inout", ""), ("input", ""), diff --git a/src/completion/mod.rs b/src/completion/mod.rs index b697b5d..7cf1f44 100644 --- a/src/completion/mod.rs +++ b/src/completion/mod.rs @@ -1,13 +1,18 @@ use crate::server::LSPServer; use crate::sources::LSPSupport; +use feature::include_path_completion; +use log::info; use ropey::{Rope, RopeSlice}; use tower_lsp::lsp_types::*; pub mod keyword; +pub mod feature; 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)?; @@ -19,6 +24,7 @@ impl LSPServer { ); // 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 { @@ -37,6 +43,14 @@ impl LSPServer { 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, } } @@ -48,7 +62,6 @@ impl LSPServer { &doc.text_document.uri, )?; - // complete keywords comps.items.extend::>( self.key_comps @@ -100,6 +113,8 @@ impl LSPServer { } } + + /// get the previous non-whitespace character fn prev_char(text: &Rope, pos: &Position) -> char { let char_idx = text.pos_to_char(pos); diff --git a/src/definition/def_types.rs b/src/definition/def_types.rs index d70a176..0087c6e 100644 --- a/src/definition/def_types.rs +++ b/src/definition/def_types.rs @@ -1,4 +1,5 @@ use crate::sources::LSPSupport; +use log::info; use ropey::Rope; use tower_lsp::lsp_types::*; @@ -61,26 +62,25 @@ pub fn copy_scopes(scopes: &[Box]) -> Vec> { scope_decs } -/// A definition of any SystemVerilog variable or construct +/// 用于定义一个 Symbol 或者 Scope 的内部变量 pub trait Definition: std::fmt::Debug + Sync + Send { - // identifier + /// symbol 或者 scope 的名字,identity 的缩写 fn ident(&self) -> String; - // byte index in file of definition + /// 相对于文本的偏移量,可以利用 Rope 的 `byte_to_pos` 函数转换成 Position fn byte_idx(&self) -> usize; - // url pointing to the file the definition is in + /// 当前 symbol 所在文件的 url fn url(&self) -> Url; - // cleaned up text of the definition + /// 输出 Definition 的字符 fn type_str(&self) -> String; - // the kind of this definition, for use in completions + /// 类别,用于自动补全 fn completion_kind(&self) -> CompletionItemKind; - // the kind of this definition, for use in showing document symbols - // for some reason this kind is different than CompletionItemKind + /// 类别,用于文件大纲 fn symbol_kind(&self) -> SymbolKind; - // the kind of this definition, simplified for internal use + /// 类别,内部使用 fn def_type(&self) -> DefinitionType; - // whether the definition identifier starts with the given token + /// 等价于 self.ident.starts_with fn starts_with(&self, token: &str) -> bool; - // constructs the completion for this definition + /// 构造 completion 的 item fn completion(&self) -> CompletionItem; fn dot_completion(&self, scope_tree: &GenericScope) -> Vec; } @@ -173,17 +173,6 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send { Vec::new() } - /// 获取当前的 scope,如果返回 None,则说明当前 scope 为 global - // fn get_current_scope(&self, byte_idx: usize, url: &Url) -> Option> { - - // for scope in self.scopes() { - // if &scope.url() == url && scope.start() <= byte_idx && byte_idx <= scope.end() { - // return scope.get_current_scope(byte_idx, url); - // } - // } - // self - // } - /// 根据输入的 token,计算出这个 token 在 scope 中的定义 /// 比如输入 clock,则返回 clock 这个变量在哪里被定义,没有则返回 None /// - `token`: 需要查找定义的完整的单词 @@ -192,6 +181,7 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send { fn get_definition(&self, token: &str, byte_idx: usize, url: &Url) -> Option { let mut definition: Option = None; + // 递归进入最浅层的 for scope in self.scopes() { if &scope.url() == url && scope.start() <= byte_idx && byte_idx <= scope.end() { definition = scope.get_definition(token, byte_idx, url); @@ -203,6 +193,7 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send { // 优先找 定义,再找 scope for def in self.defs() { if def.ident() == token { + info!("find definition: {:?}", def); return Some(GenericDec { ident: def.ident(), byte_idx: def.byte_idx(), @@ -210,7 +201,7 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send { type_str: def.type_str(), completion_kind: def.completion_kind(), symbol_kind: def.symbol_kind(), - def_type: DefinitionType::Net, + def_type: def.def_type(), }); } } diff --git a/src/definition/feature.rs b/src/definition/feature.rs index 870c1a5..1a65ad2 100644 --- a/src/definition/feature.rs +++ b/src/definition/feature.rs @@ -107,5 +107,9 @@ pub fn goto_macro_definition(server: &LSPServer, line: &RopeSlice, pos: Position pub fn goto_instance_definition() { + +} + +pub fn goto_module_definition() { } \ No newline at end of file diff --git a/src/hover/feature.rs b/src/hover/feature.rs index 0b796b1..883daeb 100644 --- a/src/hover/feature.rs +++ b/src/hover/feature.rs @@ -73,7 +73,7 @@ fn convert_to_sign_unsign<'a>(tag: &'a str, digit_string: &str) -> Option<(Strin } /// 将 1'b1 翻译成 10进制 -pub fn match_format_digit(line: &RopeSlice, pos: Position, language_id: &str) -> Option { +pub fn hover_format_digit(line: &RopeSlice, pos: Position, language_id: &str) -> Option { let regex = Regex::new(r"[0-9'bho]").unwrap(); let token_result = get_word_range_at_position(line, pos, regex); diff --git a/src/hover/mod.rs b/src/hover/mod.rs index 9d5174d..17c8ed3 100644 --- a/src/hover/mod.rs +++ b/src/hover/mod.rs @@ -5,7 +5,7 @@ use crate::server::LSPServer; use crate::sources::LSPSupport; use crate::utils::*; use regex::Regex; -use ropey::Rope; +use ropey::{Rope, RopeSlice}; use tower_lsp::lsp_types::*; pub mod feature; @@ -40,13 +40,13 @@ impl LSPServer { if let Some(global_scope) = global_scope { // match 正常 symbol - if let Some(hover) = match_common_symbol(global_scope, &token, &file, &doc, pos, &language_id) { + if let Some(hover) = hover_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, &language_id) { + if let Some(hover) = hover_format_digit(&line_text, pos, &language_id) { return Some(hover); } @@ -57,10 +57,18 @@ impl LSPServer { -/// get the hover information -fn get_hover(doc: &Rope, line: usize) -> String { +fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str) -> Option { if line == 0 { - return doc.line(line).to_string(); + 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; @@ -97,24 +105,91 @@ fn get_hover(doc: &Rope, line: usize) -> String { 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 line = line.into_owned(); + 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 mut result: Vec = Vec::new(); - for line in hover { - if let Some(stripped) = line.strip_prefix(<rim) { - let line_hover = line_handler(stripped); - result.push(line_hover); + 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 { - let line_hover = line_handler(&line); - result.push(line_hover); + 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)); } } - result.join("").trim_end().to_owned() + + if comment_markdowns.len() > 0 { + return Some(Hover { + contents: HoverContents::Array(comment_markdowns), + range: None + }); + } + + None } -fn match_common_symbol( + + +/// 计算正常 symbol 的 hover +fn hover_common_symbol( scope_tree: &GenericScope, token: &String, file: &RwLockReadGuard<'_, crate::sources::Source>, @@ -123,18 +198,26 @@ fn match_common_symbol( language_id: &String ) -> Option { - let def: GenericDec = scope_tree + let symbol_definition: 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, - }) + // 根据 symbol 的类别进行额外的判断 + + + let def_line = file.text.byte_to_line(symbol_definition.byte_idx()); + make_hover_with_comment(&file.text, def_line, &language_id) } + +/// 计算 position 赋值的 port 或者 param +/// 比如 .clk ( clk ) 中的 . +fn hover_position_port(line: &RopeSlice, pos: Position, language_id: &str) -> Option { + let position_port_regex = Regex::new(r"[.0-9a-zA-Z]").unwrap(); + if let Some((port_name, range)) = get_word_range_at_position(line, pos, position_port_regex) { + if port_name.starts_with(".") { + let port_name = &port_name[1..]; + + } + } + + None +} \ No newline at end of file diff --git a/src/server.rs b/src/server.rs index 4c73dc7..8a10ae2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -196,6 +196,8 @@ impl LanguageServer for Backend { ".".to_string(), "$".to_string(), "`".to_string(), + "\"".to_string(), + "/".to_string() ]), work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None,