use crate::{completion::feature::{get_dot_completion, include_path_completion}, core, hover::feature::make_module_profile_code, server::LspServer, sources::LSPSupport, utils::{from_uri_to_escape_path_string, get_definition_token, get_language_id_by_uri, is_character_ordered_match}}; #[allow(unused)] use log::info; use tower_lsp::lsp_types::*; use super::feature::{vlog_directives_completion, vlog_directives_completion_without_prefix}; 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 path_string = from_uri_to_escape_path_string(uri).unwrap(); // 等待解析完成 server.db.wait_parse_ready(&path_string, false); let source = server.db.get_source(&path_string)?; let source = source.read().ok()?; // 获取当前这行的结果和当前光标所在的 token let line_text = source.text.line(doc.position.line as usize); let token = get_definition_token(&line_text, doc.position); 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() { "." => { // 用户按下 . 如果是在例化 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(), }) }, "`" => { // 用户按下 ` , 补全系统宏定义和用户自己的宏定义 vlog_directives_completion(&token, server) }, "/" => { // 用户按下 / ,如果在 "" 内触发,路径的自动补全 include_path_completion(&doc.text_document.uri, &line_text, pos) }, "\"" => { // 用户按下 " ,如果开头有 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 获取上下文补全项 // 去除如下几种情况:module textmacro let mut completion_items = server.db.get_completions( &token, source.text.pos_to_byte(&doc.position), &doc.text_document.uri, )?; // info!("current completion token: {}", token); // 2. 根据 token 加入关键词 // TODO: 考虑使用前缀树进行优化 completion_items.items.extend::>( server.vlog_keyword_completion_items .iter() .filter(|x| is_character_ordered_match(&token, &x.label)) .cloned() .collect(), ); // 3. 根据 token 加入系统函数 // TODO: 考虑使用前缀树进行优化 completion_items.items.extend::>( server.vlog_sys_tasks_completion_items .iter() .filter(|x| is_character_ordered_match(&token, &x.label)) .cloned() .collect(), ); // 4. 加入例化自动补全的 completion_items.items.extend::>( make_module_completions(server, &token, &language_id) ); // 5. 加入宏的自动补全 completion_items.items.extend::>( vlog_directives_completion_without_prefix(&token, server) ); // 不知道为什么会有重复,去重就完事 completion_items.items.dedup_by_key(|i| i.label.clone()); // info!("invoked return completion_items {:?}", completion_items); Some(completion_items) } _ => None, }, None => { return None; } }; Some(CompletionResponse::List(response?)) } fn make_primitives_instantiation_code(text: &str) -> String { let mut instantiations = text.lines() .filter(|line| !line.trim().is_empty() && !line.trim().starts_with("//")) .collect::>(); // remove fake module and endmodule instantiations.remove(0); instantiations.pop(); instantiations.iter().map(|line| { let trimmed = line.trim_start(); if trimmed.contains('.') { format!("\t{}", trimmed) } else { trimmed.to_string() } }).collect::>().join("\n") } fn make_primitives_module_profile_code(text: &str) -> String { let mut lines: Vec<&str> = text.split_inclusive('\n').collect(); if lines.len() > 1 { lines.remove(0); lines.pop(); } lines.join("") } fn make_instantiation_code(module: &crate::core::hdlparam::Module) -> String { // makeVlogParamAssignments 参数信息列表 let mut snippet_codes = Vec::::new(); let mut placeholder_id: u32 = 1; snippet_codes.push(format!("{} ", module.name)); let params_length = module.params.len(); let ports_length = module.ports.len(); if params_length > 0 { snippet_codes.push("#(\n".to_string()); let max_name_length = module.params.iter().map(|param| param.name.len()).max().unwrap_or(0); let max_init_length = module.params.iter().map(|param| param.init.len()).max().unwrap_or(0); let mut i: usize = 0; for param in &module.params { // 写入 .NAME ( INIT ), let padding_name = format!("{}{}", param.name, " ".repeat(max_name_length - param.name.len() + 1)); snippet_codes.push(format!("\t.{}\t(", padding_name)); snippet_codes.push(format!("${{{}:{}}}", placeholder_id, param.init)); placeholder_id += 1; snippet_codes.push(format!("{} )", " ".repeat(max_init_length - param.init.len() + 1))); if i < params_length - 1 { snippet_codes.push(",\n".to_string()); } i += 1; } snippet_codes.push(")\n".to_string()); } snippet_codes.push(format!("u_{}", module.name)); if ports_length > 0 { let max_name_length = module.ports.iter().map(|port| port.name.len()).max().unwrap_or(0); snippet_codes.push("(\n".to_string()); let mut i: usize = 0; for port in &module.ports { let padding_name = format!("{}{}", port.name, " ".repeat(max_name_length - port.name.len() + 1)); snippet_codes.push(format!("\t.{}\t(", padding_name)); snippet_codes.push(format!("${{{}:{}}}", placeholder_id, port.name)); placeholder_id += 1; snippet_codes.push(format!("{} )", " ".repeat(max_name_length - port.name.len() + 1))); if i < ports_length - 1 { snippet_codes.push(",".to_string()); } snippet_codes.push("\n".to_string()); i += 1; } snippet_codes.push(");\n".to_string()); } snippet_codes.join("") } /// 自动补全例化模块 fn make_module_completions( server: &LspServer, token: &str, language_id: &str ) -> Vec { let mut module_completioms = Vec::::new(); let hdl_param = server.db.hdl_param.clone(); let prefix = token.to_string().to_lowercase(); let module_name_to_path = hdl_param.module_name_to_path.read().unwrap(); // 获取和自动补全相关的配置 let auto_add_output_declaration = server.db.get_lsp_configuration_bool_value("digital-ide.function.lsp.completion.vlog.auto-add-output-declaration").unwrap_or(true); // 遍历 hdlparam 中所有的 modules 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(); if auto_add_output_declaration { if let Some(declaration_string) = make_output_declaration(&module) { insert_text.push(declaration_string); } } let (insert_text, module_profile, define_info) = if file_type == "primitives" { let primitive_map = server.db.primitive_text.name_to_text.read().unwrap(); if let Some(text) = primitive_map.get(&module.name) { insert_text.push(make_primitives_instantiation_code(text)); ( insert_text.join("\n"), make_primitives_module_profile_code(text), format!("[Definition] Primitive module: {}", module.name) ) } else { 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_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 label_details = CompletionItemLabelDetails { description: Some("module instantiation".to_string()), ..Default::default() }; let item = CompletionItem { label: module.name.to_string(), detail: Some(detail), label_details: Some(label_details), documentation: Some(Documentation::MarkupContent(module_profile)), kind: Some(CompletionItemKind::CLASS), insert_text: Some(insert_text), // 给模块例化自动补全附上最高权重 sort_text: Some("0".to_string()), insert_text_format: Some(InsertTextFormat::SNIPPET), insert_text_mode: Some(InsertTextMode::ADJUST_INDENTATION), ..CompletionItem::default() }; module_completioms.push(item); } } module_completioms } fn make_output_declaration( module: &core::hdlparam::Module ) -> Option { let mut output_declaration = Vec::::new(); for port in &module.ports { if port.dir_type == "output" || port.dir_type == "out" { let mut declaration = Vec::::new(); if port.net_type == "reg" || port.net_type == "wire" { declaration.push(port.net_type.to_string()); } else { declaration.push("wire".to_string()); } if port.width != "1" { declaration.push(port.width.to_string()); } declaration.push(port.name.to_string()); output_declaration.push(format!("{};", declaration.join(" "))); } } if output_declaration.len() > 0 { let output_declaration = format!("// output declaration of module {}\n{}\n", module.name, output_declaration.join("\n")); Some(output_declaration) } else { None } }