grep_printer/
standard.rs

1use std::{
2    cell::{Cell, RefCell},
3    cmp,
4    io::{self, Write},
5    path::Path,
6    sync::Arc,
7    time::Instant,
8};
9
10use {
11    bstr::ByteSlice,
12    grep_matcher::{Match, Matcher},
13    grep_searcher::{
14        LineStep, Searcher, Sink, SinkContext, SinkFinish, SinkMatch,
15    },
16    termcolor::{ColorSpec, NoColor, WriteColor},
17};
18
19use crate::{
20    color::ColorSpecs,
21    counter::CounterWriter,
22    hyperlink::{self, HyperlinkConfig},
23    stats::Stats,
24    util::{
25        DecimalFormatter, PrinterPath, Replacer, Sunk,
26        find_iter_at_in_context, trim_ascii_prefix, trim_line_terminator,
27    },
28};
29
30/// The configuration for the standard printer.
31///
32/// This is manipulated by the StandardBuilder and then referenced by the
33/// actual implementation. Once a printer is build, the configuration is frozen
34/// and cannot changed.
35#[derive(Debug, Clone)]
36struct Config {
37    colors: ColorSpecs,
38    hyperlink: HyperlinkConfig,
39    stats: bool,
40    heading: bool,
41    path: bool,
42    only_matching: bool,
43    per_match: bool,
44    per_match_one_line: bool,
45    replacement: Arc<Option<Vec<u8>>>,
46    max_columns: Option<u64>,
47    max_columns_preview: bool,
48    column: bool,
49    byte_offset: bool,
50    trim_ascii: bool,
51    separator_search: Arc<Option<Vec<u8>>>,
52    separator_context: Arc<Option<Vec<u8>>>,
53    separator_field_match: Arc<Vec<u8>>,
54    separator_field_context: Arc<Vec<u8>>,
55    separator_path: Option<u8>,
56    path_terminator: Option<u8>,
57}
58
59impl Default for Config {
60    fn default() -> Config {
61        Config {
62            colors: ColorSpecs::default(),
63            hyperlink: HyperlinkConfig::default(),
64            stats: false,
65            heading: false,
66            path: true,
67            only_matching: false,
68            per_match: false,
69            per_match_one_line: false,
70            replacement: Arc::new(None),
71            max_columns: None,
72            max_columns_preview: false,
73            column: false,
74            byte_offset: false,
75            trim_ascii: false,
76            separator_search: Arc::new(None),
77            separator_context: Arc::new(Some(b"--".to_vec())),
78            separator_field_match: Arc::new(b":".to_vec()),
79            separator_field_context: Arc::new(b"-".to_vec()),
80            separator_path: None,
81            path_terminator: None,
82        }
83    }
84}
85
86/// A builder for the "standard" grep-like printer.
87///
88/// The builder permits configuring how the printer behaves. Configurable
89/// behavior includes, but is not limited to, limiting the number of matches,
90/// tweaking separators, executing pattern replacements, recording statistics
91/// and setting colors.
92///
93/// Some configuration options, such as the display of line numbers or
94/// contextual lines, are drawn directly from the
95/// `grep_searcher::Searcher`'s configuration.
96///
97/// Once a `Standard` printer is built, its configuration cannot be changed.
98#[derive(Clone, Debug)]
99pub struct StandardBuilder {
100    config: Config,
101}
102
103impl StandardBuilder {
104    /// Return a new builder for configuring the standard printer.
105    pub fn new() -> StandardBuilder {
106        StandardBuilder { config: Config::default() }
107    }
108
109    /// Build a printer using any implementation of `termcolor::WriteColor`.
110    ///
111    /// The implementation of `WriteColor` used here controls whether colors
112    /// are used or not when colors have been configured using the
113    /// `color_specs` method.
114    ///
115    /// For maximum portability, callers should generally use either
116    /// `termcolor::StandardStream` or `termcolor::BufferedStandardStream`
117    /// where appropriate, which will automatically enable colors on Windows
118    /// when possible.
119    ///
120    /// However, callers may also provide an arbitrary writer using the
121    /// `termcolor::Ansi` or `termcolor::NoColor` wrappers, which always enable
122    /// colors via ANSI escapes or always disable colors, respectively.
123    ///
124    /// As a convenience, callers may use `build_no_color` to automatically
125    /// select the `termcolor::NoColor` wrapper to avoid needing to import
126    /// from `termcolor` explicitly.
127    pub fn build<W: WriteColor>(&self, wtr: W) -> Standard<W> {
128        Standard {
129            config: self.config.clone(),
130            wtr: RefCell::new(CounterWriter::new(wtr)),
131            matches: vec![],
132        }
133    }
134
135    /// Build a printer from any implementation of `io::Write` and never emit
136    /// any colors, regardless of the user color specification settings.
137    ///
138    /// This is a convenience routine for
139    /// `StandardBuilder::build(termcolor::NoColor::new(wtr))`.
140    pub fn build_no_color<W: io::Write>(
141        &self,
142        wtr: W,
143    ) -> Standard<NoColor<W>> {
144        self.build(NoColor::new(wtr))
145    }
146
147    /// Set the user color specifications to use for coloring in this printer.
148    ///
149    /// A [`UserColorSpec`](crate::UserColorSpec) can be constructed from
150    /// a string in accordance with the color specification format. See
151    /// the `UserColorSpec` type documentation for more details on the
152    /// format. A [`ColorSpecs`] can then be generated from zero or more
153    /// `UserColorSpec`s.
154    ///
155    /// Regardless of the color specifications provided here, whether color
156    /// is actually used or not is determined by the implementation of
157    /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor`
158    /// is provided to `build`, then no color will ever be printed regardless
159    /// of the color specifications provided here.
160    ///
161    /// This completely overrides any previous color specifications. This does
162    /// not add to any previously provided color specifications on this
163    /// builder.
164    pub fn color_specs(&mut self, specs: ColorSpecs) -> &mut StandardBuilder {
165        self.config.colors = specs;
166        self
167    }
168
169    /// Set the configuration to use for hyperlinks output by this printer.
170    ///
171    /// Regardless of the hyperlink format provided here, whether hyperlinks
172    /// are actually used or not is determined by the implementation of
173    /// `WriteColor` provided to `build`. For example, if `termcolor::NoColor`
174    /// is provided to `build`, then no hyperlinks will ever be printed
175    /// regardless of the format provided here.
176    ///
177    /// This completely overrides any previous hyperlink format.
178    ///
179    /// The default configuration results in not emitting any hyperlinks.
180    pub fn hyperlink(
181        &mut self,
182        config: HyperlinkConfig,
183    ) -> &mut StandardBuilder {
184        self.config.hyperlink = config;
185        self
186    }
187
188    /// Enable the gathering of various aggregate statistics.
189    ///
190    /// When this is enabled (it's disabled by default), statistics will be
191    /// gathered for all uses of `Standard` printer returned by `build`,
192    /// including but not limited to, the total number of matches, the total
193    /// number of bytes searched and the total number of bytes printed.
194    ///
195    /// Aggregate statistics can be accessed via the sink's
196    /// [`StandardSink::stats`] method.
197    ///
198    /// When this is enabled, this printer may need to do extra work in order
199    /// to compute certain statistics, which could cause the search to take
200    /// longer.
201    ///
202    /// For a complete description of available statistics, see [`Stats`].
203    pub fn stats(&mut self, yes: bool) -> &mut StandardBuilder {
204        self.config.stats = yes;
205        self
206    }
207
208    /// Enable the use of "headings" in the printer.
209    ///
210    /// When this is enabled, and if a file path has been given to the printer,
211    /// then the file path will be printed once on its own line before showing
212    /// any matches. If the heading is not the first thing emitted by the
213    /// printer, then a line terminator is printed before the heading.
214    ///
215    /// By default, this option is disabled. When disabled, the printer will
216    /// not show any heading and will instead print the file path (if one is
217    /// given) on the same line as each matching (or context) line.
218    pub fn heading(&mut self, yes: bool) -> &mut StandardBuilder {
219        self.config.heading = yes;
220        self
221    }
222
223    /// When enabled, if a path was given to the printer, then it is shown in
224    /// the output (either as a heading or as a prefix to each matching line).
225    /// When disabled, then no paths are ever included in the output even when
226    /// a path is provided to the printer.
227    ///
228    /// This is enabled by default.
229    pub fn path(&mut self, yes: bool) -> &mut StandardBuilder {
230        self.config.path = yes;
231        self
232    }
233
234    /// Only print the specific matches instead of the entire line containing
235    /// each match. Each match is printed on its own line. When multi line
236    /// search is enabled, then matches spanning multiple lines are printed
237    /// such that only the matching portions of each line are shown.
238    pub fn only_matching(&mut self, yes: bool) -> &mut StandardBuilder {
239        self.config.only_matching = yes;
240        self
241    }
242
243    /// Print at least one line for every match.
244    ///
245    /// This is similar to the `only_matching` option, except the entire line
246    /// is printed for each match. This is typically useful in conjunction with
247    /// the `column` option, which will show the starting column number for
248    /// every match on every line.
249    ///
250    /// When multi-line mode is enabled, each match is printed, including every
251    /// line in the match. As with single line matches, if a line contains
252    /// multiple matches (even if only partially), then that line is printed
253    /// once for each match it participates in, assuming it's the first line in
254    /// that match. In multi-line mode, column numbers only indicate the start
255    /// of a match. Subsequent lines in a multi-line match always have a column
256    /// number of `1`.
257    ///
258    /// When a match contains multiple lines, enabling `per_match_one_line`
259    /// will cause only the first line each in match to be printed.
260    pub fn per_match(&mut self, yes: bool) -> &mut StandardBuilder {
261        self.config.per_match = yes;
262        self
263    }
264
265    /// Print at most one line per match when `per_match` is enabled.
266    ///
267    /// By default, every line in each match found is printed when `per_match`
268    /// is enabled. However, this is sometimes undesirable, e.g., when you
269    /// only ever want one line per match.
270    ///
271    /// This is only applicable when multi-line matching is enabled, since
272    /// otherwise, matches are guaranteed to span one line.
273    ///
274    /// This is disabled by default.
275    pub fn per_match_one_line(&mut self, yes: bool) -> &mut StandardBuilder {
276        self.config.per_match_one_line = yes;
277        self
278    }
279
280    /// Set the bytes that will be used to replace each occurrence of a match
281    /// found.
282    ///
283    /// The replacement bytes given may include references to capturing groups,
284    /// which may either be in index form (e.g., `$2`) or can reference named
285    /// capturing groups if present in the original pattern (e.g., `$foo`).
286    ///
287    /// For documentation on the full format, please see the `Capture` trait's
288    /// `interpolate` method in the
289    /// [grep-printer](https://docs.rs/grep-printer) crate.
290    pub fn replacement(
291        &mut self,
292        replacement: Option<Vec<u8>>,
293    ) -> &mut StandardBuilder {
294        self.config.replacement = Arc::new(replacement);
295        self
296    }
297
298    /// Set the maximum number of columns allowed for each line printed. A
299    /// single column is heuristically defined as a single byte.
300    ///
301    /// If a line is found which exceeds this maximum, then it is replaced
302    /// with a message indicating that the line has been omitted.
303    ///
304    /// The default is to not specify a limit, in which each matching or
305    /// contextual line is printed regardless of how long it is.
306    pub fn max_columns(&mut self, limit: Option<u64>) -> &mut StandardBuilder {
307        self.config.max_columns = limit;
308        self
309    }
310
311    /// When enabled, if a line is found to be over the configured maximum
312    /// column limit (measured in terms of bytes), then a preview of the long
313    /// line will be printed instead.
314    ///
315    /// The preview will correspond to the first `N` *grapheme clusters* of
316    /// the line, where `N` is the limit configured by `max_columns`.
317    ///
318    /// If no limit is set, then enabling this has no effect.
319    ///
320    /// This is disabled by default.
321    pub fn max_columns_preview(&mut self, yes: bool) -> &mut StandardBuilder {
322        self.config.max_columns_preview = yes;
323        self
324    }
325
326    /// Print the column number of the first match in a line.
327    ///
328    /// This option is convenient for use with `per_match` which will print a
329    /// line for every match along with the starting offset for that match.
330    ///
331    /// Column numbers are computed in terms of bytes from the start of the
332    /// line being printed.
333    ///
334    /// This is disabled by default.
335    pub fn column(&mut self, yes: bool) -> &mut StandardBuilder {
336        self.config.column = yes;
337        self
338    }
339
340    /// Print the absolute byte offset of the beginning of each line printed.
341    ///
342    /// The absolute byte offset starts from the beginning of each search and
343    /// is zero based.
344    ///
345    /// If the `only_matching` option is set, then this will print the absolute
346    /// byte offset of the beginning of each match.
347    pub fn byte_offset(&mut self, yes: bool) -> &mut StandardBuilder {
348        self.config.byte_offset = yes;
349        self
350    }
351
352    /// When enabled, all lines will have prefix ASCII whitespace trimmed
353    /// before being written.
354    ///
355    /// This is disabled by default.
356    pub fn trim_ascii(&mut self, yes: bool) -> &mut StandardBuilder {
357        self.config.trim_ascii = yes;
358        self
359    }
360
361    /// Set the separator used between sets of search results.
362    ///
363    /// When this is set, then it will be printed on its own line immediately
364    /// before the results for a single search if and only if a previous search
365    /// had already printed results. In effect, this permits showing a divider
366    /// between sets of search results that does not appear at the beginning
367    /// or end of all search results.
368    ///
369    /// To reproduce the classic grep format, this is typically set to `--`
370    /// (the same as the context separator) if and only if contextual lines
371    /// have been requested, but disabled otherwise.
372    ///
373    /// By default, this is disabled.
374    pub fn separator_search(
375        &mut self,
376        sep: Option<Vec<u8>>,
377    ) -> &mut StandardBuilder {
378        self.config.separator_search = Arc::new(sep);
379        self
380    }
381
382    /// Set the separator used between discontiguous runs of search context,
383    /// but only when the searcher is configured to report contextual lines.
384    ///
385    /// The separator is always printed on its own line, even if it's empty.
386    ///
387    /// If no separator is set, then nothing is printed when a context break
388    /// occurs.
389    ///
390    /// By default, this is set to `--`.
391    pub fn separator_context(
392        &mut self,
393        sep: Option<Vec<u8>>,
394    ) -> &mut StandardBuilder {
395        self.config.separator_context = Arc::new(sep);
396        self
397    }
398
399    /// Set the separator used between fields emitted for matching lines.
400    ///
401    /// For example, when the searcher has line numbers enabled, this printer
402    /// will print the line number before each matching line. The bytes given
403    /// here will be written after the line number but before the matching
404    /// line.
405    ///
406    /// By default, this is set to `:`.
407    pub fn separator_field_match(
408        &mut self,
409        sep: Vec<u8>,
410    ) -> &mut StandardBuilder {
411        self.config.separator_field_match = Arc::new(sep);
412        self
413    }
414
415    /// Set the separator used between fields emitted for context lines.
416    ///
417    /// For example, when the searcher has line numbers enabled, this printer
418    /// will print the line number before each context line. The bytes given
419    /// here will be written after the line number but before the context
420    /// line.
421    ///
422    /// By default, this is set to `-`.
423    pub fn separator_field_context(
424        &mut self,
425        sep: Vec<u8>,
426    ) -> &mut StandardBuilder {
427        self.config.separator_field_context = Arc::new(sep);
428        self
429    }
430
431    /// Set the path separator used when printing file paths.
432    ///
433    /// When a printer is configured with a file path, and when a match is
434    /// found, that file path will be printed (either as a heading or as a
435    /// prefix to each matching or contextual line, depending on other
436    /// configuration settings). Typically, printing is done by emitting the
437    /// file path as is. However, this setting provides the ability to use a
438    /// different path separator from what the current environment has
439    /// configured.
440    ///
441    /// A typical use for this option is to permit cygwin users on Windows to
442    /// set the path separator to `/` instead of using the system default of
443    /// `\`.
444    pub fn separator_path(&mut self, sep: Option<u8>) -> &mut StandardBuilder {
445        self.config.separator_path = sep;
446        self
447    }
448
449    /// Set the path terminator used.
450    ///
451    /// The path terminator is a byte that is printed after every file path
452    /// emitted by this printer.
453    ///
454    /// If no path terminator is set (the default), then paths are terminated
455    /// by either new lines (for when `heading` is enabled) or the match or
456    /// context field separators (e.g., `:` or `-`).
457    pub fn path_terminator(
458        &mut self,
459        terminator: Option<u8>,
460    ) -> &mut StandardBuilder {
461        self.config.path_terminator = terminator;
462        self
463    }
464}
465
466/// The standard printer, which implements grep-like formatting, including
467/// color support.
468///
469/// A default printer can be created with either of the `Standard::new` or
470/// `Standard::new_no_color` constructors. However, there are a considerable
471/// number of options that configure this printer's output. Those options can
472/// be configured using [`StandardBuilder`].
473///
474/// This type is generic over `W`, which represents any implementation
475/// of the `termcolor::WriteColor` trait. If colors are not desired,
476/// then the `new_no_color` constructor can be used, or, alternatively,
477/// the `termcolor::NoColor` adapter can be used to wrap any `io::Write`
478/// implementation without enabling any colors.
479#[derive(Clone, Debug)]
480pub struct Standard<W> {
481    config: Config,
482    wtr: RefCell<CounterWriter<W>>,
483    matches: Vec<Match>,
484}
485
486impl<W: WriteColor> Standard<W> {
487    /// Return a standard printer with a default configuration that writes
488    /// matches to the given writer.
489    ///
490    /// The writer should be an implementation of `termcolor::WriteColor`
491    /// and not just a bare implementation of `io::Write`. To use a normal
492    /// `io::Write` implementation (simultaneously sacrificing colors), use
493    /// the `new_no_color` constructor.
494    pub fn new(wtr: W) -> Standard<W> {
495        StandardBuilder::new().build(wtr)
496    }
497}
498
499impl<W: io::Write> Standard<NoColor<W>> {
500    /// Return a standard printer with a default configuration that writes
501    /// matches to the given writer.
502    ///
503    /// The writer can be any implementation of `io::Write`. With this
504    /// constructor, the printer will never emit colors.
505    pub fn new_no_color(wtr: W) -> Standard<NoColor<W>> {
506        StandardBuilder::new().build_no_color(wtr)
507    }
508}
509
510impl<W: WriteColor> Standard<W> {
511    /// Return an implementation of `Sink` for the standard printer.
512    ///
513    /// This does not associate the printer with a file path, which means this
514    /// implementation will never print a file path along with the matches.
515    pub fn sink<'s, M: Matcher>(
516        &'s mut self,
517        matcher: M,
518    ) -> StandardSink<'static, 's, M, W> {
519        let interpolator =
520            hyperlink::Interpolator::new(&self.config.hyperlink);
521        let stats = if self.config.stats { Some(Stats::new()) } else { None };
522        let needs_match_granularity = self.needs_match_granularity();
523        StandardSink {
524            matcher,
525            standard: self,
526            replacer: Replacer::new(),
527            interpolator,
528            path: None,
529            start_time: Instant::now(),
530            match_count: 0,
531            binary_byte_offset: None,
532            stats,
533            needs_match_granularity,
534        }
535    }
536
537    /// Return an implementation of `Sink` associated with a file path.
538    ///
539    /// When the printer is associated with a path, then it may, depending on
540    /// its configuration, print the path along with the matches found.
541    pub fn sink_with_path<'p, 's, M, P>(
542        &'s mut self,
543        matcher: M,
544        path: &'p P,
545    ) -> StandardSink<'p, 's, M, W>
546    where
547        M: Matcher,
548        P: ?Sized + AsRef<Path>,
549    {
550        if !self.config.path {
551            return self.sink(matcher);
552        }
553        let interpolator =
554            hyperlink::Interpolator::new(&self.config.hyperlink);
555        let stats = if self.config.stats { Some(Stats::new()) } else { None };
556        let ppath = PrinterPath::new(path.as_ref())
557            .with_separator(self.config.separator_path);
558        let needs_match_granularity = self.needs_match_granularity();
559        StandardSink {
560            matcher,
561            standard: self,
562            replacer: Replacer::new(),
563            interpolator,
564            path: Some(ppath),
565            start_time: Instant::now(),
566            match_count: 0,
567            binary_byte_offset: None,
568            stats,
569            needs_match_granularity,
570        }
571    }
572
573    /// Returns true if and only if the configuration of the printer requires
574    /// us to find each individual match in the lines reported by the searcher.
575    ///
576    /// We care about this distinction because finding each individual match
577    /// costs more, so we only do it when we need to.
578    fn needs_match_granularity(&self) -> bool {
579        let supports_color = self.wtr.borrow().supports_color();
580        let match_colored = !self.config.colors.matched().is_none();
581
582        // Coloring requires identifying each individual match.
583        (supports_color && match_colored)
584        // The column feature requires finding the position of the first match.
585        || self.config.column
586        // Requires finding each match for performing replacement.
587        || self.config.replacement.is_some()
588        // Emitting a line for each match requires finding each match.
589        || self.config.per_match
590        // Emitting only the match requires finding each match.
591        || self.config.only_matching
592        // Computing certain statistics requires finding each match.
593        || self.config.stats
594    }
595}
596
597impl<W> Standard<W> {
598    /// Returns true if and only if this printer has written at least one byte
599    /// to the underlying writer during any of the previous searches.
600    pub fn has_written(&self) -> bool {
601        self.wtr.borrow().total_count() > 0
602    }
603
604    /// Return a mutable reference to the underlying writer.
605    pub fn get_mut(&mut self) -> &mut W {
606        self.wtr.get_mut().get_mut()
607    }
608
609    /// Consume this printer and return back ownership of the underlying
610    /// writer.
611    pub fn into_inner(self) -> W {
612        self.wtr.into_inner().into_inner()
613    }
614}
615
616/// An implementation of `Sink` associated with a matcher and an optional file
617/// path for the standard printer.
618///
619/// A `Sink` can be created via the [`Standard::sink`] or
620/// [`Standard::sink_with_path`] methods, depending on whether you want to
621/// include a file path in the printer's output.
622///
623/// Building a `StandardSink` is cheap, and callers should create a new one
624/// for each thing that is searched. After a search has completed, callers may
625/// query this sink for information such as whether a match occurred or whether
626/// binary data was found (and if so, the offset at which it occurred).
627///
628/// This type is generic over a few type parameters:
629///
630/// * `'p` refers to the lifetime of the file path, if one is provided. When
631/// no file path is given, then this is `'static`.
632/// * `'s` refers to the lifetime of the [`Standard`] printer that this type
633/// borrows.
634/// * `M` refers to the type of matcher used by
635/// `grep_searcher::Searcher` that is reporting results to this sink.
636/// * `W` refers to the underlying writer that this printer is writing its
637/// output to.
638#[derive(Debug)]
639pub struct StandardSink<'p, 's, M: Matcher, W> {
640    matcher: M,
641    standard: &'s mut Standard<W>,
642    replacer: Replacer<M>,
643    interpolator: hyperlink::Interpolator,
644    path: Option<PrinterPath<'p>>,
645    start_time: Instant,
646    match_count: u64,
647    binary_byte_offset: Option<u64>,
648    stats: Option<Stats>,
649    needs_match_granularity: bool,
650}
651
652impl<'p, 's, M: Matcher, W: WriteColor> StandardSink<'p, 's, M, W> {
653    /// Returns true if and only if this printer received a match in the
654    /// previous search.
655    ///
656    /// This is unaffected by the result of searches before the previous
657    /// search on this sink.
658    pub fn has_match(&self) -> bool {
659        self.match_count > 0
660    }
661
662    /// Return the total number of matches reported to this sink.
663    ///
664    /// This corresponds to the number of times `Sink::matched` is called
665    /// on the previous search.
666    ///
667    /// This is unaffected by the result of searches before the previous
668    /// search on this sink.
669    pub fn match_count(&self) -> u64 {
670        self.match_count
671    }
672
673    /// If binary data was found in the previous search, this returns the
674    /// offset at which the binary data was first detected.
675    ///
676    /// The offset returned is an absolute offset relative to the entire
677    /// set of bytes searched.
678    ///
679    /// This is unaffected by the result of searches before the previous
680    /// search. e.g., If the search prior to the previous search found binary
681    /// data but the previous search found no binary data, then this will
682    /// return `None`.
683    pub fn binary_byte_offset(&self) -> Option<u64> {
684        self.binary_byte_offset
685    }
686
687    /// Return a reference to the stats produced by the printer for all
688    /// searches executed on this sink.
689    ///
690    /// This only returns stats if they were requested via the
691    /// [`StandardBuilder`] configuration.
692    pub fn stats(&self) -> Option<&Stats> {
693        self.stats.as_ref()
694    }
695
696    /// Execute the matcher over the given bytes and record the match
697    /// locations if the current configuration demands match granularity.
698    fn record_matches(
699        &mut self,
700        searcher: &Searcher,
701        bytes: &[u8],
702        range: std::ops::Range<usize>,
703    ) -> io::Result<()> {
704        self.standard.matches.clear();
705        if !self.needs_match_granularity {
706            return Ok(());
707        }
708        // If printing requires knowing the location of each individual match,
709        // then compute and stored those right now for use later. While this
710        // adds an extra copy for storing the matches, we do amortize the
711        // allocation for it and this greatly simplifies the printing logic to
712        // the extent that it's easy to ensure that we never do more than
713        // one search to find the matches (well, for replacements, we do one
714        // additional search to perform the actual replacement).
715        let matches = &mut self.standard.matches;
716        find_iter_at_in_context(
717            searcher,
718            &self.matcher,
719            bytes,
720            range.clone(),
721            |m| {
722                let (s, e) = (m.start() - range.start, m.end() - range.start);
723                matches.push(Match::new(s, e));
724                true
725            },
726        )?;
727        // Don't report empty matches appearing at the end of the bytes.
728        if !matches.is_empty()
729            && matches.last().unwrap().is_empty()
730            && matches.last().unwrap().start() >= range.end
731        {
732            matches.pop().unwrap();
733        }
734        Ok(())
735    }
736
737    /// If the configuration specifies a replacement, then this executes the
738    /// replacement, lazily allocating memory if necessary.
739    ///
740    /// To access the result of a replacement, use `replacer.replacement()`.
741    fn replace(
742        &mut self,
743        searcher: &Searcher,
744        bytes: &[u8],
745        range: std::ops::Range<usize>,
746    ) -> io::Result<()> {
747        self.replacer.clear();
748        if self.standard.config.replacement.is_some() {
749            let replacement =
750                (*self.standard.config.replacement).as_ref().unwrap();
751            self.replacer.replace_all(
752                searcher,
753                &self.matcher,
754                bytes,
755                range,
756                replacement,
757            )?;
758        }
759        Ok(())
760    }
761}
762
763impl<'p, 's, M: Matcher, W: WriteColor> Sink for StandardSink<'p, 's, M, W> {
764    type Error = io::Error;
765
766    fn matched(
767        &mut self,
768        searcher: &Searcher,
769        mat: &SinkMatch<'_>,
770    ) -> Result<bool, io::Error> {
771        self.match_count += 1;
772
773        self.record_matches(
774            searcher,
775            mat.buffer(),
776            mat.bytes_range_in_buffer(),
777        )?;
778        self.replace(searcher, mat.buffer(), mat.bytes_range_in_buffer())?;
779
780        if let Some(ref mut stats) = self.stats {
781            stats.add_matches(self.standard.matches.len() as u64);
782            stats.add_matched_lines(mat.lines().count() as u64);
783        }
784        if searcher.binary_detection().convert_byte().is_some() {
785            if self.binary_byte_offset.is_some() {
786                return Ok(false);
787            }
788        }
789        StandardImpl::from_match(searcher, self, mat).sink()?;
790        Ok(true)
791    }
792
793    fn context(
794        &mut self,
795        searcher: &Searcher,
796        ctx: &SinkContext<'_>,
797    ) -> Result<bool, io::Error> {
798        self.standard.matches.clear();
799        self.replacer.clear();
800
801        if searcher.invert_match() {
802            self.record_matches(searcher, ctx.bytes(), 0..ctx.bytes().len())?;
803            self.replace(searcher, ctx.bytes(), 0..ctx.bytes().len())?;
804        }
805        if searcher.binary_detection().convert_byte().is_some() {
806            if self.binary_byte_offset.is_some() {
807                return Ok(false);
808            }
809        }
810
811        StandardImpl::from_context(searcher, self, ctx).sink()?;
812        Ok(true)
813    }
814
815    fn context_break(
816        &mut self,
817        searcher: &Searcher,
818    ) -> Result<bool, io::Error> {
819        StandardImpl::new(searcher, self).write_context_separator()?;
820        Ok(true)
821    }
822
823    fn binary_data(
824        &mut self,
825        searcher: &Searcher,
826        binary_byte_offset: u64,
827    ) -> Result<bool, io::Error> {
828        if searcher.binary_detection().quit_byte().is_some() {
829            if let Some(ref path) = self.path {
830                log::debug!(
831                    "ignoring {path}: found binary data at \
832                     offset {binary_byte_offset}",
833                    path = path.as_path().display(),
834                );
835            }
836        }
837        self.binary_byte_offset = Some(binary_byte_offset);
838        Ok(true)
839    }
840
841    fn begin(&mut self, _searcher: &Searcher) -> Result<bool, io::Error> {
842        self.standard.wtr.borrow_mut().reset_count();
843        self.start_time = Instant::now();
844        self.match_count = 0;
845        self.binary_byte_offset = None;
846        Ok(true)
847    }
848
849    fn finish(
850        &mut self,
851        searcher: &Searcher,
852        finish: &SinkFinish,
853    ) -> Result<(), io::Error> {
854        if let Some(offset) = self.binary_byte_offset {
855            StandardImpl::new(searcher, self).write_binary_message(offset)?;
856        }
857        if let Some(stats) = self.stats.as_mut() {
858            stats.add_elapsed(self.start_time.elapsed());
859            stats.add_searches(1);
860            if self.match_count > 0 {
861                stats.add_searches_with_match(1);
862            }
863            stats.add_bytes_searched(finish.byte_count());
864            stats.add_bytes_printed(self.standard.wtr.borrow().count());
865        }
866        Ok(())
867    }
868}
869
870/// The actual implementation of the standard printer. This couples together
871/// the searcher, the sink implementation and information about the match.
872///
873/// A StandardImpl is initialized every time a match or a contextual line is
874/// reported.
875#[derive(Debug)]
876struct StandardImpl<'a, M: Matcher, W> {
877    searcher: &'a Searcher,
878    sink: &'a StandardSink<'a, 'a, M, W>,
879    sunk: Sunk<'a>,
880    /// Set to true if and only if we are writing a match with color.
881    in_color_match: Cell<bool>,
882}
883
884impl<'a, M: Matcher, W: WriteColor> StandardImpl<'a, M, W> {
885    /// Bundle self with a searcher and return the core implementation of Sink.
886    fn new(
887        searcher: &'a Searcher,
888        sink: &'a StandardSink<'_, '_, M, W>,
889    ) -> StandardImpl<'a, M, W> {
890        StandardImpl {
891            searcher,
892            sink,
893            sunk: Sunk::empty(),
894            in_color_match: Cell::new(false),
895        }
896    }
897
898    /// Bundle self with a searcher and return the core implementation of Sink
899    /// for use with handling matching lines.
900    fn from_match(
901        searcher: &'a Searcher,
902        sink: &'a StandardSink<'_, '_, M, W>,
903        mat: &'a SinkMatch<'a>,
904    ) -> StandardImpl<'a, M, W> {
905        let sunk = Sunk::from_sink_match(
906            mat,
907            &sink.standard.matches,
908            sink.replacer.replacement(),
909        );
910        StandardImpl { sunk, ..StandardImpl::new(searcher, sink) }
911    }
912
913    /// Bundle self with a searcher and return the core implementation of Sink
914    /// for use with handling contextual lines.
915    fn from_context(
916        searcher: &'a Searcher,
917        sink: &'a StandardSink<'_, '_, M, W>,
918        ctx: &'a SinkContext<'a>,
919    ) -> StandardImpl<'a, M, W> {
920        let sunk = Sunk::from_sink_context(
921            ctx,
922            &sink.standard.matches,
923            sink.replacer.replacement(),
924        );
925        StandardImpl { sunk, ..StandardImpl::new(searcher, sink) }
926    }
927
928    fn sink(&self) -> io::Result<()> {
929        self.write_search_prelude()?;
930        if self.sunk.matches().is_empty() {
931            if self.multi_line() && !self.is_context() {
932                self.sink_fast_multi_line()
933            } else {
934                self.sink_fast()
935            }
936        } else {
937            if self.multi_line() && !self.is_context() {
938                self.sink_slow_multi_line()
939            } else {
940                self.sink_slow()
941            }
942        }
943    }
944
945    /// Print matches (limited to one line) quickly by avoiding the detection
946    /// of each individual match in the lines reported in the given
947    /// `SinkMatch`.
948    ///
949    /// This should only be used when the configuration does not demand match
950    /// granularity and the searcher is not in multi line mode.
951    fn sink_fast(&self) -> io::Result<()> {
952        debug_assert!(self.sunk.matches().is_empty());
953        debug_assert!(!self.multi_line() || self.is_context());
954
955        self.write_prelude(
956            self.sunk.absolute_byte_offset(),
957            self.sunk.line_number(),
958            None,
959        )?;
960        self.write_line(self.sunk.bytes())
961    }
962
963    /// Print matches (possibly spanning more than one line) quickly by
964    /// avoiding the detection of each individual match in the lines reported
965    /// in the given `SinkMatch`.
966    ///
967    /// This should only be used when the configuration does not demand match
968    /// granularity. This may be used when the searcher is in multi line mode.
969    fn sink_fast_multi_line(&self) -> io::Result<()> {
970        debug_assert!(self.sunk.matches().is_empty());
971        // This isn't actually a required invariant for using this method,
972        // but if we wind up here and multi line mode is disabled, then we
973        // should still treat it as a bug since we should be using matched_fast
974        // instead.
975        debug_assert!(self.multi_line());
976
977        let line_term = self.searcher.line_terminator().as_byte();
978        let mut absolute_byte_offset = self.sunk.absolute_byte_offset();
979        for (i, line) in self.sunk.lines(line_term).enumerate() {
980            self.write_prelude(
981                absolute_byte_offset,
982                self.sunk.line_number().map(|n| n + i as u64),
983                None,
984            )?;
985            absolute_byte_offset += line.len() as u64;
986
987            self.write_line(line)?;
988        }
989        Ok(())
990    }
991
992    /// Print a matching line where the configuration of the printer requires
993    /// finding each individual match (e.g., for coloring).
994    fn sink_slow(&self) -> io::Result<()> {
995        debug_assert!(!self.sunk.matches().is_empty());
996        debug_assert!(!self.multi_line() || self.is_context());
997
998        if self.config().only_matching {
999            for &m in self.sunk.matches() {
1000                self.write_prelude(
1001                    self.sunk.absolute_byte_offset() + m.start() as u64,
1002                    self.sunk.line_number(),
1003                    Some(m.start() as u64 + 1),
1004                )?;
1005
1006                let buf = &self.sunk.bytes()[m];
1007                self.write_colored_line(&[Match::new(0, buf.len())], buf)?;
1008            }
1009        } else if self.config().per_match {
1010            for &m in self.sunk.matches() {
1011                self.write_prelude(
1012                    self.sunk.absolute_byte_offset() + m.start() as u64,
1013                    self.sunk.line_number(),
1014                    Some(m.start() as u64 + 1),
1015                )?;
1016                self.write_colored_line(&[m], self.sunk.bytes())?;
1017            }
1018        } else {
1019            self.write_prelude(
1020                self.sunk.absolute_byte_offset(),
1021                self.sunk.line_number(),
1022                Some(self.sunk.matches()[0].start() as u64 + 1),
1023            )?;
1024            self.write_colored_line(self.sunk.matches(), self.sunk.bytes())?;
1025        }
1026        Ok(())
1027    }
1028
1029    fn sink_slow_multi_line(&self) -> io::Result<()> {
1030        debug_assert!(!self.sunk.matches().is_empty());
1031        debug_assert!(self.multi_line());
1032
1033        if self.config().only_matching {
1034            return self.sink_slow_multi_line_only_matching();
1035        } else if self.config().per_match {
1036            return self.sink_slow_multi_per_match();
1037        }
1038
1039        let line_term = self.searcher.line_terminator().as_byte();
1040        let bytes = self.sunk.bytes();
1041        let matches = self.sunk.matches();
1042        let mut midx = 0;
1043        let mut count = 0;
1044        let mut stepper = LineStep::new(line_term, 0, bytes.len());
1045        while let Some((start, end)) = stepper.next(bytes) {
1046            let mut line = Match::new(start, end);
1047            self.write_prelude(
1048                self.sunk.absolute_byte_offset() + line.start() as u64,
1049                self.sunk.line_number().map(|n| n + count),
1050                Some(matches[0].start() as u64 + 1),
1051            )?;
1052            count += 1;
1053            self.trim_ascii_prefix(bytes, &mut line);
1054            if self.exceeds_max_columns(&bytes[line]) {
1055                self.write_exceeded_line(bytes, line, matches, &mut midx)?;
1056            } else {
1057                self.write_colored_matches(bytes, line, matches, &mut midx)?;
1058                self.write_line_term()?;
1059            }
1060        }
1061        Ok(())
1062    }
1063
1064    fn sink_slow_multi_line_only_matching(&self) -> io::Result<()> {
1065        let line_term = self.searcher.line_terminator().as_byte();
1066        let spec = self.config().colors.matched();
1067        let bytes = self.sunk.bytes();
1068        let matches = self.sunk.matches();
1069        let mut midx = 0;
1070        let mut count = 0;
1071        let mut stepper = LineStep::new(line_term, 0, bytes.len());
1072        while let Some((start, end)) = stepper.next(bytes) {
1073            let mut line = Match::new(start, end);
1074            self.trim_line_terminator(bytes, &mut line);
1075            self.trim_ascii_prefix(bytes, &mut line);
1076            while !line.is_empty() {
1077                if matches[midx].end() <= line.start() {
1078                    if midx + 1 < matches.len() {
1079                        midx += 1;
1080                        continue;
1081                    } else {
1082                        break;
1083                    }
1084                }
1085                let m = matches[midx];
1086
1087                if line.start() < m.start() {
1088                    let upto = cmp::min(line.end(), m.start());
1089                    line = line.with_start(upto);
1090                } else {
1091                    let upto = cmp::min(line.end(), m.end());
1092                    self.write_prelude(
1093                        self.sunk.absolute_byte_offset() + m.start() as u64,
1094                        self.sunk.line_number().map(|n| n + count),
1095                        Some(m.start() as u64 + 1),
1096                    )?;
1097
1098                    let this_line = line.with_end(upto);
1099                    line = line.with_start(upto);
1100                    if self.exceeds_max_columns(&bytes[this_line]) {
1101                        self.write_exceeded_line(
1102                            bytes, this_line, matches, &mut midx,
1103                        )?;
1104                    } else {
1105                        self.write_spec(spec, &bytes[this_line])?;
1106                        self.write_line_term()?;
1107                    }
1108                }
1109            }
1110            count += 1;
1111        }
1112        Ok(())
1113    }
1114
1115    fn sink_slow_multi_per_match(&self) -> io::Result<()> {
1116        let line_term = self.searcher.line_terminator().as_byte();
1117        let spec = self.config().colors.matched();
1118        let bytes = self.sunk.bytes();
1119        for &m in self.sunk.matches() {
1120            let mut count = 0;
1121            let mut stepper = LineStep::new(line_term, 0, bytes.len());
1122            while let Some((start, end)) = stepper.next(bytes) {
1123                let mut line = Match::new(start, end);
1124                if line.start() >= m.end() {
1125                    break;
1126                } else if line.end() <= m.start() {
1127                    count += 1;
1128                    continue;
1129                }
1130                self.write_prelude(
1131                    self.sunk.absolute_byte_offset() + line.start() as u64,
1132                    self.sunk.line_number().map(|n| n + count),
1133                    Some(m.start().saturating_sub(line.start()) as u64 + 1),
1134                )?;
1135                count += 1;
1136                self.trim_line_terminator(bytes, &mut line);
1137                self.trim_ascii_prefix(bytes, &mut line);
1138                if self.exceeds_max_columns(&bytes[line]) {
1139                    self.write_exceeded_line(bytes, line, &[m], &mut 0)?;
1140                    continue;
1141                }
1142
1143                while !line.is_empty() {
1144                    if m.end() <= line.start() {
1145                        self.write(&bytes[line])?;
1146                        line = line.with_start(line.end());
1147                    } else if line.start() < m.start() {
1148                        let upto = cmp::min(line.end(), m.start());
1149                        self.write(&bytes[line.with_end(upto)])?;
1150                        line = line.with_start(upto);
1151                    } else {
1152                        let upto = cmp::min(line.end(), m.end());
1153                        self.write_spec(spec, &bytes[line.with_end(upto)])?;
1154                        line = line.with_start(upto);
1155                    }
1156                }
1157                self.write_line_term()?;
1158                // It turns out that vimgrep really only wants one line per
1159                // match, even when a match spans multiple lines. So when
1160                // that option is enabled, we just quit after printing the
1161                // first line.
1162                //
1163                // See: https://github.com/BurntSushi/ripgrep/issues/1866
1164                if self.config().per_match_one_line {
1165                    break;
1166                }
1167            }
1168        }
1169        Ok(())
1170    }
1171
1172    /// Write the beginning part of a matching line. This (may) include things
1173    /// like the file path, line number among others, depending on the
1174    /// configuration and the parameters given.
1175    #[inline(always)]
1176    fn write_prelude(
1177        &self,
1178        absolute_byte_offset: u64,
1179        line_number: Option<u64>,
1180        column: Option<u64>,
1181    ) -> io::Result<()> {
1182        let mut prelude = PreludeWriter::new(self);
1183        prelude.start(line_number, column)?;
1184        prelude.write_path()?;
1185        prelude.write_line_number(line_number)?;
1186        prelude.write_column_number(column)?;
1187        prelude.write_byte_offset(absolute_byte_offset)?;
1188        prelude.end()
1189    }
1190
1191    #[inline(always)]
1192    fn write_line(&self, line: &[u8]) -> io::Result<()> {
1193        let line = if !self.config().trim_ascii {
1194            line
1195        } else {
1196            let lineterm = self.searcher.line_terminator();
1197            let full_range = Match::new(0, line.len());
1198            let range = trim_ascii_prefix(lineterm, line, full_range);
1199            &line[range]
1200        };
1201        if self.exceeds_max_columns(line) {
1202            let range = Match::new(0, line.len());
1203            self.write_exceeded_line(
1204                line,
1205                range,
1206                self.sunk.matches(),
1207                &mut 0,
1208            )?;
1209        } else {
1210            // self.write_trim(line)?;
1211            self.write(line)?;
1212            if !self.has_line_terminator(line) {
1213                self.write_line_term()?;
1214            }
1215        }
1216        Ok(())
1217    }
1218
1219    fn write_colored_line(
1220        &self,
1221        matches: &[Match],
1222        bytes: &[u8],
1223    ) -> io::Result<()> {
1224        // If we know we aren't going to emit color, then we can go faster.
1225        let spec = self.config().colors.matched();
1226        if !self.wtr().borrow().supports_color() || spec.is_none() {
1227            return self.write_line(bytes);
1228        }
1229
1230        let mut line = Match::new(0, bytes.len());
1231        self.trim_ascii_prefix(bytes, &mut line);
1232        if self.exceeds_max_columns(bytes) {
1233            self.write_exceeded_line(bytes, line, matches, &mut 0)
1234        } else {
1235            self.write_colored_matches(bytes, line, matches, &mut 0)?;
1236            self.write_line_term()?;
1237            Ok(())
1238        }
1239    }
1240
1241    /// Write the `line` portion of `bytes`, with appropriate coloring for
1242    /// each `match`, starting at `match_index`.
1243    ///
1244    /// This accounts for trimming any whitespace prefix and will *never* print
1245    /// a line terminator. If a match exceeds the range specified by `line`,
1246    /// then only the part of the match within `line` (if any) is printed.
1247    fn write_colored_matches(
1248        &self,
1249        bytes: &[u8],
1250        mut line: Match,
1251        matches: &[Match],
1252        match_index: &mut usize,
1253    ) -> io::Result<()> {
1254        self.trim_line_terminator(bytes, &mut line);
1255        if matches.is_empty() {
1256            self.write(&bytes[line])?;
1257            return Ok(());
1258        }
1259        self.start_line_highlight()?;
1260        while !line.is_empty() {
1261            if matches[*match_index].end() <= line.start() {
1262                if *match_index + 1 < matches.len() {
1263                    *match_index += 1;
1264                    continue;
1265                } else {
1266                    self.end_color_match()?;
1267                    self.write(&bytes[line])?;
1268                    break;
1269                }
1270            }
1271
1272            let m = matches[*match_index];
1273            if line.start() < m.start() {
1274                let upto = cmp::min(line.end(), m.start());
1275                self.end_color_match()?;
1276                self.write(&bytes[line.with_end(upto)])?;
1277                line = line.with_start(upto);
1278            } else {
1279                let upto = cmp::min(line.end(), m.end());
1280                self.start_color_match()?;
1281                self.write(&bytes[line.with_end(upto)])?;
1282                line = line.with_start(upto);
1283            }
1284        }
1285        self.end_color_match()?;
1286        self.end_line_highlight()?;
1287        Ok(())
1288    }
1289
1290    fn write_exceeded_line(
1291        &self,
1292        bytes: &[u8],
1293        mut line: Match,
1294        matches: &[Match],
1295        match_index: &mut usize,
1296    ) -> io::Result<()> {
1297        if self.config().max_columns_preview {
1298            let original = line;
1299            let end = bytes[line]
1300                .grapheme_indices()
1301                .map(|(_, end, _)| end)
1302                .take(self.config().max_columns.unwrap_or(0) as usize)
1303                .last()
1304                .unwrap_or(0)
1305                + line.start();
1306            line = line.with_end(end);
1307            self.write_colored_matches(bytes, line, matches, match_index)?;
1308
1309            if matches.is_empty() {
1310                self.write(b" [... omitted end of long line]")?;
1311            } else {
1312                let remaining = matches
1313                    .iter()
1314                    .filter(|m| {
1315                        m.start() >= line.end() && m.start() < original.end()
1316                    })
1317                    .count();
1318                let tense = if remaining == 1 { "match" } else { "matches" };
1319                write!(
1320                    self.wtr().borrow_mut(),
1321                    " [... {} more {}]",
1322                    remaining,
1323                    tense,
1324                )?;
1325            }
1326            self.write_line_term()?;
1327            return Ok(());
1328        }
1329        if self.sunk.original_matches().is_empty() {
1330            if self.is_context() {
1331                self.write(b"[Omitted long context line]")?;
1332            } else {
1333                self.write(b"[Omitted long matching line]")?;
1334            }
1335        } else {
1336            if self.config().only_matching {
1337                if self.is_context() {
1338                    self.write(b"[Omitted long context line]")?;
1339                } else {
1340                    self.write(b"[Omitted long matching line]")?;
1341                }
1342            } else {
1343                write!(
1344                    self.wtr().borrow_mut(),
1345                    "[Omitted long line with {} matches]",
1346                    self.sunk.original_matches().len(),
1347                )?;
1348            }
1349        }
1350        self.write_line_term()?;
1351        Ok(())
1352    }
1353
1354    /// If this printer has a file path associated with it, then this will
1355    /// write that path to the underlying writer followed by a line terminator.
1356    /// (If a path terminator is set, then that is used instead of the line
1357    /// terminator.)
1358    fn write_path_line(&self) -> io::Result<()> {
1359        if let Some(path) = self.path() {
1360            self.write_path_hyperlink(path)?;
1361            if let Some(term) = self.config().path_terminator {
1362                self.write(&[term])?;
1363            } else {
1364                self.write_line_term()?;
1365            }
1366        }
1367        Ok(())
1368    }
1369
1370    fn write_search_prelude(&self) -> io::Result<()> {
1371        let this_search_written = self.wtr().borrow().count() > 0;
1372        if this_search_written {
1373            return Ok(());
1374        }
1375        if let Some(ref sep) = *self.config().separator_search {
1376            let ever_written = self.wtr().borrow().total_count() > 0;
1377            if ever_written {
1378                self.write(sep)?;
1379                self.write_line_term()?;
1380            }
1381        }
1382        if self.config().heading {
1383            self.write_path_line()?;
1384        }
1385        Ok(())
1386    }
1387
1388    fn write_binary_message(&self, offset: u64) -> io::Result<()> {
1389        if !self.sink.has_match() {
1390            return Ok(());
1391        }
1392
1393        let bin = self.searcher.binary_detection();
1394        if let Some(byte) = bin.quit_byte() {
1395            if let Some(path) = self.path() {
1396                self.write_path_hyperlink(path)?;
1397                self.write(b": ")?;
1398            }
1399            let remainder = format!(
1400                "WARNING: stopped searching binary file after match \
1401                 (found {:?} byte around offset {})\n",
1402                [byte].as_bstr(),
1403                offset,
1404            );
1405            self.write(remainder.as_bytes())?;
1406        } else if let Some(byte) = bin.convert_byte() {
1407            if let Some(path) = self.path() {
1408                self.write_path_hyperlink(path)?;
1409                self.write(b": ")?;
1410            }
1411            let remainder = format!(
1412                "binary file matches (found {:?} byte around offset {})\n",
1413                [byte].as_bstr(),
1414                offset,
1415            );
1416            self.write(remainder.as_bytes())?;
1417        }
1418        Ok(())
1419    }
1420
1421    fn write_context_separator(&self) -> io::Result<()> {
1422        if let Some(ref sep) = *self.config().separator_context {
1423            self.write(sep)?;
1424            self.write_line_term()?;
1425        }
1426        Ok(())
1427    }
1428
1429    fn write_line_term(&self) -> io::Result<()> {
1430        self.write(self.searcher.line_terminator().as_bytes())
1431    }
1432
1433    fn write_spec(&self, spec: &ColorSpec, buf: &[u8]) -> io::Result<()> {
1434        let mut wtr = self.wtr().borrow_mut();
1435        wtr.set_color(spec)?;
1436        wtr.write_all(buf)?;
1437        wtr.reset()?;
1438        Ok(())
1439    }
1440
1441    fn write_path(&self, path: &PrinterPath) -> io::Result<()> {
1442        let mut wtr = self.wtr().borrow_mut();
1443        wtr.set_color(self.config().colors.path())?;
1444        wtr.write_all(path.as_bytes())?;
1445        wtr.reset()
1446    }
1447
1448    fn write_path_hyperlink(&self, path: &PrinterPath) -> io::Result<()> {
1449        let status = self.start_hyperlink(path, None, None)?;
1450        self.write_path(path)?;
1451        self.end_hyperlink(status)
1452    }
1453
1454    fn start_hyperlink(
1455        &self,
1456        path: &PrinterPath,
1457        line_number: Option<u64>,
1458        column: Option<u64>,
1459    ) -> io::Result<hyperlink::InterpolatorStatus> {
1460        let Some(hyperpath) = path.as_hyperlink() else {
1461            return Ok(hyperlink::InterpolatorStatus::inactive());
1462        };
1463        let values =
1464            hyperlink::Values::new(hyperpath).line(line_number).column(column);
1465        self.sink.interpolator.begin(&values, &mut *self.wtr().borrow_mut())
1466    }
1467
1468    fn end_hyperlink(
1469        &self,
1470        status: hyperlink::InterpolatorStatus,
1471    ) -> io::Result<()> {
1472        self.sink.interpolator.finish(status, &mut *self.wtr().borrow_mut())
1473    }
1474
1475    fn start_color_match(&self) -> io::Result<()> {
1476        if self.in_color_match.get() {
1477            return Ok(());
1478        }
1479        self.wtr().borrow_mut().set_color(self.config().colors.matched())?;
1480        self.in_color_match.set(true);
1481        Ok(())
1482    }
1483
1484    fn end_color_match(&self) -> io::Result<()> {
1485        if !self.in_color_match.get() {
1486            return Ok(());
1487        }
1488        if self.highlight_on() {
1489            self.wtr()
1490                .borrow_mut()
1491                .set_color(self.config().colors.highlight())?;
1492        } else {
1493            self.wtr().borrow_mut().reset()?;
1494        }
1495        self.in_color_match.set(false);
1496        Ok(())
1497    }
1498
1499    fn highlight_on(&self) -> bool {
1500        !self.config().colors.highlight().is_none() && !self.is_context()
1501    }
1502
1503    fn start_line_highlight(&self) -> io::Result<()> {
1504        if self.highlight_on() {
1505            self.wtr()
1506                .borrow_mut()
1507                .set_color(self.config().colors.highlight())?;
1508        }
1509        Ok(())
1510    }
1511
1512    fn end_line_highlight(&self) -> io::Result<()> {
1513        if self.highlight_on() {
1514            self.wtr().borrow_mut().reset()?;
1515        }
1516        Ok(())
1517    }
1518
1519    fn write(&self, buf: &[u8]) -> io::Result<()> {
1520        self.wtr().borrow_mut().write_all(buf)
1521    }
1522
1523    fn trim_line_terminator(&self, buf: &[u8], line: &mut Match) {
1524        trim_line_terminator(&self.searcher, buf, line);
1525    }
1526
1527    fn has_line_terminator(&self, buf: &[u8]) -> bool {
1528        self.searcher.line_terminator().is_suffix(buf)
1529    }
1530
1531    fn is_context(&self) -> bool {
1532        self.sunk.context_kind().is_some()
1533    }
1534
1535    /// Return the underlying configuration for this printer.
1536    fn config(&self) -> &'a Config {
1537        &self.sink.standard.config
1538    }
1539
1540    /// Return the underlying writer that we are printing to.
1541    fn wtr(&self) -> &'a RefCell<CounterWriter<W>> {
1542        &self.sink.standard.wtr
1543    }
1544
1545    /// Return the path associated with this printer, if one exists.
1546    fn path(&self) -> Option<&'a PrinterPath<'a>> {
1547        self.sink.path.as_ref()
1548    }
1549
1550    /// Return the appropriate field separator based on whether we are emitting
1551    /// matching or contextual lines.
1552    fn separator_field(&self) -> &[u8] {
1553        if self.is_context() {
1554            &self.config().separator_field_context
1555        } else {
1556            &self.config().separator_field_match
1557        }
1558    }
1559
1560    /// Returns true if and only if the given line exceeds the maximum number
1561    /// of columns set. If no maximum is set, then this always returns false.
1562    fn exceeds_max_columns(&self, line: &[u8]) -> bool {
1563        self.config().max_columns.map_or(false, |m| line.len() as u64 > m)
1564    }
1565
1566    /// Returns true if and only if the searcher may report matches over
1567    /// multiple lines.
1568    ///
1569    /// Note that this doesn't just return whether the searcher is in multi
1570    /// line mode, but also checks if the matter can match over multiple lines.
1571    /// If it can't, then we don't need multi line handling, even if the
1572    /// searcher has multi line mode enabled.
1573    fn multi_line(&self) -> bool {
1574        self.searcher.multi_line_with_matcher(&self.sink.matcher)
1575    }
1576
1577    /// Trim prefix ASCII spaces from the given slice and return the
1578    /// corresponding range.
1579    ///
1580    /// This stops trimming a prefix as soon as it sees non-whitespace or a
1581    /// line terminator.
1582    fn trim_ascii_prefix(&self, slice: &[u8], range: &mut Match) {
1583        if !self.config().trim_ascii {
1584            return;
1585        }
1586        let lineterm = self.searcher.line_terminator();
1587        *range = trim_ascii_prefix(lineterm, slice, *range)
1588    }
1589}
1590
1591/// A writer for the prelude (the beginning part of a matching line).
1592///
1593/// This encapsulates the state needed to print the prelude.
1594struct PreludeWriter<'a, M: Matcher, W> {
1595    std: &'a StandardImpl<'a, M, W>,
1596    next_separator: PreludeSeparator,
1597    field_separator: &'a [u8],
1598    interp_status: hyperlink::InterpolatorStatus,
1599}
1600
1601/// A type of separator used in the prelude
1602enum PreludeSeparator {
1603    /// No separator.
1604    None,
1605    /// The field separator, either for a matching or contextual line.
1606    FieldSeparator,
1607    /// The path terminator.
1608    PathTerminator,
1609}
1610
1611impl<'a, M: Matcher, W: WriteColor> PreludeWriter<'a, M, W> {
1612    /// Creates a new prelude printer.
1613    #[inline(always)]
1614    fn new(std: &'a StandardImpl<'a, M, W>) -> PreludeWriter<'a, M, W> {
1615        PreludeWriter {
1616            std,
1617            next_separator: PreludeSeparator::None,
1618            field_separator: std.separator_field(),
1619            interp_status: hyperlink::InterpolatorStatus::inactive(),
1620        }
1621    }
1622
1623    /// Starts the prelude with a hyperlink when applicable.
1624    ///
1625    /// If a heading was written, and the hyperlink format is invariant on
1626    /// the line number, then this doesn't hyperlink each line prelude, as it
1627    /// wouldn't point to the line anyway. The hyperlink on the heading should
1628    /// be sufficient and less confusing.
1629    #[inline(always)]
1630    fn start(
1631        &mut self,
1632        line_number: Option<u64>,
1633        column: Option<u64>,
1634    ) -> io::Result<()> {
1635        let Some(path) = self.std.path() else { return Ok(()) };
1636        if self.config().hyperlink.format().is_line_dependent()
1637            || !self.config().heading
1638        {
1639            self.interp_status =
1640                self.std.start_hyperlink(path, line_number, column)?;
1641        }
1642        Ok(())
1643    }
1644
1645    /// Ends the prelude and writes the remaining output.
1646    #[inline(always)]
1647    fn end(&mut self) -> io::Result<()> {
1648        self.std.end_hyperlink(std::mem::replace(
1649            &mut self.interp_status,
1650            hyperlink::InterpolatorStatus::inactive(),
1651        ))?;
1652        self.write_separator()
1653    }
1654
1655    /// If this printer has a file path associated with it, then this will
1656    /// write that path to the underlying writer followed by the given field
1657    /// separator. (If a path terminator is set, then that is used instead of
1658    /// the field separator.)
1659    #[inline(always)]
1660    fn write_path(&mut self) -> io::Result<()> {
1661        // The prelude doesn't handle headings, only what comes before a match
1662        // on the same line. So if we are emitting paths in headings, we should
1663        // not do it here on each line.
1664        if self.config().heading {
1665            return Ok(());
1666        }
1667        let Some(path) = self.std.path() else { return Ok(()) };
1668        self.write_separator()?;
1669        self.std.write_path(path)?;
1670
1671        self.next_separator = if self.config().path_terminator.is_some() {
1672            PreludeSeparator::PathTerminator
1673        } else {
1674            PreludeSeparator::FieldSeparator
1675        };
1676        Ok(())
1677    }
1678
1679    /// Writes the line number field if present.
1680    #[inline(always)]
1681    fn write_line_number(&mut self, line: Option<u64>) -> io::Result<()> {
1682        let Some(line_number) = line else { return Ok(()) };
1683        self.write_separator()?;
1684        let n = DecimalFormatter::new(line_number);
1685        self.std.write_spec(self.config().colors.line(), n.as_bytes())?;
1686        self.next_separator = PreludeSeparator::FieldSeparator;
1687        Ok(())
1688    }
1689
1690    /// Writes the column number field if present and configured to do so.
1691    #[inline(always)]
1692    fn write_column_number(&mut self, column: Option<u64>) -> io::Result<()> {
1693        if !self.config().column {
1694            return Ok(());
1695        }
1696        let Some(column_number) = column else { return Ok(()) };
1697        self.write_separator()?;
1698        let n = DecimalFormatter::new(column_number);
1699        self.std.write_spec(self.config().colors.column(), n.as_bytes())?;
1700        self.next_separator = PreludeSeparator::FieldSeparator;
1701        Ok(())
1702    }
1703
1704    /// Writes the byte offset field if configured to do so.
1705    #[inline(always)]
1706    fn write_byte_offset(&mut self, offset: u64) -> io::Result<()> {
1707        if !self.config().byte_offset {
1708            return Ok(());
1709        }
1710        self.write_separator()?;
1711        let n = DecimalFormatter::new(offset);
1712        self.std.write_spec(self.config().colors.column(), n.as_bytes())?;
1713        self.next_separator = PreludeSeparator::FieldSeparator;
1714        Ok(())
1715    }
1716
1717    /// Writes the separator defined by the preceding field.
1718    ///
1719    /// This is called before writing the contents of a field, and at
1720    /// the end of the prelude.
1721    #[inline(always)]
1722    fn write_separator(&mut self) -> io::Result<()> {
1723        match self.next_separator {
1724            PreludeSeparator::None => {}
1725            PreludeSeparator::FieldSeparator => {
1726                self.std.write(self.field_separator)?;
1727            }
1728            PreludeSeparator::PathTerminator => {
1729                if let Some(term) = self.config().path_terminator {
1730                    self.std.write(&[term])?;
1731                }
1732            }
1733        }
1734        self.next_separator = PreludeSeparator::None;
1735        Ok(())
1736    }
1737
1738    #[inline(always)]
1739    fn config(&self) -> &Config {
1740        self.std.config()
1741    }
1742}
1743
1744#[cfg(test)]
1745mod tests {
1746    use grep_matcher::LineTerminator;
1747    use grep_regex::{RegexMatcher, RegexMatcherBuilder};
1748    use grep_searcher::SearcherBuilder;
1749    use termcolor::{Ansi, NoColor};
1750
1751    use super::{ColorSpecs, Standard, StandardBuilder};
1752
1753    const SHERLOCK: &'static str = "\
1754For the Doctor Watsons of this world, as opposed to the Sherlock
1755Holmeses, success in the province of detective work must always
1756be, to a very large extent, the result of luck. Sherlock Holmes
1757can extract a clew from a wisp of straw or a flake of cigar ash;
1758but Doctor Watson has to have it taken out for him and dusted,
1759and exhibited clearly, with a label attached.\
1760";
1761
1762    #[allow(dead_code)]
1763    const SHERLOCK_CRLF: &'static str = "\
1764For the Doctor Watsons of this world, as opposed to the Sherlock\r
1765Holmeses, success in the province of detective work must always\r
1766be, to a very large extent, the result of luck. Sherlock Holmes\r
1767can extract a clew from a wisp of straw or a flake of cigar ash;\r
1768but Doctor Watson has to have it taken out for him and dusted,\r
1769and exhibited clearly, with a label attached.\
1770";
1771
1772    fn printer_contents(printer: &mut Standard<NoColor<Vec<u8>>>) -> String {
1773        String::from_utf8(printer.get_mut().get_ref().to_owned()).unwrap()
1774    }
1775
1776    fn printer_contents_ansi(printer: &mut Standard<Ansi<Vec<u8>>>) -> String {
1777        String::from_utf8(printer.get_mut().get_ref().to_owned()).unwrap()
1778    }
1779
1780    #[test]
1781    fn reports_match() {
1782        let matcher = RegexMatcher::new("Sherlock").unwrap();
1783        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
1784        let mut sink = printer.sink(&matcher);
1785        SearcherBuilder::new()
1786            .line_number(false)
1787            .build()
1788            .search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
1789            .unwrap();
1790        assert!(sink.has_match());
1791
1792        let matcher = RegexMatcher::new("zzzzz").unwrap();
1793        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
1794        let mut sink = printer.sink(&matcher);
1795        SearcherBuilder::new()
1796            .line_number(false)
1797            .build()
1798            .search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
1799            .unwrap();
1800        assert!(!sink.has_match());
1801    }
1802
1803    #[test]
1804    fn reports_binary() {
1805        use grep_searcher::BinaryDetection;
1806
1807        let matcher = RegexMatcher::new("Sherlock").unwrap();
1808        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
1809        let mut sink = printer.sink(&matcher);
1810        SearcherBuilder::new()
1811            .line_number(false)
1812            .build()
1813            .search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
1814            .unwrap();
1815        assert!(sink.binary_byte_offset().is_none());
1816
1817        let matcher = RegexMatcher::new(".+").unwrap();
1818        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
1819        let mut sink = printer.sink(&matcher);
1820        SearcherBuilder::new()
1821            .line_number(false)
1822            .binary_detection(BinaryDetection::quit(b'\x00'))
1823            .build()
1824            .search_reader(&matcher, &b"abc\x00"[..], &mut sink)
1825            .unwrap();
1826        assert_eq!(sink.binary_byte_offset(), Some(3));
1827    }
1828
1829    #[test]
1830    fn reports_stats() {
1831        use std::time::Duration;
1832
1833        let matcher = RegexMatcher::new("Sherlock|opposed").unwrap();
1834        let mut printer =
1835            StandardBuilder::new().stats(true).build(NoColor::new(vec![]));
1836        let stats = {
1837            let mut sink = printer.sink(&matcher);
1838            SearcherBuilder::new()
1839                .line_number(false)
1840                .build()
1841                .search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
1842                .unwrap();
1843            sink.stats().unwrap().clone()
1844        };
1845        let buf = printer_contents(&mut printer);
1846
1847        assert!(stats.elapsed() > Duration::default());
1848        assert_eq!(stats.searches(), 1);
1849        assert_eq!(stats.searches_with_match(), 1);
1850        assert_eq!(stats.bytes_searched(), SHERLOCK.len() as u64);
1851        assert_eq!(stats.bytes_printed(), buf.len() as u64);
1852        assert_eq!(stats.matched_lines(), 2);
1853        assert_eq!(stats.matches(), 3);
1854    }
1855
1856    #[test]
1857    fn reports_stats_multiple() {
1858        use std::time::Duration;
1859
1860        let matcher = RegexMatcher::new("Sherlock|opposed").unwrap();
1861        let mut printer =
1862            StandardBuilder::new().stats(true).build(NoColor::new(vec![]));
1863        let stats = {
1864            let mut sink = printer.sink(&matcher);
1865            SearcherBuilder::new()
1866                .line_number(false)
1867                .build()
1868                .search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
1869                .unwrap();
1870            SearcherBuilder::new()
1871                .line_number(false)
1872                .build()
1873                .search_reader(&matcher, &b"zzzzzzzzzz"[..], &mut sink)
1874                .unwrap();
1875            SearcherBuilder::new()
1876                .line_number(false)
1877                .build()
1878                .search_reader(&matcher, SHERLOCK.as_bytes(), &mut sink)
1879                .unwrap();
1880            sink.stats().unwrap().clone()
1881        };
1882        let buf = printer_contents(&mut printer);
1883
1884        assert!(stats.elapsed() > Duration::default());
1885        assert_eq!(stats.searches(), 3);
1886        assert_eq!(stats.searches_with_match(), 2);
1887        assert_eq!(stats.bytes_searched(), 10 + 2 * SHERLOCK.len() as u64);
1888        assert_eq!(stats.bytes_printed(), buf.len() as u64);
1889        assert_eq!(stats.matched_lines(), 4);
1890        assert_eq!(stats.matches(), 6);
1891    }
1892
1893    #[test]
1894    fn context_break() {
1895        let matcher = RegexMatcher::new("Watson").unwrap();
1896        let mut printer = StandardBuilder::new()
1897            .separator_context(Some(b"--abc--".to_vec()))
1898            .build(NoColor::new(vec![]));
1899        SearcherBuilder::new()
1900            .line_number(false)
1901            .before_context(1)
1902            .after_context(1)
1903            .build()
1904            .search_reader(
1905                &matcher,
1906                SHERLOCK.as_bytes(),
1907                printer.sink(&matcher),
1908            )
1909            .unwrap();
1910
1911        let got = printer_contents(&mut printer);
1912        let expected = "\
1913For the Doctor Watsons of this world, as opposed to the Sherlock
1914Holmeses, success in the province of detective work must always
1915--abc--
1916can extract a clew from a wisp of straw or a flake of cigar ash;
1917but Doctor Watson has to have it taken out for him and dusted,
1918and exhibited clearly, with a label attached.
1919";
1920        assert_eq_printed!(expected, got);
1921    }
1922
1923    #[test]
1924    fn context_break_multiple_no_heading() {
1925        let matcher = RegexMatcher::new("Watson").unwrap();
1926        let mut printer = StandardBuilder::new()
1927            .separator_search(Some(b"--xyz--".to_vec()))
1928            .separator_context(Some(b"--abc--".to_vec()))
1929            .build(NoColor::new(vec![]));
1930
1931        SearcherBuilder::new()
1932            .line_number(false)
1933            .before_context(1)
1934            .after_context(1)
1935            .build()
1936            .search_reader(
1937                &matcher,
1938                SHERLOCK.as_bytes(),
1939                printer.sink(&matcher),
1940            )
1941            .unwrap();
1942        SearcherBuilder::new()
1943            .line_number(false)
1944            .before_context(1)
1945            .after_context(1)
1946            .build()
1947            .search_reader(
1948                &matcher,
1949                SHERLOCK.as_bytes(),
1950                printer.sink(&matcher),
1951            )
1952            .unwrap();
1953
1954        let got = printer_contents(&mut printer);
1955        let expected = "\
1956For the Doctor Watsons of this world, as opposed to the Sherlock
1957Holmeses, success in the province of detective work must always
1958--abc--
1959can extract a clew from a wisp of straw or a flake of cigar ash;
1960but Doctor Watson has to have it taken out for him and dusted,
1961and exhibited clearly, with a label attached.
1962--xyz--
1963For the Doctor Watsons of this world, as opposed to the Sherlock
1964Holmeses, success in the province of detective work must always
1965--abc--
1966can extract a clew from a wisp of straw or a flake of cigar ash;
1967but Doctor Watson has to have it taken out for him and dusted,
1968and exhibited clearly, with a label attached.
1969";
1970        assert_eq_printed!(expected, got);
1971    }
1972
1973    #[test]
1974    fn context_break_multiple_heading() {
1975        let matcher = RegexMatcher::new("Watson").unwrap();
1976        let mut printer = StandardBuilder::new()
1977            .heading(true)
1978            .separator_search(Some(b"--xyz--".to_vec()))
1979            .separator_context(Some(b"--abc--".to_vec()))
1980            .build(NoColor::new(vec![]));
1981
1982        SearcherBuilder::new()
1983            .line_number(false)
1984            .before_context(1)
1985            .after_context(1)
1986            .build()
1987            .search_reader(
1988                &matcher,
1989                SHERLOCK.as_bytes(),
1990                printer.sink(&matcher),
1991            )
1992            .unwrap();
1993        SearcherBuilder::new()
1994            .line_number(false)
1995            .before_context(1)
1996            .after_context(1)
1997            .build()
1998            .search_reader(
1999                &matcher,
2000                SHERLOCK.as_bytes(),
2001                printer.sink(&matcher),
2002            )
2003            .unwrap();
2004
2005        let got = printer_contents(&mut printer);
2006        let expected = "\
2007For the Doctor Watsons of this world, as opposed to the Sherlock
2008Holmeses, success in the province of detective work must always
2009--abc--
2010can extract a clew from a wisp of straw or a flake of cigar ash;
2011but Doctor Watson has to have it taken out for him and dusted,
2012and exhibited clearly, with a label attached.
2013--xyz--
2014For the Doctor Watsons of this world, as opposed to the Sherlock
2015Holmeses, success in the province of detective work must always
2016--abc--
2017can extract a clew from a wisp of straw or a flake of cigar ash;
2018but Doctor Watson has to have it taken out for him and dusted,
2019and exhibited clearly, with a label attached.
2020";
2021        assert_eq_printed!(expected, got);
2022    }
2023
2024    #[test]
2025    fn path() {
2026        let matcher = RegexMatcher::new("Watson").unwrap();
2027        let mut printer =
2028            StandardBuilder::new().path(false).build(NoColor::new(vec![]));
2029        SearcherBuilder::new()
2030            .line_number(true)
2031            .build()
2032            .search_reader(
2033                &matcher,
2034                SHERLOCK.as_bytes(),
2035                printer.sink_with_path(&matcher, "sherlock"),
2036            )
2037            .unwrap();
2038
2039        let got = printer_contents(&mut printer);
2040        let expected = "\
20411:For the Doctor Watsons of this world, as opposed to the Sherlock
20425:but Doctor Watson has to have it taken out for him and dusted,
2043";
2044        assert_eq_printed!(expected, got);
2045    }
2046
2047    #[test]
2048    fn separator_field() {
2049        let matcher = RegexMatcher::new("Watson").unwrap();
2050        let mut printer = StandardBuilder::new()
2051            .separator_field_match(b"!!".to_vec())
2052            .separator_field_context(b"^^".to_vec())
2053            .build(NoColor::new(vec![]));
2054        SearcherBuilder::new()
2055            .line_number(false)
2056            .before_context(1)
2057            .after_context(1)
2058            .build()
2059            .search_reader(
2060                &matcher,
2061                SHERLOCK.as_bytes(),
2062                printer.sink_with_path(&matcher, "sherlock"),
2063            )
2064            .unwrap();
2065
2066        let got = printer_contents(&mut printer);
2067        let expected = "\
2068sherlock!!For the Doctor Watsons of this world, as opposed to the Sherlock
2069sherlock^^Holmeses, success in the province of detective work must always
2070--
2071sherlock^^can extract a clew from a wisp of straw or a flake of cigar ash;
2072sherlock!!but Doctor Watson has to have it taken out for him and dusted,
2073sherlock^^and exhibited clearly, with a label attached.
2074";
2075        assert_eq_printed!(expected, got);
2076    }
2077
2078    #[test]
2079    fn separator_path() {
2080        let matcher = RegexMatcher::new("Watson").unwrap();
2081        let mut printer = StandardBuilder::new()
2082            .separator_path(Some(b'Z'))
2083            .build(NoColor::new(vec![]));
2084        SearcherBuilder::new()
2085            .line_number(false)
2086            .build()
2087            .search_reader(
2088                &matcher,
2089                SHERLOCK.as_bytes(),
2090                printer.sink_with_path(&matcher, "books/sherlock"),
2091            )
2092            .unwrap();
2093
2094        let got = printer_contents(&mut printer);
2095        let expected = "\
2096booksZsherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
2097booksZsherlock:but Doctor Watson has to have it taken out for him and dusted,
2098";
2099        assert_eq_printed!(expected, got);
2100    }
2101
2102    #[test]
2103    fn path_terminator() {
2104        let matcher = RegexMatcher::new("Watson").unwrap();
2105        let mut printer = StandardBuilder::new()
2106            .path_terminator(Some(b'Z'))
2107            .build(NoColor::new(vec![]));
2108        SearcherBuilder::new()
2109            .line_number(false)
2110            .build()
2111            .search_reader(
2112                &matcher,
2113                SHERLOCK.as_bytes(),
2114                printer.sink_with_path(&matcher, "books/sherlock"),
2115            )
2116            .unwrap();
2117
2118        let got = printer_contents(&mut printer);
2119        let expected = "\
2120books/sherlockZFor the Doctor Watsons of this world, as opposed to the Sherlock
2121books/sherlockZbut Doctor Watson has to have it taken out for him and dusted,
2122";
2123        assert_eq_printed!(expected, got);
2124    }
2125
2126    #[test]
2127    fn heading() {
2128        let matcher = RegexMatcher::new("Watson").unwrap();
2129        let mut printer =
2130            StandardBuilder::new().heading(true).build(NoColor::new(vec![]));
2131        SearcherBuilder::new()
2132            .line_number(false)
2133            .build()
2134            .search_reader(
2135                &matcher,
2136                SHERLOCK.as_bytes(),
2137                printer.sink_with_path(&matcher, "sherlock"),
2138            )
2139            .unwrap();
2140
2141        let got = printer_contents(&mut printer);
2142        let expected = "\
2143sherlock
2144For the Doctor Watsons of this world, as opposed to the Sherlock
2145but Doctor Watson has to have it taken out for him and dusted,
2146";
2147        assert_eq_printed!(expected, got);
2148    }
2149
2150    #[test]
2151    fn no_heading() {
2152        let matcher = RegexMatcher::new("Watson").unwrap();
2153        let mut printer =
2154            StandardBuilder::new().heading(false).build(NoColor::new(vec![]));
2155        SearcherBuilder::new()
2156            .line_number(false)
2157            .build()
2158            .search_reader(
2159                &matcher,
2160                SHERLOCK.as_bytes(),
2161                printer.sink_with_path(&matcher, "sherlock"),
2162            )
2163            .unwrap();
2164
2165        let got = printer_contents(&mut printer);
2166        let expected = "\
2167sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
2168sherlock:but Doctor Watson has to have it taken out for him and dusted,
2169";
2170        assert_eq_printed!(expected, got);
2171    }
2172
2173    #[test]
2174    fn no_heading_multiple() {
2175        let matcher = RegexMatcher::new("Watson").unwrap();
2176        let mut printer =
2177            StandardBuilder::new().heading(false).build(NoColor::new(vec![]));
2178        SearcherBuilder::new()
2179            .line_number(false)
2180            .build()
2181            .search_reader(
2182                &matcher,
2183                SHERLOCK.as_bytes(),
2184                printer.sink_with_path(&matcher, "sherlock"),
2185            )
2186            .unwrap();
2187
2188        let matcher = RegexMatcher::new("Sherlock").unwrap();
2189        SearcherBuilder::new()
2190            .line_number(false)
2191            .build()
2192            .search_reader(
2193                &matcher,
2194                SHERLOCK.as_bytes(),
2195                printer.sink_with_path(&matcher, "sherlock"),
2196            )
2197            .unwrap();
2198
2199        let got = printer_contents(&mut printer);
2200        let expected = "\
2201sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
2202sherlock:but Doctor Watson has to have it taken out for him and dusted,
2203sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
2204sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
2205";
2206        assert_eq_printed!(expected, got);
2207    }
2208
2209    #[test]
2210    fn heading_multiple() {
2211        let matcher = RegexMatcher::new("Watson").unwrap();
2212        let mut printer =
2213            StandardBuilder::new().heading(true).build(NoColor::new(vec![]));
2214        SearcherBuilder::new()
2215            .line_number(false)
2216            .build()
2217            .search_reader(
2218                &matcher,
2219                SHERLOCK.as_bytes(),
2220                printer.sink_with_path(&matcher, "sherlock"),
2221            )
2222            .unwrap();
2223
2224        let matcher = RegexMatcher::new("Sherlock").unwrap();
2225        SearcherBuilder::new()
2226            .line_number(false)
2227            .build()
2228            .search_reader(
2229                &matcher,
2230                SHERLOCK.as_bytes(),
2231                printer.sink_with_path(&matcher, "sherlock"),
2232            )
2233            .unwrap();
2234
2235        let got = printer_contents(&mut printer);
2236        let expected = "\
2237sherlock
2238For the Doctor Watsons of this world, as opposed to the Sherlock
2239but Doctor Watson has to have it taken out for him and dusted,
2240sherlock
2241For the Doctor Watsons of this world, as opposed to the Sherlock
2242be, to a very large extent, the result of luck. Sherlock Holmes
2243";
2244        assert_eq_printed!(expected, got);
2245    }
2246
2247    #[test]
2248    fn trim_ascii() {
2249        let matcher = RegexMatcher::new("Watson").unwrap();
2250        let mut printer = StandardBuilder::new()
2251            .trim_ascii(true)
2252            .build(NoColor::new(vec![]));
2253        SearcherBuilder::new()
2254            .line_number(false)
2255            .build()
2256            .search_reader(
2257                &matcher,
2258                "   Watson".as_bytes(),
2259                printer.sink(&matcher),
2260            )
2261            .unwrap();
2262
2263        let got = printer_contents(&mut printer);
2264        let expected = "\
2265Watson
2266";
2267        assert_eq_printed!(expected, got);
2268    }
2269
2270    #[test]
2271    fn trim_ascii_multi_line() {
2272        let matcher = RegexMatcher::new("(?s:.{0})Watson").unwrap();
2273        let mut printer = StandardBuilder::new()
2274            .trim_ascii(true)
2275            .stats(true)
2276            .build(NoColor::new(vec![]));
2277        SearcherBuilder::new()
2278            .line_number(false)
2279            .multi_line(true)
2280            .build()
2281            .search_reader(
2282                &matcher,
2283                "   Watson".as_bytes(),
2284                printer.sink(&matcher),
2285            )
2286            .unwrap();
2287
2288        let got = printer_contents(&mut printer);
2289        let expected = "\
2290Watson
2291";
2292        assert_eq_printed!(expected, got);
2293    }
2294
2295    #[test]
2296    fn trim_ascii_with_line_term() {
2297        let matcher = RegexMatcher::new("Watson").unwrap();
2298        let mut printer = StandardBuilder::new()
2299            .trim_ascii(true)
2300            .build(NoColor::new(vec![]));
2301        SearcherBuilder::new()
2302            .line_number(true)
2303            .before_context(1)
2304            .build()
2305            .search_reader(
2306                &matcher,
2307                "\n   Watson".as_bytes(),
2308                printer.sink(&matcher),
2309            )
2310            .unwrap();
2311
2312        let got = printer_contents(&mut printer);
2313        let expected = "\
23141-
23152:Watson
2316";
2317        assert_eq_printed!(expected, got);
2318    }
2319
2320    #[test]
2321    fn line_number() {
2322        let matcher = RegexMatcher::new("Watson").unwrap();
2323        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2324        SearcherBuilder::new()
2325            .line_number(true)
2326            .build()
2327            .search_reader(
2328                &matcher,
2329                SHERLOCK.as_bytes(),
2330                printer.sink(&matcher),
2331            )
2332            .unwrap();
2333
2334        let got = printer_contents(&mut printer);
2335        let expected = "\
23361:For the Doctor Watsons of this world, as opposed to the Sherlock
23375:but Doctor Watson has to have it taken out for him and dusted,
2338";
2339        assert_eq_printed!(expected, got);
2340    }
2341
2342    #[test]
2343    fn line_number_multi_line() {
2344        let matcher = RegexMatcher::new("(?s)Watson.+Watson").unwrap();
2345        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2346        SearcherBuilder::new()
2347            .line_number(true)
2348            .multi_line(true)
2349            .build()
2350            .search_reader(
2351                &matcher,
2352                SHERLOCK.as_bytes(),
2353                printer.sink(&matcher),
2354            )
2355            .unwrap();
2356
2357        let got = printer_contents(&mut printer);
2358        let expected = "\
23591:For the Doctor Watsons of this world, as opposed to the Sherlock
23602:Holmeses, success in the province of detective work must always
23613:be, to a very large extent, the result of luck. Sherlock Holmes
23624:can extract a clew from a wisp of straw or a flake of cigar ash;
23635:but Doctor Watson has to have it taken out for him and dusted,
2364";
2365        assert_eq_printed!(expected, got);
2366    }
2367
2368    #[test]
2369    fn column_number() {
2370        let matcher = RegexMatcher::new("Watson").unwrap();
2371        let mut printer =
2372            StandardBuilder::new().column(true).build(NoColor::new(vec![]));
2373        SearcherBuilder::new()
2374            .line_number(false)
2375            .build()
2376            .search_reader(
2377                &matcher,
2378                SHERLOCK.as_bytes(),
2379                printer.sink(&matcher),
2380            )
2381            .unwrap();
2382
2383        let got = printer_contents(&mut printer);
2384        let expected = "\
238516:For the Doctor Watsons of this world, as opposed to the Sherlock
238612:but Doctor Watson has to have it taken out for him and dusted,
2387";
2388        assert_eq_printed!(expected, got);
2389    }
2390
2391    #[test]
2392    fn column_number_multi_line() {
2393        let matcher = RegexMatcher::new("(?s)Watson.+Watson").unwrap();
2394        let mut printer =
2395            StandardBuilder::new().column(true).build(NoColor::new(vec![]));
2396        SearcherBuilder::new()
2397            .line_number(false)
2398            .multi_line(true)
2399            .build()
2400            .search_reader(
2401                &matcher,
2402                SHERLOCK.as_bytes(),
2403                printer.sink(&matcher),
2404            )
2405            .unwrap();
2406
2407        let got = printer_contents(&mut printer);
2408        let expected = "\
240916:For the Doctor Watsons of this world, as opposed to the Sherlock
241016:Holmeses, success in the province of detective work must always
241116:be, to a very large extent, the result of luck. Sherlock Holmes
241216:can extract a clew from a wisp of straw or a flake of cigar ash;
241316:but Doctor Watson has to have it taken out for him and dusted,
2414";
2415        assert_eq_printed!(expected, got);
2416    }
2417
2418    #[test]
2419    fn byte_offset() {
2420        let matcher = RegexMatcher::new("Watson").unwrap();
2421        let mut printer = StandardBuilder::new()
2422            .byte_offset(true)
2423            .build(NoColor::new(vec![]));
2424        SearcherBuilder::new()
2425            .line_number(false)
2426            .build()
2427            .search_reader(
2428                &matcher,
2429                SHERLOCK.as_bytes(),
2430                printer.sink(&matcher),
2431            )
2432            .unwrap();
2433
2434        let got = printer_contents(&mut printer);
2435        let expected = "\
24360:For the Doctor Watsons of this world, as opposed to the Sherlock
2437258:but Doctor Watson has to have it taken out for him and dusted,
2438";
2439        assert_eq_printed!(expected, got);
2440    }
2441
2442    #[test]
2443    fn byte_offset_multi_line() {
2444        let matcher = RegexMatcher::new("(?s)Watson.+Watson").unwrap();
2445        let mut printer = StandardBuilder::new()
2446            .byte_offset(true)
2447            .build(NoColor::new(vec![]));
2448        SearcherBuilder::new()
2449            .line_number(false)
2450            .multi_line(true)
2451            .build()
2452            .search_reader(
2453                &matcher,
2454                SHERLOCK.as_bytes(),
2455                printer.sink(&matcher),
2456            )
2457            .unwrap();
2458
2459        let got = printer_contents(&mut printer);
2460        let expected = "\
24610:For the Doctor Watsons of this world, as opposed to the Sherlock
246265:Holmeses, success in the province of detective work must always
2463129:be, to a very large extent, the result of luck. Sherlock Holmes
2464193:can extract a clew from a wisp of straw or a flake of cigar ash;
2465258:but Doctor Watson has to have it taken out for him and dusted,
2466";
2467        assert_eq_printed!(expected, got);
2468    }
2469
2470    #[test]
2471    fn max_columns() {
2472        let matcher = RegexMatcher::new("ash|dusted").unwrap();
2473        let mut printer = StandardBuilder::new()
2474            .max_columns(Some(63))
2475            .build(NoColor::new(vec![]));
2476        SearcherBuilder::new()
2477            .line_number(false)
2478            .build()
2479            .search_reader(
2480                &matcher,
2481                SHERLOCK.as_bytes(),
2482                printer.sink(&matcher),
2483            )
2484            .unwrap();
2485
2486        let got = printer_contents(&mut printer);
2487        let expected = "\
2488[Omitted long matching line]
2489but Doctor Watson has to have it taken out for him and dusted,
2490";
2491        assert_eq_printed!(expected, got);
2492    }
2493
2494    #[test]
2495    fn max_columns_preview() {
2496        let matcher = RegexMatcher::new("exhibited|dusted").unwrap();
2497        let mut printer = StandardBuilder::new()
2498            .max_columns(Some(46))
2499            .max_columns_preview(true)
2500            .build(NoColor::new(vec![]));
2501        SearcherBuilder::new()
2502            .line_number(false)
2503            .build()
2504            .search_reader(
2505                &matcher,
2506                SHERLOCK.as_bytes(),
2507                printer.sink(&matcher),
2508            )
2509            .unwrap();
2510
2511        let got = printer_contents(&mut printer);
2512        let expected = "\
2513but Doctor Watson has to have it taken out for [... omitted end of long line]
2514and exhibited clearly, with a label attached.
2515";
2516        assert_eq_printed!(expected, got);
2517    }
2518
2519    #[test]
2520    fn max_columns_with_count() {
2521        let matcher = RegexMatcher::new("cigar|ash|dusted").unwrap();
2522        let mut printer = StandardBuilder::new()
2523            .stats(true)
2524            .max_columns(Some(63))
2525            .build(NoColor::new(vec![]));
2526        SearcherBuilder::new()
2527            .line_number(false)
2528            .build()
2529            .search_reader(
2530                &matcher,
2531                SHERLOCK.as_bytes(),
2532                printer.sink(&matcher),
2533            )
2534            .unwrap();
2535
2536        let got = printer_contents(&mut printer);
2537        let expected = "\
2538[Omitted long line with 2 matches]
2539but Doctor Watson has to have it taken out for him and dusted,
2540";
2541        assert_eq_printed!(expected, got);
2542    }
2543
2544    #[test]
2545    fn max_columns_with_count_preview_no_match() {
2546        let matcher = RegexMatcher::new("exhibited|has to have it").unwrap();
2547        let mut printer = StandardBuilder::new()
2548            .stats(true)
2549            .max_columns(Some(46))
2550            .max_columns_preview(true)
2551            .build(NoColor::new(vec![]));
2552        SearcherBuilder::new()
2553            .line_number(false)
2554            .build()
2555            .search_reader(
2556                &matcher,
2557                SHERLOCK.as_bytes(),
2558                printer.sink(&matcher),
2559            )
2560            .unwrap();
2561
2562        let got = printer_contents(&mut printer);
2563        let expected = "\
2564but Doctor Watson has to have it taken out for [... 0 more matches]
2565and exhibited clearly, with a label attached.
2566";
2567        assert_eq_printed!(expected, got);
2568    }
2569
2570    #[test]
2571    fn max_columns_with_count_preview_one_match() {
2572        let matcher = RegexMatcher::new("exhibited|dusted").unwrap();
2573        let mut printer = StandardBuilder::new()
2574            .stats(true)
2575            .max_columns(Some(46))
2576            .max_columns_preview(true)
2577            .build(NoColor::new(vec![]));
2578        SearcherBuilder::new()
2579            .line_number(false)
2580            .build()
2581            .search_reader(
2582                &matcher,
2583                SHERLOCK.as_bytes(),
2584                printer.sink(&matcher),
2585            )
2586            .unwrap();
2587
2588        let got = printer_contents(&mut printer);
2589        let expected = "\
2590but Doctor Watson has to have it taken out for [... 1 more match]
2591and exhibited clearly, with a label attached.
2592";
2593        assert_eq_printed!(expected, got);
2594    }
2595
2596    #[test]
2597    fn max_columns_with_count_preview_two_matches() {
2598        let matcher =
2599            RegexMatcher::new("exhibited|dusted|has to have it").unwrap();
2600        let mut printer = StandardBuilder::new()
2601            .stats(true)
2602            .max_columns(Some(46))
2603            .max_columns_preview(true)
2604            .build(NoColor::new(vec![]));
2605        SearcherBuilder::new()
2606            .line_number(false)
2607            .build()
2608            .search_reader(
2609                &matcher,
2610                SHERLOCK.as_bytes(),
2611                printer.sink(&matcher),
2612            )
2613            .unwrap();
2614
2615        let got = printer_contents(&mut printer);
2616        let expected = "\
2617but Doctor Watson has to have it taken out for [... 1 more match]
2618and exhibited clearly, with a label attached.
2619";
2620        assert_eq_printed!(expected, got);
2621    }
2622
2623    #[test]
2624    fn max_columns_multi_line() {
2625        let matcher = RegexMatcher::new("(?s)ash.+dusted").unwrap();
2626        let mut printer = StandardBuilder::new()
2627            .max_columns(Some(63))
2628            .build(NoColor::new(vec![]));
2629        SearcherBuilder::new()
2630            .line_number(false)
2631            .multi_line(true)
2632            .build()
2633            .search_reader(
2634                &matcher,
2635                SHERLOCK.as_bytes(),
2636                printer.sink(&matcher),
2637            )
2638            .unwrap();
2639
2640        let got = printer_contents(&mut printer);
2641        let expected = "\
2642[Omitted long matching line]
2643but Doctor Watson has to have it taken out for him and dusted,
2644";
2645        assert_eq_printed!(expected, got);
2646    }
2647
2648    #[test]
2649    fn max_columns_multi_line_preview() {
2650        let matcher =
2651            RegexMatcher::new("(?s)clew|cigar ash.+have it|exhibited")
2652                .unwrap();
2653        let mut printer = StandardBuilder::new()
2654            .stats(true)
2655            .max_columns(Some(46))
2656            .max_columns_preview(true)
2657            .build(NoColor::new(vec![]));
2658        SearcherBuilder::new()
2659            .line_number(false)
2660            .multi_line(true)
2661            .build()
2662            .search_reader(
2663                &matcher,
2664                SHERLOCK.as_bytes(),
2665                printer.sink(&matcher),
2666            )
2667            .unwrap();
2668
2669        let got = printer_contents(&mut printer);
2670        let expected = "\
2671can extract a clew from a wisp of straw or a f [... 1 more match]
2672but Doctor Watson has to have it taken out for [... 0 more matches]
2673and exhibited clearly, with a label attached.
2674";
2675        assert_eq_printed!(expected, got);
2676    }
2677
2678    #[test]
2679    fn max_matches() {
2680        let matcher = RegexMatcher::new("Sherlock").unwrap();
2681        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2682        SearcherBuilder::new()
2683            .line_number(false)
2684            .max_matches(Some(1))
2685            .build()
2686            .search_reader(
2687                &matcher,
2688                SHERLOCK.as_bytes(),
2689                printer.sink(&matcher),
2690            )
2691            .unwrap();
2692
2693        let got = printer_contents(&mut printer);
2694        let expected = "\
2695For the Doctor Watsons of this world, as opposed to the Sherlock
2696";
2697        assert_eq_printed!(expected, got);
2698    }
2699
2700    #[test]
2701    fn max_matches_context() {
2702        // after context: 1
2703        let matcher = RegexMatcher::new("Doctor Watsons").unwrap();
2704        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2705        SearcherBuilder::new()
2706            .max_matches(Some(1))
2707            .line_number(false)
2708            .after_context(1)
2709            .build()
2710            .search_reader(
2711                &matcher,
2712                SHERLOCK.as_bytes(),
2713                printer.sink(&matcher),
2714            )
2715            .unwrap();
2716
2717        let got = printer_contents(&mut printer);
2718        let expected = "\
2719For the Doctor Watsons of this world, as opposed to the Sherlock
2720Holmeses, success in the province of detective work must always
2721";
2722        assert_eq_printed!(expected, got);
2723
2724        // after context: 4
2725        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2726        SearcherBuilder::new()
2727            .max_matches(Some(1))
2728            .line_number(false)
2729            .after_context(4)
2730            .build()
2731            .search_reader(
2732                &matcher,
2733                SHERLOCK.as_bytes(),
2734                printer.sink(&matcher),
2735            )
2736            .unwrap();
2737
2738        let got = printer_contents(&mut printer);
2739        let expected = "\
2740For the Doctor Watsons of this world, as opposed to the Sherlock
2741Holmeses, success in the province of detective work must always
2742be, to a very large extent, the result of luck. Sherlock Holmes
2743can extract a clew from a wisp of straw or a flake of cigar ash;
2744but Doctor Watson has to have it taken out for him and dusted,
2745";
2746        assert_eq_printed!(expected, got);
2747
2748        // after context: 1, max matches: 2
2749        let matcher = RegexMatcher::new("Doctor Watsons|but Doctor").unwrap();
2750        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2751        SearcherBuilder::new()
2752            .max_matches(Some(2))
2753            .line_number(false)
2754            .after_context(1)
2755            .build()
2756            .search_reader(
2757                &matcher,
2758                SHERLOCK.as_bytes(),
2759                printer.sink(&matcher),
2760            )
2761            .unwrap();
2762
2763        let got = printer_contents(&mut printer);
2764        let expected = "\
2765For the Doctor Watsons of this world, as opposed to the Sherlock
2766Holmeses, success in the province of detective work must always
2767--
2768but Doctor Watson has to have it taken out for him and dusted,
2769and exhibited clearly, with a label attached.
2770";
2771        assert_eq_printed!(expected, got);
2772
2773        // after context: 4, max matches: 2
2774        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2775        SearcherBuilder::new()
2776            .max_matches(Some(2))
2777            .line_number(false)
2778            .after_context(4)
2779            .build()
2780            .search_reader(
2781                &matcher,
2782                SHERLOCK.as_bytes(),
2783                printer.sink(&matcher),
2784            )
2785            .unwrap();
2786
2787        let got = printer_contents(&mut printer);
2788        let expected = "\
2789For the Doctor Watsons of this world, as opposed to the Sherlock
2790Holmeses, success in the province of detective work must always
2791be, to a very large extent, the result of luck. Sherlock Holmes
2792can extract a clew from a wisp of straw or a flake of cigar ash;
2793but Doctor Watson has to have it taken out for him and dusted,
2794and exhibited clearly, with a label attached.
2795";
2796        assert_eq_printed!(expected, got);
2797    }
2798
2799    #[test]
2800    fn max_matches_context_invert() {
2801        // after context: 1
2802        let matcher =
2803            RegexMatcher::new("success|extent|clew|dusted|exhibited").unwrap();
2804        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2805        SearcherBuilder::new()
2806            .invert_match(true)
2807            .max_matches(Some(1))
2808            .line_number(false)
2809            .after_context(1)
2810            .build()
2811            .search_reader(
2812                &matcher,
2813                SHERLOCK.as_bytes(),
2814                printer.sink(&matcher),
2815            )
2816            .unwrap();
2817
2818        let got = printer_contents(&mut printer);
2819        let expected = "\
2820For the Doctor Watsons of this world, as opposed to the Sherlock
2821Holmeses, success in the province of detective work must always
2822";
2823        assert_eq_printed!(expected, got);
2824
2825        // after context: 4
2826        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2827        SearcherBuilder::new()
2828            .invert_match(true)
2829            .max_matches(Some(1))
2830            .line_number(false)
2831            .after_context(4)
2832            .build()
2833            .search_reader(
2834                &matcher,
2835                SHERLOCK.as_bytes(),
2836                printer.sink(&matcher),
2837            )
2838            .unwrap();
2839
2840        let got = printer_contents(&mut printer);
2841        let expected = "\
2842For the Doctor Watsons of this world, as opposed to the Sherlock
2843Holmeses, success in the province of detective work must always
2844be, to a very large extent, the result of luck. Sherlock Holmes
2845can extract a clew from a wisp of straw or a flake of cigar ash;
2846but Doctor Watson has to have it taken out for him and dusted,
2847";
2848        assert_eq_printed!(expected, got);
2849
2850        // after context: 1, max matches: 2
2851        let matcher =
2852            RegexMatcher::new("success|extent|clew|exhibited").unwrap();
2853        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2854        SearcherBuilder::new()
2855            .invert_match(true)
2856            .max_matches(Some(2))
2857            .line_number(false)
2858            .after_context(1)
2859            .build()
2860            .search_reader(
2861                &matcher,
2862                SHERLOCK.as_bytes(),
2863                printer.sink(&matcher),
2864            )
2865            .unwrap();
2866
2867        let got = printer_contents(&mut printer);
2868        let expected = "\
2869For the Doctor Watsons of this world, as opposed to the Sherlock
2870Holmeses, success in the province of detective work must always
2871--
2872but Doctor Watson has to have it taken out for him and dusted,
2873and exhibited clearly, with a label attached.
2874";
2875        assert_eq_printed!(expected, got);
2876
2877        // after context: 4, max matches: 2
2878        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2879        SearcherBuilder::new()
2880            .invert_match(true)
2881            .max_matches(Some(2))
2882            .line_number(false)
2883            .after_context(4)
2884            .build()
2885            .search_reader(
2886                &matcher,
2887                SHERLOCK.as_bytes(),
2888                printer.sink(&matcher),
2889            )
2890            .unwrap();
2891
2892        let got = printer_contents(&mut printer);
2893        let expected = "\
2894For the Doctor Watsons of this world, as opposed to the Sherlock
2895Holmeses, success in the province of detective work must always
2896be, to a very large extent, the result of luck. Sherlock Holmes
2897can extract a clew from a wisp of straw or a flake of cigar ash;
2898but Doctor Watson has to have it taken out for him and dusted,
2899and exhibited clearly, with a label attached.
2900";
2901        assert_eq_printed!(expected, got);
2902    }
2903
2904    #[test]
2905    fn max_matches_multi_line1() {
2906        let matcher = RegexMatcher::new("(?s:.{0})Sherlock").unwrap();
2907        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2908        SearcherBuilder::new()
2909            .line_number(false)
2910            .multi_line(true)
2911            .max_matches(Some(1))
2912            .build()
2913            .search_reader(
2914                &matcher,
2915                SHERLOCK.as_bytes(),
2916                printer.sink(&matcher),
2917            )
2918            .unwrap();
2919
2920        let got = printer_contents(&mut printer);
2921        let expected = "\
2922For the Doctor Watsons of this world, as opposed to the Sherlock
2923";
2924        assert_eq_printed!(expected, got);
2925    }
2926
2927    #[test]
2928    fn max_matches_multi_line2() {
2929        let matcher =
2930            RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
2931        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2932        SearcherBuilder::new()
2933            .line_number(false)
2934            .multi_line(true)
2935            .max_matches(Some(1))
2936            .build()
2937            .search_reader(
2938                &matcher,
2939                SHERLOCK.as_bytes(),
2940                printer.sink(&matcher),
2941            )
2942            .unwrap();
2943
2944        let got = printer_contents(&mut printer);
2945        let expected = "\
2946For the Doctor Watsons of this world, as opposed to the Sherlock
2947Holmeses, success in the province of detective work must always
2948";
2949        assert_eq_printed!(expected, got);
2950    }
2951
2952    #[test]
2953    fn max_matches_multi_line3() {
2954        let matcher = RegexMatcher::new(r"line 2\nline 3").unwrap();
2955        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2956        SearcherBuilder::new()
2957            .line_number(false)
2958            .multi_line(true)
2959            .max_matches(Some(1))
2960            .build()
2961            .search_reader(
2962                &matcher,
2963                "line 2\nline 3 x\nline 2\nline 3\n".as_bytes(),
2964                printer.sink(&matcher),
2965            )
2966            .unwrap();
2967
2968        let got = printer_contents(&mut printer);
2969        let expected = "\
2970line 2
2971line 3 x
2972";
2973        assert_eq_printed!(expected, got);
2974    }
2975
2976    #[test]
2977    fn max_matches_multi_line4() {
2978        let matcher =
2979            RegexMatcher::new(r"line 2\nline 3|x\nline 2\n").unwrap();
2980        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
2981        SearcherBuilder::new()
2982            .line_number(false)
2983            .multi_line(true)
2984            .max_matches(Some(1))
2985            .build()
2986            .search_reader(
2987                &matcher,
2988                "line 2\nline 3 x\nline 2\nline 3 x\n".as_bytes(),
2989                printer.sink(&matcher),
2990            )
2991            .unwrap();
2992
2993        let got = printer_contents(&mut printer);
2994        let expected = "\
2995line 2
2996line 3 x
2997";
2998        assert_eq_printed!(expected, got);
2999    }
3000
3001    #[test]
3002    fn only_matching() {
3003        let matcher = RegexMatcher::new("Doctor Watsons|Sherlock").unwrap();
3004        let mut printer = StandardBuilder::new()
3005            .only_matching(true)
3006            .column(true)
3007            .build(NoColor::new(vec![]));
3008        SearcherBuilder::new()
3009            .line_number(true)
3010            .build()
3011            .search_reader(
3012                &matcher,
3013                SHERLOCK.as_bytes(),
3014                printer.sink(&matcher),
3015            )
3016            .unwrap();
3017
3018        let got = printer_contents(&mut printer);
3019        let expected = "\
30201:9:Doctor Watsons
30211:57:Sherlock
30223:49:Sherlock
3023";
3024        assert_eq_printed!(expected, got);
3025    }
3026
3027    #[test]
3028    fn only_matching_multi_line1() {
3029        let matcher =
3030            RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
3031        let mut printer = StandardBuilder::new()
3032            .only_matching(true)
3033            .column(true)
3034            .build(NoColor::new(vec![]));
3035        SearcherBuilder::new()
3036            .multi_line(true)
3037            .line_number(true)
3038            .build()
3039            .search_reader(
3040                &matcher,
3041                SHERLOCK.as_bytes(),
3042                printer.sink(&matcher),
3043            )
3044            .unwrap();
3045
3046        let got = printer_contents(&mut printer);
3047        let expected = "\
30481:9:Doctor Watsons
30491:57:Sherlock
30503:49:Sherlock
3051";
3052        assert_eq_printed!(expected, got);
3053    }
3054
3055    #[test]
3056    fn only_matching_multi_line2() {
3057        let matcher =
3058            RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
3059        let mut printer = StandardBuilder::new()
3060            .only_matching(true)
3061            .column(true)
3062            .build(NoColor::new(vec![]));
3063        SearcherBuilder::new()
3064            .multi_line(true)
3065            .line_number(true)
3066            .build()
3067            .search_reader(
3068                &matcher,
3069                SHERLOCK.as_bytes(),
3070                printer.sink(&matcher),
3071            )
3072            .unwrap();
3073
3074        let got = printer_contents(&mut printer);
3075        let expected = "\
30761:16:Watsons of this world, as opposed to the Sherlock
30772:16:Holmeses
30785:12:Watson has to have it taken out for him and dusted,
30796:12:and exhibited clearly
3080";
3081        assert_eq_printed!(expected, got);
3082    }
3083
3084    #[test]
3085    fn only_matching_max_columns() {
3086        let matcher = RegexMatcher::new("Doctor Watsons|Sherlock").unwrap();
3087        let mut printer = StandardBuilder::new()
3088            .only_matching(true)
3089            .max_columns(Some(10))
3090            .column(true)
3091            .build(NoColor::new(vec![]));
3092        SearcherBuilder::new()
3093            .line_number(true)
3094            .build()
3095            .search_reader(
3096                &matcher,
3097                SHERLOCK.as_bytes(),
3098                printer.sink(&matcher),
3099            )
3100            .unwrap();
3101
3102        let got = printer_contents(&mut printer);
3103        let expected = "\
31041:9:[Omitted long matching line]
31051:57:Sherlock
31063:49:Sherlock
3107";
3108        assert_eq_printed!(expected, got);
3109    }
3110
3111    #[test]
3112    fn only_matching_max_columns_preview() {
3113        let matcher = RegexMatcher::new("Doctor Watsons|Sherlock").unwrap();
3114        let mut printer = StandardBuilder::new()
3115            .only_matching(true)
3116            .max_columns(Some(10))
3117            .max_columns_preview(true)
3118            .column(true)
3119            .build(NoColor::new(vec![]));
3120        SearcherBuilder::new()
3121            .line_number(true)
3122            .build()
3123            .search_reader(
3124                &matcher,
3125                SHERLOCK.as_bytes(),
3126                printer.sink(&matcher),
3127            )
3128            .unwrap();
3129
3130        let got = printer_contents(&mut printer);
3131        let expected = "\
31321:9:Doctor Wat [... 0 more matches]
31331:57:Sherlock
31343:49:Sherlock
3135";
3136        assert_eq_printed!(expected, got);
3137    }
3138
3139    #[test]
3140    fn only_matching_max_columns_multi_line1() {
3141        // The `(?s:.{0})` trick fools the matcher into thinking that it
3142        // can match across multiple lines without actually doing so. This is
3143        // so we can test multi-line handling in the case of a match on only
3144        // one line.
3145        let matcher =
3146            RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
3147        let mut printer = StandardBuilder::new()
3148            .only_matching(true)
3149            .max_columns(Some(10))
3150            .column(true)
3151            .build(NoColor::new(vec![]));
3152        SearcherBuilder::new()
3153            .multi_line(true)
3154            .line_number(true)
3155            .build()
3156            .search_reader(
3157                &matcher,
3158                SHERLOCK.as_bytes(),
3159                printer.sink(&matcher),
3160            )
3161            .unwrap();
3162
3163        let got = printer_contents(&mut printer);
3164        let expected = "\
31651:9:[Omitted long matching line]
31661:57:Sherlock
31673:49:Sherlock
3168";
3169        assert_eq_printed!(expected, got);
3170    }
3171
3172    #[test]
3173    fn only_matching_max_columns_preview_multi_line1() {
3174        // The `(?s:.{0})` trick fools the matcher into thinking that it
3175        // can match across multiple lines without actually doing so. This is
3176        // so we can test multi-line handling in the case of a match on only
3177        // one line.
3178        let matcher =
3179            RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
3180        let mut printer = StandardBuilder::new()
3181            .only_matching(true)
3182            .max_columns(Some(10))
3183            .max_columns_preview(true)
3184            .column(true)
3185            .build(NoColor::new(vec![]));
3186        SearcherBuilder::new()
3187            .multi_line(true)
3188            .line_number(true)
3189            .build()
3190            .search_reader(
3191                &matcher,
3192                SHERLOCK.as_bytes(),
3193                printer.sink(&matcher),
3194            )
3195            .unwrap();
3196
3197        let got = printer_contents(&mut printer);
3198        let expected = "\
31991:9:Doctor Wat [... 0 more matches]
32001:57:Sherlock
32013:49:Sherlock
3202";
3203        assert_eq_printed!(expected, got);
3204    }
3205
3206    #[test]
3207    fn only_matching_max_columns_multi_line2() {
3208        let matcher =
3209            RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
3210        let mut printer = StandardBuilder::new()
3211            .only_matching(true)
3212            .max_columns(Some(50))
3213            .column(true)
3214            .build(NoColor::new(vec![]));
3215        SearcherBuilder::new()
3216            .multi_line(true)
3217            .line_number(true)
3218            .build()
3219            .search_reader(
3220                &matcher,
3221                SHERLOCK.as_bytes(),
3222                printer.sink(&matcher),
3223            )
3224            .unwrap();
3225
3226        let got = printer_contents(&mut printer);
3227        let expected = "\
32281:16:Watsons of this world, as opposed to the Sherlock
32292:16:Holmeses
32305:12:[Omitted long matching line]
32316:12:and exhibited clearly
3232";
3233        assert_eq_printed!(expected, got);
3234    }
3235
3236    #[test]
3237    fn only_matching_max_columns_preview_multi_line2() {
3238        let matcher =
3239            RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
3240        let mut printer = StandardBuilder::new()
3241            .only_matching(true)
3242            .max_columns(Some(50))
3243            .max_columns_preview(true)
3244            .column(true)
3245            .build(NoColor::new(vec![]));
3246        SearcherBuilder::new()
3247            .multi_line(true)
3248            .line_number(true)
3249            .build()
3250            .search_reader(
3251                &matcher,
3252                SHERLOCK.as_bytes(),
3253                printer.sink(&matcher),
3254            )
3255            .unwrap();
3256
3257        let got = printer_contents(&mut printer);
3258        let expected = "\
32591:16:Watsons of this world, as opposed to the Sherlock
32602:16:Holmeses
32615:12:Watson has to have it taken out for him and dusted [... 0 more matches]
32626:12:and exhibited clearly
3263";
3264        assert_eq_printed!(expected, got);
3265    }
3266
3267    #[test]
3268    fn per_match() {
3269        let matcher = RegexMatcher::new("Doctor Watsons|Sherlock").unwrap();
3270        let mut printer = StandardBuilder::new()
3271            .per_match(true)
3272            .column(true)
3273            .build(NoColor::new(vec![]));
3274        SearcherBuilder::new()
3275            .line_number(true)
3276            .build()
3277            .search_reader(
3278                &matcher,
3279                SHERLOCK.as_bytes(),
3280                printer.sink(&matcher),
3281            )
3282            .unwrap();
3283
3284        let got = printer_contents(&mut printer);
3285        let expected = "\
32861:9:For the Doctor Watsons of this world, as opposed to the Sherlock
32871:57:For the Doctor Watsons of this world, as opposed to the Sherlock
32883:49:be, to a very large extent, the result of luck. Sherlock Holmes
3289";
3290        assert_eq_printed!(expected, got);
3291    }
3292
3293    #[test]
3294    fn per_match_multi_line1() {
3295        let matcher =
3296            RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
3297        let mut printer = StandardBuilder::new()
3298            .per_match(true)
3299            .column(true)
3300            .build(NoColor::new(vec![]));
3301        SearcherBuilder::new()
3302            .multi_line(true)
3303            .line_number(true)
3304            .build()
3305            .search_reader(
3306                &matcher,
3307                SHERLOCK.as_bytes(),
3308                printer.sink(&matcher),
3309            )
3310            .unwrap();
3311
3312        let got = printer_contents(&mut printer);
3313        let expected = "\
33141:9:For the Doctor Watsons of this world, as opposed to the Sherlock
33151:57:For the Doctor Watsons of this world, as opposed to the Sherlock
33163:49:be, to a very large extent, the result of luck. Sherlock Holmes
3317";
3318        assert_eq_printed!(expected, got);
3319    }
3320
3321    #[test]
3322    fn per_match_multi_line2() {
3323        let matcher =
3324            RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
3325        let mut printer = StandardBuilder::new()
3326            .per_match(true)
3327            .column(true)
3328            .build(NoColor::new(vec![]));
3329        SearcherBuilder::new()
3330            .multi_line(true)
3331            .line_number(true)
3332            .build()
3333            .search_reader(
3334                &matcher,
3335                SHERLOCK.as_bytes(),
3336                printer.sink(&matcher),
3337            )
3338            .unwrap();
3339
3340        let got = printer_contents(&mut printer);
3341        let expected = "\
33421:16:For the Doctor Watsons of this world, as opposed to the Sherlock
33432:1:Holmeses, success in the province of detective work must always
33445:12:but Doctor Watson has to have it taken out for him and dusted,
33456:1:and exhibited clearly, with a label attached.
3346";
3347        assert_eq_printed!(expected, got);
3348    }
3349
3350    #[test]
3351    fn per_match_multi_line3() {
3352        let matcher =
3353            RegexMatcher::new(r"(?s)Watson.+?Holmeses|always.+?be").unwrap();
3354        let mut printer = StandardBuilder::new()
3355            .per_match(true)
3356            .column(true)
3357            .build(NoColor::new(vec![]));
3358        SearcherBuilder::new()
3359            .multi_line(true)
3360            .line_number(true)
3361            .build()
3362            .search_reader(
3363                &matcher,
3364                SHERLOCK.as_bytes(),
3365                printer.sink(&matcher),
3366            )
3367            .unwrap();
3368
3369        let got = printer_contents(&mut printer);
3370        let expected = "\
33711:16:For the Doctor Watsons of this world, as opposed to the Sherlock
33722:1:Holmeses, success in the province of detective work must always
33732:58:Holmeses, success in the province of detective work must always
33743:1:be, to a very large extent, the result of luck. Sherlock Holmes
3375";
3376        assert_eq_printed!(expected, got);
3377    }
3378
3379    #[test]
3380    fn per_match_multi_line1_only_first_line() {
3381        let matcher =
3382            RegexMatcher::new(r"(?s:.{0})(Doctor Watsons|Sherlock)").unwrap();
3383        let mut printer = StandardBuilder::new()
3384            .per_match(true)
3385            .per_match_one_line(true)
3386            .column(true)
3387            .build(NoColor::new(vec![]));
3388        SearcherBuilder::new()
3389            .multi_line(true)
3390            .line_number(true)
3391            .build()
3392            .search_reader(
3393                &matcher,
3394                SHERLOCK.as_bytes(),
3395                printer.sink(&matcher),
3396            )
3397            .unwrap();
3398
3399        let got = printer_contents(&mut printer);
3400        let expected = "\
34011:9:For the Doctor Watsons of this world, as opposed to the Sherlock
34021:57:For the Doctor Watsons of this world, as opposed to the Sherlock
34033:49:be, to a very large extent, the result of luck. Sherlock Holmes
3404";
3405        assert_eq_printed!(expected, got);
3406    }
3407
3408    #[test]
3409    fn per_match_multi_line2_only_first_line() {
3410        let matcher =
3411            RegexMatcher::new(r"(?s)Watson.+?(Holmeses|clearly)").unwrap();
3412        let mut printer = StandardBuilder::new()
3413            .per_match(true)
3414            .per_match_one_line(true)
3415            .column(true)
3416            .build(NoColor::new(vec![]));
3417        SearcherBuilder::new()
3418            .multi_line(true)
3419            .line_number(true)
3420            .build()
3421            .search_reader(
3422                &matcher,
3423                SHERLOCK.as_bytes(),
3424                printer.sink(&matcher),
3425            )
3426            .unwrap();
3427
3428        let got = printer_contents(&mut printer);
3429        let expected = "\
34301:16:For the Doctor Watsons of this world, as opposed to the Sherlock
34315:12:but Doctor Watson has to have it taken out for him and dusted,
3432";
3433        assert_eq_printed!(expected, got);
3434    }
3435
3436    #[test]
3437    fn per_match_multi_line3_only_first_line() {
3438        let matcher =
3439            RegexMatcher::new(r"(?s)Watson.+?Holmeses|always.+?be").unwrap();
3440        let mut printer = StandardBuilder::new()
3441            .per_match(true)
3442            .per_match_one_line(true)
3443            .column(true)
3444            .build(NoColor::new(vec![]));
3445        SearcherBuilder::new()
3446            .multi_line(true)
3447            .line_number(true)
3448            .build()
3449            .search_reader(
3450                &matcher,
3451                SHERLOCK.as_bytes(),
3452                printer.sink(&matcher),
3453            )
3454            .unwrap();
3455
3456        let got = printer_contents(&mut printer);
3457        let expected = "\
34581:16:For the Doctor Watsons of this world, as opposed to the Sherlock
34592:58:Holmeses, success in the province of detective work must always
3460";
3461        assert_eq_printed!(expected, got);
3462    }
3463
3464    #[test]
3465    fn replacement_passthru() {
3466        let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
3467        let mut printer = StandardBuilder::new()
3468            .replacement(Some(b"doctah $1 MD".to_vec()))
3469            .build(NoColor::new(vec![]));
3470        SearcherBuilder::new()
3471            .line_number(true)
3472            .passthru(true)
3473            .build()
3474            .search_reader(
3475                &matcher,
3476                SHERLOCK.as_bytes(),
3477                printer.sink(&matcher),
3478            )
3479            .unwrap();
3480
3481        let got = printer_contents(&mut printer);
3482        let expected = "\
34831:For the doctah Watsons MD of this world, as opposed to the doctah  MD
34842-Holmeses, success in the province of detective work must always
34853:be, to a very large extent, the result of luck. doctah  MD Holmes
34864-can extract a clew from a wisp of straw or a flake of cigar ash;
34875:but doctah Watson MD has to have it taken out for him and dusted,
34886-and exhibited clearly, with a label attached.
3489";
3490        assert_eq_printed!(expected, got);
3491    }
3492
3493    #[test]
3494    fn replacement() {
3495        let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
3496        let mut printer = StandardBuilder::new()
3497            .replacement(Some(b"doctah $1 MD".to_vec()))
3498            .build(NoColor::new(vec![]));
3499        SearcherBuilder::new()
3500            .line_number(true)
3501            .build()
3502            .search_reader(
3503                &matcher,
3504                SHERLOCK.as_bytes(),
3505                printer.sink(&matcher),
3506            )
3507            .unwrap();
3508
3509        let got = printer_contents(&mut printer);
3510        let expected = "\
35111:For the doctah Watsons MD of this world, as opposed to the doctah  MD
35123:be, to a very large extent, the result of luck. doctah  MD Holmes
35135:but doctah Watson MD has to have it taken out for him and dusted,
3514";
3515        assert_eq_printed!(expected, got);
3516    }
3517
3518    // This is a somewhat weird test that checks the behavior of attempting
3519    // to replace a line terminator with something else.
3520    //
3521    // See: https://github.com/BurntSushi/ripgrep/issues/1311
3522    #[test]
3523    fn replacement_multi_line() {
3524        let matcher = RegexMatcher::new(r"\n").unwrap();
3525        let mut printer = StandardBuilder::new()
3526            .replacement(Some(b"?".to_vec()))
3527            .build(NoColor::new(vec![]));
3528        SearcherBuilder::new()
3529            .line_number(true)
3530            .multi_line(true)
3531            .build()
3532            .search_reader(
3533                &matcher,
3534                "hello\nworld\n".as_bytes(),
3535                printer.sink(&matcher),
3536            )
3537            .unwrap();
3538
3539        let got = printer_contents(&mut printer);
3540        let expected = "1:hello?world?\n";
3541        assert_eq_printed!(expected, got);
3542    }
3543
3544    #[test]
3545    fn replacement_multi_line_diff_line_term() {
3546        let matcher = RegexMatcherBuilder::new()
3547            .line_terminator(Some(b'\x00'))
3548            .build(r"\n")
3549            .unwrap();
3550        let mut printer = StandardBuilder::new()
3551            .replacement(Some(b"?".to_vec()))
3552            .build(NoColor::new(vec![]));
3553        SearcherBuilder::new()
3554            .line_terminator(LineTerminator::byte(b'\x00'))
3555            .line_number(true)
3556            .multi_line(true)
3557            .build()
3558            .search_reader(
3559                &matcher,
3560                "hello\nworld\n".as_bytes(),
3561                printer.sink(&matcher),
3562            )
3563            .unwrap();
3564
3565        let got = printer_contents(&mut printer);
3566        let expected = "1:hello?world?\x00";
3567        assert_eq_printed!(expected, got);
3568    }
3569
3570    #[test]
3571    fn replacement_multi_line_combine_lines() {
3572        let matcher = RegexMatcher::new(r"\n(.)?").unwrap();
3573        let mut printer = StandardBuilder::new()
3574            .replacement(Some(b"?$1".to_vec()))
3575            .build(NoColor::new(vec![]));
3576        SearcherBuilder::new()
3577            .line_number(true)
3578            .multi_line(true)
3579            .build()
3580            .search_reader(
3581                &matcher,
3582                "hello\nworld\n".as_bytes(),
3583                printer.sink(&matcher),
3584            )
3585            .unwrap();
3586
3587        let got = printer_contents(&mut printer);
3588        let expected = "1:hello?world?\n";
3589        assert_eq_printed!(expected, got);
3590    }
3591
3592    #[test]
3593    fn replacement_max_columns() {
3594        let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
3595        let mut printer = StandardBuilder::new()
3596            .max_columns(Some(67))
3597            .replacement(Some(b"doctah $1 MD".to_vec()))
3598            .build(NoColor::new(vec![]));
3599        SearcherBuilder::new()
3600            .line_number(true)
3601            .build()
3602            .search_reader(
3603                &matcher,
3604                SHERLOCK.as_bytes(),
3605                printer.sink(&matcher),
3606            )
3607            .unwrap();
3608
3609        let got = printer_contents(&mut printer);
3610        let expected = "\
36111:[Omitted long line with 2 matches]
36123:be, to a very large extent, the result of luck. doctah  MD Holmes
36135:but doctah Watson MD has to have it taken out for him and dusted,
3614";
3615        assert_eq_printed!(expected, got);
3616    }
3617
3618    #[test]
3619    fn replacement_max_columns_preview1() {
3620        let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
3621        let mut printer = StandardBuilder::new()
3622            .max_columns(Some(67))
3623            .max_columns_preview(true)
3624            .replacement(Some(b"doctah $1 MD".to_vec()))
3625            .build(NoColor::new(vec![]));
3626        SearcherBuilder::new()
3627            .line_number(true)
3628            .build()
3629            .search_reader(
3630                &matcher,
3631                SHERLOCK.as_bytes(),
3632                printer.sink(&matcher),
3633            )
3634            .unwrap();
3635
3636        let got = printer_contents(&mut printer);
3637        let expected = "\
36381:For the doctah Watsons MD of this world, as opposed to the doctah   [... 0 more matches]
36393:be, to a very large extent, the result of luck. doctah  MD Holmes
36405:but doctah Watson MD has to have it taken out for him and dusted,
3641";
3642        assert_eq_printed!(expected, got);
3643    }
3644
3645    #[test]
3646    fn replacement_max_columns_preview2() {
3647        let matcher =
3648            RegexMatcher::new("exhibited|dusted|has to have it").unwrap();
3649        let mut printer = StandardBuilder::new()
3650            .max_columns(Some(43))
3651            .max_columns_preview(true)
3652            .replacement(Some(b"xxx".to_vec()))
3653            .build(NoColor::new(vec![]));
3654        SearcherBuilder::new()
3655            .line_number(false)
3656            .build()
3657            .search_reader(
3658                &matcher,
3659                SHERLOCK.as_bytes(),
3660                printer.sink(&matcher),
3661            )
3662            .unwrap();
3663
3664        let got = printer_contents(&mut printer);
3665        let expected = "\
3666but Doctor Watson xxx taken out for him and [... 1 more match]
3667and xxx clearly, with a label attached.
3668";
3669        assert_eq_printed!(expected, got);
3670    }
3671
3672    #[test]
3673    fn replacement_only_matching() {
3674        let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
3675        let mut printer = StandardBuilder::new()
3676            .only_matching(true)
3677            .replacement(Some(b"doctah $1 MD".to_vec()))
3678            .build(NoColor::new(vec![]));
3679        SearcherBuilder::new()
3680            .line_number(true)
3681            .build()
3682            .search_reader(
3683                &matcher,
3684                SHERLOCK.as_bytes(),
3685                printer.sink(&matcher),
3686            )
3687            .unwrap();
3688
3689        let got = printer_contents(&mut printer);
3690        let expected = "\
36911:doctah Watsons MD
36921:doctah  MD
36933:doctah  MD
36945:doctah Watson MD
3695";
3696        assert_eq_printed!(expected, got);
3697    }
3698
3699    #[test]
3700    fn replacement_per_match() {
3701        let matcher = RegexMatcher::new(r"Sherlock|Doctor (\w+)").unwrap();
3702        let mut printer = StandardBuilder::new()
3703            .per_match(true)
3704            .replacement(Some(b"doctah $1 MD".to_vec()))
3705            .build(NoColor::new(vec![]));
3706        SearcherBuilder::new()
3707            .line_number(true)
3708            .build()
3709            .search_reader(
3710                &matcher,
3711                SHERLOCK.as_bytes(),
3712                printer.sink(&matcher),
3713            )
3714            .unwrap();
3715
3716        let got = printer_contents(&mut printer);
3717        let expected = "\
37181:For the doctah Watsons MD of this world, as opposed to the doctah  MD
37191:For the doctah Watsons MD of this world, as opposed to the doctah  MD
37203:be, to a very large extent, the result of luck. doctah  MD Holmes
37215:but doctah Watson MD has to have it taken out for him and dusted,
3722";
3723        assert_eq_printed!(expected, got);
3724    }
3725
3726    #[test]
3727    fn invert() {
3728        let matcher = RegexMatcher::new(r"Sherlock").unwrap();
3729        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
3730        SearcherBuilder::new()
3731            .line_number(true)
3732            .invert_match(true)
3733            .build()
3734            .search_reader(
3735                &matcher,
3736                SHERLOCK.as_bytes(),
3737                printer.sink(&matcher),
3738            )
3739            .unwrap();
3740
3741        let got = printer_contents(&mut printer);
3742        let expected = "\
37432:Holmeses, success in the province of detective work must always
37444:can extract a clew from a wisp of straw or a flake of cigar ash;
37455:but Doctor Watson has to have it taken out for him and dusted,
37466:and exhibited clearly, with a label attached.
3747";
3748        assert_eq_printed!(expected, got);
3749    }
3750
3751    #[test]
3752    fn invert_multi_line() {
3753        let matcher = RegexMatcher::new(r"(?s:.{0})Sherlock").unwrap();
3754        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
3755        SearcherBuilder::new()
3756            .multi_line(true)
3757            .line_number(true)
3758            .invert_match(true)
3759            .build()
3760            .search_reader(
3761                &matcher,
3762                SHERLOCK.as_bytes(),
3763                printer.sink(&matcher),
3764            )
3765            .unwrap();
3766
3767        let got = printer_contents(&mut printer);
3768        let expected = "\
37692:Holmeses, success in the province of detective work must always
37704:can extract a clew from a wisp of straw or a flake of cigar ash;
37715:but Doctor Watson has to have it taken out for him and dusted,
37726:and exhibited clearly, with a label attached.
3773";
3774        assert_eq_printed!(expected, got);
3775    }
3776
3777    #[test]
3778    fn invert_context() {
3779        let matcher = RegexMatcher::new(r"Sherlock").unwrap();
3780        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
3781        SearcherBuilder::new()
3782            .line_number(true)
3783            .invert_match(true)
3784            .before_context(1)
3785            .after_context(1)
3786            .build()
3787            .search_reader(
3788                &matcher,
3789                SHERLOCK.as_bytes(),
3790                printer.sink(&matcher),
3791            )
3792            .unwrap();
3793
3794        let got = printer_contents(&mut printer);
3795        let expected = "\
37961-For the Doctor Watsons of this world, as opposed to the Sherlock
37972:Holmeses, success in the province of detective work must always
37983-be, to a very large extent, the result of luck. Sherlock Holmes
37994:can extract a clew from a wisp of straw or a flake of cigar ash;
38005:but Doctor Watson has to have it taken out for him and dusted,
38016:and exhibited clearly, with a label attached.
3802";
3803        assert_eq_printed!(expected, got);
3804    }
3805
3806    #[test]
3807    fn invert_context_multi_line() {
3808        let matcher = RegexMatcher::new(r"(?s:.{0})Sherlock").unwrap();
3809        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
3810        SearcherBuilder::new()
3811            .multi_line(true)
3812            .line_number(true)
3813            .invert_match(true)
3814            .before_context(1)
3815            .after_context(1)
3816            .build()
3817            .search_reader(
3818                &matcher,
3819                SHERLOCK.as_bytes(),
3820                printer.sink(&matcher),
3821            )
3822            .unwrap();
3823
3824        let got = printer_contents(&mut printer);
3825        let expected = "\
38261-For the Doctor Watsons of this world, as opposed to the Sherlock
38272:Holmeses, success in the province of detective work must always
38283-be, to a very large extent, the result of luck. Sherlock Holmes
38294:can extract a clew from a wisp of straw or a flake of cigar ash;
38305:but Doctor Watson has to have it taken out for him and dusted,
38316:and exhibited clearly, with a label attached.
3832";
3833        assert_eq_printed!(expected, got);
3834    }
3835
3836    #[test]
3837    fn invert_context_only_matching() {
3838        let matcher = RegexMatcher::new(r"Sherlock").unwrap();
3839        let mut printer = StandardBuilder::new()
3840            .only_matching(true)
3841            .build(NoColor::new(vec![]));
3842        SearcherBuilder::new()
3843            .line_number(true)
3844            .invert_match(true)
3845            .before_context(1)
3846            .after_context(1)
3847            .build()
3848            .search_reader(
3849                &matcher,
3850                SHERLOCK.as_bytes(),
3851                printer.sink(&matcher),
3852            )
3853            .unwrap();
3854
3855        let got = printer_contents(&mut printer);
3856        let expected = "\
38571-Sherlock
38582:Holmeses, success in the province of detective work must always
38593-Sherlock
38604:can extract a clew from a wisp of straw or a flake of cigar ash;
38615:but Doctor Watson has to have it taken out for him and dusted,
38626:and exhibited clearly, with a label attached.
3863";
3864        assert_eq_printed!(expected, got);
3865    }
3866
3867    #[test]
3868    fn invert_context_only_matching_multi_line() {
3869        let matcher = RegexMatcher::new(r"(?s:.{0})Sherlock").unwrap();
3870        let mut printer = StandardBuilder::new()
3871            .only_matching(true)
3872            .build(NoColor::new(vec![]));
3873        SearcherBuilder::new()
3874            .multi_line(true)
3875            .line_number(true)
3876            .invert_match(true)
3877            .before_context(1)
3878            .after_context(1)
3879            .build()
3880            .search_reader(
3881                &matcher,
3882                SHERLOCK.as_bytes(),
3883                printer.sink(&matcher),
3884            )
3885            .unwrap();
3886
3887        let got = printer_contents(&mut printer);
3888        let expected = "\
38891-Sherlock
38902:Holmeses, success in the province of detective work must always
38913-Sherlock
38924:can extract a clew from a wisp of straw or a flake of cigar ash;
38935:but Doctor Watson has to have it taken out for him and dusted,
38946:and exhibited clearly, with a label attached.
3895";
3896        assert_eq_printed!(expected, got);
3897    }
3898
3899    #[test]
3900    fn regression_search_empty_with_crlf() {
3901        let matcher =
3902            RegexMatcherBuilder::new().crlf(true).build(r"x?").unwrap();
3903        let mut printer = StandardBuilder::new()
3904            .color_specs(ColorSpecs::default_with_color())
3905            .build(Ansi::new(vec![]));
3906        SearcherBuilder::new()
3907            .line_terminator(LineTerminator::crlf())
3908            .build()
3909            .search_reader(&matcher, &b"\n"[..], printer.sink(&matcher))
3910            .unwrap();
3911
3912        let got = printer_contents_ansi(&mut printer);
3913        assert!(!got.is_empty());
3914    }
3915
3916    #[test]
3917    fn regression_after_context_with_match() {
3918        let haystack = "\
3919a
3920b
3921c
3922d
3923e
3924d
3925e
3926d
3927e
3928d
3929e
3930";
3931
3932        let matcher = RegexMatcherBuilder::new().build(r"d").unwrap();
3933        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
3934        SearcherBuilder::new()
3935            .max_matches(Some(1))
3936            .line_number(true)
3937            .after_context(2)
3938            .build()
3939            .search_reader(
3940                &matcher,
3941                haystack.as_bytes(),
3942                printer.sink(&matcher),
3943            )
3944            .unwrap();
3945
3946        let got = printer_contents(&mut printer);
3947        let expected = "4:d\n5-e\n6:d\n";
3948        assert_eq_printed!(expected, got);
3949    }
3950
3951    #[test]
3952    fn regression_crlf_preserve() {
3953        let haystack = "hello\nworld\r\n";
3954        let matcher =
3955            RegexMatcherBuilder::new().crlf(true).build(r".").unwrap();
3956        let mut printer = StandardBuilder::new().build(NoColor::new(vec![]));
3957        let mut searcher = SearcherBuilder::new()
3958            .line_number(false)
3959            .line_terminator(LineTerminator::crlf())
3960            .build();
3961
3962        searcher
3963            .search_reader(
3964                &matcher,
3965                haystack.as_bytes(),
3966                printer.sink(&matcher),
3967            )
3968            .unwrap();
3969        let got = printer_contents(&mut printer);
3970        let expected = "hello\nworld\r\n";
3971        assert_eq_printed!(expected, got);
3972
3973        let mut printer = StandardBuilder::new()
3974            .replacement(Some(b"$0".to_vec()))
3975            .build(NoColor::new(vec![]));
3976        searcher
3977            .search_reader(
3978                &matcher,
3979                haystack.as_bytes(),
3980                printer.sink(&matcher),
3981            )
3982            .unwrap();
3983        let got = printer_contents(&mut printer);
3984        let expected = "hello\nworld\r\n";
3985        assert_eq_printed!(expected, got);
3986    }
3987}