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