]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/src/lsp_utils.rs
Merge #5732
[rust.git] / crates / rust-analyzer / src / lsp_utils.rs
1 //! Utilities for LSP-related boilerplate code.
2 use std::{error::Error, ops::Range};
3
4 use lsp_server::Notification;
5 use ra_db::Canceled;
6 use ra_ide::LineIndex;
7
8 use crate::{from_proto, global_state::GlobalState};
9
10 pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool {
11     e.downcast_ref::<Canceled>().is_some()
12 }
13
14 pub(crate) fn notification_is<N: lsp_types::notification::Notification>(
15     notification: &Notification,
16 ) -> bool {
17     notification.method == N::METHOD
18 }
19
20 #[derive(Debug, Eq, PartialEq)]
21 pub(crate) enum Progress {
22     Begin,
23     Report,
24     End,
25 }
26
27 impl Progress {
28     pub(crate) fn percentage(done: usize, total: usize) -> f64 {
29         (done as f64 / total.max(1) as f64) * 100.0
30     }
31 }
32
33 impl GlobalState {
34     pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) {
35         let message = message.into();
36         self.send_notification::<lsp_types::notification::ShowMessage>(
37             lsp_types::ShowMessageParams { typ, message },
38         )
39     }
40
41     pub(crate) fn report_progress(
42         &mut self,
43         title: &str,
44         state: Progress,
45         message: Option<String>,
46         percentage: Option<f64>,
47     ) {
48         if !self.config.client_caps.work_done_progress {
49             return;
50         }
51         let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
52         let work_done_progress = match state {
53             Progress::Begin => {
54                 self.send_request::<lsp_types::request::WorkDoneProgressCreate>(
55                     lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
56                     |_, _| (),
57                 );
58
59                 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
60                     title: title.into(),
61                     cancellable: None,
62                     message,
63                     percentage,
64                 })
65             }
66             Progress::Report => {
67                 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
68                     cancellable: None,
69                     message,
70                     percentage,
71                 })
72             }
73             Progress::End => {
74                 lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
75             }
76         };
77         self.send_notification::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
78             token,
79             value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
80         });
81     }
82 }
83
84 pub(crate) fn apply_document_changes(
85     old_text: &mut String,
86     content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
87 ) {
88     let mut line_index = LineIndex::new(old_text);
89     // The changes we got must be applied sequentially, but can cross lines so we
90     // have to keep our line index updated.
91     // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
92     // remember the last valid line in the index and only rebuild it if needed.
93     // The VFS will normalize the end of lines to `\n`.
94     enum IndexValid {
95         All,
96         UpToLineExclusive(u64),
97     }
98
99     impl IndexValid {
100         fn covers(&self, line: u64) -> bool {
101             match *self {
102                 IndexValid::UpToLineExclusive(to) => to > line,
103                 _ => true,
104             }
105         }
106     }
107
108     let mut index_valid = IndexValid::All;
109     for change in content_changes {
110         match change.range {
111             Some(range) => {
112                 if !index_valid.covers(range.end.line) {
113                     line_index = LineIndex::new(&old_text);
114                 }
115                 index_valid = IndexValid::UpToLineExclusive(range.start.line);
116                 let range = from_proto::text_range(&line_index, range);
117                 old_text.replace_range(Range::<usize>::from(range), &change.text);
118             }
119             None => {
120                 *old_text = change.text;
121                 index_valid = IndexValid::UpToLineExclusive(0);
122             }
123         }
124     }
125 }
126
127 #[cfg(test)]
128 mod tests {
129     use lsp_types::{Position, Range, TextDocumentContentChangeEvent};
130
131     use super::*;
132
133     #[test]
134     fn test_apply_document_changes() {
135         macro_rules! c {
136             [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => {
137                 vec![$(TextDocumentContentChangeEvent {
138                     range: Some(Range {
139                         start: Position { line: $sl, character: $sc },
140                         end: Position { line: $el, character: $ec },
141                     }),
142                     range_length: None,
143                     text: String::from($text),
144                 }),+]
145             };
146         }
147
148         let mut text = String::new();
149         apply_document_changes(&mut text, vec![]);
150         assert_eq!(text, "");
151         apply_document_changes(
152             &mut text,
153             vec![TextDocumentContentChangeEvent {
154                 range: None,
155                 range_length: None,
156                 text: String::from("the"),
157             }],
158         );
159         assert_eq!(text, "the");
160         apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]);
161         assert_eq!(text, "the quick");
162         apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
163         assert_eq!(text, "quick foxes");
164         apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]);
165         assert_eq!(text, "quick foxes\ndream");
166         apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]);
167         assert_eq!(text, "quick foxes\nhave dream");
168         apply_document_changes(
169             &mut text,
170             c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
171         );
172         assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
173         apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
174         assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
175         apply_document_changes(
176             &mut text,
177             c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
178         );
179         assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
180         apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
181         assert_eq!(text, "the quick \nthey have quiet dreams\n");
182
183         text = String::from("❤️");
184         apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]);
185         assert_eq!(text, "a❤️");
186
187         text = String::from("a\nb");
188         apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
189         assert_eq!(text, "adcb");
190
191         text = String::from("a\nb");
192         apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
193         assert_eq!(text, "ațc\ncb");
194     }
195 }