Disable diff hunks for untracked files, even w/ no newline at eof (#25980)

Max Brunsfeld created

This fixes an issue where diff hunks were shown for untracked files, but
only if the files did not end with a newline.

Release Notes:

- N/A

Change summary

crates/buffer_diff/src/buffer_diff.rs         | 31 ++++++++++----------
crates/editor/src/editor.rs                   | 27 ++++++++----------
crates/multi_buffer/src/multi_buffer.rs       | 15 +++------
crates/multi_buffer/src/multi_buffer_tests.rs |  2 
4 files changed, 34 insertions(+), 41 deletions(-)

Detailed changes

crates/buffer_diff/src/buffer_diff.rs 🔗

@@ -56,8 +56,8 @@ pub enum DiffHunkSecondaryStatus {
 /// A diff hunk resolved to rows in the buffer.
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct DiffHunk {
-    /// The buffer range, expressed in terms of rows.
-    pub row_range: Range<u32>,
+    /// The buffer range as points.
+    pub range: Range<Point>,
     /// The range in the buffer to which this hunk corresponds.
     pub buffer_range: Range<Anchor>,
     /// The range in the buffer's diff base text to which this hunk corresponds.
@@ -362,6 +362,7 @@ impl BufferDiffInner {
             pending_hunks = secondary.pending_hunks.clone();
         }
 
+        let max_point = buffer.max_point();
         let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
         iter::from_fn(move || loop {
             let (start_point, (start_anchor, start_base)) = summaries.next()?;
@@ -371,7 +372,7 @@ impl BufferDiffInner {
                 continue;
             }
 
-            if end_point.column > 0 {
+            if end_point.column > 0 && end_point < max_point {
                 end_point.row += 1;
                 end_point.column = 0;
                 end_anchor = buffer.anchor_before(end_point);
@@ -416,7 +417,7 @@ impl BufferDiffInner {
             }
 
             return Some(DiffHunk {
-                row_range: start_point.row..end_point.row,
+                range: start_point..end_point,
                 diff_base_byte_range: start_base..end_base,
                 buffer_range: start_anchor..end_anchor,
                 secondary_status,
@@ -442,14 +443,9 @@ impl BufferDiffInner {
 
             let hunk = cursor.item()?;
             let range = hunk.buffer_range.to_point(buffer);
-            let end_row = if range.end.column > 0 {
-                range.end.row + 1
-            } else {
-                range.end.row
-            };
 
             Some(DiffHunk {
-                row_range: range.start.row..end_row,
+                range,
                 diff_base_byte_range: hunk.diff_base_byte_range.clone(),
                 buffer_range: hunk.buffer_range.clone(),
                 // The secondary status is not used by callers of this method.
@@ -1136,12 +1132,10 @@ pub fn assert_hunks<Iter>(
     let actual_hunks = diff_hunks
         .map(|hunk| {
             (
-                hunk.row_range.clone(),
+                hunk.range.clone(),
                 &diff_base[hunk.diff_base_byte_range.clone()],
                 buffer
-                    .text_for_range(
-                        Point::new(hunk.row_range.start, 0)..Point::new(hunk.row_range.end, 0),
-                    )
+                    .text_for_range(hunk.range.clone())
                     .collect::<String>(),
                 hunk.status(),
             )
@@ -1150,7 +1144,14 @@ pub fn assert_hunks<Iter>(
 
     let expected_hunks: Vec<_> = expected_hunks
         .iter()
-        .map(|(r, s, h, status)| (r.clone(), *s, h.to_string(), *status))
+        .map(|(r, old_text, new_text, status)| {
+            (
+                Point::new(r.start, 0)..Point::new(r.end, 0),
+                *old_text,
+                new_text.to_string(),
+                *status,
+            )
+        })
         .collect();
 
     assert_eq!(actual_hunks, expected_hunks);

crates/editor/src/editor.rs 🔗

@@ -13733,7 +13733,7 @@ impl Editor {
                         buffer_range: hunk.buffer_range,
                         diff_base_byte_range: hunk.diff_base_byte_range,
                         secondary_status: hunk.secondary_status,
-                        row_range: 0..0, // unused
+                        range: Point::zero()..Point::zero(), // unused
                     })
                     .collect::<Vec<_>>(),
                 &buffer_snapshot,
@@ -16041,9 +16041,9 @@ impl Editor {
                 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
                     buffer.update(cx, |buffer, cx| {
                         buffer.edit(
-                            changes.into_iter().map(|(range, text)| {
-                                (range, text.to_string().map(Arc::<str>::from))
-                            }),
+                            changes
+                                .into_iter()
+                                .map(|(range, text)| (range, text.to_string())),
                             None,
                             cx,
                         );
@@ -17161,17 +17161,14 @@ impl EditorSnapshot {
             for hunk in self.buffer_snapshot.diff_hunks_in_range(
                 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
             ) {
-                // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
-                // when the caret is just above or just below the deleted hunk.
-                let allow_adjacent = hunk.status().is_deleted();
-                let related_to_selection = if allow_adjacent {
-                    hunk.row_range.overlaps(&query_rows)
-                        || hunk.row_range.start == query_rows.end
-                        || hunk.row_range.end == query_rows.start
-                } else {
-                    hunk.row_range.overlaps(&query_rows)
-                };
-                if related_to_selection {
+                // Include deleted hunks that are adjacent to the query range, because
+                // otherwise they would be missed.
+                let mut intersects_range = hunk.row_range.overlaps(&query_rows);
+                if hunk.status().is_deleted() {
+                    intersects_range |= hunk.row_range.start == query_rows.end;
+                    intersects_range |= hunk.row_range.end == query_rows.start;
+                }
+                if intersects_range {
                     if !processed_buffer_rows
                         .entry(hunk.buffer_id)
                         .or_default()

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -3498,17 +3498,12 @@ impl MultiBufferSnapshot {
                         if hunk.is_created_file() && !self.all_diff_hunks_expanded {
                             return None;
                         }
-                        Some((
-                            Point::new(hunk.row_range.start, 0)..Point::new(hunk.row_range.end, 0),
-                            hunk,
-                        ))
+                        Some((hunk.range.clone(), hunk))
                     }),
             )
         })
         .filter_map(move |(range, hunk, excerpt)| {
-            if range.start != range.end
-                && range.end == query_range.start
-                && !hunk.row_range.is_empty()
+            if range.start != range.end && range.end == query_range.start && !hunk.range.is_empty()
             {
                 return None;
             }
@@ -3841,12 +3836,12 @@ impl MultiBufferSnapshot {
                     &excerpt.buffer,
                 ) {
                     let hunk_range = hunk.buffer_range.to_offset(&excerpt.buffer);
-                    if hunk.row_range.end >= buffer_end_row {
+                    if hunk.range.end >= Point::new(buffer_end_row, 0) {
                         continue;
                     }
 
-                    let hunk_start = Point::new(hunk.row_range.start, 0);
-                    let hunk_end = Point::new(hunk.row_range.end, 0);
+                    let hunk_start = hunk.range.start;
+                    let hunk_end = hunk.range.end;
 
                     cursor.seek_to_buffer_position_in_current_excerpt(&DimensionPair {
                         key: hunk_range.start,

crates/multi_buffer/src/multi_buffer_tests.rs 🔗

@@ -2274,7 +2274,7 @@ impl ReferenceMultibuffer {
                 }
 
                 if !hunk.buffer_range.start.is_valid(&buffer) {
-                    log::trace!("skipping hunk with deleted start: {:?}", hunk.row_range);
+                    log::trace!("skipping hunk with deleted start: {:?}", hunk.range);
                     continue;
                 }