完成 dot completion

This commit is contained in:
锦恢 2024-10-01 16:42:34 +08:00
parent dc368a508f
commit 71e330b9e9
11 changed files with 667 additions and 453 deletions

View File

@ -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("");
@ -86,3 +87,172 @@ 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(&param);
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
}

View File

@ -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
let language_id = get_language_id_by_uri(&params.text_document_position.text_document.uri);
match language_id.as_str() {
"vhdl" => vhdl::completion(self, &params),
"verilog" | "systemverilog" => sv::completion(self, &params),
_ => None
}
}
}

182
src/completion/sv.rs Normal file
View 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 = &params.text_document_position;
let uri = &params.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 &params.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
}
}

View File

@ -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 = &params.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(

View File

@ -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(&params.text_document_position_params.text_document.uri);
match language_id.as_str() {
"vhdl" => vhdl::goto_vhdl_definition(self, &params),
"verilog" | "systemverilog" => sv::goto_definition(self, &params),
_ => 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
View 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 = &params.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),
)))
}

View File

@ -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 = &params.text_document.uri;
pub fn goto_vhdl_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
let uri = &params.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))
}

View File

@ -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)> {

View File

@ -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);
let language_id = get_language_id_by_uri(&params.text_document_position_params.text_document.uri);
match language_id.as_str() {
"vhdl" => vhdl::hover(self, &params),
"verilog" | "systemverilog" => sv::hover(self, &params),
_ => None
}
// 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(&ltrim) {
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
View 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 = &params.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(&ltrim) {
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)
}

View File

@ -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();