323 lines
13 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<CompletionResponse> {
let doc = &params.text_document_position;
let uri = &params.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 &params.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::<Vec<CompletionItem>>(
server.vlog_keyword_completion_items
.iter()
.filter(|x| is_character_ordered_match(&token, &x.label))
.cloned()
.collect(),
);
// 3. 根据 token 加入系统函数
// TODO: 考虑使用前缀树进行优化
completion_items.items.extend::<Vec<CompletionItem>>(
server.vlog_sys_tasks_completion_items
.iter()
.filter(|x| is_character_ordered_match(&token, &x.label))
.cloned()
.collect(),
);
// 4. 加入例化自动补全的
completion_items.items.extend::<Vec<CompletionItem>>(
make_module_completions(server, &token, &language_id)
);
// 5. 加入宏的自动补全
completion_items.items.extend::<Vec<CompletionItem>>(
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::<Vec<&str>>();
// 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::<Vec<String>>().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::<String>::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<CompletionItem> {
let mut module_completioms = Vec::<CompletionItem>::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::<String>::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<String> {
let mut output_declaration = Vec::<String>::new();
for port in &module.ports {
if port.dir_type == "output" || port.dir_type == "out" {
let mut declaration = Vec::<String>::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
}
}