This commit is contained in:
锦恢 2024-09-26 20:09:42 +08:00
parent 6afe6a1077
commit 3a64906029
18 changed files with 413 additions and 353 deletions

13
Cargo.lock generated
View File

@ -205,7 +205,6 @@ version = "0.0.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"flexi_logger", "flexi_logger",
"futures",
"log", "log",
"once_cell", "once_cell",
"path-clean", "path-clean",
@ -286,7 +285,6 @@ checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-executor",
"futures-io", "futures-io",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
@ -309,17 +307,6 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 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]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.28" version = "0.3.28"

View File

@ -7,7 +7,6 @@ edition = "2018"
[dependencies] [dependencies]
sv-parser = { version = "0.13.3", path = "sv-parser/sv-parser"} sv-parser = { version = "0.13.3", path = "sv-parser/sv-parser"}
once_cell = "1.8" once_cell = "1.8"
futures = "0.3"
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
log = "0.4.19" log = "0.4.19"
tower-lsp = "0.20.0" tower-lsp = "0.20.0"

View File

@ -174,7 +174,7 @@ mod tests {
use super::*; use super::*;
use crate::definition::def_types::Scope; use crate::definition::def_types::Scope;
use crate::definition::get_scopes; use crate::definition::get_scopes;
use crate::sources::{parse, LSPSupport}; use crate::sources::{recovery_sv_parse, LSPSupport};
use crate::support::test_init; use crate::support::test_init;
use ropey::Rope; use ropey::Rope;
@ -651,7 +651,7 @@ endinterface
let doc = Rope::from_str(&text); let doc = Rope::from_str(&text);
let url = Url::parse("file:///test.sv").unwrap(); 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 scope_tree = get_scopes(&syntax_tree, &url).unwrap();
let pos = Position::new(8, 9); let pos = Position::new(8, 9);
let token = get_completion_token(&doc, doc.line(pos.line as usize), pos); let token = get_completion_token(&doc, doc.line(pos.line as usize), pos);

View File

@ -1,25 +1,35 @@
use std::fs::File; use std::fs::{self, File};
use std::io::BufRead; use std::io::BufRead;
use std::{collections::HashMap, io::BufReader}; use std::{collections::HashMap, io::BufReader};
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::Error; use anyhow::Error;
use percent_encoding::percent_decode_str; use percent_encoding::percent_decode_str;
use ropey::Rope;
use tower_lsp::lsp_types::Url;
use std::env::consts::OS; use std::env::consts::OS;
use sv_parser::{parse_sv, unwrap_node, Locate, RefNode, SyntaxTree}; use sv_parser::{parse_sv, unwrap_node, Locate, RefNode, SyntaxTree};
use crate::sources::recovery_sv_parse;
use super::fast_hdlparam::{FastHdlparam, Macro}; use super::fast_hdlparam::{FastHdlparam, Macro};
#[allow(unused)] #[allow(unused)]
pub fn sv_parser(path: &str) -> Option<FastHdlparam> { pub fn sv_parser(path: &str) -> Option<FastHdlparam> {
// The path of SystemVerilog source file // The path of SystemVerilog source file
let path = PathBuf::from(path); let path = PathBuf::from(path);
// The list of defined macros
let defines = HashMap::new();
// The list of include paths // The list of include paths
let includes: Vec<PathBuf> = Vec::new(); let includes: Vec<PathBuf> = Vec::new();
let result = parse_sv(&path, &defines, &includes, false, true); let text = match fs::read_to_string(&path) {
if let Ok((syntax_tree, _)) = result { 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) { if let Ok(hdlparam) = make_fast_from_syntaxtree(&syntax_tree, &path) {
return Some(hdlparam); return Some(hdlparam);
} }

View File

@ -12,9 +12,9 @@ use tower_lsp::lsp_types::*;
use crate::core::fast_hdlparam::FastHdlparam; use crate::core::fast_hdlparam::FastHdlparam;
use crate::core::sv_parser::make_fast_from_syntaxtree; 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::server::{Backend, GLOBAL_BACKEND};
use crate::sources::parse; use crate::sources::recovery_sv_parse;
#[derive(Clone)] #[derive(Clone)]
@ -85,7 +85,8 @@ fn make_textdocumenitem_from_path(path_buf: &PathBuf) -> Option<TextDocumentItem
/// 前端交互接口: do_fast输入文件路径计算出对应的 fast 结构 /// 前端交互接口: do_fast输入文件路径计算出对应的 fast 结构
pub fn do_fast(path: String) -> Result<FastHdlparam> { pub fn do_fast(path: String) -> Result<FastHdlparam> {
info!("parse hdl path: {:?}", path); info!("parse fast {}", path);
let path_buf = PathBuf::from(&path); let path_buf = PathBuf::from(&path);
let doc = match make_textdocumenitem_from_path(&path_buf) { let doc = match make_textdocumenitem_from_path(&path_buf) {
@ -106,7 +107,10 @@ pub fn do_fast(path: String) -> Result<FastHdlparam> {
// fast 解析不需要 include // fast 解析不需要 include
let includes: Vec<PathBuf> = Vec::new(); let includes: Vec<PathBuf> = Vec::new();
let parse_result = parse(
info!("before parse {}", path);
let parse_result = recovery_sv_parse(
&text, &text,
&uri, &uri,
&None, &None,
@ -115,6 +119,7 @@ pub fn do_fast(path: String) -> Result<FastHdlparam> {
if let Some(syntax_tree) = parse_result { if let Some(syntax_tree) = parse_result {
if let Ok(hdlparam) = make_fast_from_syntaxtree(&syntax_tree, &path_buf) { if let Ok(hdlparam) = make_fast_from_syntaxtree(&syntax_tree, &path_buf) {
info!("after parse {}, get hdlparam {:?}", path, hdlparam);
return Ok(hdlparam); return Ok(hdlparam);
} }
} }
@ -154,7 +159,7 @@ impl Notification for UpdateFastNotification {
type Params = Self; type Params = Self;
} }
#[allow(unused)]
pub async fn update_fast_to_client(fast: FastHdlparam, path: &PathBuf) { pub async fn update_fast_to_client(fast: FastHdlparam, path: &PathBuf) {
// info!("send fast to foreend {:?}", fast); // info!("send fast to foreend {:?}", fast);

View File

@ -1,6 +1,5 @@
use crate::definition::def_types::*; use crate::definition::def_types::*;
use crate::definition::match_definitions; use crate::definition::match_definitions;
use log::info;
use sv_parser::*; use sv_parser::*;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;

View File

@ -1,8 +1,8 @@
use std::path::{Path, PathBuf};
use ropey::RopeSlice; use ropey::RopeSlice;
use tower_lsp::lsp_types::{GotoDefinitionResponse, LocationLink, Position, Range, Url}; 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> { pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Option<GotoDefinitionResponse> {
let line_text = line.as_str().unwrap_or(""); 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(".\\") { if path_string.starts_with("./") || path_string.starts_with(".\\") {
path_string = &path_string[2 ..]; 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()) { let target_uri = match Url::from_file_path(abs_path.as_path()) {
Ok(uri) => uri, Ok(uri) => uri,
Err(_) => return None Err(_) => return None
@ -55,6 +46,7 @@ pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Op
let links = GotoDefinitionResponse::Link(link); let links = GotoDefinitionResponse::Link(link);
return Some(links); return Some(links);
} }
} }
} }

View File

@ -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::server::LSPServer;
use crate::sources::LSPSupport; use crate::sources::LSPSupport;
use log::info; use log::info;
use ropey::{Rope, RopeSlice};
use sv_parser::*; use sv_parser::*;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
@ -29,7 +28,6 @@ impl LSPServer {
let line_text = file.text.line(pos.line as usize); let line_text = file.text.line(pos.line as usize);
let token: String = get_definition_token(&line_text, pos); let token: String = get_definition_token(&line_text, pos);
info!("definition token: {:?}", token);
let include_definition = goto_include_definition(&doc, &line_text, pos); let include_definition = goto_include_definition(&doc, &line_text, pos);
if include_definition.is_some() { if include_definition.is_some() {
return include_definition; 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> { pub fn document_symbol(&self, params: DocumentSymbolParams) -> Option<DocumentSymbolResponse> {
let uri = params.text_document.uri; let uri = params.text_document.uri;
let file_id = self.srcs.get_id(&uri).to_owned(); 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 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>>)>; 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); global_scope.scopes.append(&mut scopes);
Some(global_scope) 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(&ltrim) {
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
View 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
View 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(&ltrim) {
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,
})
}

View File

@ -1,12 +1,31 @@
#![recursion_limit = "256"] #![recursion_limit = "256"]
pub mod core; pub mod core;
// 自动补全
pub mod completion; pub mod completion;
// 定义跳转
pub mod definition; pub mod definition;
// 悬停提示
pub mod hover;
// 诊断
pub mod diagnostics; pub mod diagnostics;
// 格式化
pub mod format; pub mod format;
// 基础工具
pub mod utils;
pub mod server; pub mod server;
pub mod sources; pub mod sources;
pub mod support; pub mod support;
// 自定义发送请求
pub mod custom_request; pub mod custom_request;
// 测试模块
pub mod test; pub mod test;

View File

@ -10,6 +10,8 @@ use tower_lsp::{LspService, Server};
mod core; mod core;
mod completion; mod completion;
mod definition; mod definition;
mod hover;
mod utils;
mod diagnostics; mod diagnostics;
mod format; mod format;
mod server; mod server;

View File

@ -19,7 +19,6 @@ use std::str::FromStr;
use std::sync::{Arc, Condvar, Mutex, RwLock}; use std::sync::{Arc, Condvar, Mutex, RwLock};
use std::thread; use std::thread;
use sv_parser::*; use sv_parser::*;
use sv_parser::sv_parser_pp_range;
use thread::JoinHandle; use thread::JoinHandle;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use walkdir::WalkDir; use walkdir::WalkDir;
@ -33,11 +32,6 @@ macro_rules! unwrap_result {
}; };
} }
struct CustomRange {
pub begin: usize,
pub end: usize,
}
impl LSPServer { impl LSPServer {
pub fn did_open(&self, params: DidOpenTextDocumentParams) -> PublishDiagnosticsParams { pub fn did_open(&self, params: DidOpenTextDocumentParams) -> PublishDiagnosticsParams {
info!("[LSPServer] did open"); info!("[LSPServer] did open");
@ -68,11 +62,12 @@ impl LSPServer {
} }
pub fn did_change(&self, params: DidChangeTextDocumentParams) { 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(&params.text_document.uri); let file_id = self.srcs.get_id(&params.text_document.uri);
let file = self.srcs.get_file(file_id).unwrap(); let file = self.srcs.get_file(file_id).unwrap();
let mut file = file.write().unwrap(); let mut file = file.write().unwrap();
// loop through changes and apply // loop through changes and apply
for change in params.content_changes { for change in params.content_changes {
if change.range.is_none() { if change.range.is_none() {
@ -161,6 +156,7 @@ pub struct Sources {
// file metadata // file metadata
pub meta: Arc<RwLock<Vec<Arc<RwLock<SourceMeta>>>>>, pub meta: Arc<RwLock<Vec<Arc<RwLock<SourceMeta>>>>>,
// all source files are indexed into this tree, which can then // all source files are indexed into this tree, which can then
// be used for completion, name resolution // be used for completion, name resolution
pub scope_tree: Arc<RwLock<Option<GenericScope>>>, pub scope_tree: Arc<RwLock<Option<GenericScope>>>,
// include directories, passed to parser to resolve `include // include directories, passed to parser to resolve `include
@ -231,7 +227,7 @@ impl Sources {
let scope_handle = self.scope_tree.clone(); let scope_handle = self.scope_tree.clone();
let inc_dirs = self.include_dirs.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 // spawn parse thread
let parse_handle = thread::spawn(move || { let parse_handle = thread::spawn(move || {
@ -245,12 +241,15 @@ impl Sources {
info!("do parse in {:?}", uri.to_string()); 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 { let mut scope_tree = match &syntax_tree {
Some(tree) => get_scopes(tree, uri), Some(tree) => get_scopes(tree, uri),
None => None, None => None,
}; };
info!("finish parse {:?}", uri.to_string());
// 计算 fast // 计算 fast
// if let Some(syntax_tree) = &syntax_tree { // if let Some(syntax_tree) = &syntax_tree {
// let path = PathBuf::from(uri.path().to_string()); // let path = PathBuf::from(uri.path().to_string());
@ -406,9 +405,9 @@ impl Sources {
} }
} }
//TODO: show all unrecoverable parse errors to user /// 更加稳定地解析 sv 和 v
/// parse the file using sv-parser, attempt to recover if the parse fails /// 支持遇到错误进行自动修复,然后再解析
pub fn parse( pub fn recovery_sv_parse(
doc: &Rope, doc: &Rope,
uri: &Url, uri: &Url,
last_change_range: &Option<Range>, last_change_range: &Option<Range>,
@ -429,39 +428,48 @@ pub fn parse(
&defines, &defines,
&includes, &includes,
true, true,
true false
) { ) {
Ok((syntax_tree, _)) => { Ok((syntax_tree, _)) => {
return Some(syntax_tree); return Some(syntax_tree);
} }
Err(err) => { Err(err) => {
println!("find err : {:?}", err);
info!("find err : {:?}", err);
match err { match err {
// 语法错误 // 语法错误
sv_parser::Error::Parse(trace) => match trace { sv_parser::Error::Parse(trace) => {
Some((_, bpos)) => { if trace.is_none() {
let mut line_start = text.byte_to_line(bpos); return None;
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;
} }
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 加入解析中再次解析 // 遇到 include 错误,那就把提示中的 include 加入解析中再次解析
sv_parser::Error::Include { source: x } => { sv_parser::Error::Include { source: x } => {
info!("Error::Include");
if let sv_parser::Error::File { source: _, path: z } = *x { if let sv_parser::Error::File { source: _, path: z } = *x {
// Include paths have to be relative to the working directory // Include paths have to be relative to the working directory
// so we have to convert a source file relative path to a 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) => { 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 range = sv_parser::sv_parser_pp_range::Range { begin: 0, end: 0 };
let pathbuf = PathBuf::from_str(uri.as_str()).unwrap(); let pathbuf = PathBuf::from_str(uri.as_str()).unwrap();
let origin = Some((pathbuf, range)); let origin = Some((pathbuf, range));
@ -494,6 +503,7 @@ pub fn parse(
}; };
defines.insert(not_found_macro_name, Some(com_define)); defines.insert(not_found_macro_name, Some(com_define));
parse_iterations += 1; parse_iterations += 1;
info!("parse iteration plus one");
} }
_ => error!("parse error, {:?}", err), _ => error!("parse error, {:?}", err),
}; };
@ -687,7 +697,7 @@ endmodule"#;
d.push("test_data/top_inc.sv"); d.push("test_data/top_inc.sv");
let text = read_to_string(&d).unwrap(); let text = read_to_string(&d).unwrap();
let doc = Rope::from_str(&text); 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 // TODO: add missing header test
} }
} }

View File

@ -5,7 +5,7 @@ const TESTFILES_DIR: &str = "testfiles";
const DIGTIAL_IDE_TEST: &str = "/home/dide/project/Digital-Test/Digital-IDE-test/user"; const DIGTIAL_IDE_TEST: &str = "/home/dide/project/Digital-Test/Digital-IDE-test/user";
#[allow(unused)] #[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)] #[allow(unused)]
const INCOMPLETE_EXAMPLE: &str = "/home/dide/project/Digital-Test/Digital-IDE-test/user/src/incomplete-example"; 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 crate::core::sv_parser::sv_parser;
use super::*; use super::*;
#[test]
fn test_mycpu() {
let hdlparam = sv_parser(TEST_FILE);
println!("{hdlparam:?}");
assert!(hdlparam.is_some());
}
#[test] #[test]
fn test_testfiles() { fn test_testfiles() {
let entries = unwrap_result!(fs::read_dir(TESTFILES_DIR)); let entries = unwrap_result!(fs::read_dir(TESTFILES_DIR));
@ -122,11 +129,12 @@ mod test_fast {
#[cfg(test)] #[cfg(test)]
mod test_svparse { mod test_svparse {
use std::{fs, path::{Path, PathBuf}}; use std::{collections::HashMap, fs, path::{Path, PathBuf}};
use ropey::Rope; use ropey::Rope;
use tower_lsp::lsp_types::Url; use sv_parser::parse_sv_str;
use crate::sources::parse; use tower_lsp::lsp_types::{Position, Range, Url};
use crate::sources::recovery_sv_parse;
use super::{INCOMPLETE_EXAMPLE, TEST_FILE}; use super::{INCOMPLETE_EXAMPLE, TEST_FILE};
@ -143,10 +151,15 @@ mod test_svparse {
let doc = Rope::from_str(&text); let doc = Rope::from_str(&text);
let uri = Url::from_file_path(&path).unwrap(); 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 { match result {
Some(_) => { Some(syntax_tree) => {
println!("success"); println!("success");
println!("{:?}", syntax_tree);
}, },
None => { None => {
eprintln!("gen None"); eprintln!("gen None");
@ -204,8 +217,10 @@ mod test_svparse {
let includes: Vec<PathBuf> = Vec::new(); let includes: Vec<PathBuf> = Vec::new();
let doc = Rope::from_str(&text); let doc = Rope::from_str(&text);
let uri = Url::from_file_path(file_path).unwrap(); 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()); assert!(result.is_some());
} }
} }
} }
@ -217,19 +232,26 @@ mod test_svparse {
#[cfg(test)] #[cfg(test)]
mod test_scope_tree { mod test_scope_tree {
use std::{collections::HashMap, fs, path::{Path, PathBuf}}; use std::{fs, path::{Path, PathBuf}};
use crate::definition::{get_scopes, GenericScope}; use crate::{definition::{get_scopes, GenericScope}, sources::recovery_sv_parse};
use sv_parser::parse_sv; use ropey::Rope;
use tower_lsp::lsp_types::Url; use tower_lsp::lsp_types::Url;
use super::*; use super::*;
fn get_scope_tree(file_path: &str) -> Option<GenericScope> { fn get_scope_tree(file_path: &str) -> Option<GenericScope> {
let defines = HashMap::new();
let includes: Vec<PathBuf> = Vec::new(); let includes: Vec<PathBuf> = Vec::new();
let result = parse_sv(file_path, &defines, &includes, true, true); let text = match fs::read_to_string(file_path) {
if let Ok((syntax_tree, _)) = result { 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 file_url = format!("file://{}", file_path);
let uri = Url::parse(&file_url); let uri = Url::parse(&file_url);
if let Ok(uri) = uri { if let Ok(uri) = uri {
@ -242,6 +264,16 @@ mod test_scope_tree {
None None
} }
#[test]
fn test_mycpu() {
let result = get_scope_tree(TEST_FILE);
if let Some(scope) = result {
println!("{:?}", scope);
} else {
panic!("error parsing");
}
}
#[test] #[test]
fn test_testfiles() { fn test_testfiles() {
let entries = unwrap_result!(fs::read_dir(TESTFILES_DIR)); let entries = unwrap_result!(fs::read_dir(TESTFILES_DIR));

93
src/utils/mod.rs Normal file
View 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
}