1use std::{mem, ops::Range};
3
4use lsp_server::Notification;
5use lsp_types::request::Request;
6use triomphe::Arc;
7
8use crate::{
9 global_state::GlobalState,
10 line_index::{LineEndings, LineIndex, PositionEncoding},
11 lsp::{LspError, from_proto},
12 lsp_ext,
13};
14
15pub(crate) fn invalid_params_error(message: String) -> LspError {
16 LspError { code: lsp_server::ErrorCode::InvalidParams as i32, message }
17}
18
19pub(crate) fn notification_is<N: lsp_types::notification::Notification>(
20 notification: &Notification,
21) -> bool {
22 notification.method == N::METHOD
23}
24
25#[derive(Debug, Eq, PartialEq)]
26pub(crate) enum Progress {
27 Begin,
28 Report,
29 End,
30}
31
32impl Progress {
33 pub(crate) fn fraction(done: usize, total: usize) -> f64 {
34 assert!(done <= total);
35 done as f64 / total.max(1) as f64
36 }
37}
38
39impl GlobalState {
40 pub(crate) fn show_message(
41 &mut self,
42 typ: lsp_types::MessageType,
43 message: String,
44 show_open_log_button: bool,
45 ) {
46 match self.config.open_server_logs() && show_open_log_button {
47 true => self.send_request::<lsp_types::request::ShowMessageRequest>(
48 lsp_types::ShowMessageRequestParams {
49 typ,
50 message,
51 actions: Some(vec![lsp_types::MessageActionItem {
52 title: "Open server logs".to_owned(),
53 properties: Default::default(),
54 }]),
55 },
56 |this, resp| {
57 let lsp_server::Response { error: None, result: Some(result), .. } = resp
58 else { return };
59 if let Ok(Some(_item)) = crate::from_json::<
60 <lsp_types::request::ShowMessageRequest as lsp_types::request::Request>::Result,
61 >(
62 lsp_types::request::ShowMessageRequest::METHOD, &result
63 ) {
64 this.send_notification::<lsp_ext::OpenServerLogs>(());
65 }
66 },
67 ),
68 false => self.send_notification::<lsp_types::notification::ShowMessage>(
69 lsp_types::ShowMessageParams {
70 typ,
71 message,
72 },
73 ),
74 }
75 }
76
77 pub(crate) fn show_and_log_error(&mut self, message: String, additional_info: Option<String>) {
80 match additional_info {
81 Some(additional_info) => {
82 tracing::error!("{message}:\n{additional_info}");
83 self.show_message(
84 lsp_types::MessageType::ERROR,
85 message,
86 tracing::enabled!(tracing::Level::ERROR),
87 );
88 }
89 None => {
90 tracing::error!("{message}");
91 self.send_notification::<lsp_types::notification::ShowMessage>(
92 lsp_types::ShowMessageParams { typ: lsp_types::MessageType::ERROR, message },
93 );
94 }
95 }
96 }
97
98 pub(crate) fn poke_rust_analyzer_developer(&mut self, message: String) {
110 let from_source_build = option_env!("POKE_RA_DEVS").is_some();
111 if from_source_build {
112 self.show_and_log_error(message, None);
113 }
114 }
115
116 pub(crate) fn report_progress(
117 &mut self,
118 title: &str,
119 state: Progress,
120 message: Option<String>,
121 fraction: Option<f64>,
122 cancel_token: Option<String>,
123 ) {
124 if !self.config.work_done_progress() {
125 return;
126 }
127 let percentage = fraction.map(|f| {
128 assert!((0.0..=1.0).contains(&f));
129 (f * 100.0) as u32
130 });
131 let cancellable = Some(cancel_token.is_some());
132 let token = lsp_types::ProgressToken::String(
133 cancel_token.unwrap_or_else(|| format!("rustAnalyzer/{title}")),
134 );
135 tracing::debug!(?token, ?state, "report_progress {message:?}");
136 let work_done_progress = match state {
137 Progress::Begin => {
138 self.send_request::<lsp_types::request::WorkDoneProgressCreate>(
139 lsp_types::WorkDoneProgressCreateParams { token: token.clone() },
140 |_, _| (),
141 );
142
143 lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin {
144 title: title.into(),
145 cancellable,
146 message,
147 percentage,
148 })
149 }
150 Progress::Report => {
151 lsp_types::WorkDoneProgress::Report(lsp_types::WorkDoneProgressReport {
152 cancellable,
153 message,
154 percentage,
155 })
156 }
157 Progress::End => {
158 lsp_types::WorkDoneProgress::End(lsp_types::WorkDoneProgressEnd { message })
159 }
160 };
161 self.send_notification::<lsp_types::notification::Progress>(lsp_types::ProgressParams {
162 token,
163 value: lsp_types::ProgressParamsValue::WorkDone(work_done_progress),
164 });
165 }
166}
167
168pub(crate) fn apply_document_changes(
169 encoding: PositionEncoding,
170 file_contents: &str,
171 mut content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
172) -> String {
173 let (mut text, content_changes) =
176 match content_changes.iter().rposition(|change| change.range.is_none()) {
177 Some(idx) => {
178 let text = mem::take(&mut content_changes[idx].text);
179 (text, &content_changes[idx + 1..])
180 }
181 None => (file_contents.to_owned(), &content_changes[..]),
182 };
183 if content_changes.is_empty() {
184 return text;
185 }
186
187 let mut line_index = LineIndex {
188 index: Arc::new(ide::LineIndex::new(&text)),
190 endings: LineEndings::Unix,
192 encoding,
193 };
194
195 let mut index_valid = !0u32;
201 for change in content_changes {
202 if let Some(range) = change.range {
204 if index_valid <= range.end.line {
205 *Arc::make_mut(&mut line_index.index) = ide::LineIndex::new(&text);
206 }
207 index_valid = range.start.line;
208 if let Ok(range) = from_proto::text_range(&line_index, range) {
209 text.replace_range(Range::<usize>::from(range), &change.text);
210 }
211 }
212 }
213 text
214}
215
216pub(crate) fn all_edits_are_disjoint(
219 completion: &lsp_types::CompletionItem,
220 additional_edits: &[lsp_types::TextEdit],
221) -> bool {
222 let mut edit_ranges = Vec::new();
223 match completion.text_edit.as_ref() {
224 Some(lsp_types::CompletionTextEdit::Edit(edit)) => {
225 edit_ranges.push(edit.range);
226 }
227 Some(lsp_types::CompletionTextEdit::InsertAndReplace(edit)) => {
228 let replace = edit.replace;
229 let insert = edit.insert;
230 if replace.start != insert.start
231 || insert.start > insert.end
232 || insert.end > replace.end
233 {
234 return false;
236 }
237 edit_ranges.push(replace);
238 }
239 None => {}
240 }
241 if let Some(additional_changes) = completion.additional_text_edits.as_ref() {
242 edit_ranges.extend(additional_changes.iter().map(|edit| edit.range));
243 };
244 edit_ranges.extend(additional_edits.iter().map(|edit| edit.range));
245 edit_ranges.sort_by_key(|range| (range.start, range.end));
246 edit_ranges
247 .iter()
248 .zip(edit_ranges.iter().skip(1))
249 .all(|(previous, next)| previous.end <= next.start)
250}
251
252#[cfg(test)]
253mod tests {
254 use ide_db::line_index::WideEncoding;
255 use lsp_types::{
256 CompletionItem, CompletionTextEdit, InsertReplaceEdit, Position, Range,
257 TextDocumentContentChangeEvent,
258 };
259
260 use super::*;
261
262 #[test]
263 fn test_apply_document_changes() {
264 macro_rules! c {
265 [$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => {
266 vec![$(TextDocumentContentChangeEvent {
267 range: Some(Range {
268 start: Position { line: $sl, character: $sc },
269 end: Position { line: $el, character: $ec },
270 }),
271 range_length: None,
272 text: String::from($text),
273 }),+]
274 };
275 }
276
277 let encoding = PositionEncoding::Wide(WideEncoding::Utf16);
278 let text = apply_document_changes(encoding, "", vec![]);
279 assert_eq!(text, "");
280 let text = apply_document_changes(
281 encoding,
282 &text,
283 vec![TextDocumentContentChangeEvent {
284 range: None,
285 range_length: None,
286 text: String::from("the"),
287 }],
288 );
289 assert_eq!(text, "the");
290 let text = apply_document_changes(encoding, &text, c![0, 3; 0, 3 => " quick"]);
291 assert_eq!(text, "the quick");
292 let text =
293 apply_document_changes(encoding, &text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
294 assert_eq!(text, "quick foxes");
295 let text = apply_document_changes(encoding, &text, c![0, 11; 0, 11 => "\ndream"]);
296 assert_eq!(text, "quick foxes\ndream");
297 let text = apply_document_changes(encoding, &text, c![1, 0; 1, 0 => "have "]);
298 assert_eq!(text, "quick foxes\nhave dream");
299 let text = apply_document_changes(
300 encoding,
301 &text,
302 c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
303 );
304 assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
305 let text =
306 apply_document_changes(encoding, &text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
307 assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
308 let text = apply_document_changes(
309 encoding,
310 &text,
311 c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
312 );
313 assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
314 let text =
315 apply_document_changes(encoding, &text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
316 assert_eq!(text, "the quick \nthey have quiet dreams\n");
317
318 let text = String::from("❤️");
319 let text = apply_document_changes(encoding, &text, c![0, 0; 0, 0 => "a"]);
320 assert_eq!(text, "a❤️");
321
322 let text = String::from("a\nb");
323 let text =
324 apply_document_changes(encoding, &text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]);
325 assert_eq!(text, "adcb");
326
327 let text = String::from("a\nb");
328 let text =
329 apply_document_changes(encoding, &text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]);
330 assert_eq!(text, "ațc\ncb");
331 }
332
333 #[test]
334 fn empty_completion_disjoint_tests() {
335 let empty_completion = CompletionItem::new_simple("label".to_owned(), "detail".to_owned());
336
337 let disjoint_edit_1 = lsp_types::TextEdit::new(
338 Range::new(Position::new(2, 2), Position::new(3, 3)),
339 "new_text".to_owned(),
340 );
341 let disjoint_edit_2 = lsp_types::TextEdit::new(
342 Range::new(Position::new(3, 3), Position::new(4, 4)),
343 "new_text".to_owned(),
344 );
345
346 let joint_edit = lsp_types::TextEdit::new(
347 Range::new(Position::new(1, 1), Position::new(5, 5)),
348 "new_text".to_owned(),
349 );
350
351 assert!(
352 all_edits_are_disjoint(&empty_completion, &[]),
353 "Empty completion has all its edits disjoint"
354 );
355 assert!(
356 all_edits_are_disjoint(
357 &empty_completion,
358 &[disjoint_edit_1.clone(), disjoint_edit_2.clone()]
359 ),
360 "Empty completion is disjoint to whatever disjoint extra edits added"
361 );
362
363 assert!(
364 !all_edits_are_disjoint(
365 &empty_completion,
366 &[disjoint_edit_1, disjoint_edit_2, joint_edit]
367 ),
368 "Empty completion does not prevent joint extra edits from failing the validation"
369 );
370 }
371
372 #[test]
373 fn completion_with_joint_edits_disjoint_tests() {
374 let disjoint_edit = lsp_types::TextEdit::new(
375 Range::new(Position::new(1, 1), Position::new(2, 2)),
376 "new_text".to_owned(),
377 );
378 let disjoint_edit_2 = lsp_types::TextEdit::new(
379 Range::new(Position::new(2, 2), Position::new(3, 3)),
380 "new_text".to_owned(),
381 );
382 let joint_edit = lsp_types::TextEdit::new(
383 Range::new(Position::new(1, 1), Position::new(5, 5)),
384 "new_text".to_owned(),
385 );
386
387 let mut completion_with_joint_edits =
388 CompletionItem::new_simple("label".to_owned(), "detail".to_owned());
389 completion_with_joint_edits.additional_text_edits =
390 Some(vec![disjoint_edit.clone(), joint_edit.clone()]);
391 assert!(
392 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
393 "Completion with disjoint edits fails the validation even with empty extra edits"
394 );
395
396 completion_with_joint_edits.text_edit =
397 Some(CompletionTextEdit::Edit(disjoint_edit.clone()));
398 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit.clone()]);
399 assert!(
400 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
401 "Completion with disjoint edits fails the validation even with empty extra edits"
402 );
403
404 completion_with_joint_edits.text_edit =
405 Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
406 new_text: "new_text".to_owned(),
407 insert: disjoint_edit.range,
408 replace: disjoint_edit_2.range,
409 }));
410 completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit]);
411 assert!(
412 !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
413 "Completion with disjoint edits fails the validation even with empty extra edits"
414 );
415 }
416
417 #[test]
418 fn completion_with_disjoint_edits_disjoint_tests() {
419 let disjoint_edit = lsp_types::TextEdit::new(
420 Range::new(Position::new(1, 1), Position::new(2, 2)),
421 "new_text".to_owned(),
422 );
423 let disjoint_edit_2 = lsp_types::TextEdit::new(
424 Range::new(Position::new(2, 2), Position::new(3, 3)),
425 "new_text".to_owned(),
426 );
427 let joint_edit = lsp_types::TextEdit::new(
428 Range::new(Position::new(1, 1), Position::new(5, 5)),
429 "new_text".to_owned(),
430 );
431
432 let mut completion_with_disjoint_edits =
433 CompletionItem::new_simple("label".to_owned(), "detail".to_owned());
434 completion_with_disjoint_edits.text_edit = Some(CompletionTextEdit::Edit(disjoint_edit));
435 let completion_with_disjoint_edits = completion_with_disjoint_edits;
436
437 assert!(
438 all_edits_are_disjoint(&completion_with_disjoint_edits, &[]),
439 "Completion with disjoint edits is valid"
440 );
441 assert!(
442 !all_edits_are_disjoint(&completion_with_disjoint_edits, &[joint_edit]),
443 "Completion with disjoint edits and joint extra edit is invalid"
444 );
445 assert!(
446 all_edits_are_disjoint(&completion_with_disjoint_edits, &[disjoint_edit_2]),
447 "Completion with disjoint edits and joint extra edit is valid"
448 );
449 }
450}