266 lines
10 KiB
Rust
266 lines
10 KiB
Rust
use std::{fs, path::PathBuf, str::FromStr};
|
||
|
||
use log::info;
|
||
use ropey::RopeSlice;
|
||
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionList, Position, Url};
|
||
|
||
use crate::{server::LSPServer, utils::{resolve_path, to_escape_path}};
|
||
|
||
pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Option<CompletionList> {
|
||
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) {
|
||
// 遍历该路径下所有文件和文件夹,并构建新的自动补全列表
|
||
info!("completion active in {:?}", abs_path);
|
||
if abs_path.is_dir() {
|
||
let mut completion_items = Vec::<CompletionItem>::new();
|
||
for entry in fs::read_dir(abs_path).unwrap() {
|
||
let entry = entry.unwrap();
|
||
let path: PathBuf = entry.path();
|
||
let file_name = match path.file_name() {
|
||
Some(os_str) => os_str.to_str(),
|
||
None => continue
|
||
};
|
||
|
||
if file_name.is_none() {
|
||
continue;
|
||
}
|
||
let file_name = file_name.unwrap();
|
||
|
||
if path.is_dir() {
|
||
completion_items.push(CompletionItem {
|
||
label: file_name.to_string(),
|
||
kind: Some(CompletionItemKind::FOLDER),
|
||
..CompletionItem::default()
|
||
});
|
||
}
|
||
|
||
if path.is_file() {
|
||
completion_items.push(CompletionItem {
|
||
label: file_name.to_string(),
|
||
kind: Some(CompletionItemKind::FILE),
|
||
..CompletionItem::default()
|
||
});
|
||
}
|
||
}
|
||
|
||
if !completion_items.is_empty() {
|
||
return Some(CompletionList {
|
||
is_incomplete: false,
|
||
items: completion_items
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
None
|
||
}
|
||
|
||
pub fn get_dot_completion(
|
||
server: &LSPServer,
|
||
line: &RopeSlice,
|
||
url: &Url,
|
||
pos: &Position,
|
||
language_id: &str
|
||
) -> Option<CompletionList> {
|
||
// 判断点模式,v 中,点的模式一共两种
|
||
// 一种是 port & param position 赋值
|
||
// 一种是结构体中的属性访问
|
||
info!("current line: {:?}, pos: {:?}", line, pos);
|
||
|
||
if is_port_completion(line, pos) {
|
||
return get_position_port_param_completion(server, line, url, pos, language_id);
|
||
}
|
||
|
||
// TODO: 加入结构体的补全
|
||
|
||
None
|
||
}
|
||
|
||
fn is_port_completion(line: &RopeSlice, pos: &Position) -> bool {
|
||
let character = pos.character as usize;
|
||
if character == 0 {
|
||
match line.get_char(character) {
|
||
Some(char) => {
|
||
let char_string = char.to_string();
|
||
return char_string == ".";
|
||
},
|
||
None => return false
|
||
}
|
||
} else {
|
||
let pre_char = line.get_char(character - 1);
|
||
let cur_char = line.get_char(character);
|
||
match pre_char {
|
||
Some(pre_char) => {
|
||
let pre_char = pre_char.to_string();
|
||
if pre_char == "." {
|
||
return true;
|
||
}
|
||
match cur_char {
|
||
Some(cur_char) => {
|
||
let cur_char = cur_char.to_string();
|
||
return cur_char == ".";
|
||
},
|
||
None => {}
|
||
}
|
||
},
|
||
None => {}
|
||
}
|
||
}
|
||
false
|
||
}
|
||
|
||
fn get_position_port_param_completion(
|
||
server: &LSPServer,
|
||
#[allow(unused)]
|
||
line: &RopeSlice,
|
||
url: &Url,
|
||
pos: &Position,
|
||
#[allow(unused)]
|
||
language_id: &str
|
||
) -> Option<CompletionList> {
|
||
// 判断在不在一个模块内,并获取这个模块
|
||
let hdl_param = &server.srcs.hdl_param;
|
||
let fast_map = 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();
|
||
info!("enter get_position_port_param_completion, pos: {pos:?}");
|
||
if let Some(hdl_file) = fast_map.get(path_string) {
|
||
info!("find hdl_file, content: {:?}", hdl_file.fast.content);
|
||
// 在当前文件的 fast 中寻找
|
||
for module in &hdl_file.fast.content {
|
||
for instance in &module.instances {
|
||
if let Some(param_range) = &instance.instparams {
|
||
let mut param_range = param_range.clone();
|
||
param_range.affine(-1, -1);
|
||
if param_range.contains(pos) {
|
||
// 补全当前 module 的所有 param
|
||
let inst_module = hdl_param.find_module_by_name(&instance.inst_type);
|
||
if inst_module.is_some() {
|
||
let inst_module = inst_module.unwrap();
|
||
let mut completion_items = Vec::<CompletionItem>::new();
|
||
for param in inst_module.params {
|
||
let label_details = CompletionItemLabelDetails {
|
||
detail: Some("parameter".to_string()),
|
||
..CompletionItemLabelDetails::default()
|
||
};
|
||
|
||
let param_desc = make_param_desc(¶m);
|
||
let c_item = CompletionItem {
|
||
label: param.name,
|
||
detail: Some(param_desc),
|
||
label_details: Some(label_details),
|
||
kind: Some(CompletionItemKind::TYPE_PARAMETER),
|
||
..CompletionItem::default()
|
||
};
|
||
completion_items.push(c_item);
|
||
}
|
||
return Some(CompletionList {
|
||
is_incomplete: false,
|
||
items: completion_items
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
if instance.instports.is_some() {
|
||
let port_range = instance.gen_dot_completion_port_range();
|
||
if port_range.contains(pos) {
|
||
let inst_module = hdl_param.find_module_by_name(&instance.inst_type);
|
||
if inst_module.is_some() {
|
||
let inst_module = inst_module.unwrap();
|
||
let mut completion_items = Vec::<CompletionItem>::new();
|
||
for port in inst_module.ports {
|
||
let label_details = CompletionItemLabelDetails {
|
||
detail: Some("port".to_string()),
|
||
..CompletionItemLabelDetails::default()
|
||
};
|
||
|
||
let param_desc = make_port_desc(&port);
|
||
let c_item = CompletionItem {
|
||
label: port.name,
|
||
detail: Some(param_desc),
|
||
label_details: Some(label_details),
|
||
kind: Some(CompletionItemKind::PROPERTY),
|
||
..CompletionItem::default()
|
||
};
|
||
completion_items.push(c_item);
|
||
}
|
||
return Some(CompletionList {
|
||
is_incomplete: false,
|
||
items: completion_items
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
None
|
||
}
|
||
|
||
fn make_port_desc(port: &crate::core::hdlparam::Port) -> String {
|
||
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(" ");
|
||
port_desc
|
||
}
|
||
|
||
fn make_param_desc(param: &crate::core::hdlparam::Parameter) -> String {
|
||
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(" ");
|
||
param_desc
|
||
}
|