ad_editor/lsp/
capabilities.rs1use crate::{
3 buffer::Buffer,
4 exec::{Addr, AddrBase},
5 lsp::Pos,
6};
7use lsp_types::{InitializeResult, Location, Position, PositionEncodingKind, ServerCapabilities};
8use tracing::warn;
9
10#[derive(Debug)]
11#[allow(dead_code)]
12pub(crate) struct Capabilities {
13 inner: ServerCapabilities,
14 pub(super) position_encoding: PositionEncoding,
15}
16
17impl Capabilities {
18 pub(crate) fn try_new(res: InitializeResult) -> Option<Self> {
19 let position_encoding = match &res.capabilities.position_encoding {
20 Some(p) if *p == PositionEncodingKind::UTF8 => PositionEncoding::Utf8,
21 Some(p) if *p == PositionEncodingKind::UTF16 => PositionEncoding::Utf16,
22 Some(p) if *p == PositionEncodingKind::UTF32 => PositionEncoding::Utf32,
23 None => PositionEncoding::Utf16, Some(p) => {
26 warn!(
27 "LSP provided unknown position encoding: {p:?} {:?}",
28 res.server_info
29 );
30 return None;
31 }
32 };
33
34 Some(Self {
35 inner: res.capabilities,
36 position_encoding,
37 })
38 }
39
40 pub(crate) fn as_pretty_json(&self) -> Option<String> {
41 serde_json::to_string_pretty(&self.inner).ok()
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
68pub(crate) enum PositionEncoding {
69 Utf8,
71 Utf16,
73 Utf32,
75}
76
77impl PositionEncoding {
78 pub(crate) fn parse_lsp_position(&self, b: &Buffer, pos: Position) -> (usize, usize) {
79 let pos_line = pos.line as usize;
80 if pos_line > b.len_lines() - 1 {
81 warn!("LSP position out of bounds, clamping to EOF");
82 return (b.len_lines().saturating_sub(1), b.len_chars());
83 }
84
85 match self {
86 Self::Utf8 => {
87 let line_start = b.txt.line_to_char(pos.line as usize);
88 let byte_idx = b.txt.char_to_byte(line_start + pos.character as usize);
89 let col = b.txt.raw_byte_to_char(byte_idx);
90
91 (pos.line as usize, col)
92 }
93
94 Self::Utf16 => {
95 let slice = b.txt.line(pos.line as usize);
96 let mut character = pos.character as usize;
97 let mut col = 0;
98 for (idx, ch) in slice.chars().enumerate() {
99 let n = ch.len_utf16();
100 col = idx;
101 character -= n;
102 if character == 0 {
103 break;
104 }
105 }
106
107 (pos.line as usize, col)
108 }
109
110 Self::Utf32 => (pos.line as usize, pos.character as usize),
111 }
112 }
113
114 pub(super) fn buffer_pos(&self, b: &Buffer) -> Pos {
115 let file = b.full_name();
116 let (y, x) = b.dot.active_cur().as_yx(b);
117 let (line, character) = self.lsp_position(b, y, x);
118
119 Pos::new(file, line, character)
120 }
121
122 fn lsp_position(&self, b: &Buffer, line: usize, col: usize) -> (u32, u32) {
123 match self {
124 Self::Utf8 => {
125 let line_start = b.txt.line_to_char(line);
126 let start_idx = b.txt.char_to_byte(line_start);
127 let character = b.txt.char_to_byte(line_start + col) - start_idx;
128
129 (line as u32, character as u32)
130 }
131
132 Self::Utf16 => {
133 let slice = b.txt.line(line);
134 let mut character = 0;
135 for ch in slice.chars().take(col) {
136 character += ch.len_utf16();
137 }
138
139 (line as u32, character as u32)
140 }
141
142 Self::Utf32 => (line as u32, col as u32),
143 }
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
148pub struct Coords {
149 encoding: PositionEncoding,
150 start: Position,
151 end: Position,
152}
153
154impl Coords {
155 pub(crate) fn new(loc: Location, encoding: PositionEncoding) -> (String, Self) {
156 let filepath = loc
157 .uri
158 .to_string()
159 .strip_prefix("file://")
160 .unwrap()
161 .to_owned();
162
163 let coords = Coords {
164 encoding,
165 start: loc.range.start,
166 end: loc.range.end,
167 };
168
169 (filepath, coords)
170 }
171
172 pub fn line(&self) -> u32 {
173 self.start.line
174 }
175
176 pub fn as_addr(&self, b: &Buffer) -> Addr {
177 let (sr, sc) = self.encoding.parse_lsp_position(b, self.start);
178 let (er, ec) = self.encoding.parse_lsp_position(b, self.end);
179
180 Addr::Compound(
181 AddrBase::LineAndColumn(sr, sc).into(),
182 AddrBase::LineAndColumn(er, ec).into(),
183 )
184 }
185}