1 //! Utilities for LSP-related boilerplate code.
2 use std
::{mem, ops::Range, sync::Arc}
;
4 use lsp_server
::Notification
;
8 global_state
::GlobalState
,
9 line_index
::{LineEndings, LineIndex, PositionEncoding}
,
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>,
90 cancel_token
: Option
<String
>,
92 if !self.config
.work_done_progress() {
95 let percentage
= fraction
.map(|f
| {
96 assert
!((0.0..=1.0).contains(&f
));
99 let cancellable
= Some(cancel_token
.is_some());
100 let token
= lsp_types
::ProgressToken
::String(
101 cancel_token
.unwrap_or_else(|| format
!("rustAnalyzer/{}", title
)),
103 let work_done_progress
= match state
{
105 self.send_request
::<lsp_types
::request
::WorkDoneProgressCreate
>(
106 lsp_types
::WorkDoneProgressCreateParams { token: token.clone() }
,
110 lsp_types
::WorkDoneProgress
::Begin(lsp_types
::WorkDoneProgressBegin
{
117 Progress
::Report
=> {
118 lsp_types
::WorkDoneProgress
::Report(lsp_types
::WorkDoneProgressReport
{
125 lsp_types
::WorkDoneProgress
::End(lsp_types
::WorkDoneProgressEnd { message }
)
128 self.send_notification
::<lsp_types
::notification
::Progress
>(lsp_types
::ProgressParams
{
130 value
: lsp_types
::ProgressParamsValue
::WorkDone(work_done_progress
),
135 pub(crate) fn apply_document_changes(
136 file_contents
: impl FnOnce() -> String
,
137 mut content_changes
: Vec
<lsp_types
::TextDocumentContentChangeEvent
>,
139 // Skip to the last full document change, as it invalidates all previous changes anyways.
140 let mut start
= content_changes
143 .position(|change
| change
.range
.is_none())
144 .map(|idx
| content_changes
.len() - idx
- 1)
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
);
153 // The only change is a full document update
154 if start
== content_changes
.len() {
159 Some(_
) => file_contents(),
160 // we received no content changes
161 None
=> return file_contents(),
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
,
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
);
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
);
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
],
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
);
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
211 // insert has to be a prefix of replace but it is not
214 edit_ranges
.push(replace
);
218 if let Some(additional_changes
) = completion
.additional_text_edits
.as_ref() {
219 edit_ranges
.extend(additional_changes
.iter().map(|edit
| edit
.range
));
221 edit_ranges
.extend(additional_edits
.iter().map(|edit
| edit
.range
));
222 edit_ranges
.sort_by_key(|range
| (range
.start
, range
.end
));
225 .zip(edit_ranges
.iter().skip(1))
226 .all(|(previous
, next
)| previous
.end
<= next
.start
)
232 CompletionItem
, CompletionTextEdit
, InsertReplaceEdit
, Position
, Range
,
233 TextDocumentContentChangeEvent
,
239 fn test_apply_document_changes() {
241 [$
($sl
:expr
, $sc
:expr
; $el
:expr
, $ec
:expr
=> $text
:expr
),+] => {
242 vec
![$
(TextDocumentContentChangeEvent
{
244 start
: Position { line: $sl, character: $sc }
,
245 end
: Position { line: $el, character: $ec }
,
248 text
: String
::from($text
),
253 let text
= apply_document_changes(|| String
::new(), vec
![]);
254 assert_eq
!(text
, "");
255 let text
= apply_document_changes(
257 vec
![TextDocumentContentChangeEvent
{
260 text
: String
::from("the"),
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(
274 c
![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"],
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(
281 c
![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
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");
287 let text
= String
::from("❤️");
288 let text
= apply_document_changes(|| text
, c
![0, 0; 0, 0 => "a"]);
289 assert_eq
!(text
, "a❤️");
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");
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");
301 fn empty_completion_disjoint_tests() {
302 let empty_completion
=
303 CompletionItem
::new_simple("label".to_string(), "detail".to_string());
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(),
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(),
314 let joint_edit
= lsp_types
::TextEdit
::new(
315 Range
::new(Position
::new(1, 1), Position
::new(5, 5)),
316 "new_text".to_string(),
320 all_edits_are_disjoint(&empty_completion
, &[]),
321 "Empty completion has all its edits disjoint"
324 all_edits_are_disjoint(
326 &[disjoint_edit_1
.clone(), disjoint_edit_2
.clone()]
328 "Empty completion is disjoint to whatever disjoint extra edits added"
332 !all_edits_are_disjoint(
334 &[disjoint_edit_1
, disjoint_edit_2
, joint_edit
]
336 "Empty completion does not prevent joint extra edits from failing the validation"
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(),
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(),
350 let joint_edit
= lsp_types
::TextEdit
::new(
351 Range
::new(Position
::new(1, 1), Position
::new(5, 5)),
352 "new_text".to_string(),
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()]);
360 !all_edits_are_disjoint(&completion_with_joint_edits
, &[]),
361 "Completion with disjoint edits fails the validation even with empty extra edits"
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()]);
368 !all_edits_are_disjoint(&completion_with_joint_edits
, &[]),
369 "Completion with disjoint edits fails the validation even with empty extra edits"
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
,
378 completion_with_joint_edits
.additional_text_edits
= Some(vec
![joint_edit
]);
380 !all_edits_are_disjoint(&completion_with_joint_edits
, &[]),
381 "Completion with disjoint edits fails the validation even with empty extra edits"
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(),
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(),
395 let joint_edit
= lsp_types
::TextEdit
::new(
396 Range
::new(Position
::new(1, 1), Position
::new(5, 5)),
397 "new_text".to_string(),
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
;
406 all_edits_are_disjoint(&completion_with_disjoint_edits
, &[]),
407 "Completion with disjoint edits is valid"
410 !all_edits_are_disjoint(&completion_with_disjoint_edits
, &[joint_edit
]),
411 "Completion with disjoint edits and joint extra edit is invalid"
414 all_edits_are_disjoint(&completion_with_disjoint_edits
, &[disjoint_edit_2
]),
415 "Completion with disjoint edits and joint extra edit is valid"