//! Utilities for LSP-related boilerplate code.
-use std::{error::Error, ops::Range};
+use std::{error::Error, ops::Range, sync::Arc};
-use ide::LineIndex;
-use ide_db::base_db::Canceled;
+use ide_db::base_db::Cancelled;
use lsp_server::Notification;
-use crate::{from_proto, global_state::GlobalState};
+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 is_cancelled(e: &(dyn Error + 'static)) -> bool {
+ e.downcast_ref::<Cancelled>().is_some()
}
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 },
)
}
+ /// 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 = env!("REV").contains("dev");
+ 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,
message: Option<String>,
fraction: Option<f64>,
) {
- if !self.config.client_caps.work_done_progress {
+ if !self.config.work_done_progress() {
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));
old_text: &mut String,
content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>,
) {
- let mut line_index = LineIndex::new(old_text);
+ let mut line_index = LineIndex {
+ index: Arc::new(ide::LineIndex::new(old_text)),
+ // We don't care about line endings or offset encoding here.
+ endings: LineEndings::Unix,
+ encoding: OffsetEncoding::Utf16,
+ };
+
// The changes we got must be applied sequentially, but can cross lines so we
// have to keep our line index updated.
// Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
match change.range {
Some(range) => {
if !index_valid.covers(range.end.line) {
- line_index = 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;
}
/// Checks that the edits inside the completion and the additional edits do not overlap.
-/// LSP explicitly forbits the additional edits to overlap both with the main edit and themselves.
+/// LSP explicitly forbids the additional edits to overlap both with the main edit and themselves.
pub(crate) fn all_edits_are_disjoint(
completion: &lsp_types::CompletionItem,
additional_edits: &[lsp_types::TextEdit],
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 => {}
}
Some(vec![disjoint_edit.clone(), joint_edit.clone()]);
assert!(
!all_edits_are_disjoint(&completion_with_joint_edits, &[]),
- "Completion with disjoint edits fails the validaton even with empty extra edits"
+ "Completion with disjoint edits fails the validation even with empty extra edits"
);
completion_with_joint_edits.text_edit =
completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit.clone()]);
assert!(
!all_edits_are_disjoint(&completion_with_joint_edits, &[]),
- "Completion with disjoint edits fails the validaton 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 validaton even with empty extra edits"
+ "Completion with disjoint edits fails the validation even with empty extra edits"
);
completion_with_joint_edits.text_edit =
completion_with_joint_edits.additional_text_edits = Some(vec![joint_edit]);
assert!(
!all_edits_are_disjoint(&completion_with_joint_edits, &[]),
- "Completion with disjoint edits fails the validaton even with empty extra edits"
+ "Completion with disjoint edits fails the validation even with empty extra edits"
);
}
"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"
);
}