diff --git a/Cargo.lock b/Cargo.lock index 646133e..5a5c28e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" dependencies = [ "anstyle", - "bstr", + "bstr 1.6.0", "doc-comment", "predicates", "predicates-core", @@ -107,6 +107,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + [[package]] name = "bstr" version = "1.6.0" @@ -290,7 +299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" dependencies = [ "aho-corasick", - "bstr", + "bstr 1.6.0", "fnv", "log", "regex", @@ -594,6 +603,7 @@ dependencies = [ "memmap2", "rayon", "regex", + "similar", "tempfile", "thiserror", "unescape", @@ -605,6 +615,15 @@ version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +[[package]] +name = "similar" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aeaf503862c419d66959f5d7ca015337d864e9c49485d771b732e2a20453597" +dependencies = [ + "bstr 0.2.17", +] + [[package]] name = "strsim" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index e22d8e0..9bc059a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ ignore = "0.4.20" ansi_term = "0.12.1" is-terminal = "0.4.9" clap = { version = "4.4.3", features = ["derive", "deprecated", "wrap_help"] } +similar = { version = "2.3.0", features = ["inline", "bytes"] } [dev-dependencies] assert_cmd = "2.0.12" diff --git a/src/replacer.rs b/src/replacer.rs index f6d5d21..b947dd3 100644 --- a/src/replacer.rs +++ b/src/replacer.rs @@ -1,5 +1,6 @@ use crate::{utils, Error, Result}; use regex::bytes::Regex; +use similar::{TextDiff, ChangeTag}; use std::{fs, fs::File, io::prelude::*, path::Path}; pub(crate) struct Replacer { @@ -94,28 +95,61 @@ impl Replacer { &'a self, content: &[u8], ) -> std::borrow::Cow<'a, [u8]> { + use ansi_term::{Color, Style}; + + let replaced = self.replace(content); + let diff = TextDiff::from_lines(&*content, &*replaced); + let mut v = Vec::::new(); - let mut captures = self.regex.captures_iter(content); - - self.regex.split(content).for_each(|sur_text| { - use regex::bytes::Replacer; - - v.extend(sur_text); - if let Some(capture) = captures.next() { - v.extend_from_slice( - ansi_term::Color::Green.prefix().to_string().as_bytes(), - ); - if self.is_literal { - regex::bytes::NoExpand(&self.replace_with) - .replace_append(&capture, &mut v); - } else { - (&*self.replace_with).replace_append(&capture, &mut v); + + for group in diff.grouped_ops(3).iter() { + for op in group { + for change in diff.iter_inline_changes(op) { + match change.tag() { + ChangeTag::Delete => { + v.extend(Color::Red.prefix().to_string().as_bytes()); + let idx = match change.old_index() { + Some(old_idx) => format!("{:>3} ", old_idx).into_bytes(), + None => b" ".to_vec(), + }; + v.extend(idx); + v.push(b'-'); + }, + ChangeTag::Insert => { + v.extend(Color::Green.prefix().to_string().as_bytes()); + let idx = match change.new_index() { + Some(new_idx) => format!("{:>3} ", new_idx).into_bytes(), + None => b" ".to_vec(), + }; + v.extend(idx); + v.push(b'+'); + }, + ChangeTag::Equal => { + v.extend(b" "); + }, + } + v.push(b' '); + + for (emphasized, value) in change.iter_strings_lossy() { + if emphasized { + v.extend(Style::new().bold().paint(value).as_bytes()); + } else { + v.extend(value.as_bytes()); + } + } + + match change.tag() { + ChangeTag::Delete => v.extend(Color::Red.suffix().to_string().as_bytes()), + ChangeTag::Insert => v.extend(Color::Green.suffix().to_string().as_bytes()), + ChangeTag::Equal => (), + } + + if change.missing_newline() { + v.push(b'\n'); + } } - v.extend_from_slice( - ansi_term::Color::Green.suffix().to_string().as_bytes(), - ); } - }); + } return std::borrow::Cow::Owned(v); }