refactor: deepen conflict History alignment ownership
This commit is contained in:
+113
@@ -240,6 +240,119 @@ pub enum DisplayRow {
|
||||
Fold { start: usize, end: usize },
|
||||
}
|
||||
|
||||
/// Immutable left/base/right conflict History, aligned at shared base rows.
|
||||
pub(crate) struct HistoryComparison {
|
||||
columns: [DiffState; 3],
|
||||
}
|
||||
|
||||
/// A read-only projection for the existing diff painter.
|
||||
pub(crate) struct HistoryColumn<'a> {
|
||||
pub diff: &'a DiffState,
|
||||
pub file: &'a ChangedFile,
|
||||
pub focus: Focus,
|
||||
pub lines: &'a [String],
|
||||
}
|
||||
|
||||
impl HistoryColumn<'_> {
|
||||
pub fn source_row(&self, display: usize) -> Option<usize> {
|
||||
self.diff.display_source_row(self.focus, display)
|
||||
}
|
||||
|
||||
pub fn display_row(&self, source: usize) -> Option<usize> {
|
||||
self.diff.source_display_row(self.focus, source)
|
||||
}
|
||||
}
|
||||
|
||||
impl HistoryComparison {
|
||||
pub fn new(sources: [&str; 3]) -> Self {
|
||||
let [left, base, right] =
|
||||
sources.map(|text| text.split('\n').map(str::to_owned).collect::<Vec<_>>());
|
||||
let mut columns: [DiffState; 3] = std::array::from_fn(|_| DiffState::new());
|
||||
columns[0].set_resolver_text(&base, &left);
|
||||
columns[1].set_resolver_text(&base, &base);
|
||||
columns[2].set_resolver_text(&base, &right);
|
||||
let mut left = std::mem::take(&mut columns[0].rows).into_iter().peekable();
|
||||
let mut right = std::mem::take(&mut columns[2].rows).into_iter().peekable();
|
||||
columns[1].rows.clear();
|
||||
// Stitch the existing pairwise alignments at their shared base rows.
|
||||
while left.peek().is_some() || right.peek().is_some() {
|
||||
let insertion = left.peek().is_some_and(|row| row.before.is_none())
|
||||
|| right.peek().is_some_and(|row| row.before.is_none());
|
||||
let take_left = left
|
||||
.peek()
|
||||
.is_some_and(|row| !insertion || row.before.is_none());
|
||||
let take_right = right
|
||||
.peek()
|
||||
.is_some_and(|row| !insertion || row.before.is_none());
|
||||
let empty = || AlignedRow {
|
||||
before: None,
|
||||
after: None,
|
||||
kind: RowKind::Equal,
|
||||
changes: Vec::new(),
|
||||
};
|
||||
let left_row = if take_left {
|
||||
left.next().unwrap()
|
||||
} else {
|
||||
empty()
|
||||
};
|
||||
let right_row = if take_right {
|
||||
right.next().unwrap()
|
||||
} else {
|
||||
empty()
|
||||
};
|
||||
let base = left_row.before.or(right_row.before);
|
||||
let changed = left_row.kind != RowKind::Equal || right_row.kind != RowKind::Equal;
|
||||
let changes = left_row
|
||||
.changes
|
||||
.iter()
|
||||
.chain(&right_row.changes)
|
||||
.map(|change| [change[0].clone(), change[0].clone()])
|
||||
.collect();
|
||||
columns[1].rows.push(AlignedRow {
|
||||
before: base,
|
||||
after: base,
|
||||
kind: if changed {
|
||||
RowKind::Changed
|
||||
} else {
|
||||
RowKind::Equal
|
||||
},
|
||||
changes,
|
||||
});
|
||||
columns[0].rows.push(left_row);
|
||||
columns[2].rows.push(right_row);
|
||||
}
|
||||
for column in &mut columns {
|
||||
column.display_rows = (0..column.rows.len()).map(DisplayRow::Line).collect();
|
||||
}
|
||||
Self { columns }
|
||||
}
|
||||
|
||||
pub fn row_count(&self) -> usize {
|
||||
self.columns[0].rows.len()
|
||||
}
|
||||
|
||||
pub fn column(&self, index: usize) -> HistoryColumn<'_> {
|
||||
let diff = &self.columns[index];
|
||||
let file = &diff.comparison.as_ref().unwrap().files[0];
|
||||
let focus = if index == 1 {
|
||||
Focus::Before
|
||||
} else {
|
||||
Focus::After
|
||||
};
|
||||
let lines = if focus == Focus::Before {
|
||||
&file.before
|
||||
} else {
|
||||
&file.after
|
||||
};
|
||||
HistoryColumn {
|
||||
diff,
|
||||
file,
|
||||
focus,
|
||||
lines,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum RequestKind {
|
||||
Catalog,
|
||||
|
||||
+71
-153
@@ -41,8 +41,8 @@ use unicode_width::UnicodeWidthStr;
|
||||
use crate::{
|
||||
chrome::{ChromeLine, PopupAnchor, SegmentConfig},
|
||||
diff::{
|
||||
AlignedRow, ChangedFile, DiffState, DisplayRow, FileStatus, Focus, PaneLayout, Phase,
|
||||
RowKind, ViewMode,
|
||||
ChangedFile, DiffState, DisplayRow, FileStatus, Focus, HistoryComparison, PaneLayout,
|
||||
Phase, RowKind, ViewMode,
|
||||
},
|
||||
editor::{
|
||||
Completion, Cursor, Editor, Hover, Input, Mode, Palette, ResolverLayout, ResolverStatus,
|
||||
@@ -352,7 +352,7 @@ fn history_key(resolver: &ResolverWorkspace) -> Option<HistoryKey> {
|
||||
}
|
||||
|
||||
struct HistoryDiff {
|
||||
columns: [DiffState; 3],
|
||||
comparison: HistoryComparison,
|
||||
top: DiffWrapPosition,
|
||||
left: [usize; 3],
|
||||
widths: [usize; 3],
|
||||
@@ -369,70 +369,14 @@ impl HistoryDiff {
|
||||
index
|
||||
.and_then(|index| file.sources.get(index))
|
||||
.map_or("", |source| source.text.as_str())
|
||||
.split('\n')
|
||||
.map(str::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let base = text(resolver.selected_base);
|
||||
let mut columns: [DiffState; 3] = std::array::from_fn(|_| DiffState::new());
|
||||
columns[0].set_resolver_text(&base, &text(Some(resolver.history_sources[0])));
|
||||
columns[1].set_resolver_text(&base, &base);
|
||||
columns[2].set_resolver_text(&base, &text(Some(resolver.history_sources[1])));
|
||||
let mut left = std::mem::take(&mut columns[0].rows).into_iter().peekable();
|
||||
let mut right = std::mem::take(&mut columns[2].rows).into_iter().peekable();
|
||||
columns[1].rows.clear();
|
||||
// Stitch the existing pairwise alignments at their shared base rows.
|
||||
while left.peek().is_some() || right.peek().is_some() {
|
||||
let insertion = left.peek().is_some_and(|row| row.before.is_none())
|
||||
|| right.peek().is_some_and(|row| row.before.is_none());
|
||||
let take_left = left
|
||||
.peek()
|
||||
.is_some_and(|row| !insertion || row.before.is_none());
|
||||
let take_right = right
|
||||
.peek()
|
||||
.is_some_and(|row| !insertion || row.before.is_none());
|
||||
let empty = || AlignedRow {
|
||||
before: None,
|
||||
after: None,
|
||||
kind: RowKind::Equal,
|
||||
changes: Vec::new(),
|
||||
};
|
||||
let left_row = if take_left {
|
||||
left.next().unwrap()
|
||||
} else {
|
||||
empty()
|
||||
};
|
||||
let right_row = if take_right {
|
||||
right.next().unwrap()
|
||||
} else {
|
||||
empty()
|
||||
};
|
||||
let base = left_row.before.or(right_row.before);
|
||||
let changed = left_row.kind != RowKind::Equal || right_row.kind != RowKind::Equal;
|
||||
let changes = left_row
|
||||
.changes
|
||||
.iter()
|
||||
.chain(&right_row.changes)
|
||||
.map(|change| [change[0].clone(), change[0].clone()])
|
||||
.collect();
|
||||
columns[1].rows.push(AlignedRow {
|
||||
before: base,
|
||||
after: base,
|
||||
kind: if changed {
|
||||
RowKind::Changed
|
||||
} else {
|
||||
RowKind::Equal
|
||||
},
|
||||
changes,
|
||||
});
|
||||
columns[0].rows.push(left_row);
|
||||
columns[2].rows.push(right_row);
|
||||
}
|
||||
for column in &mut columns {
|
||||
column.display_rows = (0..column.rows.len()).map(DisplayRow::Line).collect();
|
||||
}
|
||||
let comparison = HistoryComparison::new([
|
||||
text(Some(resolver.history_sources[0])),
|
||||
text(resolver.selected_base),
|
||||
text(Some(resolver.history_sources[1])),
|
||||
]);
|
||||
Self {
|
||||
columns,
|
||||
comparison,
|
||||
top: DiffWrapPosition::default(),
|
||||
left: [0; 3],
|
||||
widths: [0; 3],
|
||||
@@ -443,28 +387,14 @@ impl HistoryDiff {
|
||||
}
|
||||
}
|
||||
|
||||
fn focus(column: usize) -> Focus {
|
||||
if column == 1 {
|
||||
Focus::Before
|
||||
} else {
|
||||
Focus::After
|
||||
}
|
||||
}
|
||||
|
||||
fn row_height(&mut self, row: usize) -> usize {
|
||||
let mut height = 1;
|
||||
for column in 0..3 {
|
||||
if self.wrapping[column]
|
||||
&& self.widths[column] > 0
|
||||
&& let Some(source) =
|
||||
self.columns[column].display_source_row(Self::focus(column), row)
|
||||
&& let Some(source) = self.comparison.column(column).source_row(row)
|
||||
{
|
||||
let file = &self.columns[column].comparison.as_ref().unwrap().files[0];
|
||||
let lines = if column == 1 {
|
||||
&file.before
|
||||
} else {
|
||||
&file.after
|
||||
};
|
||||
let lines = self.comparison.column(column).lines;
|
||||
height = height.max(
|
||||
self.wrap[column]
|
||||
.segments(source, lines.get(source).map_or("", String::as_str))
|
||||
@@ -479,7 +409,7 @@ impl HistoryDiff {
|
||||
if down {
|
||||
if self.top.continuation + 1 < self.row_height(self.top.display) {
|
||||
self.top.continuation += 1;
|
||||
} else if self.top.display + 1 < self.columns[0].rows.len() {
|
||||
} else if self.top.display + 1 < self.comparison.row_count() {
|
||||
self.top.display += 1;
|
||||
self.top.continuation = 0;
|
||||
}
|
||||
@@ -500,17 +430,10 @@ impl HistoryDiff {
|
||||
for column in 0..3 {
|
||||
let mut segments = [None; 2];
|
||||
let mut final_segments = [false; 2];
|
||||
let side = usize::from(column != 1);
|
||||
if let Some(source) =
|
||||
self.columns[column].display_source_row(Self::focus(column), self.top.display)
|
||||
{
|
||||
let file = &self.columns[column].comparison.as_ref().unwrap().files[0];
|
||||
let lines = if column == 1 {
|
||||
&file.before
|
||||
} else {
|
||||
&file.after
|
||||
};
|
||||
let text = lines.get(source).map_or("", String::as_str);
|
||||
let projection = self.comparison.column(column);
|
||||
let side = usize::from(projection.focus == Focus::After);
|
||||
if let Some(source) = projection.source_row(self.top.display) {
|
||||
let text = projection.lines.get(source).map_or("", String::as_str);
|
||||
if self.wrapping[column] {
|
||||
let parts = self.wrap[column].segments(source, text);
|
||||
segments[side] = parts.get(self.top.continuation).copied();
|
||||
@@ -951,14 +874,13 @@ impl BufferViewports {
|
||||
history.widths = [0; 3];
|
||||
for &(column, pane, content) in panes.iter().flatten() {
|
||||
editor.with_render_pane(pane, |view| {
|
||||
let diff = &history.columns[column];
|
||||
let file = &diff.comparison.as_ref().unwrap().files[0];
|
||||
let projection = history.comparison.column(column);
|
||||
let width = usize::from(content.width);
|
||||
history.widths[column] = width.saturating_sub(diff_gutter(
|
||||
diff,
|
||||
file,
|
||||
projection.diff,
|
||||
projection.file,
|
||||
width,
|
||||
HistoryDiff::focus(column),
|
||||
projection.focus,
|
||||
));
|
||||
history.wrapping[column] = view.word_wrap();
|
||||
history.wrap[column].prepare((view.buffer_id(), 0), history.widths[column]);
|
||||
@@ -989,52 +911,48 @@ 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 diff = &history.columns[column];
|
||||
if let Some(display) =
|
||||
diff.source_display_row(HistoryDiff::focus(column), cursor.row)
|
||||
{
|
||||
let column_offset = display_column(&editor.lines()[cursor.row], cursor.column);
|
||||
let continuation = if history.wrapping[column] {
|
||||
caret_segment(
|
||||
history.wrap[column].segments(cursor.row, &editor.lines()[cursor.row]),
|
||||
column_offset,
|
||||
)
|
||||
} else {
|
||||
history.left[column] = scroll_offset(
|
||||
history.left[column],
|
||||
column_offset,
|
||||
history.widths[column],
|
||||
editor.scroll_distance(),
|
||||
);
|
||||
0
|
||||
};
|
||||
let caret = DiffWrapPosition {
|
||||
display,
|
||||
continuation,
|
||||
};
|
||||
let screen = history.visible[column]
|
||||
.iter()
|
||||
.position(|row| row.position == caret);
|
||||
let height = usize::from(content.height);
|
||||
let margin = scroll_margin(height, editor.scroll_distance());
|
||||
let anchor = if height == 0 {
|
||||
None
|
||||
} else if caret < history.top || screen.is_some_and(|row| row < margin) {
|
||||
Some(margin)
|
||||
} else if screen.is_none_or(|row| row >= height.saturating_sub(margin)) {
|
||||
Some(height.saturating_sub(margin + 1))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(anchor) = anchor {
|
||||
history.top = caret;
|
||||
for _ in 0..anchor {
|
||||
history.step(false);
|
||||
}
|
||||
let column_offset = display_column(&editor.lines()[cursor.row], cursor.column);
|
||||
let continuation = if history.wrapping[column] {
|
||||
caret_segment(
|
||||
history.wrap[column].segments(cursor.row, &editor.lines()[cursor.row]),
|
||||
column_offset,
|
||||
)
|
||||
} else {
|
||||
history.left[column] = scroll_offset(
|
||||
history.left[column],
|
||||
column_offset,
|
||||
history.widths[column],
|
||||
editor.scroll_distance(),
|
||||
);
|
||||
0
|
||||
};
|
||||
let caret = DiffWrapPosition {
|
||||
display,
|
||||
continuation,
|
||||
};
|
||||
let screen = history.visible[column]
|
||||
.iter()
|
||||
.position(|row| row.position == caret);
|
||||
let height = usize::from(content.height);
|
||||
let margin = scroll_margin(height, editor.scroll_distance());
|
||||
let anchor = if height == 0 {
|
||||
None
|
||||
} else if caret < history.top || screen.is_some_and(|row| row < margin) {
|
||||
Some(margin)
|
||||
} else if screen.is_none_or(|row| row >= height.saturating_sub(margin)) {
|
||||
Some(height.saturating_sub(margin + 1))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(anchor) = anchor {
|
||||
history.top = caret;
|
||||
for _ in 0..anchor {
|
||||
history.step(false);
|
||||
}
|
||||
history.fill(height);
|
||||
}
|
||||
history.fill(height);
|
||||
}
|
||||
history.caret = Some(identity);
|
||||
}
|
||||
@@ -1051,12 +969,11 @@ impl BufferViewports {
|
||||
}
|
||||
for (column, pane, content) in panes.into_iter().flatten() {
|
||||
editor.with_render_pane(pane, |view| {
|
||||
let diff = &history.columns[column];
|
||||
let file = &diff.comparison.as_ref().unwrap().files[0];
|
||||
let projection = history.comparison.column(column);
|
||||
let syntax = self.syntax.entry(view.buffer_id()).or_default();
|
||||
let source_rows = history.visible[column].iter().filter_map(|row| {
|
||||
diff.display_source_row(HistoryDiff::focus(column), row.position.display)
|
||||
});
|
||||
let source_rows = history.visible[column]
|
||||
.iter()
|
||||
.filter_map(|row| projection.source_row(row.position.display));
|
||||
let first = source_rows
|
||||
.clone()
|
||||
.min()
|
||||
@@ -1074,10 +991,10 @@ impl BufferViewports {
|
||||
);
|
||||
let text_area = draw_diff_pane(
|
||||
frame,
|
||||
diff,
|
||||
file,
|
||||
projection.diff,
|
||||
projection.file,
|
||||
content,
|
||||
column == 1,
|
||||
projection.focus == Focus::Before,
|
||||
[&*syntax, &*syntax],
|
||||
[view.semantic_tokens(), view.semantic_tokens()],
|
||||
(history.top.display, &mut history.left[column], None),
|
||||
@@ -1095,9 +1012,9 @@ impl BufferViewports {
|
||||
self.panels.caret = paint_diff_editor(
|
||||
frame,
|
||||
view,
|
||||
diff,
|
||||
projection.diff,
|
||||
text_area,
|
||||
HistoryDiff::focus(column),
|
||||
projection.focus,
|
||||
(history.top.display, history.left[column]),
|
||||
Some(&history.visible[column]),
|
||||
)
|
||||
@@ -1194,8 +1111,9 @@ impl BufferViewports {
|
||||
} else {
|
||||
history.left[column].saturating_sub(3)
|
||||
};
|
||||
history.left[column] = history.columns[column].bound_horizontal(
|
||||
HistoryDiff::focus(column),
|
||||
let projection = history.comparison.column(column);
|
||||
history.left[column] = projection.diff.bound_horizontal(
|
||||
projection.focus,
|
||||
left,
|
||||
history.widths[column],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user