From 20500a55cae1ad5b9ffe556eee61f0de0f24648e Mon Sep 17 00:00:00 2001 From: LSTM-Kirigaya <1193466151@qq.com> Date: Thu, 12 Dec 2024 22:09:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E5=8A=A0=E5=AE=8C=E5=96=84=E7=9A=84?= =?UTF-8?q?=E5=AE=8F=E7=9B=B8=E5=85=B3=E7=9A=84=E8=87=AA=E5=8A=A8=E8=A1=A5?= =?UTF-8?q?=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 4 +- src/completion/feature.rs | 42 ++++++++- src/completion/keyword.rs | 6 +- src/completion/sv.rs | 156 ++++++---------------------------- src/core/hdlparam.rs | 7 ++ src/core/scope_tree/common.rs | 2 + src/core/scope_tree/parse.rs | 5 ++ src/sources.rs | 2 +- src/test/mod.rs | 12 ++- src/utils/mod.rs | 35 +++++++- 10 files changed, 134 insertions(+), 137 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9e26dfe..5f799d9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "rust-analyzer.trace.server": "verbose" +} \ No newline at end of file diff --git a/src/completion/feature.rs b/src/completion/feature.rs index 1e29f03..bb7f1ce 100644 --- a/src/completion/feature.rs +++ b/src/completion/feature.rs @@ -4,9 +4,14 @@ 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::{is_character_ordered_match, resolve_path, to_escape_path}}; -pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Option { +/// 补全 include 内部的系统路径 +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; @@ -87,6 +92,39 @@ pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Op None } +/// 补全宏 +pub fn vlog_directives_completion( + token: &str, + server: &LspServer +) -> Option { + // 先把固定宏定义比如 include, define 这种的放入其中 + let mut completion_items = server.vlog_directives.clone(); + + // 再从每个文件中搜集定义的宏 + let hdl_param = server.srcs.hdl_param.clone(); + let path_to_hdl_file = hdl_param.path_to_hdl_file.read().unwrap(); + + // 遍历所有的 defines + for hdl_file in path_to_hdl_file.values() { + for define in &hdl_file.fast.fast_macro.defines { + if is_character_ordered_match(token, &define.name) { + completion_items.push(CompletionItem { + label: define.name.to_string(), + kind: Some(CompletionItemKind::CONSTANT), + // 用户定义的宏默认最高优先级 + sort_text: Some("0001".to_string()), + ..CompletionItem::default() + }); + } + } + } + + Some(CompletionList { + is_incomplete: false, + items: completion_items + }) +} + pub fn get_dot_completion( server: &LspServer, line: &RopeSlice, diff --git a/src/completion/keyword.rs b/src/completion/keyword.rs index 09acc90..09fc4f0 100644 --- a/src/completion/keyword.rs +++ b/src/completion/keyword.rs @@ -136,8 +136,8 @@ pub const VLOG_KEYWORDS: &[(&str, &str)] = &[ ("incdir", ""), ("include", "`include \"$1\""), ("initial", "initial begin\n\t$1\nend"), - ("inout", ""), - ("input", ""), + ("inout", "inout $1"), + ("input", "input $1"), ("inside", ""), ("instance", ""), ("int", ""), @@ -174,7 +174,7 @@ pub const VLOG_KEYWORDS: &[(&str, &str)] = &[ ("notif1", ""), ("null", ""), ("or", ""), - ("output", ""), + ("output", "output $1"), ("package", "package $1;\nendpackage"), ("packed", ""), ("parameter", ""), diff --git a/src/completion/sv.rs b/src/completion/sv.rs index 885dd00..023b999 100644 --- a/src/completion/sv.rs +++ b/src/completion/sv.rs @@ -1,8 +1,10 @@ -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_definition_token, get_language_id_by_uri, is_character_ordered_match}}; use log::info; use ropey::{Rope, RopeSlice}; use tower_lsp::lsp_types::*; +use super::feature::vlog_directives_completion; + pub fn completion(server: &LspServer, params: &CompletionParams) -> Option { let doc = ¶ms.text_document_position; @@ -15,46 +17,48 @@ pub fn completion(server: &LspServer, params: &CompletionParams) -> Option match context.trigger_kind { // 特殊字符触发 CompletionTriggerKind::TRIGGER_CHARACTER => { - // info!("trigger character"); + let trigger_character = context.trigger_character.clone().unwrap(); match trigger_character.as_str() { "." => { - info!("trigger dot completion"); + // 用户按下 . 如果是在例化 scope 中,则补全对应的 port get_dot_completion(server, &line_text, uri, &pos, &language_id) }, - "$" => Some(CompletionList { - is_incomplete: false, - items: server.vlog_sys_tasks_completion_items.clone(), - }), - "`" => Some(CompletionList { - is_incomplete: false, - items: server.vlog_directives.clone(), - }), + "$" => { + // 用户按下 $ 补全系统函数 + Some(CompletionList { + is_incomplete: false, + items: server.vlog_sys_tasks_completion_items.clone(), + }) + }, + "`" => { + // 用户按下 ` , 补全系统宏定义和用户自己的宏定义 + vlog_directives_completion(&token, server) + }, "/" => { - info!("trigger include"); + // 用户按下 / ,如果在 "" 内触发,路径的自动补全 include_path_completion(&doc.text_document.uri, &line_text, pos) }, "\"" => { - info!("trigger include"); + // 用户按下 " ,如果开头有 include,则自动补全 include_path_completion(&doc.text_document.uri, &line_text, pos) } _ => None, } } + + // 对于上一次自动补全结果 is_incomplete: true 的操作,还会额外触发一次自动补全, + // 它的逻辑在这个分支里面 CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None, + // 常规触发 CompletionTriggerKind::INVOKED => { // 1. 先根据 AST 获取上下文补全项 @@ -71,7 +75,7 @@ pub fn completion(server: &LspServer, params: &CompletionParams) -> Option>( server.vlog_keyword_completion_items .iter() - .filter(|x| x.label.starts_with(&token)) + .filter(|x| is_character_ordered_match(&token, &x.label)) .cloned() .collect(), ); @@ -81,7 +85,7 @@ pub fn completion(server: &LspServer, params: &CompletionParams) -> Option>( server.vlog_sys_tasks_completion_items .iter() - .filter(|x| x.label.starts_with(&token)) + .filter(|x| is_character_ordered_match(&token, &x.label)) .cloned() .collect(), ); @@ -91,126 +95,22 @@ pub fn completion(server: &LspServer, params: &CompletionParams) -> Option 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.vlog_sys_tasks_completion_items.clone(), - }), - '`' => Some(CompletionList { - is_incomplete: false, - items: server.vlog_directives.clone(), - }), - _ => { - let mut completion_items = server.srcs.get_completions( - &token, - file.text.pos_to_byte(&doc.position), - &doc.text_document.uri, - )?; - // info!("current completion token: {}", token); - - completion_items.items.extend::>( - server.vlog_keyword_completion_items - .iter() - .filter(|x| x.label.starts_with(&token)) - .cloned() - .collect(), - ); - completion_items.items.dedup_by_key(|i| i.label.clone()); - - Some(completion_items) - } - } + return None; } }; - // 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 - } -} - fn make_primitives_instantiation_code(text: &str) -> String { let mut instantiations = text.lines() .filter(|line| !line.trim().is_empty() && !line.trim().starts_with("//")) diff --git a/src/core/hdlparam.rs b/src/core/hdlparam.rs index ba810de..449c456 100644 --- a/src/core/hdlparam.rs +++ b/src/core/hdlparam.rs @@ -311,9 +311,16 @@ pub struct DefineParam { #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Define { + /// 宏的名字 + /// `define {name} {replacement} pub name: String, + /// 宏的名字 + /// `define {name} {replacement} pub replacement: String, + /// 宏的范围 pub range: Range, + /// 宏的参数(如果为函数宏才会有这个选项) + /// `define {name} {replacement}({...params}) pub params: Vec } diff --git a/src/core/scope_tree/common.rs b/src/core/scope_tree/common.rs index a3293bc..7c0a9e9 100644 --- a/src/core/scope_tree/common.rs +++ b/src/core/scope_tree/common.rs @@ -141,6 +141,7 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send { completions } + /// return a dot completion from the scope tree, this function should be called on the global /// scope fn get_dot_completion( @@ -176,6 +177,7 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send { Vec::new() } + /// 根据输入的 token,计算出这个 token 在 scope 中的定义 /// 比如输入 clock,则返回 clock 这个变量在哪里被定义,没有则返回 None /// - `token`: 需要查找定义的完整的单词 diff --git a/src/core/scope_tree/parse.rs b/src/core/scope_tree/parse.rs index d8ea885..76b17e9 100644 --- a/src/core/scope_tree/parse.rs +++ b/src/core/scope_tree/parse.rs @@ -1,6 +1,7 @@ use super::common::*; use super::match_definitions; +use log::info; use sv_parser::*; use tower_lsp::lsp_types::*; @@ -45,6 +46,8 @@ macro_rules! advance_until_leave { }}; } +/// 遍历 $tree,直到找到 $node +/// 中间遍历遇到的所有 node 的字面量会被存储进入 $tokens 中 macro_rules! advance_until_enter { ($tokens:ident, $tree:ident, $event_iter:ident, $node:path, $type:ty) => {{ let mut result: Option<$type> = None; @@ -2352,6 +2355,8 @@ pub fn text_macro_def( &TextMacroIdentifier ); + // 最终渲染的基本字面量 + text_macro.type_str = "`define".to_string(); // 自动补全用的 text_macro.completion_kind = CompletionItemKind::CONSTANT; // document 用的 diff --git a/src/sources.rs b/src/sources.rs index 632f138..2557df2 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -458,7 +458,7 @@ impl Sources { *self.names.read().unwrap().get(uri).unwrap() } - /// compute identifier completions + /// 根据输入 token,计算当前 pub fn get_completions( &self, token: &str, diff --git a/src/test/mod.rs b/src/test/mod.rs index 3d8c7ab..896a358 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -449,7 +449,7 @@ mod test_scope_tree { mod test_file { use std::fs; - use crate::utils::{file_size_in_kb, get_language_id_by_pathbuf, RecursiveFileIterator}; + use crate::utils::{file_size_in_kb, get_language_id_by_pathbuf, is_character_ordered_match, RecursiveFileIterator}; use super::*; #[test] @@ -476,4 +476,14 @@ mod test_file { fn test_cache() { let _ = fs::create_dir_all("/home/dide/project/digital-lsp-server/.cache"); } + + #[test] + fn test_utils() { + println!("enter function"); + assert!(is_character_ordered_match("parm", "param")); + assert!(is_character_ordered_match("param", "param")); + assert!(is_character_ordered_match("prm", "PARAM")); + assert!(is_character_ordered_match("car", "careful")); + assert!(!is_character_ordered_match("suprt", "super")); + } } \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 081efae..be9429a 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -83,6 +83,7 @@ pub fn get_word_range_at_position(line: &RopeSlice, pos: Position, regex: Regex) /// 根据 uri 获取 hdl 的 language id /// 返回值为 "vhdl" | "verilog" | "systemverilog" | "plaintext" /// 不采用枚举是因为需要在 lsp 中使用到它们的字符串值 +#[allow(unused)] pub fn get_language_id_by_uri(uri: &Url) -> String { let path = uri.path(); let ext_name = std::path::Path::new(path) @@ -96,6 +97,7 @@ pub fn get_language_id_by_uri(uri: &Url) -> String { /// 根据路径字符串获取 hdl 的 language id /// 返回值为 "vhdl" | "verilog" | "systemverilog" | "plaintext" /// 不采用枚举是因为需要在 lsp 中使用到它们的字符串值 +#[allow(unused)] pub fn get_language_id_by_pathbuf(pathbuf: &PathBuf) -> String { let ext_name = pathbuf.as_path() .extension() @@ -107,6 +109,7 @@ pub fn get_language_id_by_pathbuf(pathbuf: &PathBuf) -> String { /// 根据路径字符串获取 hdl 的 language id /// 返回值为 "vhdl" | "verilog" | "systemverilog" | "plaintext" /// 不采用枚举是因为需要在 lsp 中使用到它们的字符串值 +#[allow(unused)] pub fn get_language_id_by_path_str(path_str: &str) -> String { let ext_name = std::path::Path::new(path_str) .extension() @@ -337,4 +340,34 @@ impl BracketMatcher { BracketMatchResult::Valid } -} \ No newline at end of file +} + +/// 基于字符顺序判断是否匹配 +/// 检查是否可以从 `candidate` 中提取出与 `input` 顺序一致的字符 +/// input 忽略大小写 +pub fn is_character_ordered_match( + input: &str, + candidate: &str +) -> bool { + let mut input_chars = input.chars().peekable(); + let mut candidate_chars = candidate.chars().peekable(); + + while let Some(input_char) = input_chars.next() { + // 在 candidate 中找到与 input_char 匹配的字符 + let mut matched = false; + while let Some(candidate_char) = candidate_chars.next() { + if candidate_char.to_lowercase().eq(input_char.to_lowercase()) { + matched = true; + break; + } + } + + // 如果未找到匹配的字符,返回 false + if !matched { + return false; + } + } + + // 如果 input 的所有字符都匹配完成,返回 true + true +}