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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ local r = require('fff').content_search('TODO', {
time_budget_ms = 0,
enforce_time_budget = false, -- also bound zero-match searches
trim_whitespace = false,
classify_definitions = false,
classify_definitions = nil, -- off unless grep.classify_definitions is set in your config
cwd = nil, -- switch indexed root if different
wait_for_index_ms = nil, -- override the default scan wait timeout
})
Expand Down Expand Up @@ -497,7 +497,7 @@ require('fff').setup({
enforce_time_budget = false, -- apply time_budget_ms even before anything matched (off = zero-match queries scan everything)
modes = { 'plain', 'regex', 'fuzzy', 'definition_only' },
trim_whitespace = false,
classify_definitions = false, -- show matching definitions first within each page
classify_definitions = true, -- show matching definitions first within each page
enable_filename_constraint = false, -- treat filename-like tokens (e.g. `score.rs`) in a grep query as a file-path filter scoping the search; off = searched as literal text
location_format = ':%d:%d', -- printf format for line:col prefix in grep results, e.g. ':%d' for line-only
},
Expand Down Expand Up @@ -536,7 +536,7 @@ require('fff').setup({

`<S-Tab>` cycles between `plain`, `regex`, `fuzzy`, and `definition_only`. The list is configurable via `grep.modes`, and single-mode setups hide the indicator entirely. `definition_only` uses plain substring matching and returns only matched declarations, regardless of `grep.classify_definitions`. Its status label uses `hl.grep_definition_active` (default: `DiagnosticOk`). `regex` matches stay within one line unless the pattern contains `\n`.

Set `grep.classify_definitions = true` to show matching definition lines first within each page, grouped under their file names. Each page uses one ordinary grep search; classification does not scan ahead for definitions or search the files again for usages. It defaults to `false`, which skips definition classification and grouping. It also accepts the strings `'classify'`, `'definition_only'`, and `'usage_only'`. A **Definitions** divider separates the sections when both are on the same page. With a bottom prompt, the order reverses so definitions stay nearest the prompt. Files with both kinds of matches appear in both sections, with each match shown once.
`grep.classify_definitions` shows matching definition lines first within each page, grouped under their file names. Each page uses one ordinary grep search; classification does not scan ahead for definitions or search the files again for usages. It defaults to `true`. Set it to `false` to keep file order without definition grouping. It also accepts the strings `'classify'`, `'definition_only'`, and `'usage_only'`. A **Definitions** divider separates the sections when both are on the same page. With a bottom prompt, the order reverses so definitions stay nearest the prompt. Files with both kinds of matches appear in both sections, with each match shown once.

Definition filtering occurs before page and per-file result limits. A timed-out page can be empty; move down (top prompt) or up (bottom prompt) to continue searching. Context lines can still contain ordinary code.

Expand Down
2 changes: 1 addition & 1 deletion lua/fff/conf.lua
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ local function init()
enforce_time_budget = false, -- Apply time_budget_ms even before anything matched (off = zero-match queries scan everything)
modes = { 'plain', 'regex', 'fuzzy', 'definition_only' }, -- Available grep modes and their cycling order
trim_whitespace = false, -- Strip leading whitespace from matched lines (useful for cleaner display)
classify_definitions = false, -- Show matching definitions first within each page
classify_definitions = true, -- Show matching definitions first within each page
-- Treat filename-like tokens (e.g. `score.rs`, `src/main.rs`) in a grep query as a
-- file-path filter, scoping the content search to matching files. When off, such
-- tokens are searched as literal text. A token is a filename if it has a valid-looking
Expand Down
6 changes: 4 additions & 2 deletions lua/fff/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ end
--- @field time_budget_ms? number Max wall-clock time, 0 = unlimited (default: config.grep.time_budget_ms).
--- @field enforce_time_budget? boolean Apply the budget even with zero matches (default: config.grep.enforce_time_budget).
--- @field trim_whitespace? boolean Strip leading whitespace from matched lines (default: config.grep.trim_whitespace).
--- @field classify_definitions? boolean|FffDefinitionMode Classify matches or return definitions/usages only (default: config.grep.classify_definitions).
--- @field classify_definitions? boolean|FffDefinitionMode Classify matches or return definitions/usages only (default: grep.classify_definitions from the user config, otherwise off).
--- @field cwd? string Switch indexed root before grepping (same semantics as `file_search`).
--- @field wait_for_index_ms? number Block up to this many ms for the index to be ready.

Expand Down Expand Up @@ -387,7 +387,9 @@ function M.content_search(query, opts)
classify_definitions = opts.classify_definitions,
}
if merged_grep_cfg.classify_definitions == nil then
merged_grep_cfg.classify_definitions = grep_cfg.classify_definitions
-- Programmatic results keep file order unless the user's own config enables grouping.
local user_grep = type(vim.g.fff) == 'table' and vim.g.fff.grep or nil
merged_grep_cfg.classify_definitions = type(user_grep) == 'table' and user_grep.classify_definitions or false
end

local ok, result = pcall(grep.search, query, opts.file_offset or 0, opts.page_size or 50, merged_grep_cfg, mode)
Expand Down
6 changes: 6 additions & 0 deletions tests/grep_classification_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ describe('grep classification bridge', function()
end)
end

it('classifies definitions by default', function()
grep.search('needle', 7, 5, nil, 'plain')
assert.are.equal(1, #calls)
assert.are.equal('classify', calls[1][12])
end)

it('requests filtering only when the definitions mode is selected', function()
grep.search('needle', 7, 5, { classify_definitions = true }, 'definition_only')
assert.are.equal(1, #calls)
Expand Down
18 changes: 18 additions & 0 deletions tests/programmatic_search_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,24 @@ describe('programmatic search APIs', function()
assert.is_true(count >= 2, 'expected the marker in at least 2 files, got ' .. count)
end)

it('keeps file order unless the user config enables classification', function()
local fuzzy = require('fff.fuzzy')
local original, saved = fuzzy.live_grep, vim.g.fff
local modes = {}
fuzzy.live_grep = function(...)
modes[#modes + 1] = select(12, ...)
return original(...)
end
local ok, err = pcall(function()
fff.content_search(marker)
vim.g.fff = vim.tbl_deep_extend('force', saved or {}, { grep = { classify_definitions = true } })
fff.content_search(marker)
end)
fuzzy.live_grep, vim.g.fff = original, saved
assert(ok, err)
assert.are.same({ 'off', 'classify' }, modes)
end)

it('regex mode matches a pattern', function()
local result = fff.content_search('canonicalize_\\w+', { mode = 'regex' })
assert.is_true(#result.items > 0, 'regex content_search returned no matches')
Expand Down
Loading