完成了 vhdl 的自动补全(关键词自动补全 + 自动例化)
This commit is contained in:
parent
3f9d5ff1cc
commit
c40e66f3df
@ -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", ""),
|
||||
];
|
@ -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(¶ms.text_document_position.text_document.uri);
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 = ¶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<Compl
|
||||
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 => {
|
||||
// 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("")
|
||||
}
|
@ -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 {
|
||||
|
@ -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())),
|
||||
|
Loading…
x
Reference in New Issue
Block a user