cli: in color-words diffs, highlight word-level hunks in context lines

Since context lines are styled in the same way as diff lines, it makes sense
to highlight words within context lines.
This commit is contained in:
Yuya Nishihara 2024-09-23 15:43:22 +09:00
parent 1eeeb1462a
commit 68f48605e9
2 changed files with 64 additions and 37 deletions

View file

@ -542,21 +542,14 @@ fn show_color_words_diff_hunks(
for hunk in line_diff.hunks() { for hunk in line_diff.hunks() {
match hunk.kind { match hunk.kind {
DiffHunkKind::Matching => { DiffHunkKind::Matching => contexts.push(hunk.contents),
// TODO: better handling of unmatched contexts
debug_assert!(hunk
.contents
.iter()
.map(|content| content.split_inclusive(|b| *b == b'\n').count())
.all_equal());
contexts.push(hunk.contents[1]);
}
DiffHunkKind::Different => { DiffHunkKind::Different => {
let num_after = if emitted { options.context } else { 0 }; let num_after = if emitted { options.context } else { 0 };
line_number = show_color_words_context_lines( line_number = show_color_words_context_lines(
formatter, formatter,
&contexts, &contexts,
line_number, line_number,
options,
num_after, num_after,
options.context, options.context,
)?; )?;
@ -569,7 +562,14 @@ fn show_color_words_diff_hunks(
} }
if emitted { if emitted {
show_color_words_context_lines(formatter, &contexts, line_number, options.context, 0)?; show_color_words_context_lines(
formatter,
&contexts,
line_number,
options,
options.context,
0,
)?;
} }
Ok(()) Ok(())
} }
@ -577,46 +577,73 @@ fn show_color_words_diff_hunks(
/// Prints `num_after` lines, ellipsis, and `num_before` lines. /// Prints `num_after` lines, ellipsis, and `num_before` lines.
fn show_color_words_context_lines( fn show_color_words_context_lines(
formatter: &mut dyn Formatter, formatter: &mut dyn Formatter,
contents: &[&BStr], contexts: &[Vec<&BStr>],
mut line_number: DiffLineNumber, mut line_number: DiffLineNumber,
options: &ColorWordsDiffOptions,
num_after: usize, num_after: usize,
num_before: usize, num_before: usize,
) -> io::Result<DiffLineNumber> { ) -> io::Result<DiffLineNumber> {
const SKIPPED_CONTEXT_LINE: &str = " ...\n"; const SKIPPED_CONTEXT_LINE: &str = " ...\n";
let extract = || -> (Vec<&[u8]>, Vec<&[u8]>, u32) { let extract = |side: usize| -> (Vec<&[u8]>, Vec<&[u8]>, u32) {
let mut lines = contents let mut lines = contexts
.iter() .iter()
.flat_map(|content| content.split_inclusive(|b| *b == b'\n')) .flat_map(|contents| contents[side].split_inclusive(|b| *b == b'\n'))
.fuse(); .fuse();
let after_lines = lines.by_ref().take(num_after).collect(); let after_lines = lines.by_ref().take(num_after).collect();
let before_lines = lines.by_ref().rev().take(num_before + 1).collect(); let before_lines = lines.by_ref().rev().take(num_before + 1).collect();
let num_skipped: u32 = lines.count().try_into().unwrap(); let num_skipped: u32 = lines.count().try_into().unwrap();
(after_lines, before_lines, num_skipped) (after_lines, before_lines, num_skipped)
}; };
let show = |formatter: &mut dyn Formatter, lines: &[&[u8]], mut line_number: DiffLineNumber| { let show = |formatter: &mut dyn Formatter,
for line in lines { left_lines: &[&[u8]],
right_lines: &[&[u8]],
mut line_number: DiffLineNumber| {
if left_lines == right_lines {
for line in left_lines {
show_color_words_line_number( show_color_words_line_number(
formatter, formatter,
Some(line_number.left), Some(line_number.left),
Some(line_number.right), Some(line_number.right),
)?; )?;
show_color_words_inline_hunks(formatter, &[(DiffLineHunkSide::Both, line.as_ref())])?; show_color_words_inline_hunks(
formatter,
&[(DiffLineHunkSide::Both, line.as_ref())],
)?;
line_number.left += 1; line_number.left += 1;
line_number.right += 1; line_number.right += 1;
} }
io::Result::Ok(line_number) Ok(line_number)
} else {
let left = left_lines.concat();
let right = right_lines.concat();
show_color_words_diff_lines(
formatter,
&[BStr::new(&left), BStr::new(&right)],
line_number,
options,
)
}
}; };
let (after_lines, mut before_lines, num_skipped) = extract(); let (left_after, mut left_before, num_left_skipped) = extract(0);
line_number = show(formatter, &after_lines, line_number)?; let (right_after, mut right_before, num_right_skipped) = extract(1);
if num_skipped > 0 { line_number = show(formatter, &left_after, &right_after, line_number)?;
if num_left_skipped > 0 || num_right_skipped > 0 {
write!(formatter, "{SKIPPED_CONTEXT_LINE}")?; write!(formatter, "{SKIPPED_CONTEXT_LINE}")?;
before_lines.pop(); line_number.left += num_left_skipped;
line_number.left += num_skipped + 1; line_number.right += num_right_skipped;
line_number.right += num_skipped + 1; if left_before.len() > num_before {
left_before.pop();
line_number.left += 1;
} }
before_lines.reverse(); if right_before.len() > num_before {
line_number = show(formatter, &before_lines, line_number)?; right_before.pop();
line_number.right += 1;
}
}
left_before.reverse();
right_before.reverse();
line_number = show(formatter, &left_before, &right_before, line_number)?;
Ok(line_number) Ok(line_number)
} }

View file

@ -1604,11 +1604,11 @@ fn test_diff_ignore_whitespace() {
insta::assert_snapshot!(stdout, @r#" insta::assert_snapshot!(stdout, @r#"
Modified regular file file1: Modified regular file file1:
 1: {  1: {
 1  2: foo {  1  2:  foo {
 2  3: bar;  2  3:  bar;
 3  4: }  3  4:  }
 5: }  5: }
 4  6: baz { }  4  6: baz { }
"#); "#);
let stdout = test_env.jj_cmd_success( let stdout = test_env.jj_cmd_success(
&repo_path, &repo_path,
@ -1618,7 +1618,7 @@ fn test_diff_ignore_whitespace() {
Modified regular file file1: Modified regular file file1:
 1: {  1: {
 1  2:  foo {  1  2:  foo {
 2  3: bar;  2  3:  bar;
 4:  }  4:  }
 3  5: }  3  5: }
 4  6: baz { }  4  6: baz { }