feat: show base-relative changes in conflict panes

This commit is contained in:
2026-10-24 23:55:12 +02:00
parent c88582f2ba
commit c1bff63111
9 changed files with 509 additions and 322 deletions
+46 -9
View File
@@ -19,7 +19,8 @@ fn conflict_history_preserves_source_gaps_base_changes_and_native_eof_rows() {
assert_eq!(
rows,
[
[Some(0), Some(0), Some(0)],
[None, Some(0), None],
[Some(0), None, Some(0)],
[None, None, Some(1)],
[Some(1), Some(1), Some(2)],
[Some(2), Some(2), Some(3)],
@@ -44,13 +45,13 @@ fn conflict_history_preserves_source_gaps_base_changes_and_native_eof_rows() {
assert!(line.changes().any(|range| range.contains(&column)));
}
assert!(!line.changes().any(|range| range.contains(&4)));
let ColumnRow::Text(line) = base.row(2).unwrap() else {
let ColumnRow::Text(line) = base.row(3).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 {
let ColumnRow::Text(line) = right.view().row(2).unwrap() else {
panic!("inserted right row must contain text");
};
assert_eq!(line.text, Some("added"));
@@ -62,9 +63,45 @@ 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).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);
assert_eq!(history.column(1).view().native_row(2), Some(1));
assert_eq!(history.column(0).view().native_row(2), None);
assert_eq!(history.column(2).view().native_row(2), None);
// Deletions are visible on either side, but never become editable source rows.
let history = HistoryComparison::new(["keep\n", "old\nkeep\n", "new\nkeep\n"]);
for column in [0, 2] {
let projection = history.column(column).view();
let ColumnRow::Text(line) = projection.row(0).unwrap() else {
panic!("removed base text must remain visible");
};
assert_eq!(line.text, Some("old"));
assert!(line.changed);
assert_eq!(line.polarity, Focus::Before);
assert!(!line.native);
assert_eq!(projection.native_row(0), None);
assert_eq!(projection.text_row(0), Some((usize::MAX, "old")));
}
assert!(matches!(
history.column(0).view().row(1),
Some(ColumnRow::Gap)
));
let right = history.column(2).view();
let ColumnRow::Text(line) = right.row(1).unwrap() else {
panic!("replacement must be an addition");
};
assert_eq!(line.text, Some("new"));
assert_eq!(line.polarity, Focus::After);
assert_eq!(right.native_row(1), Some(0));
assert_eq!(right.display_row(0), Some(1));
let history = HistoryComparison::new(["left", "", "right"]);
for column in [0, 2] {
let projection = history.column(column).view();
let ColumnRow::Text(line) = projection.row(0).unwrap() else {
panic!("add/add without a base must show additions on both sides");
};
assert!(line.changed && line.native);
assert_eq!(line.polarity, Focus::After);
assert_eq!(projection.native_row(0), Some(0));
}
for text in ["", "\n", "same", "same\n"] {
let history = HistoryComparison::new([text; 3]);
let native_rows = text.split('\n').count();
@@ -83,7 +120,7 @@ fn resolver_equal_text_retains_native_rows_and_cached_viewport() {
for text in ["", "\n", "same\n", "same"] {
let lines: Vec<_> = text.split('\n').map(str::to_owned).collect();
let mut state = DiffState::new();
state.set_resolver_text(&lines, &lines);
state.set_resolver_text(&lines, &lines, None);
assert_eq!(state.pane_layout(), PaneLayout::Split);
assert!(state.comparison.as_ref().unwrap().files[0].hunks.is_empty());
assert_eq!(
@@ -99,7 +136,7 @@ fn resolver_equal_text_retains_native_rows_and_cached_viewport() {
state.top = 1;
state.left_before = 3;
state.left_after = 4;
state.set_resolver_text(&lines, &lines);
state.set_resolver_text(&lines, &lines, None);
assert_eq!(state.comparison_id, id);
assert_eq!((state.top, state.left_before, state.left_after), (1, 3, 4));
}
@@ -2407,7 +2444,7 @@ 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);
state.set_resolver_text(&lines, &lines, None);
for focus in [Focus::Before, Focus::After] {
let view = state.column_view(focus).unwrap();
let native = lines.len() - 1;