完成了 vhdl 的自动补全(关键词自动补全 + 自动例化)

This commit is contained in:
锦恢 2024-11-26 16:59:15 +08:00
parent 3f9d5ff1cc
commit c40e66f3df
6 changed files with 310 additions and 57 deletions

View File

@ -34,7 +34,7 @@ pub fn other_completions(tasks: &[&str]) -> Vec<CompletionItem> {
.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", ""),
];

View File

@ -6,7 +6,6 @@ pub mod feature;
mod vhdl;
mod sv;
impl LSPServer {
pub fn completion(&self, params: CompletionParams) -> Option<CompletionResponse> {
let language_id = get_language_id_by_uri(&params.text_document_position.text_document.uri);

View File

@ -59,7 +59,8 @@ pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option<Compl
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,
@ -67,25 +68,25 @@ pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option<Compl
info!("current completion token: {}", token);
// complete keywords
comps.items.extend::<Vec<CompletionItem>>(
server.key_comps
// 2. 根据 token 再加入关键词
completion_items.items.extend::<Vec<CompletionItem>>(
server.vlog_keyword_completion_items
.iter()
.filter(|x| x.label.starts_with(&token))
.cloned()
.collect(),
);
// 加入例化自动补全的
comps.items.extend::<Vec<CompletionItem>>(
// 3. 加入例化自动补全的
completion_items.items.extend::<Vec<CompletionItem>>(
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<Compl
items: server.directives.clone(),
}),
_ => {
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::<Vec<CompletionItem>>(
server.key_comps
completion_items.items.extend::<Vec<CompletionItem>>(
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)
}
}
}

View File

@ -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<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 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<Compl
doc.position,
);
// info!("trigger completion token: {}", token);
// let line_text = file.text.line(pos.line as usize);
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() {
// "." => {
// 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::<Vec<CompletionItem>>(
server.key_comps
// 2. 根据 token 再加入关键词
completion_items.items.extend::<Vec<CompletionItem>>(
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::<Vec<CompletionItem>>(
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<Compl
&doc.text_document.uri,
)?;
comps.items.extend::<Vec<CompletionItem>>(
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<CompletionItem> {
let mut module_completioms = Vec::<CompletionItem>::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::<String>::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::<String>::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("")
}

View File

@ -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::<String>::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 {

View File

@ -13,7 +13,8 @@ use tower_lsp::{Client, LanguageServer};
pub struct LSPServer {
pub srcs: Sources,
pub cache: CacheManager,
pub key_comps: Vec<CompletionItem>,
pub vlog_keyword_completion_items: Vec<CompletionItem>,
pub vhdl_keyword_completiom_items: Vec<CompletionItem>,
pub sys_tasks: Vec<CompletionItem>,
pub directives: Vec<CompletionItem>,
pub conf: Arc<RwLock<ProjectConfig>>,
@ -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())),