@@ -31,12 +31,12 @@ impl Command for Find {
3131 )
3232 . switch (
3333 "ignore-case" ,
34- "case-insensitive regex mode; equivalent to (?i)" ,
34+ "case-insensitive; when in regex mode, this is equivalent to (?i)" ,
3535 Some ( 'i' ) ,
3636 )
3737 . switch (
3838 "multiline" ,
39- "multi-line regex mode: ^ and $ match begin/end of line; equivalent to (?m)" ,
39+ "don't split multi-line strings into lists of lines. you should use this option when using the (?m) or (?s) flags in regex mode " ,
4040 Some ( 'm' ) ,
4141 )
4242 . switch (
@@ -72,16 +72,16 @@ impl Command for Find {
7272 result: None ,
7373 } ,
7474 Example {
75- description: "Search and highlight text for a term in a string. Note that regular search is case insensitive " ,
76- example: r#"'Cargo.toml' | find cargo "# ,
75+ description: "Search and highlight text for a term in a string." ,
76+ example: r#"'Cargo.toml' | find Cargo "# ,
7777 result: Some ( Value :: test_string(
7878 "\u{1b} [37m\u{1b} [0m\u{1b} [41;37mCargo\u{1b} [0m\u{1b} [37m.toml\u{1b} [0m"
7979 . to_owned( ) ,
8080 ) ) ,
8181 } ,
8282 Example {
8383 description: "Search a number or a file size in a list of numbers" ,
84- example: r#"[1 5 3kb 4 3Mb] | find 5 3kb"# ,
84+ example: r#"[1 5 3kb 4 35 3Mb] | find 5 3kb"# ,
8585 result: Some ( Value :: list(
8686 vec![ Value :: test_int( 5 ) , Value :: test_filesize( 3000 ) ] ,
8787 Span :: test_data( ) ,
@@ -103,25 +103,25 @@ impl Command for Find {
103103 ) ) ,
104104 } ,
105105 Example {
106- description: "Find using regex" ,
107- example: r#"[abc bde arc abf] | find --regex "ab ""# ,
106+ description: "Search using regex" ,
107+ example: r#"[abc odb arc abf] | find --regex "b. ""# ,
108108 result: Some ( Value :: list(
109109 vec![
110110 Value :: test_string(
111- "\u{1b} [37m \u{1b} [0m\u{1b} [41;37mab \u{1b} [0m\u{1b} [37mc \u{1b} [0m"
111+ "\u{1b} [37ma \u{1b} [0m\u{1b} [41;37mbc \u{1b} [0m\u{1b} [37m \u{1b} [0m"
112112 . to_string( ) ,
113113 ) ,
114114 Value :: test_string(
115- "\u{1b} [37m \u{1b} [0m\u{1b} [41;37mab \u{1b} [0m\u{1b} [37mf \u{1b} [0m"
115+ "\u{1b} [37ma \u{1b} [0m\u{1b} [41;37mbf \u{1b} [0m\u{1b} [37m \u{1b} [0m"
116116 . to_string( ) ,
117117 ) ,
118118 ] ,
119119 Span :: test_data( ) ,
120120 ) ) ,
121121 } ,
122122 Example {
123- description: "Find using regex case insensitive" ,
124- example: r#"[aBc bde Arc abf] | find --regex "ab" -i"# ,
123+ description: "Case insensitive search " ,
124+ example: r#"[aBc bde Arc abf] | find "ab" -i"# ,
125125 result: Some ( Value :: list(
126126 vec![
127127 Value :: test_string(
@@ -211,11 +211,33 @@ impl Command for Find {
211211 Span :: test_data( ) ,
212212 ) ) ,
213213 } ,
214+ Example {
215+ description: "Find in a multi-line string" ,
216+ example: r#""Violets are red\nAnd roses are blue\nWhen metamaterials\nAlter their hue" | find "ue""# ,
217+ result: Some ( Value :: list(
218+ vec![
219+ Value :: test_string(
220+ "\u{1b} [37mAnd roses are bl\u{1b} [0m\u{1b} [41;37mue\u{1b} [0m\u{1b} [37m\u{1b} [0m" ,
221+ ) ,
222+ Value :: test_string(
223+ "\u{1b} [37mAlter their h\u{1b} [0m\u{1b} [41;37mue\u{1b} [0m\u{1b} [37m\u{1b} [0m" ,
224+ ) ,
225+ ] ,
226+ Span :: test_data( ) ,
227+ ) ) ,
228+ } ,
229+ Example {
230+ description: "Find in a multi-line string without splitting the input into a list of lines" ,
231+ example: r#""Violets are red\nAnd roses are blue\nWhen metamaterials\nAlter their hue" | find --multiline "ue""# ,
232+ result: Some ( Value :: test_string(
233+ "\u{1b} [37mViolets are red\n And roses are bl\u{1b} [0m\u{1b} [41;37mue\u{1b} [0m\u{1b} [37m\n When metamaterials\n Alter their h\u{1b} [0m\u{1b} [41;37mue\u{1b} [0m\u{1b} [37m\u{1b} [0m" ,
234+ ) ) ,
235+ } ,
214236 ]
215237 }
216238
217239 fn search_terms ( & self ) -> Vec < & str > {
218- vec ! [ "filter" , "regex" , "search" , "condition" ]
240+ vec ! [ "filter" , "regex" , "search" , "condition" , "grep" ]
219241 }
220242
221243 fn run (
@@ -227,11 +249,25 @@ impl Command for Find {
227249 ) -> Result < PipelineData , ShellError > {
228250 let pattern = get_match_pattern_from_arguments ( engine_state, stack, call) ?;
229251
252+ let multiline = call. has_flag ( engine_state, stack, "multiline" ) ?;
253+
230254 let columns_to_search: Vec < _ > = call
231255 . get_flag ( engine_state, stack, "columns" ) ?
232256 . unwrap_or_default ( ) ;
233257
234- let input = split_string_if_multiline ( input, call. head ) ;
258+ let input = if multiline {
259+ if let PipelineData :: ByteStream ( ..) = input {
260+ // ByteStream inputs are processed by iterating over the lines, which necessarily
261+ // breaks the multi-line text being streamed into a list of lines.
262+ return Err ( ShellError :: IncompatibleParametersSingle {
263+ msg : "Flag `--multiline` currently doesn't work for byte stream inputs. Consider using `collect`" . into ( ) ,
264+ span : call. get_flag_span ( stack, "multiline" ) . expect ( "has flag" ) ,
265+ } ) ;
266+ } ;
267+ input
268+ } else {
269+ split_string_if_multiline ( input, call. head )
270+ } ;
235271
236272 find_in_pipelinedata ( pattern, columns_to_search, engine_state, stack, input)
237273 }
@@ -242,8 +278,11 @@ struct MatchPattern {
242278 /// the regex to be used for matching in text
243279 regex : Regex ,
244280
245- /// the list of match terms converted to lowercase strings, or empty if a regex was provided
246- lower_terms : Vec < String > ,
281+ /// the list of match terms (converted to lowercase if needed), or empty if a regex was provided
282+ search_terms : Vec < String > ,
283+
284+ /// case-insensitive match
285+ ignore_case : bool ,
247286
248287 /// return a modified version of the value where matching parts are highlighted
249288 highlight : bool ,
@@ -272,6 +311,10 @@ fn get_match_pattern_from_arguments(
272311 let invert = call. has_flag ( engine_state, stack, "invert" ) ?;
273312 let highlight = !call. has_flag ( engine_state, stack, "no-highlight" ) ?;
274313
314+ let ignore_case = call. has_flag ( engine_state, stack, "ignore-case" ) ?;
315+
316+ let dotall = call. has_flag ( engine_state, stack, "dotall" ) ?;
317+
275318 let style_computer = StyleComputer :: from_config ( engine_state, stack) ;
276319 // Currently, search results all use the same style.
277320 // Also note that this sample string is passed into user-written code (the closure that may or may not be
@@ -280,55 +323,62 @@ fn get_match_pattern_from_arguments(
280323 let highlight_style =
281324 style_computer. compute ( "search_result" , & Value :: string ( "search result" , span) ) ;
282325
283- let ( regex_str, lower_terms ) = if let Some ( regex) = regex {
326+ let ( regex_str, search_terms ) = if let Some ( regex) = regex {
284327 if !terms. is_empty ( ) {
285328 return Err ( ShellError :: IncompatibleParametersSingle {
286329 msg : "Cannot use a `--regex` parameter with additional search terms" . into ( ) ,
287330 span : call. get_flag_span ( stack, "regex" ) . expect ( "has flag" ) ,
288331 } ) ;
289332 }
290333
291- let insensitive = call. has_flag ( engine_state, stack, "ignore-case" ) ?;
292- let multiline = call. has_flag ( engine_state, stack, "multiline" ) ?;
293- let dotall = call. has_flag ( engine_state, stack, "dotall" ) ?;
294-
295- let flags = match ( insensitive, multiline, dotall) {
296- ( false , false , false ) => "" ,
297- ( true , false , false ) => "(?i)" , // case insensitive
298- ( false , true , false ) => "(?m)" , // multi-line mode
299- ( false , false , true ) => "(?s)" , // allow . to match \n
300- ( true , true , false ) => "(?im)" , // case insensitive and multi-line mode
301- ( true , false , true ) => "(?is)" , // case insensitive and allow . to match \n
302- ( false , true , true ) => "(?ms)" , // multi-line mode and allow . to match \n
303- ( true , true , true ) => "(?ims)" , // case insensitive, multi-line mode and allow . to match \n
334+ let flags = match ( ignore_case, dotall) {
335+ ( false , false ) => "" ,
336+ ( true , false ) => "(?i)" , // case insensitive
337+ ( false , true ) => "(?s)" , // allow . to match \n
338+ ( true , true ) => "(?is)" , // case insensitive and allow . to match \n
304339 } ;
305340
306341 ( flags. to_string ( ) + regex. as_str ( ) , Vec :: new ( ) )
307342 } else {
343+ if dotall {
344+ return Err ( ShellError :: IncompatibleParametersSingle {
345+ msg : "Flag --dotall only works for regex search" . into ( ) ,
346+ span : call. get_flag_span ( stack, "dotall" ) . expect ( "has flag" ) ,
347+ } ) ;
348+ }
349+
308350 let mut regex = String :: new ( ) ;
309351
310- regex += "(?i)" ;
352+ if ignore_case {
353+ regex += "(?i)" ;
354+ }
311355
312- let lower_terms = terms
356+ let search_terms = terms
313357 . iter ( )
314- . map ( |v| escape ( & v. to_expanded_string ( "" , & config) . to_lowercase ( ) ) . into ( ) )
358+ . map ( |v| {
359+ if ignore_case {
360+ v. to_expanded_string ( "" , & config) . to_lowercase ( )
361+ } else {
362+ v. to_expanded_string ( "" , & config)
363+ }
364+ } )
315365 . collect :: < Vec < String > > ( ) ;
316366
317- if let Some ( term) = lower_terms. first ( ) {
367+ let escaped_terms = search_terms
368+ . iter ( )
369+ . map ( |v| escape ( v) . into ( ) )
370+ . collect :: < Vec < String > > ( ) ;
371+
372+ if let Some ( term) = escaped_terms. first ( ) {
318373 regex += term;
319374 }
320375
321- for term in lower_terms . iter ( ) . skip ( 1 ) {
376+ for term in escaped_terms . iter ( ) . skip ( 1 ) {
322377 regex += "|" ;
323378 regex += term;
324379 }
325380
326- let lower_terms = terms
327- . iter ( )
328- . map ( |v| v. to_expanded_string ( "" , & config) . to_lowercase ( ) )
329- . collect :: < Vec < String > > ( ) ;
330-
331- ( regex, lower_terms)
381+ ( regex, search_terms)
332382 } ;
333383
334384 let regex = Regex :: new ( regex_str. as_str ( ) ) . map_err ( |e| ShellError :: TypeMismatch {
@@ -338,7 +388,8 @@ fn get_match_pattern_from_arguments(
338388
339389 Ok ( MatchPattern {
340390 regex,
341- lower_terms,
391+ search_terms,
392+ ignore_case,
342393 invert,
343394 highlight,
344395 string_style,
@@ -507,7 +558,11 @@ fn value_should_be_printed(
507558 columns_to_search : & [ String ] ,
508559 config : & Config ,
509560) -> bool {
510- let lower_value = value. to_expanded_string ( "" , config) . to_lowercase ( ) ;
561+ let value_as_string = if pattern. ignore_case {
562+ value. to_expanded_string ( "" , config) . to_lowercase ( )
563+ } else {
564+ value. to_expanded_string ( "" , config)
565+ } ;
511566
512567 match value {
513568 Value :: Bool { .. }
@@ -519,18 +574,18 @@ fn value_should_be_printed(
519574 | Value :: Float { .. }
520575 | Value :: Closure { .. }
521576 | Value :: Nothing { .. } => {
522- if !pattern. lower_terms . is_empty ( ) {
577+ if !pattern. search_terms . is_empty ( ) {
523578 // look for exact match when searching with terms
524579 pattern
525- . lower_terms
580+ . search_terms
526581 . iter ( )
527- . any ( |term : & String | term == & lower_value )
582+ . any ( |term : & String | term == & value_as_string )
528583 } else {
529- string_should_be_printed ( pattern, & lower_value )
584+ string_should_be_printed ( pattern, & value_as_string )
530585 }
531586 }
532587 Value :: Glob { .. } | Value :: CellPath { .. } | Value :: Custom { .. } => {
533- string_should_be_printed ( pattern, & lower_value )
588+ string_should_be_printed ( pattern, & value_as_string )
534589 }
535590 Value :: String { val, .. } => string_should_be_printed ( pattern, val) ,
536591 Value :: List { vals, .. } => vals
@@ -597,7 +652,8 @@ pub fn find_internal(
597652
598653 let pattern = MatchPattern {
599654 regex,
600- lower_terms : vec ! [ search_term. to_lowercase( ) ] ,
655+ search_terms : vec ! [ search_term. to_lowercase( ) ] ,
656+ ignore_case : true ,
601657 highlight,
602658 invert : false ,
603659 string_style,
0 commit comments