Fix panic when multi-cursor edit attempted in deleted hunk (#23633)

Conrad Irwin created

Release Notes:

- N/A

Change summary

crates/editor/src/actions.rs            |  2 
crates/editor/src/editor.rs             |  6 ++++
crates/editor/src/editor_tests.rs       | 39 +++++++++++++++++++++++++++
crates/multi_buffer/src/multi_buffer.rs |  4 +-
4 files changed, 48 insertions(+), 3 deletions(-)

Detailed changes

crates/editor/src/actions.rs ๐Ÿ”—

@@ -372,7 +372,6 @@ gpui::actions!(
         ToggleAutoSignatureHelp,
         ToggleGitBlame,
         ToggleGitBlameInline,
-        ToggleSelectedDiffHunks,
         ToggleIndentGuides,
         ToggleInlayHints,
         ToggleInlineCompletions,
@@ -397,3 +396,4 @@ gpui::actions!(
 action_as!(go_to_line, ToggleGoToLine as Toggle);
 
 action_with_deprecated_aliases!(editor, OpenSelectedFilename, ["editor::OpenFile"]);
+action_with_deprecated_aliases!(editor, ToggleSelectedDiffHunks, ["editor::ToggleDiffHunk"]);

crates/editor/src/editor.rs ๐Ÿ”—

@@ -3724,6 +3724,9 @@ impl Editor {
         }
 
         let position = self.selections.newest_anchor().head();
+        if position.diff_base_anchor.is_some() {
+            return;
+        }
         let (buffer, buffer_position) =
             if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
                 output
@@ -4391,6 +4394,9 @@ impl Editor {
     fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
         let buffer = self.buffer.read(cx);
         let newest_selection = self.selections.newest_anchor().clone();
+        if newest_selection.head().diff_base_anchor.is_some() {
+            return None;
+        }
         let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
         let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
         if start_buffer != end_buffer {

crates/editor/src/editor_tests.rs ๐Ÿ”—

@@ -13229,6 +13229,45 @@ async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
     "}
         .to_string(),
     );
+
+    cx.set_state(indoc! { "
+        one
+        TWO
+        ห‡three
+        four
+        five
+    "});
+    cx.run_until_parked();
+    cx.update_editor(|editor, cx| {
+        editor.toggle_selected_diff_hunks(&Default::default(), cx);
+    });
+
+    cx.assert_state_with_diff(
+        indoc! { "
+            one
+          - two
+          + TWO
+            ห‡three
+            four
+            five
+        "}
+        .to_string(),
+    );
+    cx.update_editor(|editor, cx| {
+        editor.move_up(&Default::default(), cx);
+        editor.move_up(&Default::default(), cx);
+        editor.toggle_selected_diff_hunks(&Default::default(), cx);
+    });
+    cx.assert_state_with_diff(
+        indoc! { "
+            one
+            ห‡TWO
+            three
+            four
+            five
+        "}
+        .to_string(),
+    );
 }
 
 #[gpui::test]

crates/multi_buffer/src/multi_buffer.rs ๐Ÿ”—

@@ -796,7 +796,7 @@ impl MultiBuffer {
         for (ix, (range, new_text)) in edits.into_iter().enumerate() {
             let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0);
 
-            cursor.seek_forward(&range.start);
+            cursor.seek(&range.start);
             let mut start_region = cursor.region().expect("start offset out of bounds");
             if !start_region.is_main_buffer {
                 cursor.next();
@@ -2266,7 +2266,7 @@ impl MultiBuffer {
                         diff_hunk.buffer_range.start,
                     );
                 }
-                if diff_hunk.row_range.start.0 == peek_end.row
+                if diff_hunk.row_range.start.0 <= peek_end.row
                     && diff_hunk.excerpt_id == end.excerpt_id
                 {
                     end = Anchor::in_buffer(