diff --git a/Cargo.lock b/Cargo.lock index 4069054..cd95f0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,7 @@ dependencies = [ "ignore 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index d79807d..a170707 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ ansi_term = "0.9" clap = "2.26.0" atty = "0.2" regex = "0.2" +regex-syntax = "0.4" ignore = "0.2" num_cpus = "1.6.2" diff --git a/src/internal.rs b/src/internal.rs index 1c1d66a..185068a 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -4,6 +4,7 @@ use std::io::Write; use lscolors::LsColors; use walk::FileType; +use regex_syntax::{Expr, ExprBuilder}; /// Root directory #[cfg(unix)] @@ -78,3 +79,28 @@ pub fn error(message: &str) -> ! { writeln!(&mut ::std::io::stderr(), "{}", message).expect("Failed writing to stderr"); process::exit(1); } + +/// Determine if a regex pattern contains a literal uppercase character. +pub fn pattern_has_uppercase_char(pattern: &str) -> bool { + ExprBuilder::new() + .parse(pattern) + .map(|expr| expr_has_uppercase_char(&expr)) + .unwrap_or(false) +} + +/// Determine if a regex expression contains a literal uppercase character. +fn expr_has_uppercase_char(expr: &Expr) -> bool { + match *expr { + Expr::Literal { ref chars, .. } => chars.iter().any(|c| c.is_uppercase()), + Expr::Class(ref ranges) => { + ranges.iter().any(|r| { + r.start.is_uppercase() || r.end.is_uppercase() + }) + } + Expr::Group { ref e, .. } => expr_has_uppercase_char(e), + Expr::Repeat { ref e, .. } => expr_has_uppercase_char(e), + Expr::Concat(ref es) => es.iter().any(expr_has_uppercase_char), + Expr::Alternate(ref es) => es.iter().any(expr_has_uppercase_char), + _ => false, + } +} diff --git a/src/main.rs b/src/main.rs index 9912afd..2e8b283 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ extern crate clap; extern crate ansi_term; extern crate atty; extern crate regex; +extern crate regex_syntax; extern crate ignore; extern crate num_cpus; @@ -22,7 +23,7 @@ use std::time; use atty::Stream; use regex::RegexBuilder; -use internal::{error, FdOptions, PathDisplay, ROOT_DIR}; +use internal::{error, pattern_has_uppercase_char, FdOptions, PathDisplay, ROOT_DIR}; use lscolors::LsColors; use walk::FileType; @@ -65,11 +66,8 @@ fn main() { // The search will be case-sensitive if the command line flag is set or // if the pattern has an uppercase character (smart case). - let case_sensitive = if !matches.is_present("ignore-case") { - matches.is_present("case-sensitive") || pattern.chars().any(char::is_uppercase) - } else { - false - }; + let case_sensitive = !matches.is_present("ignore-case") && + (matches.is_present("case-sensitive") || pattern_has_uppercase_char(pattern)); let colored_output = match matches.value_of("color") { Some("always") => true, diff --git a/tests/tests.rs b/tests/tests.rs index 49faaef..dfd02fb 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -115,6 +115,14 @@ fn test_smart_case() { te.assert_output(&["C.Foo"], "one/two/C.Foo2"); te.assert_output(&["Foo"], "one/two/C.Foo2"); + + // Only literal uppercase chars should trigger case sensitivity. + te.assert_output( + &["\\Ac"], + "one/two/c.foo + one/two/C.Foo2", + ); + te.assert_output(&["\\AC"], "one/two/C.Foo2"); } /// Case sensitivity (--case-sensitive)