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