refactor: hide logical comparison rows from painting
This commit is contained in:
+140
-13
@@ -13,7 +13,7 @@ fn conflict_history_preserves_source_gaps_base_changes_and_native_eof_rows() {
|
||||
]);
|
||||
let rows: Vec<_> = (0..history.row_count())
|
||||
.map(|row| {
|
||||
std::array::from_fn::<_, 3, _>(|column| history.column(column).source_row(row))
|
||||
std::array::from_fn::<_, 3, _>(|column| history.column(column).view().native_row(row))
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(
|
||||
@@ -26,7 +26,8 @@ fn conflict_history_preserves_source_gaps_base_changes_and_native_eof_rows() {
|
||||
]
|
||||
);
|
||||
for column in 0..3 {
|
||||
let projection = history.column(column);
|
||||
let history_column = history.column(column);
|
||||
let projection = history_column.view();
|
||||
for (display, row) in rows.iter().enumerate() {
|
||||
if let Some(source) = row[column] {
|
||||
assert_eq!(projection.display_row(source), Some(display));
|
||||
@@ -34,14 +35,26 @@ fn conflict_history_preserves_source_gaps_base_changes_and_native_eof_rows() {
|
||||
}
|
||||
}
|
||||
let base = history.column(1);
|
||||
let changed = &base.diff.rows[0].changes;
|
||||
let base = base.view();
|
||||
let ColumnRow::Text(line) = base.row(0).unwrap() else {
|
||||
panic!("changed base row must contain text");
|
||||
};
|
||||
assert!(line.changed);
|
||||
for column in [0, 6] {
|
||||
assert!(changed.iter().any(|range| range[0].contains(&column)));
|
||||
assert!(line.changes().any(|range| range.contains(&column)));
|
||||
}
|
||||
assert!(!changed.iter().any(|range| range[0].contains(&4)));
|
||||
assert_eq!(base.diff.rows[2].kind, RowKind::Equal);
|
||||
assert!(base.diff.rows[2].changes.is_empty());
|
||||
assert_eq!(history.column(2).lines[1], "added");
|
||||
assert!(!line.changes().any(|range| range.contains(&4)));
|
||||
let ColumnRow::Text(line) = base.row(2).unwrap() else {
|
||||
panic!("unchanged base row must contain text");
|
||||
};
|
||||
assert!(!line.changed);
|
||||
assert_eq!(line.changes().count(), 0);
|
||||
let right = history.column(2);
|
||||
let ColumnRow::Text(line) = right.view().row(1).unwrap() else {
|
||||
panic!("inserted right row must contain text");
|
||||
};
|
||||
assert_eq!(line.text, Some("added"));
|
||||
assert!(matches!(base.row(1), Some(ColumnRow::Gap)));
|
||||
|
||||
// Rebuilding for a new input/base choice must not retain old source mappings.
|
||||
let history = HistoryComparison::new([
|
||||
@@ -49,17 +62,17 @@ fn conflict_history_preserves_source_gaps_base_changes_and_native_eof_rows() {
|
||||
"abc deg\nadded\nkeep\n",
|
||||
"xbc def\nkeep\n",
|
||||
]);
|
||||
assert_eq!(history.column(1).source_row(1), Some(1));
|
||||
assert_eq!(history.column(0).source_row(1), None);
|
||||
assert_eq!(history.column(2).source_row(1), None);
|
||||
assert_eq!(history.column(1).view().native_row(1), Some(1));
|
||||
assert_eq!(history.column(0).view().native_row(1), None);
|
||||
assert_eq!(history.column(2).view().native_row(1), None);
|
||||
for text in ["", "\n", "same", "same\n"] {
|
||||
let history = HistoryComparison::new([text; 3]);
|
||||
let native_rows = text.split('\n').count();
|
||||
assert_eq!(history.row_count(), native_rows);
|
||||
for column in 0..3 {
|
||||
for row in 0..native_rows {
|
||||
assert_eq!(history.column(column).source_row(row), Some(row));
|
||||
assert_eq!(history.column(column).display_row(row), Some(row));
|
||||
assert_eq!(history.column(column).view().native_row(row), Some(row));
|
||||
assert_eq!(history.column(column).view().display_row(row), Some(row));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1815,6 +1828,120 @@ fn live_state(before: Vec<String>, after: Vec<String>, eof: bool) -> DiffState {
|
||||
state
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_view_preserves_split_gaps_unified_sides_and_folded_coordinates() {
|
||||
let mut state = live_state(
|
||||
["head", "tail"].map(str::to_owned).to_vec(),
|
||||
["head", "added", "tail"].map(str::to_owned).to_vec(),
|
||||
false,
|
||||
);
|
||||
let before = state.column_view(Focus::Before).unwrap();
|
||||
assert!(matches!(before.row(1), Some(ColumnRow::Gap)));
|
||||
assert_eq!(before.native_row(1), None);
|
||||
assert_eq!(before.display_row(1), Some(2));
|
||||
state.set_view_mode(ViewMode::Unified);
|
||||
let before = state.column_view(Focus::Before).unwrap();
|
||||
let ColumnRow::Text(line) = before.row(1).unwrap() else {
|
||||
panic!("unified insertion must paint the after source");
|
||||
};
|
||||
assert_eq!((line.text, line.side, line.source_row), (Some("added"), Focus::After, 1));
|
||||
assert_eq!(line.numbers, [None, Some(1)]);
|
||||
assert!(line.changed);
|
||||
assert_eq!(before.native_row(1), None);
|
||||
assert_eq!(before.review_row(Focus::Before, 1), None);
|
||||
assert_eq!(before.review_row(Focus::After, 1), Some(1));
|
||||
let ColumnRow::Text(line) = before.row(2).unwrap() else {
|
||||
panic!("equal context must contain text");
|
||||
};
|
||||
assert_eq!(line.numbers, [Some(1), Some(2)]);
|
||||
assert_eq!(before.native_row(2), Some(1));
|
||||
assert_eq!(before.review_row(Focus::Before, 2), Some(1));
|
||||
assert_eq!(before.review_row(Focus::After, 2), Some(2));
|
||||
assert_eq!(before.display_row(1), Some(2));
|
||||
for invalid in [before.info.display_len, usize::MAX] {
|
||||
assert!(before.row(invalid).is_none());
|
||||
assert_eq!(before.native_row(invalid), None);
|
||||
assert_eq!(before.review_row(Focus::After, invalid), None);
|
||||
}
|
||||
assert_eq!(before.display_row(usize::MAX), None);
|
||||
|
||||
let lines: Vec<_> = (0..30).map(|row| format!("line {row}")).collect();
|
||||
let mut changed = lines.clone();
|
||||
changed[15] = "changed".into();
|
||||
let mut state = live_state(lines, changed, false);
|
||||
state.set_context_options(0, 1);
|
||||
for mode in [ViewMode::Split, ViewMode::Unified] {
|
||||
state.set_view_mode(mode);
|
||||
let view = state.column_view(Focus::After).unwrap();
|
||||
assert!(matches!(view.row(0), Some(ColumnRow::Fold { hidden: 15 })));
|
||||
assert_eq!(view.native_row(0), None);
|
||||
assert_eq!(view.review_row(Focus::After, 0), None);
|
||||
assert_eq!(view.display_row(0), None);
|
||||
let display = view.display_row(15).unwrap();
|
||||
let ColumnRow::Text(line) = view.row(display).unwrap() else {
|
||||
panic!("visible changed source must contain text");
|
||||
};
|
||||
assert_eq!((line.text, line.source_row), (Some("changed"), 15));
|
||||
assert_eq!(view.native_row(display), Some(15));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_view_distinguishes_native_terminal_rows_from_missing_ordinary_text() {
|
||||
for text in ["", "same\n"] {
|
||||
let lines: Vec<_> = text.split('\n').map(str::to_owned).collect();
|
||||
let mut state = DiffState::new();
|
||||
state.set_resolver_text(&lines, &lines);
|
||||
for focus in [Focus::Before, Focus::After] {
|
||||
let view = state.column_view(focus).unwrap();
|
||||
let native = lines.len() - 1;
|
||||
let display = view.display_row(native).unwrap();
|
||||
let ColumnRow::Text(line) = view.row(display).unwrap() else {
|
||||
panic!("native terminal row must contain text");
|
||||
};
|
||||
assert_eq!((line.text, line.source_row), (Some(""), native));
|
||||
assert_eq!(line.newline, text.ends_with('\n'));
|
||||
assert_eq!(view.native_row(display), Some(native));
|
||||
assert_eq!(view.info.source_extents, [lines.len(); 2]);
|
||||
assert_eq!(view.info.stored_lengths, [lines.len() - 1; 2]);
|
||||
}
|
||||
}
|
||||
let mut state = live_state(vec!["old".into()], vec!["new".into()], false);
|
||||
// A mapped but unavailable ordinary source is not a synthetic terminal row.
|
||||
state.comparison.as_mut().unwrap().files[0].after.clear();
|
||||
let view = state.column_view(Focus::After).unwrap();
|
||||
let ColumnRow::Text(line) = view.row(0).unwrap() else {
|
||||
panic!("missing source text must retain its mapped row");
|
||||
};
|
||||
assert_eq!(line.text, None);
|
||||
assert_eq!(line.source_row, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_view_suppresses_whole_file_emphasis_without_losing_change_markers() {
|
||||
let mut state = live_state(vec!["abc".into()], vec!["aXYbc".into()], false);
|
||||
for (focus, expected) in [(Focus::Before, 1..1), (Focus::After, 1..3)] {
|
||||
let view = state.column_view(focus).unwrap();
|
||||
let ColumnRow::Text(line) = view.row(0).unwrap() else {
|
||||
panic!("changed row must contain text");
|
||||
};
|
||||
assert!(line.changed && line.emphasize);
|
||||
assert_eq!(line.changes().cloned().collect::<Vec<_>>(), [expected]);
|
||||
}
|
||||
for status in [FileStatus::Added, FileStatus::Removed] {
|
||||
state.comparison.as_mut().unwrap().files[0].status = status;
|
||||
for focus in [Focus::Before, Focus::After] {
|
||||
let view = state.column_view(focus).unwrap();
|
||||
let ColumnRow::Text(line) = view.row(0).unwrap() else {
|
||||
panic!("whole-file status must retain source text");
|
||||
};
|
||||
assert!(line.changed);
|
||||
assert!(!line.emphasize);
|
||||
assert_eq!(line.changes().count(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn working_refresh_preserves_noops_errors_and_view_state_but_invalidates_old_identity() {
|
||||
let mut state = live_state(vec!["old".into()], vec!["new".into()], false);
|
||||
|
||||
+3
-2
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::diff::RowKind;
|
||||
use crate::plugin::TextPosition;
|
||||
use std::{
|
||||
fs,
|
||||
@@ -2403,7 +2404,7 @@ fn diff_scope_labels_identify_multiple_scopes_and_sticky_renamed_sides() {
|
||||
assert_eq!(rows[0].scope, [true; 2]);
|
||||
assert_eq!(rows[1].scope, [false; 2]);
|
||||
assert_eq!(rows[0].position, rows[1].position);
|
||||
let first = diff_screen_rows(&diff, diff.top, 48, Some(rows))
|
||||
let first = diff_screen_rows(diff.display_rows.len(), diff.top, 48, Some(rows))
|
||||
.next()
|
||||
.unwrap();
|
||||
assert_eq!((first.0, first.1), (1, diff.top));
|
||||
@@ -3054,7 +3055,7 @@ fn local_review_highlights_exact_graphemes_newlines_clips_and_wrap_continuations
|
||||
let gutter = diff_gutter(&diff, file, content.width as usize, focus);
|
||||
let width = content.width as usize - gutter;
|
||||
for (screen, display, segments) in
|
||||
diff_screen_rows(&diff, 0, content.height, wrapped)
|
||||
diff_screen_rows(diff.display_rows.len(), 0, content.height, wrapped)
|
||||
{
|
||||
let mut expected = vec![false; width];
|
||||
if let DisplayRow::Line(aligned) = diff.display_rows[display] {
|
||||
|
||||
Reference in New Issue
Block a user