实现 digit hover
This commit is contained in:
parent
302dafbc05
commit
5d8eb18042
@ -1,4 +1,3 @@
|
||||
[build]
|
||||
jobs = 16 # 并行构建任务的数量,默认等于 CPU 的核心数
|
||||
rustc = "rustc" # rust 编译器
|
||||
target-dir = "target" # 存放编译输出结果的目录
|
@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -18,8 +18,6 @@ mod server;
|
||||
mod sources;
|
||||
mod custom_request;
|
||||
|
||||
#[cfg(test)]
|
||||
mod support;
|
||||
use server::{Backend, GLOBAL_BACKEND};
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
|
160
src/sources.rs
160
src/sources.rs
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
pub fn test_init() {
|
||||
let _ = flexi_logger::Logger::with(flexi_logger::LogSpecification::info()).start();
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user