1 //! Utilities for LSP-related boilerplate code.
2 use std::{ops::Range, sync::Arc};
4 use lsp_server::Notification;
8 global_state::GlobalState,
9 line_index::{LineEndings, LineIndex, OffsetEncoding},
13 pub(crate) fn invalid_params_error(message: String) -> LspError {
14 LspError { code: lsp_server::ErrorCode::InvalidParams as i32, message }
17 pub(crate) fn notification_is<N: lsp_types::notification::Notification>(
18 notification: &Notification,
20 notification.method == N::METHOD
23 #[derive(Debug, Eq, PartialEq)]
24 pub(crate) enum Progress {
31 pub(crate) fn fraction(done: usize, total: usize) -> f64 {
32 assert!(done <= total);
33 done as f64 / total.max(1) as f64
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 },
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.");
57 None => tracing::error!("{}", &message),
60 self.send_notification::<lsp_types::notification::ShowMessage>(
61 lsp_types::ShowMessageParams { typ: lsp_types::MessageType::ERROR, message },
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
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.
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)
84 pub(crate) fn report_progress(
88 message: Option<String>,
89 fraction: Option<f64>,
91 if !self.config.work_done_progress() {
94 let percentage = fraction.map(|f| {
95 assert!((0.0..=1.0).contains(&f));
98 let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
99 let work_done_progress = match state {
101 self.send_request::<lsp_types::request::WorkDoneProgressCreate>(
102 lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
106 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
113 Progress::Report => {
114 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
121 lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
124 self.send_notification::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
126 value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
131 pub(crate) fn apply_document_changes(
132 old_text: &mut String,
133 content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
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,
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`.
149 UpToLineExclusive(u32),
153 fn covers(&self, line: u32) -> bool {
155 IndexValid::UpToLineExclusive(to) => to > line,
161 let mut index_valid = IndexValid::All;
162 for change in content_changes {
165 if !index_valid.covers(range.end.line) {
166 line_index.index = Arc::new(ide::LineIndex::new(old_text));
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);
174 *old_text = change.text;
175 index_valid = IndexValid::UpToLineExclusive(0);
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],
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);
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
199 // insert has to be a prefix of replace but it is not
202 edit_ranges.push(replace);
206 if let Some(additional_changes) = completion.additional_text_edits.as_ref() {
207 edit_ranges.extend(additional_changes.iter().map(|edit| edit.range));
209 edit_ranges.extend(additional_edits.iter().map(|edit| edit.range));
210 edit_ranges.sort_by_key(|range| (range.start, range.end));
213 .zip(edit_ranges.iter().skip(1))
214 .all(|(previous, next)| previous.end <= next.start)
220 CompletionItem, CompletionTextEdit, InsertReplaceEdit, Position, Range,
221 TextDocumentContentChangeEvent,
227 fn test_apply_document_changes() {
229 [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => {
230 vec![$(TextDocumentContentChangeEvent {
232 start: Position { line: $sl, character: $sc },
233 end: Position { line: $el, character: $ec },
236 text: String::from($text),
241 let mut text = String::new();
242 apply_document_changes(&mut text, vec![]);
243 assert_eq!(text, "");
244 apply_document_changes(
246 vec![TextDocumentContentChangeEvent {
249 text: String::from("the"),
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(
263 c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
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(
270 c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
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");
276 text = String::from("❤️");
277 apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]);
278 assert_eq!(text, "a❤️");
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");
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");
290 fn empty_completion_disjoint_tests() {
291 let empty_completion =
292 CompletionItem::new_simple("label".to_string(), "detail".to_string());
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(),
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(),
303 let joint_edit = lsp_types::TextEdit::new(
304 Range::new(Position::new(1, 1), Position::new(5, 5)),
305 "new_text".to_string(),
309 all_edits_are_disjoint(&empty_completion, &[]),
310 "Empty completion has all its edits disjoint"
313 all_edits_are_disjoint(
315 &[disjoint_edit_1.clone(), disjoint_edit_2.clone()]
317 "Empty completion is disjoint to whatever disjoint extra edits added"
321 !all_edits_are_disjoint(
323 &[disjoint_edit_1, disjoint_edit_2, joint_edit]
325 "Empty completion does not prevent joint extra edits from failing the validation"
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(),
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(),
339 let joint_edit = lsp_types::TextEdit::new(
340 Range::new(Position::new(1, 1), Position::new(5, 5)),
341 "new_text".to_string(),
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()]);
349 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
350 "Completion with disjoint edits fails the validation even with empty extra edits"
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()]);
357 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
358 "Completion with disjoint edits fails the validation even with empty extra edits"
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,
367 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit]);
369 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
370 "Completion with disjoint edits fails the validation even with empty extra edits"
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(),
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(),
384 let joint_edit = lsp_types::TextEdit::new(
385 Range::new(Position::new(1, 1), Position::new(5, 5)),
386 "new_text".to_string(),
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;
395 all_edits_are_disjoint(&completion_with_disjoint_edits, &[]),
396 "Completion with disjoint edits is valid"
399 !all_edits_are_disjoint(&completion_with_disjoint_edits, &[joint_edit]),
400 "Completion with disjoint edits and joint extra edit is invalid"
403 all_edits_are_disjoint(&completion_with_disjoint_edits, &[disjoint_edit_2]),
404 "Completion with disjoint edits and joint extra edit is valid"