From c40e66f3dfbc556d000a0ffe3dd20b79bc1c9638 Mon Sep 17 00:00:00 2001 From: LSTM-Kirigaya <1193466151@qq.com> Date: Tue, 26 Nov 2024 16:59:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BA=86=20vhdl=20=E7=9A=84?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=A1=A5=E5=85=A8=EF=BC=88=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E8=AF=8D=E8=87=AA=E5=8A=A8=E8=A1=A5=E5=85=A8=20+=20=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E4=BE=8B=E5=8C=96=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/completion/keyword.rs | 102 ++++++++++++++++++++- src/completion/mod.rs | 1 - src/completion/sv.rs | 31 +++---- src/completion/vhdl.rs | 183 ++++++++++++++++++++++++++++++-------- src/hover/feature.rs | 44 +++++++++ src/server.rs | 6 +- 6 files changed, 310 insertions(+), 57 deletions(-) diff --git a/src/completion/keyword.rs b/src/completion/keyword.rs index 945d6b1..f9ff6f7 100644 --- a/src/completion/keyword.rs +++ b/src/completion/keyword.rs @@ -34,7 +34,7 @@ pub fn other_completions(tasks: &[&str]) -> Vec { .collect() } -pub const KEYWORDS: &[(&str, &str)] = &[ +pub const VLOG_KEYWORDS: &[(&str, &str)] = &[ ("accept_on", ""), ("alias", ""), ("always", "always @($1) begin\nend"), @@ -465,3 +465,103 @@ pub const DIRECTIVES: &[&str] = &[ "delay_mode_unit", "delay_mode_zero", ]; + +pub const VHDL_KEYWORDS: &[(&str, &str)] = &[ + ("abs", ""), + ("access", ""), + ("after", ""), + ("alias", "alias $1 is $2;"), + ("all", ""), + ("and", ""), + ("architecture", "architecture $1 of $2 is\nbegin\n\t$3\nend $1;"), + ("array", "array $1 is $2;"), + ("assert", "assert $1 report $2 severity $3;"), + ("attribute", "attribute $1 : $2;"), + ("begin", "begin\n\t$1\nend;"), + ("block", "block ($1) is\nbegin\n\t$2\nend block;"), + ("body", "body $1 is\nbegin\n\t$2\nend $1;"), + ("buffer", ""), + ("bus", ""), + ("case", "case $1 is\n\twhen $2 => $3;\nend case;"), + ("component", "component $1 is\n\tport (\n\t\t$2\n\t);\nend component;"), + ("configuration", "configuration $1 of $2 is\nfor $3\n\t$4\nend for;\nend $1;"), + ("constant", "constant $1 : $2 := $3;"), + ("disconnect", "disconnect $1 after $2;"), + ("downto", ""), + ("else", ""), + ("elsif", ""), + ("end", ""), + ("entity", "entity $1 is\n\tport (\n\t\t$2\n\t);\nend $1;"), + ("exit", "exit $1 when $2;"), + ("file", "file $1 : $2;"), + ("for", "for $1 in $2 loop\n\t$3\nend loop;"), + ("function", "function $1 return $2 is\nbegin\n\t$3\nend $1;"), + ("generate", "generate\n\t$1\nend generate;"), + ("generic", "generic (\n\t$1\n);"), + ("group", "group $1 : $2 ($3);"), + ("guarded", ""), + ("if", "if $1 then\n\t$2\nend if;"), + ("impure", ""), + ("in", ""), + ("inertial", ""), + ("inout", ""), + ("is", ""), + ("label", ""), + ("library", "library $1;"), + ("linkage", ""), + ("literal", ""), + ("loop", "loop\n\t$1\nend loop;"), + ("map", "map ($1 => $2);"), + ("mod", ""), + ("nand", ""), + ("new", ""), + ("next", "next $1 when $2;"), + ("nor", ""), + ("not", ""), + ("null", "null;"), + ("of", ""), + ("on", ""), + ("open", ""), + ("or", ""), + ("others", ""), + ("out", ""), + ("package", "package $1 is\n\t$2\nend $1;"), + ("port", "port (\n\t$1\n);"), + ("postponed", ""), + ("procedure", "procedure $1 is\nbegin\n\t$2\nend $1;"), + ("process", "process ($1) is\nbegin\n\t$2\nend process;"), + ("pure", ""), + ("range", ""), + ("record", "record\n\t$1\nend record;"), + ("register", ""), + ("reject", ""), + ("rem", ""), + ("report", "report $1 severity $2;"), + ("return", "return $1;"), + ("rol", ""), + ("ror", ""), + ("select", "select\n\t$1\nend select;"), + ("severity", ""), + ("signal", "signal $1 : $2 := $3;"), + ("shared", ""), + ("sla", ""), + ("sll", ""), + ("sra", ""), + ("srl", ""), + ("subtype", "subtype $1 is $2;"), + ("then", ""), + ("to", ""), + ("transport", ""), + ("type", "type $1 is $2;"), + ("unaffected", ""), + ("units", "units $1;\n\t$2\nend units;"), + ("until", ""), + ("use", "use $1;"), + ("variable", "variable $1 : $2 := $3;"), + ("wait", "wait on $1;"), + ("when", ""), + ("while", "while $1 loop\n\t$2\nend loop;"), + ("with", "with $1 select\n\t$2\nend select;"), + ("xnor", ""), + ("xor", ""), +]; \ No newline at end of file diff --git a/src/completion/mod.rs b/src/completion/mod.rs index 6f5ee52..ddaf741 100644 --- a/src/completion/mod.rs +++ b/src/completion/mod.rs @@ -6,7 +6,6 @@ pub mod feature; mod vhdl; mod sv; - impl LSPServer { pub fn completion(&self, params: CompletionParams) -> Option { let language_id = get_language_id_by_uri(¶ms.text_document_position.text_document.uri); diff --git a/src/completion/sv.rs b/src/completion/sv.rs index 2cb1ca6..6b5e2a2 100644 --- a/src/completion/sv.rs +++ b/src/completion/sv.rs @@ -59,7 +59,8 @@ pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option None, // 常规触发 CompletionTriggerKind::INVOKED => { - let mut comps = server.srcs.get_completions( + // 1. 先根据 AST 获取上下文补全项 + let mut completion_items = server.srcs.get_completions( &token, file.text.pos_to_byte(&doc.position), &doc.text_document.uri, @@ -67,25 +68,25 @@ pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option>( - server.key_comps + // 2. 根据 token 再加入关键词 + completion_items.items.extend::>( + server.vlog_keyword_completion_items .iter() .filter(|x| x.label.starts_with(&token)) .cloned() .collect(), ); - // 加入例化自动补全的 - comps.items.extend::>( + // 3. 加入例化自动补全的 + completion_items.items.extend::>( make_module_completions(server, &token, &language_id) ); - comps.items.dedup_by_key(|i| i.label.clone()); + completion_items.items.dedup_by_key(|i| i.label.clone()); - // info!("invoked return comps {:?}", comps); - Some(comps) + // info!("invoked return completion_items {:?}", completion_items); + Some(completion_items) } _ => None, }, @@ -106,23 +107,23 @@ pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option { - let mut comps = server.srcs.get_completions( + 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); + // info!("current completion token: {}", token); - comps.items.extend::>( - server.key_comps + completion_items.items.extend::>( + server.vlog_keyword_completion_items .iter() .filter(|x| x.label.starts_with(&token)) .cloned() .collect(), ); - comps.items.dedup_by_key(|i| i.label.clone()); + completion_items.items.dedup_by_key(|i| i.label.clone()); - Some(comps) + Some(completion_items) } } } diff --git a/src/completion/vhdl.rs b/src/completion/vhdl.rs index f69ff72..3264d79 100644 --- a/src/completion/vhdl.rs +++ b/src/completion/vhdl.rs @@ -2,16 +2,18 @@ use log::info; use ropey::{Rope, RopeSlice}; use tower_lsp::lsp_types::*; +use crate::hover::feature::make_vhdl_module_profile_code; #[allow(unused)] use crate::{server::LSPServer, sources::LSPSupport, utils::get_language_id_by_uri}; + + /// Called when the client requests a completion. /// This function looks in the source code to find suitable options and then returns them pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option { 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 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); @@ -24,55 +26,48 @@ pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option 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::TRIGGER_CHARACTER => { + let trigger_character = context.trigger_character.clone().unwrap(); + match trigger_character.as_str() { + // 按下 . 时需要触发的补全效果 + "." => { + info!("trigger vhdl dot completion"); + None + }, + _ => None, + } + } + CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None, + + // 一般情况下根据字符触发的补全项目 CompletionTriggerKind::INVOKED => { - let mut comps = server.srcs.get_completions( + // 1. 先根据 AST 获取上下文补全项 + let mut completion_items = server.srcs.get_completions( &token, file.text.pos_to_byte(&doc.position), &doc.text_document.uri, )?; - // complete keywords - comps.items.extend::>( - server.key_comps + // 2. 根据 token 再加入关键词 + completion_items.items.extend::>( + server.vhdl_keyword_completiom_items .iter() .filter(|x| x.label.starts_with(&token)) .cloned() .collect(), ); - comps.items.dedup_by_key(|i| i.label.clone()); - Some(comps) + + // 3. 加入例化自动补全的 + completion_items.items.extend::>( + make_module_completions(server, &token, &language_id) + ); + + // 去重 + completion_items.items.dedup_by_key(|i| i.label.to_string()); + Some(completion_items) } _ => None, }, @@ -99,7 +94,7 @@ pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option>( - server.key_comps + server.vhdl_keyword_completiom_items .iter() .filter(|x| x.label.starts_with(&token)) .cloned() @@ -181,3 +176,115 @@ fn get_completion_token(text: &Rope, line: RopeSlice, pos: Position) -> String { } } +fn make_module_completions( + server: &LSPServer, + token: &str, + language_id: &str +) -> Vec { + let mut module_completioms = Vec::::new(); + let hdl_param = server.srcs.hdl_param.clone(); + + let prefix = token.to_string().to_lowercase(); + let module_name_to_path = hdl_param.module_name_to_path.read().unwrap(); + + // 遍历 hdlparam 中所有的 modules,匹配符合 prefix 前缀的(不区分大小写) + for module_name in module_name_to_path.keys() { + if !module_name.to_string().to_lowercase().starts_with(&prefix) { + continue; + } + if let Some((module, file_type, def_path)) = hdl_param.find_module_context_by_name(&module_name) { + let mut insert_text = Vec::::new(); + + let (insert_text, module_profile, define_info) = if file_type == "primitives" { + // TODO: 支持原语 + continue; + } else { + insert_text.push(make_instantiation_code(&module)); + let path_uri = Url::from_file_path(def_path).unwrap().to_string(); + let def_row = module.range.start.line; + let def_col = module.range.start.character; + + ( + insert_text.join("\n"), + make_vhdl_module_profile_code(&module), + format!("Go to [Definition]({path_uri}#L{def_row}:{def_col})") + ) + }; + + let module_profile = MarkupContent { + kind: MarkupKind::Markdown, + value: format!("```{}\n{}\n```\n{}", language_id, module_profile, define_info) + }; + + let detail = format!("module instantiation ({})", file_type); + + let item = CompletionItem { + label: module.name.to_string(), + detail: Some(detail), + documentation: Some(Documentation::MarkupContent(module_profile)), + kind: Some(CompletionItemKind::CLASS), + insert_text: Some(insert_text), + // 给模块例化自动补全附上最高权重 + sort_text: Some("0001".to_string()), + insert_text_format: Some(InsertTextFormat::SNIPPET), + insert_text_mode: Some(InsertTextMode::ADJUST_INDENTATION), + ..CompletionItem::default() + }; + module_completioms.push(item); + } + } + + module_completioms +} + +/// 实现 vhdl 例化补全的代码片段 +fn make_instantiation_code(module: &crate::core::hdlparam::Module) -> String { + // TODO: 显性和隐性例化 + let mut snippet_codes = Vec::::new(); + let mut placeholder_id: u32 = 1; + + snippet_codes.push(format!("u_{} : {}\n", module.name, module.name)); + + // 2001 style,先计算出 generic 和 port,然后加入总体例化样板中 + let params_length = module.params.len(); + let ports_length = module.ports.len(); + + if params_length > 0 { + snippet_codes.push("generic map(\n".to_string()); + + let max_param_name = module.params.iter().map(|param| param.name.len()).max().unwrap_or(0); + let mut i: usize = 0; + for generic in &module.params { + let n_padding = " ".repeat(max_param_name - generic.name.len() + 1); + let placeholder_init = format!("${{{}:{}}}", placeholder_id, generic.init); + snippet_codes.push(format!("\t{}{} => {}", generic.name, n_padding, placeholder_init)); + placeholder_id += 1; + if i < params_length - 1 { + snippet_codes.push(",\n".to_string()); + } + i += 1; + } + snippet_codes.push(")\n".to_string()); + } + + if ports_length > 0 { + snippet_codes.push("port map(\n\t-- ports\n".to_string()); + + let max_port_name = module.ports.iter().map(|port| port.name.len()).max().unwrap_or(0); + let mut i: usize = 0; + + for port in &module.ports { + let n_padding = " ".repeat(max_port_name - port.name.len() + 1); + let placeholder_name = format!("${{{}:{}}}", placeholder_id, port.name); + snippet_codes.push(format!("\t{}{} => {}", port.name, n_padding, placeholder_name)); + placeholder_id += 1; + if i < ports_length - 1 { + snippet_codes.push(",\n".to_string()); + } + i += 1; + } + snippet_codes.push(");\n".to_string()); + } + + snippet_codes.join("") +} \ No newline at end of file diff --git a/src/hover/feature.rs b/src/hover/feature.rs index 2d1b556..009ba50 100644 --- a/src/hover/feature.rs +++ b/src/hover/feature.rs @@ -740,6 +740,50 @@ pub fn make_module_profile_code(module: &crate::core::hdlparam::Module) -> Strin profile_string } +pub fn make_vhdl_module_profile_code(module: &crate::core::hdlparam::Module) -> String { + let mut snippet_codes = Vec::::new(); + + snippet_codes.push(format!("u_{} : {}\n", module.name, module.name)); + + // 2001 style,先计算出 generic 和 port,然后加入总体例化样板中 + let params_length = module.params.len(); + let ports_length = module.ports.len(); + + if params_length > 0 { + snippet_codes.push("generic map(\n".to_string()); + + let max_param_name = module.params.iter().map(|param| param.name.len()).max().unwrap_or(0); + let mut i: usize = 0; + for generic in &module.params { + let n_padding = " ".repeat(max_param_name - generic.name.len() + 1); + snippet_codes.push(format!("\t{}{} => {}", generic.name, n_padding, generic.init)); + if i < params_length - 1 { + snippet_codes.push(",\n".to_string()); + } + i += 1; + } + snippet_codes.push(")\n".to_string()); + } + + if ports_length > 0 { + snippet_codes.push("port map(\n\t-- ports\n".to_string()); + + let max_port_name = module.ports.iter().map(|port| port.name.len()).max().unwrap_or(0); + let mut i: usize = 0; + + for port in &module.ports { + let n_padding = " ".repeat(max_port_name - port.name.len() + 1); + snippet_codes.push(format!("\t{}{} => {}", port.name, n_padding, port.name)); + if i < ports_length - 1 { + snippet_codes.push(",\n".to_string()); + } + i += 1; + } + snippet_codes.push(");\n".to_string()); + } + + snippet_codes.join("") +} /// vhdl 的 entity 的 profile pub fn make_entity_profile_code(module: &crate::core::hdlparam::Module) -> String { diff --git a/src/server.rs b/src/server.rs index 295b15f..a4c14c9 100644 --- a/src/server.rs +++ b/src/server.rs @@ -13,7 +13,8 @@ use tower_lsp::{Client, LanguageServer}; pub struct LSPServer { pub srcs: Sources, pub cache: CacheManager, - pub key_comps: Vec, + pub vlog_keyword_completion_items: Vec, + pub vhdl_keyword_completiom_items: Vec, pub sys_tasks: Vec, pub directives: Vec, pub conf: Arc>, @@ -28,7 +29,8 @@ impl LSPServer { LSPServer { srcs: Sources::new(), cache: CacheManager::new(dide_home), - key_comps: keyword_completions(KEYWORDS), + vlog_keyword_completion_items: keyword_completions(VLOG_KEYWORDS), + vhdl_keyword_completiom_items: keyword_completions(VHDL_KEYWORDS), sys_tasks: other_completions(SYS_TASKS), directives: other_completions(DIRECTIVES), conf: Arc::new(RwLock::new(ProjectConfig::default())),