243 lines
7.9 KiB
Rust
243 lines
7.9 KiB
Rust
use super::*;
|
|
|
|
#[test]
|
|
fn native_definitions_preserve_snapshot_identity_and_utf16_until_navigation() {
|
|
let source = Source {
|
|
buffer_id: 7,
|
|
revision: 3,
|
|
row: 0,
|
|
column: 0,
|
|
request_id: 11,
|
|
};
|
|
let path = Path::new("/vamp-immutable-snapshot/Before/renamed/λ.rs");
|
|
let span = json!({
|
|
"start": {"line": 4, "character": 5},
|
|
"end": {"line": 4, "character": 8}
|
|
});
|
|
for location in [
|
|
json!({"uri": file_uri(path).unwrap(), "range": span}),
|
|
json!({"targetUri": file_uri(path).unwrap(),
|
|
"targetRange": span, "targetSelectionRange": span}),
|
|
] {
|
|
let effects = Runtime::definition_effects(&source, &location).unwrap();
|
|
let expected_target_range = location.get("targetUri").map(|_| Utf16Range {
|
|
start: (4, 5),
|
|
end: (4, 8),
|
|
});
|
|
assert!(matches!(
|
|
effects.as_slice(),
|
|
[UpdateEffect::NavigateUtf16 {
|
|
buffer_id: 7, revision: 3, path: target,
|
|
range: Utf16Range { start: (4, 5), end: (4, 8) }, target_range,
|
|
}] if target == path && *target_range == expected_target_range
|
|
));
|
|
}
|
|
for invalid in [
|
|
json!({"start": {"line": 4, "character": 8},
|
|
"end": {"line": 4, "character": 5}}),
|
|
json!({"start": {"line": -1, "character": 0},
|
|
"end": {"line": 0, "character": 0}}),
|
|
] {
|
|
for location in [
|
|
json!({"uri": file_uri(path).unwrap(), "range": invalid}),
|
|
json!({"targetUri": file_uri(path).unwrap(),
|
|
"targetRange": invalid, "targetSelectionRange": span}),
|
|
json!({"targetUri": file_uri(path).unwrap(),
|
|
"targetRange": span, "targetSelectionRange": invalid}),
|
|
] {
|
|
assert!(Runtime::definition_effects(&source, &location).is_err());
|
|
}
|
|
}
|
|
assert!(
|
|
serde_json::from_value::<UpdateEffect>(json!({
|
|
"type": "navigate_utf16", "buffer_id": 7, "revision": 3,
|
|
"path": path, "range": {"start": [4,5], "end": [4,8]},
|
|
"target_range": null
|
|
}))
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn semantic_decoder_validates_raw_coordinates_before_grapheme_rounding() {
|
|
let options = semantic_options(Some(&json!({
|
|
"full": true,
|
|
"legend": {"tokenTypes": ["custom", "function"],
|
|
"tokenModifiers": ["custom", "readonly", "declaration", "async"]}
|
|
})))
|
|
.unwrap()
|
|
.unwrap();
|
|
let tokens = decode_semantic_tokens(
|
|
"A😀e\u{301}Z\r\n\r\nx",
|
|
&json!({"data": [
|
|
0, 1, 2, 1, 2,
|
|
0, 2, 1, 1, 12,
|
|
0, 1, 1, 1, 0,
|
|
0, 1, 999, 0, 0,
|
|
1, 0, 1, 1, 0,
|
|
1, 0, 0, 1, 0,
|
|
0, 0, 100, 1, 0
|
|
]}),
|
|
&options,
|
|
)
|
|
.unwrap()
|
|
.unwrap();
|
|
assert_eq!(tokens.len(), 4);
|
|
assert_eq!(tokens[0].start, TextPosition { row: 0, column: 1 });
|
|
assert_eq!(tokens[0].end, TextPosition { row: 0, column: 2 });
|
|
assert_eq!(tokens[0].modifiers, SemanticTokenModifier::Readonly as u16);
|
|
assert_eq!(tokens[1].start, TextPosition { row: 0, column: 2 });
|
|
assert_eq!(tokens[1].end, TextPosition { row: 0, column: 3 });
|
|
assert_eq!(
|
|
tokens[1].modifiers,
|
|
SemanticTokenModifier::Declaration as u16 | SemanticTokenModifier::Async as u16
|
|
);
|
|
assert_eq!(tokens[2].start, tokens[1].start);
|
|
assert_eq!(tokens[3].end, TextPosition { row: 2, column: 1 });
|
|
for data in [
|
|
json!([0, 0, 1, 1]),
|
|
json!([-1, 0, 1, 1, 0]),
|
|
json!([0, 0.5, 1, 1, 0]),
|
|
json!([0, 0, 1, 1, 2147483648u64]),
|
|
json!([0, 0, 1, 65536, 0]),
|
|
json!([0, 0, 1, 2, 0]),
|
|
json!([0, 0, 1, 1, 16]),
|
|
json!([0, 2, 1, 0, 0]),
|
|
json!([0, 1, 1, 1, 0]),
|
|
json!([0, 7, 1, 1, 0]),
|
|
json!([2, 0, 1, 1, 0]),
|
|
json!([0, 1, 2, 1, 0, 0, 1, 1, 1, 0]),
|
|
] {
|
|
assert!(
|
|
decode_semantic_tokens("A😀e\u{301}Z\n", &json!({"data": data}), &options).is_err(),
|
|
"{data}"
|
|
);
|
|
}
|
|
let count = MAX_BODY / std::mem::size_of::<SemanticToken>() + 1;
|
|
assert!(decode_semantic_tokens("x", &json!({"data": vec![0; count * 5]}), &options).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn unicode_positions_and_local_uri_boundaries() {
|
|
let text = "A😀e\u{301}Z\n";
|
|
let (start, end) = range(
|
|
text,
|
|
&json!({"start": {"line": 0, "character": 2}, "end": {"line": 0, "character": 4}}),
|
|
)
|
|
.unwrap();
|
|
assert_eq!(start, TextPosition { row: 0, column: 1 });
|
|
assert_eq!(end, TextPosition { row: 0, column: 3 });
|
|
assert_eq!(
|
|
outbound_position(text, 0, 3).unwrap(),
|
|
json!({"line": 0, "character": 5})
|
|
);
|
|
assert_eq!(
|
|
position(text, &json!({"line": 1, "character": 999}), false).unwrap(),
|
|
TextPosition { row: 1, column: 0 }
|
|
);
|
|
assert!(
|
|
range(
|
|
text,
|
|
&json!({"start": {"line": 0, "character": 3}, "end": {"line": 0, "character": 2}})
|
|
)
|
|
.is_err()
|
|
);
|
|
let path = Path::new("/tmp/source space λ😀.rs");
|
|
assert_eq!(uri_path(&file_uri(path).unwrap()).unwrap(), path);
|
|
assert_eq!(
|
|
uri_path("file://localhost/tmp/a%23b").unwrap(),
|
|
Path::new("/tmp/a#b")
|
|
);
|
|
for uri in [
|
|
"https://example.invalid/a",
|
|
"file://remote/tmp/a",
|
|
"file:///tmp/%",
|
|
"file:///tmp/%00",
|
|
"file:///tmp/%FF",
|
|
"file:///tmp/a?query",
|
|
] {
|
|
assert!(uri_path(uri).is_err(), "{uri}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn hover_contents_keep_lines_and_reject_malformed_values() {
|
|
let source = "A😀e\u{301}Z\r\n";
|
|
for kind in ["plaintext", "markdown"] {
|
|
assert_eq!(
|
|
hover_text(
|
|
&json!({
|
|
"contents": {"kind": kind, "value": "😀e\u{301}\r\n\tcode\rnext\u{1b}\u{85}"},
|
|
"range": {"start": {"line": 0, "character": 2},
|
|
"end": {"line": 0, "character": 4}}
|
|
}),
|
|
source,
|
|
)
|
|
.unwrap(),
|
|
Some("😀e\u{301}\n code\nnext ".into()),
|
|
);
|
|
}
|
|
for contents in [
|
|
json!("line\ncode"),
|
|
json!({"language": "rust", "value": "line\ncode"}),
|
|
] {
|
|
assert_eq!(
|
|
hover_text(&json!({"contents": contents}), source).unwrap(),
|
|
Some("line\ncode".into()),
|
|
);
|
|
}
|
|
assert_eq!(
|
|
hover_text(
|
|
&json!({"contents": ["first", {"language": "rust", "value": "fn main() {\n}"}]}),
|
|
source,
|
|
)
|
|
.unwrap(),
|
|
Some("first\n\nfn main() {\n}".into()),
|
|
);
|
|
for value in [
|
|
Value::Null,
|
|
json!({"contents": ""}),
|
|
json!({"contents": []}),
|
|
] {
|
|
assert_eq!(hover_text(&value, source).unwrap(), None);
|
|
}
|
|
for value in [
|
|
json!({}),
|
|
json!({"contents": {"kind": "html", "value": "x"}}),
|
|
json!({"contents": {"language": "rust", "value": false}}),
|
|
json!({"contents": [{"kind": "markdown", "value": "x"}]}),
|
|
json!({"contents": "x", "range": {"start": {"line": 1, "character": 0},
|
|
"end": {"line": 0, "character": 0}}}),
|
|
json!({"contents": "\t".repeat(MAX_BODY / 4 + 1)}),
|
|
] {
|
|
assert!(hover_text(&value, source).is_err());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn completion_text_limit_counts_the_atomic_batch_not_sort_metadata() {
|
|
let source = Source {
|
|
buffer_id: 1,
|
|
revision: 0,
|
|
row: 0,
|
|
column: 0,
|
|
request_id: 1,
|
|
};
|
|
let at_limit = json!([{"label": "x", "insertText": "x".repeat(MAX_BODY - 1)}]);
|
|
assert_eq!(
|
|
decode_completion("", &at_limit, &source, None).unwrap()[0].edits[0]
|
|
.text
|
|
.len(),
|
|
MAX_BODY - 1
|
|
);
|
|
let over_limit = json!([
|
|
{"label": "x", "insertText": "x".repeat(MAX_BODY - 1)},
|
|
{"label": "y"}
|
|
]);
|
|
assert!(
|
|
decode_completion("", &over_limit, &source, None)
|
|
.unwrap_err()
|
|
.contains("4 MiB")
|
|
);
|
|
}
|