Change behavior of inline diff to be word based
This also fixes a bug with bad indexes and updates the inline terminal example.
This commit is contained in:
parent
459fdfdf9d
commit
f3e401fc17
4 changed files with 208 additions and 43 deletions
|
|
@ -1,39 +1,56 @@
|
||||||
use console::Style;
|
use std::fmt;
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
use console::{style, Style};
|
||||||
use similar::text::{ChangeTag, TextDiff};
|
use similar::text::{ChangeTag, TextDiff};
|
||||||
|
|
||||||
fn main() {
|
struct Line(Option<usize>);
|
||||||
let diff = TextDiff::from_lines(
|
|
||||||
"schtzngrmm\nschtzngrmm\nt-t-t-t\nt-t-t-t\ngrrrmmmmm\nt-t-t-t\n\
|
|
||||||
s---------c---------h\ntzngrmm\ntzngrmm\ntzngrmm\ngrrrmmmmm\n\
|
|
||||||
schtzn\nschtzn\nt-t-t-t\nt-t-t-t\nschtzngrmm\nschtzngrmm\n\
|
|
||||||
tssssssssssssss\ngrrt\ngrrrrrt\ngrrrrrrrrrt\nscht\nscht\n\
|
|
||||||
t-t-t-t-t-t-t-t-t-t\nscht\ntzngrmm\ntzngrmm\nt-t-t-t-t-t-t-t-t-t\n\
|
|
||||||
scht\nscht\nscht\nscht\nscht\ngrrrrrrrrrrrrrrrrrrrrrrrrrrrr\nt-tt",
|
|
||||||
"schützengraben\nschützengraben\nt-t-t-t\nt-t-t-t\ngrrrmmmmm\nt-t-t-t\n\
|
|
||||||
s---------c---------h\ntzngrmm\ntzngrmm\ntzngrmm\ngrrrmmmmm\nschützen\n\
|
|
||||||
schützen\nt-t-t-t\nt-t-t-t\nschützengraben\nschützengraben\n\
|
|
||||||
tssssssssssssss\ngrrt\ngrrrrrt\ngrrrrrrrrrt\nscht\nscht\n\
|
|
||||||
t-t-t-t-t-t-t-t-t-t\nscht\ntzngrmm\ntzngrmm\nt-t-t-t-t-t-t-t-t-t\n\
|
|
||||||
scht\nscht\nscht\nscht\nscht\ngrrrrrrrrrrrrrrrrrrrrrrrrrrrr\nt-tt",
|
|
||||||
);
|
|
||||||
|
|
||||||
for op in diff.ops() {
|
impl fmt::Display for Line {
|
||||||
for change in diff.iter_inline_changes(op) {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let (sign, style) = match change.tag() {
|
match self.0 {
|
||||||
ChangeTag::Delete => ("-", Style::new().red()),
|
None => write!(f, " "),
|
||||||
ChangeTag::Insert => ("+", Style::new().green()),
|
Some(idx) => write!(f, "{:<4}", idx + 1),
|
||||||
ChangeTag::Equal => (" ", Style::new()),
|
}
|
||||||
};
|
}
|
||||||
print!("{}", style.apply_to(sign).bold(),);
|
}
|
||||||
for &(emphasized, value) in change.values() {
|
|
||||||
if emphasized {
|
fn main() {
|
||||||
print!("{}", style.apply_to(value).underlined().on_black());
|
let args: Vec<_> = std::env::args_os().collect();
|
||||||
} else {
|
if args.len() != 3 {
|
||||||
print!("{}", style.apply_to(value));
|
eprintln!("usage: terminal-inline [old] [new]");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let old = read_to_string(&args[1]).unwrap();
|
||||||
|
let new = read_to_string(&args[2]).unwrap();
|
||||||
|
let diff = TextDiff::from_lines(&old, &new);
|
||||||
|
|
||||||
|
for group in diff.grouped_ops(5) {
|
||||||
|
for op in group {
|
||||||
|
for change in diff.iter_inline_changes(&op) {
|
||||||
|
let (sign, s) = match change.tag() {
|
||||||
|
ChangeTag::Delete => ("-", Style::new().red()),
|
||||||
|
ChangeTag::Insert => ("+", Style::new().green()),
|
||||||
|
ChangeTag::Equal => (" ", Style::new()),
|
||||||
|
};
|
||||||
|
print!(
|
||||||
|
"{}{} |{}",
|
||||||
|
style(Line(change.old_index())).dim(),
|
||||||
|
style(Line(change.new_index())).dim(),
|
||||||
|
s.apply_to(sign).bold(),
|
||||||
|
);
|
||||||
|
for &(emphasized, value) in change.values() {
|
||||||
|
if emphasized {
|
||||||
|
print!("{}", s.apply_to(value).underlined().on_black());
|
||||||
|
} else {
|
||||||
|
print!("{}", s.apply_to(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if change.is_missing_newline() {
|
||||||
|
println!();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if change.is_missing_newline() {
|
|
||||||
println!();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::{fmt, iter};
|
||||||
use crate::algorithms::{Algorithm, DiffOp, DiffTag};
|
use crate::algorithms::{Algorithm, DiffOp, DiffTag};
|
||||||
use crate::text::{Change, ChangeTag, TextDiff};
|
use crate::text::{Change, ChangeTag, TextDiff};
|
||||||
|
|
||||||
use super::split_chars;
|
use super::split_words;
|
||||||
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ impl<'s> From<Change<'s>> for InlineChange<'s> {
|
||||||
InlineChange {
|
InlineChange {
|
||||||
tag: change.tag(),
|
tag: change.tag(),
|
||||||
old_index: change.old_index(),
|
old_index: change.old_index(),
|
||||||
new_index: change.old_index(),
|
new_index: change.new_index(),
|
||||||
values: vec![(false, change.value())],
|
values: vec![(false, change.value())],
|
||||||
missing_newline: change.missing_newline(),
|
missing_newline: change.missing_newline(),
|
||||||
}
|
}
|
||||||
|
|
@ -118,8 +118,8 @@ pub(crate) fn iter_inline_changes<'diff>(
|
||||||
(ChangeTag::Delete, Some(ChangeTag::Insert)) => {
|
(ChangeTag::Delete, Some(ChangeTag::Insert)) => {
|
||||||
let old_value = change.value();
|
let old_value = change.value();
|
||||||
let new_value = next_change.unwrap().value();
|
let new_value = next_change.unwrap().value();
|
||||||
let old_chars = split_chars(&old_value).collect::<Vec<_>>();
|
let old_chars = split_words(&old_value).collect::<Vec<_>>();
|
||||||
let new_chars = split_chars(&new_value).collect::<Vec<_>>();
|
let new_chars = split_words(&new_value).collect::<Vec<_>>();
|
||||||
let old_mindex = MultiIndex::new(&old_chars, old_value);
|
let old_mindex = MultiIndex::new(&old_chars, old_value);
|
||||||
let new_mindex = MultiIndex::new(&new_chars, new_value);
|
let new_mindex = MultiIndex::new(&new_chars, new_value);
|
||||||
let inline_diff = TextDiff::configure()
|
let inline_diff = TextDiff::configure()
|
||||||
|
|
@ -158,7 +158,7 @@ pub(crate) fn iter_inline_changes<'diff>(
|
||||||
Some(InlineChange {
|
Some(InlineChange {
|
||||||
tag: ChangeTag::Delete,
|
tag: ChangeTag::Delete,
|
||||||
old_index: change.old_index(),
|
old_index: change.old_index(),
|
||||||
new_index: change.new_index(),
|
new_index: None,
|
||||||
values: old_values,
|
values: old_values,
|
||||||
missing_newline: newline_terminated
|
missing_newline: newline_terminated
|
||||||
&& !old_value.ends_with(&['\r', '\n'][..]),
|
&& !old_value.ends_with(&['\r', '\n'][..]),
|
||||||
|
|
@ -167,8 +167,8 @@ pub(crate) fn iter_inline_changes<'diff>(
|
||||||
.chain(
|
.chain(
|
||||||
Some(InlineChange {
|
Some(InlineChange {
|
||||||
tag: ChangeTag::Insert,
|
tag: ChangeTag::Insert,
|
||||||
old_index: change.old_index(),
|
old_index: None,
|
||||||
new_index: change.new_index(),
|
new_index: next_change.unwrap().new_index(),
|
||||||
values: new_values,
|
values: new_values,
|
||||||
missing_newline: newline_terminated
|
missing_newline: newline_terminated
|
||||||
&& !new_value.ends_with(&['\r', '\n'][..]),
|
&& !new_value.ends_with(&['\r', '\n'][..]),
|
||||||
|
|
|
||||||
|
|
@ -483,10 +483,9 @@ impl<'old, 'new, 'bufs> TextDiff<'old, 'new, 'bufs> {
|
||||||
/// Iterates over the changes the op expands to with inline emphasis.
|
/// Iterates over the changes the op expands to with inline emphasis.
|
||||||
///
|
///
|
||||||
/// This is very similar to [`iter_changes`] but it performs a second
|
/// This is very similar to [`iter_changes`] but it performs a second
|
||||||
/// level per-character diff on adjacent line replacements. The exact
|
/// level diff on adjacent line replacements. The exact behavior of
|
||||||
/// behavior of this function with regards to how it detects those
|
/// this function with regards to how it detects those inline changes
|
||||||
/// inline changes is currently not defined and will likely change
|
/// is currently not defined and will likely change over time.
|
||||||
/// over time.
|
|
||||||
pub fn iter_inline_changes(&self, op: &DiffOp) -> impl Iterator<Item = InlineChange> {
|
pub fn iter_inline_changes(&self, op: &DiffOp) -> impl Iterator<Item = InlineChange> {
|
||||||
iter_inline_changes(self, op)
|
iter_inline_changes(self, op)
|
||||||
}
|
}
|
||||||
|
|
@ -783,6 +782,21 @@ fn test_virtual_newlines() {
|
||||||
insta::assert_debug_snapshot!(&changes);
|
insta::assert_debug_snapshot!(&changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_line_ops_inline() {
|
||||||
|
let diff = TextDiff::from_lines(
|
||||||
|
"Hello World\nsome stuff here\nsome more stuff here\n\nAha stuff here\nand more stuff",
|
||||||
|
"Stuff\nHello World\nsome amazing stuff here\nsome more stuff here\n",
|
||||||
|
);
|
||||||
|
assert_eq!(diff.newline_terminated(), true);
|
||||||
|
let changes = diff
|
||||||
|
.ops()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|op| diff.iter_inline_changes(op))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
insta::assert_debug_snapshot!(&changes);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_char_diff() {
|
fn test_char_diff() {
|
||||||
let diff = TextDiff::from_chars("Hello World", "Hallo Welt");
|
let diff = TextDiff::from_chars("Hello World", "Hallo Welt");
|
||||||
|
|
|
||||||
134
src/text/snapshots/similar__text__line_ops_inline.snap
Normal file
134
src/text/snapshots/similar__text__line_ops_inline.snap
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
---
|
||||||
|
source: src/text/mod.rs
|
||||||
|
expression: "&changes"
|
||||||
|
---
|
||||||
|
[
|
||||||
|
InlineChange {
|
||||||
|
tag: Insert,
|
||||||
|
old_index: None,
|
||||||
|
new_index: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
values: [
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
"Stuff\n",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
missing_newline: false,
|
||||||
|
},
|
||||||
|
InlineChange {
|
||||||
|
tag: Equal,
|
||||||
|
old_index: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
new_index: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
values: [
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
"Hello World\n",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
missing_newline: false,
|
||||||
|
},
|
||||||
|
InlineChange {
|
||||||
|
tag: Delete,
|
||||||
|
old_index: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
new_index: None,
|
||||||
|
values: [
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
"some ",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
"stuff here\n",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
missing_newline: false,
|
||||||
|
},
|
||||||
|
InlineChange {
|
||||||
|
tag: Insert,
|
||||||
|
old_index: None,
|
||||||
|
new_index: Some(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
values: [
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
"some ",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
true,
|
||||||
|
"amazing ",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
"stuff here\n",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
missing_newline: false,
|
||||||
|
},
|
||||||
|
InlineChange {
|
||||||
|
tag: Equal,
|
||||||
|
old_index: Some(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
new_index: Some(
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
values: [
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
"some more stuff here\n",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
missing_newline: false,
|
||||||
|
},
|
||||||
|
InlineChange {
|
||||||
|
tag: Delete,
|
||||||
|
old_index: Some(
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
new_index: None,
|
||||||
|
values: [
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
"\n",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
missing_newline: false,
|
||||||
|
},
|
||||||
|
InlineChange {
|
||||||
|
tag: Delete,
|
||||||
|
old_index: Some(
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
new_index: None,
|
||||||
|
values: [
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
"Aha stuff here\n",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
missing_newline: false,
|
||||||
|
},
|
||||||
|
InlineChange {
|
||||||
|
tag: Delete,
|
||||||
|
old_index: Some(
|
||||||
|
5,
|
||||||
|
),
|
||||||
|
new_index: None,
|
||||||
|
values: [
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
"and more stuff",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
missing_newline: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
Loading…
Add table
Add a link
Reference in a new issue