1
0
mirror of https://github.com/BurntSushi/ripgrep.git synced 2025-06-04 05:57:39 +02:00

Color --replace text using the match type

Closes #521
This commit is contained in:
James McCoy 2017-10-06 22:15:57 -04:00 committed by Andrew Gallant
parent 214f2bef66
commit 67b835fe2a

View File

@ -3,29 +3,58 @@ use std::fmt;
use std::path::Path; use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use regex::bytes::{Captures, Regex, Replacer}; use regex::bytes::{Captures, Match, Regex, Replacer};
use termcolor::{Color, ColorSpec, ParseColorError, WriteColor}; use termcolor::{Color, ColorSpec, ParseColorError, WriteColor};
use pathutil::strip_prefix; use pathutil::strip_prefix;
use ignore::types::FileTypeDef; use ignore::types::FileTypeDef;
/// Track the start and end of replacements to allow coloring them on output.
#[derive(Debug)]
struct Offset {
start: usize,
end: usize,
}
impl Offset {
fn new(start: usize, end: usize) -> Offset {
Offset { start: start, end: end }
}
}
impl<'m, 'r> From<&'m Match<'r>> for Offset {
fn from(m: &'m Match<'r>) -> Self {
Offset{ start: m.start(), end: m.end() }
}
}
/// CountingReplacer implements the Replacer interface for Regex, /// CountingReplacer implements the Replacer interface for Regex,
/// and counts how often replacement is being performed. /// and counts how often replacement is being performed.
struct CountingReplacer<'r> { struct CountingReplacer<'r> {
replace: &'r [u8], replace: &'r [u8],
count: &'r mut usize, count: &'r mut usize,
offsets: &'r mut Vec<Offset>,
} }
impl<'r> CountingReplacer<'r> { impl<'r> CountingReplacer<'r> {
fn new(replace: &'r [u8], count: &'r mut usize) -> CountingReplacer<'r> { fn new(
CountingReplacer { replace: replace, count: count } replace: &'r [u8],
count: &'r mut usize,
offsets: &'r mut Vec<Offset>,
) -> CountingReplacer<'r> {
CountingReplacer { replace: replace, count: count, offsets: offsets, }
} }
} }
impl<'r> Replacer for CountingReplacer<'r> { impl<'r> Replacer for CountingReplacer<'r> {
fn replace_append(&mut self, caps: &Captures, dst: &mut Vec<u8>) { fn replace_append(&mut self, caps: &Captures, dst: &mut Vec<u8>) {
*self.count += 1; *self.count += 1;
let start = dst.len();
caps.expand(self.replace, dst); caps.expand(self.replace, dst);
let end = dst.len();
if start != end {
self.offsets.push(Offset::new(start, end));
}
} }
} }
@ -283,9 +312,10 @@ impl<W: WriteColor> Printer<W> {
} }
if self.replace.is_some() { if self.replace.is_some() {
let mut count = 0; let mut count = 0;
let mut offsets = Vec::new();
let line = { let line = {
let replacer = CountingReplacer::new( let replacer = CountingReplacer::new(
self.replace.as_ref().unwrap(), &mut count); self.replace.as_ref().unwrap(), &mut count, &mut offsets);
re.replace_all(&buf[start..end], replacer) re.replace_all(&buf[start..end], replacer)
}; };
if self.max_columns.map_or(false, |m| line.len() > m) { if self.max_columns.map_or(false, |m| line.len() > m) {
@ -295,44 +325,40 @@ impl<W: WriteColor> Printer<W> {
self.write_eol(); self.write_eol();
return; return;
} }
self.write(&line); self.write_matched_line(offsets, &*line, false);
if line.last() != Some(&self.eol) {
self.write_eol();
}
} else { } else {
if self.only_matching { let buf = if self.only_matching {
let buf = &buf[start + match_start..start + match_end]; &buf[start + match_start..start + match_end]
self.write_matched_line(re, buf, true);
} else { } else {
self.write_matched_line(re, &buf[start..end], false); &buf[start..end]
};
if self.max_columns.map_or(false, |m| buf.len() > m) {
let count = re.find_iter(buf).count();
let msg = format!("[Omitted long line with {} matches]", count);
self.write_colored(msg.as_bytes(), |colors| colors.matched());
self.write_eol();
return;
} }
let only_match = self.only_matching;
self.write_matched_line(
re.find_iter(buf).map(|x| Offset::from(&x)), buf, only_match);
} }
} }
fn write_matched_line( fn write_matched_line<I>(&mut self, offsets: I, buf: &[u8], only_match: bool)
&mut self, where I: IntoIterator<Item=Offset>,
re: &Regex, {
buf: &[u8],
only_match: bool,
) {
if self.max_columns.map_or(false, |m| buf.len() > m) {
let count = re.find_iter(buf).count();
let msg = format!("[Omitted long line with {} matches]", count);
self.write_colored(msg.as_bytes(), |colors| colors.matched());
self.write_eol();
return;
}
if !self.wtr.supports_color() || self.colors.matched().is_none() { if !self.wtr.supports_color() || self.colors.matched().is_none() {
self.write(buf); self.write(buf);
} else if only_match { } else if only_match {
self.write_colored(buf, |colors| colors.matched()); self.write_colored(buf, |colors| colors.matched());
} else { } else {
let mut last_written = 0; let mut last_written = 0;
for m in re.find_iter(buf) { for o in offsets {
self.write(&buf[last_written..m.start()]); self.write(&buf[last_written..o.start]);
self.write_colored( self.write_colored(
&buf[m.start()..m.end()], |colors| colors.matched()); &buf[o.start..o.end], |colors| colors.matched());
last_written = m.end(); last_written = o.end;
} }
self.write(&buf[last_written..]); self.write(&buf[last_written..]);
} }