From ba0756d011c1d887959ba265d9f0bd02dddebc20 Mon Sep 17 00:00:00 2001 From: Dmitriy Kovalenko Date: Thu, 15 Oct 2026 20:30:54 -0700 Subject: [PATCH 1/2] feat(nvim): show definitions first in live grep by default Live grep and content_search now group matching definitions before usages within each page unless grep.classify_definitions is false. --- README.md | 6 +++--- lua/fff/conf.lua | 2 +- tests/grep_classification_spec.lua | 6 ++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d6aba19e8..2cbdcfe2a 100644 --- a/README.md +++ b/README.md @@ -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, -- defaults to grep.classify_definitions cwd = nil, -- switch indexed root if different wait_for_index_ms = nil, -- override the default scan wait timeout }) @@ -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 }, @@ -536,7 +536,7 @@ require('fff').setup({ `` 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. diff --git a/lua/fff/conf.lua b/lua/fff/conf.lua index 7ad2a7ca8..7b7996a77 100644 --- a/lua/fff/conf.lua +++ b/lua/fff/conf.lua @@ -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 diff --git a/tests/grep_classification_spec.lua b/tests/grep_classification_spec.lua index 3d8bf0367..f9c1eaf13 100644 --- a/tests/grep_classification_spec.lua +++ b/tests/grep_classification_spec.lua @@ -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) From 12221f29832d06a76b3e7cb71fa07d21b7325fe1 Mon Sep 17 00:00:00 2001 From: Dmitriy Kovalenko Date: Thu, 15 Oct 2026 21:19:15 -0700 Subject: [PATCH 2/2] fix(nvim): keep content_search in file order unless configured The new picker default does not change programmatic results: content_search classifies only when the caller or the user config asks for it. --- README.md | 2 +- lua/fff/main.lua | 6 ++++-- tests/programmatic_search_spec.lua | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2cbdcfe2a..a63fa414a 100644 --- a/README.md +++ b/README.md @@ -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 = nil, -- defaults to grep.classify_definitions + 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 }) diff --git a/lua/fff/main.lua b/lua/fff/main.lua index 56cfcf0e5..a81616d0c 100644 --- a/lua/fff/main.lua +++ b/lua/fff/main.lua @@ -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. @@ -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) diff --git a/tests/programmatic_search_spec.lua b/tests/programmatic_search_spec.lua index 40befe10e..cfb55c443 100644 --- a/tests/programmatic_search_spec.lua +++ b/tests/programmatic_search_spec.lua @@ -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')