iced_code_editor/canvas_editor/
lsp.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct LspPosition {
6 pub line: u32,
8 pub character: u32,
10}
11
12#[derive(Debug, Clone)]
14pub struct LspDocument {
15 pub uri: String,
17 pub language_id: String,
19 pub version: i32,
21}
22
23impl LspDocument {
24 pub fn new(uri: impl Into<String>, language_id: impl Into<String>) -> Self {
26 Self { uri: uri.into(), language_id: language_id.into(), version: 0 }
27 }
28}
29
30#[derive(Debug, Clone, Copy)]
32pub struct LspRange {
33 pub start: LspPosition,
35 pub end: LspPosition,
37}
38
39#[derive(Debug, Clone)]
41pub struct LspTextChange {
42 pub range: LspRange,
44 pub text: String,
46}
47
48pub trait LspClient {
50 fn did_open(&mut self, _document: &LspDocument, _text: &str) {}
52 fn did_change(
54 &mut self,
55 _document: &LspDocument,
56 _changes: &[LspTextChange],
57 ) {
58 }
59 fn did_save(&mut self, _document: &LspDocument, _text: &str) {}
61 fn did_close(&mut self, _document: &LspDocument) {}
63 fn request_hover(
65 &mut self,
66 _document: &LspDocument,
67 _position: LspPosition,
68 ) {
69 }
70 fn request_completion(
72 &mut self,
73 _document: &LspDocument,
74 _position: LspPosition,
75 ) {
76 }
77 fn request_definition(
83 &mut self,
84 _document: &LspDocument,
85 _position: LspPosition,
86 ) {
87 }
88}
89
90pub fn compute_text_change(old: &str, new: &str) -> Option<LspTextChange> {
94 if old == new {
95 return None;
96 }
97
98 let old_chars: Vec<char> = old.chars().collect();
99 let new_chars: Vec<char> = new.chars().collect();
100 let old_len = old_chars.len();
101 let new_len = new_chars.len();
102
103 let mut prefix = 0;
104 while prefix < old_len
105 && prefix < new_len
106 && old_chars[prefix] == new_chars[prefix]
107 {
108 prefix += 1;
109 }
110
111 let mut suffix = 0;
112 while suffix < old_len.saturating_sub(prefix)
113 && suffix < new_len.saturating_sub(prefix)
114 && old_chars[old_len - 1 - suffix] == new_chars[new_len - 1 - suffix]
115 {
116 suffix += 1;
117 }
118
119 let removed_len = old_len.saturating_sub(prefix + suffix);
120 let inserted: String =
121 new_chars[prefix..new_len.saturating_sub(suffix)].iter().collect();
122
123 let start = position_for_char_index(old, prefix);
124 let end = position_for_char_index(old, prefix + removed_len);
125
126 Some(LspTextChange { range: LspRange { start, end }, text: inserted })
127}
128
129fn position_for_char_index(text: &str, target_index: usize) -> LspPosition {
131 let mut line: u32 = 0;
132 let mut character: u32 = 0;
133 for (index, ch) in text.chars().enumerate() {
134 if index == target_index {
135 return LspPosition { line, character };
136 }
137 if ch == '\n' {
138 line = line.saturating_add(1);
139 character = 0;
140 } else {
141 character = character.saturating_add(1);
142 }
143 }
144
145 LspPosition { line, character }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_compute_text_change_none_when_equal() {
154 let change = compute_text_change("abc", "abc");
155 assert!(change.is_none());
156 }
157
158 #[test]
159 fn test_compute_text_change_insertion() {
160 let change = compute_text_change("abc", "abXc");
161 assert!(change.is_some());
162 if let Some(change) = change {
163 assert_eq!(change.text, "X");
164 assert_eq!(
165 change.range.start,
166 LspPosition { line: 0, character: 2 }
167 );
168 assert_eq!(change.range.end, LspPosition { line: 0, character: 2 });
169 }
170 }
171
172 #[test]
173 fn test_compute_text_change_deletion_across_lines() {
174 let change = compute_text_change("a\nbc", "a\nc");
175 assert!(change.is_some());
176 if let Some(change) = change {
177 assert_eq!(change.text, "");
178 assert_eq!(
179 change.range.start,
180 LspPosition { line: 1, character: 0 }
181 );
182 assert_eq!(change.range.end, LspPosition { line: 1, character: 1 });
183 }
184 }
185
186 #[test]
187 fn test_position_for_char_index_end_of_text() {
188 let pos = position_for_char_index("a\nb", 3);
189 assert_eq!(pos, LspPosition { line: 1, character: 1 });
190 }
191}