@@ -31,12 +31,12 @@ impl Command for Find {
31
31
)
32
32
. switch (
33
33
"ignore-case" ,
34
- "case-insensitive regex mode; equivalent to (?i)" ,
34
+ "case-insensitive; when in regex mode, this is equivalent to (?i)" ,
35
35
Some ( 'i' ) ,
36
36
)
37
37
. switch (
38
38
"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 " ,
40
40
Some ( 'm' ) ,
41
41
)
42
42
. switch (
@@ -72,16 +72,16 @@ impl Command for Find {
72
72
result: None ,
73
73
} ,
74
74
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 "# ,
77
77
result: Some ( Value :: test_string(
78
78
"\u{1b} [37m\u{1b} [0m\u{1b} [41;37mCargo\u{1b} [0m\u{1b} [37m.toml\u{1b} [0m"
79
79
. to_owned( ) ,
80
80
) ) ,
81
81
} ,
82
82
Example {
83
83
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"# ,
85
85
result: Some ( Value :: list(
86
86
vec![ Value :: test_int( 5 ) , Value :: test_filesize( 3000 ) ] ,
87
87
Span :: test_data( ) ,
@@ -103,25 +103,25 @@ impl Command for Find {
103
103
) ) ,
104
104
} ,
105
105
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. ""# ,
108
108
result: Some ( Value :: list(
109
109
vec![
110
110
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"
112
112
. to_string( ) ,
113
113
) ,
114
114
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"
116
116
. to_string( ) ,
117
117
) ,
118
118
] ,
119
119
Span :: test_data( ) ,
120
120
) ) ,
121
121
} ,
122
122
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"# ,
125
125
result: Some ( Value :: list(
126
126
vec![
127
127
Value :: test_string(
@@ -211,11 +211,33 @@ impl Command for Find {
211
211
Span :: test_data( ) ,
212
212
) ) ,
213
213
} ,
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
+ } ,
214
236
]
215
237
}
216
238
217
239
fn search_terms ( & self ) -> Vec < & str > {
218
- vec ! [ "filter" , "regex" , "search" , "condition" ]
240
+ vec ! [ "filter" , "regex" , "search" , "condition" , "grep" ]
219
241
}
220
242
221
243
fn run (
@@ -227,11 +249,25 @@ impl Command for Find {
227
249
) -> Result < PipelineData , ShellError > {
228
250
let pattern = get_match_pattern_from_arguments ( engine_state, stack, call) ?;
229
251
252
+ let multiline = call. has_flag ( engine_state, stack, "multiline" ) ?;
253
+
230
254
let columns_to_search: Vec < _ > = call
231
255
. get_flag ( engine_state, stack, "columns" ) ?
232
256
. unwrap_or_default ( ) ;
233
257
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
+ } ;
235
271
236
272
find_in_pipelinedata ( pattern, columns_to_search, engine_state, stack, input)
237
273
}
@@ -242,8 +278,11 @@ struct MatchPattern {
242
278
/// the regex to be used for matching in text
243
279
regex : Regex ,
244
280
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 ,
247
286
248
287
/// return a modified version of the value where matching parts are highlighted
249
288
highlight : bool ,
@@ -272,6 +311,10 @@ fn get_match_pattern_from_arguments(
272
311
let invert = call. has_flag ( engine_state, stack, "invert" ) ?;
273
312
let highlight = !call. has_flag ( engine_state, stack, "no-highlight" ) ?;
274
313
314
+ let ignore_case = call. has_flag ( engine_state, stack, "ignore-case" ) ?;
315
+
316
+ let dotall = call. has_flag ( engine_state, stack, "dotall" ) ?;
317
+
275
318
let style_computer = StyleComputer :: from_config ( engine_state, stack) ;
276
319
// Currently, search results all use the same style.
277
320
// 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(
280
323
let highlight_style =
281
324
style_computer. compute ( "search_result" , & Value :: string ( "search result" , span) ) ;
282
325
283
- let ( regex_str, lower_terms ) = if let Some ( regex) = regex {
326
+ let ( regex_str, search_terms ) = if let Some ( regex) = regex {
284
327
if !terms. is_empty ( ) {
285
328
return Err ( ShellError :: IncompatibleParametersSingle {
286
329
msg : "Cannot use a `--regex` parameter with additional search terms" . into ( ) ,
287
330
span : call. get_flag_span ( stack, "regex" ) . expect ( "has flag" ) ,
288
331
} ) ;
289
332
}
290
333
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
304
339
} ;
305
340
306
341
( flags. to_string ( ) + regex. as_str ( ) , Vec :: new ( ) )
307
342
} 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
+
308
350
let mut regex = String :: new ( ) ;
309
351
310
- regex += "(?i)" ;
352
+ if ignore_case {
353
+ regex += "(?i)" ;
354
+ }
311
355
312
- let lower_terms = terms
356
+ let search_terms = terms
313
357
. 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
+ } )
315
365
. collect :: < Vec < String > > ( ) ;
316
366
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 ( ) {
318
373
regex += term;
319
374
}
320
375
321
- for term in lower_terms . iter ( ) . skip ( 1 ) {
376
+ for term in escaped_terms . iter ( ) . skip ( 1 ) {
322
377
regex += "|" ;
323
378
regex += term;
324
379
}
325
380
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)
332
382
} ;
333
383
334
384
let regex = Regex :: new ( regex_str. as_str ( ) ) . map_err ( |e| ShellError :: TypeMismatch {
@@ -338,7 +388,8 @@ fn get_match_pattern_from_arguments(
338
388
339
389
Ok ( MatchPattern {
340
390
regex,
341
- lower_terms,
391
+ search_terms,
392
+ ignore_case,
342
393
invert,
343
394
highlight,
344
395
string_style,
@@ -507,7 +558,11 @@ fn value_should_be_printed(
507
558
columns_to_search : & [ String ] ,
508
559
config : & Config ,
509
560
) -> 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
+ } ;
511
566
512
567
match value {
513
568
Value :: Bool { .. }
@@ -519,18 +574,18 @@ fn value_should_be_printed(
519
574
| Value :: Float { .. }
520
575
| Value :: Closure { .. }
521
576
| Value :: Nothing { .. } => {
522
- if !pattern. lower_terms . is_empty ( ) {
577
+ if !pattern. search_terms . is_empty ( ) {
523
578
// look for exact match when searching with terms
524
579
pattern
525
- . lower_terms
580
+ . search_terms
526
581
. iter ( )
527
- . any ( |term : & String | term == & lower_value )
582
+ . any ( |term : & String | term == & value_as_string )
528
583
} else {
529
- string_should_be_printed ( pattern, & lower_value )
584
+ string_should_be_printed ( pattern, & value_as_string )
530
585
}
531
586
}
532
587
Value :: Glob { .. } | Value :: CellPath { .. } | Value :: Custom { .. } => {
533
- string_should_be_printed ( pattern, & lower_value )
588
+ string_should_be_printed ( pattern, & value_as_string )
534
589
}
535
590
Value :: String { val, .. } => string_should_be_printed ( pattern, val) ,
536
591
Value :: List { vals, .. } => vals
@@ -597,7 +652,8 @@ pub fn find_internal(
597
652
598
653
let pattern = MatchPattern {
599
654
regex,
600
- lower_terms : vec ! [ search_term. to_lowercase( ) ] ,
655
+ search_terms : vec ! [ search_term. to_lowercase( ) ] ,
656
+ ignore_case : true ,
601
657
highlight,
602
658
invert : false ,
603
659
string_style,
0 commit comments