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