Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions crates/fff-c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,11 @@ pub unsafe extern "C" fn fff_live_grep(
let options = fff::GrepSearchOptions {
max_file_size: default_u64(max_file_size, 10 * 1024 * 1024),
max_matches_per_file: max_matches_per_file as usize,
smart_case,
case_mode: Some(if smart_case {
fff::CaseMode::Smart
} else {
fff::CaseMode::Sensitive
}),
file_offset: file_offset as usize,
page_limit: default_u32(page_limit, 50) as usize,
mode: grep_mode_from_u8(mode),
Expand All @@ -746,6 +750,7 @@ pub unsafe extern "C" fn fff_live_grep(
classify_definitions,
trim_whitespace: false,
abort_signal: None,
..Default::default()
};

let result = picker.grep(&parsed, &options);
Expand Down Expand Up @@ -839,7 +844,11 @@ pub unsafe extern "C" fn fff_multi_grep(
let options = fff::GrepSearchOptions {
max_file_size: default_u64(max_file_size, 10 * 1024 * 1024),
max_matches_per_file: max_matches_per_file as usize,
smart_case,
case_mode: Some(if smart_case {
fff::CaseMode::Smart
} else {
fff::CaseMode::Sensitive
}),
file_offset: file_offset as usize,
page_limit: default_u32(page_limit, 50) as usize,
mode: fff::GrepMode::PlainText, // ignored by multi_grep_search
Expand All @@ -849,6 +858,7 @@ pub unsafe extern "C" fn fff_multi_grep(
classify_definitions,
trim_whitespace: false,
abort_signal: None,
..Default::default()
};

let result = picker.multi_grep(&patterns, constraint_refs, &options);
Expand Down
59 changes: 42 additions & 17 deletions crates/fff-core/src/grep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,14 @@ fn replace_unescaped_newline_escapes(text: &str) -> String {
String::from_utf8(result).unwrap_or_else(|_| text.to_string())
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CaseMode {
#[default]
Smart,
Sensitive,
Insensitive,
}

/// Controls how the grep pattern is interpreted.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GrepMode {
Expand Down Expand Up @@ -334,7 +342,10 @@ pub use crate::constants::MAX_FFFILE_SIZE;
pub struct GrepSearchOptions {
pub max_file_size: u64,
pub max_matches_per_file: usize,
#[deprecated(note = "use `case_mode` instead")]
pub smart_case: bool,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deprecate it

/// When `Some`, overrides `smart_case`.
pub case_mode: Option<CaseMode>,
/// File-based pagination offset: index into the sorted/filtered file list
/// to start searching from. Pass 0 for the first page, then use
/// `GrepResult::next_file_offset` for subsequent pages.
Expand Down Expand Up @@ -364,12 +375,25 @@ pub struct GrepSearchOptions {
pub abort_signal: Option<Arc<AtomicBool>>,
}

impl GrepSearchOptions {
fn effective_case_mode(&self) -> CaseMode {
#[allow(deprecated)]
match self.case_mode {
Some(m) => m,
None if self.smart_case => CaseMode::Smart,
None => CaseMode::Sensitive,
}
}
}

impl Default for GrepSearchOptions {
fn default() -> Self {
#[allow(deprecated)]
Self {
max_file_size: MAX_FFFILE_SIZE,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 50,
mode: GrepMode::default(),
Expand Down Expand Up @@ -1045,11 +1069,10 @@ pub(crate) fn multi_grep_search<'a>(
};
}

// Smart case: case-insensitive when all patterns are lowercase
let case_insensitive = if options.smart_case {
!patterns.iter().any(|p| p.chars().any(|c| c.is_uppercase()))
} else {
false
let case_insensitive = match options.effective_case_mode() {
CaseMode::Smart => !patterns.iter().any(|p| p.chars().any(|c| c.is_uppercase())),
CaseMode::Insensitive => true,
CaseMode::Sensitive => false,
};

let ac = aho_corasick::AhoCorasickBuilder::new()
Expand Down Expand Up @@ -1117,7 +1140,7 @@ const fn is_utf8_char_boundary(b: u8) -> bool {
/// - The input is passed directly to the regex engine without escaping
/// - Smart case still applies
/// - Returns `None` for invalid regex patterns — the caller falls back to literal mode
fn build_regex(pattern: &str, smart_case: bool) -> Result<regex::bytes::Regex, String> {
fn build_regex(pattern: &str, case_mode: CaseMode) -> Result<regex::bytes::Regex, String> {
if pattern.is_empty() {
return Err("empty pattern".to_string());
}
Expand All @@ -1128,10 +1151,10 @@ fn build_regex(pattern: &str, smart_case: bool) -> Result<regex::bytes::Regex, S
pattern.to_string()
};

let case_insensitive = if smart_case {
!pattern.chars().any(|c| c.is_uppercase())
} else {
false
let case_insensitive = match case_mode {
CaseMode::Smart => !pattern.chars().any(|c| c.is_uppercase()),
CaseMode::Insensitive => true,
CaseMode::Sensitive => false,
};

regex::bytes::RegexBuilder::new(&regex_pattern)
Expand Down Expand Up @@ -1995,10 +2018,10 @@ pub(crate) fn grep_search<'a>(
};
}

let case_insensitive = if options.smart_case {
!grep_text.chars().any(|c| c.is_uppercase())
} else {
false
let case_insensitive = match options.effective_case_mode() {
CaseMode::Smart => !grep_text.chars().any(|c| c.is_uppercase()),
CaseMode::Insensitive => true,
CaseMode::Sensitive => false,
};

let mut regex_fallback_error: Option<String> = None;
Expand Down Expand Up @@ -2089,7 +2112,7 @@ pub(crate) fn grep_search<'a>(
overflow_arena,
);
}
GrepMode::Regex => build_regex(&grep_text, options.smart_case)
GrepMode::Regex => build_regex(&grep_text, options.effective_case_mode())
.inspect_err(|err| {
tracing::warn!("Regex compilation failed for {}. Error {}", grep_text, err);

Expand Down Expand Up @@ -2444,7 +2467,7 @@ mod tests {
let options = super::GrepSearchOptions {
max_file_size: MAX_FFFILE_SIZE,
max_matches_per_file: 0,
smart_case: true,
case_mode: Some(super::CaseMode::Smart),
file_offset: 0,
page_limit: 100,
mode: super::GrepMode::PlainText,
Expand All @@ -2454,6 +2477,7 @@ mod tests {
classify_definitions: false,
trim_whitespace: false,
abort_signal: None,
..Default::default()
};
let no_cancel = AtomicBool::new(false);

Expand Down Expand Up @@ -2628,7 +2652,7 @@ mod tests {
let options = super::GrepSearchOptions {
max_file_size: MAX_FFFILE_SIZE,
max_matches_per_file: 0,
smart_case: true,
case_mode: Some(super::CaseMode::Smart),
file_offset: 0,
page_limit: 100,
mode: super::GrepMode::PlainText,
Expand All @@ -2638,6 +2662,7 @@ mod tests {
classify_definitions: false,
trim_whitespace: false,
abort_signal: Some(std::sync::Arc::new(AtomicBool::new(false))),
..Default::default()
};
let result = picker.grep(&query, &options);

Expand Down
5 changes: 3 additions & 2 deletions crates/fff-core/tests/bigram_overlay_coherence_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1309,7 +1309,7 @@ fn grep_opts() -> GrepSearchOptions {
GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: Some(fff_search::grep::CaseMode::Smart),
file_offset: 0,
page_limit: 500,
mode: GrepMode::PlainText,
Expand All @@ -1319,6 +1319,7 @@ fn grep_opts() -> GrepSearchOptions {
classify_definitions: false,
trim_whitespace: false,
abort_signal: None,
..Default::default()
}
}

Expand Down Expand Up @@ -1653,4 +1654,4 @@ fn bigram_overlay_coherence_fuzzy_grep_finds_overflow_files() {
}

stop_picker(&shared_picker);
}
}
5 changes: 3 additions & 2 deletions crates/fff-core/tests/bigram_overlay_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ fn grep_opts() -> GrepSearchOptions {
GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: Some(fff_search::grep::CaseMode::Smart),
file_offset: 0,
page_limit: 200,
mode: GrepMode::PlainText,
Expand All @@ -378,6 +378,7 @@ fn grep_opts() -> GrepSearchOptions {
classify_definitions: false,
trim_whitespace: false,
abort_signal: None,
..Default::default()
}
}

Expand Down Expand Up @@ -407,4 +408,4 @@ fn wait_for_bigram(shared_picker: &SharedFilePicker) {
"Timed out waiting for bigram build"
);
}
}
}
5 changes: 3 additions & 2 deletions crates/fff-core/tests/fuzz_file_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ fn grep_plain_opts() -> GrepSearchOptions {
GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: Some(fff_search::grep::CaseMode::Smart),
file_offset: 0,
page_limit: 500,
mode: GrepMode::PlainText,
Expand All @@ -636,6 +636,7 @@ fn grep_plain_opts() -> GrepSearchOptions {
classify_definitions: false,
trim_whitespace: false,
abort_signal: None,
..Default::default()
}
}

Expand Down Expand Up @@ -857,4 +858,4 @@ fn drop_during_post_scan_does_not_crash() {
The test is not exercising the race. ({caught_active}/10)"
);
eprintln!("Caught post-scan active in {caught_active}/10 rounds");
}
}
11 changes: 7 additions & 4 deletions crates/fff-core/tests/fuzz_git_watcher_stress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ fn grep_plain_matches(shared: &SharedFilePicker, query: &str) -> Vec<String> {
let opts = GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: Some(fff_search::grep::CaseMode::Smart),
file_offset: 0,
page_limit: 500,
mode: GrepMode::PlainText,
Expand All @@ -901,6 +901,7 @@ fn grep_plain_matches(shared: &SharedFilePicker, query: &str) -> Vec<String> {
classify_definitions: false,
trim_whitespace: false,
abort_signal: None,
..Default::default()
};
let result = picker.grep(&parsed, &opts);
// `GrepResult::files` is the already-deduplicated list of files that
Expand Down Expand Up @@ -928,7 +929,7 @@ fn grep_fuzzy_matches(shared: &SharedFilePicker, query: &str) -> Vec<String> {
let opts = GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: Some(fff_search::grep::CaseMode::Smart),
file_offset: 0,
page_limit: 500,
mode: GrepMode::Fuzzy,
Expand All @@ -938,6 +939,7 @@ fn grep_fuzzy_matches(shared: &SharedFilePicker, query: &str) -> Vec<String> {
classify_definitions: false,
trim_whitespace: false,
abort_signal: None,
..Default::default()
};
let result = picker.grep(&parsed, &opts);
result
Expand All @@ -960,7 +962,7 @@ fn grep_regex_matches(shared: &SharedFilePicker, query: &str) -> Vec<String> {
let opts = GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: Some(fff_search::grep::CaseMode::Smart),
file_offset: 0,
page_limit: 500,
mode: GrepMode::Regex,
Expand All @@ -970,6 +972,7 @@ fn grep_regex_matches(shared: &SharedFilePicker, query: &str) -> Vec<String> {
classify_definitions: false,
trim_whitespace: false,
abort_signal: None,
..Default::default()
};
let result = picker.grep(&parsed, &opts);
result
Expand Down Expand Up @@ -1607,4 +1610,4 @@ fn expect_file_status(
}
std::thread::sleep(CONVERGE_POLL);
}
}
}
5 changes: 3 additions & 2 deletions crates/fff-core/tests/fuzz_real_repos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ fn grep_opts(mode: GrepMode) -> GrepSearchOptions {
GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: Some(fff_search::grep::CaseMode::Smart),
file_offset: 0,
page_limit: 500,
mode,
Expand All @@ -236,6 +236,7 @@ fn grep_opts(mode: GrepMode) -> GrepSearchOptions {
classify_definitions: false,
trim_whitespace: false,
abort_signal: None,
..Default::default()
}
}

Expand Down Expand Up @@ -737,4 +738,4 @@ proptest! {
fn fuzz_real_repos_proptest(ops in ops_strategy()) {
run_scenario(&ops);
}
}
}
Loading
Loading