1 //! Utilities for LSP-related boilerplate code.
2 use std::{error::Error, ops::Range, sync::Arc};
4 use ide_db::base_db::Canceled;
5 use lsp_server::Notification;
9 global_state::GlobalState,
10 line_index::{LineEndings, LineIndex, OffsetEncoding},
13 pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool {
14 e.downcast_ref::<Canceled>().is_some()
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.into();
40 self.send_notification::<lsp_types::notification::ShowMessage>(
41 lsp_types::ShowMessageParams { typ, message },
45 pub(crate) fn report_progress(
49 message: Option<String>,
50 fraction: Option<f64>,
52 if !self.config.work_done_progress() {
55 let percentage = fraction.map(|f| {
56 assert!(0.0 <= f && f <= 1.0);
59 let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
60 let work_done_progress = match state {
62 self.send_request::<lsp_types::request::WorkDoneProgressCreate>(
63 lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
67 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
75 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
82 lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
85 self.send_notification::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
87 value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
92 pub(crate) fn apply_document_changes(
93 old_text: &mut String,
94 content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
96 let mut line_index = LineIndex {
97 index: Arc::new(ide::LineIndex::new(old_text)),
98 // We don't care about line endings or offset encoding here.
99 endings: LineEndings::Unix,
100 encoding: OffsetEncoding::Utf16,
103 // The changes we got must be applied sequentially, but can cross lines so we
104 // have to keep our line index updated.
105 // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
106 // remember the last valid line in the index and only rebuild it if needed.
107 // The VFS will normalize the end of lines to `\n`.
110 UpToLineExclusive(u32),
114 fn covers(&self, line: u32) -> bool {
116 IndexValid::UpToLineExclusive(to) => to > line,
122 let mut index_valid = IndexValid::All;
123 for change in content_changes {
126 if !index_valid.covers(range.end.line) {
127 line_index.index = Arc::new(ide::LineIndex::new(&old_text));
129 index_valid = IndexValid::UpToLineExclusive(range.start.line);
130 let range = from_proto::text_range(&line_index, range);
131 old_text.replace_range(Range::<usize>::from(range), &change.text);
134 *old_text = change.text;
135 index_valid = IndexValid::UpToLineExclusive(0);
141 /// Checks that the edits inside the completion and the additional edits do not overlap.
142 /// LSP explicitly forbids the additional edits to overlap both with the main edit and themselves.
143 pub(crate) fn all_edits_are_disjoint(
144 completion: &lsp_types::CompletionItem,
145 additional_edits: &[lsp_types::TextEdit],
147 let mut edit_ranges = Vec::new();
148 match completion.text_edit.as_ref() {
149 Some(lsp_types::CompletionTextEdit::Edit(edit)) => {
150 edit_ranges.push(edit.range);
152 Some(lsp_types::CompletionTextEdit::InsertAndReplace(edit)) => {
153 edit_ranges.push(edit.insert);
154 edit_ranges.push(edit.replace);
158 if let Some(additional_changes) = completion.additional_text_edits.as_ref() {
159 edit_ranges.extend(additional_changes.iter().map(|edit| edit.range));
161 edit_ranges.extend(additional_edits.iter().map(|edit| edit.range));
162 edit_ranges.sort_by_key(|range| (range.start, range.end));
165 .zip(edit_ranges.iter().skip(1))
166 .all(|(previous, next)| previous.end <= next.start)
172 CompletionItem, CompletionTextEdit, InsertReplaceEdit, Position, Range,
173 TextDocumentContentChangeEvent,
179 fn test_apply_document_changes() {
181 [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => {
182 vec![$(TextDocumentContentChangeEvent {
184 start: Position { line: $sl, character: $sc },
185 end: Position { line: $el, character: $ec },
188 text: String::from($text),
193 let mut text = String::new();
194 apply_document_changes(&mut text, vec![]);
195 assert_eq!(text, "");
196 apply_document_changes(
198 vec![TextDocumentContentChangeEvent {
201 text: String::from("the"),
204 assert_eq!(text, "the");
205 apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]);
206 assert_eq!(text, "the quick");
207 apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
208 assert_eq!(text, "quick foxes");
209 apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]);
210 assert_eq!(text, "quick foxes\ndream");
211 apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]);
212 assert_eq!(text, "quick foxes\nhave dream");
213 apply_document_changes(
215 c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
217 assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
218 apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
219 assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
220 apply_document_changes(
222 c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
224 assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
225 apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
226 assert_eq!(text, "the quick \nthey have quiet dreams\n");
228 text = String::from("❤️");
229 apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]);
230 assert_eq!(text, "a❤️");
232 text = String::from("a\nb");
233 apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
234 assert_eq!(text, "adcb");
236 text = String::from("a\nb");
237 apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
238 assert_eq!(text, "ațc\ncb");
242 fn empty_completion_disjoint_tests() {
243 let empty_completion =
244 CompletionItem::new_simple("label".to_string(), "detail".to_string());
246 let disjoint_edit_1 = lsp_types::TextEdit::new(
247 Range::new(Position::new(2, 2), Position::new(3, 3)),
248 "new_text".to_string(),
250 let disjoint_edit_2 = lsp_types::TextEdit::new(
251 Range::new(Position::new(3, 3), Position::new(4, 4)),
252 "new_text".to_string(),
255 let joint_edit = lsp_types::TextEdit::new(
256 Range::new(Position::new(1, 1), Position::new(5, 5)),
257 "new_text".to_string(),
261 all_edits_are_disjoint(&empty_completion, &[]),
262 "Empty completion has all its edits disjoint"
265 all_edits_are_disjoint(
267 &[disjoint_edit_1.clone(), disjoint_edit_2.clone()]
269 "Empty completion is disjoint to whatever disjoint extra edits added"
273 !all_edits_are_disjoint(
275 &[disjoint_edit_1, disjoint_edit_2, joint_edit]
277 "Empty completion does not prevent joint extra edits from failing the validation"
282 fn completion_with_joint_edits_disjoint_tests() {
283 let disjoint_edit = lsp_types::TextEdit::new(
284 Range::new(Position::new(1, 1), Position::new(2, 2)),
285 "new_text".to_string(),
287 let disjoint_edit_2 = lsp_types::TextEdit::new(
288 Range::new(Position::new(2, 2), Position::new(3, 3)),
289 "new_text".to_string(),
291 let joint_edit = lsp_types::TextEdit::new(
292 Range::new(Position::new(1, 1), Position::new(5, 5)),
293 "new_text".to_string(),
296 let mut completion_with_joint_edits =
297 CompletionItem::new_simple("label".to_string(), "detail".to_string());
298 completion_with_joint_edits.additional_text_edits =
299 Some(vec![disjoint_edit.clone(), joint_edit.clone()]);
301 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
302 "Completion with disjoint edits fails the validation even with empty extra edits"
305 completion_with_joint_edits.text_edit =
306 Some(CompletionTextEdit::Edit(disjoint_edit.clone()));
307 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit.clone()]);
309 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
310 "Completion with disjoint edits fails the validation even with empty extra edits"
313 completion_with_joint_edits.text_edit =
314 Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
315 new_text: "new_text".to_string(),
316 insert: disjoint_edit.range,
317 replace: joint_edit.range,
319 completion_with_joint_edits.additional_text_edits = None;
321 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
322 "Completion with disjoint edits fails the validation even with empty extra edits"
325 completion_with_joint_edits.text_edit =
326 Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
327 new_text: "new_text".to_string(),
328 insert: disjoint_edit.range,
329 replace: disjoint_edit_2.range,
331 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit]);
333 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
334 "Completion with disjoint edits fails the validation even with empty extra edits"
339 fn completion_with_disjoint_edits_disjoint_tests() {
340 let disjoint_edit = lsp_types::TextEdit::new(
341 Range::new(Position::new(1, 1), Position::new(2, 2)),
342 "new_text".to_string(),
344 let disjoint_edit_2 = lsp_types::TextEdit::new(
345 Range::new(Position::new(2, 2), Position::new(3, 3)),
346 "new_text".to_string(),
348 let joint_edit = lsp_types::TextEdit::new(
349 Range::new(Position::new(1, 1), Position::new(5, 5)),
350 "new_text".to_string(),
353 let mut completion_with_disjoint_edits =
354 CompletionItem::new_simple("label".to_string(), "detail".to_string());
355 completion_with_disjoint_edits.text_edit = Some(CompletionTextEdit::Edit(disjoint_edit));
356 let completion_with_disjoint_edits = completion_with_disjoint_edits;
359 all_edits_are_disjoint(&completion_with_disjoint_edits, &[]),
360 "Completion with disjoint edits is valid"
363 !all_edits_are_disjoint(&completion_with_disjoint_edits, &[joint_edit]),
364 "Completion with disjoint edits and joint extra edit is invalid"
367 all_edits_are_disjoint(&completion_with_disjoint_edits, &[disjoint_edit_2]),
368 "Completion with disjoint edits and joint extra edit is valid"