From 2fcfed4674b4a47f6a8b4223edd98f356561b96f Mon Sep 17 00:00:00 2001 From: light-ly Date: Wed, 27 Nov 2024 03:25:55 +0800 Subject: [PATCH] refactor vhdl lsp service --- Cargo.lock | 102 +++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/completion/vhdl.rs | 30 ++++++++++- src/definition/def_types.rs | 12 ++++- src/definition/vhdl.rs | 45 ++++++++++++---- src/document_symbol/vhdl.rs | 66 ++++++++++++++++++++++- src/hover/vhdl.rs | 50 +++++++++++++----- src/sources.rs | 95 ++++++++++++++++++++++++++++++++- vhdl-parser | 2 +- 9 files changed, 373 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d11d787..d6cd34b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,6 +286,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[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" @@ -348,6 +357,7 @@ dependencies = [ "tokio", "tower-lsp", "vhdl_lang", + "vhdl_ls", "walkdir", "xml", ] @@ -426,6 +436,29 @@ dependencies = [ "syn 2.0.79", ] +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -544,6 +577,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.15" @@ -609,6 +651,12 @@ version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +[[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.61" @@ -720,6 +768,18 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[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" @@ -733,6 +793,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.7.4" @@ -1462,6 +1535,16 @@ dependencies = [ "syn 2.0.79", ] +[[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.8.0" @@ -1578,7 +1661,7 @@ dependencies = [ "dashmap", "futures", "httparse", - "lsp-types", + "lsp-types 0.94.1", "memchr", "serde", "serde_json", @@ -1736,6 +1819,23 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "vhdl_ls" +version = "0.83.0" +dependencies = [ + "clap 4.5.19", + "env_logger", + "fnv", + "fuzzy-matcher", + "log", + "lsp-server", + "lsp-types 0.95.1", + "serde", + "serde_json", + "tower-lsp", + "vhdl_lang", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 477fe73..6c324b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] sv-parser = { version = "0.13.3", path = "sv-parser/sv-parser"} +vhdl_ls = { version = "^0.83.0", path = "vhdl-parser/vhdl_ls" } vhdl_lang = { version = "^0.83.0", path = "vhdl-parser/vhdl_lang" } dirs-next = "2.0" bincode = "1.3" diff --git a/src/completion/vhdl.rs b/src/completion/vhdl.rs index 3264d79..90df862 100644 --- a/src/completion/vhdl.rs +++ b/src/completion/vhdl.rs @@ -1,8 +1,10 @@ +use std::{path::PathBuf, str::FromStr}; + #[allow(unused)] use log::info; use ropey::{Rope, RopeSlice}; use tower_lsp::lsp_types::*; -use crate::hover::feature::make_vhdl_module_profile_code; +use crate::{hover::feature::make_vhdl_module_profile_code, utils::{from_lsp_pos, to_escape_path}}; #[allow(unused)] use crate::{server::LSPServer, sources::LSPSupport, utils::get_language_id_by_uri}; @@ -26,6 +28,27 @@ pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option path, + Err(error) => { + info!("error happen in vhdl : {:?}", error); + return None; + } + }; + let escape_path = to_escape_path(&path); + let project_file = escape_path.as_path(); + let Some(source) = global_project.project.get_source(project_file) else { + return None + }; + let cursor = from_lsp_pos(params.text_document_position.position); + + let vhdl_project_completion_items = global_project.project + .list_completion_options(&source, cursor) + .into_iter() + .map(|item| vhdl_ls::VHDLServer::completion_item_to_tower_lsp_item(item)) + .collect::>(); let response = match ¶ms.context { Some(context) => match context.trigger_kind { @@ -65,6 +88,8 @@ pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option Option String { @@ -749,6 +753,12 @@ impl Definition for ModInst { } } +pub struct VhdlProject { + pub project: Project, + pub std_config: vhdl_lang::Config, + pub config_file_strs: Vec +} + #[derive(Debug)] pub struct GenericScope { pub ident: String, diff --git a/src/definition/vhdl.rs b/src/definition/vhdl.rs index f21ca87..0bb1d83 100644 --- a/src/definition/vhdl.rs +++ b/src/definition/vhdl.rs @@ -1,7 +1,9 @@ +use std::{path::PathBuf, str::FromStr}; + #[allow(unused)] use log::info; use tower_lsp::lsp_types::*; -use crate::{server::LSPServer, sources::LSPSupport, utils::get_definition_token}; +use crate::{server::LSPServer, utils::{from_lsp_pos, get_definition_token, srcpos_to_location, to_escape_path}}; use super::{Definition, Scope}; @@ -16,6 +18,29 @@ pub fn goto_vhdl_definition(server: &LSPServer, params: &GotoDefinitionParams) - let line_text = file.text.line(pos.line as usize); let token: String = get_definition_token(&line_text, pos); + let project = server.srcs.vhdl_project.read().ok()?; + let global_project = project.as_ref().unwrap(); + let path = match PathBuf::from_str(doc.path()) { + Ok(path) => path, + Err(error) => { + info!("error happen in vhdl : {:?}", error); + return None; + } + }; + let escape_path = to_escape_path(&path); + let project_file = escape_path.as_path(); + let Some(source) = global_project.project.get_source(project_file) else { + return None + }; + + let ents = global_project.project.find_implementation(&source, from_lsp_pos(pos)); + + Some(GotoDefinitionResponse::Array( + ents.into_iter() + .filter_map(|ent| ent.decl_pos().map(srcpos_to_location)) + .collect(), + )) + // // match include // if let Some(definition) = goto_include_definition(doc, &line_text, pos) { // return Some(definition); @@ -28,7 +53,7 @@ pub fn goto_vhdl_definition(server: &LSPServer, params: &GotoDefinitionParams) - // match instance - let scope_tree = server.srcs.scope_tree.read().ok()?; + // let scope_tree = server.srcs.scope_tree.read().ok()?; // // match position port & param // if let Some(definition) = goto_position_port_param_definition(server, &line_text, doc, pos) { @@ -40,13 +65,13 @@ pub fn goto_vhdl_definition(server: &LSPServer, params: &GotoDefinitionParams) - // return Some(definition); // } - let byte_idx = file.text.pos_to_byte(&pos); - let global_scope = scope_tree.as_ref()?; - let def = global_scope.get_definition(&token, byte_idx, doc)?; + // let byte_idx = file.text.pos_to_byte(&pos); + // let global_scope = scope_tree.as_ref()?; + // let def = global_scope.get_definition(&token, byte_idx, doc)?; - let def_pos = file.text.byte_to_pos(def.byte_idx()); - Some(GotoDefinitionResponse::Scalar(Location::new( - def.url(), - Range::new(def_pos, def_pos), - ))) + // let def_pos = file.text.byte_to_pos(def.byte_idx()); + // Some(GotoDefinitionResponse::Scalar(Location::new( + // def.url(), + // Range::new(def_pos, def_pos), + // ))) } \ No newline at end of file diff --git a/src/document_symbol/vhdl.rs b/src/document_symbol/vhdl.rs index 74792d8..f02b226 100644 --- a/src/document_symbol/vhdl.rs +++ b/src/document_symbol/vhdl.rs @@ -1,7 +1,10 @@ +use std::{path::PathBuf, str::FromStr}; + #[allow(unused)] use log::info; use tower_lsp::lsp_types::*; -use crate::{definition::Scope, server::LSPServer}; +use vhdl_lang::{EntHierarchy, Token}; +use crate::{definition::Scope, server::LSPServer, utils::{to_escape_path, to_lsp_range, to_symbol_kind}}; pub fn document_symbol(server: &LSPServer, params: &DocumentSymbolParams) -> Option { // info!("enter document symbol"); @@ -13,7 +16,66 @@ pub fn document_symbol(server: &LSPServer, params: &DocumentSymbolParams) -> Opt let file = file.read().ok()?; let scope_tree = server.srcs.scope_tree.read().ok()?; + let project = server.srcs.vhdl_project.read().ok()?; + let global_project = project.as_ref().unwrap(); + let path = match PathBuf::from_str(uri.path()) { + Ok(path) => path, + Err(error) => { + info!("error happen in vhdl : {:?}", error); + return None; + } + }; + let escape_path = to_escape_path(&path); + let project_file = escape_path.as_path(); + let Some(source) = global_project.project.get_source(project_file) else { + return None + }; + + // Some files are mapped to multiple libraries, only use the first library for document symbols + let library_name = global_project.project + .library_mapping_of(&source) + .into_iter() + .next()?; + + 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( - scope_tree.as_ref()?.document_symbols(uri, &file.text), + global_project.project + .document_symbols(&library_name, &source) + .into_iter() + .map(|(hierarchy, tokens)| to_document_symbol(hierarchy, tokens)) + .collect(), )) + // Some(DocumentSymbolResponse::Nested( + // scope_tree.as_ref()?.document_symbols(uri, &file.text), + // )) } \ No newline at end of file diff --git a/src/hover/vhdl.rs b/src/hover/vhdl.rs index 2d7a180..c9749a0 100644 --- a/src/hover/vhdl.rs +++ b/src/hover/vhdl.rs @@ -6,7 +6,7 @@ use ropey::Rope; use tower_lsp::lsp_types::*; use crate::{core::hdlparam::{Instance, Module}, definition::{Definition, DefinitionType, GenericDec, Scope}, hover::{BracketMatchResult, BracketMatcher}, server::LSPServer, sources::LSPSupport}; -use super::{feature::hover_format_digit, get_definition_token, get_language_id_by_uri, to_escape_path}; +use super::{from_lsp_pos, get_definition_token, get_language_id_by_uri, to_escape_path}; pub fn hover(server: &LSPServer, params: &HoverParams) -> Option { let doc = ¶ms.text_document_position_params.text_document.uri; @@ -20,6 +20,30 @@ pub fn hover(server: &LSPServer, params: &HoverParams) -> Option { let token: String = get_definition_token(&line_text, pos); let language_id = get_language_id_by_uri(doc); + let project = server.srcs.vhdl_project.read().ok()?; + let global_project = project.as_ref().unwrap(); + + let path = match PathBuf::from_str(doc.path()) { + Ok(path) => path, + Err(error) => { + info!("error happen in vhdl : {:?}", error); + return None; + } + }; + let escape_path = to_escape_path(&path); + let source = global_project.project.get_source(&escape_path)?; + + let ent = global_project.project.find_declaration(&source, from_lsp_pos(pos))?; + let value = global_project.project.format_declaration(ent)?; + + Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format!("```vhdl\n{value}\n```"), + }), + range: None, + }) + // // match `include // if let Some(hover) = hover_include(doc, &line_text, pos, &language_id) { // return Some(hover); @@ -30,11 +54,11 @@ pub fn hover(server: &LSPServer, params: &HoverParams) -> Option { // return Some(hover); // } - let scope_tree = server.srcs.scope_tree.read().ok()?; - let global_scope = scope_tree.as_ref().unwrap(); + // let scope_tree = server.srcs.scope_tree.read().ok()?; + // let global_scope = scope_tree.as_ref().unwrap(); - let symbol_definition: GenericDec = global_scope - .get_definition(&token, file.text.pos_to_byte(&pos), doc)?; + // let symbol_definition: GenericDec = global_scope + // .get_definition(&token, file.text.pos_to_byte(&pos), doc)?; // // match positional port param // if let Some(hover) = hover_position_port_param(server, &line_text, doc, pos, &language_id) { @@ -47,16 +71,16 @@ pub fn hover(server: &LSPServer, params: &HoverParams) -> Option { // } // match 正常 symbol - if let Some(hover) = hover_common_symbol(server, &token, &symbol_definition, &file, doc, pos, &language_id) { - return Some(hover); - } + // if let Some(hover) = hover_common_symbol(server, &token, &symbol_definition, &file, doc, pos, &language_id) { + // return Some(hover); + // } - // match digit 5'b00110 - if let Some(hover) = hover_format_digit(&line_text, pos, &language_id) { - return Some(hover); - } + // // match digit 5'b00110 + // if let Some(hover) = hover_format_digit(&line_text, pos, &language_id) { + // return Some(hover); + // } - None + // None } fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_code: bool) -> Option { diff --git a/src/sources.rs b/src/sources.rs index 48b5091..8784fee 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -22,11 +22,13 @@ use std::env::current_dir; #[allow(unused)] use std::ops::Deref; use std::ops::Range as StdRange; +use std::path::Path; use std::path::PathBuf; use std::str::FromStr; use std::sync::{Arc, Condvar, Mutex, RwLock}; use std::thread; use sv_parser::*; +use vhdl_lang::Project; use thread::JoinHandle; use tower_lsp::lsp_types::*; use walkdir::WalkDir; @@ -143,7 +145,33 @@ impl LSPServer { None => {}, } } - + + { + let mut vhdl_project = self.srcs.vhdl_project.write().unwrap(); + match &mut *vhdl_project { + Some(vhdl_project) => { + let config_file_strs = vhdl_project.config_file_strs.clone() + .into_iter() + .filter(|x| x != path_string) + .collect::>(); + let mut messages = Vec::new(); + let config_str = format!( + r#" + [libraries] + digital_lsp.files = [{}] + "#, config_file_strs.join(",") + ); + let config = vhdl_lang::Config::from_str(&config_str, Path::new("")); + if let Ok(mut config) = config { + config.append(&vhdl_project.std_config, &mut messages); + let mut project = Project::from_config(config, &mut messages); + project.analyse(); + *vhdl_project = VhdlProject { project, std_config: vhdl_project.std_config.clone(), config_file_strs }; + } + } + None => () + } + } } } } @@ -214,6 +242,8 @@ pub struct Sources { pub meta: Arc>>>>, /// scope tree 类型的树形结构,用于提供 sv 的 lsp pub scope_tree: Arc>>, + // vhdl project, store vhdl design files, do lsp + pub vhdl_project: Arc>>, // include directories, passed to parser to resolve `include pub include_dirs: Arc>>, // primitive instance text @@ -239,6 +269,7 @@ impl Sources { names: Arc::new(RwLock::new(HashMap::new())), meta: Arc::new(RwLock::new(Vec::new())), scope_tree: Arc::new(RwLock::new(None)), + vhdl_project: Arc::new(RwLock::new(None)), include_dirs: Arc::new(RwLock::new(Vec::new())), primitive_text: Arc::new(PrimitiveText::new()), hdl_param: Arc::new(HdlParam::new()), @@ -280,6 +311,7 @@ impl Sources { })); let source_handle = source.clone(); let scope_handle = self.scope_tree.clone(); + let project_handle = self.vhdl_project.clone(); let hdl_param_handle = self.hdl_param.clone(); let inc_dirs = self.include_dirs.clone(); @@ -321,6 +353,7 @@ impl Sources { &conf, &source_handle, &scope_handle, + &project_handle, &hdl_param_handle, &text, uri, @@ -707,6 +740,7 @@ pub fn vhdl_parser_pipeline( conf: &Arc>, source_handle: &Arc>, scope_handle: &Arc>>, + project_handle: &Arc>>, hdl_param_handle: &Arc, doc: &Rope, uri: &Url @@ -797,6 +831,65 @@ pub fn vhdl_parser_pipeline( None => *global_scope = scope_tree, } // eprintln!("{:#?}", *global_scope); + + let mut global_project = project_handle.write().unwrap(); + match &mut *global_project { + Some(vhdl_project) => { + if let Some(source) = vhdl_project.project.get_source(&escape_path) { + source.change(None, &doc.to_string()); + vhdl_project.project.update_source(&source); + vhdl_project.project.analyse(); + } else { + vhdl_project.config_file_strs.push(format!("{:?}", escape_path)); + let mut messages = Vec::new(); + let config_str = format!( + r#" + [libraries] + digital_lsp.files = [{}] + "#, vhdl_project.config_file_strs.join(",") + ); + let config = vhdl_lang::Config::from_str(&config_str, Path::new("")); + if let Ok(mut config) = config { + config.append(&vhdl_project.std_config, &mut messages); + let mut project = Project::from_config(config, &mut messages); + project.analyse(); + *vhdl_project = VhdlProject { project, std_config: vhdl_project.std_config.clone(), config_file_strs: vhdl_project.config_file_strs.clone() }; + } + } + } + None => { + let std_cfg_path = match PathBuf::from_str(&(extension_path + "/resources/dide-lsp/static/vhdl_libraries/vhdl_ls.toml")) { + Ok(path) => path, + Err(e) => { + info!("error happen in : {:?}", e); + return; + } + }; + match vhdl_lang::Config::read_file_path(&std_cfg_path) { + Ok(std_config) => { + let config_str = format!( + r#" + [libraries] + digital_lsp.files = [{:?}] + "#, escape_path + ); + let mut config_file_strs = Vec::new(); + config_file_strs.push(format!("{:?}", escape_path)); + let config = vhdl_lang::Config::from_str(&config_str, Path::new("")); + if let Ok(mut config) = config { + let mut messages = Vec::new(); + config.append(&std_config, &mut messages); + let mut project = Project::from_config(config, &mut messages); + project.analyse(); + *global_project = Some(VhdlProject { project, std_config, config_file_strs }); + } + } + Err(e) => info!("error happen in : Can't load standard vhdl lib from {std_cfg_path:#?} because {e:#?}") + } + } + } + drop(global_project); + drop(global_scope); } diff --git a/vhdl-parser b/vhdl-parser index eb7ea05..2446bd0 160000 --- a/vhdl-parser +++ b/vhdl-parser @@ -1 +1 @@ -Subproject commit eb7ea05f13a133a175352da8ba537ac1717a35cf +Subproject commit 2446bd0a812d1667ad7033f10cd27a03967fa15e