Skip to content

Commit 1595f0f

Browse files
committed
Add --smart-case.
It does what it says on the tin. Closes #70.
1 parent 8eeb0c0 commit 1595f0f

File tree

5 files changed

+63
-1
lines changed

5 files changed

+63
-1
lines changed

doc/rg.1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,12 @@ Alias for \-\-color=always \-\-heading \-n.
238238
.RS
239239
.RE
240240
.TP
241+
.B \-S, \-\-smart\-case
242+
Search case insensitively if the pattern is all lowercase.
243+
Search case sensitively otherwise.
244+
.RS
245+
.RE
246+
.TP
241247
.B \-j, \-\-threads \f[I]ARG\f[]
242248
The number of threads to use.
243249
Defaults to the number of logical CPUs (capped at 6).

doc/rg.1.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ the raw speed of grep.
154154
-p, --pretty
155155
: Alias for --color=always --heading -n.
156156

157+
-S, --smart-case
158+
: Search case insensitively if the pattern is all lowercase.
159+
Search case sensitively otherwise.
160+
157161
-j, --threads *ARG*
158162
: The number of threads to use. Defaults to the number of logical CPUs
159163
(capped at 6). [default: 0]

grep/src/search.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pub struct GrepBuilder {
5252
#[derive(Clone, Debug)]
5353
struct Options {
5454
case_insensitive: bool,
55+
case_smart: bool,
5556
line_terminator: u8,
5657
size_limit: usize,
5758
dfa_size_limit: usize,
@@ -61,6 +62,7 @@ impl Default for Options {
6162
fn default() -> Options {
6263
Options {
6364
case_insensitive: false,
65+
case_smart: false,
6466
line_terminator: b'\n',
6567
size_limit: 10 * (1 << 20),
6668
dfa_size_limit: 10 * (1 << 20),
@@ -98,6 +100,18 @@ impl GrepBuilder {
98100
self
99101
}
100102

103+
/// Whether to enable smart case search or not (disabled by default).
104+
///
105+
/// Smart case uses case insensitive search if the regex is contains all
106+
/// lowercase literal characters. Otherwise, a case sensitive search is
107+
/// used instead.
108+
///
109+
/// Enabling the case_insensitive flag overrides this.
110+
pub fn case_smart(mut self, yes: bool) -> GrepBuilder {
111+
self.opts.case_smart = yes;
112+
self
113+
}
114+
101115
/// Set the approximate size limit of the compiled regular expression.
102116
///
103117
/// This roughly corresponds to the number of bytes occupied by a
@@ -148,8 +162,11 @@ impl GrepBuilder {
148162
/// Creates a new regex from the given expression with the current
149163
/// configuration.
150164
fn regex(&self, expr: &Expr) -> Result<Regex> {
165+
let casei =
166+
self.opts.case_insensitive
167+
|| (self.opts.case_smart && !has_uppercase_literal(expr));
151168
RegexBuilder::new(&expr.to_string())
152-
.case_insensitive(self.opts.case_insensitive)
169+
.case_insensitive(casei)
153170
.multi_line(true)
154171
.unicode(true)
155172
.size_limit(self.opts.size_limit)
@@ -274,6 +291,23 @@ impl<'b, 's> Iterator for Iter<'b, 's> {
274291
}
275292
}
276293

294+
fn has_uppercase_literal(expr: &Expr) -> bool {
295+
use syntax::Expr::*;
296+
match *expr {
297+
Literal { ref chars, casei } => {
298+
casei || chars.iter().any(|c| c.is_uppercase())
299+
}
300+
LiteralBytes { ref bytes, casei } => {
301+
casei || bytes.iter().any(|&b| b'A' <= b && b <= b'Z')
302+
}
303+
Group { ref e, .. } => has_uppercase_literal(e),
304+
Repeat { ref e, .. } => has_uppercase_literal(e),
305+
Concat(ref es) => es.iter().any(has_uppercase_literal),
306+
Alternate(ref es) => es.iter().any(has_uppercase_literal),
307+
_ => false,
308+
}
309+
}
310+
277311
#[cfg(test)]
278312
mod tests {
279313
#![allow(unused_imports)]

src/args.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ Less common options:
154154
-p, --pretty
155155
Alias for --color=always --heading -n.
156156
157+
-S, --smart-case
158+
Search case insensitively if the pattern is all lowercase.
159+
Search case sensitively otherwise.
160+
157161
-j, --threads ARG
158162
The number of threads to use. Defaults to the number of logical CPUs
159163
(capped at 6). [default: 0]
@@ -217,6 +221,7 @@ pub struct RawArgs {
217221
flag_quiet: bool,
218222
flag_regexp: Vec<String>,
219223
flag_replace: Option<String>,
224+
flag_smart_case: bool,
220225
flag_text: bool,
221226
flag_threads: usize,
222227
flag_type: Vec<String>,
@@ -348,6 +353,7 @@ impl RawArgs {
348353
let types = try!(btypes.build());
349354
let grep = try!(
350355
GrepBuilder::new(&pattern)
356+
.case_smart(self.flag_smart_case)
351357
.case_insensitive(self.flag_ignore_case)
352358
.line_terminator(eol)
353359
.build()

tests/tests.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,18 @@ clean!(feature_68, "test", ".", |wd: WorkDir, mut cmd: Command| {
699699
assert_eq!(lines, "foo:test\n");
700700
});
701701

702+
// See: https://github.com/BurntSushi/ripgrep/issues/70
703+
sherlock!(feature_70, "sherlock", ".", |wd: WorkDir, mut cmd: Command| {
704+
cmd.arg("--smart-case");
705+
706+
let lines: String = wd.stdout(&mut cmd);
707+
let expected = "\
708+
sherlock:For the Doctor Watsons of this world, as opposed to the Sherlock
709+
sherlock:be, to a very large extent, the result of luck. Sherlock Holmes
710+
";
711+
assert_eq!(lines, expected);
712+
});
713+
702714
#[test]
703715
fn binary_nosearch() {
704716
let wd = WorkDir::new("binary_nosearch");

0 commit comments

Comments
 (0)