Gutter-highlight removed and added portions of expanded diff hunks separately (#24834)

cc @iamnbutler 

Release Notes:

- Color gutter highlights separately for removed and deleted portions of
git modification hunks

Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
Cole Miller 2025-02-14 10:31:30 -05:00 committed by GitHub
parent 4aae0e2f6c
commit 39c9b1f170
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -79,17 +79,18 @@ use workspace::{item::Item, notifications::NotifyTaskExt, Workspace};
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.; const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
/// Note that for a "modified" MultiBufferDiffHunk, there are two DisplayDiffHunks,
/// one for the deleted portion and one for the added portion.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
enum DisplayDiffHunk { enum DisplayDiffHunk {
Folded { Folded {
display_row: DisplayRow, display_row: DisplayRow,
}, },
Unfolded { Unfolded {
diff_base_byte_range: Range<usize>,
display_row_range: Range<DisplayRow>, display_row_range: Range<DisplayRow>,
multi_buffer_range: Range<Anchor>, multi_buffer_range: Range<Anchor>,
status: DiffHunkStatus, status: DiffHunkStatus,
contains_expanded: bool, expanded: bool,
}, },
} }
@ -104,7 +105,7 @@ struct SelectionLayout {
} }
impl SelectionLayout { impl SelectionLayout {
fn new<T: ToPoint + ToDisplayPoint + Clone>( fn new<T: multi_buffer::ToPoint + ToDisplayPoint + Clone>(
selection: Selection<T>, selection: Selection<T>,
line_mode: bool, line_mode: bool,
cursor_shape: CursorShape, cursor_shape: CursorShape,
@ -1557,36 +1558,97 @@ impl EditorElement {
let hunk_end_point = Point::new(hunk.row_range.end.0, 0); let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
let hunk_display_start = snapshot.point_to_display_point(hunk_start_point, Bias::Left); let hunk_display_start = snapshot.point_to_display_point(hunk_start_point, Bias::Left);
let hunk_added_start_at =
Anchor::in_buffer(hunk.excerpt_id, hunk.buffer_id, hunk.buffer_range.start);
let hunk_deleted_to_added_break = snapshot.point_to_display_point(
hunk_added_start_at.to_point(&snapshot.buffer_snapshot),
Bias::Right,
);
let hunk_display_end = snapshot.point_to_display_point(hunk_end_point, Bias::Right); let hunk_display_end = snapshot.point_to_display_point(hunk_end_point, Bias::Right);
let display_hunk = if hunk_display_start.column() != 0 { if hunk_display_start.column() != 0 {
display_hunks.push((
DisplayDiffHunk::Folded { DisplayDiffHunk::Folded {
display_row: hunk_display_start.row(), display_row: hunk_display_start.row(),
} },
None,
));
} else { } else {
let mut end_row = hunk_display_end.row(); let mut end_row = hunk_display_end.row();
if hunk_display_end.column() > 0 { if hunk_display_end.column() > 0 {
end_row.0 += 1; end_row.0 += 1;
} }
let start_row = hunk_display_start.row(); let deleted_count = snapshot
let contains_expanded = snapshot .buffer_snapshot
.row_infos(start_row) .row_infos(hunk.row_range.start)
.take(end_row.0 as usize - start_row.0 as usize) .take(hunk.row_range.end.0 as usize - hunk.row_range.start.0 as usize)
.any(|row_info| row_info.diff_status.is_some()); .take_while(|row_info| {
matches!(row_info.diff_status, Some(DiffHunkStatus::Removed(_)))
})
.count();
let has_added = snapshot
.buffer_snapshot
.row_infos(hunk.row_range.start)
.take(hunk.row_range.end.0 as usize - hunk.row_range.start.0 as usize)
.any(|row_info| matches!(row_info.diff_status, Some(DiffHunkStatus::Added(_))));
let expanded = deleted_count > 0 || has_added;
if deleted_count > 0 && has_added {
display_hunks.push((
DisplayDiffHunk::Unfolded { DisplayDiffHunk::Unfolded {
status: hunk.status(), status: DiffHunkStatus::Removed(hunk.secondary_status),
diff_base_byte_range: hunk.diff_base_byte_range, display_row_range: hunk_display_start.row()
..hunk_display_start.row() + DisplayRow(deleted_count as u32),
multi_buffer_range: Anchor::range_in_buffer(
hunk.excerpt_id,
hunk.buffer_id,
hunk.buffer_range.clone(),
),
expanded,
},
None,
));
display_hunks.push((
DisplayDiffHunk::Unfolded {
status: DiffHunkStatus::Added(hunk.secondary_status),
display_row_range: hunk_display_start.row()
+ DisplayRow(deleted_count as u32)
..end_row,
multi_buffer_range: Anchor::range_in_buffer(
hunk.excerpt_id,
hunk.buffer_id,
hunk.buffer_range,
),
expanded,
},
None,
));
} else {
let status = if expanded && matches!(hunk.status(), DiffHunkStatus::Modified(_))
{
if hunk_display_start.row() < hunk_deleted_to_added_break.row() {
DiffHunkStatus::Removed(hunk.secondary_status)
} else {
DiffHunkStatus::Added(hunk.secondary_status)
}
} else {
hunk.status()
};
display_hunks.push((
DisplayDiffHunk::Unfolded {
status,
display_row_range: hunk_display_start.row()..end_row, display_row_range: hunk_display_start.row()..end_row,
multi_buffer_range: Anchor::range_in_buffer( multi_buffer_range: Anchor::range_in_buffer(
hunk.excerpt_id, hunk.excerpt_id,
hunk.buffer_id, hunk.buffer_id,
hunk.buffer_range, hunk.buffer_range,
), ),
contains_expanded, expanded,
},
None,
));
} }
}; };
display_hunks.push((display_hunk, None));
} }
let git_gutter_setting = ProjectSettings::get_global(cx) let git_gutter_setting = ProjectSettings::get_global(cx)
@ -1924,7 +1986,8 @@ impl EditorElement {
if tasks.offset.0 < offset_range_start || tasks.offset.0 >= offset_range_end { if tasks.offset.0 < offset_range_start || tasks.offset.0 >= offset_range_end {
return None; return None;
} }
let multibuffer_point = tasks.offset.0.to_point(&snapshot.buffer_snapshot); let multibuffer_point =
multi_buffer::ToPoint::to_point(&tasks.offset.0, &snapshot.buffer_snapshot);
let multibuffer_row = MultiBufferRow(multibuffer_point.row); let multibuffer_row = MultiBufferRow(multibuffer_point.row);
let buffer_folded = snapshot let buffer_folded = snapshot
.buffer_snapshot .buffer_snapshot
@ -4579,7 +4642,7 @@ impl EditorElement {
DisplayDiffHunk::Unfolded { DisplayDiffHunk::Unfolded {
status, status,
display_row_range, display_row_range,
contains_expanded, expanded,
.. ..
} => hitbox.as_ref().map(|hunk_hitbox| match status { } => hitbox.as_ref().map(|hunk_hitbox| match status {
DiffHunkStatus::Added(secondary_status) => ( DiffHunkStatus::Added(secondary_status) => (
@ -4587,14 +4650,14 @@ impl EditorElement {
cx.theme().colors().version_control_added.opacity(0.7), cx.theme().colors().version_control_added.opacity(0.7),
corners, corners,
secondary_status, secondary_status,
*contains_expanded, *expanded,
), ),
DiffHunkStatus::Modified(secondary_status) => ( DiffHunkStatus::Modified(secondary_status) => (
hunk_hitbox.bounds, hunk_hitbox.bounds,
cx.theme().colors().version_control_modified.opacity(0.7), cx.theme().colors().version_control_modified.opacity(0.7),
corners, corners,
secondary_status, secondary_status,
*contains_expanded, *expanded,
), ),
DiffHunkStatus::Removed(secondary_status) DiffHunkStatus::Removed(secondary_status)
if !display_row_range.is_empty() => if !display_row_range.is_empty() =>
@ -4604,7 +4667,7 @@ impl EditorElement {
cx.theme().colors().version_control_deleted.opacity(0.7), cx.theme().colors().version_control_deleted.opacity(0.7),
corners, corners,
secondary_status, secondary_status,
*contains_expanded, *expanded,
) )
} }
DiffHunkStatus::Removed(secondary_status) => ( DiffHunkStatus::Removed(secondary_status) => (
@ -4618,7 +4681,7 @@ impl EditorElement {
cx.theme().colors().version_control_deleted.opacity(0.7), cx.theme().colors().version_control_deleted.opacity(0.7),
Corners::all(1. * line_height), Corners::all(1. * line_height),
secondary_status, secondary_status,
*contains_expanded, *expanded,
), ),
}), }),
}; };
@ -4628,12 +4691,11 @@ impl EditorElement {
background_color, background_color,
corner_radii, corner_radii,
secondary_status, secondary_status,
contains_expanded, expanded,
)) = hunk_to_paint )) = hunk_to_paint
{ {
let background = if *secondary_status != DiffHunkSecondaryStatus::None let background =
&& contains_expanded if *secondary_status != DiffHunkSecondaryStatus::None && expanded {
{
pattern_slash(background_color, line_height.0 / 2.5) pattern_slash(background_color, line_height.0 / 2.5)
} else { } else {
solid_color(background_color) solid_color(background_color)