455 lines
16 KiB
Rust
455 lines
16 KiB
Rust
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::hdlparam::{Define, FastHdlparam}, server::LSPServer};
|
|
|
|
use super::{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::<u32>();
|
|
if width.is_err() {
|
|
return None;
|
|
}
|
|
|
|
return Some((tag.unwrap(), digit.unwrap()));
|
|
},
|
|
None => return None
|
|
};
|
|
}
|
|
|
|
fn convert_tag_to_radix(tag: &str) -> Option<u32> {
|
|
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<Hover> {
|
|
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<Hover> {
|
|
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 <goto_include_definition>: {:?}", 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<String> = 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<Hover> {
|
|
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<Hover> {
|
|
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!("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!("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<Hover> {
|
|
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 mut port_desc_array = Vec::<String>::new();
|
|
port_desc_array.push(port.dir_type.to_string());
|
|
if port.net_type != "unknown" {
|
|
port_desc_array.push(port.net_type.to_string());
|
|
}
|
|
|
|
if port.signed != "unsigned" {
|
|
port_desc_array.push("signed".to_string());
|
|
}
|
|
|
|
if port.width != "1" {
|
|
port_desc_array.push(port.width.to_string());
|
|
}
|
|
|
|
port_desc_array.push(port.name.to_string());
|
|
let port_desc = port_desc_array.join(" ");
|
|
let language_string = LanguageString {
|
|
language: language_id.to_string(),
|
|
value: port_desc
|
|
};
|
|
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 mut param_desc_array = Vec::<String>::new();
|
|
param_desc_array.push(format!("parameter {}", param.name));
|
|
|
|
if param.init != "unknown" {
|
|
param_desc_array.push("=".to_string());
|
|
param_desc_array.push(param.init.to_string());
|
|
}
|
|
|
|
let param_desc = param_desc_array.join(" ");
|
|
let language_string = LanguageString {
|
|
language: language_id.to_string(),
|
|
value: param_desc
|
|
};
|
|
Hover {
|
|
contents: HoverContents::Scalar(MarkedString::LanguageString(language_string)),
|
|
range: Some(range.clone())
|
|
}
|
|
}
|
|
|
|
pub fn hover_module_declaration(
|
|
server: &LSPServer,
|
|
token_name: &str,
|
|
language_id: &str
|
|
) -> Option<Hover> {
|
|
if let Some(module) = server.srcs.hdl_param.find_module_by_name(token_name) {
|
|
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 path_string = server.srcs.hdl_param.find_module_definition_path(&module.name).unwrap_or("unknown".to_string());
|
|
let define_info = format!("define in {path_string}");
|
|
|
|
let mut markdowns = Vec::<MarkedString>::new();
|
|
markdowns.push(MarkedString::String(port_desc));
|
|
markdowns.push(MarkedString::String(io_desc));
|
|
markdowns.push(MarkedString::String("".to_string()));
|
|
markdowns.push(MarkedString::String(define_info));
|
|
markdowns.push(MarkedString::String("---".to_string()));
|
|
|
|
info!("module range: {:?}", module.range);
|
|
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);
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
|
|
/// 根据 module 获取 module 的简单描述代码
|
|
fn make_module_profile_code(module: &crate::core::hdlparam::Module) -> String {
|
|
let mut codes = Vec::<String>::new();
|
|
let param_num = module.params.len();
|
|
let port_num = module.ports.len();
|
|
|
|
if module.params.len() > 0 {
|
|
codes.push(format!("module {} #(", module.name));
|
|
for (i, param) in module.params.iter().enumerate() {
|
|
let mut param_desc_array = Vec::<String>::new();
|
|
param_desc_array.push(format!("parameter {}", param.name));
|
|
if param.init != "unknown" {
|
|
param_desc_array.push(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 {
|
|
for (i, port) in module.ports.iter().enumerate() {
|
|
let mut port_desc_array = Vec::<String>::new();
|
|
port_desc_array.push(port.dir_type.to_string());
|
|
if port.net_type != "unknown" {
|
|
port_desc_array.push(port.net_type.to_string());
|
|
}
|
|
|
|
if port.signed != "unsigned" {
|
|
port_desc_array.push("signed".to_string());
|
|
}
|
|
|
|
if port.width != "1" {
|
|
port_desc_array.push(port.width.to_string());
|
|
}
|
|
|
|
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
|
|
} |