]> git.lizzy.rs Git - rust.git/blobdiff - crates/rust-analyzer/src/lsp_utils.rs
Auto merge of #13320 - Veykril:ty-alias-hover, r=Veykril
[rust.git] / crates / rust-analyzer / src / lsp_utils.rs
index 6ca94921f70c1732acf3731c67914beaa4048b93..5a37cbe2e334baa95346e4d522b600f2597a7545 100644 (file)
@@ -1,14 +1,17 @@
 //! Utilities for LSP-related boilerplate code.
-use std::{error::Error, ops::Range};
+use std::{ops::Range, sync::Arc};
 
-use ide::LineIndex;
-use ide_db::base_db::Canceled;
 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 notification_is<N: lsp_types::notification::Notification>(
@@ -33,12 +36,51 @@ pub(crate) fn fraction(done: usize, total: usize) -> f64 {
 
 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,
@@ -50,7 +92,7 @@ pub(crate) fn report_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));
@@ -90,7 +132,13 @@ pub(crate) fn apply_document_changes(
     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
@@ -115,11 +163,12 @@ fn covers(&self, line: u32) -> bool {
         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;
@@ -141,8 +190,16 @@ pub(crate) fn all_edits_are_disjoint(
             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 => {}
     }
@@ -290,7 +347,7 @@ fn completion_with_joint_edits_disjoint_tests() {
             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 =
@@ -298,19 +355,7 @@ fn completion_with_joint_edits_disjoint_tests() {
         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 =
@@ -322,7 +367,7 @@ fn completion_with_joint_edits_disjoint_tests() {
         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"
         );
     }
 
@@ -351,11 +396,11 @@ fn completion_with_disjoint_edits_disjoint_tests() {
             "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"
         );
     }