]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/lsp_utils.rs
Merge #7326
[rust.git] / crates / rust-analyzer / src / lsp_utils.rs
1 //! Utilities for LSP-related boilerplate code.
2 use std::{error::Error, ops::Range};
3
4 use ide::LineIndex;
5 use ide_db::base_db::Canceled;
6 use lsp_server::Notification;
7
8 use crate::{from_proto, global_state::GlobalState};
9
10 pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool {
11     e.downcast_ref::<Canceled>().is_some()
12 }
13
14 pub(crate) fn notification_is<N: lsp_types::notification::Notification>(
15     notification: &Notification,
16 ) -> bool {
17     notification.method == N::METHOD
18 }
19
20 #[derive(Debug, Eq, PartialEq)]
21 pub(crate) enum Progress {
22     Begin,
23     Report,
24     End,
25 }
26
27 impl Progress {
28     pub(crate) fn fraction(done: usize, total: usize) -> f64 {
29         assert!(done <= total);
30         done as f64 / total.max(1) as f64
31     }
32 }
33
34 impl GlobalState {
35     pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) {
36         let message = message.into();
37         self.send_notification::<lsp_types::notification::ShowMessage>(
38             lsp_types::ShowMessageParams { typ, message },
39         )
40     }
41
42     pub(crate) fn report_progress(
43         &mut self,
44         title: &str,
45         state: Progress,
46         message: Option<String>,
47         fraction: Option<f64>,
48     ) {
49         if !self.config.work_done_progress() {
50             return;
51         }
52         let percentage = fraction.map(|f| {
53             assert!(0.0 <= f && f <= 1.0);
54             (f * 100.0) as u32
55         });
56         let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
57         let work_done_progress = match state {
58             Progress::Begin => {
59                 self.send_request::<lsp_types::request::WorkDoneProgressCreate>(
60                     lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
61                     |_, _| (),
62                 );
63
64                 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
65                     title: title.into(),
66                     cancellable: None,
67                     message,
68                     percentage,
69                 })
70             }
71             Progress::Report => {
72                 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
73                     cancellable: None,
74                     message,
75                     percentage,
76                 })
77             }
78             Progress::End => {
79                 lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
80             }
81         };
82         self.send_notification::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
83             token,
84             value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
85         });
86     }
87 }
88
89 pub(crate) fn apply_document_changes(
90     old_text: &mut String,
91     content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
92 ) {
93     let mut line_index = LineIndex::new(old_text);
94     // The changes we got must be applied sequentially, but can cross lines so we
95     // have to keep our line index updated.
96     // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
97     // remember the last valid line in the index and only rebuild it if needed.
98     // The VFS will normalize the end of lines to `\n`.
99     enum IndexValid {
100         All,
101         UpToLineExclusive(u32),
102     }
103
104     impl IndexValid {
105         fn covers(&self, line: u32) -> bool {
106             match *self {
107                 IndexValid::UpToLineExclusive(to) => to > line,
108                 _ => true,
109             }
110         }
111     }
112
113     let mut index_valid = IndexValid::All;
114     for change in content_changes {
115         match change.range {
116             Some(range) => {
117                 if !index_valid.covers(range.end.line) {
118                     line_index = LineIndex::new(&old_text);
119                 }
120                 index_valid = IndexValid::UpToLineExclusive(range.start.line);
121                 let range = from_proto::text_range(&line_index, range);
122                 old_text.replace_range(Range::<usize>::from(range), &change.text);
123             }
124             None => {
125                 *old_text = change.text;
126                 index_valid = IndexValid::UpToLineExclusive(0);
127             }
128         }
129     }
130 }
131
132 /// Checks that the edits inside the completion and the additional edits do not overlap.
133 /// LSP explicitly forbids the additional edits to overlap both with the main edit and themselves.
134 pub(crate) fn all_edits_are_disjoint(
135     completion: &lsp_types::CompletionItem,
136     additional_edits: &[lsp_types::TextEdit],
137 ) -> bool {
138     let mut edit_ranges = Vec::new();
139     match completion.text_edit.as_ref() {
140         Some(lsp_types::CompletionTextEdit::Edit(edit)) => {
141             edit_ranges.push(edit.range);
142         }
143         Some(lsp_types::CompletionTextEdit::InsertAndReplace(edit)) => {
144             edit_ranges.push(edit.insert);
145             edit_ranges.push(edit.replace);
146         }
147         None => {}
148     }
149     if let Some(additional_changes) = completion.additional_text_edits.as_ref() {
150         edit_ranges.extend(additional_changes.iter().map(|edit| edit.range));
151     };
152     edit_ranges.extend(additional_edits.iter().map(|edit| edit.range));
153     edit_ranges.sort_by_key(|range| (range.start, range.end));
154     edit_ranges
155         .iter()
156         .zip(edit_ranges.iter().skip(1))
157         .all(|(previous, next)| previous.end <= next.start)
158 }
159
160 #[cfg(test)]
161 mod tests {
162     use lsp_types::{
163         CompletionItem, CompletionTextEdit, InsertReplaceEdit, Position, Range,
164         TextDocumentContentChangeEvent,
165     };
166
167     use super::*;
168
169     #[test]
170     fn test_apply_document_changes() {
171         macro_rules! c {
172             [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => {
173                 vec![$(TextDocumentContentChangeEvent {
174                     range: Some(Range {
175                         start: Position { line: $sl, character: $sc },
176                         end: Position { line: $el, character: $ec },
177                     }),
178                     range_length: None,
179                     text: String::from($text),
180                 }),+]
181             };
182         }
183
184         let mut text = String::new();
185         apply_document_changes(&mut text, vec![]);
186         assert_eq!(text, "");
187         apply_document_changes(
188             &mut text,
189             vec![TextDocumentContentChangeEvent {
190                 range: None,
191                 range_length: None,
192                 text: String::from("the"),
193             }],
194         );
195         assert_eq!(text, "the");
196         apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]);
197         assert_eq!(text, "the quick");
198         apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
199         assert_eq!(text, "quick foxes");
200         apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]);
201         assert_eq!(text, "quick foxes\ndream");
202         apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]);
203         assert_eq!(text, "quick foxes\nhave dream");
204         apply_document_changes(
205             &mut text,
206             c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
207         );
208         assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
209         apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
210         assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
211         apply_document_changes(
212             &mut text,
213             c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
214         );
215         assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
216         apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
217         assert_eq!(text, "the quick \nthey have quiet dreams\n");
218
219         text = String::from("❤️");
220         apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]);
221         assert_eq!(text, "a❤️");
222
223         text = String::from("a\nb");
224         apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
225         assert_eq!(text, "adcb");
226
227         text = String::from("a\nb");
228         apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
229         assert_eq!(text, "ațc\ncb");
230     }
231
232     #[test]
233     fn empty_completion_disjoint_tests() {
234         let empty_completion =
235             CompletionItem::new_simple("label".to_string(), "detail".to_string());
236
237         let disjoint_edit_1 = lsp_types::TextEdit::new(
238             Range::new(Position::new(2, 2), Position::new(3, 3)),
239             "new_text".to_string(),
240         );
241         let disjoint_edit_2 = lsp_types::TextEdit::new(
242             Range::new(Position::new(3, 3), Position::new(4, 4)),
243             "new_text".to_string(),
244         );
245
246         let joint_edit = lsp_types::TextEdit::new(
247             Range::new(Position::new(1, 1), Position::new(5, 5)),
248             "new_text".to_string(),
249         );
250
251         assert!(
252             all_edits_are_disjoint(&empty_completion, &[]),
253             "Empty completion has all its edits disjoint"
254         );
255         assert!(
256             all_edits_are_disjoint(
257                 &empty_completion,
258                 &[disjoint_edit_1.clone(), disjoint_edit_2.clone()]
259             ),
260             "Empty completion is disjoint to whatever disjoint extra edits added"
261         );
262
263         assert!(
264             !all_edits_are_disjoint(
265                 &empty_completion,
266                 &[disjoint_edit_1, disjoint_edit_2, joint_edit]
267             ),
268             "Empty completion does not prevent joint extra edits from failing the validation"
269         );
270     }
271
272     #[test]
273     fn completion_with_joint_edits_disjoint_tests() {
274         let disjoint_edit = lsp_types::TextEdit::new(
275             Range::new(Position::new(1, 1), Position::new(2, 2)),
276             "new_text".to_string(),
277         );
278         let disjoint_edit_2 = lsp_types::TextEdit::new(
279             Range::new(Position::new(2, 2), Position::new(3, 3)),
280             "new_text".to_string(),
281         );
282         let joint_edit = lsp_types::TextEdit::new(
283             Range::new(Position::new(1, 1), Position::new(5, 5)),
284             "new_text".to_string(),
285         );
286
287         let mut completion_with_joint_edits =
288             CompletionItem::new_simple("label".to_string(), "detail".to_string());
289         completion_with_joint_edits.additional_text_edits =
290             Some(vec![disjoint_edit.clone(), joint_edit.clone()]);
291         assert!(
292             !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
293             "Completion with disjoint edits fails the validation even with empty extra edits"
294         );
295
296         completion_with_joint_edits.text_edit =
297             Some(CompletionTextEdit::Edit(disjoint_edit.clone()));
298         completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit.clone()]);
299         assert!(
300             !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
301             "Completion with disjoint edits fails the validation even with empty extra edits"
302         );
303
304         completion_with_joint_edits.text_edit =
305             Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
306                 new_text: "new_text".to_string(),
307                 insert: disjoint_edit.range,
308                 replace: joint_edit.range,
309             }));
310         completion_with_joint_edits.additional_text_edits = None;
311         assert!(
312             !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
313             "Completion with disjoint edits fails the validation even with empty extra edits"
314         );
315
316         completion_with_joint_edits.text_edit =
317             Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
318                 new_text: "new_text".to_string(),
319                 insert: disjoint_edit.range,
320                 replace: disjoint_edit_2.range,
321             }));
322         completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit]);
323         assert!(
324             !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
325             "Completion with disjoint edits fails the validation even with empty extra edits"
326         );
327     }
328
329     #[test]
330     fn completion_with_disjoint_edits_disjoint_tests() {
331         let disjoint_edit = lsp_types::TextEdit::new(
332             Range::new(Position::new(1, 1), Position::new(2, 2)),
333             "new_text".to_string(),
334         );
335         let disjoint_edit_2 = lsp_types::TextEdit::new(
336             Range::new(Position::new(2, 2), Position::new(3, 3)),
337             "new_text".to_string(),
338         );
339         let joint_edit = lsp_types::TextEdit::new(
340             Range::new(Position::new(1, 1), Position::new(5, 5)),
341             "new_text".to_string(),
342         );
343
344         let mut completion_with_disjoint_edits =
345             CompletionItem::new_simple("label".to_string(), "detail".to_string());
346         completion_with_disjoint_edits.text_edit = Some(CompletionTextEdit::Edit(disjoint_edit));
347         let completion_with_disjoint_edits = completion_with_disjoint_edits;
348
349         assert!(
350             all_edits_are_disjoint(&completion_with_disjoint_edits, &[]),
351             "Completion with disjoint edits is valid"
352         );
353         assert!(
354             !all_edits_are_disjoint(&completion_with_disjoint_edits, &[joint_edit.clone()]),
355             "Completion with disjoint edits and joint extra edit is invalid"
356         );
357         assert!(
358             all_edits_are_disjoint(&completion_with_disjoint_edits, &[disjoint_edit_2.clone()]),
359             "Completion with disjoint edits and joint extra edit is valid"
360         );
361     }
362 }