]> git.proxmox.com Git - rustc.git/blob - src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_utils.rs
New upstream version 1.67.1+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / rust-analyzer / src / lsp_utils.rs
1 //! Utilities for LSP-related boilerplate code.
2 use std::{mem, 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, PositionEncoding},
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 cancel_token: Option<String>,
91 ) {
92 if !self.config.work_done_progress() {
93 return;
94 }
95 let percentage = fraction.map(|f| {
96 assert!((0.0..=1.0).contains(&f));
97 (f * 100.0) as u32
98 });
99 let cancellable = Some(cancel_token.is_some());
100 let token = lsp_types::ProgressToken::String(
101 cancel_token.unwrap_or_else(|| format!("rustAnalyzer/{}", title)),
102 );
103 let work_done_progress = match state {
104 Progress::Begin => {
105 self.send_request::<lsp_types::request::WorkDoneProgressCreate>(
106 lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
107 |_, _| (),
108 );
109
110 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
111 title: title.into(),
112 cancellable,
113 message,
114 percentage,
115 })
116 }
117 Progress::Report => {
118 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
119 cancellable,
120 message,
121 percentage,
122 })
123 }
124 Progress::End => {
125 lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
126 }
127 };
128 self.send_notification::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
129 token,
130 value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
131 });
132 }
133 }
134
135 pub(crate) fn apply_document_changes(
136 file_contents: impl FnOnce() -> String,
137 mut content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
138 ) -> String {
139 // Skip to the last full document change, as it invalidates all previous changes anyways.
140 let mut start = content_changes
141 .iter()
142 .rev()
143 .position(|change| change.range.is_none())
144 .map(|idx| content_changes.len() - idx - 1)
145 .unwrap_or(0);
146
147 let mut text: String = match content_changes.get_mut(start) {
148 // peek at the first content change as an optimization
149 Some(lsp_types::TextDocumentContentChangeEvent { range: None, text, .. }) => {
150 let text = mem::take(text);
151 start += 1;
152
153 // The only change is a full document update
154 if start == content_changes.len() {
155 return text;
156 }
157 text
158 }
159 Some(_) => file_contents(),
160 // we received no content changes
161 None => return file_contents(),
162 };
163
164 let mut line_index = LineIndex {
165 // the index will be overwritten in the bottom loop's first iteration
166 index: Arc::new(ide::LineIndex::new(&text)),
167 // We don't care about line endings or offset encoding here.
168 endings: LineEndings::Unix,
169 encoding: PositionEncoding::Utf16,
170 };
171
172 // The changes we got must be applied sequentially, but can cross lines so we
173 // have to keep our line index updated.
174 // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
175 // remember the last valid line in the index and only rebuild it if needed.
176 // The VFS will normalize the end of lines to `\n`.
177 let mut index_valid = !0u32;
178 for change in content_changes {
179 // The None case can't happen as we have handled it above already
180 if let Some(range) = change.range {
181 if index_valid <= range.end.line {
182 *Arc::make_mut(&mut line_index.index) = ide::LineIndex::new(&text);
183 }
184 index_valid = range.start.line;
185 if let Ok(range) = from_proto::text_range(&line_index, range) {
186 text.replace_range(Range::<usize>::from(range), &change.text);
187 }
188 }
189 }
190 text
191 }
192
193 /// Checks that the edits inside the completion and the additional edits do not overlap.
194 /// LSP explicitly forbids the additional edits to overlap both with the main edit and themselves.
195 pub(crate) fn all_edits_are_disjoint(
196 completion: &lsp_types::CompletionItem,
197 additional_edits: &[lsp_types::TextEdit],
198 ) -> bool {
199 let mut edit_ranges = Vec::new();
200 match completion.text_edit.as_ref() {
201 Some(lsp_types::CompletionTextEdit::Edit(edit)) => {
202 edit_ranges.push(edit.range);
203 }
204 Some(lsp_types::CompletionTextEdit::InsertAndReplace(edit)) => {
205 let replace = edit.replace;
206 let insert = edit.insert;
207 if replace.start != insert.start
208 || insert.start > insert.end
209 || insert.end > replace.end
210 {
211 // insert has to be a prefix of replace but it is not
212 return false;
213 }
214 edit_ranges.push(replace);
215 }
216 None => {}
217 }
218 if let Some(additional_changes) = completion.additional_text_edits.as_ref() {
219 edit_ranges.extend(additional_changes.iter().map(|edit| edit.range));
220 };
221 edit_ranges.extend(additional_edits.iter().map(|edit| edit.range));
222 edit_ranges.sort_by_key(|range| (range.start, range.end));
223 edit_ranges
224 .iter()
225 .zip(edit_ranges.iter().skip(1))
226 .all(|(previous, next)| previous.end <= next.start)
227 }
228
229 #[cfg(test)]
230 mod tests {
231 use lsp_types::{
232 CompletionItem, CompletionTextEdit, InsertReplaceEdit, Position, Range,
233 TextDocumentContentChangeEvent,
234 };
235
236 use super::*;
237
238 #[test]
239 fn test_apply_document_changes() {
240 macro_rules! c {
241 [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => {
242 vec![$(TextDocumentContentChangeEvent {
243 range: Some(Range {
244 start: Position { line: $sl, character: $sc },
245 end: Position { line: $el, character: $ec },
246 }),
247 range_length: None,
248 text: String::from($text),
249 }),+]
250 };
251 }
252
253 let text = apply_document_changes(|| String::new(), vec![]);
254 assert_eq!(text, "");
255 let text = apply_document_changes(
256 || text,
257 vec![TextDocumentContentChangeEvent {
258 range: None,
259 range_length: None,
260 text: String::from("the"),
261 }],
262 );
263 assert_eq!(text, "the");
264 let text = apply_document_changes(|| text, c![0, 3; 0, 3 => " quick"]);
265 assert_eq!(text, "the quick");
266 let text = apply_document_changes(|| text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
267 assert_eq!(text, "quick foxes");
268 let text = apply_document_changes(|| text, c![0, 11; 0, 11 => "\ndream"]);
269 assert_eq!(text, "quick foxes\ndream");
270 let text = apply_document_changes(|| text, c![1, 0; 1, 0 => "have "]);
271 assert_eq!(text, "quick foxes\nhave dream");
272 let text = apply_document_changes(
273 || text,
274 c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
275 );
276 assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
277 let text = apply_document_changes(|| text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
278 assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
279 let text = apply_document_changes(
280 || text,
281 c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
282 );
283 assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
284 let text = apply_document_changes(|| text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
285 assert_eq!(text, "the quick \nthey have quiet dreams\n");
286
287 let text = String::from("❤️");
288 let text = apply_document_changes(|| text, c![0, 0; 0, 0 => "a"]);
289 assert_eq!(text, "a❤️");
290
291 let text = String::from("a\nb");
292 let text = apply_document_changes(|| text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
293 assert_eq!(text, "adcb");
294
295 let text = String::from("a\nb");
296 let text = apply_document_changes(|| text, c![0, 1; 1, 0 => \nc", 0, 2; 0, 2 => "c"]);
297 assert_eq!(text, "ațc\ncb");
298 }
299
300 #[test]
301 fn empty_completion_disjoint_tests() {
302 let empty_completion =
303 CompletionItem::new_simple("label".to_string(), "detail".to_string());
304
305 let disjoint_edit_1 = lsp_types::TextEdit::new(
306 Range::new(Position::new(2, 2), Position::new(3, 3)),
307 "new_text".to_string(),
308 );
309 let disjoint_edit_2 = lsp_types::TextEdit::new(
310 Range::new(Position::new(3, 3), Position::new(4, 4)),
311 "new_text".to_string(),
312 );
313
314 let joint_edit = lsp_types::TextEdit::new(
315 Range::new(Position::new(1, 1), Position::new(5, 5)),
316 "new_text".to_string(),
317 );
318
319 assert!(
320 all_edits_are_disjoint(&empty_completion, &[]),
321 "Empty completion has all its edits disjoint"
322 );
323 assert!(
324 all_edits_are_disjoint(
325 &empty_completion,
326 &[disjoint_edit_1.clone(), disjoint_edit_2.clone()]
327 ),
328 "Empty completion is disjoint to whatever disjoint extra edits added"
329 );
330
331 assert!(
332 !all_edits_are_disjoint(
333 &empty_completion,
334 &[disjoint_edit_1, disjoint_edit_2, joint_edit]
335 ),
336 "Empty completion does not prevent joint extra edits from failing the validation"
337 );
338 }
339
340 #[test]
341 fn completion_with_joint_edits_disjoint_tests() {
342 let disjoint_edit = lsp_types::TextEdit::new(
343 Range::new(Position::new(1, 1), Position::new(2, 2)),
344 "new_text".to_string(),
345 );
346 let disjoint_edit_2 = lsp_types::TextEdit::new(
347 Range::new(Position::new(2, 2), Position::new(3, 3)),
348 "new_text".to_string(),
349 );
350 let joint_edit = lsp_types::TextEdit::new(
351 Range::new(Position::new(1, 1), Position::new(5, 5)),
352 "new_text".to_string(),
353 );
354
355 let mut completion_with_joint_edits =
356 CompletionItem::new_simple("label".to_string(), "detail".to_string());
357 completion_with_joint_edits.additional_text_edits =
358 Some(vec![disjoint_edit.clone(), joint_edit.clone()]);
359 assert!(
360 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
361 "Completion with disjoint edits fails the validation even with empty extra edits"
362 );
363
364 completion_with_joint_edits.text_edit =
365 Some(CompletionTextEdit::Edit(disjoint_edit.clone()));
366 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit.clone()]);
367 assert!(
368 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
369 "Completion with disjoint edits fails the validation even with empty extra edits"
370 );
371
372 completion_with_joint_edits.text_edit =
373 Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
374 new_text: "new_text".to_string(),
375 insert: disjoint_edit.range,
376 replace: disjoint_edit_2.range,
377 }));
378 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit]);
379 assert!(
380 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
381 "Completion with disjoint edits fails the validation even with empty extra edits"
382 );
383 }
384
385 #[test]
386 fn completion_with_disjoint_edits_disjoint_tests() {
387 let disjoint_edit = lsp_types::TextEdit::new(
388 Range::new(Position::new(1, 1), Position::new(2, 2)),
389 "new_text".to_string(),
390 );
391 let disjoint_edit_2 = lsp_types::TextEdit::new(
392 Range::new(Position::new(2, 2), Position::new(3, 3)),
393 "new_text".to_string(),
394 );
395 let joint_edit = lsp_types::TextEdit::new(
396 Range::new(Position::new(1, 1), Position::new(5, 5)),
397 "new_text".to_string(),
398 );
399
400 let mut completion_with_disjoint_edits =
401 CompletionItem::new_simple("label".to_string(), "detail".to_string());
402 completion_with_disjoint_edits.text_edit = Some(CompletionTextEdit::Edit(disjoint_edit));
403 let completion_with_disjoint_edits = completion_with_disjoint_edits;
404
405 assert!(
406 all_edits_are_disjoint(&completion_with_disjoint_edits, &[]),
407 "Completion with disjoint edits is valid"
408 );
409 assert!(
410 !all_edits_are_disjoint(&completion_with_disjoint_edits, &[joint_edit]),
411 "Completion with disjoint edits and joint extra edit is invalid"
412 );
413 assert!(
414 all_edits_are_disjoint(&completion_with_disjoint_edits, &[disjoint_edit_2]),
415 "Completion with disjoint edits and joint extra edit is valid"
416 );
417 }
418 }