1use lsp_types::PositionEncodingKind;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum PositionEncoding {
13 Utf8,
15 Utf16,
18 Utf32,
20}
21
22impl PositionEncoding {
23 pub fn capability(self) -> PositionEncodingKind {
25 match self {
26 PositionEncoding::Utf8 => PositionEncodingKind::UTF8,
27 PositionEncoding::Utf16 => PositionEncodingKind::UTF16,
28 PositionEncoding::Utf32 => PositionEncodingKind::UTF32,
29 }
30 }
31
32 pub fn negotiate(offered: &[PositionEncodingKind]) -> Option<Self> {
39 let from_str = |s: &str| match s {
40 "utf-8" => Some(PositionEncoding::Utf8),
41 "utf-16" => Some(PositionEncoding::Utf16),
42 "utf-32" => Some(PositionEncoding::Utf32),
43 _ => None,
44 };
45 offered
46 .iter()
47 .filter_map(|kind| from_str(kind.as_str()))
48 .min_by_key(|enc| match enc {
49 PositionEncoding::Utf8 => 0,
50 PositionEncoding::Utf16 => 1,
51 PositionEncoding::Utf32 => 2,
52 })
53 }
54
55 pub(crate) fn units_of(self, text: &str) -> u32 {
57 match self {
58 PositionEncoding::Utf8 => text.len() as u32,
59 PositionEncoding::Utf16 => text.chars().map(char::len_utf16).sum::<usize>() as u32,
60 PositionEncoding::Utf32 => text.chars().count() as u32,
61 }
62 }
63
64 pub(crate) fn offset_of_units(self, text: &str, units: u32) -> usize {
72 if self == PositionEncoding::Utf8 {
73 return text.len().min(units as usize);
74 }
75 let mut seen = 0u32;
76 for (byte_idx, ch) in text.char_indices() {
77 if seen >= units {
78 return byte_idx;
79 }
80 seen += match self {
81 PositionEncoding::Utf8 => unreachable!(),
82 PositionEncoding::Utf16 => ch.len_utf16() as u32,
83 PositionEncoding::Utf32 => 1,
84 };
85 }
86 text.len()
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 const TEXT: &str = "aĆ©šb"; #[test]
97 fn units_of_counts_per_encoding() {
98 assert_eq!(PositionEncoding::Utf8.units_of(TEXT), 8);
99 assert_eq!(PositionEncoding::Utf16.units_of(TEXT), 5);
100 assert_eq!(PositionEncoding::Utf32.units_of(TEXT), 4);
101 }
102
103 #[test]
104 fn offset_of_units_hits_char_boundaries() {
105 let cases = [
106 (PositionEncoding::Utf8, 0usize, 0usize),
107 (PositionEncoding::Utf8, 1, 1),
108 (PositionEncoding::Utf8, 3, 3),
109 (PositionEncoding::Utf8, 7, 7),
110 (PositionEncoding::Utf8, 8, 8),
111 (PositionEncoding::Utf8, 100, 8),
112 (PositionEncoding::Utf16, 0, 0),
113 (PositionEncoding::Utf16, 1, 1),
114 (PositionEncoding::Utf16, 2, 3),
115 (PositionEncoding::Utf16, 3, 7),
116 (PositionEncoding::Utf16, 4, 7),
117 (PositionEncoding::Utf16, 5, 8),
118 (PositionEncoding::Utf16, 99, 8),
119 (PositionEncoding::Utf32, 0, 0),
120 (PositionEncoding::Utf32, 2, 3),
121 (PositionEncoding::Utf32, 3, 7),
122 (PositionEncoding::Utf32, 4, 8),
123 ];
124 for (enc, units, expected) in cases {
125 assert_eq!(
126 enc.offset_of_units(TEXT, units as u32),
127 expected,
128 "{enc:?} @{units}"
129 );
130 }
131 }
132
133 #[test]
134 fn negotiate_prefers_utf8_then_utf16() {
135 let utf8 = PositionEncodingKind::UTF8;
136 let utf16 = PositionEncodingKind::UTF16;
137 let utf32 = PositionEncodingKind::UTF32;
138
139 assert_eq!(PositionEncoding::negotiate(&[]), None);
140 assert_eq!(
141 PositionEncoding::negotiate(std::slice::from_ref(&utf16)),
142 Some(PositionEncoding::Utf16)
143 );
144 assert_eq!(
145 PositionEncoding::negotiate(&[utf16.clone(), utf8.clone()]),
146 Some(PositionEncoding::Utf8)
147 );
148 assert_eq!(
149 PositionEncoding::negotiate(&[utf32.clone(), utf16]),
150 Some(PositionEncoding::Utf16)
151 );
152 assert_eq!(
153 PositionEncoding::negotiate(&[utf32]),
154 Some(PositionEncoding::Utf32)
155 );
156 }
157}