update
This commit is contained in:
parent
6afe6a1077
commit
3a64906029
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -205,7 +205,6 @@ version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"flexi_logger",
|
||||
"futures",
|
||||
"log",
|
||||
"once_cell",
|
||||
"path-clean",
|
||||
@ -286,7 +285,6 @@ checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
@ -309,17 +307,6 @@ version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.28"
|
||||
|
@ -7,7 +7,6 @@ edition = "2018"
|
||||
[dependencies]
|
||||
sv-parser = { version = "0.13.3", path = "sv-parser/sv-parser"}
|
||||
once_cell = "1.8"
|
||||
futures = "0.3"
|
||||
percent-encoding = "2.1.0"
|
||||
log = "0.4.19"
|
||||
tower-lsp = "0.20.0"
|
||||
|
@ -174,7 +174,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::definition::def_types::Scope;
|
||||
use crate::definition::get_scopes;
|
||||
use crate::sources::{parse, LSPSupport};
|
||||
use crate::sources::{recovery_sv_parse, LSPSupport};
|
||||
use crate::support::test_init;
|
||||
use ropey::Rope;
|
||||
|
||||
@ -651,7 +651,7 @@ endinterface
|
||||
|
||||
let doc = Rope::from_str(&text);
|
||||
let url = Url::parse("file:///test.sv").unwrap();
|
||||
let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap();
|
||||
let syntax_tree = recovery_sv_parse(&doc, &url, &None, &Vec::new()).unwrap();
|
||||
let scope_tree = get_scopes(&syntax_tree, &url).unwrap();
|
||||
let pos = Position::new(8, 9);
|
||||
let token = get_completion_token(&doc, doc.line(pos.line as usize), pos);
|
@ -1,25 +1,35 @@
|
||||
use std::fs::File;
|
||||
use std::fs::{self, File};
|
||||
use std::io::BufRead;
|
||||
use std::{collections::HashMap, io::BufReader};
|
||||
use std::path::PathBuf;
|
||||
use anyhow::Error;
|
||||
use percent_encoding::percent_decode_str;
|
||||
use ropey::Rope;
|
||||
use tower_lsp::lsp_types::Url;
|
||||
use std::env::consts::OS;
|
||||
use sv_parser::{parse_sv, unwrap_node, Locate, RefNode, SyntaxTree};
|
||||
|
||||
use crate::sources::recovery_sv_parse;
|
||||
|
||||
use super::fast_hdlparam::{FastHdlparam, Macro};
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn sv_parser(path: &str) -> Option<FastHdlparam> {
|
||||
// The path of SystemVerilog source file
|
||||
let path = PathBuf::from(path);
|
||||
// The list of defined macros
|
||||
let defines = HashMap::new();
|
||||
// The list of include paths
|
||||
let includes: Vec<PathBuf> = Vec::new();
|
||||
|
||||
let result = parse_sv(&path, &defines, &includes, false, true);
|
||||
if let Ok((syntax_tree, _)) = result {
|
||||
let text = match fs::read_to_string(&path) {
|
||||
Ok(text) => text,
|
||||
Err(_) => return None
|
||||
};
|
||||
|
||||
let doc = Rope::from_str(&text);
|
||||
let uri = Url::from_file_path(&path).unwrap();
|
||||
let result = recovery_sv_parse(&doc, &uri, &None, &includes);
|
||||
|
||||
if let Some(syntax_tree) = result {
|
||||
if let Ok(hdlparam) = make_fast_from_syntaxtree(&syntax_tree, &path) {
|
||||
return Some(hdlparam);
|
||||
}
|
||||
|
@ -12,9 +12,9 @@ use tower_lsp::lsp_types::*;
|
||||
use crate::core::fast_hdlparam::FastHdlparam;
|
||||
use crate::core::sv_parser::make_fast_from_syntaxtree;
|
||||
|
||||
use crate::definition::get_language_id_by_uri;
|
||||
use crate::utils::*;
|
||||
use crate::server::{Backend, GLOBAL_BACKEND};
|
||||
use crate::sources::parse;
|
||||
use crate::sources::recovery_sv_parse;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -85,7 +85,8 @@ fn make_textdocumenitem_from_path(path_buf: &PathBuf) -> Option<TextDocumentItem
|
||||
|
||||
/// 前端交互接口: do_fast,输入文件路径,计算出对应的 fast 结构
|
||||
pub fn do_fast(path: String) -> Result<FastHdlparam> {
|
||||
info!("parse hdl path: {:?}", path);
|
||||
info!("parse fast {}", path);
|
||||
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
let doc = match make_textdocumenitem_from_path(&path_buf) {
|
||||
@ -105,8 +106,11 @@ pub fn do_fast(path: String) -> Result<FastHdlparam> {
|
||||
let text = Rope::from(doc.text);
|
||||
// fast 解析不需要 include
|
||||
let includes: Vec<PathBuf> = Vec::new();
|
||||
|
||||
|
||||
info!("before parse {}", path);
|
||||
|
||||
let parse_result = parse(
|
||||
let parse_result = recovery_sv_parse(
|
||||
&text,
|
||||
&uri,
|
||||
&None,
|
||||
@ -115,6 +119,7 @@ pub fn do_fast(path: String) -> Result<FastHdlparam> {
|
||||
|
||||
if let Some(syntax_tree) = parse_result {
|
||||
if let Ok(hdlparam) = make_fast_from_syntaxtree(&syntax_tree, &path_buf) {
|
||||
info!("after parse {}, get hdlparam {:?}", path, hdlparam);
|
||||
return Ok(hdlparam);
|
||||
}
|
||||
}
|
||||
@ -154,7 +159,7 @@ impl Notification for UpdateFastNotification {
|
||||
type Params = Self;
|
||||
}
|
||||
|
||||
|
||||
#[allow(unused)]
|
||||
pub async fn update_fast_to_client(fast: FastHdlparam, path: &PathBuf) {
|
||||
// info!("send fast to foreend {:?}", fast);
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::definition::def_types::*;
|
||||
use crate::definition::match_definitions;
|
||||
use log::info;
|
||||
use sv_parser::*;
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use ropey::RopeSlice;
|
||||
use tower_lsp::lsp_types::{GotoDefinitionResponse, LocationLink, Position, Range, Url};
|
||||
|
||||
use crate::utils::resolve_path;
|
||||
|
||||
/// 跳转到定义
|
||||
pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Option<GotoDefinitionResponse> {
|
||||
let line_text = line.as_str().unwrap_or("");
|
||||
@ -22,17 +22,8 @@ pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Op
|
||||
if path_string.starts_with("./") || path_string.starts_with(".\\") {
|
||||
path_string = &path_string[2 ..];
|
||||
}
|
||||
// 此处得到路径,根据是否为绝对路径进行判断
|
||||
let path = Path::new(path_string);
|
||||
let mut abs_path: PathBuf;
|
||||
if path.is_absolute() {
|
||||
abs_path = PathBuf::from(path);
|
||||
} else {
|
||||
let base = Path::new(uri.path()).parent()?;
|
||||
abs_path = base.join(path);
|
||||
}
|
||||
|
||||
if abs_path.exists() {
|
||||
if let Some(abs_path) = resolve_path(uri.path(), path_string) {
|
||||
let target_uri = match Url::from_file_path(abs_path.as_path()) {
|
||||
Ok(uri) => uri,
|
||||
Err(_) => return None
|
||||
@ -55,6 +46,7 @@ pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Op
|
||||
let links = GotoDefinitionResponse::Link(link);
|
||||
return Some(links);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
use crate::definition::extract_defs::get_ident;
|
||||
use crate::{definition::extract_defs::get_ident, utils::get_definition_token};
|
||||
use crate::server::LSPServer;
|
||||
use crate::sources::LSPSupport;
|
||||
|
||||
use log::info;
|
||||
use ropey::{Rope, RopeSlice};
|
||||
use sv_parser::*;
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
@ -29,7 +28,6 @@ impl LSPServer {
|
||||
let line_text = file.text.line(pos.line as usize);
|
||||
let token: String = get_definition_token(&line_text, pos);
|
||||
|
||||
info!("definition token: {:?}", token);
|
||||
let include_definition = goto_include_definition(&doc, &line_text, pos);
|
||||
if include_definition.is_some() {
|
||||
return include_definition;
|
||||
@ -49,35 +47,6 @@ 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 scope_tree: std::sync::RwLockReadGuard<'_, Option<GenericScope>> = self.srcs.scope_tree.read().ok()?;
|
||||
|
||||
let def: GenericDec = scope_tree
|
||||
.as_ref()?
|
||||
.get_definition(&token, file.text.pos_to_byte(&pos), &doc)?;
|
||||
|
||||
let def_line = file.text.byte_to_line(def.byte_idx());
|
||||
let language_id = get_language_id_by_uri(&doc);
|
||||
|
||||
Some(Hover {
|
||||
contents: HoverContents::Scalar(MarkedString::LanguageString(LanguageString {
|
||||
language: language_id,
|
||||
value: get_hover(&file.text, def_line),
|
||||
})),
|
||||
range: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn document_symbol(&self, params: DocumentSymbolParams) -> Option<DocumentSymbolResponse> {
|
||||
let uri = params.text_document.uri;
|
||||
let file_id = self.srcs.get_id(&uri).to_owned();
|
||||
@ -137,47 +106,6 @@ fn all_identifiers(syntax_tree: &SyntaxTree, token: &str) -> Vec<(String, usize)
|
||||
idents
|
||||
}
|
||||
|
||||
/// retrieve the token the user invoked goto definition or hover on
|
||||
fn get_definition_token(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();
|
||||
while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') {
|
||||
token.push(c.unwrap());
|
||||
c = line_iter.prev();
|
||||
}
|
||||
token = token.chars().rev().collect();
|
||||
line_iter = line.chars();
|
||||
for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) {
|
||||
line_iter.next();
|
||||
}
|
||||
let mut c = line_iter.next();
|
||||
while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') {
|
||||
token.push(c.unwrap());
|
||||
c = line_iter.next();
|
||||
}
|
||||
token
|
||||
}
|
||||
|
||||
|
||||
pub fn get_language_id_by_uri(uri: &Url) -> String {
|
||||
let path = uri.path();
|
||||
let ext_name = std::path::Path::new(path)
|
||||
.extension()
|
||||
.and_then(std::ffi::OsStr::to_str)
|
||||
.unwrap_or("");
|
||||
|
||||
match ext_name {
|
||||
"vhd" | "vhdl" | "vho" | "vht" => "vhdl".to_string(),
|
||||
"v" | "V" | "vh" | "vl" => "verilog".to_string(),
|
||||
"sv" | "svh" => "systemverilog".to_string(),
|
||||
_ => "plaintext".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type ScopesAndDefs = Option<(Vec<Box<dyn Scope>>, Vec<Box<dyn Definition>>)>;
|
||||
|
||||
@ -340,197 +268,3 @@ pub fn get_scopes(syntax_tree: &SyntaxTree, url: &Url) -> Option<GenericScope> {
|
||||
global_scope.scopes.append(&mut scopes);
|
||||
Some(global_scope)
|
||||
}
|
||||
|
||||
/// get the hover information
|
||||
fn get_hover(doc: &Rope, line: usize) -> String {
|
||||
if line == 0 {
|
||||
return doc.line(line).to_string();
|
||||
}
|
||||
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;
|
||||
|
||||
// iterate upwards from the definition, and grab the comments
|
||||
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 mut result: Vec<String> = Vec::new();
|
||||
for i in hover {
|
||||
if let Some(stripped) = i.strip_prefix(<rim) {
|
||||
result.push(stripped.to_owned());
|
||||
} else {
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
result.join("").trim_end().to_owned()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::sources::{parse, LSPSupport};
|
||||
use crate::support::test_init;
|
||||
use ropey::Rope;
|
||||
use std::fs::read_to_string;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn test_definition_token() {
|
||||
test_init();
|
||||
let line = Rope::from_str("assign ab_c[2:0] = 3'b000;");
|
||||
let line_text = line.line(0);
|
||||
let token = get_definition_token(&line_text, Position::new(0, 10));
|
||||
assert_eq!(token, "ab_c".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_definition() {
|
||||
test_init();
|
||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
d.push("test_data/definition_test.sv");
|
||||
let text = read_to_string(d).unwrap();
|
||||
let doc = Rope::from_str(&text);
|
||||
let url = Url::parse("file:///test_data/definition_test.sv").unwrap();
|
||||
let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap();
|
||||
let scope_tree = get_scopes(&syntax_tree, &url).unwrap();
|
||||
|
||||
let line_text = doc.line(3);
|
||||
let token = get_definition_token(&line_text, Position::new(3, 13));
|
||||
for def in scope_tree.defs {
|
||||
if token == def.ident() {
|
||||
assert_eq!(doc.byte_to_pos(def.byte_idx()), Position::new(3, 9))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hover() {
|
||||
test_init();
|
||||
let text = r#"
|
||||
// module test
|
||||
// test module
|
||||
module test;
|
||||
/* a */
|
||||
logic a;
|
||||
/**
|
||||
* b
|
||||
*/
|
||||
logic b;
|
||||
endmodule"#;
|
||||
let doc = Rope::from_str(text);
|
||||
eprintln!("{}", get_hover(&doc, 2));
|
||||
assert_eq!(
|
||||
get_hover(&doc, 3),
|
||||
r#"// module test
|
||||
// test module
|
||||
module test;"#
|
||||
.to_owned()
|
||||
);
|
||||
assert_eq!(
|
||||
get_hover(&doc, 9),
|
||||
r#"/**
|
||||
* b
|
||||
*/
|
||||
logic b;"#
|
||||
.to_owned()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symbols() {
|
||||
test_init();
|
||||
let text = r#"
|
||||
module test;
|
||||
logic a;
|
||||
logic b;
|
||||
endmodule"#;
|
||||
let doc = Rope::from_str(&text);
|
||||
let url = Url::parse("file:///test.sv").unwrap();
|
||||
let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap();
|
||||
let scope_tree = get_scopes(&syntax_tree, &url).unwrap();
|
||||
let symbol = scope_tree.document_symbols(&url, &doc);
|
||||
let symbol = symbol.get(0).unwrap();
|
||||
assert_eq!(&symbol.name, "test");
|
||||
let names: Vec<String> = symbol
|
||||
.children
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|x| x.name.clone())
|
||||
.collect();
|
||||
assert!(names.contains(&"a".to_string()));
|
||||
assert!(names.contains(&"b".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_highlight() {
|
||||
test_init();
|
||||
let text = r#"
|
||||
module test;
|
||||
logic clk;
|
||||
assign clk = 1'b1;
|
||||
endmodule"#;
|
||||
let doc = Rope::from_str(&text);
|
||||
let url = Url::parse("file:///test.sv").unwrap();
|
||||
let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap();
|
||||
let scope_tree = get_scopes(&syntax_tree, &url).unwrap();
|
||||
let references = all_identifiers(&syntax_tree, "clk");
|
||||
let highlights = scope_tree.document_highlights(
|
||||
&url,
|
||||
&doc,
|
||||
references,
|
||||
doc.pos_to_byte(&Position::new(2, 8)),
|
||||
);
|
||||
let expected = vec![
|
||||
DocumentHighlight {
|
||||
range: Range {
|
||||
start: Position {
|
||||
line: 2,
|
||||
character: 8,
|
||||
},
|
||||
end: Position {
|
||||
line: 2,
|
||||
character: 11,
|
||||
},
|
||||
},
|
||||
kind: None,
|
||||
},
|
||||
DocumentHighlight {
|
||||
range: Range {
|
||||
start: Position {
|
||||
line: 3,
|
||||
character: 9,
|
||||
},
|
||||
end: Position {
|
||||
line: 3,
|
||||
character: 12,
|
||||
},
|
||||
},
|
||||
kind: None,
|
||||
},
|
||||
];
|
||||
assert_eq!(highlights, expected)
|
||||
}
|
||||
}
|
20
src/hover/feature.rs
Normal file
20
src/hover/feature.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use log::info;
|
||||
use regex::Regex;
|
||||
use ropey::RopeSlice;
|
||||
use tower_lsp::lsp_types::{Hover, Position};
|
||||
|
||||
use super::get_word_range_at_position;
|
||||
|
||||
|
||||
|
||||
/// 将 1'b1 翻译成 10进制
|
||||
pub fn match_format_digit(line: &RopeSlice, pos: Position) -> Option<Hover> {
|
||||
let line_text = line.as_str().unwrap_or("");
|
||||
let regex = Regex::new(r"[0-9'bho]").unwrap();
|
||||
let digit_string = get_word_range_at_position(line, pos, regex);
|
||||
if digit_string.len() > 0 {
|
||||
info!("current digit: {}", digit_string);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
158
src/hover/mod.rs
Normal file
158
src/hover/mod.rs
Normal file
@ -0,0 +1,158 @@
|
||||
use std::{fmt::format, sync::RwLockReadGuard};
|
||||
|
||||
use crate::definition::*;
|
||||
use crate::server::LSPServer;
|
||||
use crate::sources::LSPSupport;
|
||||
use crate::utils::*;
|
||||
use ropey::{Rope, RopeSlice};
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
pub mod feature;
|
||||
use feature::*;
|
||||
|
||||
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) = match_include(&doc, &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();
|
||||
|
||||
if let Some(global_scope) = global_scope {
|
||||
// match 正常 symbol
|
||||
if let Some(hover) = match_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) {
|
||||
return Some(hover);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// get the hover information
|
||||
fn get_hover(doc: &Rope, line: usize) -> String {
|
||||
if line == 0 {
|
||||
return doc.line(line).to_string();
|
||||
}
|
||||
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;
|
||||
|
||||
// iterate upwards from the definition, and grab the comments
|
||||
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 mut result: Vec<String> = Vec::new();
|
||||
for i in hover {
|
||||
if let Some(stripped) = i.strip_prefix(<rim) {
|
||||
result.push(stripped.to_owned());
|
||||
} else {
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
result.join("").trim_end().to_owned()
|
||||
}
|
||||
|
||||
|
||||
fn match_include(uri: &Url, line: &RopeSlice, pos: Position, language_id: &String) -> Option<Hover> {
|
||||
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 ..];
|
||||
}
|
||||
|
||||
if let Some(abs_path) = resolve_path(uri.path(), path_string) {
|
||||
let content = format!("{:?}", abs_path);
|
||||
let language_string = LanguageString {
|
||||
language: language_id.to_string(),
|
||||
value: content
|
||||
};
|
||||
let markdown = MarkedString::LanguageString(language_string);
|
||||
return Some(Hover { contents: HoverContents::Scalar(markdown), range: None })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn match_common_symbol(
|
||||
scope_tree: &GenericScope,
|
||||
token: &String,
|
||||
file: &RwLockReadGuard<'_, crate::sources::Source>,
|
||||
doc: &Url,
|
||||
pos: Position,
|
||||
language_id: &String
|
||||
) -> Option<Hover> {
|
||||
|
||||
let def: 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,
|
||||
})
|
||||
}
|
19
src/lib.rs
19
src/lib.rs
@ -1,12 +1,31 @@
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
pub mod core;
|
||||
|
||||
// 自动补全
|
||||
pub mod completion;
|
||||
|
||||
// 定义跳转
|
||||
pub mod definition;
|
||||
|
||||
// 悬停提示
|
||||
pub mod hover;
|
||||
|
||||
// 诊断
|
||||
pub mod diagnostics;
|
||||
|
||||
// 格式化
|
||||
pub mod format;
|
||||
|
||||
// 基础工具
|
||||
pub mod utils;
|
||||
|
||||
pub mod server;
|
||||
pub mod sources;
|
||||
pub mod support;
|
||||
|
||||
// 自定义发送请求
|
||||
pub mod custom_request;
|
||||
|
||||
// 测试模块
|
||||
pub mod test;
|
@ -10,6 +10,8 @@ use tower_lsp::{LspService, Server};
|
||||
mod core;
|
||||
mod completion;
|
||||
mod definition;
|
||||
mod hover;
|
||||
mod utils;
|
||||
mod diagnostics;
|
||||
mod format;
|
||||
mod server;
|
||||
|
@ -19,7 +19,6 @@ use std::str::FromStr;
|
||||
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||
use std::thread;
|
||||
use sv_parser::*;
|
||||
use sv_parser::sv_parser_pp_range;
|
||||
use thread::JoinHandle;
|
||||
use tower_lsp::lsp_types::*;
|
||||
use walkdir::WalkDir;
|
||||
@ -33,11 +32,6 @@ macro_rules! unwrap_result {
|
||||
};
|
||||
}
|
||||
|
||||
struct CustomRange {
|
||||
pub begin: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
impl LSPServer {
|
||||
pub fn did_open(&self, params: DidOpenTextDocumentParams) -> PublishDiagnosticsParams {
|
||||
info!("[LSPServer] did open");
|
||||
@ -68,11 +62,12 @@ impl LSPServer {
|
||||
}
|
||||
|
||||
pub fn did_change(&self, params: DidChangeTextDocumentParams) {
|
||||
info!("[LSPServer] did change");
|
||||
info!("[LSPServer] did change, change content: {:?}", params.content_changes);
|
||||
|
||||
let file_id = self.srcs.get_id(¶ms.text_document.uri);
|
||||
let file = self.srcs.get_file(file_id).unwrap();
|
||||
let mut file = file.write().unwrap();
|
||||
|
||||
// loop through changes and apply
|
||||
for change in params.content_changes {
|
||||
if change.range.is_none() {
|
||||
@ -161,6 +156,7 @@ pub struct Sources {
|
||||
// file metadata
|
||||
pub meta: Arc<RwLock<Vec<Arc<RwLock<SourceMeta>>>>>,
|
||||
// all source files are indexed into this tree, which can then
|
||||
|
||||
// be used for completion, name resolution
|
||||
pub scope_tree: Arc<RwLock<Option<GenericScope>>>,
|
||||
// include directories, passed to parser to resolve `include
|
||||
@ -231,7 +227,7 @@ impl Sources {
|
||||
let scope_handle = self.scope_tree.clone();
|
||||
let inc_dirs = self.include_dirs.clone();
|
||||
|
||||
// let backend = Arc::new(backend);
|
||||
info!("launch worker to parse {:?}", doc.uri.to_string());
|
||||
|
||||
// spawn parse thread
|
||||
let parse_handle = thread::spawn(move || {
|
||||
@ -245,12 +241,15 @@ impl Sources {
|
||||
|
||||
info!("do parse in {:?}", uri.to_string());
|
||||
|
||||
let syntax_tree = parse(&text, uri, range, &inc_dirs.read().unwrap());
|
||||
let syntax_tree = recovery_sv_parse(&text, uri, range, &inc_dirs.read().unwrap());
|
||||
let mut scope_tree = match &syntax_tree {
|
||||
Some(tree) => get_scopes(tree, uri),
|
||||
None => None,
|
||||
};
|
||||
|
||||
|
||||
info!("finish parse {:?}", uri.to_string());
|
||||
|
||||
// 计算 fast
|
||||
// if let Some(syntax_tree) = &syntax_tree {
|
||||
// let path = PathBuf::from(uri.path().to_string());
|
||||
@ -406,9 +405,9 @@ impl Sources {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: show all unrecoverable parse errors to user
|
||||
/// parse the file using sv-parser, attempt to recover if the parse fails
|
||||
pub fn parse(
|
||||
/// 更加稳定地解析 sv 和 v
|
||||
/// 支持遇到错误进行自动修复,然后再解析
|
||||
pub fn recovery_sv_parse(
|
||||
doc: &Rope,
|
||||
uri: &Url,
|
||||
last_change_range: &Option<Range>,
|
||||
@ -429,39 +428,48 @@ pub fn parse(
|
||||
&defines,
|
||||
&includes,
|
||||
true,
|
||||
true
|
||||
false
|
||||
) {
|
||||
Ok((syntax_tree, _)) => {
|
||||
return Some(syntax_tree);
|
||||
}
|
||||
Err(err) => {
|
||||
println!("find err : {:?}", err);
|
||||
info!("find err : {:?}", err);
|
||||
match err {
|
||||
// 语法错误
|
||||
sv_parser::Error::Parse(trace) => match trace {
|
||||
Some((_, bpos)) => {
|
||||
let mut line_start = text.byte_to_line(bpos);
|
||||
let mut line_end = text.byte_to_line(bpos) + 1;
|
||||
if !reverted_change {
|
||||
if let Some(range) = last_change_range {
|
||||
line_start = range.start.line as usize;
|
||||
line_end = range.end.line as usize + 1;
|
||||
reverted_change = true;
|
||||
}
|
||||
}
|
||||
for line_idx in line_start..line_end {
|
||||
let line = text.line(line_idx);
|
||||
let start_char = text.line_to_char(line_idx);
|
||||
let line_length = line.len_chars();
|
||||
text.remove(start_char..(start_char + line_length - 1));
|
||||
text.insert(start_char, &" ".to_owned().repeat(line_length));
|
||||
}
|
||||
parse_iterations += 1;
|
||||
sv_parser::Error::Parse(trace) => {
|
||||
if trace.is_none() {
|
||||
return None;
|
||||
}
|
||||
None => return None,
|
||||
|
||||
let (_, bpos) = trace.unwrap();
|
||||
let mut line_start = text.byte_to_line(bpos);
|
||||
let mut line_end = text.byte_to_line(bpos) + 1;
|
||||
// println!("enter Error::Parse, start: {}, end: {}", line_start, line_end);
|
||||
|
||||
if !reverted_change {
|
||||
if let Some(range) = last_change_range {
|
||||
line_start = range.start.line as usize;
|
||||
line_end = range.end.line as usize + 1;
|
||||
reverted_change = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 把 last_change_range 中的涉及到的所有行内容用等长的空格替代
|
||||
for line_idx in line_start..line_end {
|
||||
let line = text.line(line_idx);
|
||||
let start_char = text.line_to_char(line_idx);
|
||||
let line_length = line.len_chars();
|
||||
text.remove(start_char..(start_char + line_length - 1));
|
||||
text.insert(start_char, &" ".to_owned().repeat(line_length));
|
||||
}
|
||||
parse_iterations += 1;
|
||||
},
|
||||
|
||||
// 遇到 include 错误,那就把提示中的 include 加入解析中再次解析
|
||||
sv_parser::Error::Include { source: x } => {
|
||||
info!("Error::Include");
|
||||
if let sv_parser::Error::File { source: _, path: z } = *x {
|
||||
// Include paths have to be relative to the working directory
|
||||
// so we have to convert a source file relative path to a working directory
|
||||
@ -484,6 +492,7 @@ pub fn parse(
|
||||
|
||||
// 宏定义不存在的错误
|
||||
sv_parser::Error::DefineNotFound(not_found_macro_name) => {
|
||||
info!("Error::DefineNotFound");
|
||||
let range = sv_parser::sv_parser_pp_range::Range { begin: 0, end: 0 };
|
||||
let pathbuf = PathBuf::from_str(uri.as_str()).unwrap();
|
||||
let origin = Some((pathbuf, range));
|
||||
@ -494,6 +503,7 @@ pub fn parse(
|
||||
};
|
||||
defines.insert(not_found_macro_name, Some(com_define));
|
||||
parse_iterations += 1;
|
||||
info!("parse iteration plus one");
|
||||
}
|
||||
_ => error!("parse error, {:?}", err),
|
||||
};
|
||||
@ -687,7 +697,7 @@ endmodule"#;
|
||||
d.push("test_data/top_inc.sv");
|
||||
let text = read_to_string(&d).unwrap();
|
||||
let doc = Rope::from_str(&text);
|
||||
assert!(parse(&doc, &Url::from_file_path(d).unwrap(), &None, &Vec::new()).is_some(),);
|
||||
assert!(recovery_sv_parse(&doc, &Url::from_file_path(d).unwrap(), &None, &Vec::new()).is_some(),);
|
||||
// TODO: add missing header test
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ const TESTFILES_DIR: &str = "testfiles";
|
||||
const DIGTIAL_IDE_TEST: &str = "/home/dide/project/Digital-Test/Digital-IDE-test/user";
|
||||
|
||||
#[allow(unused)]
|
||||
const TEST_FILE: &str = "/home/dide/project/Digital-Test/Digital-IDE-test/user/src/svlog/tools/ivtest/br_gh330.sv";
|
||||
const TEST_FILE: &str = "/home/dide/project/Digital-Test/MipsDesign/src/MyCpu.v";
|
||||
|
||||
#[allow(unused)]
|
||||
const INCOMPLETE_EXAMPLE: &str = "/home/dide/project/Digital-Test/Digital-IDE-test/user/src/incomplete-example";
|
||||
@ -37,6 +37,13 @@ mod test_fast {
|
||||
use crate::core::sv_parser::sv_parser;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mycpu() {
|
||||
let hdlparam = sv_parser(TEST_FILE);
|
||||
println!("{hdlparam:?}");
|
||||
assert!(hdlparam.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_testfiles() {
|
||||
let entries = unwrap_result!(fs::read_dir(TESTFILES_DIR));
|
||||
@ -122,11 +129,12 @@ mod test_fast {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_svparse {
|
||||
use std::{fs, path::{Path, PathBuf}};
|
||||
use std::{collections::HashMap, fs, path::{Path, PathBuf}};
|
||||
|
||||
use ropey::Rope;
|
||||
use tower_lsp::lsp_types::Url;
|
||||
use crate::sources::parse;
|
||||
use sv_parser::parse_sv_str;
|
||||
use tower_lsp::lsp_types::{Position, Range, Url};
|
||||
use crate::sources::recovery_sv_parse;
|
||||
|
||||
use super::{INCOMPLETE_EXAMPLE, TEST_FILE};
|
||||
|
||||
@ -143,10 +151,15 @@ mod test_svparse {
|
||||
|
||||
let doc = Rope::from_str(&text);
|
||||
let uri = Url::from_file_path(&path).unwrap();
|
||||
let result = parse(&doc, &uri, &None, &includes);
|
||||
let last_change_range = Range::new(
|
||||
Position { line: 0, character: 0 },
|
||||
Position { line: 0, character: 0 },
|
||||
);
|
||||
let result = recovery_sv_parse(&doc, &uri, &Some(last_change_range), &includes);
|
||||
match result {
|
||||
Some(_) => {
|
||||
Some(syntax_tree) => {
|
||||
println!("success");
|
||||
println!("{:?}", syntax_tree);
|
||||
},
|
||||
None => {
|
||||
eprintln!("gen None");
|
||||
@ -204,8 +217,10 @@ mod test_svparse {
|
||||
let includes: Vec<PathBuf> = Vec::new();
|
||||
let doc = Rope::from_str(&text);
|
||||
let uri = Url::from_file_path(file_path).unwrap();
|
||||
let result = parse(&doc, &uri, &None, &includes);
|
||||
let result = recovery_sv_parse(&doc, &uri, &None, &includes);
|
||||
|
||||
assert!(result.is_some());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -217,19 +232,26 @@ mod test_svparse {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_scope_tree {
|
||||
use std::{collections::HashMap, fs, path::{Path, PathBuf}};
|
||||
use crate::definition::{get_scopes, GenericScope};
|
||||
use sv_parser::parse_sv;
|
||||
use std::{fs, path::{Path, PathBuf}};
|
||||
use crate::{definition::{get_scopes, GenericScope}, sources::recovery_sv_parse};
|
||||
use ropey::Rope;
|
||||
use tower_lsp::lsp_types::Url;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn get_scope_tree(file_path: &str) -> Option<GenericScope> {
|
||||
let defines = HashMap::new();
|
||||
let includes: Vec<PathBuf> = Vec::new();
|
||||
|
||||
let result = parse_sv(file_path, &defines, &includes, true, true);
|
||||
if let Ok((syntax_tree, _)) = result {
|
||||
let text = match fs::read_to_string(file_path) {
|
||||
Ok(text) => text,
|
||||
Err(_) => return None
|
||||
};
|
||||
|
||||
let doc = Rope::from_str(&text);
|
||||
let uri = Url::from_file_path(&file_path).unwrap();
|
||||
let result = recovery_sv_parse(&doc, &uri, &None, &includes);
|
||||
|
||||
if let Some(syntax_tree) = result {
|
||||
let file_url = format!("file://{}", file_path);
|
||||
let uri = Url::parse(&file_url);
|
||||
if let Ok(uri) = uri {
|
||||
@ -242,6 +264,16 @@ mod test_scope_tree {
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mycpu() {
|
||||
let result = get_scope_tree(TEST_FILE);
|
||||
if let Some(scope) = result {
|
||||
println!("{:?}", scope);
|
||||
} else {
|
||||
panic!("error parsing");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_testfiles() {
|
||||
let entries = unwrap_result!(fs::read_dir(TESTFILES_DIR));
|
||||
|
93
src/utils/mod.rs
Normal file
93
src/utils/mod.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use regex::Regex;
|
||||
use ropey::RopeSlice;
|
||||
use tower_lsp::lsp_types::*;
|
||||
|
||||
/// 根据 pos 获取到当前光标所在的字符串,相当于 getWordRangeAtPosition
|
||||
pub fn get_definition_token(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();
|
||||
while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') {
|
||||
token.push(c.unwrap());
|
||||
c = line_iter.prev();
|
||||
}
|
||||
token = token.chars().rev().collect();
|
||||
line_iter = line.chars();
|
||||
for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) {
|
||||
line_iter.next();
|
||||
}
|
||||
let mut c = line_iter.next();
|
||||
while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') {
|
||||
token.push(c.unwrap());
|
||||
c = line_iter.next();
|
||||
}
|
||||
token
|
||||
}
|
||||
|
||||
pub fn get_word_range_at_position(line: &RopeSlice, pos: Position, regex: Regex) -> 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();
|
||||
|
||||
while c.is_some() && (regex.is_match(&c.unwrap().to_string())) {
|
||||
token.push(c.unwrap());
|
||||
c = line_iter.prev();
|
||||
}
|
||||
token = token.chars().rev().collect();
|
||||
line_iter = line.chars();
|
||||
for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) {
|
||||
line_iter.next();
|
||||
}
|
||||
let mut c = line_iter.next();
|
||||
while c.is_some() && (regex.is_match(&c.unwrap().to_string())) {
|
||||
token.push(c.unwrap());
|
||||
c = line_iter.next();
|
||||
}
|
||||
token
|
||||
}
|
||||
|
||||
/// 根据 uri 获取 hdl 的 language id
|
||||
pub fn get_language_id_by_uri(uri: &Url) -> String {
|
||||
let path = uri.path();
|
||||
let ext_name = std::path::Path::new(path)
|
||||
.extension()
|
||||
.and_then(std::ffi::OsStr::to_str)
|
||||
.unwrap_or("");
|
||||
|
||||
match ext_name {
|
||||
"vhd" | "vhdl" | "vho" | "vht" => "vhdl".to_string(),
|
||||
"v" | "V" | "vh" | "vl" => "verilog".to_string(),
|
||||
"sv" | "svh" => "systemverilog".to_string(),
|
||||
_ => "plaintext".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据基础路径和给出的 path 路径,计算出绝对路径
|
||||
/// 如果 path 是绝对路径直接返回
|
||||
/// 会进行存在性检查,不存在的路径直接返回 None
|
||||
/// 例子:
|
||||
/// resolve_path("/home/ubuntu/hello.v", "./control.v") -> Some("/home/ubuntu/control.v")
|
||||
/// resolve_path("/home/ubuntu/hello.v", "/opt/control.v") -> Some("/opt/control.v")
|
||||
pub fn resolve_path(base: &str, path_string: &str) -> Option<PathBuf> {
|
||||
let path = Path::new(path_string);
|
||||
let abs_path: PathBuf;
|
||||
if path.is_absolute() {
|
||||
abs_path = PathBuf::from(path);
|
||||
} else {
|
||||
let base = Path::new(base).parent()?;
|
||||
abs_path = base.join(path);
|
||||
}
|
||||
if abs_path.exists() {
|
||||
let path = abs_path.as_path().to_owned();
|
||||
return Some(path);
|
||||
}
|
||||
None
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user