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