More elaborate completions (#205)

* Implement more complex completion item

* Add more completion kinds

* Add support for snippets

* clippy

* remove non rendered text

* Add primitive overload resolution

* clippy

* improve overloaded format

* Re-add detail to standard completion item

* Make label and insertion text different

* fmt

* fix tests

* Remove items method and make local arena private again

* Refactor completion

* Add 'all' case

* Implement changes in server

* remove obsolete change

* Refactor to AnyEnt instead of ID

* Remove obsolete get_end function

* Fix unpredictable text order

* Fix: All should use the Completion item kind 'keyword'

* Reduce code duplication

* rustify PortsOrGenericsExtractor

* Remove unused function

* Refactor for any keyword and use function instead of text

* Conditional snippet support

* Don't complete when the last token is not a left par or comma

* Also complete with an identifier on the right hand side

* Refactor: standalone functions from methods

* Remove serde dependency from vhdl_lang

* clippy

* symmetry in docstring
This commit is contained in:
Schottkyc137 2023-11-02 22:15:52 +01:00 committed by GitHub
parent 4732a6ce18
commit c84e06b410
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 591 additions and 182 deletions

View file

@ -35,6 +35,7 @@ mod completion;
mod tests;
pub use self::root::{DesignRoot, EntHierarchy};
pub use completion::CompletionItem;
pub use named_entity::{
AnyEnt, AnyEntKind, Concurrent, Design, EntRef, EntityId, HasEntityId, Object, Overloaded,
Related, Sequential, Type,

View file

@ -1,17 +1,33 @@
use crate::analysis::DesignRoot;
use crate::analysis::region::{AsUnique, NamedEntities};
use crate::analysis::{DesignRoot, HasEntityId};
use crate::ast::visitor::{Visitor, VisitorResult};
use crate::ast::{
AnyDesignUnit, AnyPrimaryUnit, AnySecondaryUnit, ComponentDeclaration, Declaration,
AnyDesignUnit, AnyPrimaryUnit, AnySecondaryUnit, ComponentDeclaration, Designator,
EntityDeclaration, InstantiationStatement, InterfaceDeclaration, MapAspect, PackageDeclaration,
SubprogramDeclaration, UnitKey,
};
use crate::data::{ContentReader, Symbol};
use crate::syntax::Kind::*;
use crate::syntax::{Symbols, Token, TokenAccess, Tokenizer, Value};
use crate::{EntityId, Position, Source};
use itertools::Itertools;
use crate::syntax::{Kind, Symbols, Token, TokenAccess, Tokenizer, Value};
use crate::AnyEntKind::Design;
use crate::{EntRef, EntityId, Position, Source};
use std::collections::HashSet;
use std::default::Default;
use std::iter::once;
#[derive(Debug, PartialEq, Clone)]
pub enum CompletionItem<'a> {
/// Simply complete the entities
/// e.g., `use std.` should simply list all elements in the std library
Simple(EntRef<'a>),
/// Formal parameter, e.g., in a port map
/// `port map (` might choose to complete `<item> => $1`
Formal(EntRef<'a>),
/// Multiple overloaded items are applicable.
/// The argument is the count of overloaded items in total.
Overloaded(Designator, usize),
/// Complete a keyword
Keyword(Kind),
}
macro_rules! kind {
($kind: pat) => {
@ -39,25 +55,45 @@ enum MapAspectKind {
/// The entity can be an `Entity`, `Component` or `Package`.
/// After walking the AST, the ports or generics are written to the `items` vector.
/// The `kind` member chooses whether to select ports or generics.
struct PortsOrGenericsExtractor<'a> {
struct PortsOrGenericsExtractor {
id: EntityId,
items: &'a mut Vec<String>,
items: Vec<EntityId>,
kind: MapAspectKind,
}
impl DesignRoot {
fn extract_port_or_generic_names(
&self,
id: EntityId,
items: &mut Vec<String>,
kind: MapAspectKind,
) {
let mut searcher = PortsOrGenericsExtractor { id, items, kind };
fn extract_port_or_generic_names(&self, id: EntityId, kind: MapAspectKind) -> Vec<EntityId> {
let mut searcher = PortsOrGenericsExtractor::new(id, kind);
self.walk(&mut searcher);
searcher.items
}
}
impl<'a> Visitor for PortsOrGenericsExtractor<'a> {
impl PortsOrGenericsExtractor {
pub fn new(id: EntityId, kind: MapAspectKind) -> PortsOrGenericsExtractor {
PortsOrGenericsExtractor {
id,
items: vec![],
kind,
}
}
fn add_map_aspect_items(&mut self, map_aspect: &Vec<InterfaceDeclaration>) {
for decl in map_aspect {
if let Some(id) = decl.ent_id() {
self.items.push(id)
}
}
}
fn add_optional_map_aspect_items(&mut self, map_aspect: &Option<Vec<InterfaceDeclaration>>) {
if let Some(map_aspect) = map_aspect {
self.add_map_aspect_items(map_aspect);
}
}
}
impl Visitor for PortsOrGenericsExtractor {
fn visit_component_declaration(
&mut self,
node: &ComponentDeclaration,
@ -67,14 +103,10 @@ impl<'a> Visitor for PortsOrGenericsExtractor<'a> {
return VisitorResult::Skip;
}
if self.kind == MapAspectKind::Port {
for port in &node.port_list {
self.items.push(port.completable_name())
}
self.add_map_aspect_items(&node.port_list);
}
if self.kind == MapAspectKind::Generic {
for generic in &node.generic_list {
self.items.push(generic.completable_name())
}
self.add_map_aspect_items(&node.generic_list);
}
VisitorResult::Stop
}
@ -88,18 +120,10 @@ impl<'a> Visitor for PortsOrGenericsExtractor<'a> {
return VisitorResult::Skip;
}
if self.kind == MapAspectKind::Port {
if let Some(ports) = &node.port_clause {
for port in ports {
self.items.push(port.completable_name())
}
}
self.add_optional_map_aspect_items(&node.port_clause);
}
if self.kind == MapAspectKind::Generic {
if let Some(generics) = &node.generic_clause {
for generic in generics {
self.items.push(generic.completable_name())
}
}
self.add_optional_map_aspect_items(&node.generic_clause);
}
VisitorResult::Stop
}
@ -113,42 +137,34 @@ impl<'a> Visitor for PortsOrGenericsExtractor<'a> {
return VisitorResult::Skip;
}
if self.kind == MapAspectKind::Generic {
if let Some(generics) = &node.generic_clause {
for generic in generics {
self.items.push(generic.completable_name())
}
}
self.add_optional_map_aspect_items(&node.generic_clause);
}
VisitorResult::Stop
}
}
impl InterfaceDeclaration {
/// Returns completable names for an interface declarations.
/// Example:
/// `signal my_signal : natural := 5` => `my_signal`
fn completable_name(&self) -> String {
match self {
InterfaceDeclaration::Object(obj) => obj.ident.tree.to_string(),
InterfaceDeclaration::File(file) => file.ident.to_string(),
InterfaceDeclaration::Type(typ) => typ.tree.item.name().to_string(),
InterfaceDeclaration::Subprogram(decl, _) => match decl {
SubprogramDeclaration::Procedure(proc) => proc.designator.to_string(),
SubprogramDeclaration::Function(func) => func.designator.to_string(),
},
InterfaceDeclaration::Package(package) => package.package_name.to_string(),
}
}
}
/// Visitor responsible for completions in selected AST elements
struct AutocompletionVisitor<'a> {
root: &'a DesignRoot,
cursor: Position,
completions: &'a mut Vec<String>,
completions: Vec<CompletionItem<'a>>,
tokens: Vec<Token>,
}
impl<'a> AutocompletionVisitor<'a> {
pub fn new(
root: &'a DesignRoot,
cursor: Position,
tokens: Vec<Token>,
) -> AutocompletionVisitor<'a> {
AutocompletionVisitor {
root,
cursor,
completions: Vec::new(),
tokens,
}
}
/// Loads completion options for the given map aspect.
/// Returns `true`, when the cursor is inside the map aspect and the search should not continue.
/// Returns `false` otherwise
@ -162,13 +178,19 @@ impl<'a> AutocompletionVisitor<'a> {
if !map.span(ctx).contains(self.cursor) {
return false;
}
let formals_in_map: HashSet<String> =
HashSet::from_iter(map.formals().map(|name| name.to_string().to_lowercase()));
match self.tokens[..] {
[.., kind!(LeftPar | Comma)] | [.., kind!(LeftPar | Comma), kind!(Identifier)] => {}
_ => return false,
}
let formals_in_map: HashSet<EntityId> =
HashSet::from_iter(map.formals().filter_map(|it| *it));
if let Some(ent) = node.entity_reference() {
self.root
.extract_port_or_generic_names(ent, self.completions, kind);
self.completions
.retain(|name| !formals_in_map.contains(&name.to_lowercase()));
let ids = self.root.extract_port_or_generic_names(ent, kind);
self.completions.extend(
ids.iter()
.filter(|id| !formals_in_map.contains(id))
.map(|id| CompletionItem::Formal(self.root.get_ent(*id))),
);
}
true
}
@ -216,31 +238,6 @@ impl<'a> Visitor for AutocompletionVisitor<'a> {
}
}
/// Returns the completable string representation of a declaration
/// for example:
/// `let alias = parse_vhdl("alias my_alias is ...")`
/// `declaration_to_string(Declaration::Alias(alias)) == "my_alias"`
/// Returns `None` if the declaration has no string representation that can be used for completion
/// purposes.
fn declaration_to_string(decl: &Declaration) -> Option<String> {
match decl {
Declaration::Object(o) => Some(o.ident.tree.item.to_string()),
Declaration::File(f) => Some(f.ident.tree.item.to_string()),
Declaration::Type(t) => Some(t.ident.tree.item.to_string()),
Declaration::Component(c) => Some(c.ident.tree.item.to_string()),
Declaration::Attribute(a) => match a {
crate::ast::Attribute::Specification(spec) => Some(spec.ident.item.to_string()),
crate::ast::Attribute::Declaration(decl) => Some(decl.ident.tree.item.to_string()),
},
Declaration::Alias(a) => Some(a.designator.to_string()),
Declaration::SubprogramDeclaration(decl) => Some(decl.subpgm_designator().to_string()),
Declaration::SubprogramBody(_) => None,
Declaration::Use(_) => None,
Declaration::Package(p) => Some(p.ident.to_string()),
Declaration::Configuration(_) => None,
}
}
/// Tokenizes `source` up to `cursor` but no further. The last token returned is the token
/// where the cursor currently resides or the token right before the cursor.
///
@ -283,50 +280,76 @@ fn tokenize_input(symbols: &Symbols, source: &Source, cursor: Position) -> Vec<T
impl DesignRoot {
/// helper function to list the name of all available libraries
fn list_all_libraries(&self) -> Vec<String> {
self.available_libraries()
.map(|k| k.name().to_string())
fn list_all_libraries(&self) -> Vec<CompletionItem> {
self.libraries()
.map(|lib| CompletionItem::Simple(self.get_ent(lib.id())))
.collect()
}
/// List the name of all primary units for a given library.
/// If the library is non-resolvable, list an empty vector
fn list_primaries_for_lib(&self, lib: &Symbol) -> Vec<String> {
let Some(lib) = self.get_library_units(lib) else {
fn list_primaries_for_lib(&self, lib: &Symbol) -> Vec<CompletionItem> {
let Some(lib) = self.get_lib(lib) else {
return vec![];
};
lib.keys()
.filter_map(|key| match key {
UnitKey::Primary(prim) => Some(prim.name().to_string()),
UnitKey::Secondary(_, _) => None,
})
lib.primary_units()
.filter_map(|it| it.unit.get().and_then(|unit| unit.ent_id()))
.map(|id| CompletionItem::Simple(self.get_ent(id)))
.collect()
}
/// Lists all available declarations for a primary unit inside a given library
/// If the library does not exist or there is no primary unit with the given name for that library,
/// return an empty vector
fn list_available_declarations(&self, lib: &Symbol, primary_unit: &Symbol) -> Vec<String> {
let Some(lib) = self.get_library_units(lib) else {
fn list_available_declarations(
&self,
lib: &Symbol,
primary_unit: &Symbol,
) -> Vec<CompletionItem> {
let Some(unit) = self
.get_lib(lib)
.and_then(|lib| lib.primary_unit(primary_unit))
.and_then(|unit| unit.unit.get())
else {
return vec![];
};
let Some(unit) = lib.get(&UnitKey::Primary(primary_unit.clone())) else {
return vec![];
};
let unit = unit.unit.get();
match unit.unwrap().to_owned() {
AnyDesignUnit::Primary(AnyPrimaryUnit::Package(pkg)) => pkg
.decl
.iter()
.filter_map(declaration_to_string)
.unique()
.chain(vec!["all".to_string()])
.collect_vec(),
match unit.data() {
AnyDesignUnit::Primary(AnyPrimaryUnit::Package(pkg)) => {
let Some(pkg_id) = pkg.ident.decl else {
return Vec::default();
};
let ent = self.get_ent(pkg_id);
match &ent.kind {
Design(crate::analysis::Design::Package(_, region)) => region
.entities
.values()
.map(|named_ent| match named_ent {
NamedEntities::Single(ent) => CompletionItem::Simple(ent),
NamedEntities::Overloaded(overloaded) => match overloaded.as_unique() {
None => CompletionItem::Overloaded(
overloaded.designator().clone(),
overloaded.len(),
),
Some(ent_ref) => CompletionItem::Simple(ent_ref),
},
})
.chain(once(CompletionItem::Keyword(All)))
.collect(),
_ => Vec::default(),
}
}
_ => Vec::default(),
}
}
pub fn list_completion_options(&self, source: &Source, cursor: Position) -> Vec<String> {
/// Main entry point for completion. Given a source-file and a cursor position,
/// lists available completion options at the cursor position.
pub fn list_completion_options(
&self,
source: &Source,
cursor: Position,
) -> Vec<CompletionItem> {
let tokens = tokenize_input(&self.symbols, source, cursor);
match &tokens[..] {
[.., kind!(Library)] | [.., kind!(Use)] | [.., kind!(Use), kind!(Identifier)] => {
@ -341,14 +364,9 @@ impl DesignRoot {
self.list_available_declarations(library, selected)
}
_ => {
let mut completions = vec![];
let mut visitor = AutocompletionVisitor {
completions: &mut completions,
root: self,
cursor,
};
let mut visitor = AutocompletionVisitor::new(self, cursor, tokens);
self.walk(&mut visitor);
completions
visitor.completions
}
}
}
@ -426,16 +444,20 @@ mod test {
let (root, _) = LibraryBuilder::new().get_analyzed_root();
let code = Code::new("use std.");
let cursor = code.pos().end();
let mut options = root.list_completion_options(code.source(), cursor);
options.sort();
assert_eq!(options, vec!["env", "standard", "textio"]);
let options = root.list_completion_options(code.source(), cursor);
assert!(options.contains(&CompletionItem::Simple(root.find_textio_pkg())));
assert!(options.contains(&CompletionItem::Simple(root.find_standard_pkg())));
assert!(options.contains(&CompletionItem::Simple(root.find_env_pkg())));
assert_eq!(options.len(), 3);
let code = Code::new("use std.t");
let cursor = code.pos().end();
let mut options = root.list_completion_options(code.source(), cursor);
options.sort();
let options = root.list_completion_options(code.source(), cursor);
// Note that the filtering only happens at client side
assert_eq!(options, vec!["env", "standard", "textio"]);
assert!(options.contains(&CompletionItem::Simple(root.find_textio_pkg())));
assert!(options.contains(&CompletionItem::Simple(root.find_standard_pkg())));
assert!(options.contains(&CompletionItem::Simple(root.find_env_pkg())));
assert_eq!(options.len(), 3);
}
#[test]
@ -445,7 +467,21 @@ mod test {
let (root, _) = input.get_analyzed_root();
let cursor = code.pos().end();
let options = root.list_completion_options(code.source(), cursor);
assert_eq!(options, vec!["stop", "finish", "resolution_limit", "all"])
println!("{:?}", options);
assert!(options.contains(&CompletionItem::Overloaded(
Designator::Identifier(root.symbol_utf8("stop")),
2
)));
assert!(options.contains(&CompletionItem::Overloaded(
Designator::Identifier(root.symbol_utf8("finish")),
2
)));
assert!(options.contains(&CompletionItem::Simple(
root.find_env_symbol("resolution_limit"),
)));
assert!(options.contains(&CompletionItem::Keyword(All)));
assert_eq!(options.len(), 4);
}
#[test]
@ -454,40 +490,67 @@ mod test {
let code = input.code(
"libname",
"\
entity my_ent is
end entity my_ent;
entity my_ent is
end entity my_ent;
architecture arch of my_ent is
component comp is
generic (
A: natural := 5;
B: integer
);
port (
clk : in bit;
rst : in bit;
dout : out bit
);
end component comp;
signal clk, rst: bit;
begin
comp_inst: comp
generic map (
A => 2
)
port map (
clk => clk
);
end arch;
",
architecture arch of my_ent is
component comp is
generic (
A: natural := 5;
B: integer
);
port (
clk : in bit;
rst : in bit;
dout : out bit
);
end component comp;
signal clk, rst: bit;
begin
comp_inst: comp
generic map (
A => 2
)
port map (
clk => clk
);
end arch;
",
);
let (root, _) = input.get_analyzed_root();
let cursor = code.s1("generic map (").pos().end();
let options = root.list_completion_options(code.source(), cursor);
assert_eq!(options, vec!["B"]);
let ent = root
.search_reference(code.source(), code.s1("B").start())
.unwrap();
assert_eq!(options, vec![CompletionItem::Formal(ent)]);
let rst = root
.search_reference(code.source(), code.s1("rst").start())
.unwrap();
let dout = root
.search_reference(code.source(), code.s1("dout").start())
.unwrap();
let cursor = code.s1("port map (").pos().end();
let options = root.list_completion_options(code.source(), cursor);
assert_eq!(options, vec!["rst", "dout"]);
assert!(options.contains(&CompletionItem::Formal(rst)));
assert!(options.contains(&CompletionItem::Formal(dout)));
assert_eq!(options.len(), 2);
let cursor = code
.s1("port map (
clk =>")
.pos()
.end();
let options = root.list_completion_options(code.source(), cursor);
assert_eq!(options.len(), 0);
let cursor = code
.s1("port map (
clk => c")
.pos()
.end();
let options = root.list_completion_options(code.source(), cursor);
assert_eq!(options.len(), 0);
}
}

View file

@ -6,10 +6,14 @@
use super::formal_region::FormalRegion;
use super::region::Region;
use crate::ast::ExternalObjectClass;
use crate::ast::{
AnyPrimaryUnit, Designator, HasIdent, Ident, ObjectClass, SubprogramDeclaration, WithDecl,
AliasDeclaration, AnyDesignUnit, AnyPrimaryUnit, AnySecondaryUnit, Attribute,
AttributeDeclaration, AttributeSpecification, ComponentDeclaration, Declaration, Designator,
FileDeclaration, HasIdent, Ident, InterfaceFileDeclaration, InterfacePackageDeclaration,
ObjectClass, ObjectDeclaration, PackageInstantiation, SubprogramBody, SubprogramDeclaration,
TypeDeclaration, WithDecl,
};
use crate::ast::{ExternalObjectClass, InterfaceDeclaration, InterfaceObjectDeclaration};
use crate::data::*;
mod types;
@ -455,6 +459,144 @@ impl HasEntityId for AnyPrimaryUnit {
}
}
impl HasEntityId for AnyDesignUnit {
fn ent_id(&self) -> Option<EntityId> {
match self {
AnyDesignUnit::Primary(primary) => match primary {
AnyPrimaryUnit::Entity(ent) => ent.ident.decl,
AnyPrimaryUnit::Configuration(config) => config.ident.decl,
AnyPrimaryUnit::Package(pkg) => pkg.ident.decl,
AnyPrimaryUnit::PackageInstance(inst) => inst.ident.decl,
AnyPrimaryUnit::Context(ctx) => ctx.ident.decl,
},
AnyDesignUnit::Secondary(secondary) => match secondary {
AnySecondaryUnit::Architecture(arch) => arch.ident.decl,
AnySecondaryUnit::PackageBody(bod) => bod.ident.decl,
},
}
}
}
impl HasEntityId for InterfaceDeclaration {
fn ent_id(&self) -> Option<EntityId> {
match self {
InterfaceDeclaration::Object(object) => object.ent_id(),
InterfaceDeclaration::File(file) => file.ent_id(),
InterfaceDeclaration::Type(typ) => typ.decl,
InterfaceDeclaration::Subprogram(decl, _) => decl.ent_id(),
InterfaceDeclaration::Package(pkg) => pkg.ent_id(),
}
}
}
impl HasEntityId for InterfaceObjectDeclaration {
fn ent_id(&self) -> Option<EntityId> {
self.ident.decl
}
}
impl HasEntityId for InterfaceFileDeclaration {
fn ent_id(&self) -> Option<EntityId> {
self.ident.decl
}
}
impl HasEntityId for SubprogramDeclaration {
fn ent_id(&self) -> Option<EntityId> {
match self {
SubprogramDeclaration::Procedure(proc) => proc.designator.decl,
SubprogramDeclaration::Function(func) => func.designator.decl,
}
}
}
impl HasEntityId for InterfacePackageDeclaration {
fn ent_id(&self) -> Option<EntityId> {
self.ident.decl
}
}
impl HasEntityId for Declaration {
fn ent_id(&self) -> Option<EntityId> {
match self {
Declaration::Object(object) => object.ent_id(),
Declaration::File(file) => file.ent_id(),
Declaration::Type(typ) => typ.ent_id(),
Declaration::Component(comp) => comp.ent_id(),
Declaration::Attribute(attr) => attr.ent_id(),
Declaration::Alias(alias) => alias.ent_id(),
Declaration::SubprogramDeclaration(decl) => decl.ent_id(),
Declaration::SubprogramBody(body) => body.ent_id(),
Declaration::Package(pkg) => pkg.ent_id(),
Declaration::Use(_) => None,
Declaration::Configuration(_) => None,
}
}
}
impl HasEntityId for PackageInstantiation {
fn ent_id(&self) -> Option<EntityId> {
self.ident.decl
}
}
impl HasEntityId for SubprogramBody {
fn ent_id(&self) -> Option<EntityId> {
self.specification.ent_id()
}
}
impl HasEntityId for AliasDeclaration {
fn ent_id(&self) -> Option<EntityId> {
self.designator.decl
}
}
impl HasEntityId for ObjectDeclaration {
fn ent_id(&self) -> Option<EntityId> {
self.ident.decl
}
}
impl HasEntityId for FileDeclaration {
fn ent_id(&self) -> Option<EntityId> {
self.ident.decl
}
}
impl HasEntityId for TypeDeclaration {
fn ent_id(&self) -> Option<EntityId> {
self.ident.decl
}
}
impl HasEntityId for ComponentDeclaration {
fn ent_id(&self) -> Option<EntityId> {
self.ident.decl
}
}
impl HasEntityId for Attribute {
fn ent_id(&self) -> Option<EntityId> {
match self {
Attribute::Specification(spec) => spec.ent_id(),
Attribute::Declaration(decl) => decl.ent_id(),
}
}
}
impl HasEntityId for AttributeDeclaration {
fn ent_id(&self) -> Option<EntityId> {
self.ident.decl
}
}
impl HasEntityId for AttributeSpecification {
fn ent_id(&self) -> Option<EntityId> {
self.ident.reference
}
}
impl WithDecl<Ident> {
pub fn define<'a>(
&mut self,

View file

@ -275,4 +275,16 @@ impl EntityId {
fn local_id(&self) -> LocalId {
LocalId((self.id & (u32::MAX as usize)) as u32)
}
/// Returns an `EntityId` from a raw `usize` value
/// for deserialization purposes.
pub fn from_raw(id: usize) -> EntityId {
EntityId { id }
}
/// Converts an `EntityId` to a raw `usize` value
/// for serialization purposes.
pub fn to_raw(&self) -> usize {
self.id
}
}

View file

@ -84,7 +84,7 @@ impl HasIdent for LockedUnit {
/// Represents a VHDL library containing zero or more design units.
///
/// This struct also keeps track of which source file contained which design units.
struct Library {
pub struct Library {
name: Symbol,
/// Arena is only used to store the AnyEnt for the library itself
@ -252,6 +252,21 @@ impl Library {
fn get_unit(&self, key: &UnitKey) -> Option<&LockedUnit> {
self.units.get(key)
}
pub fn id(&self) -> EntityId {
self.id
}
pub(super) fn primary_units(&self) -> impl Iterator<Item = &LockedUnit> {
self.units.iter().filter_map(|(key, value)| match key {
UnitKey::Primary(_) => Some(value),
UnitKey::Secondary(_, _) => None,
})
}
pub(super) fn primary_unit(&self, symbol: &Symbol) -> Option<&LockedUnit> {
self.units.get(&UnitKey::Primary(symbol.clone()))
}
}
/// Contains the entire design state.
@ -330,6 +345,14 @@ impl DesignRoot {
self.libraries.keys()
}
pub fn libraries(&self) -> impl Iterator<Item = &Library> {
self.libraries.values()
}
pub fn get_lib(&self, sym: &Symbol) -> Option<&Library> {
self.libraries.get(sym)
}
pub(crate) fn get_design_entity<'a>(
&'a self,
library_name: &Symbol,
@ -545,12 +568,11 @@ impl DesignRoot {
}
#[cfg(test)]
pub fn find_standard_pkg(&self) -> &AnyEnt {
fn find_std_package(&self, symbol: &str) -> &AnyEnt {
let std_lib = self.libraries.get(&self.symbol_utf8("std")).unwrap();
let unit = std_lib
.get_unit(&UnitKey::Primary(self.symbol_utf8("standard")))
.get_unit(&UnitKey::Primary(self.symbol_utf8(symbol)))
.unwrap();
if let AnyPrimaryUnit::Package(pkg) = unit.unit.write().as_primary_mut().unwrap() {
self.get_ent(pkg.ident.decl.unwrap())
} else {
@ -558,14 +580,55 @@ impl DesignRoot {
}
}
#[cfg(test)]
pub fn find_standard_pkg(&self) -> &AnyEnt {
self.find_std_package("standard")
}
#[cfg(test)]
pub fn find_textio_pkg(&self) -> &AnyEnt {
self.find_std_package("textio")
}
#[cfg(test)]
pub fn find_env_pkg(&self) -> &AnyEnt {
self.find_std_package("env")
}
#[cfg(test)]
pub fn find_standard_symbol(&self, name: &str) -> &AnyEnt {
if let AnyEntKind::Design(Design::Package(_, region)) = self.find_standard_pkg().kind() {
region
.lookup_immediate(&Designator::Identifier(self.symbol_utf8(name)))
.unwrap()
.as_non_overloaded()
.unwrap()
self.find_std_symbol("standard", name)
}
#[cfg(test)]
pub fn find_env_symbol(&self, name: &str) -> &AnyEnt {
self.find_std_symbol("env", name)
}
#[cfg(test)]
pub fn find_overloaded_env_symbols(&self, name: &str) -> &NamedEntities {
self.find_std_symbols("env", name)
}
#[cfg(test)]
fn find_std_symbol(&self, package: &str, name: &str) -> &AnyEnt {
if let AnyEntKind::Design(Design::Package(_, region)) =
self.find_std_package(package).kind()
{
let sym = region.lookup_immediate(&Designator::Identifier(self.symbol_utf8(name)));
sym.unwrap().first()
} else {
panic!("Not a package");
}
}
#[cfg(test)]
fn find_std_symbols(&self, package: &str, name: &str) -> &NamedEntities {
if let AnyEntKind::Design(Design::Package(_, region)) =
self.find_std_package(package).kind()
{
let sym = region.lookup_immediate(&Designator::Identifier(self.symbol_utf8(name)));
sym.unwrap()
} else {
panic!("Not a package");
}

View file

@ -1069,7 +1069,7 @@ pub struct MapAspect {
impl MapAspect {
/// Returns an iterator over the formal elements of this map
pub fn formals(&self) -> impl Iterator<Item = &Designator> {
pub fn formals(&self) -> impl Iterator<Item = &Option<EntityId>> {
self.list.formals()
}
@ -1172,11 +1172,11 @@ impl<T> Default for SeparatedList<T> {
impl SeparatedList<AssociationElement> {
/// Returns an iterator over the formal elements of this list
pub fn formals(&self) -> impl Iterator<Item = &Designator> {
pub fn formals(&self) -> impl Iterator<Item = &Option<EntityId>> {
self.items.iter().filter_map(|el| match &el.formal {
None => None,
Some(name) => match &name.item {
Name::Designator(desi) => Some(&desi.item),
Name::Designator(desi) => Some(&desi.reference),
_ => None,
},
})

View file

@ -23,9 +23,10 @@ pub use crate::data::{
NullDiagnostics, NullMessages, Position, Range, Severity, Source, SrcPos,
};
pub use crate::analysis::CompletionItem;
pub use crate::analysis::{
AnyEnt, AnyEntKind, Concurrent, Design, EntHierarchy, EntRef, EntityId, Object, Overloaded,
Type,
};
pub use crate::project::{Project, SourceFile};
pub use crate::syntax::{ParserResult, VHDLParser};
pub use crate::syntax::{kind_str, ParserResult, VHDLParser};

View file

@ -4,11 +4,11 @@
//
// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
use crate::analysis::{AnyEnt, DesignRoot, EntRef};
use crate::analysis::{AnyEnt, CompletionItem, DesignRoot, EntRef};
use crate::ast::DesignFile;
use crate::config::Config;
use crate::syntax::VHDLParser;
use crate::{data::*, EntHierarchy};
use crate::{data::*, EntHierarchy, EntityId};
use fnv::{FnvHashMap, FnvHashSet};
use std::collections::hash_map::Entry;
use std::path::{Path, PathBuf};
@ -278,6 +278,11 @@ impl Project {
self.root.format_declaration(ent)
}
pub fn format_entity(&self, id: EntityId) -> Option<String> {
let ent = self.root.get_ent(id);
self.format_declaration(ent)
}
/// Search for all references to the declaration at decl_pos
pub fn find_all_references(&self, ent: &AnyEnt) -> Vec<SrcPos> {
self.root.find_all_references(ent)
@ -293,7 +298,11 @@ impl Project {
self.files.values()
}
pub fn list_completion_options(&self, source: &Source, cursor: Position) -> Vec<String> {
pub fn list_completion_options(
&self,
source: &Source,
cursor: Position,
) -> Vec<CompletionItem> {
self.root.list_completion_options(source, cursor)
}
}

View file

@ -214,6 +214,14 @@ impl ConnectionRpcChannel {
}
Err(request) => request,
};
let request = match extract::<request::ResolveCompletionItem>(request) {
Ok((id, params)) => {
let res = server.resolve_completion_item(&params);
self.send_response(lsp_server::Response::new_ok(id, res));
return;
}
Err(request) => request,
};
debug!("Unhandled request: {:?}", request);
self.send_response(lsp_server::Response::new_err(

View file

@ -15,8 +15,8 @@ use crate::rpc_channel::SharedRpcChannel;
use std::io;
use std::path::{Path, PathBuf};
use vhdl_lang::{
AnyEntKind, Concurrent, Config, Diagnostic, EntHierarchy, EntRef, Message, MessageHandler,
Object, Overloaded, Project, Severity, Source, SrcPos, Type,
kind_str, AnyEntKind, Concurrent, Config, Diagnostic, EntHierarchy, EntRef, EntityId, Message,
MessageHandler, Object, Overloaded, Project, Severity, Source, SrcPos, Type,
};
#[derive(Default, Clone)]
@ -130,11 +130,12 @@ impl VHDLServer {
workspace_symbol_provider: Some(OneOf::Left(true)),
document_symbol_provider: Some(OneOf::Left(true)),
completion_provider: Some(CompletionOptions {
resolve_provider: Some(false),
resolve_provider: Some(true),
trigger_characters: Some(trigger_chars),
all_commit_characters: None,
work_done_progress_options: Default::default(),
completion_item: Default::default(),
completion_item: Some(CompletionOptionsCompletionItem {
label_details_support: Some(true),
}),
..Default::default()
}),
..Default::default()
};
@ -260,6 +261,41 @@ impl VHDLServer {
}
}
fn completion_item_to_lsp_item(
&self,
item: vhdl_lang::CompletionItem,
) -> lsp_types::CompletionItem {
match item {
vhdl_lang::CompletionItem::Simple(ent) => entity_to_completion_item(ent),
vhdl_lang::CompletionItem::Formal(ent) => {
let mut item = entity_to_completion_item(ent);
if self.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()
},
}
}
/// 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(&mut self, params: &CompletionParams) -> CompletionList {
@ -284,10 +320,7 @@ impl VHDLServer {
.project
.list_completion_options(&source, cursor)
.into_iter()
.map(|option| CompletionItem {
label: option,
..Default::default()
})
.map(|item| self.completion_item_to_lsp_item(item))
.collect();
CompletionList {
@ -296,6 +329,24 @@ impl VHDLServer {
}
}
pub fn resolve_completion_item(&mut self, params: &CompletionItem) -> CompletionItem {
let mut params = params.clone();
let eid = params
.data
.clone()
.and_then(|val| serde_json::from_value::<usize>(val).ok())
.map(EntityId::from_raw);
if let Some(id) = eid {
if let Some(text) = self.project.format_entity(id) {
params.documentation = Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: format!("```vhdl\n{text}\n```"),
}));
}
}
params
}
fn client_supports_related_information(&self) -> bool {
let try_fun = || {
self.init_params
@ -324,6 +375,22 @@ impl VHDLServer {
try_fun().unwrap_or(false)
}
fn client_supports_snippets(&self) -> bool {
let try_fun = || {
self.init_params
.as_ref()?
.capabilities
.text_document
.as_ref()?
.completion
.as_ref()?
.completion_item
.as_ref()?
.snippet_support
};
try_fun().unwrap_or(false)
}
fn client_has_hierarchical_document_symbol_support(&self) -> bool {
let try_fun = || {
self.init_params
@ -650,6 +717,49 @@ impl VHDLServer {
}
}
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::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,
}
}
struct MessageFilter {
silent: bool,
rpc: SharedRpcChannel,