use serde_json::{Map, Value};
use crate::event::TextChange;
fn object<const N: usize>(entries: [(&str, Value); N]) -> Value {
let mut map = Map::with_capacity(N);
for (key, value) in entries {
map.insert(key.to_string(), value);
}
Value::Object(map)
}
pub fn did_open(uri: &str, language_id: String, version: i32, text: String) -> Value {
object([(
"textDocument",
object([
("uri", Value::from(uri)),
("languageId", Value::String(language_id)),
("version", Value::from(version)),
("text", Value::String(text)),
]),
)])
}
pub fn did_change_full(uri: &str, version: i32, text: String) -> Value {
object([
(
"textDocument",
object([("uri", Value::from(uri)), ("version", Value::from(version))]),
),
(
"contentChanges",
Value::Array(vec![object([("text", Value::String(text))])]),
),
])
}
pub fn did_change_incremental(uri: &str, version: i32, changes: Vec<TextChange>) -> Value {
let content_changes: Vec<Value> = changes
.into_iter()
.map(|c| {
object([
(
"range",
object([
(
"start",
object([
("line", Value::from(c.start_line)),
("character", Value::from(c.start_col)),
]),
),
(
"end",
object([
("line", Value::from(c.end_line)),
("character", Value::from(c.end_col)),
]),
),
]),
),
("text", Value::String(c.text)),
])
})
.collect();
object([
(
"textDocument",
object([("uri", Value::from(uri)), ("version", Value::from(version))]),
),
("contentChanges", Value::Array(content_changes)),
])
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
fn doc() -> String {
"fn main() {\n println!(\"héllo — \\u{1F600}\\t\");\n}\n".to_string()
}
#[test]
fn did_open_matches_json_macro() {
let uri = "file:///tmp/ws/src/main.rs";
let language_id = "rust".to_string();
let text = doc();
let expected = json!({
"textDocument": {
"uri": uri,
"languageId": language_id,
"version": 1,
"text": text,
}
});
let actual = did_open(uri, language_id, 1, text);
assert_eq!(actual, expected);
assert_eq!(
serde_json::to_string(&actual).unwrap(),
serde_json::to_string(&expected).unwrap(),
);
}
#[test]
fn did_change_full_matches_json_macro() {
let uri = "file:///tmp/ws/src/main.rs";
let version: i32 = 42;
let text = doc();
let expected = json!({
"textDocument": { "uri": uri, "version": version },
"contentChanges": [{ "text": text }],
});
let actual = did_change_full(uri, version, text);
assert_eq!(actual, expected);
assert_eq!(
serde_json::to_string(&actual).unwrap(),
serde_json::to_string(&expected).unwrap(),
);
}
#[test]
fn did_change_incremental_matches_json_macro() {
let uri = "file:///tmp/ws/src/main.rs";
let version: i32 = 7;
let changes = vec![
TextChange {
start_line: 0,
start_col: 4,
end_line: 0,
end_col: 9,
text: doc(),
},
TextChange {
start_line: 12,
start_col: 0,
end_line: 13,
end_col: 0,
text: String::new(),
},
];
let expected = json!({
"textDocument": { "uri": uri, "version": version },
"contentChanges": changes.iter().map(|c| json!({
"range": {
"start": { "line": c.start_line, "character": c.start_col },
"end": { "line": c.end_line, "character": c.end_col },
},
"text": c.text,
})).collect::<Vec<_>>(),
});
let actual = did_change_incremental(uri, version, changes);
assert_eq!(actual, expected);
assert_eq!(
serde_json::to_string(&actual).unwrap(),
serde_json::to_string(&expected).unwrap(),
);
}
#[test]
fn document_text_is_moved_not_copied() {
let text = "x".repeat(64 * 1024);
let ptr = text.as_ptr();
let params = did_change_full("file:///tmp/a.rs", 1, text);
let Value::String(out) = ¶ms["contentChanges"][0]["text"] else {
panic!("expected a string");
};
assert_eq!(out.as_ptr(), ptr, "document text was copied, not moved");
}
}