Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
2 changes: 2 additions & 0 deletions crates/fff-c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,7 @@ pub unsafe extern "C" fn fff_live_grep(
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: None,
file_offset: file_offset as usize,
page_limit: default_u32(page_limit, 50) as usize,
mode: grep_mode_from_u8(mode),
Expand Down Expand Up @@ -840,6 +841,7 @@ pub unsafe extern "C" fn fff_multi_grep(
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: None,
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 Down
56 changes: 41 additions & 15 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 @@ -335,6 +343,10 @@ pub struct GrepSearchOptions {
pub max_file_size: u64,
pub max_matches_per_file: usize,
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

/// Explicit case mode. When `Some`, overrides `smart_case`. When `None`,

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.

make comment smaller

/// `smart_case` is used (true => Smart, false => Sensitive). Non-breaking
/// addition; existing callers can ignore this field.
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 +376,25 @@ pub struct GrepSearchOptions {
pub abort_signal: Option<Arc<AtomicBool>>,
}

impl GrepSearchOptions {
/// Resolves the effective case mode, preferring explicit `case_mode` over
/// the legacy `smart_case` boolean.
pub fn effective_case_mode(&self) -> CaseMode {

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.

make privat eremov ecommetn

match self.case_mode {
Some(m) => m,
None if self.smart_case => CaseMode::Smart,
None => CaseMode::Sensitive,
}
}
}

impl Default for GrepSearchOptions {
fn default() -> Self {
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 +1070,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 +1141,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 +1152,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 +2019,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 +2113,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 @@ -2445,6 +2469,7 @@ mod tests {
max_file_size: MAX_FFFILE_SIZE,
max_matches_per_file: 0,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 100,
mode: super::GrepMode::PlainText,
Expand Down Expand Up @@ -2629,6 +2654,7 @@ mod tests {
max_file_size: MAX_FFFILE_SIZE,
max_matches_per_file: 0,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 100,
mode: super::GrepMode::PlainText,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-core/tests/bigram_overlay_coherence_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,7 @@ fn grep_opts() -> GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 500,
mode: GrepMode::PlainText,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-core/tests/bigram_overlay_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ fn grep_opts() -> GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 200,
mode: GrepMode::PlainText,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-core/tests/fuzz_file_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ fn grep_plain_opts() -> GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 500,
mode: GrepMode::PlainText,
Expand Down
3 changes: 3 additions & 0 deletions crates/fff-core/tests/fuzz_git_watcher_stress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,7 @@ fn grep_plain_matches(shared: &SharedFilePicker, query: &str) -> Vec<String> {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 500,
mode: GrepMode::PlainText,
Expand Down Expand Up @@ -929,6 +930,7 @@ fn grep_fuzzy_matches(shared: &SharedFilePicker, query: &str) -> Vec<String> {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 500,
mode: GrepMode::Fuzzy,
Expand Down Expand Up @@ -961,6 +963,7 @@ fn grep_regex_matches(shared: &SharedFilePicker, query: &str) -> Vec<String> {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 500,
mode: GrepMode::Regex,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-core/tests/fuzz_real_repos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ fn grep_opts(mode: GrepMode) -> GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 500,
mode,
Expand Down
3 changes: 3 additions & 0 deletions crates/fff-core/tests/grep_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fn plain_opts() -> GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 200,
mode: GrepMode::PlainText,
Expand All @@ -50,6 +51,7 @@ fn regex_opts() -> GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 200,
mode: GrepMode::Regex,
Expand All @@ -68,6 +70,7 @@ fn fuzzy_opts() -> GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 200,
mode: GrepMode::Fuzzy,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-core/tests/new_directory_watcher_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ fn grep_plain_count(picker: &FilePicker, query: &str) -> usize {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 500,
mode: GrepMode::PlainText,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-core/tests/path_separator_constraint_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ fn plain_opts() -> GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 200,
mode: GrepMode::PlainText,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-core/tests/real_binary_fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fn plain_opts() -> GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 200,
mode: GrepMode::PlainText,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-mcp/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ fn make_grep_options(
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: matches_per_file,
smart_case: true,
case_mode: None,
file_offset,
page_limit: 50,
mode,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-nvim/benches/fuzzy_search_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ fn bench_grep_search(c: &mut Criterion) {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 0,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 100,
mode: GrepMode::PlainText,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-nvim/benches/grep_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ fn plain_options() -> GrepSearchOptions {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 50,
mode: GrepMode::PlainText,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-nvim/src/bin/bench_grep_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fn run_grep(picker: &FilePicker, query: &str, iters: usize) {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: usize::MAX,
mode: GrepMode::PlainText,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-nvim/src/bin/fuzzy_grep_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ fn run_fuzzy_query(picker: &FilePicker, query: &str, label: &str) {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 100,
mode: GrepMode::Fuzzy,
Expand Down
2 changes: 2 additions & 0 deletions crates/fff-nvim/src/bin/grep_profiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl<'a> GrepBench<'a> {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 50,
mode,
Expand Down Expand Up @@ -410,6 +411,7 @@ fn main() {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset,
page_limit: 50,
mode: Default::default(),
Expand Down
2 changes: 2 additions & 0 deletions crates/fff-nvim/src/bin/grep_vs_rg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ fn run_fff_full(picker: &FilePicker, query: &str) -> (usize, Duration) {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: usize::MAX,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: usize::MAX,
mode: Default::default(),
Expand All @@ -186,6 +187,7 @@ fn run_fff_page(picker: &FilePicker, query: &str) -> (usize, Duration) {
max_file_size: 10 * 1024 * 1024,
max_matches_per_file: 200,
smart_case: true,
case_mode: None,
file_offset: 0,
page_limit: 50,
mode: Default::default(),
Expand Down
10 changes: 10 additions & 0 deletions crates/fff-nvim/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ pub fn live_grep(
grep_mode,
time_budget_ms,
trim_whitespace,
case_mode,
): (
String,
Option<usize>,
Expand All @@ -446,6 +447,7 @@ pub fn live_grep(
Option<String>,
Option<u64>,
Option<bool>,
Option<String>,
),
) -> LuaResult<LuaValue> {
let file_picker_guard = FILE_PICKER.read().into_lua_result()?;
Expand All @@ -460,10 +462,18 @@ pub fn live_grep(
_ => fff::GrepMode::PlainText, // "plain" or nil or unknown
};

let case_mode = match case_mode.as_deref() {
Some("smart") => Some(fff::CaseMode::Smart),
Some("sensitive") => Some(fff::CaseMode::Sensitive),
Some("insensitive") => Some(fff::CaseMode::Insensitive),
_ => None,
};

let options = fff::GrepSearchOptions {
max_file_size: max_file_size.unwrap_or(10 * 1024 * 1024),
max_matches_per_file: max_matches_per_file.unwrap_or(200),
smart_case: smart_case.unwrap_or(true),
case_mode,
file_offset: file_offset.unwrap_or(0),
page_limit: page_size.unwrap_or(50),
mode,
Expand Down
1 change: 1 addition & 0 deletions crates/fff-python/src/finder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ fn grep_options(
max_file_size: defaulted_u64(max_file_size, defaults.max_file_size),
max_matches_per_file: max_matches_per_file as usize,
smart_case,
case_mode: None,
file_offset: cursor_offset,
page_limit: defaulted_usize(page_limit, defaults.page_limit),
mode,
Expand Down
4 changes: 3 additions & 1 deletion lua/fff/conf.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ local M = {}
--- @field max_file_size number
--- @field max_matches_per_file number
--- @field smart_case boolean
--- @field case_mode? "smart"|"sensitive"|"insensitive"
--- @field time_budget_ms number
--- @field modes string[]
--- @field trim_whitespace boolean
Expand Down Expand Up @@ -414,7 +415,8 @@ local function init()
grep = {
max_file_size = 10 * 1024 * 1024, -- Skip files larger than 10MB
max_matches_per_file = 100, -- Maximum matches per file (set 0 to unlimited)
smart_case = true, -- Case-insensitive unless query has uppercase
smart_case = true, -- Case-insensitive unless query has uppercase (legacy; prefer case_mode)
case_mode = nil, -- Optional: "smart" | "sensitive" | "insensitive". Overrides smart_case when set.
time_budget_ms = 150, -- Max search time in ms per call (prevents UI freeze, 0 = no limit)
modes = { 'plain', 'regex', 'fuzzy' }, -- Available grep modes and their cycling order
trim_whitespace = false, -- Strip leading whitespace from matched lines (useful for cleaner display)
Expand Down
Loading
Loading