use log::info; use regex::Regex; use ropey::Rope; use tower_lsp::lsp_types::*; use crate::{core::hdlparam::{HdlFile, Instance, Module}, hover::{to_escape_path, BracketMatchResult, BracketMatcher}, server::LSPServer, sources::LSPSupport}; use super::feature::*; use std::{path::PathBuf, str::FromStr, sync::RwLockReadGuard}; use crate::definition::*; use super::{get_definition_token, get_language_id_by_uri}; pub fn hover(server: &LSPServer, params: &HoverParams) -> Option { let doc = ¶ms.text_document_position_params.text_document.uri; let pos: Position = params.text_document_position_params.position; let file_id: usize = server.srcs.get_id(doc).to_owned(); server.srcs.wait_parse_ready(file_id, false); let file: std::sync::Arc> = server.srcs.get_file(file_id)?; let file: std::sync::RwLockReadGuard<'_, crate::sources::Source> = file.read().ok()?; let line_text = file.text.line(pos.line as usize); let token: String = get_definition_token(&line_text, pos); let language_id = get_language_id_by_uri(doc); // match `include if let Some(hover) = hover_include(doc, &line_text, pos, &language_id) { return Some(hover); } // match macro if let Some(hover) = hover_macro(server, &line_text, pos, &language_id) { return Some(hover); } let scope_tree = server.srcs.scope_tree.read().ok()?; let global_scope = scope_tree.as_ref(); // match positional port param if let Some(hover) = hover_position_port_param(server, &line_text, doc, pos, &language_id) { return Some(hover); } // match module name if let Some(hover) = hover_module_declaration(server, &token, &language_id) { return Some(hover); } if let Some(global_scope) = global_scope { // match 正常 symbol if let Some(hover) = hover_common_symbol(server, global_scope, &token, &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); } None } fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_code: bool) -> Option { if line == 0 { let language_string = LanguageString { language: language_id.to_string(), value: doc.line(line).to_string() }; let markdown = MarkedString::LanguageString(language_string); return Some(Hover { contents: HoverContents::Scalar(markdown), range: None, }); } let mut hover: Vec = Vec::new(); let mut multiline: bool = false; let mut valid: bool = true; let mut current: String = doc.line(line).to_string(); let ltrim: String = " ".repeat(current.len() - current.trim_start().len()); let mut line_idx = line; // 寻找周围的注释 while valid { hover.push(current.clone()); line_idx -= 1; valid = false; if line_idx > 0 { current = doc.line(line_idx).to_string(); let currentl = current.clone().trim_start().to_owned(); let currentr = current.clone().trim_end().to_owned(); if currentl.starts_with("/*") && currentr.ends_with("*/") { valid = true; } else if currentr.ends_with("*/") { multiline = true; valid = true; } else if currentl.starts_with("/*") { multiline = false; valid = true; } else { valid = currentl.starts_with("//") || multiline; } } } hover.reverse(); let multi_space_regex = Regex::new(r"\s+").unwrap(); let line_handler = |line: &str| -> String { let line = multi_space_regex.replace_all(line.trim(), " "); let mut line = line.into_owned(); if line.starts_with("/*") { line = line.strip_prefix("/*").unwrap().trim().to_string(); } else if line.starts_with("//") { line = line.strip_prefix("//").unwrap().trim().to_string(); } if line.ends_with("*/") { line = line.strip_suffix("*/").unwrap().trim().to_string(); } return format!("{}\n", line); }; let line_comment_extractor = |line: &str| -> Option<(String, String)> { let line = line.trim(); if line.contains("//") { let comment_start = line.split_once("//"); match comment_start { Some((code, comment)) => { let code = code.trim().to_string(); let comment = comment.trim().to_string(); Some((code, comment)) }, None => None } } else if line.contains("/*") { let comment_start = line.split_once("/*"); match comment_start { Some((code, comment)) => { let code = code.trim().to_string(); let comment = comment.trim().strip_suffix("*/").unwrap_or("").to_string(); Some((code, comment)) }, None => None } } else { return None; } }; // 所有的 markdown let mut comment_markdowns = Vec::::new(); for (line_no, line_text) in hover.iter().enumerate() { let line_hover = if let Some(stripped) = line_text.strip_prefix(<rim) { line_handler(stripped) } else { line_handler(&line_text) }; if line_no == hover.len() - 1 { // 最后一个为定义所在的一行 if let Some((code, comment)) = line_comment_extractor(&line_text) { comment_markdowns.push(MarkedString::LanguageString(LanguageString { language: language_id.to_string(), value: code })); comment_markdowns.insert(0, MarkedString::String(comment.trim().to_string())); } else { // 这行只有代码,没有注释 comment_markdowns.push(MarkedString::LanguageString(LanguageString { language: language_id.to_string(), value: line_hover })); } } else { // 否则,都是上方的注释 comment_markdowns.push(MarkedString::String(line_hover.trim().to_string())); } } // 合并其中的 markdown let mut merge_markdowns = Vec::::new(); let mut string_buffer = String::new(); for (_, md) in comment_markdowns.iter().enumerate() { match md { MarkedString::String(markdown_string) => { string_buffer.push_str(format!("{}\n", markdown_string.trim()).as_str()); } MarkedString::LanguageString(code) => { if !string_buffer.is_empty() { merge_markdowns.push(MarkedString::String(string_buffer.to_string())); string_buffer.clear(); } if !exclude_code { merge_markdowns.push(MarkedString::LanguageString(code.to_owned())); } } } } if comment_markdowns.len() > 0 { return Some(Hover { contents: HoverContents::Array(merge_markdowns), range: None }); } None } /// 计算正常 symbol 的 hover fn hover_common_symbol( #[allow(unused)] server: &LSPServer, scope_tree: &GenericScope, token: &String, file: &RwLockReadGuard<'_, crate::sources::Source>, doc: &Url, pos: Position, language_id: &String ) -> Option { let symbol_definition: GenericDec = scope_tree .get_definition(token, file.text.pos_to_byte(&pos), doc)?; // 根据 symbol 的类别进行额外的判断 match symbol_definition.def_type { DefinitionType::ModuleInstantiation => { let hdlparam = server.srcs.hdl_param.clone(); let pathbuf = PathBuf::from_str(doc.path()).unwrap(); let pathbuf = to_escape_path(&pathbuf); let path_string = pathbuf.to_str().unwrap().replace("\\", "/"); let find_name_condition = |_: &Module, instance: &Instance| { symbol_definition.ident == instance.name }; if let Some(instance) = hdlparam.walk_instantiation(&path_string, find_name_condition) { info!("instance {:?}", instance); let def_line = file.text.byte_to_line(symbol_definition.byte_idx()); let mut markdown_comment = match make_hover_with_comment(&file.text, def_line, &language_id, true) { Some(hover) => { match hover.contents { HoverContents::Array(array) => array, _ => Vec::::new() } }, None => Vec::::new() }; // 扫描到右括号 let mut current_line = def_line; let mut buffer = String::new(); let len_lines = file.text.len_lines(); let mut matcher = BracketMatcher::new(); loop { if current_line >= len_lines { break; } let line_text = file.text.line(current_line).to_string(); buffer.push_str(&line_text); match matcher.consume_string(&line_text) { BracketMatchResult::Invalid | BracketMatchResult::Complete => break, BracketMatchResult::Valid => {} } current_line += 1; } markdown_comment.push(MarkedString::LanguageString(LanguageString { language: language_id.to_string(), value: buffer })); return Some(Hover { contents: HoverContents::Array(markdown_comment), range: None }); } } _ => {} }; let def_line = file.text.byte_to_line(symbol_definition.byte_idx()); make_hover_with_comment(&file.text, def_line, &language_id, false) }