实现 digit hover

This commit is contained in:
锦恢 2024-09-27 17:06:26 +08:00
parent 302dafbc05
commit 5d8eb18042
11 changed files with 143 additions and 957 deletions

View File

@ -1,4 +1,3 @@
[build]
jobs = 16 # 并行构建任务的数量,默认等于 CPU 的核心数
rustc = "rustc" # rust 编译器
target-dir = "target" # 存放编译输出结果的目录

View File

@ -168,607 +168,3 @@ fn get_completion_token(text: &Rope, line: RopeSlice, pos: Position) -> String {
result
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::definition::def_types::Scope;
use crate::definition::get_scopes;
use crate::sources::{recovery_sv_parse, LSPSupport};
use crate::support::test_init;
use ropey::Rope;
#[test]
fn test_get_completion_token() {
test_init();
let text = Rope::from_str("abc abc.cba de_fg cde[4]");
let mut result = get_completion_token(
&text,
text.line(0),
Position {
line: 0,
character: 3,
},
);
assert_eq!(&result, "abc");
result = get_completion_token(
&text,
text.line(0),
Position {
line: 0,
character: 11,
},
);
assert_eq!(&result, "abc.cba");
result = get_completion_token(
&text,
text.line(0),
Position {
line: 0,
character: 16,
},
);
assert_eq!(&result, "de_f");
result = get_completion_token(
&text,
text.line(0),
Position {
line: 0,
character: 23,
},
);
assert_eq!(&result, "cde");
}
#[test]
fn test_completion() {
test_init();
let server = LSPServer::new(None);
let uri = Url::parse("file:///test.sv").unwrap();
let text = r#"module test;
logic abc;
logic abcd;
endmodule
"#;
let open_params = DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri.clone(),
language_id: "systemverilog".to_owned(),
version: 0,
text: text.to_owned(),
},
};
server.did_open(open_params);
let fid = server.srcs.get_id(&uri);
server.srcs.wait_parse_ready(fid, true);
let change_params = DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: uri.clone(),
version: 3,
},
content_changes: vec![
TextDocumentContentChangeEvent {
range: Some(Range {
start: Position {
line: 3,
character: 0,
},
end: Position {
line: 3,
character: 0,
},
}),
range_length: None,
text: "\n".to_owned(),
},
TextDocumentContentChangeEvent {
range: Some(Range {
start: Position {
line: 4,
character: 0,
},
end: Position {
line: 4,
character: 0,
},
}),
range_length: None,
text: " ".to_owned(),
},
TextDocumentContentChangeEvent {
range: Some(Range {
start: Position {
line: 4,
character: 2,
},
end: Position {
line: 4,
character: 2,
},
}),
range_length: None,
text: "a".to_owned(),
},
],
};
server.did_change(change_params);
server.srcs.wait_parse_ready(fid, true);
let completion_params = CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri },
position: Position {
line: 4,
character: 3,
},
},
work_done_progress_params: WorkDoneProgressParams::default(),
partial_result_params: PartialResultParams::default(),
context: Some(CompletionContext {
trigger_kind: CompletionTriggerKind::INVOKED,
trigger_character: None,
}),
};
let response: CompletionResponse = server.completion(completion_params).unwrap();
let item1 = CompletionItem {
label: "abc".to_owned(),
kind: Some(CompletionItemKind::VARIABLE),
detail: Some("logic".to_string()),
..CompletionItem::default()
};
let item2 = CompletionItem {
label: "abcd".to_owned(),
kind: Some(CompletionItemKind::VARIABLE),
detail: Some("logic".to_string()),
..CompletionItem::default()
};
if let CompletionResponse::List(item) = response {
assert!(item.items.contains(&item1));
assert!(item.items.contains(&item2));
} else {
panic!();
}
}
#[test]
fn test_nested_completion() {
test_init();
let server = LSPServer::new(None);
let uri = Url::parse("file:///test.sv").unwrap();
let text = r#"module test;
logic aouter;
function func1();
logic abc;
func1 = abc;
endfunction
function func2();
logic abcd;
func2 = abcd;
endfunction
endmodule
"#;
let open_params = DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri.clone(),
language_id: "systemverilog".to_owned(),
version: 0,
text: text.to_owned(),
},
};
server.did_open(open_params);
let fid = server.srcs.get_id(&uri);
server.srcs.wait_parse_ready(fid, true);
let change_params = DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier {
uri: uri.clone(),
version: 3,
},
content_changes: vec![
TextDocumentContentChangeEvent {
range: Some(Range {
start: Position {
line: 4,
character: 0,
},
end: Position {
line: 4,
character: 0,
},
}),
range_length: None,
text: "\n".to_owned(),
},
TextDocumentContentChangeEvent {
range: Some(Range {
start: Position {
line: 4,
character: 0,
},
end: Position {
line: 4,
character: 0,
},
}),
range_length: None,
text: " ".to_owned(),
},
TextDocumentContentChangeEvent {
range: Some(Range {
start: Position {
line: 4,
character: 2,
},
end: Position {
line: 4,
character: 2,
},
}),
range_length: None,
text: "a".to_owned(),
},
],
};
server.did_change(change_params);
server.srcs.wait_parse_ready(fid, true);
let completion_params = CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri },
position: Position {
line: 4,
character: 3,
},
},
work_done_progress_params: WorkDoneProgressParams::default(),
partial_result_params: PartialResultParams::default(),
context: Some(CompletionContext {
trigger_kind: CompletionTriggerKind::INVOKED,
trigger_character: None,
}),
};
let response: CompletionResponse = server.completion(completion_params).unwrap();
let item1 = CompletionItem {
label: "abc".to_owned(),
kind: Some(CompletionItemKind::VARIABLE),
detail: Some("logic".to_string()),
..CompletionItem::default()
};
let item3 = CompletionItem {
label: "aouter".to_owned(),
kind: Some(CompletionItemKind::VARIABLE),
detail: Some("logic".to_string()),
..CompletionItem::default()
};
if let CompletionResponse::List(item) = response {
eprintln!("{:#?}", item);
assert!(item.items.contains(&item1));
for comp in &item.items {
assert!(comp.label != "abcd");
}
assert!(item.items.contains(&item3));
} else {
panic!();
}
}
#[test]
fn test_dot_completion() {
test_init();
let server = LSPServer::new(None);
let uri = Url::parse("file:///test.sv").unwrap();
let text = r#"interface test_inter;
wire abcd;
endinterface
module test(
test_inter abc
);
abc.
test_inter.
endmodule
"#;
let open_params = DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri.clone(),
language_id: "systemverilog".to_owned(),
version: 0,
text: text.to_owned(),
},
};
server.did_open(open_params);
let fid = server.srcs.get_id(&uri);
server.srcs.wait_parse_ready(fid, true);
let file = server.srcs.get_file(fid).unwrap();
let file = file.read().unwrap();
eprintln!("{}", file.syntax_tree.as_ref().unwrap());
eprintln!(
"{:#?}",
server.srcs.scope_tree.read().unwrap().as_ref().unwrap()
);
let completion_params = CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: uri.clone() },
position: Position {
line: 6,
character: 8,
},
},
work_done_progress_params: WorkDoneProgressParams::default(),
partial_result_params: PartialResultParams::default(),
context: Some(CompletionContext {
trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER,
trigger_character: Some(".".to_string()),
}),
};
let response: CompletionResponse = server.completion(completion_params).unwrap();
dbg!(&response);
let item1 = CompletionItem {
label: "abcd".to_owned(),
kind: Some(CompletionItemKind::VARIABLE),
detail: Some("wire".to_string()),
..CompletionItem::default()
};
if let CompletionResponse::List(item) = response {
eprintln!("{:#?}", item);
assert!(item.items.contains(&item1));
assert!(item.items.len() == 1);
} else {
panic!();
}
let completion_params = CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri },
position: Position {
line: 7,
character: 14,
},
},
work_done_progress_params: WorkDoneProgressParams::default(),
partial_result_params: PartialResultParams::default(),
context: Some(CompletionContext {
trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER,
trigger_character: Some(".".to_string()),
}),
};
let response: CompletionResponse = server.completion(completion_params).unwrap();
if let CompletionResponse::List(item) = response {
eprintln!("{:#?}", item);
assert!(item.items.contains(&item1));
assert!(item.items.len() == 1);
} else {
panic!();
}
}
#[test]
fn test_trigger_dot_nocontext() {
test_init();
let server = LSPServer::new(None);
let uri = Url::parse("file:///test.sv").unwrap();
let text = r#"interface test_inter;
wire abcd;
endinterface
module test(
test_inter abc
);
abc.
test_inter.
endmodule
"#;
let open_params = DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri.clone(),
language_id: "systemverilog".to_owned(),
version: 0,
text: text.to_owned(),
},
};
server.did_open(open_params);
let fid = server.srcs.get_id(&uri);
server.srcs.wait_parse_ready(fid, true);
let file = server.srcs.get_file(fid).unwrap();
let file = file.read().unwrap();
eprintln!("{}", file.syntax_tree.as_ref().unwrap());
eprintln!(
"{:#?}",
server.srcs.scope_tree.read().unwrap().as_ref().unwrap()
);
let completion_params = CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri: uri.clone() },
position: Position {
line: 6,
character: 8,
},
},
work_done_progress_params: WorkDoneProgressParams::default(),
partial_result_params: PartialResultParams::default(),
context: None,
};
let response: CompletionResponse = server.completion(completion_params).unwrap();
dbg!(&response);
let item1 = CompletionItem {
label: "abcd".to_owned(),
kind: Some(CompletionItemKind::VARIABLE),
detail: Some("wire".to_string()),
..CompletionItem::default()
};
if let CompletionResponse::List(item) = response {
eprintln!("{:#?}", item);
assert!(item.items.contains(&item1));
assert!(item.items.len() == 1);
} else {
panic!();
}
let completion_params = CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri },
position: Position {
line: 7,
character: 14,
},
},
work_done_progress_params: WorkDoneProgressParams::default(),
partial_result_params: PartialResultParams::default(),
context: Some(CompletionContext {
trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER,
trigger_character: Some(".".to_string()),
}),
};
let response: CompletionResponse = server.completion(completion_params).unwrap();
if let CompletionResponse::List(item) = response {
eprintln!("{:#?}", item);
assert!(item.items.contains(&item1));
assert!(item.items.len() == 1);
} else {
panic!();
}
}
#[test]
fn test_dot_completion_instantiation() {
test_init();
let text = r#"interface test_inter;
wire wrong;
logic clk;
endinterface
module test;
logic clk;
test_inter2 t (
.clk(clk),
.
)
endmodule
interface test_inter2;
wire abcd;
logic clk;
endinterface
"#;
let doc = Rope::from_str(&text);
let url = Url::parse("file:///test.sv").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);
let completions = scope_tree.get_dot_completion(
token.trim_end_matches('.'),
doc.pos_to_byte(&pos),
&url,
&scope_tree,
);
let labels: Vec<String> = completions.iter().map(|x| x.label.clone()).collect();
assert_eq!(labels, vec!["abcd", "clk"]);
}
/*
#[test]
fn test_package_completion() {
test_init();
let text = r#"package p;
struct {int x;} s1;
struct {int x;} s2;
function void f();
int x;
endfunction
endpackage
module m;
import p::*;
if (1) begin : s1
initial begin
s1.x = 1;
f.x = 1;
end
int x;
end
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();
dbg!(&scope_tree);
/*
let pos = Position::new(8, 9);
let token = get_completion_token(&doc, doc.line(pos.line as usize), pos);
let completions = scope_tree.get_dot_completion(
token.trim_end_matches('.'),
doc.pos_to_byte(&pos),
&url,
&scope_tree,
);
let labels: Vec<String> = completions.iter().map(|x| x.label.clone()).collect();
assert_eq!(labels, vec!["abcd", "clk"]);
*/
panic!();
}
*/
#[test]
fn test_inter_file_completion() {
test_init();
let server = LSPServer::new(None);
let uri = Url::parse("file:///test.sv").unwrap();
let uri2 = Url::parse("file:///test2.sv").unwrap();
let text = r#"module test;
s
endmodule
"#;
let text2 = r#"interface simple_bus;
logic clk;
endinterface"#;
let open_params = DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri.clone(),
language_id: "systemverilog".to_owned(),
version: 0,
text: text.to_owned(),
},
};
let open_params2 = DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri2.clone(),
language_id: "systemverilog".to_owned(),
version: 0,
text: text2.to_owned(),
},
};
server.did_open(open_params);
server.did_open(open_params2);
let fid = server.srcs.get_id(&uri);
let fid2 = server.srcs.get_id(&uri2);
server.srcs.wait_parse_ready(fid, true);
server.srcs.wait_parse_ready(fid2, true);
let completion_params = CompletionParams {
text_document_position: TextDocumentPositionParams {
text_document: TextDocumentIdentifier { uri },
position: Position {
line: 1,
character: 5,
},
},
work_done_progress_params: WorkDoneProgressParams::default(),
partial_result_params: PartialResultParams::default(),
context: Some(CompletionContext {
trigger_kind: CompletionTriggerKind::INVOKED,
trigger_character: None,
}),
};
let response: CompletionResponse = server.completion(completion_params).unwrap();
let scope_tree = server.srcs.scope_tree.read().unwrap();
dbg!(scope_tree.as_ref().unwrap());
if let CompletionResponse::List(item) = response {
// eprintln!("{:#?}", item);
let names: Vec<&String> = item.items.iter().map(|x| &x.label).collect();
assert!(names.contains(&&"simple_bus".to_string()));
} else {
panic!();
}
}
}

View File

@ -199,120 +199,3 @@ fn verible_syntax(
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::support::test_init;
use std::fs::File;
use std::io::Write;
use tempdir::TempDir;
#[test]
fn test_unsaved_file() {
test_init();
let uri = Url::parse("file://test.sv").unwrap();
get_diagnostics(
uri.clone(),
&Rope::default(),
vec![uri],
&ProjectConfig::default(),
);
}
#[test]
fn test_verible_syntax() {
let text = r#"module test;
logic abc;
logic abcd;
a
endmodule
"#;
let doc = Rope::from_str(text);
let errors = verible_syntax(&doc, "verible-verilog-syntax", &[])
.expect("verible-verilog-syntax not found, test can not run");
let expected: Vec<Diagnostic> = vec![Diagnostic {
range: Range {
start: Position {
line: 5,
character: 0,
},
end: Position {
line: 5,
character: 8,
},
},
severity: Some(DiagnosticSeverity::ERROR),
code: None,
source: Some("verible".to_string()),
message: "syntax error at token".to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
}];
assert_eq!(errors, expected);
}
#[test]
fn test_verilator_syntax() {
let text = r#"module test;
logic abc;
logic abcd;
a
endmodule
"#;
let doc = Rope::from_str(text);
// verilator can't read from stdin so we must create a temp dir to place our
// test file
let dir = TempDir::new("verilator_test").unwrap();
let file_path_1 = dir.path().join("test.sv");
let mut f = File::create(&file_path_1).unwrap();
f.write_all(text.as_bytes()).unwrap();
f.sync_all().unwrap();
let errors = verilator_syntax(
&doc,
file_path_1,
"verilator",
&[
"--lint-only".to_string(),
"--sv".to_string(),
"-Wall".to_string(),
],
)
.expect("verilator not found, test can not run");
drop(f);
dir.close().unwrap();
let expected: Vec<Diagnostic> = vec![Diagnostic {
range: Range {
start: Position {
line: 5,
character: 0,
},
end: Position {
line: 5,
character: 0,
},
},
severity: Some(DiagnosticSeverity::ERROR),
code: None,
source: Some("verilator".to_string()),
message: "syntax error, unexpected endmodule, expecting IDENTIFIER or randomize"
.to_string(),
related_information: None,
tags: None,
code_description: None,
data: None,
}];
assert_eq!(errors[0].severity, expected[0].severity);
assert_eq!(errors[0].range.start.line, expected[0].range.start.line);
assert_eq!(errors[0].range.end.line, expected[0].range.end.line);
assert!(errors[0].message.contains("syntax error"));
}
}

View File

@ -90,84 +90,3 @@ pub fn format_document(
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::server::ProjectConfig;
use crate::support::test_init;
use which::which;
#[test]
fn test_formatting() {
test_init();
let text = r#"
module test;
logic a;
logic b;
endmodule"#;
let text_fixed = r#"
module test;
logic a;
logic b;
endmodule
"#;
let doc = Rope::from_str(&text);
if which("verible-verilog-format").is_ok() {
assert_eq!(
format_document(
&doc,
None,
&ProjectConfig::default().verible.format.path,
&[]
)
.unwrap(),
text_fixed.to_string()
);
}
}
#[test]
fn test_range_formatting() {
test_init();
let text = r#"module t1;
logic a;
logic b;
logic c;
endmodule
module t2;
logic a;
logic b;
logic c;
endmodule"#;
let text_fixed = r#"module t1;
logic a;
logic b;
logic c;
endmodule
module t2;
logic a;
logic b;
logic c;
endmodule
"#;
let doc = Rope::from_str(&text);
if which("verible-verilog-format").is_ok() {
assert_eq!(
format_document(
&doc,
Some(Range::new(Position::new(0, 0), Position::new(4, 9))),
&ProjectConfig::default().verible.format.path,
&[]
)
.unwrap(),
text_fixed.to_string()
);
}
}
}

View File

@ -1,19 +1,101 @@
use log::info;
use regex::Regex;
use ropey::RopeSlice;
use tower_lsp::lsp_types::{Hover, Position};
use tower_lsp::lsp_types::{Hover, HoverContents, LanguageString, MarkedString, MarkupContent, Position};
use super::get_word_range_at_position;
/// 将 4'b0011 分解为 ("b", "0011")
fn parse_digit_string(digit_string: &str) -> Option<(&str, &str)> {
if digit_string.len() == 0 {
return None;
}
match digit_string.split_once("'") {
Some((width_string, body_string)) => {
let tag = body_string.get(0..1);
let digit = body_string.get(1..);
if tag.is_none() || digit.is_none() {
return None;
}
let width = width_string.parse::<u32>();
if width.is_err() {
return None;
}
return Some((tag.unwrap(), digit.unwrap()));
},
None => return None
};
}
fn convert_tag_to_radix(tag: &str) -> Option<u32> {
let tag = &tag.to_string().to_lowercase()[..];
match tag {
"b" => Some(2),
"o" => Some(8),
"h" => Some(16),
_ => None
}
}
/// 计算出有符号和无符号下的表示
fn convert_to_sign_unsign<'a>(tag: &'a str, digit_string: &str) -> Option<(String, String)> {
let radix = convert_tag_to_radix(tag);
if radix.is_none() {
return None;
}
let radix = radix.unwrap();
let unsigned_decimal = u128::from_str_radix(digit_string, radix);
if unsigned_decimal.is_err() {
return None;
}
let unsigned_decimal = unsigned_decimal.unwrap();
let pow = radix.pow(digit_string.len() as u32) as u128;
let mut signed_decimal = unsigned_decimal as i128;
if unsigned_decimal >= pow >> 1 {
signed_decimal = (unsigned_decimal as i128) - (pow as i128);
}
Some((
signed_decimal.to_string(),
unsigned_decimal.to_string()
))
}
/// 将 1'b1 翻译成 10进制
pub fn match_format_digit(line: &RopeSlice, pos: Position) -> Option<Hover> {
let line_text = line.as_str().unwrap_or("");
pub fn match_format_digit(line: &RopeSlice, pos: Position, language_id: &str) -> Option<Hover> {
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);
let token_result = get_word_range_at_position(line, pos, regex);
if token_result.is_none() {
return None;
}
let ( digit_string, range ) = token_result.unwrap();
if let Some((tag, digit)) = parse_digit_string(&digit_string) {
if let Some((signed_string, unsigned_string)) = convert_to_sign_unsign(tag, digit) {
let digit_title = LanguageString {
language: language_id.to_string(),
value: format!("{}'{}{}", digit.len(), tag, digit)
};
let markdown = HoverContents::Array(vec![
MarkedString::LanguageString(digit_title),
MarkedString::String(format!("`unsigned` {}\n", unsigned_string)),
MarkedString::String(format!("`signed` {}", signed_string))
]);
let hover = Hover {
contents: markdown,
range: Some(range)
};
return Some(hover);
}
}
None

View File

@ -40,7 +40,7 @@ impl LSPServer {
}
// match digit 5'b00110
if let Some(hover) = match_format_digit(&line_text, pos) {
if let Some(hover) = match_format_digit(&line_text, pos, &language_id) {
return Some(hover);
}
@ -124,7 +124,11 @@ fn match_include(uri: &Url, line: &RopeSlice, pos: Position, language_id: &Strin
value: content
};
let markdown = MarkedString::LanguageString(language_string);
return Some(Hover { contents: HoverContents::Scalar(markdown), range: None })
let hover = Hover {
contents: HoverContents::Scalar(markdown),
range: None
};
return Some(hover);
}
}
}

View File

@ -1,5 +1,6 @@
#![recursion_limit = "256"]
// 核心 hdlparam
pub mod core;
// 自动补全
@ -20,9 +21,11 @@ pub mod format;
// 基础工具
pub mod utils;
// LSP 服务器
pub mod server;
// 管理所有代码
pub mod sources;
pub mod support;
// 自定义发送请求
pub mod custom_request;

View File

@ -18,8 +18,6 @@ mod server;
mod sources;
mod custom_request;
#[cfg(test)]
mod support;
use server::{Backend, GLOBAL_BACKEND};
#[derive(StructOpt, Debug)]

View File

@ -427,48 +427,41 @@ pub fn recovery_sv_parse(
&defines,
&includes,
true,
false
true
) {
Ok((syntax_tree, _)) => {
return Some(syntax_tree);
}
Err(err) => {
println!("find err : {:?}", err);
info!("find err : {:?}", err);
match err {
// 语法错误
sv_parser::Error::Parse(trace) => {
if trace.is_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;
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;
}
}
}
// 把 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));
// 把 last_change 处的地方替换成空格
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;
}
parse_iterations += 1;
None => return None,
},
// 遇到 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
@ -491,18 +484,13 @@ pub fn recovery_sv_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));
let com_define = Define {
identifier: not_found_macro_name.to_string(),
arguments: Vec::new(),
text: Some(DefineText {text: "undefined".to_string(), origin})
text: Some(DefineText {text: "undefined".to_string(), origin: None})
};
defines.insert(not_found_macro_name, Some(com_define));
parse_iterations += 1;
info!("parse iteration plus one");
}
_ => error!("parse error, {:?}", err),
};
@ -510,6 +498,7 @@ pub fn recovery_sv_parse(
}
}
None
}
//TODO: add bounds checking for utf8<->utf16 conversions
@ -597,106 +586,3 @@ impl<'a> LSPSupport for RopeSlice<'a> {
panic!("can't edit a rope slice");
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::support::test_init;
use std::fs::read_to_string;
#[test]
fn test_open_and_change() {
test_init();
let server = LSPServer::new(None);
let uri = Url::parse("file:///test.sv").unwrap();
let text = r#"module test;
logic abc;
endmodule"#;
let open_params = DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri.clone(),
language_id: "systemverilog".to_owned(),
version: 0,
text: text.to_owned(),
},
};
server.did_open(open_params);
let fid = server.srcs.get_id(&uri);
let file = server.srcs.get_file(fid).unwrap();
let file = file.read().unwrap();
assert_eq!(file.text.to_string(), text.to_owned());
drop(file);
let change_params = DidChangeTextDocumentParams {
text_document: VersionedTextDocumentIdentifier { uri, version: 1 },
content_changes: vec![TextDocumentContentChangeEvent {
range: Some(Range {
start: Position {
line: 1,
character: 8,
},
end: Position {
line: 1,
character: 11,
},
}),
range_length: None,
text: "var1".to_owned(),
}],
};
server.did_change(change_params);
let file = server.srcs.get_file(fid).unwrap();
let file = file.read().unwrap();
assert_eq!(
file.text.to_string(),
r#"module test;
logic var1;
endmodule"#
.to_owned()
);
assert_eq!(file.version, 1);
}
#[test]
fn test_fault_tolerance() {
test_init();
let server = LSPServer::new(None);
let uri = Url::parse("file:///test.sv").unwrap();
let text = r#"module test;
logic abc
endmodule"#;
let open_params = DidOpenTextDocumentParams {
text_document: TextDocumentItem {
uri: uri.clone(),
language_id: "systemverilog".to_owned(),
version: 0,
text: text.to_owned(),
},
};
server.did_open(open_params);
let fid = server.srcs.get_id(&uri);
server.srcs.wait_parse_ready(fid, true);
assert!(server
.srcs
.scope_tree
.read()
.unwrap()
.as_ref()
.unwrap()
.contains_scope("test"));
}
#[test]
fn test_header() {
test_init();
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
d.push("test_data/top_inc.sv");
let text = read_to_string(&d).unwrap();
let doc = Rope::from_str(&text);
assert!(recovery_sv_parse(&doc, &Url::from_file_path(d).unwrap(), &None, &Vec::new()).is_some(),);
// TODO: add missing header test
}
}

View File

@ -1,3 +0,0 @@
pub fn test_init() {
let _ = flexi_logger::Logger::with(flexi_logger::LogSpecification::info()).start();
}

View File

@ -29,29 +29,48 @@ pub fn get_definition_token(line: &RopeSlice, pos: Position) -> String {
token
}
pub fn get_word_range_at_position(line: &RopeSlice, pos: Position, regex: Regex) -> String {
let mut token = String::new();
pub fn get_word_range_at_position(line: &RopeSlice, pos: Position, regex: Regex) -> Option<(String, Range)> {
let mut line_iter = line.chars();
// 找到 character 这个位置
for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) {
line_iter.next();
}
let mut c = line_iter.prev();
let mut token = String::new();
let mut start_character = pos.character;
// 往前找
while c.is_some() && (regex.is_match(&c.unwrap().to_string())) {
token.push(c.unwrap());
start_character -= 1;
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();
let mut end_character = pos.character;
// 往后找
while c.is_some() && (regex.is_match(&c.unwrap().to_string())) {
token.push(c.unwrap());
end_character += 1;
c = line_iter.next();
}
token
if token.len() == 0 {
return None;
}
let range = Range::new(
Position { line: pos.line, character: start_character },
Position { line: pos.line, character: end_character }
);
Some((token, range))
}
/// 根据 uri 获取 hdl 的 language id