add vhdl completion
This commit is contained in:
parent
ed5782bad5
commit
699e4c1d1e
@ -0,0 +1,234 @@
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use log::info;
|
||||
use tower_lsp::lsp_types::*;
|
||||
use vhdl_lang::{ast::{Designator, ObjectClass}, kind_str, AnyEntKind, Design, EntRef, InterfaceEnt, Overloaded};
|
||||
use crate::{server::LSPServer, utils::{from_lsp_pos, to_escape_path}};
|
||||
|
||||
/// Called when the client requests a completion.
|
||||
/// This function looks in the source code to find suitable options and then returns them
|
||||
pub fn request_completion(server: &LSPServer, params: &CompletionParams) -> Option<CompletionList> {
|
||||
let doc = ¶ms.text_document_position;
|
||||
// let pos = doc.position;
|
||||
|
||||
let file_id = server.srcs.get_id(&doc.text_document.uri).to_owned();
|
||||
server.srcs.wait_parse_ready(file_id, false);
|
||||
let projects = server.srcs.design_file_map.read().ok()?;
|
||||
|
||||
let path = match PathBuf::from_str(&doc.text_document.uri.path()) {
|
||||
Ok(path) => path,
|
||||
Err(error) => {
|
||||
info!("error happen in <goto_include_definition>: {:?}", error);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let escape_path = to_escape_path(&path);
|
||||
let escape_path_string = escape_path.to_str().unwrap_or("");
|
||||
if escape_path_string.len() == 0 {
|
||||
info!("error happen in [vhdl_parser_pipeline], escape_path_string is empty");
|
||||
return None;
|
||||
}
|
||||
let project = match projects.get(escape_path_string) {
|
||||
Some(project) => project,
|
||||
None => return None
|
||||
};
|
||||
|
||||
let source = project.get_source(&escape_path)?;
|
||||
|
||||
let binding = escape_path;
|
||||
let file = binding.as_path();
|
||||
// 1) get source position, and source file
|
||||
let Some(source) = project.get_source(file) else {
|
||||
// Do not enable completions for files that are not part of the project
|
||||
return Some(CompletionList {
|
||||
..Default::default()
|
||||
});
|
||||
};
|
||||
let cursor = from_lsp_pos(params.text_document_position.position);
|
||||
// 2) Optimization chance: go to last recognizable token before the cursor. For example:
|
||||
// - Any primary unit (e.g. entity declaration, package declaration, ...)
|
||||
// => keyword `entity`, `package`, ...
|
||||
// - Any secondary unit (e.g. package body, architecture)
|
||||
// => keyword `architecture`, ...
|
||||
|
||||
// 3) Run the parser until the point of the cursor. Then exit with possible completions
|
||||
let options = project
|
||||
.list_completion_options(&source, cursor)
|
||||
.into_iter()
|
||||
.map(|item| completion_item_to_lsp_item(server, item))
|
||||
.collect();
|
||||
|
||||
Some(CompletionList {
|
||||
items: options,
|
||||
is_incomplete: true,
|
||||
})
|
||||
}
|
||||
|
||||
fn completion_item_to_lsp_item(
|
||||
server: &LSPServer,
|
||||
item: vhdl_lang::CompletionItem,
|
||||
) -> CompletionItem {
|
||||
match item {
|
||||
vhdl_lang::CompletionItem::Simple(ent) => entity_to_completion_item(ent),
|
||||
vhdl_lang::CompletionItem::Work => CompletionItem {
|
||||
label: "work".to_string(),
|
||||
detail: Some("work library".to_string()),
|
||||
kind: Some(CompletionItemKind::MODULE),
|
||||
insert_text: Some("work".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
vhdl_lang::CompletionItem::Formal(ent) => {
|
||||
let mut item = entity_to_completion_item(ent);
|
||||
// TODO: Confirm vscode support
|
||||
// if server.client_supports_snippets() {
|
||||
item.insert_text_format = Some(InsertTextFormat::SNIPPET);
|
||||
item.insert_text = Some(format!("{} => $1,", item.insert_text.unwrap()));
|
||||
// }
|
||||
item
|
||||
}
|
||||
vhdl_lang::CompletionItem::Overloaded(desi, count) => CompletionItem {
|
||||
label: desi.to_string(),
|
||||
detail: Some(format!("+{count} overloaded")),
|
||||
kind: match desi {
|
||||
Designator::Identifier(_) => Some(CompletionItemKind::FUNCTION),
|
||||
Designator::OperatorSymbol(_) => Some(CompletionItemKind::OPERATOR),
|
||||
_ => None,
|
||||
},
|
||||
insert_text: Some(desi.to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
vhdl_lang::CompletionItem::Keyword(kind) => CompletionItem {
|
||||
label: kind_str(kind).to_string(),
|
||||
detail: Some(kind_str(kind).to_string()),
|
||||
insert_text: Some(kind_str(kind).to_string()),
|
||||
kind: Some(CompletionItemKind::KEYWORD),
|
||||
..Default::default()
|
||||
},
|
||||
vhdl_lang::CompletionItem::Instantiation(ent, architectures) => {
|
||||
let work_name = "work";
|
||||
|
||||
let library_names = if let Some(lib_name) = ent.library_name() {
|
||||
vec![work_name.to_string(), lib_name.name().to_string()]
|
||||
} else {
|
||||
vec![work_name.to_string()]
|
||||
};
|
||||
let (region, is_component_instantiation) = match ent.kind() {
|
||||
AnyEntKind::Design(Design::Entity(_, region)) => (region, false),
|
||||
AnyEntKind::Component(region) => (region, true),
|
||||
// should never happen but better return some value instead of crashing
|
||||
_ => return entity_to_completion_item(ent),
|
||||
};
|
||||
// TODO: Confirm vscode support
|
||||
let template = if true { // if server.client_supports_snippets() {
|
||||
let mut line = if is_component_instantiation {
|
||||
format!("${{1:{}_inst}}: {}", ent.designator, ent.designator)
|
||||
} else {
|
||||
format!(
|
||||
"${{1:{}_inst}}: entity ${{2|{}|}}.{}",
|
||||
ent.designator,
|
||||
library_names.join(","),
|
||||
ent.designator
|
||||
)
|
||||
};
|
||||
if architectures.len() > 1 {
|
||||
line.push_str("(${3|");
|
||||
for (i, architecture) in architectures.iter().enumerate() {
|
||||
line.push_str(&architecture.designator().to_string());
|
||||
if i != architectures.len() - 1 {
|
||||
line.push(',')
|
||||
}
|
||||
}
|
||||
line.push_str("|})");
|
||||
}
|
||||
let (ports, generics) = region.ports_and_generics();
|
||||
let mut idx = 4;
|
||||
let mut interface_ent = |elements: Vec<InterfaceEnt>, purpose: &str| {
|
||||
line += &*format!("\n {} map(\n", purpose);
|
||||
for (i, generic) in elements.iter().enumerate() {
|
||||
line += &*format!(
|
||||
" {} => ${{{}:{}}}",
|
||||
generic.designator, idx, generic.designator
|
||||
);
|
||||
idx += 1;
|
||||
if i != elements.len() - 1 {
|
||||
line += ","
|
||||
}
|
||||
line += "\n";
|
||||
}
|
||||
line += ")";
|
||||
};
|
||||
if !generics.is_empty() {
|
||||
interface_ent(generics, "generic");
|
||||
}
|
||||
if !ports.is_empty() {
|
||||
interface_ent(ports, "port");
|
||||
}
|
||||
line += ";";
|
||||
line
|
||||
} else {
|
||||
format!("{}", ent.designator)
|
||||
};
|
||||
CompletionItem {
|
||||
label: format!("{} instantiation", ent.designator),
|
||||
insert_text: Some(template),
|
||||
insert_text_format: Some(InsertTextFormat::SNIPPET),
|
||||
kind: Some(CompletionItemKind::MODULE),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
vhdl_lang::CompletionItem::Attribute(attribute) => CompletionItem {
|
||||
label: format!("{attribute}"),
|
||||
detail: Some(format!("{attribute}")),
|
||||
insert_text: Some(format!("{attribute}")),
|
||||
kind: Some(CompletionItemKind::REFERENCE),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn entity_to_completion_item(ent: EntRef) -> CompletionItem {
|
||||
CompletionItem {
|
||||
label: ent.designator.to_string(),
|
||||
detail: Some(ent.describe()),
|
||||
kind: Some(entity_kind_to_completion_kind(ent.kind())),
|
||||
data: serde_json::to_value(ent.id.to_raw()).ok(),
|
||||
insert_text: Some(ent.designator.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn entity_kind_to_completion_kind(kind: &AnyEntKind) -> CompletionItemKind {
|
||||
match kind {
|
||||
AnyEntKind::ExternalAlias { .. } | AnyEntKind::ObjectAlias { .. } => {
|
||||
CompletionItemKind::FIELD
|
||||
}
|
||||
AnyEntKind::File(_) | AnyEntKind::InterfaceFile(_) => CompletionItemKind::FILE,
|
||||
AnyEntKind::Component(_) => CompletionItemKind::MODULE,
|
||||
AnyEntKind::Attribute(_) => CompletionItemKind::REFERENCE,
|
||||
AnyEntKind::Overloaded(overloaded) => match overloaded {
|
||||
Overloaded::SubprogramDecl(_)
|
||||
| Overloaded::Subprogram(_)
|
||||
| Overloaded::UninstSubprogramDecl(..)
|
||||
| Overloaded::UninstSubprogram(..)
|
||||
| Overloaded::InterfaceSubprogram(_) => CompletionItemKind::FUNCTION,
|
||||
Overloaded::EnumLiteral(_) => CompletionItemKind::ENUM_MEMBER,
|
||||
Overloaded::Alias(_) => CompletionItemKind::FIELD,
|
||||
},
|
||||
AnyEntKind::Type(_) => CompletionItemKind::TYPE_PARAMETER,
|
||||
AnyEntKind::ElementDeclaration(_) => CompletionItemKind::FIELD,
|
||||
AnyEntKind::Concurrent(_) => CompletionItemKind::MODULE,
|
||||
AnyEntKind::Sequential(_) => CompletionItemKind::MODULE,
|
||||
AnyEntKind::Object(object) => match object.class {
|
||||
ObjectClass::Signal => CompletionItemKind::EVENT,
|
||||
ObjectClass::Constant => CompletionItemKind::CONSTANT,
|
||||
ObjectClass::Variable | ObjectClass::SharedVariable => CompletionItemKind::VARIABLE,
|
||||
},
|
||||
AnyEntKind::LoopParameter(_) => CompletionItemKind::MODULE,
|
||||
AnyEntKind::PhysicalLiteral(_) => CompletionItemKind::UNIT,
|
||||
AnyEntKind::DeferredConstant(_) => CompletionItemKind::CONSTANT,
|
||||
AnyEntKind::Library => CompletionItemKind::MODULE,
|
||||
AnyEntKind::Design(_) => CompletionItemKind::MODULE,
|
||||
AnyEntKind::View(_) => CompletionItemKind::INTERFACE,
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user