实现 digit hover
This commit is contained in:
parent
302dafbc05
commit
5d8eb18042
@ -1,4 +1,3 @@
|
|||||||
[build]
|
[build]
|
||||||
jobs = 16 # 并行构建任务的数量,默认等于 CPU 的核心数
|
|
||||||
rustc = "rustc" # rust 编译器
|
rustc = "rustc" # rust 编译器
|
||||||
target-dir = "target" # 存放编译输出结果的目录
|
target-dir = "target" # 存放编译输出结果的目录
|
@ -168,607 +168,3 @@ fn get_completion_token(text: &Rope, line: RopeSlice, pos: Position) -> String {
|
|||||||
result
|
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
|
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
|
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 log::info;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use ropey::RopeSlice;
|
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;
|
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进制
|
/// 将 1'b1 翻译成 10进制
|
||||||
pub fn match_format_digit(line: &RopeSlice, pos: Position) -> Option<Hover> {
|
pub fn match_format_digit(line: &RopeSlice, pos: Position, language_id: &str) -> Option<Hover> {
|
||||||
let line_text = line.as_str().unwrap_or("");
|
|
||||||
let regex = Regex::new(r"[0-9'bho]").unwrap();
|
let regex = Regex::new(r"[0-9'bho]").unwrap();
|
||||||
let digit_string = get_word_range_at_position(line, pos, regex);
|
let token_result = get_word_range_at_position(line, pos, regex);
|
||||||
if digit_string.len() > 0 {
|
|
||||||
info!("current digit: {}", digit_string);
|
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
|
None
|
||||||
|
@ -40,7 +40,7 @@ impl LSPServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// match digit 5'b00110
|
// 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);
|
return Some(hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +124,11 @@ fn match_include(uri: &Url, line: &RopeSlice, pos: Position, language_id: &Strin
|
|||||||
value: content
|
value: content
|
||||||
};
|
};
|
||||||
let markdown = MarkedString::LanguageString(language_string);
|
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"]
|
#![recursion_limit = "256"]
|
||||||
|
|
||||||
|
// 核心 hdlparam
|
||||||
pub mod core;
|
pub mod core;
|
||||||
|
|
||||||
// 自动补全
|
// 自动补全
|
||||||
@ -20,9 +21,11 @@ pub mod format;
|
|||||||
// 基础工具
|
// 基础工具
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
// LSP 服务器
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
|
// 管理所有代码
|
||||||
pub mod sources;
|
pub mod sources;
|
||||||
pub mod support;
|
|
||||||
|
|
||||||
// 自定义发送请求
|
// 自定义发送请求
|
||||||
pub mod custom_request;
|
pub mod custom_request;
|
||||||
|
@ -18,8 +18,6 @@ mod server;
|
|||||||
mod sources;
|
mod sources;
|
||||||
mod custom_request;
|
mod custom_request;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod support;
|
|
||||||
use server::{Backend, GLOBAL_BACKEND};
|
use server::{Backend, GLOBAL_BACKEND};
|
||||||
|
|
||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
|
130
src/sources.rs
130
src/sources.rs
@ -427,26 +427,18 @@ pub fn recovery_sv_parse(
|
|||||||
&defines,
|
&defines,
|
||||||
&includes,
|
&includes,
|
||||||
true,
|
true,
|
||||||
false
|
true
|
||||||
) {
|
) {
|
||||||
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) => {
|
sv_parser::Error::Parse(trace) => match trace {
|
||||||
if trace.is_none() {
|
Some((_, bpos)) => {
|
||||||
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;
|
||||||
@ -455,7 +447,7 @@ pub fn recovery_sv_parse(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 把 last_change_range 中的涉及到的所有行内容用等长的空格替代
|
// 把 last_change 处的地方替换成空格
|
||||||
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);
|
||||||
@ -464,11 +456,12 @@ pub fn recovery_sv_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
|
||||||
@ -491,18 +484,13 @@ pub fn recovery_sv_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 pathbuf = PathBuf::from_str(uri.as_str()).unwrap();
|
|
||||||
let origin = Some((pathbuf, range));
|
|
||||||
let com_define = Define {
|
let com_define = Define {
|
||||||
identifier: not_found_macro_name.to_string(),
|
identifier: not_found_macro_name.to_string(),
|
||||||
arguments: Vec::new(),
|
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));
|
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),
|
||||||
};
|
};
|
||||||
@ -510,6 +498,7 @@ pub fn recovery_sv_parse(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: add bounds checking for utf8<->utf16 conversions
|
//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");
|
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
|
token
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_word_range_at_position(line: &RopeSlice, pos: Position, regex: Regex) -> String {
|
pub fn get_word_range_at_position(line: &RopeSlice, pos: Position, regex: Regex) -> Option<(String, Range)> {
|
||||||
let mut token = String::new();
|
|
||||||
let mut line_iter = line.chars();
|
let mut line_iter = line.chars();
|
||||||
|
|
||||||
|
// 找到 character 这个位置
|
||||||
for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) {
|
for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) {
|
||||||
line_iter.next();
|
line_iter.next();
|
||||||
}
|
}
|
||||||
let mut c = line_iter.prev();
|
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())) {
|
while c.is_some() && (regex.is_match(&c.unwrap().to_string())) {
|
||||||
token.push(c.unwrap());
|
token.push(c.unwrap());
|
||||||
|
start_character -= 1;
|
||||||
c = line_iter.prev();
|
c = line_iter.prev();
|
||||||
}
|
}
|
||||||
token = token.chars().rev().collect();
|
token = token.chars().rev().collect();
|
||||||
line_iter = line.chars();
|
line_iter = line.chars();
|
||||||
|
|
||||||
for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) {
|
for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) {
|
||||||
line_iter.next();
|
line_iter.next();
|
||||||
}
|
}
|
||||||
let mut c = 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())) {
|
while c.is_some() && (regex.is_match(&c.unwrap().to_string())) {
|
||||||
token.push(c.unwrap());
|
token.push(c.unwrap());
|
||||||
|
end_character += 1;
|
||||||
c = line_iter.next();
|
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
|
/// 根据 uri 获取 hdl 的 language id
|
||||||
|
Loading…
x
Reference in New Issue
Block a user