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