hjkl_lsp/encoding.rs
1//! Position-encoding negotiation (LSP `general.positionEncodings`).
2//!
3//! The LSP spec lets client and server negotiate the unit used for the
4//! `character` field of every `Position`/`Range` exchanged between them:
5//! UTF-8 byte offsets, UTF-16 code units (the spec default — every server
6//! MUST support it even if it never says so), or UTF-32 code points (rare,
7//! unsupported here). hjkl advertises both `"utf-8"` and `"utf-16"` in
8//! `general.positionEncodings` (see `crate::server::initialize_handshake`)
9//! and honours whichever the server picks back in `initialize`'s
10//! `capabilities.positionEncoding`.
11
12/// The unit used for the `character` field of every `Position`/`Range`
13/// exchanged with a given server.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15pub enum PositionEncoding {
16 /// UTF-8 byte offsets within a line. hjkl's preferred encoding — skips
17 /// per-line UTF-16 unit counting when the server supports it.
18 Utf8,
19 /// UTF-16 code units within a line. The LSP spec's default: a server
20 /// that never mentions `positionEncoding` in its `initialize` response
21 /// is using this.
22 #[default]
23 Utf16,
24}
25
26impl PositionEncoding {
27 /// Parse the negotiated encoding from a server's `initialize` response
28 /// `capabilities`. Absence of `positionEncoding`, or any value other
29 /// than `"utf-8"`, means UTF-16 per spec — this also covers a server
30 /// that (incorrectly) echoes `"utf-32"`, which hjkl doesn't implement;
31 /// falling back to UTF-16 is the spec-mandated safe default rather than
32 /// silently mis-converting UTF-32 offsets as UTF-16 ones.
33 pub fn from_capabilities(capabilities: &serde_json::Value) -> Self {
34 match capabilities
35 .get("positionEncoding")
36 .and_then(|v| v.as_str())
37 {
38 Some("utf-8") => Self::Utf8,
39 _ => Self::Utf16,
40 }
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use super::PositionEncoding;
47 use serde_json::json;
48
49 #[test]
50 fn missing_field_defaults_to_utf16() {
51 assert_eq!(
52 PositionEncoding::from_capabilities(&json!({})),
53 PositionEncoding::Utf16
54 );
55 }
56
57 #[test]
58 fn explicit_utf8() {
59 assert_eq!(
60 PositionEncoding::from_capabilities(&json!({ "positionEncoding": "utf-8" })),
61 PositionEncoding::Utf8
62 );
63 }
64
65 #[test]
66 fn explicit_utf16() {
67 assert_eq!(
68 PositionEncoding::from_capabilities(&json!({ "positionEncoding": "utf-16" })),
69 PositionEncoding::Utf16
70 );
71 }
72
73 #[test]
74 fn unknown_value_defaults_to_utf16() {
75 assert_eq!(
76 PositionEncoding::from_capabilities(&json!({ "positionEncoding": "utf-32" })),
77 PositionEncoding::Utf16
78 );
79 }
80
81 #[test]
82 fn null_capabilities_defaults_to_utf16() {
83 assert_eq!(
84 PositionEncoding::from_capabilities(&serde_json::Value::Null),
85 PositionEncoding::Utf16
86 );
87 }
88}