From f0f31c6c973cad9cdbc1ea71bc97d636990f2ad9 Mon Sep 17 00:00:00 2001 From: light-ly Date: Mon, 30 Sep 2024 23:01:06 +0800 Subject: [PATCH 1/7] add utils of vhdl | add document_symbol --- .gitignore | 4 +- Cargo.lock | 113 +++++++++++++++++++++++++++++++++--- Cargo.toml | 1 + rust_hdl | 2 +- src/document_symbol/vhdl.rs | 109 +++++++++++++++++++++++++++++++++- src/sources.rs | 13 +++-- src/utils/mod.rs | 113 ++++++++++++++++++++++++++++++++++++ 7 files changed, 337 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 4dcd28e..074a64a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ tests_rtl log_files test.txt -build.bat \ No newline at end of file +build.bat + +.DS_Store \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a86bf14..03d8c33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,6 +281,15 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -343,6 +352,7 @@ dependencies = [ "tokio", "tower-lsp", "vhdl_lang", + "vhdl_ls", "walkdir", "which", ] @@ -400,6 +410,29 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c012a26a7f605efc424dd53697843a72be7dc86ad2d01f7814337794a12231d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -528,6 +561,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "getrandom" version = "0.2.14" @@ -608,6 +650,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -725,6 +773,18 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +[[package]] +name = "lsp-server" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "550446e84739dcaf6d48a4a093973850669e13e8a34d8f8d64851041be267cd9" +dependencies = [ + "crossbeam-channel", + "log", + "serde", + "serde_json", +] + [[package]] name = "lsp-types" version = "0.94.1" @@ -738,6 +798,19 @@ dependencies = [ "url", ] +[[package]] +name = "lsp-types" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e34d33a8e9b006cd3fc4fe69a921affa097bae4bb65f76271f4644f9a334365" +dependencies = [ + "bitflags 1.3.2", + "serde", + "serde_json", + "serde_repr", + "url", +] + [[package]] name = "memchr" version = "2.5.0" @@ -1203,18 +1276,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.179" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.179" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -1223,9 +1296,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" dependencies = [ "itoa", "ryu", @@ -1483,6 +1556,16 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1602,7 +1685,7 @@ dependencies = [ "dashmap", "futures", "httparse", - "lsp-types", + "lsp-types 0.94.1", "memchr", "serde", "serde_json", @@ -1760,6 +1843,22 @@ dependencies = [ "syn 2.0.28", ] +[[package]] +name = "vhdl_ls" +version = "0.83.0" +dependencies = [ + "clap 4.4.18", + "env_logger", + "fnv", + "fuzzy-matcher", + "log", + "lsp-server", + "lsp-types 0.95.1", + "serde", + "serde_json", + "vhdl_lang", +] + [[package]] name = "walkdir" version = "2.3.3" diff --git a/Cargo.toml b/Cargo.toml index 29788d0..f399886 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] sv-parser = { version = "0.13.3", path = "sv-parser/sv-parser"} vhdl_lang = { version = "^0.83.0", path = "rust_hdl/vhdl_lang" } +vhdl_ls = { version = "^0.83.0", path = "rust_hdl/vhdl_ls" } once_cell = "1.8" percent-encoding = "2.1.0" log = "0.4.19" diff --git a/rust_hdl b/rust_hdl index f881ca1..d3f01fe 160000 --- a/rust_hdl +++ b/rust_hdl @@ -1 +1 @@ -Subproject commit f881ca161ac60accdf65295851ded9abf413da75 +Subproject commit d3f01fe8ec8d6d48031e3eaf043a65244297ca9c diff --git a/src/document_symbol/vhdl.rs b/src/document_symbol/vhdl.rs index bb8fb17..1701cb7 100644 --- a/src/document_symbol/vhdl.rs +++ b/src/document_symbol/vhdl.rs @@ -1,6 +1,109 @@ +use log::info; use tower_lsp::lsp_types::*; -use crate::server::LSPServer; +use crate::{server::LSPServer, utils::to_lsp_range}; +use crate::utils::{to_escape_path, to_symbol_kind}; -pub fn vhdl_document_symbol(server: &LSPServer, param: &DocumentSymbolParams) -> Option { - None +use std::path::PathBuf; +use std::str::FromStr; +use vhdl_lang::{EntHierarchy, Token}; + +pub fn vhdl_document_symbol(server: &LSPServer, params: &DocumentSymbolParams) -> Option { + let uri = ¶ms.text_document.uri; + let file_id = server.srcs.get_id(&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(uri.path()) { + Ok(path) => path, + Err(error) => { + info!("error happen in : {:?}", 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)?; + + // Some files are mapped to multiple libraries, only use the first library for document symbols + let library_name = project + .library_mapping_of(&source) + .into_iter() + .next()?; + + // TODO: Confirm vscode support + // if server.client_has_hierarchical_document_symbol_support() { + fn to_document_symbol( + EntHierarchy { ent, children }: EntHierarchy, + ctx: &Vec, + ) -> DocumentSymbol { + // Use the declaration position, if it exists, + // else the position of the first source range token. + // The latter is applicable for unnamed elements, e.g., processes or loops. + let selection_pos = ent.decl_pos().unwrap_or(ent.src_span.start_token.pos(ctx)); + let src_range = ent.src_span.pos(ctx).range(); + #[allow(deprecated)] + DocumentSymbol { + name: ent.describe(), + kind: to_symbol_kind(ent.kind()), + tags: None, + detail: None, + selection_range: to_lsp_range(selection_pos.range), + range: to_lsp_range(src_range), + children: if !children.is_empty() { + Some( + children + .into_iter() + .map(|hierarchy| to_document_symbol(hierarchy, ctx)) + .collect(), + ) + } else { + None + }, + deprecated: None, + } + } + + Some(DocumentSymbolResponse::Nested( + project + .document_symbols(&library_name, &source) + .into_iter() + .map(|(hierarchy, tokens)| to_document_symbol(hierarchy, tokens)) + .collect(), + )) + // } else { + // #[allow(clippy::ptr_arg)] + // fn to_symbol_information(ent: EntRef, ctx: &Vec) -> SymbolInformation { + // let selection_pos = ent.decl_pos().unwrap_or(ent.src_span.start_token.pos(ctx)); + // #[allow(deprecated)] + // SymbolInformation { + // name: ent.describe(), + // kind: to_symbol_kind(ent.kind()), + // tags: None, + // location: srcpos_to_location(selection_pos), + // deprecated: None, + // container_name: ent.parent_in_same_source().map(|ent| ent.describe()), + // } + // } + + // Some(DocumentSymbolResponse::Flat( + // project + // .document_symbols(&library_name, &source) + // .into_iter() + // .flat_map(|(a, ctx)| { + // a.into_flat() + // .into_iter() + // .map(|hierarchy| to_symbol_information(hierarchy, ctx)) + // }) + // .collect(), + // )) + // } } \ No newline at end of file diff --git a/src/sources.rs b/src/sources.rs index d0b4a3e..646a310 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -1,4 +1,3 @@ -use crate::core::fast_hdlparam::FastHdlparam; use crate::core::fast_hdlparam::HdlParam; use crate::core::sv_parser::make_fast_from_syntaxtree; use crate::core::vhdl_parser::make_fast_from_design_file; @@ -13,7 +12,7 @@ use log::info; use log::{debug, error}; use pathdiff::diff_paths; use ropey::{Rope, RopeSlice}; -use vhdl_lang::ast::DesignFile; +use vhdl_lang::MessagePrinter; use std::cmp::min; use std::collections::HashMap; use std::env::current_dir; @@ -130,7 +129,7 @@ pub struct Source { pub enum ParseIR { /// 基于 rust_hdl 的IR,存放 VHDL #[allow(unused)] - DesignFile(vhdl_lang::ast::DesignFile), + VHDLProject(vhdl_lang::Project), /// 存放 sv 的 IR SyntaxTree(sv_parser::SyntaxTree) } @@ -178,7 +177,7 @@ pub struct Sources { /// scope tree 类型的树形结构,用于提供 sv 的 lsp pub scope_tree: Arc>>, /// 存储 vhdl design file ir 的对象 - pub design_file_map: Arc>>, + pub design_file_map: Arc>>, // include directories, passed to parser to resolve `include pub include_dirs: Arc>>, // source directories @@ -567,7 +566,7 @@ pub fn sv_parser_pipeline( } pub fn vhdl_parser_pipeline( - design_file_handle: &Arc>>, + design_file_handle: &Arc>>, hdl_param_handle: &Arc, uri: &Url ) { @@ -589,7 +588,9 @@ pub fn vhdl_parser_pipeline( if let Some(fast) = make_fast_from_design_file(&design_file) { hdl_param_handle.update_fast(escape_path_string.to_string(), fast); } - design_files.insert(escape_path_string.to_string(), design_file); + let mut msg_printer = MessagePrinter::default(); + let project = vhdl_lang::Project::new_without_config(&escape_path, &mut msg_printer, None); + design_files.insert(escape_path_string.to_string(), project); } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index fc80ac5..823ef49 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,6 +4,7 @@ use percent_encoding::percent_decode_str; use regex::Regex; use ropey::RopeSlice; use tower_lsp::lsp_types::*; +use vhdl_lang::{ast::ObjectClass, AnyEntKind, Object, Overloaded, Type, Concurrent}; pub mod fast; @@ -139,4 +140,116 @@ pub fn to_escape_path(path: &PathBuf) -> PathBuf { PathBuf::from(decoded_path_str) }, } +} + +pub fn to_lsp_pos(position: vhdl_lang::Position) -> Position { + Position { + line: position.line, + character: position.character, + } +} + +pub fn to_lsp_range(range: vhdl_lang::Range) -> Range { + Range { + start: to_lsp_pos(range.start), + end: to_lsp_pos(range.end), + } +} + +pub fn to_symbol_kind(kind: &AnyEntKind) -> SymbolKind { + match kind { + AnyEntKind::ExternalAlias { class, .. } => object_class_kind(ObjectClass::from(*class)), + AnyEntKind::ObjectAlias { base_object, .. } => object_kind(base_object.object()), + AnyEntKind::Object(o) => object_kind(o), + AnyEntKind::LoopParameter(_) => SymbolKind::CONSTANT, + AnyEntKind::PhysicalLiteral(_) => SymbolKind::CONSTANT, + AnyEntKind::DeferredConstant(_) => SymbolKind::CONSTANT, + AnyEntKind::File { .. } => SymbolKind::FILE, + AnyEntKind::InterfaceFile { .. } => SymbolKind::INTERFACE, + AnyEntKind::Component(_) => SymbolKind::CLASS, + AnyEntKind::Attribute(_) => SymbolKind::PROPERTY, + AnyEntKind::Overloaded(o) => overloaded_kind(o), + AnyEntKind::Type(t) => type_kind(t), + AnyEntKind::ElementDeclaration(_) => SymbolKind::FIELD, + AnyEntKind::Sequential(_) => SymbolKind::NAMESPACE, + AnyEntKind::Concurrent(Some(Concurrent::Instance)) => SymbolKind::MODULE, + AnyEntKind::Concurrent(_) => SymbolKind::NAMESPACE, + AnyEntKind::Library => SymbolKind::NAMESPACE, + AnyEntKind::View(_) => SymbolKind::INTERFACE, + AnyEntKind::Design(d) => match d { + vhdl_lang::Design::Entity(_, _) => SymbolKind::MODULE, + vhdl_lang::Design::Architecture(..) => SymbolKind::MODULE, + vhdl_lang::Design::Configuration => SymbolKind::MODULE, + vhdl_lang::Design::Package(_, _) => SymbolKind::PACKAGE, + vhdl_lang::Design::PackageBody(..) => SymbolKind::PACKAGE, + vhdl_lang::Design::UninstPackage(_, _) => SymbolKind::PACKAGE, + vhdl_lang::Design::PackageInstance(_) => SymbolKind::PACKAGE, + vhdl_lang::Design::InterfacePackageInstance(_) => SymbolKind::PACKAGE, + vhdl_lang::Design::Context(_) => SymbolKind::NAMESPACE, + }, + } +} + +pub fn type_kind(t: &Type) -> SymbolKind { + match t { + vhdl_lang::Type::Array { .. } => SymbolKind::ARRAY, + vhdl_lang::Type::Enum(_) => SymbolKind::ENUM, + vhdl_lang::Type::Integer => SymbolKind::NUMBER, + vhdl_lang::Type::Real => SymbolKind::NUMBER, + vhdl_lang::Type::Physical => SymbolKind::NUMBER, + vhdl_lang::Type::Access(_) => SymbolKind::ENUM, + vhdl_lang::Type::Record(_) => SymbolKind::STRUCT, + vhdl_lang::Type::Incomplete => SymbolKind::NULL, + vhdl_lang::Type::Subtype(t) => type_kind(t.type_mark().kind()), + vhdl_lang::Type::Protected(_, _) => SymbolKind::CLASS, + vhdl_lang::Type::File => SymbolKind::FILE, + vhdl_lang::Type::Interface => SymbolKind::TYPE_PARAMETER, + vhdl_lang::Type::Alias(t) => type_kind(t.kind()), + vhdl_lang::Type::Universal(_) => SymbolKind::NUMBER, + } +} + +pub fn overloaded_kind(overloaded: &Overloaded) -> SymbolKind { + match overloaded { + Overloaded::SubprogramDecl(_) => SymbolKind::FUNCTION, + Overloaded::Subprogram(_) => SymbolKind::FUNCTION, + Overloaded::UninstSubprogramDecl(..) => SymbolKind::FUNCTION, + Overloaded::UninstSubprogram(..) => SymbolKind::FUNCTION, + Overloaded::InterfaceSubprogram(_) => SymbolKind::FUNCTION, + Overloaded::EnumLiteral(_) => SymbolKind::ENUM_MEMBER, + Overloaded::Alias(o) => overloaded_kind(o.kind()), + } +} + +pub fn object_kind(object: &Object) -> SymbolKind { + if matches!(object.subtype.type_mark().kind(), Type::Protected(..)) { + SymbolKind::OBJECT + } else if object.iface.is_some() { + SymbolKind::INTERFACE + } else { + object_class_kind(object.class) + } +} + +pub fn object_class_kind(class: ObjectClass) -> SymbolKind { + match class { + ObjectClass::Signal => SymbolKind::EVENT, + ObjectClass::Constant => SymbolKind::CONSTANT, + ObjectClass::Variable => SymbolKind::VARIABLE, + ObjectClass::SharedVariable => SymbolKind::VARIABLE, + } +} + +pub fn from_lsp_pos(position: Position) -> vhdl_lang::Position { + vhdl_lang::Position { + line: position.line, + character: position.character, + } +} + +pub fn from_lsp_range(range: Range) -> vhdl_lang::Range { + vhdl_lang::Range { + start: from_lsp_pos(range.start), + end: from_lsp_pos(range.end), + } } \ No newline at end of file From b84c30ab711cef36ee4964f3143b4f5d50c515d0 Mon Sep 17 00:00:00 2001 From: light-ly Date: Mon, 30 Sep 2024 23:07:22 +0800 Subject: [PATCH 2/7] add vhdl highlight --- src/document_highlight/mod.rs | 4 +-- src/document_highlight/vhdl.rs | 48 ++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/document_highlight/mod.rs b/src/document_highlight/mod.rs index ea9f358..1530789 100644 --- a/src/document_highlight/mod.rs +++ b/src/document_highlight/mod.rs @@ -12,7 +12,7 @@ impl LSPServer { &self, params: DocumentHighlightParams, ) -> Option> { - let uri = params.text_document_position_params.text_document.uri; + let uri = ¶ms.text_document_position_params.text_document.uri; let pos = params.text_document_position_params.position; let file_id = self.srcs.get_id(&uri).to_owned(); self.srcs.wait_parse_ready(file_id, false); @@ -24,7 +24,7 @@ impl LSPServer { let language_id = get_language_id_by_uri(&uri); match language_id.as_str() { - "vhdl" => vhdl_document_highlight(), + "vhdl" => vhdl_document_highlight(self, ¶ms.text_document_position_params), "verilog" | "systemverilog" => sv_document_highlight( self, &token, diff --git a/src/document_highlight/vhdl.rs b/src/document_highlight/vhdl.rs index 6eeca88..e62de6b 100644 --- a/src/document_highlight/vhdl.rs +++ b/src/document_highlight/vhdl.rs @@ -1,5 +1,49 @@ +use std::{path::PathBuf, str::FromStr}; + +use log::info; use tower_lsp::lsp_types::*; -pub fn vhdl_document_highlight() -> Option> { - None +use crate::{server::LSPServer, utils::{from_lsp_pos, to_escape_path, to_lsp_range}}; + +pub fn vhdl_document_highlight( + server: &LSPServer, + params: &TextDocumentPositionParams +) -> Option> { + let uri = ¶ms.text_document.uri; + let file_id = server.srcs.get_id(&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(uri.path()) { + Ok(path) => path, + Err(error) => { + info!("error happen in : {:?}", 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 ent = project.find_declaration(&source, from_lsp_pos(params.position))?; + + Some( + project + .find_all_references_in_source(&source, ent) + .iter() + .map(|pos| DocumentHighlight { + range: to_lsp_range(pos.range()), + kind: Some(DocumentHighlightKind::TEXT), + }) + .collect(), + ) } \ No newline at end of file From ed5782bad52e857375f716350d1b24882ece0f1f Mon Sep 17 00:00:00 2001 From: light-ly Date: Mon, 30 Sep 2024 23:14:33 +0800 Subject: [PATCH 3/7] add vhdl definition --- src/definition/mod.rs | 2 +- src/definition/vhdl.rs | 37 +++++++++++++++++++++++++++++++++---- src/utils/mod.rs | 10 +++++++++- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/definition/mod.rs b/src/definition/mod.rs index 781d89a..4b43c3d 100644 --- a/src/definition/mod.rs +++ b/src/definition/mod.rs @@ -1,4 +1,4 @@ -use crate::{utils::get_definition_token}; +use crate::utils::get_definition_token; use crate::server::LSPServer; use crate::sources::LSPSupport; diff --git a/src/definition/vhdl.rs b/src/definition/vhdl.rs index 399c669..06a423c 100644 --- a/src/definition/vhdl.rs +++ b/src/definition/vhdl.rs @@ -1,6 +1,35 @@ -use tower_lsp::lsp_types::*; -use crate::server::LSPServer; +use std::{path::PathBuf, str::FromStr}; -pub fn goto_vhdl_definition() { - +use log::info; +use tower_lsp::lsp_types::*; +use crate::{server::LSPServer, utils::{from_lsp_pos, srcpos_to_location, to_escape_path}}; + +pub fn goto_vhdl_definition(server: &LSPServer, params: TextDocumentPositionParams) -> Option { + let uri = ¶ms.text_document.uri; + let file_id = server.srcs.get_id(&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(uri.path()) { + Ok(path) => path, + Err(error) => { + info!("error happen in : {:?}", 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 ent = project.find_definition(&source, from_lsp_pos(params.position))?; + Some(srcpos_to_location(ent.decl_pos()?)) } \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 823ef49..043344c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,7 +4,7 @@ use percent_encoding::percent_decode_str; use regex::Regex; use ropey::RopeSlice; use tower_lsp::lsp_types::*; -use vhdl_lang::{ast::ObjectClass, AnyEntKind, Object, Overloaded, Type, Concurrent}; +use vhdl_lang::{ast::ObjectClass, AnyEntKind, Concurrent, Object, Overloaded, SrcPos, Type}; pub mod fast; @@ -252,4 +252,12 @@ pub fn from_lsp_range(range: Range) -> vhdl_lang::Range { start: from_lsp_pos(range.start), end: from_lsp_pos(range.end), } +} + +pub fn srcpos_to_location(pos: &SrcPos) -> Location { + let uri = Url::from_file_path(pos.source.file_name()).unwrap(); + Location { + uri, + range: to_lsp_range(pos.range()), + } } \ No newline at end of file From 699e4c1d1e3195949579e00a8da6fdf1928373d1 Mon Sep 17 00:00:00 2001 From: light-ly Date: Mon, 30 Sep 2024 23:34:41 +0800 Subject: [PATCH 4/7] add vhdl completion --- src/completion/vhdl.rs | 234 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) diff --git a/src/completion/vhdl.rs b/src/completion/vhdl.rs index e69de29..c24f3af 100644 --- a/src/completion/vhdl.rs +++ b/src/completion/vhdl.rs @@ -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 { + 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 : {:?}", 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, 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, + } +} From da06620710726d2bc796f42a59e29211e628b0ec Mon Sep 17 00:00:00 2001 From: light-ly Date: Tue, 1 Oct 2024 00:02:09 +0800 Subject: [PATCH 5/7] remove unused vhdl_ls --- Cargo.lock | 101 +---------------------------------------------------- Cargo.toml | 1 - 2 files changed, 1 insertion(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03d8c33..cb468f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,15 +281,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" -[[package]] -name = "crossbeam-channel" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -352,7 +343,6 @@ dependencies = [ "tokio", "tower-lsp", "vhdl_lang", - "vhdl_ls", "walkdir", "which", ] @@ -410,29 +400,6 @@ dependencies = [ "syn 2.0.28", ] -[[package]] -name = "env_filter" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c012a26a7f605efc424dd53697843a72be7dc86ad2d01f7814337794a12231d" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "humantime", - "log", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -561,15 +528,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fuzzy-matcher" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" -dependencies = [ - "thread_local", -] - [[package]] name = "getrandom" version = "0.2.14" @@ -650,12 +608,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "iana-time-zone" version = "0.1.57" @@ -773,18 +725,6 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" -[[package]] -name = "lsp-server" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "550446e84739dcaf6d48a4a093973850669e13e8a34d8f8d64851041be267cd9" -dependencies = [ - "crossbeam-channel", - "log", - "serde", - "serde_json", -] - [[package]] name = "lsp-types" version = "0.94.1" @@ -798,19 +738,6 @@ dependencies = [ "url", ] -[[package]] -name = "lsp-types" -version = "0.95.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e34d33a8e9b006cd3fc4fe69a921affa097bae4bb65f76271f4644f9a334365" -dependencies = [ - "bitflags 1.3.2", - "serde", - "serde_json", - "serde_repr", - "url", -] - [[package]] name = "memchr" version = "2.5.0" @@ -1556,16 +1483,6 @@ dependencies = [ "syn 2.0.28", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -1685,7 +1602,7 @@ dependencies = [ "dashmap", "futures", "httparse", - "lsp-types 0.94.1", + "lsp-types", "memchr", "serde", "serde_json", @@ -1843,22 +1760,6 @@ dependencies = [ "syn 2.0.28", ] -[[package]] -name = "vhdl_ls" -version = "0.83.0" -dependencies = [ - "clap 4.4.18", - "env_logger", - "fnv", - "fuzzy-matcher", - "log", - "lsp-server", - "lsp-types 0.95.1", - "serde", - "serde_json", - "vhdl_lang", -] - [[package]] name = "walkdir" version = "2.3.3" diff --git a/Cargo.toml b/Cargo.toml index f399886..29788d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ edition = "2018" [dependencies] sv-parser = { version = "0.13.3", path = "sv-parser/sv-parser"} vhdl_lang = { version = "^0.83.0", path = "rust_hdl/vhdl_lang" } -vhdl_ls = { version = "^0.83.0", path = "rust_hdl/vhdl_ls" } once_cell = "1.8" percent-encoding = "2.1.0" log = "0.4.19" From e289d8089fa34279e42829cde27ae756b419d9b1 Mon Sep 17 00:00:00 2001 From: light-ly Date: Tue, 1 Oct 2024 16:12:37 +0800 Subject: [PATCH 6/7] add vhdl hover --- src/hover/vhdl.rs | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/src/hover/vhdl.rs b/src/hover/vhdl.rs index 3144ebf..6eea4b4 100644 --- a/src/hover/vhdl.rs +++ b/src/hover/vhdl.rs @@ -1,9 +1,46 @@ +use std::{path::PathBuf, str::FromStr}; + +use log::info; use tower_lsp::lsp_types::*; use crate::server::LSPServer; -pub fn hover_vhdl(server: &LSPServer, param: &DocumentSymbolParams) -> Option { - let design_file = server.srcs.design_file_map.write().unwrap(); - - - None +use super::{from_lsp_pos, to_escape_path}; + +pub fn hover_vhdl(server: &LSPServer, params: &TextDocumentPositionParams) -> Option { + let uri = ¶ms.text_document.uri; + let file_id = server.srcs.get_id(&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(uri.path()) { + Ok(path) => path, + Err(error) => { + info!("error happen in : {:?}", 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 ent = project.find_declaration(&source, from_lsp_pos(params.position))?; + + let value = project.format_declaration(ent)?; + + Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format!("```vhdl\n{value}\n```"), + }), + range: None, + }) } \ No newline at end of file From 701617dd66c0b4f661969a0dc67f0082427c3757 Mon Sep 17 00:00:00 2001 From: light-ly Date: Tue, 1 Oct 2024 16:23:15 +0800 Subject: [PATCH 7/7] add inner parameter parse for sv --- src/core/sv_parser.rs | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/core/sv_parser.rs b/src/core/sv_parser.rs index cf8e57f..024f5d9 100644 --- a/src/core/sv_parser.rs +++ b/src/core/sv_parser.rs @@ -157,6 +157,55 @@ pub fn make_fast_from_syntaxtree(syntax_tree: &SyntaxTree, path: &PathBuf) -> Re } } } + RefNode::ParameterDeclaration(x) => { + if let Some(id) = unwrap_node!(x, ParameterIdentifier) { + let id = get_identifier(id).unwrap(); + let name = syntax_tree.get_str(&id).unwrap(); + match unwrap_node!(x, ParameterDeclarationParam, ParameterPortDeclarationParamList) { + Some(RefNode::ParameterDeclarationParam(param_node)) => { + // println!("{:?}", param_node); + let keyword_locate = param_node.nodes.0.nodes.0; + // println!("keyword {:#?}", keyword_locate); + let (start_line, start_character) = (id.line, get_column_by_offset(&content, keyword_locate.offset) as u32); + let (mut end_line, mut end_character) = (id.line, start_character + id.len as u32); + let net_type = match unwrap_node!(param_node, DataType) { + Some(RefNode::DataType(data_type)) => { + let id = unwrap_node!(data_type, SimpleIdentifier, Keyword); + if id == None { + "wire" + } else { + let id = get_identifier(id.unwrap()).unwrap(); + syntax_tree.get_str(&id).unwrap() + } + } + _ => "wire" + }; + let init = if let Some(init) = unwrap_node!(param_node, ConstantMintypmaxExpression) { + match init { + RefNode::ConstantMintypmaxExpression(expression) => { + // println!("expression {:?}", x); + let param_init = sv_parser::NeedParseExpression::Parameter(expression.clone()); + let (exp, last_locate) = parse_expression(&syntax_tree, ¶m_init); + (end_line, end_character) = if last_locate != None { + // println!("param {:?} lastlocate {:?}", name, last_locate); + (last_locate.unwrap().line, (get_column_by_offset(&content, last_locate.unwrap().offset) + last_locate.unwrap().len) as u32) + } else { + (end_line, end_character) + }; + // println!("end pos {} {}", end_line, end_character); + exp + } + _ => "unknown".to_string() + } + } else { + "unknown".to_string() + }; + hdlparam.add_parameter(name, net_type, init.as_str(), start_line, start_character, end_line, end_character); + } + _ => () + } + } + } RefNode::PortDeclaration(x) => { if let Some(id) = unwrap_node!(x, InputDeclaration, OutputDeclaration, InoutDeclaration) { let id = get_identifier(id).unwrap();