完成 dot completion
This commit is contained in:
parent
dc368a508f
commit
71e330b9e9
@ -1,10 +1,11 @@
|
||||
use std::{fs, path::PathBuf, str::FromStr};
|
||||
|
||||
use log::info;
|
||||
use regex::Regex;
|
||||
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> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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::sources::LSPSupport;
|
||||
use feature::include_path_completion;
|
||||
use log::info;
|
||||
use ropey::{Rope, RopeSlice};
|
||||
use crate::{server::LSPServer, utils::get_language_id_by_uri};
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
pub mod keyword;
|
||||
pub mod feature;
|
||||
|
||||
pub mod vhdl;
|
||||
mod vhdl;
|
||||
mod sv;
|
||||
|
||||
impl LSPServer {
|
||||
pub fn completion(&self, params: CompletionParams) -> Option<CompletionResponse> {
|
||||
let doc = params.text_document_position;
|
||||
let pos = doc.position;
|
||||
|
||||
let file_id = self.srcs.get_id(&doc.text_document.uri).to_owned();
|
||||
self.srcs.wait_parse_ready(file_id, false);
|
||||
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
|
||||
pub fn completion(&self, params: CompletionParams) -> Option<CompletionResponse> {
|
||||
let language_id = get_language_id_by_uri(¶ms.text_document_position.text_document.uri);
|
||||
match language_id.as_str() {
|
||||
"vhdl" => vhdl::completion(self, ¶ms),
|
||||
"verilog" | "systemverilog" => sv::completion(self, ¶ms),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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.
|
||||
/// 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 pos = doc.position;
|
||||
|
||||
@ -33,16 +33,17 @@ pub fn request_completion(server: &LSPServer, params: &CompletionParams) -> Opti
|
||||
None => return None
|
||||
};
|
||||
|
||||
let source = project.get_source(&escape_path)?;
|
||||
// let source = project.get_source(&escape_path)?;
|
||||
|
||||
let binding = escape_path;
|
||||
let file = binding.as_path();
|
||||
// 1) get source position, and source file
|
||||
let Some(source) = project.get_source(file) else {
|
||||
// Do not enable completions for files that are not part of the project
|
||||
return Some(CompletionList {
|
||||
|
||||
return Some(CompletionResponse::List(CompletionList {
|
||||
..Default::default()
|
||||
});
|
||||
}));
|
||||
};
|
||||
let cursor = from_lsp_pos(params.text_document_position.position);
|
||||
// 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()
|
||||
.map(|item| completion_item_to_lsp_item(server, item))
|
||||
.collect();
|
||||
|
||||
Some(CompletionList {
|
||||
Some(CompletionResponse::List(CompletionList {
|
||||
items: options,
|
||||
is_incomplete: true,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
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::sources::LSPSupport;
|
||||
|
||||
#[allow(unused)]
|
||||
use log::info;
|
||||
@ -16,58 +15,21 @@ pub use feature::*;
|
||||
pub mod extract_defs;
|
||||
pub use extract_defs::*;
|
||||
|
||||
pub mod vhdl;
|
||||
mod sv;
|
||||
mod vhdl;
|
||||
|
||||
impl LSPServer {
|
||||
pub fn goto_definition(&self, params: GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
|
||||
let doc = params.text_document_position_params.text_document.uri;
|
||||
let pos = params.text_document_position_params.position;
|
||||
let file_id = self.srcs.get_id(&doc).to_owned();
|
||||
self.srcs.wait_parse_ready(file_id, false);
|
||||
let file = self.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);
|
||||
let language_id = get_language_id_by_uri(¶ms.text_document_position_params.text_document.uri);
|
||||
match language_id.as_str() {
|
||||
"vhdl" => vhdl::goto_vhdl_definition(self, ¶ms),
|
||||
"verilog" | "systemverilog" => sv::goto_definition(self, ¶ms),
|
||||
_ => None
|
||||
}
|
||||
|
||||
// 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>>)>;
|
||||
|
||||
/// 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 crate::{server::LSPServer, utils::{from_lsp_pos, srcpos_to_location, to_escape_path}};
|
||||
|
||||
pub fn goto_vhdl_definition(server: &LSPServer, params: TextDocumentPositionParams) -> Option<Location> {
|
||||
let uri = ¶ms.text_document.uri;
|
||||
pub fn goto_vhdl_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
|
||||
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();
|
||||
server.srcs.wait_parse_ready(file_id, false);
|
||||
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 ent = project.find_definition(&source, from_lsp_pos(params.position))?;
|
||||
Some(srcpos_to_location(ent.decl_pos()?))
|
||||
let ent = project.find_definition(&source, from_lsp_pos(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 regex::Regex;
|
||||
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 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")
|
||||
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::sources::LSPSupport;
|
||||
use crate::utils::*;
|
||||
#[allow(unused)]
|
||||
use log::info;
|
||||
use regex::Regex;
|
||||
use ropey::Rope;
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
pub mod feature;
|
||||
use feature::*;
|
||||
|
||||
pub mod vhdl;
|
||||
mod sv;
|
||||
mod vhdl;
|
||||
|
||||
impl LSPServer {
|
||||
pub fn hover(&self, params: HoverParams) -> Option<Hover> {
|
||||
let doc: Url = params.text_document_position_params.text_document.uri;
|
||||
let pos: Position = params.text_document_position_params.position;
|
||||
let file_id: usize = self.srcs.get_id(&doc).to_owned();
|
||||
self.srcs.wait_parse_ready(file_id, false);
|
||||
let file: std::sync::Arc<std::sync::RwLock<crate::sources::Source>> = self.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(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;
|
||||
}
|
||||
let language_id = get_language_id_by_uri(¶ms.text_document_position_params.text_document.uri);
|
||||
match language_id.as_str() {
|
||||
"vhdl" => vhdl::hover(self, ¶ms),
|
||||
"verilog" | "systemverilog" => sv::hover(self, ¶ms),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
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 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();
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user