use std::{path::PathBuf, str::FromStr}; use log::info; use regex::Regex; use ropey::RopeSlice; use tower_lsp::lsp_types::{Hover, HoverContents, LanguageString, MarkedString, Position, Range, Url}; use crate::{core::{self, hdlparam::{Define, FastHdlparam}}, definition::{DefinitionType, GenericDec}, server::LSPServer}; use super::{get_language_id_by_path_str, get_word_range_at_position, resolve_path, to_escape_path}; /// 将 4'b0011 分解为 ("b", "0011") fn parse_digit_string(digit_string: &str) -> Option<(&str, &str)> { if digit_string.len() == 0 { return None; } match digit_string.split_once("'") { Some((width_string, body_string)) => { let tag = body_string.get(0..1); let digit = body_string.get(1..); if tag.is_none() || digit.is_none() { return None; } let width = width_string.parse::(); if width.is_err() { return None; } return Some((tag.unwrap(), digit.unwrap())); }, None => return None }; } fn convert_tag_to_radix(tag: &str) -> Option { let tag = &tag.to_string().to_lowercase()[..]; match tag { "b" => Some(2), "o" => Some(8), "h" => Some(16), _ => None } } /// 计算出有符号和无符号下的表示 fn convert_to_sign_unsign<'a>(tag: &'a str, digit_string: &str) -> Option<(String, String)> { let radix = convert_tag_to_radix(tag); if radix.is_none() { return None; } let radix = radix.unwrap(); let unsigned_decimal = u128::from_str_radix(digit_string, radix); if unsigned_decimal.is_err() { return None; } let unsigned_decimal = unsigned_decimal.unwrap(); let pow = radix.pow(digit_string.len() as u32) as u128; let mut signed_decimal = unsigned_decimal as i128; if unsigned_decimal >= pow >> 1 { signed_decimal = (unsigned_decimal as i128) - (pow as i128); } Some(( signed_decimal.to_string(), unsigned_decimal.to_string() )) } /// 将 1'b1 翻译成 10进制 pub fn hover_format_digit(line: &RopeSlice, pos: Position, language_id: &str) -> Option { let regex = Regex::new(r"[0-9'bho_]").unwrap(); let token_result = get_word_range_at_position(line, pos, regex); if token_result.is_none() { return None; } let ( digit_string, range ) = token_result.unwrap(); if let Some((tag, digit)) = parse_digit_string(&digit_string) { if let Some((signed_string, unsigned_string)) = convert_to_sign_unsign(tag, digit) { let digit_title = LanguageString { language: language_id.to_string(), value: format!("{}'{}{}", digit.len(), tag, digit) }; let markdown = HoverContents::Array(vec![ MarkedString::LanguageString(digit_title), MarkedString::String(format!("`unsigned` {}\n", unsigned_string)), MarkedString::String(format!("`signed` {}", signed_string)) ]); let hover = Hover { contents: markdown, range: Some(range) }; return Some(hover); } } None } pub fn hover_include(uri: &Url, line: &RopeSlice, pos: Position, language_id: &String) -> Option { let line_text = line.as_str().unwrap_or(""); if line_text.trim().starts_with("`include") { let character = pos.character as usize; let first_quote_idx = line_text.find("\""); let last_quote_idx = line_text.rfind("\""); if first_quote_idx.is_none() || last_quote_idx.is_none() { return None; } let first_quote_idx = first_quote_idx.unwrap(); let last_quote_idx = last_quote_idx.unwrap(); if character >= first_quote_idx && character <= last_quote_idx { let mut path_string = &line_text[(first_quote_idx + 1) .. last_quote_idx]; if path_string.starts_with("./") || path_string.starts_with(".\\") { path_string = &path_string[2 ..]; } // 路径转换 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 = escape_path.to_str().unwrap_or(""); if escape_path.len() == 0 { return None; } if let Some(abs_path) = resolve_path(escape_path, path_string) { let content = format!("{:?}", abs_path).replace("\\", "/"); let language_string = LanguageString { language: language_id.to_string(), value: content }; let markdown = MarkedString::LanguageString(language_string); let range = Range::new( Position { line: pos.line, character: first_quote_idx as u32 }, Position { line: pos.line, character: (last_quote_idx + 1) as u32 } ); let hover = Hover { contents: HoverContents::Scalar(markdown), range: Some(range) }; return Some(hover); } } } None } fn make_macro_define_content(macro_define: &Define) -> String { let macro_name = macro_define.name.trim(); let macro_value = macro_define.replacement.trim(); if macro_define.params.len() == 0 { format!("`define {} {}", macro_name, macro_value) } else { let mut macro_sig_vec: Vec = Vec::new(); for macro_param in ¯o_define.params { if macro_param.value == "Unknown" { macro_sig_vec.push(macro_param.name.to_string()); } else { macro_sig_vec.push(format!("{}={}", macro_param.name, macro_param.value)); } } let macro_sig = macro_sig_vec.join(", "); format!("`define {}({}) {}", macro_name, macro_sig, macro_value) } } pub fn hover_macro(server: &LSPServer, line: &RopeSlice, pos: Position, language_id: &str) -> Option { let macro_text_regex = Regex::new(r"[`_0-9a-zA-Z]").unwrap(); if let Some((macro_text, range)) = get_word_range_at_position(line, pos, macro_text_regex) { if macro_text.starts_with("`") { if let Some((macro_define, _)) = server.find_macros(¯o_text) { let content = make_macro_define_content(¯o_define); let language_string = LanguageString { language: language_id.to_string(), value: content }; let markdown = MarkedString::LanguageString(language_string); let hover = Hover { contents: HoverContents::Scalar(markdown), range: Some(range) }; return Some(hover); } } } None } fn goto_instantiation<'a>( server: &LSPServer, fast: &'a FastHdlparam, token_name: &str, pos: &Position, range: &Range, language_id: &str ) -> Option { for module in &fast.content { for instance in &module.instances { if let Some(param_range) = &instance.instparams { // let in_scope = compare_pos(&range.start, pos) != 1 && compare_pos(pos, &range.end) != 1; // info!("pos: {pos:?}, param_range: {range:?}, in_scope: {in_scope:?}"); if param_range.contains(pos) { let module = match server.srcs.hdl_param.find_module_by_name(&instance.inst_type) { Some(module) => module, None => return None }; // info!("current pos: {pos:#?}"); // info!("param_range: {param_range:#?}"); // info!("position param find belong module: {:?}", module); for param in &module.params { if token_name == param.name { let hover = make_param_desc_hover(param, range, language_id); return Some(hover); } } return None; } } if let Some(port_range) = &instance.instports { if port_range.contains(pos) { let module = match server.srcs.hdl_param.find_module_by_name(&instance.inst_type) { Some(module) => module, None => return None }; // info!("current pos: {pos:#?}"); // info!("port_range: {port_range:#?}"); // info!("position port find belong module: {:?}", module); for port in &module.ports { if token_name == port.name { let hover = make_port_desc_hover(port, range, language_id); return Some(hover); } } return None; } } } } None } /// 计算 position 赋值的 port 或者 param /// 比如 .clk ( clk ) 中的 .clk pub fn hover_position_port_param( server: &LSPServer, line: &RopeSlice, url: &Url, pos: Position, language_id: &str ) -> Option { let position_port_regex = Regex::new(r"[._0-9a-zA-Z]").unwrap(); if let Some((name, range)) = get_word_range_at_position(line, pos, position_port_regex) { if name.starts_with(".") { let name = &name[1..]; // 进入最近的 scope 寻找 let fast_map = server.srcs.hdl_param.path_to_hdl_file.read().unwrap(); let path = PathBuf::from_str(url.path()).unwrap(); let path = to_escape_path(&path); let path_string = path.to_str().unwrap(); if let Some(hdl_file) = fast_map.get(path_string) { // 先找到属于哪一个 module let fast = &hdl_file.fast; if let Some(hover) = goto_instantiation(server, fast, name, &pos, &range, language_id) { return Some(hover); } } } } None } fn make_port_desc_hover(port: &crate::core::hdlparam::Port, range: &Range, language_id: &str) -> Hover { let language_string = LanguageString { language: language_id.to_string(), value: port.to_description() }; Hover { contents: HoverContents::Scalar(MarkedString::LanguageString(language_string)), range: Some(range.clone()) } } fn make_param_desc_hover(param: &crate::core::hdlparam::Parameter, range: &Range, language_id: &str) -> Hover { let language_string = LanguageString { language: language_id.to_string(), value: param.to_description() }; Hover { contents: HoverContents::Scalar(MarkedString::LanguageString(language_string)), range: Some(range.clone()) } } pub fn hover_module_declaration( server: &LSPServer, token_name: &str, #[allow(unused)] language_id: &str ) -> Option { // info!("token: {:?}, module info: {:?}", token_name, module_info); // let test = server.srcs.hdl_param.module_name_to_path.read().unwrap(); // info!("module name to path: {:#?}", test); let hdl_param = server.srcs.hdl_param.clone(); if let Some((module, file_type, def_path)) = hdl_param.find_module_context_by_name(token_name) { match file_type.as_str() { "common" => { hover_common_module_declaration( server, token_name, &module, &def_path ) }, "ip" => { hover_ip_module_declaration( server, token_name, &module, &def_path ) }, "primitives" => { hover_primitives_module_declaration( server, token_name, &module, &def_path ) }, _ => None } } else { None } } fn hover_common_module_declaration( #[allow(unused)] server: &LSPServer, #[allow(unused)] token_name: &str, #[allow(unused)] module: &core::hdlparam::Module, #[allow(unused)] def_path: &str ) -> Option { 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; let define_info = format!("Go to [Definition]({path_uri}#L{def_row}:{def_col})"); let port_num = module.ports.len(); let param_num = module.params.len(); let instance_num = module.instances.len(); let port_desc = format!("`port` {port_num}, `param` {param_num}, `instantiation` {instance_num}"); // 统计 dir let mut input_count = 0 as u32; let mut output_count = 0 as u32; let mut inout_count = 0 as u32; for port in &module.ports { match port.dir_type.as_str() { "input" => input_count += 1, "output" => output_count += 1, "inout" => inout_count += 1, _ => {} } } let io_desc = format!("`input` {input_count}, `output` {output_count}, `inout` {inout_count}"); let mut markdowns = Vec::::new(); markdowns.push(MarkedString::String(port_desc)); markdowns.push(MarkedString::String(io_desc)); markdowns.push(MarkedString::String(define_info)); 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 profile_markdown = LanguageString { language: language_id.to_string(), value: module_profile }; markdowns.push(MarkedString::LanguageString(profile_markdown)); let hover = Hover { contents: HoverContents::Array(markdowns), range: None }; return Some(hover); } fn hover_ip_module_declaration( #[allow(unused)] server: &LSPServer, #[allow(unused)] token_name: &str, #[allow(unused)] module: &core::hdlparam::Module, #[allow(unused)] def_path: &str ) -> Option { None } fn hover_primitives_module_declaration( #[allow(unused)] server: &LSPServer, #[allow(unused)] token_name: &str, #[allow(unused)] module: &core::hdlparam::Module, #[allow(unused)] def_path: &str ) -> Option { None } /// 根据 module 获取 module 的简单描述代码 pub fn make_module_profile_code(module: &crate::core::hdlparam::Module) -> String { let mut codes = Vec::::new(); let param_num = module.params.len(); let port_num = module.ports.len(); if module.params.len() > 0 { codes.push(format!("module {} #(", module.name)); let max_param_name_length = module.params.iter().map(|param| param.name.len()).max().unwrap_or(0); for (i, param) in module.params.iter().enumerate() { let mut param_desc_array = Vec::::new(); param_desc_array.push(format!("parameter {}{}", param.name, " ".repeat(max_param_name_length - param.name.len()))); 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{param_desc}")); } else { codes.push(format!("\t{param_desc},")); } } codes.push(")(".to_string()); } else { codes.push(format!("module {} (", module.name)); } if module.ports.len() > 0 { let net_mapper = |net_type: &str| { if net_type == "unknown" { 0 } else { net_type.len() } }; let sign_mapper = |signed: &str| { if signed == "unsigned" { 0 } else { "signed".len() } }; let width_mapper = |width: &str| { if width == "1" { 0 } else { width.len() } }; let max_net_length = module.ports.iter().map(|port| net_mapper(&port.net_type)).max().unwrap_or(0); let max_sign_length = module.ports.iter().map(|port| sign_mapper(&port.signed)).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(); // input, output, inout port_desc_array.push(port.dir_type.to_string()); port_desc_array.push(" ".repeat(max_dir_length - port.dir_type.len() + 1)); // reg, wire if port.net_type != "unknown" { port_desc_array.push(port.net_type.to_string()); } port_desc_array.push(" ".repeat(max_net_length - net_mapper(&port.net_type) + 1)); // signed, unsigned if port.signed != "unsigned" { port_desc_array.push("signed".to_string()); } port_desc_array.push(" ".repeat(max_sign_length - sign_mapper(&port.signed) + 1)); // [3:0] if port.width != "1" { port_desc_array.push(port.width.to_string()); } port_desc_array.push(" ".repeat(max_width_length - width_mapper(&port.width) + 1)); // port 的名字 port_desc_array.push(port.name.to_string()); let port_desc = port_desc_array.join(""); if i == port_num - 1 { codes.push(format!("\t{port_desc}")); } else { codes.push(format!("\t{port_desc},")); } } codes.push(format!(")")); } else { codes.push(format!(")")); } let profile_string = codes.join("\n"); profile_string }