diff --git a/src/core/cache_storage.rs b/src/core/cache_storage.rs index 419b578..48a1925 100644 --- a/src/core/cache_storage.rs +++ b/src/core/cache_storage.rs @@ -1,9 +1,10 @@ -use std::{collections::HashMap, fs::{self}, hash::{DefaultHasher, Hash, Hasher}, path::PathBuf, sync::{Arc, RwLock}}; +use std::{collections::HashMap, fs::{self}, hash::{DefaultHasher, Hash, Hasher}, path::PathBuf, str::FromStr, sync::{Arc, RwLock}}; use log::info; use serde::{Deserialize, Serialize}; +use tower_lsp::lsp_types::Url; -use crate::utils::{file_size_in_kb, get_last_modified_time, k_deserialize, k_serialize}; +use crate::utils::{file_size_in_kb, get_last_modified_time, k_deserialize, k_serialize, to_escape_path}; use super::hdlparam::FastHdlparam; @@ -59,7 +60,17 @@ impl CacheManager { } } - pub fn is_big_file(&self, path: &PathBuf) -> bool { + pub fn is_big_file(path: &PathBuf) -> bool { + if let Ok(size) = file_size_in_kb(path.to_str().unwrap()) { + return size >= 1024; + } + + false + } + + pub fn uri_is_big_file(uri: &Url) -> bool { + let path = PathBuf::from_str(uri.path()).unwrap(); + let path = to_escape_path(&path); if let Ok(size) = file_size_in_kb(path.to_str().unwrap()) { return size >= 1024; } @@ -83,7 +94,7 @@ impl CacheManager { /// * 如果本来就没有缓存 /// * 缓冲的 version 对不上 pub fn try_get_fast_cache(&self, path: &PathBuf) -> CacheResult { - if !self.is_big_file(path) { + if !CacheManager::is_big_file(path) { return CacheResult::NotNeedCache } let path_string = path.to_str().unwrap(); diff --git a/src/core/hdlparam.rs b/src/core/hdlparam.rs index e621af2..b04ca8a 100644 --- a/src/core/hdlparam.rs +++ b/src/core/hdlparam.rs @@ -171,6 +171,31 @@ impl Port { port_desc_array.push(self.width.to_string()); } + port_desc_array.push(self.name.to_string()); + let port_desc = port_desc_array.join(" "); + port_desc + } + pub fn vhdl_to_vlog_description(&self) -> String { + let mut port_desc_array = Vec::::new(); + let dir_type = match self.dir_type.as_str() { + "in" => "input", + "out" => "output", + _ => &self.dir_type + }; + + port_desc_array.push(dir_type.to_string()); + if self.net_type != "unknown" { + port_desc_array.push(self.net_type.to_string()); + } + + if self.signed != "unsigned" { + port_desc_array.push("signed".to_string()); + } + + if self.width != "1" { + port_desc_array.push(self.width.to_string()); + } + port_desc_array.push(self.name.to_string()); let port_desc = port_desc_array.join(" "); port_desc diff --git a/src/definition/feature.rs b/src/definition/feature.rs index cdd5b1a..e640637 100644 --- a/src/definition/feature.rs +++ b/src/definition/feature.rs @@ -207,6 +207,8 @@ pub fn goto_module_declaration_definition( token_name: &str ) -> Option { let hdl_param = server.srcs.hdl_param.clone(); + info!("get into goto_module_declaration_definition"); + if let Some((module, file_type, def_path)) = hdl_param.find_module_context_by_name(token_name) { match file_type.as_str() { "common" => { @@ -280,6 +282,7 @@ fn goto_ip_module_declaration_definition( let target_uri = Url::from_file_path(PathBuf::from_str(&def_path).unwrap()).unwrap(); let target_range = module.range.clone(); + info!("target range: {:?}", target_range); let target_range = target_range.to_lsp_range(); let link = vec![LocationLink { diff --git a/src/hover/feature.rs b/src/hover/feature.rs index 9226287..643fe37 100644 --- a/src/hover/feature.rs +++ b/src/hover/feature.rs @@ -433,6 +433,8 @@ fn hover_ip_module_declaration( if def_path_buf.exists() { // TODO: 当前工具链只支持 Xilinx 下的工具链,所以此处的代码是 vhdl 的代码 + // 如果未来需要支持其他的工具链,则需要从 server 下读取对应的变量 + let path_uri = Url::from_file_path(def_path.to_string()).unwrap().to_string(); let def_row = module.range.start.line; let def_col = module.range.start.character; @@ -467,7 +469,7 @@ fn hover_ip_module_declaration( markdowns.push(MarkedString::String("---".to_string())); let language_id = get_language_id_by_path_str(def_path); - let module_profile = make_module_profile_code(&module); + let module_profile = make_entity_profile_code(&module); let profile_markdown = LanguageString { language: language_id.to_string(), value: module_profile @@ -596,11 +598,109 @@ pub fn make_module_profile_code(module: &crate::core::hdlparam::Module) -> Strin codes.push(format!("\t{port_desc},")); } } - codes.push(format!(")")); - } else { - codes.push(format!(")")); } + + codes.push(format!(");")); let profile_string = codes.join("\n"); profile_string -} \ No newline at end of file +} + + +/// vhdl 的 entity 的 profile +pub fn make_entity_profile_code(module: &crate::core::hdlparam::Module) -> String { + let mut codes = Vec::::new(); + // param 其实就是 generic + let param_num = module.params.len(); + let port_num = module.ports.len(); + + codes.push(format!("entity {} is", module.name)); + // 缩进字符 + if module.params.len() > 0 { + codes.push("\tgeneric (".to_string()); + let max_param_name_length = module.params.iter().map(|param| param.name.len()).max().unwrap_or(0); + let max_param_type_length = module.params.iter().map(|param| param.net_type.len()).max().unwrap_or(0); + + for (i, param) in module.params.iter().enumerate() { + let mut param_desc_array = Vec::::new(); + let param_name_align_spaces = " ".repeat(max_param_name_length - param.name.len() + 1); + param_desc_array.push(format!("{}{}:", param.name, param_name_align_spaces)); + + let param_type_align_spaces = " ".repeat(max_param_type_length - param.net_type.len() + 1); + param_desc_array.push(format!("{}{}", param.net_type, param_type_align_spaces)); + + if param.init != "unknown" { + param_desc_array.push(format!(":= {}", param.init.to_string())); + } + + let param_desc = param_desc_array.join(" "); + if i == param_num - 1 { + codes.push(format!("\t\t{param_desc}")); + } else { + codes.push(format!("\t\t{param_desc};")); + } + } + codes.push("\t);".to_string()); + } + + if module.ports.len() > 0 { + codes.push("\tport (".to_string()); + + let net_mapper = |net_type: &str| { + if net_type == "unknown" { + 0 + } else { + net_type.len() + } + }; + + let width_mapper = |width: &str| { + if width == "1" { + 0 + } else { + width.len() + 5 + } + }; + + let max_port_length = module.ports.iter().map(|port| port.name.len()).max().unwrap_or(0); + let max_net_length = module.ports.iter().map(|port| net_mapper(&port.net_type)).max().unwrap_or(0); + // let max_width_length = module.ports.iter().map(|port| width_mapper(&port.width)).max().unwrap_or(0); + let max_dir_length = module.ports.iter().map(|port| port.dir_type.len()).max().unwrap_or(0); + + for (i, port) in module.ports.iter().enumerate() { + let mut port_desc_array = Vec::::new(); + + // port 的名字 + let port_name_align_spaces = " ".repeat(max_port_length - port.name.len() + 1); + port_desc_array.push(format!("{}{}: ", port.name, port_name_align_spaces)); + + // in, out, inout + let port_dir_align_spaces = " ".repeat(max_dir_length - port.dir_type.len() + 1); + port_desc_array.push(format!("{}{}", port.dir_type, port_dir_align_spaces)); + + // std_logic, signed, unsigned 等等 + if port.net_type != "unknown" { + port_desc_array.push(port.net_type.to_string()); + } + + // (57 downto 0) + if port.width != "1" { + // 内部的 width 统一表示成 [57:0] 这样的格式,需要转换一下 + let width_string = port.width.replace("[", "(").replace("]", ")").replace(":", " downto "); + port_desc_array.push(width_string); + } + let port_desc = port_desc_array.join(""); + if i == port_num - 1 { + codes.push(format!("\t\t{port_desc}")); + } else { + codes.push(format!("\t\t{port_desc};")); + } + } + codes.push("\t);".to_string()); + } + + codes.push(format!("end entity {};", module.name)); + + let profile_string = codes.join("\n"); + profile_string +} diff --git a/src/inlay_hint/sv.rs b/src/inlay_hint/sv.rs index a4b746f..8cc14e8 100644 --- a/src/inlay_hint/sv.rs +++ b/src/inlay_hint/sv.rs @@ -6,7 +6,7 @@ use log::info; use ropey::Rope; use tower_lsp::lsp_types::*; -use super::to_escape_path; +use super::{get_language_id_by_path_str, to_escape_path}; pub fn inlay_hint(server: &LSPServer, params: &InlayHintParams) -> Option> { let uri = ¶ms.text_document.uri; @@ -95,7 +95,6 @@ fn find_instport_inlay_hints_position( let end_offset = position_to_index(rope, range.end); let instport_text = rope.slice(start_offset .. end_offset); let instport_text = instport_text.to_string(); - info!("{:?}", instport_text); if let Some(offset) = instport_text.find("(") { let target_offset = start_offset + offset + 1; @@ -112,12 +111,13 @@ fn make_instport_hints( instance: &core::hdlparam::Instance, rope: &Rope ) -> Vec { + let hdl_param = server.srcs.hdl_param.clone(); let mut hints = Vec::::new(); - let define_module = server.srcs.hdl_param.find_module_by_name(&instance.inst_type); - if define_module.is_none() { + let module_context = hdl_param.find_module_context_by_name(&instance.inst_type); + if module_context.is_none() { return hints; } - let define_module = define_module.unwrap(); + let (define_module, file_type, def_path) = module_context.unwrap(); // 制作 port name 到 port 的映射 let mut port_map = HashMap::::new(); for port in &define_module.ports { @@ -132,15 +132,18 @@ fn make_instport_hints( } let port_info = port.unwrap(); let instport_range = port_assigment.range.to_lsp_range(); - info!("inst name: {:?}, range: {:?}", instance.name, instport_range); if let Some(hint_position) = find_instport_inlay_hints_position(rope, &instport_range) { - let port_desc = MarkupContent { - kind: MarkupKind::Markdown, - value: format!("```verilog\n{}\n```", port_info.to_description()) - }; let label = { let dir_type = port_info.dir_type.to_string(); + + // vhdl 转成 verilog + let dir_type = match dir_type.as_str() { + "out" => "output", + "in" => "input", + _ => &dir_type + }; + if dir_type == "output" { format!("{}", dir_type) } else { @@ -148,6 +151,28 @@ fn make_instport_hints( } }; + let language_id = get_language_id_by_path_str(&def_path); + + let port_desc_value = match file_type.as_str() { + "common" => { + format!("```{}\n{}\n```", language_id, port_info.to_description()) + } + "ip" => { + // TODO: 支持更多的 IP + format!("```verilog\n{}\n```", port_info.vhdl_to_vlog_description()) + } + "primitives" => { + format!("```{}\n{}\n```", language_id, port_info.to_description()) + } + _ => { + format!("```{}\n{}\n```", language_id, port_info.to_description()) + } + }; + + let port_desc = MarkupContent { + kind: MarkupKind::Markdown, + value: port_desc_value + }; let hint = InlayHint { position: hint_position, diff --git a/src/request/mod.rs b/src/request/mod.rs index bfe0256..1dd793f 100644 --- a/src/request/mod.rs +++ b/src/request/mod.rs @@ -255,6 +255,23 @@ fn do_vhdl_fast( }; hdl_param.update_fast(path.to_string(), ip_fast.clone()); return Ok(ip_fast); + } else if let Some(design_file) = vhdl_parse(&pathbuf) { + if let Some(mut fast) = make_fast_from_design_file(&design_file) { + fast.file_type = file_type.to_string(); + // IP 不需要内部的 instance,其实现是加密的,只需要暴露 module 的接口即可 + // 所以此处需要清空所有的 module 中的 instance + for module in &mut fast.content { + module.instances.clear(); + } + hdl_param.update_fast(path.to_string(), fast.clone()); + return Ok(fast); + } + } else { + return Err(tower_lsp::jsonrpc::Error { + code: tower_lsp::jsonrpc::ErrorCode::ParseError, + message: Cow::Owned(format!("error happen when parse {path} in [do_vhdl_fast]")), + data: None + }); } }, _ => {} @@ -264,9 +281,6 @@ fn do_vhdl_fast( // 没有特殊情况,则正常解析并写入 if let Some(design_file) = vhdl_parse(&pathbuf) { if let Some(mut fast) = make_fast_from_design_file(&design_file) { - if path.contains("ip") { - info!("vhdl fast: {:?}", fast); - } fast.file_type = file_type.to_string(); hdl_param.update_fast(path.to_string(), fast.clone()); return Ok(fast); diff --git a/src/server.rs b/src/server.rs index 52b21d2..3cf7d9d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -69,29 +69,15 @@ pub enum LogLevel { #[derive(Debug, Serialize, Deserialize)] #[serde(default)] pub struct ProjectConfig { - // if true, recursively search the working directory for files to run diagnostics on - pub auto_search_workdir: bool, - // list of directories with header files - pub include_dirs: Vec, - // list of directories to recursively search for SystemVerilog/Verilog sources - pub source_dirs: Vec, - // config options for verible tools - pub verible: Verible, - // config options for verilator tools - pub verilator: Verilator, - // log level - pub log_level: LogLevel, + pub workspace_folder: Option, + pub tool_chain: String } impl Default for ProjectConfig { fn default() -> Self { ProjectConfig { - auto_search_workdir: true, - include_dirs: Vec::new(), - source_dirs: Vec::new(), - verible: Verible::default(), - verilator: Verilator::default(), - log_level: LogLevel::Info, + workspace_folder: None, + tool_chain: "xilinx".to_string() } } } @@ -170,15 +156,23 @@ impl Default for VeribleFormat { #[tower_lsp::async_trait] impl LanguageServer for Backend { - async fn initialize(&self, _: InitializeParams) -> Result { - self.server.srcs.init(); - + async fn initialize(&self, params: InitializeParams) -> Result { // 申明 LSP 的基本信息和提供的能力 let server_info = Some(ServerInfo { name: "Digital IDE 专用 LSP 后端服务器".to_string(), version: Some("0.4.0".to_string()) }); + let root_uri = ¶ms.root_uri; + if root_uri.is_none() { + self.client.show_message(MessageType::ERROR, "LSP 启动失败,原因: 没有找到启动的 workspaceFolder").await; + return Ok(InitializeResult::default()); + } + + let mut configure = self.server.conf.write().unwrap(); + configure.workspace_folder = root_uri.clone(); + info!("当前客户端配置 workspaceFolder: {:?}", params.root_uri); + let text_document_sync = TextDocumentSyncCapability::Options( TextDocumentSyncOptions { open_close: Some(true), @@ -239,18 +233,30 @@ impl LanguageServer for Backend { } async fn did_open(&self, params: DidOpenTextDocumentParams) { - let diagnostics = self.server.did_open(params); - self.client - .publish_diagnostics( - diagnostics.uri, - diagnostics.diagnostics, - diagnostics.version, - ) - .await; + // // 如果文件太大则显示错误 + // if CacheManager::uri_is_big_file(¶ms.text_document.uri) { + // self.client.show_message(MessageType::WARNING, "考虑到性能问题,对于大于 1MB 的文件不会主动提供语言服务") + // .await; + // } else { + let diagnostics = self.server.did_open(params); + self.client + .publish_diagnostics( + diagnostics.uri, + diagnostics.diagnostics, + diagnostics.version, + ) + .await; + // } } async fn did_change(&self, params: DidChangeTextDocumentParams) { - self.server.did_change(params); + // // 如果文件太大则显示错误 + // if CacheManager::uri_is_big_file(¶ms.text_document.uri) { + // // self.client.show_message(MessageType::WARNING, "考虑到性能问题,对于大于 1MB 的文件不会主动提供语言服务") + // // .await; + // } else { + self.server.did_change(params); + // } } async fn did_delete_files(&self, params: DeleteFilesParams) { @@ -258,14 +264,18 @@ impl LanguageServer for Backend { } async fn did_save(&self, params: DidSaveTextDocumentParams) { - let diagnostics = self.server.did_save(params); - self.client - .publish_diagnostics( - diagnostics.uri, - diagnostics.diagnostics, - diagnostics.version, - ) - .await; + // if CacheManager::uri_is_big_file(¶ms.text_document.uri) { + + // } else { + let diagnostics = self.server.did_save(params); + self.client + .publish_diagnostics( + diagnostics.uri, + diagnostics.diagnostics, + diagnostics.version, + ) + .await; + // } } async fn completion(&self, params: CompletionParams) -> Result> { diff --git a/src/sources.rs b/src/sources.rs index 5a59720..841909c 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -1,3 +1,4 @@ +use crate::core::cache_storage::CacheManager; use crate::core::hdlparam::HdlParam; use crate::core::sv_parser::make_fast_from_syntaxtree; use crate::core::vhdl_parser::make_fast_from_design_file; @@ -237,35 +238,10 @@ impl Sources { fast_sync_controller: Arc::new(RwLock::new(HashMap::>>::new())) } } - pub fn init(&self) { - let mut paths: Vec = Vec::new(); - for path in &*self.include_dirs.read().unwrap() { - paths.push(path.clone()); - } - for path in &*self.source_dirs.read().unwrap() { - paths.push(path.clone()); - } - // find and add all source/header files recursively from configured include and source directories - let src_paths = find_src_paths(&paths); - - for path in src_paths { - let url = unwrap_result!(Url::from_file_path(&path)); - let text = unwrap_result!(fs::read_to_string(&path)); - let doc = TextDocumentItem::new( - url, - "systemverilog".to_string(), - -1, - text, - ); - self.add(doc); - } - } /// 增加一个 hdl 文件,并为该文件添加单独的解析线程 pub fn add(&self, doc: TextDocumentItem) { - // use a condvar to synchronize the parse thread - // the valid bool decides whether or not the file - // needs to be re-parsed + // 对于当前的文件增加一个解析线程,不断进行解析和同步 #[allow(clippy::mutex_atomic)] // https://github.com/rust-lang/rust-clippy/issues/1516 let valid_parse = Arc::new((Mutex::new(false), Condvar::new())); let valid_parse2 = valid_parse.clone(); @@ -689,8 +665,10 @@ pub fn vhdl_parser_pipeline( return; } - let mut file = source_handle.write().unwrap(); + // TODO: 通过更加精细的方法获取下面的变量 + + let mut file = source_handle.write().unwrap(); let mut scope_tree = if let Some(design_file) = vhdl_parse(&escape_path) { //let mut design_files = design_file_handle.write().unwrap();