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