//! Utilities for LSP-related boilerplate code.
-use std::{error::Error, ops::Range, sync::Arc};
+use std::{ops::Range, sync::Arc};
-use ide_db::base_db::Canceled;
use lsp_server::Notification;
use crate::{
from_proto,
global_state::GlobalState,
line_index::{LineEndings, LineIndex, OffsetEncoding},
+ LspError,
};
-pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool {
- e.downcast_ref::<Canceled>().is_some()
+pub(crate) fn invalid_params_error(message: String) -> LspError {
+ LspError { code: lsp_server::ErrorCode::InvalidParams as i32, message }
}
pub(crate) fn notification_is<N: lsp_types::notification::Notification>(
impl GlobalState {
pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) {
- let message = message.into();
+ let message = message;
self.send_notification::<lsp_types::notification::ShowMessage>(
lsp_types::ShowMessageParams { typ, message },
)
}
+ /// Sends a notification to the client containing the error `message`.
+ /// If `additional_info` is [`Some`], appends a note to the notification telling to check the logs.
+ /// This will always log `message` + `additional_info` to the server's error log.
+ pub(crate) fn show_and_log_error(&mut self, message: String, additional_info: Option<String>) {
+ let mut message = message;
+ match additional_info {
+ Some(additional_info) => {
+ tracing::error!("{}\n\n{}", &message, &additional_info);
+ if tracing::enabled!(tracing::Level::ERROR) {
+ message.push_str("\n\nCheck the server logs for additional info.");
+ }
+ }
+ None => tracing::error!("{}", &message),
+ }
+
+ self.send_notification::<lsp_types::notification::ShowMessage>(
+ lsp_types::ShowMessageParams { typ: lsp_types::MessageType::ERROR, message },
+ )
+ }
+
+ /// rust-analyzer is resilient -- if it fails, this doesn't usually affect
+ /// the user experience. Part of that is that we deliberately hide panics
+ /// from the user.
+ ///
+ /// We do however want to pester rust-analyzer developers with panics and
+ /// other "you really gotta fix that" messages. The current strategy is to
+ /// be noisy for "from source" builds or when profiling is enabled.
+ ///
+ /// It's unclear if making from source `cargo xtask install` builds more
+ /// panicky is a good idea, let's see if we can keep our awesome bleeding
+ /// edge users from being upset!
+ pub(crate) fn poke_rust_analyzer_developer(&mut self, message: String) {
+ let from_source_build = option_env!("POKE_RA_DEVS").is_some();
+ let profiling_enabled = std::env::var("RA_PROFILE").is_ok();
+ if from_source_build || profiling_enabled {
+ self.show_message(lsp_types::MessageType::ERROR, message)
+ }
+ }
+
pub(crate) fn report_progress(
&mut self,
title: &str,
return;
}
let percentage = fraction.map(|f| {
- assert!(0.0 <= f && f <= 1.0);
+ assert!((0.0..=1.0).contains(&f));
(f * 100.0) as u32
});
let token = lsp_types::ProgressToken::String(format!("rustAnalyzer/{}", title));
match change.range {
Some(range) => {
if !index_valid.covers(range.end.line) {
- line_index.index = Arc::new(ide::LineIndex::new(&old_text));
+ line_index.index = Arc::new(ide::LineIndex::new(old_text));
}
index_valid = IndexValid::UpToLineExclusive(range.start.line);
- let range = from_proto::text_range(&line_index, range);
- old_text.replace_range(Range::<usize>::from(range), &change.text);
+ if let Ok(range) = from_proto::text_range(&line_index, range) {
+ old_text.replace_range(Range::<usize>::from(range), &change.text);
+ }
}
None => {
*old_text = change.text;
edit_ranges.push(edit.range);
}
Some(lsp_types::CompletionTextEdit::InsertAndReplace(edit)) => {
- edit_ranges.push(edit.insert);
- edit_ranges.push(edit.replace);
+ let replace = edit.replace;
+ let insert = edit.insert;
+ if replace.start != insert.start
+ || insert.start > insert.end
+ || insert.end > replace.end
+ {
+ // insert has to be a prefix of replace but it is not
+ return false;
+ }
+ edit_ranges.push(replace);
}
None => {}
}
"Completion with disjoint edits fails the validation even with empty extra edits"
);
- completion_with_joint_edits.text_edit =
- Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
- new_text: "new_text".to_string(),
- insert: disjoint_edit.range,
- replace: joint_edit.range,
- }));
- completion_with_joint_edits.additional_text_edits = None;
- assert!(
- !all_edits_are_disjoint(&completion_with_joint_edits, &[]),
- "Completion with disjoint edits fails the validation even with empty extra edits"
- );
-
completion_with_joint_edits.text_edit =
Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit {
new_text: "new_text".to_string(),
"Completion with disjoint edits is valid"
);
assert!(
- !all_edits_are_disjoint(&completion_with_disjoint_edits, &[joint_edit.clone()]),
+ !all_edits_are_disjoint(&completion_with_disjoint_edits, &[joint_edit]),
"Completion with disjoint edits and joint extra edit is invalid"
);
assert!(
- all_edits_are_disjoint(&completion_with_disjoint_edits, &[disjoint_edit_2.clone()]),
+ all_edits_are_disjoint(&completion_with_disjoint_edits, &[disjoint_edit_2]),
"Completion with disjoint edits and joint extra edit is valid"
);
}