feat: add configurable diff modes and split orientation
This commit is contained in:
+276
-71
@@ -34,7 +34,9 @@ use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::{
|
||||
chrome::{ChromeLine, PopupAnchor, SegmentConfig},
|
||||
diff::{ChangedFile, DiffState, DisplayRow, FileStatus, Focus, PaneLayout, Phase, RowKind},
|
||||
diff::{
|
||||
ChangedFile, DiffState, DisplayRow, FileStatus, Focus, PaneLayout, Phase, RowKind, ViewMode,
|
||||
},
|
||||
editor::{
|
||||
Completion, Cursor, Editor, Hover, Input, Mode, Palette, Selection, StartupDiagnostic,
|
||||
},
|
||||
@@ -2041,6 +2043,9 @@ struct DiffGeometry {
|
||||
viewport: Option<(u64, usize, usize, usize, usize)>,
|
||||
text_widths: [u16; 2],
|
||||
wrapping: bool,
|
||||
view_mode: ViewMode,
|
||||
split_axis: SplitAxis,
|
||||
caret_row: Option<usize>,
|
||||
}
|
||||
|
||||
fn diff_panel(
|
||||
@@ -2270,14 +2275,7 @@ fn paint_diff_editor(
|
||||
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 DisplayRow::Line(aligned) = diff.display_rows[display] else {
|
||||
continue;
|
||||
};
|
||||
let Some(source) = (match focus {
|
||||
Focus::Before => diff.rows[aligned].before,
|
||||
Focus::After => diff.rows[aligned].after,
|
||||
Focus::Files => None,
|
||||
}) else {
|
||||
let Some(source) = diff.display_source_row(focus, display) else {
|
||||
continue;
|
||||
};
|
||||
let mut line_area = Rect::new(area.x, area.y + screen as u16, area.width, 1);
|
||||
@@ -2524,6 +2522,9 @@ fn draw_diff(
|
||||
viewport: None,
|
||||
text_widths: [0; 2],
|
||||
wrapping: diff.word_wrap,
|
||||
view_mode: diff.view_mode,
|
||||
split_axis: diff.split_axis,
|
||||
caret_row: None,
|
||||
});
|
||||
if let Some(editor) = editor {
|
||||
draw_diff_popups(frame, editor, area, None, panels);
|
||||
@@ -2572,10 +2573,15 @@ fn draw_diff(
|
||||
(workspace, None)
|
||||
};
|
||||
let pane_layout = diff.pane_layout();
|
||||
let [before_area, after_area] = match pane_layout {
|
||||
PaneLayout::Split => split_rect(pane_rect(panes), SplitAxis::Vertical).map(terminal_rect),
|
||||
PaneLayout::Before | PaneLayout::Empty => [panes, Rect::default()],
|
||||
PaneLayout::After => [Rect::default(), panes],
|
||||
let unified = diff.view_mode == ViewMode::Unified;
|
||||
let [before_area, after_area] = if unified {
|
||||
[panes, panes]
|
||||
} else {
|
||||
match pane_layout {
|
||||
PaneLayout::Split => split_rect(pane_rect(panes), diff.split_axis).map(terminal_rect),
|
||||
PaneLayout::Before | PaneLayout::Empty => [panes, Rect::default()],
|
||||
PaneLayout::After => [Rect::default(), panes],
|
||||
}
|
||||
};
|
||||
let mut document_caret = None;
|
||||
let mut top = diff.top;
|
||||
@@ -2586,7 +2592,9 @@ fn draw_diff(
|
||||
.previous_diff
|
||||
.as_ref()
|
||||
.filter(|previous| {
|
||||
previous.wrapping != wrapping
|
||||
(previous.wrapping != wrapping
|
||||
|| previous.view_mode != diff.view_mode
|
||||
|| previous.split_axis != diff.split_axis)
|
||||
&& previous.viewport.is_some_and(|(id, file, _, _, _)| {
|
||||
Some(id) == diff.comparison_id && file == diff.file
|
||||
})
|
||||
@@ -2594,6 +2602,9 @@ fn draw_diff(
|
||||
.and_then(|previous| {
|
||||
let editor = editor?;
|
||||
let focus = editor.diff_buffer_focus()?;
|
||||
if previous.view_mode != diff.view_mode || previous.split_axis != diff.split_axis {
|
||||
return previous.caret_row;
|
||||
}
|
||||
let cursor = editor.cursor();
|
||||
if previous.wrapping {
|
||||
let file = selected?;
|
||||
@@ -2635,7 +2646,7 @@ fn draw_diff(
|
||||
Focus::After => (after_area, &mut after_left),
|
||||
Focus::Files => unreachable!("native diff buffers belong to a text pane"),
|
||||
};
|
||||
let height = usize::from(pane.height.saturating_sub(2));
|
||||
let height = usize::from(diff_inner(pane).height);
|
||||
if let Some(row) = diff.source_display_row(focus, editor.cursor().row) {
|
||||
if let Some(anchor) = toggle_row {
|
||||
top = row.saturating_sub(anchor.min(height.saturating_sub(1)));
|
||||
@@ -2651,14 +2662,10 @@ fn draw_diff(
|
||||
Focus::After => file.after.len(),
|
||||
Focus::Files => unreachable!("native buffer belongs to a text pane"),
|
||||
});
|
||||
let digits = source_len.max(1).ilog10() as usize + 1;
|
||||
let content_width = usize::from(pane.width.saturating_sub(2));
|
||||
let gutter = if content_width > digits + 2 {
|
||||
digits + 2
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let width = content_width.saturating_sub(gutter);
|
||||
let width = selected.map_or_else(
|
||||
|| diff_text_width(pane, source_len),
|
||||
|file| diff_pane_text_width(diff, file, pane, focus),
|
||||
);
|
||||
let column = display_column(&editor.lines()[editor.cursor().row], editor.cursor().column);
|
||||
if column < *left {
|
||||
*left = column;
|
||||
@@ -2721,15 +2728,18 @@ fn draw_diff(
|
||||
if pane_layout == PaneLayout::Empty {
|
||||
diff_pane_panel(frame, diff, Some(file), before_area, true);
|
||||
} else {
|
||||
if before_area != Rect::default() {
|
||||
if before_area != Rect::default() && (!unified || diff.focus == Focus::Before) {
|
||||
let text_area = draw_diff_pane(
|
||||
frame,
|
||||
diff,
|
||||
file,
|
||||
before_area,
|
||||
true,
|
||||
&syntax.before,
|
||||
editor.map_or(&[], |editor| editor.diff_semantic_tokens(Focus::Before)),
|
||||
[&syntax.before, &syntax.after],
|
||||
[
|
||||
editor.map_or(&[], |editor| editor.diff_semantic_tokens(Focus::Before)),
|
||||
editor.map_or(&[], |editor| editor.diff_semantic_tokens(Focus::After)),
|
||||
],
|
||||
(
|
||||
top,
|
||||
&mut before_left,
|
||||
@@ -2754,15 +2764,18 @@ fn draw_diff(
|
||||
.map(|position| (text_area, position));
|
||||
}
|
||||
}
|
||||
if after_area != Rect::default() {
|
||||
if after_area != Rect::default() && (!unified || diff.focus != Focus::Before) {
|
||||
let text_area = draw_diff_pane(
|
||||
frame,
|
||||
diff,
|
||||
file,
|
||||
after_area,
|
||||
false,
|
||||
&syntax.after,
|
||||
editor.map_or(&[], |editor| editor.diff_semantic_tokens(Focus::After)),
|
||||
[&syntax.before, &syntax.after],
|
||||
[
|
||||
editor.map_or(&[], |editor| editor.diff_semantic_tokens(Focus::Before)),
|
||||
editor.map_or(&[], |editor| editor.diff_semantic_tokens(Focus::After)),
|
||||
],
|
||||
(
|
||||
top,
|
||||
&mut after_left,
|
||||
@@ -2804,14 +2817,32 @@ fn draw_diff(
|
||||
panels.diff = Some(DiffGeometry {
|
||||
regions: [
|
||||
(navigator.unwrap_or_default(), Focus::Files),
|
||||
(before_area, Focus::Before),
|
||||
(after_area, Focus::After),
|
||||
(
|
||||
if unified && diff.focus != Focus::Before {
|
||||
Rect::default()
|
||||
} else {
|
||||
before_area
|
||||
},
|
||||
Focus::Before,
|
||||
),
|
||||
(
|
||||
if unified && diff.focus == Focus::Before {
|
||||
Rect::default()
|
||||
} else {
|
||||
after_area
|
||||
},
|
||||
Focus::After,
|
||||
),
|
||||
],
|
||||
viewport: diff
|
||||
.comparison_id
|
||||
.map(|id| (id, diff.file, top, before_left, after_left)),
|
||||
text_widths,
|
||||
wrapping,
|
||||
view_mode: diff.view_mode,
|
||||
split_axis: diff.split_axis,
|
||||
caret_row: document_caret
|
||||
.map(|(text_area, caret)| usize::from(caret.y.saturating_sub(text_area.y))),
|
||||
});
|
||||
if let Some(editor) = editor {
|
||||
draw_diff_popups(frame, editor, area, document_caret, panels);
|
||||
@@ -2840,7 +2871,17 @@ fn diff_pane_panel(
|
||||
let title = file.map_or_else(
|
||||
|| " Diff ".into(),
|
||||
|file| {
|
||||
let label = if before { "Before" } else { "After" };
|
||||
let label = if diff.view_mode == ViewMode::Unified {
|
||||
if before {
|
||||
"Unified · Before"
|
||||
} else {
|
||||
"Unified · After"
|
||||
}
|
||||
} else if before {
|
||||
"Before"
|
||||
} else {
|
||||
"After"
|
||||
};
|
||||
let path = if before {
|
||||
file.old_path.as_ref().unwrap_or(&file.path)
|
||||
} else {
|
||||
@@ -2857,7 +2898,7 @@ fn diff_pane_panel(
|
||||
.comparison
|
||||
.as_ref()
|
||||
.map_or_else(String::new, |comparison| {
|
||||
if diff.pane_layout() == PaneLayout::Split {
|
||||
if diff.view_mode == ViewMode::Split && diff.pane_layout() == PaneLayout::Split {
|
||||
format!(
|
||||
" {} ",
|
||||
display_text(if before {
|
||||
@@ -2890,8 +2931,8 @@ fn draw_diff_pane(
|
||||
file: &ChangedFile,
|
||||
area: Rect,
|
||||
before: bool,
|
||||
syntax: &SyntaxCache,
|
||||
semantic: &[SemanticToken],
|
||||
syntax: [&SyntaxCache; 2],
|
||||
semantic: [&[SemanticToken]; 2],
|
||||
viewport: (usize, &mut usize, Option<u16>),
|
||||
wrapped: Option<&[WrappedDiffRow]>,
|
||||
colors: DiffColors,
|
||||
@@ -2915,14 +2956,9 @@ fn draw_diff_pane(
|
||||
);
|
||||
return content;
|
||||
}
|
||||
let source = if before { &file.before } else { &file.after };
|
||||
let unified = diff.view_mode == ViewMode::Unified;
|
||||
let (top, left, previous_width) = viewport;
|
||||
let digits = source.len().max(1).ilog10() as usize + 1;
|
||||
let gutter = if usize::from(content.width) > digits + 2 {
|
||||
digits + 2
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let gutter = diff_gutter(diff, file, usize::from(content.width), focus);
|
||||
let width = usize::from(content.width).saturating_sub(gutter);
|
||||
if wrapped.is_none() && previous_width.is_some_and(|previous| usize::from(previous) != width) {
|
||||
*left = diff.bound_horizontal(focus, *left, width);
|
||||
@@ -2946,7 +2982,16 @@ fn draw_diff_pane(
|
||||
}
|
||||
};
|
||||
let row = &diff.rows[aligned_row];
|
||||
let index = if before { row.before } else { row.after };
|
||||
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::Rgb(72, 72, 72)).bg(Color::Reset);
|
||||
let buffer = frame.buffer_mut();
|
||||
@@ -3013,14 +3058,47 @@ fn draw_diff_pane(
|
||||
} else {
|
||||
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;
|
||||
match (old, new) {
|
||||
(Some(old), Some(new)) => {
|
||||
format!("{old:>old_digits$} {new:>new_digits$}{marker} ")
|
||||
}
|
||||
(Some(old), None) => format!("{old:>old_digits$} {:>new_digits$}{marker} ", ""),
|
||||
(None, Some(new)) => format!("{:>old_digits$} {new:>new_digits$}{marker} ", ""),
|
||||
(None, None) => unreachable!("displayed diff line has a source"),
|
||||
}
|
||||
} else {
|
||||
let digits = source.len().max(1).ilog10() as usize + 1;
|
||||
format!("{:>digits$}{marker} ", index + 1)
|
||||
};
|
||||
frame.render_widget(
|
||||
Paragraph::new(format!("{:>digits$}{marker} ", index + 1))
|
||||
.style(Style::default().fg(color).bg(background)),
|
||||
Paragraph::new(label).style(Style::default().fg(color).bg(background)),
|
||||
Rect::new(content.x, row_area.y, gutter as u16, 1),
|
||||
);
|
||||
}
|
||||
paint_syntax_line(frame, source_text, syntax.line(index), text_area, left);
|
||||
paint_semantic_line(frame, source_text, semantic, index, text_area, left);
|
||||
paint_syntax_line(
|
||||
frame,
|
||||
source_text,
|
||||
syntax[usize::from(!before)].line(index),
|
||||
text_area,
|
||||
left,
|
||||
);
|
||||
paint_semantic_line(
|
||||
frame,
|
||||
source_text,
|
||||
semantic[usize::from(!before)],
|
||||
index,
|
||||
text_area,
|
||||
left,
|
||||
);
|
||||
if !matches!(file.status, FileStatus::Added | FileStatus::Removed) {
|
||||
let style = Style::default().bg(colors.character[usize::from(!before)]);
|
||||
for change in &row.changes {
|
||||
@@ -3122,6 +3200,7 @@ struct WrappedDiffRow {
|
||||
#[derive(Default)]
|
||||
struct DiffWrapViewport {
|
||||
key: Option<(u64, usize, Option<u64>)>,
|
||||
view_mode: Option<ViewMode>,
|
||||
revision: u64,
|
||||
before: crate::wrap::WrapCache,
|
||||
after: crate::wrap::WrapCache,
|
||||
@@ -3144,6 +3223,22 @@ fn diff_text_width(area: Rect, source_len: usize) -> usize {
|
||||
width.saturating_sub(if width > digits + 2 { digits + 2 } else { 0 })
|
||||
}
|
||||
|
||||
fn diff_gutter(diff: &DiffState, file: &ChangedFile, width: usize, focus: Focus) -> usize {
|
||||
let old = file.before.len().max(1).ilog10() as usize + 1;
|
||||
let new = file.after.len().max(1).ilog10() as usize + 1;
|
||||
let gutter = if diff.view_mode == ViewMode::Unified {
|
||||
old + new + 3
|
||||
} else {
|
||||
(if focus == Focus::Before { old } else { new }) + 2
|
||||
};
|
||||
if width > gutter { gutter } else { 0 }
|
||||
}
|
||||
|
||||
fn diff_pane_text_width(diff: &DiffState, file: &ChangedFile, area: Rect, focus: Focus) -> usize {
|
||||
let width = usize::from(diff_inner(area).width);
|
||||
width.saturating_sub(diff_gutter(diff, file, width, focus))
|
||||
}
|
||||
|
||||
impl DiffWrapViewport {
|
||||
fn row(
|
||||
&mut self,
|
||||
@@ -3153,9 +3248,12 @@ impl DiffWrapViewport {
|
||||
) -> WrappedDiffRow {
|
||||
let mut segments = [None; 2];
|
||||
let mut final_segments = [false; 2];
|
||||
if let Some(DisplayRow::Line(index)) = diff.display_rows.get(position.display) {
|
||||
let aligned = &diff.rows[*index];
|
||||
for (side, source) in [aligned.before, aligned.after].into_iter().enumerate() {
|
||||
if matches!(
|
||||
diff.display_rows.get(position.display),
|
||||
Some(DisplayRow::Line(_))
|
||||
) {
|
||||
for (side, focus) in [Focus::Before, Focus::After].into_iter().enumerate() {
|
||||
let source = diff.display_source_row(focus, position.display);
|
||||
if let Some(source) = source.filter(|_| self.widths[side] > 0) {
|
||||
let (cache, lines) = if side == 0 {
|
||||
(&mut self.before, &file.before)
|
||||
@@ -3177,12 +3275,12 @@ impl DiffWrapViewport {
|
||||
}
|
||||
|
||||
fn height(&mut self, diff: &DiffState, file: &ChangedFile, display: usize) -> usize {
|
||||
let Some(DisplayRow::Line(index)) = diff.display_rows.get(display) else {
|
||||
if !matches!(diff.display_rows.get(display), Some(DisplayRow::Line(_))) {
|
||||
return 1;
|
||||
};
|
||||
let aligned = &diff.rows[*index];
|
||||
}
|
||||
let mut height = 1;
|
||||
for (side, source) in [aligned.before, aligned.after].into_iter().enumerate() {
|
||||
for (side, focus) in [Focus::Before, Focus::After].into_iter().enumerate() {
|
||||
let source = diff.display_source_row(focus, display);
|
||||
if let Some(source) = source.filter(|_| self.widths[side] > 0) {
|
||||
let (cache, lines) = if side == 0 {
|
||||
(&mut self.before, &file.before)
|
||||
@@ -3273,12 +3371,14 @@ impl DiffWrapViewport {
|
||||
let key = Some((id, diff.file, diff.live_revision));
|
||||
if self.key.map(|(id, file, _)| (id, file)) != Some((id, diff.file))
|
||||
|| self.top.display != diff.top
|
||||
|| self.view_mode != Some(diff.view_mode)
|
||||
{
|
||||
self.top = DiffWrapPosition {
|
||||
display: diff.top,
|
||||
continuation: 0,
|
||||
};
|
||||
}
|
||||
self.view_mode = Some(diff.view_mode);
|
||||
if self.key != key {
|
||||
self.key = key;
|
||||
self.revision = self
|
||||
@@ -3287,8 +3387,8 @@ impl DiffWrapViewport {
|
||||
.expect("wrap revisions exhausted");
|
||||
}
|
||||
self.widths = [
|
||||
diff_text_width(areas[0], file.before.len()),
|
||||
diff_text_width(areas[1], file.after.len()),
|
||||
diff_pane_text_width(diff, file, areas[0], Focus::Before),
|
||||
diff_pane_text_width(diff, file, areas[1], Focus::After),
|
||||
];
|
||||
self.before.prepare((diff.file, id), self.widths[0]);
|
||||
self.after
|
||||
@@ -3301,8 +3401,11 @@ impl DiffWrapViewport {
|
||||
.top
|
||||
.continuation
|
||||
.min(self.height(diff, file, self.top.display) - 1);
|
||||
let height = usize::from(diff_inner(areas[0]).height.max(diff_inner(areas[1]).height));
|
||||
self.fill(diff, file, height);
|
||||
let active = usize::from(diff.focus == Focus::After || areas[0].height == 0);
|
||||
let height = usize::from(diff_inner(areas[active]).height);
|
||||
let paint_height =
|
||||
usize::from(diff_inner(areas[0]).height.max(diff_inner(areas[1]).height));
|
||||
self.fill(diff, file, paint_height);
|
||||
if let Some((focus, cursor)) = caret
|
||||
&& let Some(position) = self.caret_position(diff, file, focus, cursor)
|
||||
{
|
||||
@@ -3311,16 +3414,21 @@ impl DiffWrapViewport {
|
||||
for _ in 0..row.min(height.saturating_sub(1)) {
|
||||
self.top = self.step(diff, file, self.top, false);
|
||||
}
|
||||
self.fill(diff, file, height);
|
||||
self.fill(diff, file, paint_height);
|
||||
} else if position < self.top {
|
||||
self.top = position;
|
||||
self.fill(diff, file, height);
|
||||
} else if !self.rows.iter().any(|row| row.position == position) {
|
||||
self.fill(diff, file, paint_height);
|
||||
} else if !self
|
||||
.rows
|
||||
.iter()
|
||||
.take(height)
|
||||
.any(|row| row.position == position)
|
||||
{
|
||||
self.top = position;
|
||||
for _ in 1..height {
|
||||
self.top = self.step(diff, file, self.top, false);
|
||||
}
|
||||
self.fill(diff, file, height);
|
||||
self.fill(diff, file, paint_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3433,15 +3541,9 @@ fn diff_visible_rows(
|
||||
let count = wrapped.map_or(usize::from(height), |rows| {
|
||||
rows.last().map_or(0, |row| row.position.display - top + 1)
|
||||
});
|
||||
for row in diff.display_rows.iter().skip(top).take(count) {
|
||||
let DisplayRow::Line(index) = *row else {
|
||||
continue;
|
||||
};
|
||||
let source = if before {
|
||||
diff.rows[index].before
|
||||
} else {
|
||||
diff.rows[index].after
|
||||
};
|
||||
for display in top..top.saturating_add(count).min(diff.display_rows.len()) {
|
||||
let source =
|
||||
diff.display_source_row(if before { Focus::Before } else { Focus::After }, display);
|
||||
if let Some(source) = source {
|
||||
if let Some(last) = ranges.last_mut()
|
||||
&& last.end == source
|
||||
@@ -8068,6 +8170,109 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn horizontal_diff_split_keeps_after_caret_in_smaller_bottom_pane() {
|
||||
let before = (0..35)
|
||||
.map(|row| format!("old{row}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let after = before.replace("old", "new");
|
||||
let mut editor = readonly_diff_editor(syntax_diff_file("axis.txt", &before, &after));
|
||||
editor.handle(Input::Char('\t'));
|
||||
editor.handle(Input::Char('\t'));
|
||||
for _ in 0..20 {
|
||||
editor.handle(Input::Down);
|
||||
}
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(100, 17)).unwrap();
|
||||
let mut views = BufferViewports::default();
|
||||
render_native_diff(&mut terminal, &mut views, &mut editor);
|
||||
editor.execute("diff.orientation.toggle");
|
||||
let caret = render_native_diff(&mut terminal, &mut views, &mut editor);
|
||||
let geometry = views.panels.diff.as_ref().unwrap();
|
||||
assert_eq!(geometry.regions[1].0.x, geometry.regions[2].0.x);
|
||||
assert!(geometry.regions[1].0.bottom() <= geometry.regions[2].0.y);
|
||||
assert!(diff_inner(geometry.regions[2].0).contains(caret));
|
||||
editor.execute("word_wrap.toggle");
|
||||
let caret = render_native_diff(&mut terminal, &mut views, &mut editor);
|
||||
assert!(diff_inner(views.panels.diff.as_ref().unwrap().regions[2].0).contains(caret));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unified_diff_renders_ordered_coordinates_and_switches_logical_focus() {
|
||||
let mut file = syntax_diff_file("unified.txt", "same\nold\nend", "same\nnew\nend");
|
||||
file.hunks = crate::diff::line_hunks(&file.before, false, &file.after, false);
|
||||
let mut editor = readonly_diff_editor(file);
|
||||
editor.execute("diff.view.toggle");
|
||||
editor.handle(Input::Char('\t'));
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(100, 20)).unwrap();
|
||||
let mut views = BufferViewports::default();
|
||||
render_native_diff(&mut terminal, &mut views, &mut editor);
|
||||
let geometry = views.panels.diff.as_ref().unwrap();
|
||||
assert_eq!(geometry.regions[2].0, Rect::default());
|
||||
let area = diff_inner(geometry.regions[1].0);
|
||||
let buffer = terminal.backend().buffer();
|
||||
let lines = (area.y..area.bottom())
|
||||
.map(|y| {
|
||||
(area.x..area.right())
|
||||
.map(|x| buffer[(x, y)].symbol())
|
||||
.collect::<String>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let tokens: Vec<Vec<&str>> = lines[..4]
|
||||
.iter()
|
||||
.map(|line| line.split_whitespace().collect())
|
||||
.collect();
|
||||
assert_eq!(
|
||||
tokens,
|
||||
[
|
||||
vec!["1", "1", "same"],
|
||||
vec!["2", "-", "old"],
|
||||
vec!["2+", "new"],
|
||||
vec!["3", "3", "end"]
|
||||
]
|
||||
);
|
||||
assert!(!lines.iter().any(|line| line.contains('╱')));
|
||||
editor.handle(Input::Char('\t'));
|
||||
render_native_diff(&mut terminal, &mut views, &mut editor);
|
||||
let geometry = views.panels.diff.as_ref().unwrap();
|
||||
assert_eq!(geometry.regions[1].0, Rect::default());
|
||||
assert_eq!(diff_inner(geometry.regions[2].0), area);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unified_wrapping_uses_only_the_display_rows_source_side() {
|
||||
let before = format!("{}\nend", "old ".repeat(40));
|
||||
let mut file = syntax_diff_file("wrap.txt", &before, "new\nend");
|
||||
file.hunks = crate::diff::line_hunks(&file.before, false, &file.after, false);
|
||||
let mut editor = readonly_diff_editor(file);
|
||||
editor.execute("diff.view.toggle");
|
||||
editor.execute("word_wrap.toggle");
|
||||
editor.handle(Input::Char('\t'));
|
||||
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(100, 30)).unwrap();
|
||||
let mut views = BufferViewports::default();
|
||||
render_native_diff(&mut terminal, &mut views, &mut editor);
|
||||
let wrap = views.panels.diff_wrap.as_ref().unwrap();
|
||||
let removed = wrap
|
||||
.rows
|
||||
.iter()
|
||||
.filter(|row| row.position.display == 0)
|
||||
.collect::<Vec<_>>();
|
||||
assert!(removed.len() > 1);
|
||||
assert!(
|
||||
removed
|
||||
.iter()
|
||||
.all(|row| row.segments[0].is_some() && row.segments[1].is_none())
|
||||
);
|
||||
let added = wrap
|
||||
.rows
|
||||
.iter()
|
||||
.filter(|row| row.position.display == 1)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(added.len(), 1);
|
||||
assert!(added[0].segments[0].is_none());
|
||||
assert!(added[0].segments[1].is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapped_diff_aligns_continuations_and_preserves_native_selection_and_caret() {
|
||||
let before = format!("// {}BEFORE_END\nsentinel", "old ".repeat(70));
|
||||
|
||||
Reference in New Issue
Block a user