完成 dot completion
This commit is contained in:
parent
dc368a508f
commit
71e330b9e9
@ -1,10 +1,11 @@
|
|||||||
use std::{fs, path::PathBuf, str::FromStr};
|
use std::{fs, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
|
use regex::Regex;
|
||||||
use ropey::RopeSlice;
|
use ropey::RopeSlice;
|
||||||
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, CompletionList, Position, Url};
|
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionList, Position, Url};
|
||||||
|
|
||||||
use crate::utils::{resolve_path, to_escape_path};
|
use crate::{server::LSPServer, utils::{get_word_range_at_position, resolve_path, to_escape_path}};
|
||||||
|
|
||||||
pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Option<CompletionList> {
|
pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Option<CompletionList> {
|
||||||
let line_text = line.as_str().unwrap_or("");
|
let line_text = line.as_str().unwrap_or("");
|
||||||
@ -85,4 +86,173 @@ pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Op
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
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,
|
||||||
|
line: &RopeSlice,
|
||||||
|
url: &Url,
|
||||||
|
pos: &Position,
|
||||||
|
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();
|
||||||
|
if let Some(hdl_file) = fast_map.get(path_string) {
|
||||||
|
// 在当前文件的 fast 中寻找
|
||||||
|
for module in &hdl_file.fast.content {
|
||||||
|
for instance in &module.instances {
|
||||||
|
if let Some(param_range) = &instance.instparams {
|
||||||
|
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::CONSTANT),
|
||||||
|
..CompletionItem::default()
|
||||||
|
};
|
||||||
|
completion_items.push(c_item);
|
||||||
|
}
|
||||||
|
return Some(CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items: completion_items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(port_range) = &instance.instports {
|
||||||
|
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::CONSTANT),
|
||||||
|
..CompletionItem::default()
|
||||||
|
};
|
||||||
|
completion_items.push(c_item);
|
||||||
|
}
|
||||||
|
return Some(CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items: completion_items
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_port_desc(port: &crate::core::fast_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::fast_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(param.init.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let param_desc = param_desc_array.join(" ");
|
||||||
|
param_desc
|
||||||
|
}
|
||||||
|
@ -1,187 +1,19 @@
|
|||||||
use crate::server::LSPServer;
|
use crate::{server::LSPServer, utils::get_language_id_by_uri};
|
||||||
use crate::sources::LSPSupport;
|
|
||||||
use feature::include_path_completion;
|
|
||||||
use log::info;
|
|
||||||
use ropey::{Rope, RopeSlice};
|
|
||||||
use tower_lsp::lsp_types::*;
|
use tower_lsp::lsp_types::*;
|
||||||
|
|
||||||
pub mod keyword;
|
pub mod keyword;
|
||||||
pub mod feature;
|
pub mod feature;
|
||||||
|
|
||||||
pub mod vhdl;
|
mod vhdl;
|
||||||
|
mod sv;
|
||||||
|
|
||||||
impl LSPServer {
|
impl LSPServer {
|
||||||
pub fn completion(&self, params: CompletionParams) -> Option<CompletionResponse> {
|
pub fn completion(&self, params: CompletionParams) -> Option<CompletionResponse> {
|
||||||
let doc = params.text_document_position;
|
let language_id = get_language_id_by_uri(¶ms.text_document_position.text_document.uri);
|
||||||
let pos = doc.position;
|
match language_id.as_str() {
|
||||||
|
"vhdl" => vhdl::completion(self, ¶ms),
|
||||||
let file_id = self.srcs.get_id(&doc.text_document.uri).to_owned();
|
"verilog" | "systemverilog" => sv::completion(self, ¶ms),
|
||||||
self.srcs.wait_parse_ready(file_id, false);
|
_ => None
|
||||||
let file = self.srcs.get_file(file_id)?;
|
}
|
||||||
let file = file.read().ok()?;
|
|
||||||
let token = get_completion_token(
|
|
||||||
&file.text,
|
|
||||||
file.text.line(doc.position.line as usize),
|
|
||||||
doc.position,
|
|
||||||
);
|
|
||||||
|
|
||||||
// info!("trigger completion token: {}", token);
|
|
||||||
let line_text = file.text.line(pos.line as usize);
|
|
||||||
|
|
||||||
let response = match params.context {
|
|
||||||
Some(context) => match context.trigger_kind {
|
|
||||||
CompletionTriggerKind::TRIGGER_CHARACTER => {
|
|
||||||
match context.trigger_character?.as_str() {
|
|
||||||
"." => Some(self.srcs.get_dot_completions(
|
|
||||||
token.trim_end_matches('.'),
|
|
||||||
file.text.pos_to_byte(&doc.position),
|
|
||||||
&doc.text_document.uri,
|
|
||||||
)?),
|
|
||||||
"$" => Some(CompletionList {
|
|
||||||
is_incomplete: false,
|
|
||||||
items: self.sys_tasks.clone(),
|
|
||||||
}),
|
|
||||||
"`" => Some(CompletionList {
|
|
||||||
is_incomplete: false,
|
|
||||||
items: self.directives.clone(),
|
|
||||||
}),
|
|
||||||
"/" => {
|
|
||||||
info!("trigger include");
|
|
||||||
include_path_completion(&doc.text_document.uri, &line_text, pos)
|
|
||||||
},
|
|
||||||
"\"" => {
|
|
||||||
info!("trigger include");
|
|
||||||
include_path_completion(&doc.text_document.uri, &line_text, pos)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None,
|
|
||||||
CompletionTriggerKind::INVOKED => {
|
|
||||||
let mut comps = self.srcs.get_completions(
|
|
||||||
&token,
|
|
||||||
file.text.pos_to_byte(&doc.position),
|
|
||||||
&doc.text_document.uri,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// complete keywords
|
|
||||||
comps.items.extend::<Vec<CompletionItem>>(
|
|
||||||
self.key_comps
|
|
||||||
.iter()
|
|
||||||
.filter(|x| x.label.starts_with(&token))
|
|
||||||
.cloned()
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
Some(comps)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
let trigger = prev_char(&file.text, &doc.position);
|
|
||||||
match trigger {
|
|
||||||
'.' => Some(self.srcs.get_dot_completions(
|
|
||||||
token.trim_end_matches('.'),
|
|
||||||
file.text.pos_to_byte(&doc.position),
|
|
||||||
&doc.text_document.uri,
|
|
||||||
)?),
|
|
||||||
'$' => Some(CompletionList {
|
|
||||||
is_incomplete: false,
|
|
||||||
items: self.sys_tasks.clone(),
|
|
||||||
}),
|
|
||||||
'`' => Some(CompletionList {
|
|
||||||
is_incomplete: false,
|
|
||||||
items: self.directives.clone(),
|
|
||||||
}),
|
|
||||||
_ => {
|
|
||||||
let mut comps = self.srcs.get_completions(
|
|
||||||
&token,
|
|
||||||
file.text.pos_to_byte(&doc.position),
|
|
||||||
&doc.text_document.uri,
|
|
||||||
)?;
|
|
||||||
comps.items.extend::<Vec<CompletionItem>>(
|
|
||||||
self.key_comps
|
|
||||||
.iter()
|
|
||||||
.filter(|x| x.label.starts_with(&token))
|
|
||||||
.cloned()
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
Some(comps)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// eprintln!("comp response: {}", now.elapsed().as_millis());
|
|
||||||
Some(CompletionResponse::List(response?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// get the previous non-whitespace character
|
|
||||||
fn prev_char(text: &Rope, pos: &Position) -> char {
|
|
||||||
let char_idx = text.pos_to_char(pos);
|
|
||||||
if char_idx > 0 {
|
|
||||||
for i in (0..char_idx).rev() {
|
|
||||||
let res = text.char(i);
|
|
||||||
if !res.is_whitespace() {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
' '
|
|
||||||
} else {
|
|
||||||
' '
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// attempt to get the token the user was trying to complete, by
|
|
||||||
/// filtering out characters unneeded for name resolution
|
|
||||||
fn get_completion_token(text: &Rope, line: RopeSlice, pos: Position) -> String {
|
|
||||||
let mut token = String::new();
|
|
||||||
let mut line_iter = line.chars();
|
|
||||||
for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) {
|
|
||||||
line_iter.next();
|
|
||||||
}
|
|
||||||
let mut c = line_iter.prev();
|
|
||||||
//TODO: make this a regex
|
|
||||||
while c.is_some()
|
|
||||||
&& (c.unwrap().is_alphanumeric()
|
|
||||||
|| c.unwrap() == '_'
|
|
||||||
|| c.unwrap() == '.'
|
|
||||||
|| c.unwrap() == '['
|
|
||||||
|| c.unwrap() == ']')
|
|
||||||
{
|
|
||||||
token.push(c.unwrap());
|
|
||||||
c = line_iter.prev();
|
|
||||||
}
|
|
||||||
let mut result: String = token.chars().rev().collect();
|
|
||||||
if result.contains('[') {
|
|
||||||
let l_bracket_offset = result.find('[').unwrap_or(result.len());
|
|
||||||
result.replace_range(l_bracket_offset.., "");
|
|
||||||
}
|
|
||||||
if &result == "." {
|
|
||||||
// probably a instantiation, the token should be what we're instatiating
|
|
||||||
let mut char_iter = text.chars();
|
|
||||||
let mut token = String::new();
|
|
||||||
for _ in 0..text.pos_to_char(&pos) {
|
|
||||||
char_iter.next();
|
|
||||||
}
|
|
||||||
let mut c = char_iter.prev();
|
|
||||||
|
|
||||||
// go to the last semicolon
|
|
||||||
while c.is_some() && (c.unwrap() != ';') {
|
|
||||||
c = char_iter.prev();
|
|
||||||
}
|
|
||||||
// go the the start of the next symbol
|
|
||||||
while c.is_some() && !(c.unwrap().is_alphanumeric() || c.unwrap() == '_') {
|
|
||||||
c = char_iter.next();
|
|
||||||
}
|
|
||||||
// then extract the next symbol
|
|
||||||
while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') {
|
|
||||||
token.push(c.unwrap());
|
|
||||||
c = char_iter.next();
|
|
||||||
}
|
|
||||||
token
|
|
||||||
} else {
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
182
src/completion/sv.rs
Normal file
182
src/completion/sv.rs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
use crate::{completion::feature::{get_dot_completion, include_path_completion}, server::LSPServer, sources::LSPSupport, utils::get_language_id_by_uri};
|
||||||
|
use log::info;
|
||||||
|
use ropey::{Rope, RopeSlice};
|
||||||
|
use tower_lsp::lsp_types::*;
|
||||||
|
|
||||||
|
|
||||||
|
pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option<CompletionResponse> {
|
||||||
|
let doc = ¶ms.text_document_position;
|
||||||
|
let uri = ¶ms.text_document_position.text_document.uri;
|
||||||
|
let pos = doc.position;
|
||||||
|
let language_id = get_language_id_by_uri(uri);
|
||||||
|
|
||||||
|
let file_id = server.srcs.get_id(uri).to_owned();
|
||||||
|
server.srcs.wait_parse_ready(file_id, false);
|
||||||
|
let file = server.srcs.get_file(file_id)?;
|
||||||
|
let file = file.read().ok()?;
|
||||||
|
let line_text = file.text.line(doc.position.line as usize);
|
||||||
|
let token = get_completion_token(
|
||||||
|
&file.text,
|
||||||
|
line_text.clone(),
|
||||||
|
doc.position,
|
||||||
|
);
|
||||||
|
|
||||||
|
// info!("trigger completion token: {}", token);
|
||||||
|
let line_text = file.text.line(pos.line as usize);
|
||||||
|
|
||||||
|
let response = match ¶ms.context {
|
||||||
|
Some(context) => match context.trigger_kind {
|
||||||
|
CompletionTriggerKind::TRIGGER_CHARACTER => {
|
||||||
|
let trigger_character = context.trigger_character.clone().unwrap();
|
||||||
|
match trigger_character.as_str() {
|
||||||
|
"." => {
|
||||||
|
info!("trigger dot completion");
|
||||||
|
get_dot_completion(server, &line_text, uri, &pos, &language_id)
|
||||||
|
},
|
||||||
|
"$" => Some(CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items: server.sys_tasks.clone(),
|
||||||
|
}),
|
||||||
|
"`" => Some(CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items: server.directives.clone(),
|
||||||
|
}),
|
||||||
|
"/" => {
|
||||||
|
info!("trigger include");
|
||||||
|
include_path_completion(&doc.text_document.uri, &line_text, pos)
|
||||||
|
},
|
||||||
|
"\"" => {
|
||||||
|
info!("trigger include");
|
||||||
|
include_path_completion(&doc.text_document.uri, &line_text, pos)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None,
|
||||||
|
CompletionTriggerKind::INVOKED => {
|
||||||
|
let mut comps = server.srcs.get_completions(
|
||||||
|
&token,
|
||||||
|
file.text.pos_to_byte(&doc.position),
|
||||||
|
&doc.text_document.uri,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// complete keywords
|
||||||
|
comps.items.extend::<Vec<CompletionItem>>(
|
||||||
|
server.key_comps
|
||||||
|
.iter()
|
||||||
|
.filter(|x| x.label.starts_with(&token))
|
||||||
|
.cloned()
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
Some(comps)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let trigger = prev_char(&file.text, &doc.position);
|
||||||
|
match trigger {
|
||||||
|
'.' => Some(server.srcs.get_dot_completions(
|
||||||
|
token.trim_end_matches('.'),
|
||||||
|
file.text.pos_to_byte(&doc.position),
|
||||||
|
&doc.text_document.uri,
|
||||||
|
)?),
|
||||||
|
'$' => Some(CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items: server.sys_tasks.clone(),
|
||||||
|
}),
|
||||||
|
'`' => Some(CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items: server.directives.clone(),
|
||||||
|
}),
|
||||||
|
_ => {
|
||||||
|
let mut comps = server.srcs.get_completions(
|
||||||
|
&token,
|
||||||
|
file.text.pos_to_byte(&doc.position),
|
||||||
|
&doc.text_document.uri,
|
||||||
|
)?;
|
||||||
|
comps.items.extend::<Vec<CompletionItem>>(
|
||||||
|
server.key_comps
|
||||||
|
.iter()
|
||||||
|
.filter(|x| x.label.starts_with(&token))
|
||||||
|
.cloned()
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
Some(comps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// eprintln!("comp response: {}", now.elapsed().as_millis());
|
||||||
|
Some(CompletionResponse::List(response?))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// get the previous non-whitespace character
|
||||||
|
fn prev_char(text: &Rope, pos: &Position) -> char {
|
||||||
|
let char_idx = text.pos_to_char(pos);
|
||||||
|
if char_idx > 0 {
|
||||||
|
for i in (0..char_idx).rev() {
|
||||||
|
let res = text.char(i);
|
||||||
|
if !res.is_whitespace() {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' '
|
||||||
|
} else {
|
||||||
|
' '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// attempt to get the token the user was trying to complete, by
|
||||||
|
/// filtering out characters unneeded for name resolution
|
||||||
|
fn get_completion_token(text: &Rope, line: RopeSlice, pos: Position) -> String {
|
||||||
|
let mut token = String::new();
|
||||||
|
let mut line_iter = line.chars();
|
||||||
|
for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) {
|
||||||
|
line_iter.next();
|
||||||
|
}
|
||||||
|
let mut c = line_iter.prev();
|
||||||
|
//TODO: make this a regex
|
||||||
|
while c.is_some()
|
||||||
|
&& (c.unwrap().is_alphanumeric()
|
||||||
|
|| c.unwrap() == '_'
|
||||||
|
|| c.unwrap() == '.'
|
||||||
|
|| c.unwrap() == '['
|
||||||
|
|| c.unwrap() == ']')
|
||||||
|
{
|
||||||
|
token.push(c.unwrap());
|
||||||
|
c = line_iter.prev();
|
||||||
|
}
|
||||||
|
let mut result: String = token.chars().rev().collect();
|
||||||
|
if result.contains('[') {
|
||||||
|
let l_bracket_offset = result.find('[').unwrap_or(result.len());
|
||||||
|
result.replace_range(l_bracket_offset.., "");
|
||||||
|
}
|
||||||
|
if &result == "." {
|
||||||
|
// probably a instantiation, the token should be what we're instatiating
|
||||||
|
let mut char_iter = text.chars();
|
||||||
|
let mut token = String::new();
|
||||||
|
for _ in 0..text.pos_to_char(&pos) {
|
||||||
|
char_iter.next();
|
||||||
|
}
|
||||||
|
let mut c = char_iter.prev();
|
||||||
|
|
||||||
|
// go to the last semicolon
|
||||||
|
while c.is_some() && (c.unwrap() != ';') {
|
||||||
|
c = char_iter.prev();
|
||||||
|
}
|
||||||
|
// go the the start of the next symbol
|
||||||
|
while c.is_some() && !(c.unwrap().is_alphanumeric() || c.unwrap() == '_') {
|
||||||
|
c = char_iter.next();
|
||||||
|
}
|
||||||
|
// then extract the next symbol
|
||||||
|
while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') {
|
||||||
|
token.push(c.unwrap());
|
||||||
|
c = char_iter.next();
|
||||||
|
}
|
||||||
|
token
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::{server::LSPServer, utils::{from_lsp_pos, to_escape_path}};
|
|||||||
|
|
||||||
/// Called when the client requests a completion.
|
/// Called when the client requests a completion.
|
||||||
/// This function looks in the source code to find suitable options and then returns them
|
/// This function looks in the source code to find suitable options and then returns them
|
||||||
pub fn request_completion(server: &LSPServer, params: &CompletionParams) -> Option<CompletionList> {
|
pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option<CompletionResponse> {
|
||||||
let doc = ¶ms.text_document_position;
|
let doc = ¶ms.text_document_position;
|
||||||
// let pos = doc.position;
|
// let pos = doc.position;
|
||||||
|
|
||||||
@ -33,16 +33,17 @@ pub fn request_completion(server: &LSPServer, params: &CompletionParams) -> Opti
|
|||||||
None => return None
|
None => return None
|
||||||
};
|
};
|
||||||
|
|
||||||
let source = project.get_source(&escape_path)?;
|
// let source = project.get_source(&escape_path)?;
|
||||||
|
|
||||||
let binding = escape_path;
|
let binding = escape_path;
|
||||||
let file = binding.as_path();
|
let file = binding.as_path();
|
||||||
// 1) get source position, and source file
|
// 1) get source position, and source file
|
||||||
let Some(source) = project.get_source(file) else {
|
let Some(source) = project.get_source(file) else {
|
||||||
// Do not enable completions for files that are not part of the project
|
// Do not enable completions for files that are not part of the project
|
||||||
return Some(CompletionList {
|
|
||||||
|
return Some(CompletionResponse::List(CompletionList {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
}));
|
||||||
};
|
};
|
||||||
let cursor = from_lsp_pos(params.text_document_position.position);
|
let cursor = from_lsp_pos(params.text_document_position.position);
|
||||||
// 2) Optimization chance: go to last recognizable token before the cursor. For example:
|
// 2) Optimization chance: go to last recognizable token before the cursor. For example:
|
||||||
@ -57,11 +58,10 @@ pub fn request_completion(server: &LSPServer, params: &CompletionParams) -> Opti
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|item| completion_item_to_lsp_item(server, item))
|
.map(|item| completion_item_to_lsp_item(server, item))
|
||||||
.collect();
|
.collect();
|
||||||
|
Some(CompletionResponse::List(CompletionList {
|
||||||
Some(CompletionList {
|
|
||||||
items: options,
|
items: options,
|
||||||
is_incomplete: true,
|
is_incomplete: true,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn completion_item_to_lsp_item(
|
fn completion_item_to_lsp_item(
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use crate::utils::get_definition_token;
|
use crate::utils::get_language_id_by_uri;
|
||||||
use crate::server::LSPServer;
|
use crate::server::LSPServer;
|
||||||
use crate::sources::LSPSupport;
|
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
use log::info;
|
use log::info;
|
||||||
@ -16,58 +15,21 @@ pub use feature::*;
|
|||||||
pub mod extract_defs;
|
pub mod extract_defs;
|
||||||
pub use extract_defs::*;
|
pub use extract_defs::*;
|
||||||
|
|
||||||
pub mod vhdl;
|
mod sv;
|
||||||
|
mod vhdl;
|
||||||
|
|
||||||
impl LSPServer {
|
impl LSPServer {
|
||||||
pub fn goto_definition(&self, params: GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
|
pub fn goto_definition(&self, params: GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
|
||||||
let doc = params.text_document_position_params.text_document.uri;
|
let language_id = get_language_id_by_uri(¶ms.text_document_position_params.text_document.uri);
|
||||||
let pos = params.text_document_position_params.position;
|
match language_id.as_str() {
|
||||||
let file_id = self.srcs.get_id(&doc).to_owned();
|
"vhdl" => vhdl::goto_vhdl_definition(self, ¶ms),
|
||||||
self.srcs.wait_parse_ready(file_id, false);
|
"verilog" | "systemverilog" => sv::goto_definition(self, ¶ms),
|
||||||
let file = self.srcs.get_file(file_id)?;
|
_ => None
|
||||||
let file = file.read().ok()?;
|
|
||||||
|
|
||||||
let line_text = file.text.line(pos.line as usize);
|
|
||||||
let token: String = get_definition_token(&line_text, pos);
|
|
||||||
|
|
||||||
// match include
|
|
||||||
if let Some(definition) = goto_include_definition(&doc, &line_text, pos) {
|
|
||||||
return Some(definition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// match macro
|
|
||||||
if let Some(definition) = goto_macro_definition(self, &line_text, pos) {
|
|
||||||
return Some(definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
// match instance
|
|
||||||
|
|
||||||
let scope_tree = self.srcs.scope_tree.read().ok()?;
|
|
||||||
|
|
||||||
// match position port & param
|
|
||||||
if let Some(definition) = goto_position_port_param_definition(self, &line_text, &doc, pos) {
|
|
||||||
return Some(definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
// match module name
|
|
||||||
if let Some(definition) = goto_module_declaration_definition(self, &token) {
|
|
||||||
return Some(definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
let byte_idx = file.text.pos_to_byte(&pos);
|
|
||||||
let global_scope = scope_tree.as_ref()?;
|
|
||||||
let def = global_scope.get_definition(&token, byte_idx, &doc)?;
|
|
||||||
|
|
||||||
let def_pos = file.text.byte_to_pos(def.byte_idx());
|
|
||||||
Some(GotoDefinitionResponse::Scalar(Location::new(
|
|
||||||
def.url(),
|
|
||||||
Range::new(def_pos, def_pos),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type ScopesAndDefs = Option<(Vec<Box<dyn Scope>>, Vec<Box<dyn Definition>>)>;
|
type ScopesAndDefs = Option<(Vec<Box<dyn Scope>>, Vec<Box<dyn Definition>>)>;
|
||||||
|
|
||||||
/// Take a given syntax node from a sv-parser syntax tree and extract out the definition/scope at
|
/// Take a given syntax node from a sv-parser syntax tree and extract out the definition/scope at
|
||||||
|
56
src/definition/sv.rs
Normal file
56
src/definition/sv.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use crate::utils::get_definition_token;
|
||||||
|
use crate::server::LSPServer;
|
||||||
|
use crate::sources::LSPSupport;
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
use log::info;
|
||||||
|
use tower_lsp::lsp_types::*;
|
||||||
|
|
||||||
|
use super::{feature::*, Definition, Scope};
|
||||||
|
|
||||||
|
pub fn goto_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
|
||||||
|
let doc = ¶ms.text_document_position_params.text_document.uri;
|
||||||
|
let pos = params.text_document_position_params.position;
|
||||||
|
let file_id = server.srcs.get_id(doc).to_owned();
|
||||||
|
server.srcs.wait_parse_ready(file_id, false);
|
||||||
|
let file = server.srcs.get_file(file_id)?;
|
||||||
|
let file = file.read().ok()?;
|
||||||
|
|
||||||
|
let line_text = file.text.line(pos.line as usize);
|
||||||
|
let token: String = get_definition_token(&line_text, pos);
|
||||||
|
|
||||||
|
// match include
|
||||||
|
if let Some(definition) = goto_include_definition(doc, &line_text, pos) {
|
||||||
|
return Some(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// match macro
|
||||||
|
if let Some(definition) = goto_macro_definition(server, &line_text, pos) {
|
||||||
|
return Some(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// match instance
|
||||||
|
|
||||||
|
let scope_tree = server.srcs.scope_tree.read().ok()?;
|
||||||
|
|
||||||
|
// match position port & param
|
||||||
|
if let Some(definition) = goto_position_port_param_definition(server, &line_text, doc, pos) {
|
||||||
|
return Some(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// match module name
|
||||||
|
if let Some(definition) = goto_module_declaration_definition(server, &token) {
|
||||||
|
return Some(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
let byte_idx = file.text.pos_to_byte(&pos);
|
||||||
|
let global_scope = scope_tree.as_ref()?;
|
||||||
|
let def = global_scope.get_definition(&token, byte_idx, doc)?;
|
||||||
|
|
||||||
|
let def_pos = file.text.byte_to_pos(def.byte_idx());
|
||||||
|
Some(GotoDefinitionResponse::Scalar(Location::new(
|
||||||
|
def.url(),
|
||||||
|
Range::new(def_pos, def_pos),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
@ -4,8 +4,9 @@ use log::info;
|
|||||||
use tower_lsp::lsp_types::*;
|
use tower_lsp::lsp_types::*;
|
||||||
use crate::{server::LSPServer, utils::{from_lsp_pos, srcpos_to_location, to_escape_path}};
|
use crate::{server::LSPServer, utils::{from_lsp_pos, srcpos_to_location, to_escape_path}};
|
||||||
|
|
||||||
pub fn goto_vhdl_definition(server: &LSPServer, params: TextDocumentPositionParams) -> Option<Location> {
|
pub fn goto_vhdl_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
|
||||||
let uri = ¶ms.text_document.uri;
|
let uri = ¶ms.text_document_position_params.text_document.uri;
|
||||||
|
let pos = params.text_document_position_params.position;
|
||||||
let file_id = server.srcs.get_id(&uri).to_owned();
|
let file_id = server.srcs.get_id(&uri).to_owned();
|
||||||
server.srcs.wait_parse_ready(file_id, false);
|
server.srcs.wait_parse_ready(file_id, false);
|
||||||
let projects = server.srcs.design_file_map.read().ok()?;
|
let projects = server.srcs.design_file_map.read().ok()?;
|
||||||
@ -30,6 +31,7 @@ pub fn goto_vhdl_definition(server: &LSPServer, params: TextDocumentPositionPara
|
|||||||
|
|
||||||
let source = project.get_source(&escape_path)?;
|
let source = project.get_source(&escape_path)?;
|
||||||
|
|
||||||
let ent = project.find_definition(&source, from_lsp_pos(params.position))?;
|
let ent = project.find_definition(&source, from_lsp_pos(pos))?;
|
||||||
Some(srcpos_to_location(ent.decl_pos()?))
|
let location = srcpos_to_location(ent.decl_pos()?);
|
||||||
|
Some(GotoDefinitionResponse::Scalar(location))
|
||||||
}
|
}
|
@ -3,11 +3,11 @@ use std::{path::PathBuf, str::FromStr};
|
|||||||
use log::info;
|
use log::info;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use ropey::RopeSlice;
|
use ropey::RopeSlice;
|
||||||
use tower_lsp::lsp_types::{Hover, HoverContents, LanguageString, MarkedString, MarkupContent, MarkupKind, Position, Range, Url};
|
use tower_lsp::lsp_types::{Hover, HoverContents, LanguageString, MarkedString, Position, Range, Url};
|
||||||
|
|
||||||
use crate::{core::fast_hdlparam::{Define, FastHdlparam}, server::LSPServer};
|
use crate::{core::fast_hdlparam::{Define, FastHdlparam}, server::LSPServer};
|
||||||
|
|
||||||
use super::{file::get_range_text, get_word_range_at_position, resolve_path, to_escape_path};
|
use super::{get_word_range_at_position, resolve_path, to_escape_path};
|
||||||
|
|
||||||
/// 将 4'b0011 分解为 ("b", "0011")
|
/// 将 4'b0011 分解为 ("b", "0011")
|
||||||
fn parse_digit_string(digit_string: &str) -> Option<(&str, &str)> {
|
fn parse_digit_string(digit_string: &str) -> Option<(&str, &str)> {
|
||||||
|
220
src/hover/mod.rs
220
src/hover/mod.rs
@ -1,225 +1,21 @@
|
|||||||
use std::sync::RwLockReadGuard;
|
|
||||||
|
|
||||||
use crate::definition::*;
|
|
||||||
use crate::server::LSPServer;
|
use crate::server::LSPServer;
|
||||||
use crate::sources::LSPSupport;
|
|
||||||
use crate::utils::*;
|
use crate::utils::*;
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
use log::info;
|
use log::info;
|
||||||
use regex::Regex;
|
|
||||||
use ropey::Rope;
|
|
||||||
use tower_lsp::lsp_types::*;
|
use tower_lsp::lsp_types::*;
|
||||||
|
|
||||||
pub mod feature;
|
pub mod feature;
|
||||||
use feature::*;
|
|
||||||
|
|
||||||
pub mod vhdl;
|
mod sv;
|
||||||
|
mod vhdl;
|
||||||
|
|
||||||
impl LSPServer {
|
impl LSPServer {
|
||||||
pub fn hover(&self, params: HoverParams) -> Option<Hover> {
|
pub fn hover(&self, params: HoverParams) -> Option<Hover> {
|
||||||
let doc: Url = params.text_document_position_params.text_document.uri;
|
let language_id = get_language_id_by_uri(¶ms.text_document_position_params.text_document.uri);
|
||||||
let pos: Position = params.text_document_position_params.position;
|
match language_id.as_str() {
|
||||||
let file_id: usize = self.srcs.get_id(&doc).to_owned();
|
"vhdl" => vhdl::hover(self, ¶ms),
|
||||||
self.srcs.wait_parse_ready(file_id, false);
|
"verilog" | "systemverilog" => sv::hover(self, ¶ms),
|
||||||
let file: std::sync::Arc<std::sync::RwLock<crate::sources::Source>> = self.srcs.get_file(file_id)?;
|
_ => None
|
||||||
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(self, &line_text, pos, &language_id) {
|
|
||||||
return Some(hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
let scope_tree: RwLockReadGuard<'_, Option<GenericScope>> = self.srcs.scope_tree.read().ok()?;
|
|
||||||
let global_scope = scope_tree.as_ref();
|
|
||||||
|
|
||||||
|
|
||||||
// match positional port param
|
|
||||||
if let Some(hover) = hover_position_port_param(self, &line_text, &doc, pos, &language_id) {
|
|
||||||
return Some(hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
// match module name
|
|
||||||
if let Some(hover) = hover_module_declaration(self, &token, &language_id) {
|
|
||||||
return Some(hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(global_scope) = global_scope {
|
|
||||||
|
|
||||||
// match 正常 symbol
|
|
||||||
if let Some(hover) = hover_common_symbol(self, 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) -> Option<Hover> {
|
|
||||||
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<String> = 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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::<MarkedString>::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));
|
|
||||||
} else {
|
|
||||||
// 这行只有代码,没有注释
|
|
||||||
comment_markdowns.push(MarkedString::LanguageString(LanguageString {
|
|
||||||
language: language_id.to_string(),
|
|
||||||
value: line_hover
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 否则,都是上方的注释
|
|
||||||
comment_markdowns.push(MarkedString::String(line_hover));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if comment_markdowns.len() > 0 {
|
|
||||||
return Some(Hover {
|
|
||||||
contents: HoverContents::Array(comment_markdowns),
|
|
||||||
range: None
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// 计算正常 symbol 的 hover
|
|
||||||
fn hover_common_symbol(
|
|
||||||
server: &LSPServer,
|
|
||||||
scope_tree: &GenericScope,
|
|
||||||
token: &String,
|
|
||||||
file: &RwLockReadGuard<'_, crate::sources::Source>,
|
|
||||||
doc: &Url,
|
|
||||||
pos: Position,
|
|
||||||
language_id: &String
|
|
||||||
) -> Option<Hover> {
|
|
||||||
|
|
||||||
let symbol_definition: GenericDec = scope_tree
|
|
||||||
.get_definition(token, file.text.pos_to_byte(&pos), doc)?;
|
|
||||||
|
|
||||||
// 根据 symbol 的类别进行额外的判断
|
|
||||||
|
|
||||||
let def_line = file.text.byte_to_line(symbol_definition.byte_idx());
|
|
||||||
make_hover_with_comment(&file.text, def_line, &language_id)
|
|
||||||
}
|
|
||||||
|
|
214
src/hover/sv.rs
Normal file
214
src/hover/sv.rs
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
use regex::Regex;
|
||||||
|
use ropey::Rope;
|
||||||
|
use tower_lsp::lsp_types::*;
|
||||||
|
use crate::{server::LSPServer, sources::LSPSupport};
|
||||||
|
use super::feature::*;
|
||||||
|
use std::sync::RwLockReadGuard;
|
||||||
|
|
||||||
|
use crate::definition::*;
|
||||||
|
|
||||||
|
use super::{get_definition_token, get_language_id_by_uri};
|
||||||
|
|
||||||
|
pub fn hover(server: &LSPServer, params: &HoverParams) -> Option<Hover> {
|
||||||
|
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<std::sync::RwLock<crate::sources::Source>> = 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) -> Option<Hover> {
|
||||||
|
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<String> = 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<MarkedString>::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));
|
||||||
|
} else {
|
||||||
|
// 这行只有代码,没有注释
|
||||||
|
comment_markdowns.push(MarkedString::LanguageString(LanguageString {
|
||||||
|
language: language_id.to_string(),
|
||||||
|
value: line_hover
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 否则,都是上方的注释
|
||||||
|
comment_markdowns.push(MarkedString::String(line_hover));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if comment_markdowns.len() > 0 {
|
||||||
|
return Some(Hover {
|
||||||
|
contents: HoverContents::Array(comment_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<Hover> {
|
||||||
|
|
||||||
|
let symbol_definition: GenericDec = scope_tree
|
||||||
|
.get_definition(token, file.text.pos_to_byte(&pos), doc)?;
|
||||||
|
|
||||||
|
// 根据 symbol 的类别进行额外的判断
|
||||||
|
|
||||||
|
let def_line = file.text.byte_to_line(symbol_definition.byte_idx());
|
||||||
|
make_hover_with_comment(&file.text, def_line, &language_id)
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
use tower_lsp::lsp_types::*;
|
use tower_lsp::lsp_types::*;
|
||||||
use crate::server::LSPServer;
|
use crate::server::LSPServer;
|
||||||
|
|
||||||
pub fn hover_vhdl(server: &LSPServer, param: &DocumentSymbolParams) -> Option<Hover> {
|
pub fn hover(server: &LSPServer, params: &HoverParams) -> Option<Hover> {
|
||||||
let design_file = server.srcs.design_file_map.write().unwrap();
|
let design_file = server.srcs.design_file_map.write().unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user