diff --git a/Cargo.lock b/Cargo.lock index b742a02..ace6718 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "aho-corasick" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] @@ -29,9 +29,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" +checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" [[package]] name = "assert_cmd" @@ -72,9 +72,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bstr" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" +checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" dependencies = [ "lazy_static", "memchr", @@ -83,9 +83,9 @@ dependencies = [ [[package]] name = "byteorder" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cfg-if" @@ -110,9 +110,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ "cfg-if", "crossbeam-utils", @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" +checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94" dependencies = [ "cfg-if", "crossbeam-utils", @@ -144,9 +144,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" +checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278" dependencies = [ "autocfg", "cfg-if", @@ -201,17 +201,6 @@ dependencies = [ "regex", ] -[[package]] -name = "globwalk" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" -dependencies = [ - "bitflags", - "ignore", - "walkdir", -] - [[package]] name = "heck" version = "0.3.2" @@ -248,6 +237,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "itertools" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -256,9 +254,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.86" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" +checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" [[package]] name = "log" @@ -280,9 +278,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" [[package]] name = "memmap" @@ -296,9 +294,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" +checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" dependencies = [ "autocfg", ] @@ -315,9 +313,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.7.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10acf907b94fc1b1a152d08ef97e7759650268cf986bf127f387e602b02c7e5a" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" [[package]] name = "ppv-lite86" @@ -327,9 +325,9 @@ checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "predicates" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb433456c1a57cc93554dea3ce40b4c19c4057e41c55d4a0f3d84ea71c325aa" +checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" dependencies = [ "difference", "predicates-core", @@ -377,9 +375,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ "unicode-xid", ] @@ -460,23 +458,22 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.5" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.4.3" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", "regex-syntax", - "thread_local", ] [[package]] @@ -490,9 +487,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.22" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "remove_dir_all" @@ -532,8 +529,8 @@ dependencies = [ "anyhow", "assert_cmd", "atty", - "globwalk", "ignore", + "itertools", "man", "memmap", "rayon", @@ -576,9 +573,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.60" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" dependencies = [ "proc-macro2", "quote", @@ -663,9 +660,9 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "vec_map" @@ -675,9 +672,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "wait-timeout" @@ -690,9 +687,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", "winapi", diff --git a/Cargo.toml b/Cargo.toml index c7215ba..967ef6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,23 +10,24 @@ license = "MIT" homepage = "https://github.com/chmln/sd" repository = "https://github.com/chmln/sd.git" categories = ["command-line-utilities", "text-processing", "development-tools"] +resolver = "2" [dependencies] -regex = "1.4.3" +regex = "1.5.4" structopt = "0.3.21" rayon = "1.5.0" unescape = "0.1.0" memmap = "0.7.0" tempfile = "3.2.0" thiserror = "1.0.24" -globwalk = "0.8.1" atty = "0.2.14" ignore = "0.4.17" ansi_term = "0.12.1" +itertools = "0.10.0" [dev-dependencies] assert_cmd = "1.0.3" -anyhow = "1.0.38" +anyhow = "1.0.40" [build-dependencies] structopt = "0.3.21" diff --git a/src/cli.rs b/src/cli.rs index ab092db..4664d4f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -15,10 +15,6 @@ pub(crate) struct Options { /// Treat expressions as non-regex strings. pub literal_mode: bool, - #[structopt(short = "r")] - /// Recursively replace files - pub recursive: bool, - #[structopt(short = "n")] /// Limit the number of replacements pub replacements: Option, diff --git a/src/input.rs b/src/input.rs index 9751a6d..45cd690 100644 --- a/src/input.rs +++ b/src/input.rs @@ -69,7 +69,7 @@ impl App { (Source::Files(paths), true) => { let stdout = std::io::stdout(); let mut handle = stdout.lock(); - let print_path = paths.len() > 1; + let separator = "─".repeat(72); paths.iter().try_for_each(|path| { if let Err(_) = Replacer::check_not_empty(File::open(path)?) @@ -78,18 +78,27 @@ impl App { } let file = unsafe { memmap::Mmap::map(&File::open(path)?)? }; + if self.replacer.has_matches(&file) { - if print_path { - writeln!( - handle, - "----- FILE {} -----", - path.display() - )?; + if paths.len() > 1 { + ansi_term::Color::Blue + .paint(path.display().to_string().as_bytes()) + .write_to(&mut handle)?; + + handle.write(b"\n")?; + ansi_term::Color::Blue + .paint(separator.as_bytes()) + .write_to(&mut handle)?; + + handle.write(b"\n")?; } handle .write_all(&self.replacer.replace_preview(&file))?; - writeln!(handle)?; + + if paths.len() > 1 { + handle.write(b"\n")?; + } } Ok(()) diff --git a/src/main.rs b/src/main.rs index 436904a..1af9020 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,10 +12,10 @@ fn main() -> Result<()> { use structopt::StructOpt; let options = cli::Options::from_args(); - let source = if options.recursive { - Source::recursive()? - } else if options.files.len() > 0 { + let source = if options.files.len() > 0 { Source::Files(options.files) + } else if atty::is(atty::Stream::Stdin) { + Source::recursive()? } else { Source::Stdin }; diff --git a/src/replacer.rs b/src/replacer.rs index fa3f6c2..cc9693a 100644 --- a/src/replacer.rs +++ b/src/replacer.rs @@ -1,6 +1,6 @@ use crate::{utils, Error, Result}; use regex::bytes::Regex; -use std::{fs, fs::File, io::prelude::*, path::Path}; +use std::{borrow::Cow, fs, fs::File, io::prelude::*, path::Path}; pub(crate) struct Replacer { regex: Regex, @@ -74,10 +74,7 @@ impl Replacer { Ok(()) } - pub(crate) fn replace<'a>( - &'a self, - content: &'a [u8], - ) -> std::borrow::Cow<'a, [u8]> { + pub(crate) fn replace<'a>(&'a self, content: &'a [u8]) -> Cow<'a, [u8]> { if self.is_literal { self.regex.replacen( &content, @@ -95,32 +92,81 @@ impl Replacer { pub(crate) fn replace_preview<'a>( &'a self, - content: &[u8], - ) -> std::borrow::Cow<'a, [u8]> { - 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); + content: &'a [u8], + ) -> Cow<'a, [u8]> { + use ansi_term::Color; + use itertools::Itertools; + use regex::bytes::Replacer; + + let captures = self + .regex + .captures_iter(content) + .enumerate() + .collect::>(); + let num_captures = captures.len(); + + if num_captures == 0 { + return Cow::Borrowed(content); + } + + let surrounding_text = self.regex.split(content).collect::>(); + let mut output = Vec::::with_capacity(5000); + + captures.into_iter().for_each(|(capture_index, capture)| { + let text_before = surrounding_text.get(capture_index).unwrap(); + let text_after = surrounding_text.get(capture_index + 1); + + let l_pos = text_before + .iter() + .positions(|c| c == &b'\n') + .collect::>(); + + if l_pos.len() > 0 { + if let Some(i) = l_pos + .get(l_pos.len() - 3) + .or_else(|| l_pos.get(l_pos.len() - 2)) + .or_else(|| l_pos.get(l_pos.len() - 1)) + { + output.extend_from_slice(&text_before[*i..]); + } + } + + output.extend_from_slice( + Color::Green.prefix().to_string().as_bytes(), + ); + + if self.is_literal { + regex::bytes::NoExpand(&self.replace_with) + .replace_append(&capture, &mut output); + } else { + (&*self.replace_with).replace_append(&capture, &mut output); + } + + output.extend_from_slice( + Color::Green.suffix().to_string().as_bytes(), + ); + + if let Some(text_after) = text_after { + let pos = text_after + .iter() + .positions(|c| c == &b'\n') + .collect::>(); + + if let Some(i) = + pos.get(2).or_else(|| pos.get(1)).or_else(|| pos.get(0)) + { + output.extend_from_slice(&text_after[..*i]); + output.push(b'\n'); + if num_captures > 1 && capture_index != num_captures - 1 { + output.extend_from_slice(&[b'.', b'.', b'.', b'\n']) + } } else { - (&*self.replace_with).replace_append(&capture, &mut v); + output.extend_from_slice(&text_after); } - v.extend_from_slice( - ansi_term::Color::Green.suffix().to_string().as_bytes(), - ); } }); - return std::borrow::Cow::Owned(v); + return Cow::Owned(output); } pub(crate) fn replace_file(&self, path: &Path) -> Result<()> { diff --git a/tests/cli.rs b/tests/cli.rs index 8efe355..eb988be 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -81,8 +81,9 @@ mod cli { sd().args(&["-p", "abc\\d+", "", file.path().to_str().unwrap()]) .assert() .success() + .stdout(format!( - "{}{}def\n", + "{}def{}\n", ansi_term::Color::Green.prefix().to_string(), ansi_term::Color::Green.suffix().to_string() ));