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 = [
|
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"
|
||||||
|
@ -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"
|
||||||
|
@ -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);
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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::*;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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(<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"]
|
#![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;
|
@ -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;
|
||||||
|
@ -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(¶ms.text_document.uri);
|
let file_id = self.srcs.get_id(¶ms.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,18 +428,26 @@ 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() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (_, bpos) = trace.unwrap();
|
||||||
let mut line_start = text.byte_to_line(bpos);
|
let mut line_start = text.byte_to_line(bpos);
|
||||||
let mut line_end = text.byte_to_line(bpos) + 1;
|
let mut line_end = text.byte_to_line(bpos) + 1;
|
||||||
|
// println!("enter Error::Parse, start: {}, end: {}", line_start, line_end);
|
||||||
|
|
||||||
if !reverted_change {
|
if !reverted_change {
|
||||||
if let Some(range) = last_change_range {
|
if let Some(range) = last_change_range {
|
||||||
line_start = range.start.line as usize;
|
line_start = range.start.line as usize;
|
||||||
@ -448,6 +455,8 @@ pub fn parse(
|
|||||||
reverted_change = true;
|
reverted_change = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 把 last_change_range 中的涉及到的所有行内容用等长的空格替代
|
||||||
for line_idx in line_start..line_end {
|
for line_idx in line_start..line_end {
|
||||||
let line = text.line(line_idx);
|
let line = text.line(line_idx);
|
||||||
let start_char = text.line_to_char(line_idx);
|
let start_char = text.line_to_char(line_idx);
|
||||||
@ -456,12 +465,11 @@ pub fn parse(
|
|||||||
text.insert(start_char, &" ".to_owned().repeat(line_length));
|
text.insert(start_char, &" ".to_owned().repeat(line_length));
|
||||||
}
|
}
|
||||||
parse_iterations += 1;
|
parse_iterations += 1;
|
||||||
}
|
|
||||||
None => return None,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// 遇到 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
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