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