迁移 lsp 逻辑
This commit is contained in:
parent
b3d7a9faa1
commit
c3e88d63f9
88
src/completion/feature.rs
Normal file
88
src/completion/feature.rs
Normal file
@ -0,0 +1,88 @@
|
||||
use std::{fs, path::PathBuf, str::FromStr};
|
||||
|
||||
use log::info;
|
||||
use ropey::RopeSlice;
|
||||
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, CompletionList, Position, Url};
|
||||
|
||||
use crate::utils::{resolve_path, to_escape_path};
|
||||
|
||||
pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Option<CompletionList> {
|
||||
let line_text = line.as_str().unwrap_or("");
|
||||
if line_text.trim().starts_with("`include") {
|
||||
let character = pos.character as usize;
|
||||
let first_quote_idx = line_text.find("\"");
|
||||
let last_quote_idx = line_text.rfind("\"");
|
||||
|
||||
if first_quote_idx.is_none() || last_quote_idx.is_none() {
|
||||
return None;
|
||||
}
|
||||
let first_quote_idx = first_quote_idx.unwrap();
|
||||
let last_quote_idx = last_quote_idx.unwrap();
|
||||
|
||||
if character >= first_quote_idx && character <= last_quote_idx {
|
||||
let mut path_string = &line_text[(first_quote_idx + 1) .. last_quote_idx];
|
||||
if path_string.starts_with("./") || path_string.starts_with(".\\") {
|
||||
path_string = &path_string[2 ..];
|
||||
}
|
||||
|
||||
// 路径转换
|
||||
let path = match PathBuf::from_str(uri.path()) {
|
||||
Ok(path) => path,
|
||||
Err(error) => {
|
||||
info!("error happen in <goto_include_definition>: {:?}", error);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let escape_path = to_escape_path(&path);
|
||||
let escape_path = escape_path.to_str().unwrap_or("");
|
||||
if escape_path.len() == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(abs_path) = resolve_path(escape_path, path_string) {
|
||||
// 遍历该路径下所有文件和文件夹,并构建新的自动补全列表
|
||||
info!("completion active in {:?}", abs_path);
|
||||
if abs_path.is_dir() {
|
||||
let mut completion_items = Vec::<CompletionItem>::new();
|
||||
for entry in fs::read_dir(abs_path).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let path: PathBuf = entry.path();
|
||||
let file_name = match path.file_name() {
|
||||
Some(os_str) => os_str.to_str(),
|
||||
None => continue
|
||||
};
|
||||
|
||||
if file_name.is_none() {
|
||||
continue;
|
||||
}
|
||||
let file_name = file_name.unwrap();
|
||||
|
||||
if path.is_dir() {
|
||||
completion_items.push(CompletionItem {
|
||||
label: file_name.to_string(),
|
||||
kind: Some(CompletionItemKind::FOLDER),
|
||||
..CompletionItem::default()
|
||||
});
|
||||
}
|
||||
|
||||
if path.is_file() {
|
||||
completion_items.push(CompletionItem {
|
||||
label: file_name.to_string(),
|
||||
kind: Some(CompletionItemKind::FILE),
|
||||
..CompletionItem::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if !completion_items.is_empty() {
|
||||
return Some(CompletionList {
|
||||
is_incomplete: false,
|
||||
items: completion_items
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
@ -133,7 +133,7 @@ pub const KEYWORDS: &[(&str, &str)] = &[
|
||||
("implies", ""),
|
||||
("import", ""),
|
||||
("incdir", ""),
|
||||
("include", ""),
|
||||
("include", "`include \"$1\""),
|
||||
("initial", ""),
|
||||
("inout", ""),
|
||||
("input", ""),
|
||||
|
@ -1,13 +1,18 @@
|
||||
use crate::server::LSPServer;
|
||||
use crate::sources::LSPSupport;
|
||||
use feature::include_path_completion;
|
||||
use log::info;
|
||||
use ropey::{Rope, RopeSlice};
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
pub mod keyword;
|
||||
pub mod feature;
|
||||
|
||||
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)?;
|
||||
@ -19,6 +24,7 @@ impl LSPServer {
|
||||
);
|
||||
|
||||
// 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 {
|
||||
@ -37,6 +43,14 @@ impl LSPServer {
|
||||
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,
|
||||
}
|
||||
}
|
||||
@ -48,7 +62,6 @@ impl LSPServer {
|
||||
&doc.text_document.uri,
|
||||
)?;
|
||||
|
||||
|
||||
// complete keywords
|
||||
comps.items.extend::<Vec<CompletionItem>>(
|
||||
self.key_comps
|
||||
@ -100,6 +113,8 @@ impl LSPServer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// get the previous non-whitespace character
|
||||
fn prev_char(text: &Rope, pos: &Position) -> char {
|
||||
let char_idx = text.pos_to_char(pos);
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::sources::LSPSupport;
|
||||
use log::info;
|
||||
use ropey::Rope;
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
@ -61,26 +62,25 @@ pub fn copy_scopes(scopes: &[Box<dyn Scope>]) -> Vec<Box<dyn Scope>> {
|
||||
scope_decs
|
||||
}
|
||||
|
||||
/// A definition of any SystemVerilog variable or construct
|
||||
/// 用于定义一个 Symbol 或者 Scope 的内部变量
|
||||
pub trait Definition: std::fmt::Debug + Sync + Send {
|
||||
// identifier
|
||||
/// symbol 或者 scope 的名字,identity 的缩写
|
||||
fn ident(&self) -> String;
|
||||
// byte index in file of definition
|
||||
/// 相对于文本的偏移量,可以利用 Rope 的 `byte_to_pos` 函数转换成 Position
|
||||
fn byte_idx(&self) -> usize;
|
||||
// url pointing to the file the definition is in
|
||||
/// 当前 symbol 所在文件的 url
|
||||
fn url(&self) -> Url;
|
||||
// cleaned up text of the definition
|
||||
/// 输出 Definition 的字符
|
||||
fn type_str(&self) -> String;
|
||||
// the kind of this definition, for use in completions
|
||||
/// 类别,用于自动补全
|
||||
fn completion_kind(&self) -> CompletionItemKind;
|
||||
// the kind of this definition, for use in showing document symbols
|
||||
// for some reason this kind is different than CompletionItemKind
|
||||
/// 类别,用于文件大纲
|
||||
fn symbol_kind(&self) -> SymbolKind;
|
||||
// the kind of this definition, simplified for internal use
|
||||
/// 类别,内部使用
|
||||
fn def_type(&self) -> DefinitionType;
|
||||
// whether the definition identifier starts with the given token
|
||||
/// 等价于 self.ident.starts_with
|
||||
fn starts_with(&self, token: &str) -> bool;
|
||||
// constructs the completion for this definition
|
||||
/// 构造 completion 的 item
|
||||
fn completion(&self) -> CompletionItem;
|
||||
fn dot_completion(&self, scope_tree: &GenericScope) -> Vec<CompletionItem>;
|
||||
}
|
||||
@ -173,17 +173,6 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// 获取当前的 scope,如果返回 None,则说明当前 scope 为 global
|
||||
// fn get_current_scope(&self, byte_idx: usize, url: &Url) -> Option<Box<dyn Scope>> {
|
||||
|
||||
// for scope in self.scopes() {
|
||||
// if &scope.url() == url && scope.start() <= byte_idx && byte_idx <= scope.end() {
|
||||
// return scope.get_current_scope(byte_idx, url);
|
||||
// }
|
||||
// }
|
||||
// self
|
||||
// }
|
||||
|
||||
/// 根据输入的 token,计算出这个 token 在 scope 中的定义
|
||||
/// 比如输入 clock,则返回 clock 这个变量在哪里被定义,没有则返回 None
|
||||
/// - `token`: 需要查找定义的完整的单词
|
||||
@ -192,6 +181,7 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send {
|
||||
fn get_definition(&self, token: &str, byte_idx: usize, url: &Url) -> Option<GenericDec> {
|
||||
let mut definition: Option<GenericDec> = None;
|
||||
|
||||
// 递归进入最浅层的
|
||||
for scope in self.scopes() {
|
||||
if &scope.url() == url && scope.start() <= byte_idx && byte_idx <= scope.end() {
|
||||
definition = scope.get_definition(token, byte_idx, url);
|
||||
@ -203,6 +193,7 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send {
|
||||
// 优先找 定义,再找 scope
|
||||
for def in self.defs() {
|
||||
if def.ident() == token {
|
||||
info!("find definition: {:?}", def);
|
||||
return Some(GenericDec {
|
||||
ident: def.ident(),
|
||||
byte_idx: def.byte_idx(),
|
||||
@ -210,7 +201,7 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send {
|
||||
type_str: def.type_str(),
|
||||
completion_kind: def.completion_kind(),
|
||||
symbol_kind: def.symbol_kind(),
|
||||
def_type: DefinitionType::Net,
|
||||
def_type: def.def_type(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -107,5 +107,9 @@ pub fn goto_macro_definition(server: &LSPServer, line: &RopeSlice, pos: Position
|
||||
|
||||
|
||||
pub fn goto_instance_definition() {
|
||||
|
||||
}
|
||||
|
||||
pub fn goto_module_definition() {
|
||||
|
||||
}
|
@ -73,7 +73,7 @@ fn convert_to_sign_unsign<'a>(tag: &'a str, digit_string: &str) -> Option<(Strin
|
||||
}
|
||||
|
||||
/// 将 1'b1 翻译成 10进制
|
||||
pub fn match_format_digit(line: &RopeSlice, pos: Position, language_id: &str) -> Option<Hover> {
|
||||
pub fn hover_format_digit(line: &RopeSlice, pos: Position, language_id: &str) -> Option<Hover> {
|
||||
let regex = Regex::new(r"[0-9'bho]").unwrap();
|
||||
let token_result = get_word_range_at_position(line, pos, regex);
|
||||
|
||||
|
139
src/hover/mod.rs
139
src/hover/mod.rs
@ -5,7 +5,7 @@ use crate::server::LSPServer;
|
||||
use crate::sources::LSPSupport;
|
||||
use crate::utils::*;
|
||||
use regex::Regex;
|
||||
use ropey::Rope;
|
||||
use ropey::{Rope, RopeSlice};
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
pub mod feature;
|
||||
@ -40,13 +40,13 @@ impl LSPServer {
|
||||
|
||||
if let Some(global_scope) = global_scope {
|
||||
// match 正常 symbol
|
||||
if let Some(hover) = match_common_symbol(global_scope, &token, &file, &doc, pos, &language_id) {
|
||||
if let Some(hover) = hover_common_symbol(global_scope, &token, &file, &doc, pos, &language_id) {
|
||||
return Some(hover);
|
||||
}
|
||||
}
|
||||
|
||||
// match digit 5'b00110
|
||||
if let Some(hover) = match_format_digit(&line_text, pos, &language_id) {
|
||||
if let Some(hover) = hover_format_digit(&line_text, pos, &language_id) {
|
||||
return Some(hover);
|
||||
}
|
||||
|
||||
@ -57,10 +57,18 @@ impl LSPServer {
|
||||
|
||||
|
||||
|
||||
/// get the hover information
|
||||
fn get_hover(doc: &Rope, line: usize) -> String {
|
||||
fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str) -> Option<Hover> {
|
||||
if line == 0 {
|
||||
return doc.line(line).to_string();
|
||||
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;
|
||||
@ -97,24 +105,91 @@ fn get_hover(doc: &Rope, line: usize) -> String {
|
||||
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 line = line.into_owned();
|
||||
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 mut result: Vec<String> = Vec::new();
|
||||
for line in hover {
|
||||
if let Some(stripped) = line.strip_prefix(<rim) {
|
||||
let line_hover = line_handler(stripped);
|
||||
result.push(line_hover);
|
||||
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 {
|
||||
let line_hover = line_handler(&line);
|
||||
result.push(line_hover);
|
||||
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));
|
||||
}
|
||||
}
|
||||
result.join("").trim_end().to_owned()
|
||||
|
||||
if comment_markdowns.len() > 0 {
|
||||
return Some(Hover {
|
||||
contents: HoverContents::Array(comment_markdowns),
|
||||
range: None
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn match_common_symbol(
|
||||
|
||||
|
||||
/// 计算正常 symbol 的 hover
|
||||
fn hover_common_symbol(
|
||||
scope_tree: &GenericScope,
|
||||
token: &String,
|
||||
file: &RwLockReadGuard<'_, crate::sources::Source>,
|
||||
@ -123,18 +198,26 @@ fn match_common_symbol(
|
||||
language_id: &String
|
||||
) -> Option<Hover> {
|
||||
|
||||
let def: GenericDec = scope_tree
|
||||
let symbol_definition: GenericDec = scope_tree
|
||||
.get_definition(token, file.text.pos_to_byte(&pos), doc)?;
|
||||
|
||||
let def_line = file.text.byte_to_line(def.byte_idx());
|
||||
let language_string = LanguageString {
|
||||
language: language_id.to_string(),
|
||||
value: get_hover(&file.text, def_line)
|
||||
};
|
||||
let markdown = MarkedString::LanguageString(language_string);
|
||||
|
||||
Some(Hover {
|
||||
contents: HoverContents::Scalar(markdown),
|
||||
range: None,
|
||||
})
|
||||
// 根据 symbol 的类别进行额外的判断
|
||||
|
||||
|
||||
let def_line = file.text.byte_to_line(symbol_definition.byte_idx());
|
||||
make_hover_with_comment(&file.text, def_line, &language_id)
|
||||
}
|
||||
|
||||
/// 计算 position 赋值的 port 或者 param
|
||||
/// 比如 .clk ( clk ) 中的 .
|
||||
fn hover_position_port(line: &RopeSlice, pos: Position, language_id: &str) -> Option<Hover> {
|
||||
let position_port_regex = Regex::new(r"[.0-9a-zA-Z]").unwrap();
|
||||
if let Some((port_name, range)) = get_word_range_at_position(line, pos, position_port_regex) {
|
||||
if port_name.starts_with(".") {
|
||||
let port_name = &port_name[1..];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
@ -196,6 +196,8 @@ impl LanguageServer for Backend {
|
||||
".".to_string(),
|
||||
"$".to_string(),
|
||||
"`".to_string(),
|
||||
"\"".to_string(),
|
||||
"/".to_string()
|
||||
]),
|
||||
work_done_progress_options: WorkDoneProgressOptions {
|
||||
work_done_progress: None,
|
||||
|
Loading…
x
Reference in New Issue
Block a user