feat: show base-relative changes in conflict panes
This commit is contained in:
+276
-84
@@ -356,6 +356,9 @@ pub(crate) struct ColumnLine<'a> {
|
||||
pub side: Focus,
|
||||
pub source_row: usize,
|
||||
pub numbers: [Option<usize>; 2],
|
||||
/// Change polarity is independent of the native source/pane.
|
||||
pub polarity: Focus,
|
||||
pub native: bool,
|
||||
pub changed: bool,
|
||||
pub emphasize: bool,
|
||||
pub newline: bool,
|
||||
@@ -409,6 +412,46 @@ impl<'a> ColumnView<'a> {
|
||||
DisplayRow::Line(index) => index,
|
||||
};
|
||||
let row = self.diff.rows.get(index)?;
|
||||
let side = self.diff.display_focus(display).unwrap_or(self.info.focus);
|
||||
if let Some(conflict) = &self.diff.conflict {
|
||||
let Some(line) = &conflict.rows[index][usize::from(side == Focus::After)] else {
|
||||
return Some(ColumnRow::Gap);
|
||||
};
|
||||
let source = if line.removed {
|
||||
&conflict.base
|
||||
} else if side == Focus::Before {
|
||||
&self.file.before
|
||||
} else {
|
||||
&self.file.after
|
||||
};
|
||||
return Some(ColumnRow::Text(ColumnLine {
|
||||
text: source.get(line.source).map(String::as_str).or(Some("")),
|
||||
side,
|
||||
source_row: line.source,
|
||||
numbers: if line.removed {
|
||||
[None; 2]
|
||||
} else {
|
||||
[row.before, row.after]
|
||||
},
|
||||
polarity: if line.removed {
|
||||
Focus::Before
|
||||
} else {
|
||||
Focus::After
|
||||
},
|
||||
native: !line.removed,
|
||||
changed: line.changed,
|
||||
emphasize: true,
|
||||
newline: line.source + 1 < source.len()
|
||||
|| if line.removed {
|
||||
conflict.base.last().is_some_and(String::is_empty)
|
||||
} else if side == Focus::Before {
|
||||
self.file.before_eof
|
||||
} else {
|
||||
self.file.after_eof
|
||||
},
|
||||
changes: Cow::Borrowed(&line.changes),
|
||||
}));
|
||||
}
|
||||
let side = if self.info.unified {
|
||||
self.diff.display_focus(display).unwrap_or(self.info.focus)
|
||||
} else {
|
||||
@@ -436,6 +479,8 @@ impl<'a> ColumnView<'a> {
|
||||
self.diff.display_source_row(Focus::Before, display),
|
||||
self.diff.display_source_row(Focus::After, display),
|
||||
],
|
||||
polarity: side,
|
||||
native: true,
|
||||
changed: row.kind == RowKind::Changed
|
||||
|| (before && row.kind == RowKind::Removed)
|
||||
|| (!before && row.kind == RowKind::Added),
|
||||
@@ -459,6 +504,35 @@ impl<'a> ColumnView<'a> {
|
||||
}))
|
||||
}
|
||||
|
||||
/// Wrapping includes display-only removals without computing character diffs.
|
||||
/// Removed base rows occupy a disjoint cache-key space from native rows.
|
||||
pub fn text_row(&self, display: usize) -> Option<(usize, &'a str)> {
|
||||
let DisplayRow::Line(index) = *self.diff.display_rows.get(display)? else {
|
||||
return None;
|
||||
};
|
||||
let side = self.info.focus;
|
||||
if self
|
||||
.diff
|
||||
.display_focus(display)
|
||||
.is_some_and(|focus| focus != side)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
if let Some(conflict) = &self.diff.conflict {
|
||||
let line = conflict.rows[index][usize::from(side == Focus::After)].as_ref()?;
|
||||
if line.removed {
|
||||
return Some((usize::MAX - line.source, &conflict.base[line.source]));
|
||||
}
|
||||
}
|
||||
let source = self.diff.display_source_row(side, display)?;
|
||||
let lines = if side == Focus::Before {
|
||||
&self.file.before
|
||||
} else {
|
||||
&self.file.after
|
||||
};
|
||||
Some((source, lines.get(source).map_or("", String::as_str)))
|
||||
}
|
||||
|
||||
/// Caret mapping stays on this view's native side, even in unified display.
|
||||
pub fn native_row(&self, display: usize) -> Option<usize> {
|
||||
self.diff.display_source_row(self.info.focus, display)
|
||||
@@ -489,9 +563,105 @@ impl<'a> ColumnView<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Base-relative candidate patches with display-only removed base lines.
|
||||
#[derive(Debug)]
|
||||
struct ConflictChanges {
|
||||
base: Vec<String>,
|
||||
rows: Vec<[Option<ConflictLine>; 2]>,
|
||||
base_rows: Vec<Option<usize>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ConflictLine {
|
||||
source: usize,
|
||||
removed: bool,
|
||||
changed: bool,
|
||||
changes: Vec<[std::ops::Range<usize>; 2]>,
|
||||
}
|
||||
|
||||
impl ConflictChanges {
|
||||
fn new(base: &[String], candidates: [&[String]; 2]) -> Self {
|
||||
let mut pairs = candidates.map(|candidate| {
|
||||
let mut diff = DiffState::new();
|
||||
diff.set_resolver_text(base, candidate, None);
|
||||
diff
|
||||
});
|
||||
for pair in &mut pairs {
|
||||
if pair.lazy_changes {
|
||||
for row in &mut pair.rows {
|
||||
if row.kind == RowKind::Changed {
|
||||
// Large inputs keep the existing whole-line fallback instead of
|
||||
// eagerly running a character diff for every changed row.
|
||||
row.changes = vec![[0..usize::MAX, 0..usize::MAX]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let [left, right] = pairs.map(|pair| pair.rows.into_iter().peekable());
|
||||
let (mut left, mut right) = (left, right);
|
||||
let mut result = Self {
|
||||
base: base.to_vec(),
|
||||
rows: Vec::new(),
|
||||
base_rows: Vec::new(),
|
||||
};
|
||||
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 rows = [&mut left, &mut right].map(|rows| {
|
||||
if rows
|
||||
.peek()
|
||||
.is_some_and(|row| !insertion || row.before.is_none())
|
||||
{
|
||||
rows.next()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let base_row = rows.iter().flatten().find_map(|row| row.before);
|
||||
let removal =
|
||||
base_row.is_some() && rows.iter().flatten().any(|row| row.kind != RowKind::Equal);
|
||||
let line = |row: &AlignedRow, removed: bool| {
|
||||
let source = if removed { row.before } else { row.after }?;
|
||||
let side = usize::from(!removed);
|
||||
Some(ConflictLine {
|
||||
source,
|
||||
removed,
|
||||
changed: row.kind != RowKind::Equal,
|
||||
changes: row
|
||||
.changes
|
||||
.iter()
|
||||
.map(|change| [change[side].clone(), change[side].clone()])
|
||||
.collect(),
|
||||
})
|
||||
};
|
||||
if removal {
|
||||
result.rows.push(std::array::from_fn(|side| {
|
||||
let row = rows[side].as_ref()?;
|
||||
line(row, row.kind != RowKind::Equal)
|
||||
}));
|
||||
result.base_rows.push(base_row);
|
||||
}
|
||||
let additions = std::array::from_fn(|side| {
|
||||
let row = rows[side].as_ref()?;
|
||||
if removal && row.kind == RowKind::Equal {
|
||||
None
|
||||
} else {
|
||||
line(row, false)
|
||||
}
|
||||
});
|
||||
if additions.iter().any(Option::is_some) {
|
||||
result.rows.push(additions);
|
||||
result.base_rows.push(if removal { None } else { base_row });
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
/// Immutable left/base/right conflict History, aligned at shared base rows.
|
||||
pub(crate) struct HistoryComparison {
|
||||
columns: [DiffState; 3],
|
||||
candidates: DiffState,
|
||||
base: DiffState,
|
||||
}
|
||||
|
||||
/// A read-only projection for the existing diff painter.
|
||||
@@ -499,7 +669,6 @@ pub(crate) struct HistoryColumn<'a> {
|
||||
pub diff: &'a DiffState,
|
||||
pub file: &'a ChangedFile,
|
||||
pub focus: Focus,
|
||||
pub lines: &'a [String],
|
||||
}
|
||||
|
||||
impl<'a> HistoryColumn<'a> {
|
||||
@@ -514,92 +683,54 @@ impl<'a> HistoryColumn<'a> {
|
||||
|
||||
impl HistoryComparison {
|
||||
pub fn new(sources: [&str; 3]) -> Self {
|
||||
let [left, base, right] =
|
||||
let [left, base_lines, 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 {
|
||||
let mut candidates = DiffState::new();
|
||||
candidates.set_resolver_text(&left, &right, Some(&base_lines));
|
||||
let mut base = DiffState::new();
|
||||
base.set_resolver_text(&base_lines, &base_lines, None);
|
||||
let conflict = candidates.conflict.as_ref().unwrap();
|
||||
base.rows = conflict
|
||||
.base_rows
|
||||
.iter()
|
||||
.zip(&candidates.rows)
|
||||
.zip(&conflict.rows)
|
||||
.map(|((&source, row), lines)| AlignedRow {
|
||||
before: source,
|
||||
after: source,
|
||||
kind: row.kind,
|
||||
changes: lines
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|line| line.removed)
|
||||
.flat_map(|line| line.changes.iter().cloned())
|
||||
.collect(),
|
||||
})
|
||||
.collect();
|
||||
for column in [&mut candidates, &mut base] {
|
||||
column.display_rows = (0..column.rows.len()).map(DisplayRow::Line).collect();
|
||||
column.rebuild_source_ranges();
|
||||
}
|
||||
Self { columns }
|
||||
Self { candidates, base }
|
||||
}
|
||||
|
||||
pub fn row_count(&self) -> usize {
|
||||
self.columns[0].rows.len()
|
||||
self.candidates.rows.len()
|
||||
}
|
||||
|
||||
pub fn column(&self, index: usize) -> HistoryColumn<'_> {
|
||||
let diff = &self.columns[index];
|
||||
let diff = if index == 1 {
|
||||
&self.base
|
||||
} else {
|
||||
&self.candidates
|
||||
};
|
||||
let file = &diff.comparison.as_ref().unwrap().files[0];
|
||||
let focus = if index == 1 {
|
||||
Focus::Before
|
||||
} else {
|
||||
let focus = if index == 2 {
|
||||
Focus::After
|
||||
};
|
||||
let lines = if focus == Focus::Before {
|
||||
&file.before
|
||||
} else {
|
||||
&file.after
|
||||
Focus::Before
|
||||
};
|
||||
HistoryColumn {
|
||||
diff,
|
||||
file,
|
||||
focus,
|
||||
lines,
|
||||
}
|
||||
HistoryColumn { diff, file, focus }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,6 +791,7 @@ pub struct DiffState {
|
||||
lazy_changes: bool,
|
||||
/// Native empty terminal rows explicitly supplied by resolver text.
|
||||
terminal_rows: [Option<usize>; 2],
|
||||
conflict: Option<ConflictChanges>,
|
||||
excerpt_ranges: Vec<(usize, usize)>,
|
||||
pub live_buffer: Option<usize>,
|
||||
pub live_revision: Option<u64>,
|
||||
@@ -723,6 +855,7 @@ impl DiffState {
|
||||
display_rows: Vec::new(),
|
||||
display_sides: Vec::new(),
|
||||
source_ranges: Default::default(),
|
||||
conflict: None,
|
||||
lazy_changes: false,
|
||||
terminal_rows: [None; 2],
|
||||
excerpt_ranges: Vec::new(),
|
||||
@@ -786,7 +919,12 @@ impl DiffState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_resolver_text(&mut self, draft: &[String], alternative: &[String]) {
|
||||
pub(crate) fn set_resolver_text(
|
||||
&mut self,
|
||||
draft: &[String],
|
||||
alternative: &[String],
|
||||
base: Option<&[String]>,
|
||||
) {
|
||||
fn content(lines: &[String]) -> (&[String], bool) {
|
||||
let eof = lines.len() > 1 && lines.last().is_some_and(String::is_empty);
|
||||
let content = if eof {
|
||||
@@ -809,6 +947,11 @@ impl DiffState {
|
||||
let after_terminal = (alternative.len() > after.len()).then_some(after.len());
|
||||
if self.phase == Phase::View
|
||||
&& self.terminal_rows == [before_terminal, after_terminal]
|
||||
&& self
|
||||
.conflict
|
||||
.as_ref()
|
||||
.map(|conflict| conflict.base.as_slice())
|
||||
== base
|
||||
&& self.comparison.as_ref().is_some_and(|comparison| {
|
||||
let file = &comparison.files[self.file];
|
||||
file.before == before
|
||||
@@ -836,15 +979,59 @@ impl DiffState {
|
||||
file.before_eof = before_eof;
|
||||
file.after_eof = after_eof;
|
||||
file.hunks = line_hunks(before, before_eof, after, after_eof);
|
||||
self.align_file();
|
||||
if base.is_none() {
|
||||
self.align_file();
|
||||
if before_terminal.is_some() || after_terminal.is_some() {
|
||||
self.rows.push(AlignedRow {
|
||||
before: before_terminal,
|
||||
after: after_terminal,
|
||||
kind: RowKind::Equal,
|
||||
changes: Vec::new(),
|
||||
});
|
||||
self.reset_excerpts();
|
||||
}
|
||||
}
|
||||
self.terminal_rows = [before_terminal, after_terminal];
|
||||
if before_terminal.is_some() || after_terminal.is_some() {
|
||||
self.rows.push(AlignedRow {
|
||||
before: before_terminal,
|
||||
after: after_terminal,
|
||||
kind: RowKind::Equal,
|
||||
changes: Vec::new(),
|
||||
});
|
||||
if let Some(base) = base {
|
||||
let conflict = ConflictChanges::new(base, [draft, alternative]);
|
||||
self.rows = conflict
|
||||
.rows
|
||||
.iter()
|
||||
.map(|lines| {
|
||||
let native = |side: usize| {
|
||||
lines[side]
|
||||
.as_ref()
|
||||
.filter(|line| !line.removed)
|
||||
.map(|line| line.source)
|
||||
};
|
||||
AlignedRow {
|
||||
before: native(0),
|
||||
after: native(1),
|
||||
kind: if lines.iter().flatten().any(|line| line.changed) {
|
||||
RowKind::Changed
|
||||
} else {
|
||||
RowKind::Equal
|
||||
},
|
||||
changes: Vec::new(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let base_width = base.iter().map(|line| line_width(line)).max().unwrap_or(0);
|
||||
self.max_before = draft
|
||||
.iter()
|
||||
.map(|line| line_width(line))
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
.max(base_width);
|
||||
self.max_after = alternative
|
||||
.iter()
|
||||
.map(|line| line_width(line))
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
.max(base_width);
|
||||
self.lazy_changes = false;
|
||||
self.after_widths.clear();
|
||||
self.conflict = Some(conflict);
|
||||
self.reset_excerpts();
|
||||
}
|
||||
if self.comparison.as_ref().unwrap().files[self.file]
|
||||
@@ -2677,6 +2864,7 @@ impl DiffState {
|
||||
fn rebuild_file(&mut self, reset: bool) {
|
||||
self.normalize_focus();
|
||||
self.rows.clear();
|
||||
self.conflict = None;
|
||||
self.terminal_rows = [None; 2];
|
||||
self.display_rows.clear();
|
||||
self.display_sides.clear();
|
||||
@@ -2857,7 +3045,11 @@ impl DiffState {
|
||||
// Unified hunks show all removals before all additions, without alignment holes.
|
||||
for side in [Focus::Before, Focus::After] {
|
||||
for aligned in run_start..index {
|
||||
if self.source_row(side, aligned).is_some() {
|
||||
if self.source_row(side, aligned).is_some()
|
||||
|| self.conflict.as_ref().is_some_and(|conflict| {
|
||||
conflict.rows[aligned][usize::from(side == Focus::After)].is_some()
|
||||
})
|
||||
{
|
||||
self.display_rows.push(DisplayRow::Line(aligned));
|
||||
self.display_sides.push(Some(side));
|
||||
}
|
||||
|
||||
+27
-4
@@ -1694,7 +1694,7 @@ pub struct ResolverFile {
|
||||
pub seed_source: usize,
|
||||
projection: Option<crate::resolve::Projection>,
|
||||
diff: DiffState,
|
||||
comparison_key: Option<(u64, usize)>,
|
||||
comparison_key: Option<(u64, usize, Option<usize>)>,
|
||||
}
|
||||
|
||||
impl ResolverFile {
|
||||
@@ -3088,11 +3088,34 @@ impl Editor {
|
||||
let Some(buffer) = self.buffer(draft_buffer) else {
|
||||
return;
|
||||
};
|
||||
let key = (buffer.text_revision, source);
|
||||
let base_index = resolver.selected_base;
|
||||
let key = (buffer.text_revision, source, base_index);
|
||||
let changed = file.comparison_key != Some(key);
|
||||
let first_comparison = file.diff.comparison_id.is_none();
|
||||
if changed {
|
||||
if file.comparison_key.is_none_or(|(_, index)| index != source) {
|
||||
let base = match base_index {
|
||||
Some(index) => match &file.projection {
|
||||
Some(projection) => projection.alternative(index),
|
||||
None => file
|
||||
.sources
|
||||
.get(index)
|
||||
.map(|source| source.text.as_str())
|
||||
.ok_or_else(|| "No such conflict base".into()),
|
||||
},
|
||||
None => Ok(""),
|
||||
};
|
||||
let base = match base {
|
||||
Ok(text) => split_lines(text),
|
||||
Err(error) => {
|
||||
self.resolvers.get_mut(&self.tab_id).unwrap().files[file_index].error =
|
||||
Some(error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if file
|
||||
.comparison_key
|
||||
.is_none_or(|(_, index, _)| index != source)
|
||||
{
|
||||
let alternative = match &file.projection {
|
||||
Some(projection) => projection.alternative(source),
|
||||
None => Ok(file
|
||||
@@ -3141,7 +3164,7 @@ impl Editor {
|
||||
.set_context_options(self.diff_context_options.0, self.diff_context_options.1);
|
||||
let previous_id = file.diff.comparison_id;
|
||||
file.diff
|
||||
.set_resolver_text(&draft.lines, &alternative.lines);
|
||||
.set_resolver_text(&draft.lines, &alternative.lines, Some(&base));
|
||||
if file.diff.comparison_id != previous_id {
|
||||
self.next_diff_request = self
|
||||
.next_diff_request
|
||||
|
||||
+55
-51
@@ -392,14 +392,9 @@ impl HistoryDiff {
|
||||
for column in 0..3 {
|
||||
if self.wrapping[column]
|
||||
&& self.widths[column] > 0
|
||||
&& let Some(source) = self.comparison.column(column).source_row(row)
|
||||
&& let Some((key, text)) = self.comparison.column(column).view().text_row(row)
|
||||
{
|
||||
let lines = self.comparison.column(column).lines;
|
||||
height = height.max(
|
||||
self.wrap[column]
|
||||
.segments(source, lines.get(source).map_or("", String::as_str))
|
||||
.len(),
|
||||
);
|
||||
height = height.max(self.wrap[column].segments(key, text).len());
|
||||
}
|
||||
}
|
||||
height
|
||||
@@ -432,10 +427,9 @@ impl HistoryDiff {
|
||||
let mut final_segments = [false; 2];
|
||||
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 let Some((key, text)) = projection.view().text_row(self.top.display) {
|
||||
if self.wrapping[column] {
|
||||
let parts = self.wrap[column].segments(source, text);
|
||||
let parts = self.wrap[column].segments(key, text);
|
||||
segments[side] = parts.get(self.top.continuation).copied();
|
||||
final_segments[side] = self.top.continuation + 1 == parts.len();
|
||||
} else if self.top.continuation == 0 {
|
||||
@@ -4682,6 +4676,7 @@ fn paint_diff_content<'a>(
|
||||
continue;
|
||||
};
|
||||
let before = line.side == Focus::Before;
|
||||
let removed = line.polarity == 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);
|
||||
@@ -4694,7 +4689,7 @@ fn paint_diff_content<'a>(
|
||||
let text_area = Rect::new(content.x + gutter as u16, row_area.y, paint_width as u16, 1);
|
||||
let changed = line.changed;
|
||||
let background = if changed && line.emphasize {
|
||||
colors.row[usize::from(!before)]
|
||||
colors.row[usize::from(!removed)]
|
||||
} else {
|
||||
Color::Reset
|
||||
};
|
||||
@@ -4711,12 +4706,12 @@ fn paint_diff_content<'a>(
|
||||
);
|
||||
if gutter > 0 && wrapped.is_none_or(|rows| rows[screen].position.continuation == 0) {
|
||||
let marker = if changed {
|
||||
if before { '-' } else { '+' }
|
||||
if removed { '-' } else { '+' }
|
||||
} else {
|
||||
' '
|
||||
};
|
||||
let color = if changed {
|
||||
if before { Color::Red } else { Color::Green }
|
||||
if removed { Color::Red } else { Color::Green }
|
||||
} else {
|
||||
Color::DarkGray
|
||||
};
|
||||
@@ -4732,35 +4727,41 @@ fn paint_diff_content<'a>(
|
||||
}
|
||||
(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"),
|
||||
(None, None) => format!("{:>old_digits$} {:>new_digits$}{marker} ", "", ""),
|
||||
}
|
||||
} else {
|
||||
let digits = view.info.source_extents[usize::from(!before)]
|
||||
.max(1)
|
||||
.ilog10() as usize
|
||||
+ 1;
|
||||
format!("{:>digits$}{marker} ", index + 1)
|
||||
if line.native {
|
||||
format!("{:>digits$}{marker} ", index + 1)
|
||||
} else {
|
||||
format!("{:>digits$}{marker} ", "")
|
||||
}
|
||||
};
|
||||
frame.render_widget(
|
||||
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[usize::from(!before)].line(index),
|
||||
text_area,
|
||||
left,
|
||||
);
|
||||
paint_semantic_line(
|
||||
frame,
|
||||
source_text,
|
||||
semantic[usize::from(!before)],
|
||||
index,
|
||||
text_area,
|
||||
left,
|
||||
);
|
||||
if line.native {
|
||||
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 let Some(characters) = whitespace_characters {
|
||||
paint_whitespace(
|
||||
frame,
|
||||
@@ -4769,13 +4770,17 @@ fn paint_diff_content<'a>(
|
||||
left,
|
||||
characters,
|
||||
whitespace_scope,
|
||||
syntax[usize::from(!before)].string_spans(index),
|
||||
if line.native {
|
||||
syntax[usize::from(!before)].string_spans(index)
|
||||
} else {
|
||||
&[]
|
||||
},
|
||||
line.newline,
|
||||
true,
|
||||
);
|
||||
}
|
||||
if line.emphasize {
|
||||
let style = Style::default().bg(colors.character[usize::from(!before)]);
|
||||
let style = Style::default().bg(colors.character[usize::from(!removed)]);
|
||||
for range in line.changes() {
|
||||
if !range.is_empty() {
|
||||
paint_grapheme_range(frame, text_area, left, source_text, range.clone(), style);
|
||||
@@ -5080,7 +5085,7 @@ impl DiffWrapViewport {
|
||||
fn row(
|
||||
&mut self,
|
||||
diff: &DiffState,
|
||||
file: &ChangedFile,
|
||||
_file: &ChangedFile,
|
||||
position: DiffWrapPosition,
|
||||
) -> WrappedDiffRow {
|
||||
let mut segments = [None; 2];
|
||||
@@ -5099,15 +5104,16 @@ impl DiffWrapViewport {
|
||||
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)
|
||||
let view = diff.column_view(focus).unwrap();
|
||||
if let Some((key, text)) = view.text_row(position.display)
|
||||
&& self.widths[side] > 0
|
||||
{
|
||||
let cache = if side == 0 {
|
||||
&mut self.before
|
||||
} else {
|
||||
(&mut self.after, &file.after)
|
||||
&mut self.after
|
||||
};
|
||||
let text = lines.get(source).map_or("", String::as_str);
|
||||
let parts = cache.segments(source, text);
|
||||
let parts = cache.segments(key, text);
|
||||
segments[side] = parts.get(position.continuation).copied();
|
||||
final_segments[side] = position.continuation + 1 == parts.len();
|
||||
}
|
||||
@@ -5122,7 +5128,7 @@ impl DiffWrapViewport {
|
||||
}
|
||||
}
|
||||
|
||||
fn height(&mut self, diff: &DiffState, file: &ChangedFile, display: usize) -> usize {
|
||||
fn height(&mut self, diff: &DiffState, _file: &ChangedFile, display: usize) -> usize {
|
||||
if !diff.word_wrap {
|
||||
return 1;
|
||||
}
|
||||
@@ -5131,18 +5137,16 @@ impl DiffWrapViewport {
|
||||
}
|
||||
let mut height = 1;
|
||||
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)
|
||||
let view = diff.column_view(focus).unwrap();
|
||||
if let Some((key, text)) = view.text_row(display)
|
||||
&& self.widths[side] > 0
|
||||
{
|
||||
let cache = if side == 0 {
|
||||
&mut self.before
|
||||
} else {
|
||||
(&mut self.after, &file.after)
|
||||
&mut self.after
|
||||
};
|
||||
height = height.max(
|
||||
cache
|
||||
.segments(source, lines.get(source).map_or("", String::as_str))
|
||||
.len(),
|
||||
);
|
||||
height = height.max(cache.segments(key, text).len());
|
||||
}
|
||||
}
|
||||
height
|
||||
|
||||
Reference in New Issue
Block a user