From f3e401fc177ffcdfe85643888620b095b87fe035 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sun, 31 Jan 2021 22:02:08 +0100 Subject: [PATCH] Change behavior of inline diff to be word based This also fixes a bug with bad indexes and updates the inline terminal example. --- examples/terminal-inline.rs | 81 ++++++----- src/text/inline.rs | 14 +- src/text/mod.rs | 22 ++- .../similar__text__line_ops_inline.snap | 134 ++++++++++++++++++ 4 files changed, 208 insertions(+), 43 deletions(-) create mode 100644 src/text/snapshots/similar__text__line_ops_inline.snap diff --git a/examples/terminal-inline.rs b/examples/terminal-inline.rs index 97840ec..8df3da6 100644 --- a/examples/terminal-inline.rs +++ b/examples/terminal-inline.rs @@ -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}; -fn main() { - 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", - ); +struct Line(Option); - for op in diff.ops() { - for change in diff.iter_inline_changes(op) { - let (sign, style) = match change.tag() { - ChangeTag::Delete => ("-", Style::new().red()), - ChangeTag::Insert => ("+", Style::new().green()), - ChangeTag::Equal => (" ", Style::new()), - }; - print!("{}", style.apply_to(sign).bold(),); - for &(emphasized, value) in change.values() { - if emphasized { - print!("{}", style.apply_to(value).underlined().on_black()); - } else { - print!("{}", style.apply_to(value)); +impl fmt::Display for Line { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + None => write!(f, " "), + Some(idx) => write!(f, "{:<4}", idx + 1), + } + } +} + +fn main() { + let args: Vec<_> = std::env::args_os().collect(); + if args.len() != 3 { + 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!(); } } } diff --git a/src/text/inline.rs b/src/text/inline.rs index 1a8d38d..56ed2c0 100644 --- a/src/text/inline.rs +++ b/src/text/inline.rs @@ -3,7 +3,7 @@ use std::{fmt, iter}; use crate::algorithms::{Algorithm, DiffOp, DiffTag}; use crate::text::{Change, ChangeTag, TextDiff}; -use super::split_chars; +use super::split_words; use std::ops::Range; @@ -75,7 +75,7 @@ impl<'s> From> for InlineChange<'s> { InlineChange { tag: change.tag(), old_index: change.old_index(), - new_index: change.old_index(), + new_index: change.new_index(), values: vec![(false, change.value())], missing_newline: change.missing_newline(), } @@ -118,8 +118,8 @@ pub(crate) fn iter_inline_changes<'diff>( (ChangeTag::Delete, Some(ChangeTag::Insert)) => { let old_value = change.value(); let new_value = next_change.unwrap().value(); - let old_chars = split_chars(&old_value).collect::>(); - let new_chars = split_chars(&new_value).collect::>(); + let old_chars = split_words(&old_value).collect::>(); + let new_chars = split_words(&new_value).collect::>(); let old_mindex = MultiIndex::new(&old_chars, old_value); let new_mindex = MultiIndex::new(&new_chars, new_value); let inline_diff = TextDiff::configure() @@ -158,7 +158,7 @@ pub(crate) fn iter_inline_changes<'diff>( Some(InlineChange { tag: ChangeTag::Delete, old_index: change.old_index(), - new_index: change.new_index(), + new_index: None, values: old_values, missing_newline: newline_terminated && !old_value.ends_with(&['\r', '\n'][..]), @@ -167,8 +167,8 @@ pub(crate) fn iter_inline_changes<'diff>( .chain( Some(InlineChange { tag: ChangeTag::Insert, - old_index: change.old_index(), - new_index: change.new_index(), + old_index: None, + new_index: next_change.unwrap().new_index(), values: new_values, missing_newline: newline_terminated && !new_value.ends_with(&['\r', '\n'][..]), diff --git a/src/text/mod.rs b/src/text/mod.rs index f7db170..a89a942 100644 --- a/src/text/mod.rs +++ b/src/text/mod.rs @@ -483,10 +483,9 @@ impl<'old, 'new, 'bufs> TextDiff<'old, 'new, 'bufs> { /// Iterates over the changes the op expands to with inline emphasis. /// /// This is very similar to [`iter_changes`] but it performs a second - /// level per-character diff on adjacent line replacements. The exact - /// behavior of this function with regards to how it detects those - /// inline changes is currently not defined and will likely change - /// over time. + /// level diff on adjacent line replacements. The exact behavior of + /// this function with regards to how it detects those inline changes + /// is currently not defined and will likely change over time. pub fn iter_inline_changes(&self, op: &DiffOp) -> impl Iterator { iter_inline_changes(self, op) } @@ -783,6 +782,21 @@ fn test_virtual_newlines() { 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::>(); + insta::assert_debug_snapshot!(&changes); +} + #[test] fn test_char_diff() { let diff = TextDiff::from_chars("Hello World", "Hallo Welt"); diff --git a/src/text/snapshots/similar__text__line_ops_inline.snap b/src/text/snapshots/similar__text__line_ops_inline.snap new file mode 100644 index 0000000..8d6d51f --- /dev/null +++ b/src/text/snapshots/similar__text__line_ops_inline.snap @@ -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, + }, +]