use std::collections::HashSet; 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 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 => { // info!("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.vlog_sys_tasks_completion_items.clone(), }), "`" => Some(CompletionList { is_incomplete: false, items: server.vlog_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 => { // 1. 先根据 AST 获取上下文补全项 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); // 2. 根据 token 加入关键词 // TODO: 考虑使用前缀树进行优化 completion_items.items.extend::>( server.vlog_keyword_completion_items .iter() .filter(|x| x.label.starts_with(&token)) .cloned() .collect(), ); // 3. 根据 token 加入系统函数 // TODO: 考虑使用前缀树进行优化 completion_items.items.extend::>( server.vlog_sys_tasks_completion_items .iter() .filter(|x| x.label.starts_with(&token)) .cloned() .collect(), ); // 4. 加入例化自动补全的 completion_items.items.extend::>( make_module_completions(server, &token, &language_id) ); completion_items.items.dedup_by_key(|i| i.label.clone()); // info!("invoked return completion_items {:?}", completion_items); Some(completion_items) } _ => 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) } } } }; // 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("//")) .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.srcs.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.srcs.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.srcs.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 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 } 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 } }