Improve deleted hunk blocks' behavior (#11340)

Kirill Bulatov created

* clear their selections on focus lost
* allow reverting diff hunks when the caret is inside of the deleted
hunk diff editor block

Release Notes:

- N/A

Change summary

crates/editor/src/hunk_diff.rs | 143 +++++++++++++++++------------------
1 file changed, 70 insertions(+), 73 deletions(-)

Detailed changes

crates/editor/src/hunk_diff.rs 🔗

@@ -1,4 +1,4 @@
-use std::ops::Range;
+use std::{ops::Range, sync::Arc};
 
 use collections::{hash_map, HashMap, HashSet};
 use git::diff::{DiffHunk, DiffHunkStatus};
@@ -14,7 +14,8 @@ use util::{debug_panic, RangeExt};
 use crate::{
     git::{diff_hunk_to_display, DisplayDiffHunk},
     hunks_for_selections, BlockDisposition, BlockId, BlockProperties, BlockStyle, DiffRowHighlight,
-    Editor, ExpandAllHunkDiffs, RangeToAnchorExt, ToDisplayPoint, ToggleHunkDiff,
+    Editor, ExpandAllHunkDiffs, RangeToAnchorExt, RevertSelectedHunks, ToDisplayPoint,
+    ToggleHunkDiff,
 };
 
 #[derive(Debug, Clone)]
@@ -215,35 +216,29 @@ impl Editor {
         let hunk_end = hunk.multi_buffer_range.end;
 
         let buffer = self.buffer().clone();
-        let (diff_base_buffer, deleted_text_range, deleted_text_lines) =
-            buffer.update(cx, |buffer, cx| {
-                let snapshot = buffer.snapshot(cx);
-                let hunk = buffer_diff_hunk(&snapshot, multi_buffer_row_range.clone())?;
-                let mut buffer_ranges = buffer.range_to_buffer_ranges(multi_buffer_row_range, cx);
-                if buffer_ranges.len() == 1 {
-                    let (buffer, _, _) = buffer_ranges.pop()?;
-                    let diff_base_buffer = diff_base_buffer
-                        .or_else(|| self.current_diff_base_buffer(&buffer, cx))
-                        .or_else(|| create_diff_base_buffer(&buffer, cx));
-                    let buffer = buffer.read(cx);
-                    let deleted_text_lines = buffer.diff_base().map(|diff_base| {
-                        let diff_start_row = diff_base
-                            .offset_to_point(hunk.diff_base_byte_range.start)
-                            .row;
-                        let diff_end_row =
-                            diff_base.offset_to_point(hunk.diff_base_byte_range.end).row;
-                        let line_count = diff_end_row - diff_start_row;
-                        line_count as usize
-                    });
-                    Some((
-                        diff_base_buffer?,
-                        hunk.diff_base_byte_range,
-                        deleted_text_lines,
-                    ))
-                } else {
-                    None
-                }
-            })?;
+        let (diff_base_buffer, deleted_text_lines) = buffer.update(cx, |buffer, cx| {
+            let snapshot = buffer.snapshot(cx);
+            let hunk = buffer_diff_hunk(&snapshot, multi_buffer_row_range.clone())?;
+            let mut buffer_ranges = buffer.range_to_buffer_ranges(multi_buffer_row_range, cx);
+            if buffer_ranges.len() == 1 {
+                let (buffer, _, _) = buffer_ranges.pop()?;
+                let diff_base_buffer = diff_base_buffer
+                    .or_else(|| self.current_diff_base_buffer(&buffer, cx))
+                    .or_else(|| create_diff_base_buffer(&buffer, cx))?;
+                let buffer = buffer.read(cx);
+                let deleted_text_lines = buffer.diff_base().map(|diff_base| {
+                    let diff_start_row = diff_base
+                        .offset_to_point(hunk.diff_base_byte_range.start)
+                        .row;
+                    let diff_end_row = diff_base.offset_to_point(hunk.diff_base_byte_range.end).row;
+                    let line_count = diff_end_row - diff_start_row;
+                    line_count as u8
+                })?;
+                Some((diff_base_buffer, deleted_text_lines))
+            } else {
+                None
+            }
+        })?;
 
         let block_insert_index = match self.expanded_hunks.hunks.binary_search_by(|probe| {
             probe
@@ -256,13 +251,9 @@ impl Editor {
         };
 
         let block = match hunk.status {
-            DiffHunkStatus::Removed => self.add_deleted_lines(
-                deleted_text_lines,
-                hunk_start,
-                diff_base_buffer,
-                deleted_text_range,
-                cx,
-            ),
+            DiffHunkStatus::Removed => {
+                self.insert_deleted_text_block(diff_base_buffer, deleted_text_lines, &hunk, cx)
+            }
             DiffHunkStatus::Added => {
                 self.highlight_rows::<DiffRowHighlight>(
                     hunk_start..hunk_end,
@@ -277,13 +268,7 @@ impl Editor {
                     Some(added_hunk_color(cx)),
                     cx,
                 );
-                self.add_deleted_lines(
-                    deleted_text_lines,
-                    hunk_start,
-                    diff_base_buffer,
-                    deleted_text_range,
-                    cx,
-                )
+                self.insert_deleted_text_block(diff_base_buffer, deleted_text_lines, &hunk, cx)
             }
         };
         self.expanded_hunks.hunks.insert(
@@ -300,43 +285,20 @@ impl Editor {
         Some(())
     }
 
-    fn add_deleted_lines(
-        &mut self,
-        deleted_text_lines: Option<usize>,
-        hunk_start: Anchor,
-        diff_base_buffer: Model<Buffer>,
-        deleted_text_range: Range<usize>,
-        cx: &mut ViewContext<'_, Self>,
-    ) -> Option<BlockId> {
-        if let Some(deleted_text_lines) = deleted_text_lines {
-            self.insert_deleted_text_block(
-                hunk_start,
-                diff_base_buffer,
-                deleted_text_range,
-                deleted_text_lines as u8,
-                cx,
-            )
-        } else {
-            debug_panic!("Found no deleted text for removed hunk on position {hunk_start:?}");
-            None
-        }
-    }
-
     fn insert_deleted_text_block(
         &mut self,
-        position: Anchor,
         diff_base_buffer: Model<Buffer>,
-        deleted_text_range: Range<usize>,
         deleted_text_height: u8,
+        hunk: &HunkToExpand,
         cx: &mut ViewContext<'_, Self>,
     ) -> Option<BlockId> {
         let deleted_hunk_color = deleted_hunk_color(cx);
         let (editor_height, editor_with_deleted_text) =
-            editor_with_deleted_text(diff_base_buffer, deleted_text_range, deleted_hunk_color, cx);
+            editor_with_deleted_text(diff_base_buffer, deleted_hunk_color, hunk, cx);
         let parent_gutter_offset = self.gutter_dimensions.width + self.gutter_dimensions.margin;
         let mut new_block_ids = self.insert_blocks(
             Some(BlockProperties {
-                position,
+                position: hunk.multi_buffer_range.start,
                 height: editor_height.max(deleted_text_height),
                 style: BlockStyle::Flex,
                 render: Box::new(move |_| {
@@ -571,10 +533,11 @@ fn deleted_hunk_color(cx: &AppContext) -> Hsla {
 
 fn editor_with_deleted_text(
     diff_base_buffer: Model<Buffer>,
-    deleted_text_range: Range<usize>,
     deleted_color: Hsla,
+    hunk: &HunkToExpand,
     cx: &mut ViewContext<'_, Editor>,
 ) -> (u8, View<Editor>) {
+    let parent_editor = cx.view().downgrade();
     let editor = cx.new_view(|cx| {
         let multi_buffer =
             cx.new_model(|_| MultiBuffer::without_headers(0, language::Capability::ReadOnly));
@@ -582,7 +545,7 @@ fn editor_with_deleted_text(
             multi_buffer.push_excerpts(
                 diff_base_buffer,
                 Some(ExcerptRange {
-                    context: deleted_text_range,
+                    context: hunk.diff_base_byte_range.clone(),
                     primary: None,
                 }),
                 cx,
@@ -603,6 +566,40 @@ fn editor_with_deleted_text(
             .anchor_after(editor.buffer.read(cx).len(cx));
 
         editor.highlight_rows::<DiffRowHighlight>(start..end, Some(deleted_color), cx);
+        let hunk_related_subscription = cx.on_blur(&editor.focus_handle, |editor, cx| {
+            editor.change_selections(None, cx, |s| {
+                s.try_cancel();
+            });
+        });
+        editor._subscriptions.push(hunk_related_subscription);
+        let original_multi_buffer_range = hunk.multi_buffer_range.clone();
+        let diff_base_range = hunk.diff_base_byte_range.clone();
+        editor.register_action::<RevertSelectedHunks>(move |_, cx| {
+            parent_editor
+                .update(cx, |editor, cx| {
+                    let Some((buffer, original_text)) = editor.buffer().update(cx, |buffer, cx| {
+                        let (_, buffer, _) =
+                            buffer.excerpt_containing(original_multi_buffer_range.start, cx)?;
+                        let original_text =
+                            buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
+                        Some((buffer, Arc::from(original_text.to_string())))
+                    }) else {
+                        return;
+                    };
+                    buffer.update(cx, |buffer, cx| {
+                        buffer.edit(
+                            Some((
+                                original_multi_buffer_range.start.text_anchor
+                                    ..original_multi_buffer_range.end.text_anchor,
+                                original_text,
+                            )),
+                            None,
+                            cx,
+                        )
+                    });
+                })
+                .ok();
+        });
         editor
     });