]> git.lizzy.rs Git - rust.git/blobdiff - crates/rust-analyzer/src/lsp_utils.rs
Replaced fold with for loop
[rust.git] / crates / rust-analyzer / src / lsp_utils.rs
index 60c12e4e2eacfa9cb5a0cbc1113dd4ff7fc72264..b09c411908af6c75356f8040f1060d73efac9a13 100644 (file)
@@ -1,14 +1,22 @@
 //! 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>(
@@ -33,12 +41,31 @@ 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 },
         )
     }
 
+    /// 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,
@@ -46,11 +73,11 @@ pub(crate) fn report_progress(
         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));
@@ -90,7 +117,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 +148,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;
@@ -130,7 +164,7 @@ fn covers(&self, line: u32) -> bool {
 }
 
 /// 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],
@@ -141,8 +175,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 +332,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 +340,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 +352,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 +381,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"
         );
     }