refactor: hide logical comparison rows from painting

This commit is contained in:
Valdemar Lorenzen
2026-10-16 17:32:04 +02:00
parent a1fdc1e1b0
commit 4511bc2df9
5 changed files with 462 additions and 133 deletions
+134 -111
View File
@@ -41,8 +41,8 @@ use unicode_width::UnicodeWidthStr;
use crate::{
chrome::{ChromeLine, PopupAnchor, SegmentConfig},
diff::{
ChangedFile, DiffState, DisplayRow, FileStatus, Focus, HistoryComparison, PaneLayout,
Phase, RowKind, ViewMode,
ChangedFile, ColumnEmpty, ColumnRow, ColumnView, DiffState, DisplayRow, FileStatus, Focus,
HistoryComparison, PaneLayout, Phase, ViewMode,
},
editor::{
Completion, Cursor, Editor, Hover, Input, Mode, Palette, ResolverLayout, ResolverStatus,
@@ -911,7 +911,11 @@ impl BufferViewports {
);
if !editor.file_picker().is_some_and(|picker| picker.focused)
&& (history.caret != Some(identity) || previous_widths != history.widths)
&& let Some(display) = history.comparison.column(column).display_row(cursor.row)
&& let Some(display) = history
.comparison
.column(column)
.view()
.display_row(cursor.row)
{
let column_offset = display_column(&editor.lines()[cursor.row], cursor.column);
let continuation = if history.wrapping[column] {
@@ -1012,9 +1016,8 @@ impl BufferViewports {
self.panels.caret = paint_diff_editor(
frame,
view,
projection.diff,
projection.view(),
text_area,
projection.focus,
(history.top.display, history.left[column]),
Some(&history.visible[column]),
)
@@ -3225,9 +3228,8 @@ fn paint_editor_line(
fn paint_diff_editor(
frame: &mut Frame,
editor: &Editor,
diff: &DiffState,
view: ColumnView<'_>,
area: Rect,
focus: Focus,
viewport: (usize, usize),
wrapped: Option<&[WrappedDiffRow]>,
) -> Option<Position> {
@@ -3237,8 +3239,11 @@ fn paint_diff_editor(
let source_cursor = editor.cursor();
let cursor_column = display_column(&editor.lines()[source_cursor.row], source_cursor.column);
let mut cursor = None;
for (screen, display, segments) in diff_screen_rows(diff, top, area.height, wrapped) {
let Some(source) = diff.display_source_row(focus, display) else {
let focus = view.info.focus;
for (screen, display, segments) in
diff_screen_rows(view.info.display_len, top, area.height, wrapped)
{
let Some(source) = view.native_row(display) else {
continue;
};
let mut line_area = Rect::new(area.x, area.y + screen as u16, area.width, 1);
@@ -3895,9 +3900,8 @@ fn draw_diff(
document_caret = paint_diff_editor(
frame,
editor,
diff,
diff.column_view(Focus::Before).unwrap(),
text_area,
Focus::Before,
(top, before_left),
wrapped,
)
@@ -3936,9 +3940,8 @@ fn draw_diff(
document_caret = paint_diff_editor(
frame,
editor,
diff,
diff.column_view(Focus::After).unwrap(),
text_area,
Focus::After,
(top, after_left),
wrapped,
)
@@ -4269,25 +4272,13 @@ fn draw_diff_pane(
} else {
diff_pane_panel(frame, diff, Some(file), area, before, resolver)
};
if diff.display_rows.is_empty() {
frame.render_widget(
Paragraph::new(file.notice.as_deref().map(display_text).unwrap_or_else(|| {
if diff.rows.is_empty() {
"Empty file"
} else {
"No text changes"
}
.into()
})),
content,
);
return content;
}
let view = diff.column_view(focus).expect("prepared comparison column");
let unified = diff.view_mode == ViewMode::Unified;
let (top, left, previous_width) = viewport;
let gutter = diff_gutter(diff, file, usize::from(content.width), focus);
let width = usize::from(content.width).saturating_sub(gutter);
if !diff.word_wrap
if view.info.display_len > 0
&& !diff.word_wrap
&& !content_only
&& previous_width.is_some_and(|previous| usize::from(previous) != width)
{
@@ -4310,68 +4301,95 @@ fn draw_diff_pane(
&& comment.anchor.path == *path
&& diff.review_anchor_current(&comment.anchor)
});
paint_diff_content(
frame,
view,
content,
syntax,
semantic,
(top, left),
wrapped,
colors,
highlight_trailing_whitespace,
whitespace_characters,
whitespace_scope,
review_comments,
diff.local_review.selected,
)
}
#[allow(clippy::too_many_arguments)]
fn paint_diff_content<'a>(
frame: &mut Frame,
view: ColumnView<'a>,
content: Rect,
syntax: [&SyntaxCache; 2],
semantic: [&[SemanticToken]; 2],
viewport: (usize, usize),
wrapped: Option<&[WrappedDiffRow]>,
colors: DiffColors,
highlight_trailing_whitespace: bool,
whitespace_characters: Option<&BTreeMap<char, char>>,
whitespace_scope: WhitespaceScope,
review_comments: impl Iterator<Item = &'a crate::review::Comment> + Clone,
selected_review: Option<u64>,
) -> Rect {
if view.info.display_len == 0 {
let notice = match view.info.empty {
ColumnEmpty::Notice(text) => display_text(text),
ColumnEmpty::EmptyFile => "Empty file".into(),
ColumnEmpty::NoChanges => "No text changes".into(),
};
frame.render_widget(Paragraph::new(notice), content);
return content;
}
let unified = view.info.unified;
let (top, left) = viewport;
let gutter = column_gutter(
unified,
view.info.source_extents,
usize::from(content.width),
view.info.focus,
);
let width = usize::from(content.width).saturating_sub(gutter);
let selected_anchor = review_comments
.clone()
.find(|comment| diff.local_review.selected == Some(comment.id))
.find(|comment| selected_review == Some(comment.id))
.map(|comment| &comment.anchor);
let review_source_row = |side, display| {
diff.display_source_row(side, display).or_else(|| {
let DisplayRow::Line(index) = diff.display_rows[display] else {
return None;
};
let row = &diff.rows[index];
(unified && row.kind == RowKind::Equal)
.then_some(match side {
Focus::Before => row.before,
Focus::After => row.after,
Focus::Files => None,
})
.flatten()
})
};
for (screen, display_row, segments) in diff_screen_rows(diff, top, content.height, wrapped) {
let row = &diff.display_rows[display_row];
for (screen, display_row, segments) in
diff_screen_rows(view.info.display_len, top, content.height, wrapped)
{
let row_area = Rect::new(content.x, content.y + screen as u16, content.width, 1);
let aligned_row = match row {
DisplayRow::Line(index) => *index,
DisplayRow::Fold { start, end } => {
let line = match view.row(display_row) {
Some(ColumnRow::Fold { hidden }) => {
frame.render_widget(
Paragraph::new(format!(
"… {} unchanged lines hidden · [/] expand context",
end - start
hidden
))
.style(Style::default().fg(Color::DarkGray)),
row_area,
);
continue;
}
};
let row = &diff.rows[aligned_row];
let before = if unified {
diff.display_focus(display_row).unwrap_or(focus) == Focus::Before
} else {
before
};
let source = if before { &file.before } else { &file.after };
let index = diff.display_source_row(
if before { Focus::Before } else { Focus::After },
display_row,
);
let Some(index) = index else {
let style = Style::default().fg(Color::DarkGray).bg(Color::Reset);
let buffer = frame.buffer_mut();
for x in 0..content.width {
buffer[(content.x + x, row_area.y)]
.set_symbol("╱")
.set_style(style);
Some(ColumnRow::Text(line)) => line,
Some(ColumnRow::Gap) => {
let style = Style::default().fg(Color::DarkGray).bg(Color::Reset);
let buffer = frame.buffer_mut();
for x in 0..content.width {
buffer[(content.x + x, row_area.y)]
.set_symbol("╱")
.set_style(style);
}
continue;
}
continue;
};
let source_text = match source.get(index) {
Some(text) => text.as_str(),
None if resolver.is_some() && index == source.len() => "",
None => continue,
};
let Some(source_text) = line.text else {
continue;
};
let before = line.side == Focus::Before;
let index = line.source_row;
let segment = segments.and_then(|segments| segments[usize::from(!before)]);
let left = segment.map_or(left, |segment| segment.start);
let text_width = segment.map_or(width, |segment| segment.end - segment.start);
@@ -4381,15 +4399,12 @@ fn draw_diff_pane(
segment_paint_width(segment, final_segment, width)
});
let text_area = Rect::new(content.x + gutter as u16, row_area.y, paint_width as u16, 1);
let changed = matches!(row.kind, RowKind::Changed)
|| (before && row.kind == RowKind::Removed)
|| (!before && row.kind == RowKind::Added);
let background =
if changed && !matches!(file.status, FileStatus::Added | FileStatus::Removed) {
colors.row[usize::from(!before)]
} else {
Color::Reset
};
let changed = line.changed;
let background = if changed && line.emphasize {
colors.row[usize::from(!before)]
} else {
Color::Reset
};
frame
.buffer_mut()
.set_style(row_area, Style::default().bg(background));
@@ -4413,14 +4428,11 @@ fn draw_diff_pane(
Color::DarkGray
};
let label = if unified {
let old = diff
.display_source_row(Focus::Before, display_row)
.map(|row| row + 1);
let new = diff
.display_source_row(Focus::After, display_row)
.map(|row| row + 1);
let old_digits = file.before.len().max(1).ilog10() as usize + 1;
let new_digits = file.after.len().max(1).ilog10() as usize + 1;
let [old, new] = line.numbers.map(|row| row.map(|row| row + 1));
let [old_digits, new_digits] = view
.info
.stored_lengths
.map(|len| len.max(1).ilog10() as usize + 1);
match (old, new) {
(Some(old), Some(new)) => {
format!("{old:>old_digits$} {new:>new_digits$}{marker} ")
@@ -4430,7 +4442,10 @@ fn draw_diff_pane(
(None, None) => unreachable!("displayed diff line has a source"),
}
} else {
let digits = diff_source_len(diff, file, before).max(1).ilog10() as usize + 1;
let digits = view.info.source_extents[usize::from(!before)]
.max(1)
.ilog10() as usize
+ 1;
format!("{:>digits$}{marker} ", index + 1)
};
frame.render_widget(
@@ -4462,19 +4477,13 @@ fn draw_diff_pane(
characters,
whitespace_scope,
syntax[usize::from(!before)].string_spans(index),
index + 1 < source.len()
|| if before {
file.before_eof
} else {
file.after_eof
},
line.newline,
true,
);
}
if !matches!(file.status, FileStatus::Added | FileStatus::Removed) {
if line.emphasize {
let style = Style::default().bg(colors.character[usize::from(!before)]);
for change in &row.changes {
let range = &change[usize::from(!before)];
for range in line.changes() {
if !range.is_empty() {
paint_grapheme_range(frame, text_area, left, source_text, range.clone(), style);
}
@@ -4504,7 +4513,8 @@ fn draw_diff_pane(
crate::review::Side::Before => Focus::Before,
crate::review::Side::After => Focus::After,
};
if let Some(source_row) = review_source_row(side, display_row)
if let Some(source_row) = view
.review_row(side, display_row)
.filter(|source_row| anchor.rows().contains(source_row))
{
let start = if source_row == anchor.start.row {
@@ -4534,9 +4544,11 @@ fn draw_diff_pane(
crate::review::Side::Before => Focus::Before,
crate::review::Side::After => Focus::After,
};
let selected = diff.local_review.selected == Some(comment.id);
for (screen, display, _) in diff_screen_rows(diff, top, content.height, wrapped) {
let source = review_source_row(side, display);
let selected = selected_review == Some(comment.id);
for (screen, display, _) in
diff_screen_rows(view.info.display_len, top, content.height, wrapped)
{
let source = view.review_row(side, display);
if wrapped.is_some_and(|rows| rows[screen].position.continuation != 0)
|| !source.is_some_and(|row| comment.anchor.rows().contains(&row))
{
@@ -4687,9 +4699,20 @@ fn diff_source_len(diff: &DiffState, file: &ChangedFile, before: bool) -> usize
}
fn diff_gutter(diff: &DiffState, file: &ChangedFile, width: usize, focus: Focus) -> usize {
let old = diff_source_len(diff, file, true).max(1).ilog10() as usize + 1;
let new = diff_source_len(diff, file, false).max(1).ilog10() as usize + 1;
let gutter = if diff.view_mode == ViewMode::Unified {
column_gutter(
diff.view_mode == ViewMode::Unified,
[
diff_source_len(diff, file, true),
diff_source_len(diff, file, false),
],
width,
focus,
)
}
fn column_gutter(unified: bool, extents: [usize; 2], width: usize, focus: Focus) -> usize {
let [old, new] = extents.map(|len| len.max(1).ilog10() as usize + 1);
let gutter = if unified {
old + new + 3
} else {
(if focus == Focus::Before { old } else { new }) + 2
@@ -5103,7 +5126,7 @@ impl DiffWrapViewport {
}
fn diff_screen_rows<'a>(
diff: &'a DiffState,
display_len: usize,
top: usize,
height: u16,
wrapped: Option<&'a [WrappedDiffRow]>,
@@ -5120,7 +5143,7 @@ fn diff_screen_rows<'a>(
)
})
} else {
(top + screen < diff.display_rows.len()).then_some((screen, top + screen, None))
(top + screen < display_len).then_some((screen, top + screen, None))
}
})
}
@@ -5268,7 +5291,7 @@ fn diff_visible_rows(
} else {
pane.height
};
for (_, display, _) in diff_screen_rows(diff, top, height, wrapped) {
for (_, display, _) in diff_screen_rows(diff.display_rows.len(), top, height, wrapped) {
let source =
diff.display_source_row(if before { Focus::Before } else { Focus::After }, display);
if ranges