Skip to main content

lspkit_vfs/
encoding.rs

1//! Position encoding negotiation.
2//!
3//! LSP 3.17 lets the server negotiate UTF-8, UTF-16, or UTF-32 character
4//! offsets. Internally we always store text as UTF-8; this module exposes the
5//! negotiated encoding so position math can be performed correctly.
6
7use std::fmt;
8use std::str::FromStr;
9
10/// The character offset encoding negotiated with the client.
11#[non_exhaustive]
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
13pub enum PositionEncoding {
14    /// UTF-8 byte offsets.
15    Utf8,
16    /// UTF-16 code-unit offsets. LSP's historical default.
17    #[default]
18    Utf16,
19    /// UTF-32 code-point offsets.
20    Utf32,
21}
22
23impl PositionEncoding {
24    /// Wire-format identifier as used in LSP `positionEncoding`.
25    #[must_use]
26    pub const fn as_str(self) -> &'static str {
27        match self {
28            Self::Utf8 => "utf-8",
29            Self::Utf16 => "utf-16",
30            Self::Utf32 => "utf-32",
31        }
32    }
33}
34
35impl fmt::Display for PositionEncoding {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        f.write_str(self.as_str())
38    }
39}
40
41impl FromStr for PositionEncoding {
42    type Err = UnknownEncoding;
43
44    fn from_str(s: &str) -> Result<Self, Self::Err> {
45        match s {
46            "utf-8" => Ok(Self::Utf8),
47            "utf-16" => Ok(Self::Utf16),
48            "utf-32" => Ok(Self::Utf32),
49            other => Err(UnknownEncoding(other.to_owned())),
50        }
51    }
52}
53
54/// Error returned when an encoding string is not recognized.
55#[derive(Debug, thiserror::Error)]
56#[error("unknown position encoding: {0}")]
57pub struct UnknownEncoding(pub String);
58
59/// Pick a server-preferred encoding from a list of client-supported encodings.
60///
61/// Preference order matches the LSP 3.17 specification: the server SHOULD
62/// honor the first encoding the client lists that the server also supports.
63/// When `client_supported` is empty, returns [`PositionEncoding::Utf16`].
64#[must_use]
65pub fn negotiate(client_supported: &[PositionEncoding]) -> PositionEncoding {
66    client_supported
67        .first()
68        .copied()
69        .unwrap_or(PositionEncoding::Utf16)
70}