diff --git a/lua/snacks/picker/config/sources.lua b/lua/snacks/picker/config/sources.lua index 6893ead0..fe8e93b1 100644 --- a/lua/snacks/picker/config/sources.lua +++ b/lua/snacks/picker/config/sources.lua @@ -587,6 +587,7 @@ M.lsp_references = { -- LSP document symbols ---@class snacks.picker.lsp.symbols.Config: snacks.picker.Config ---@field tree? boolean show symbol tree +---@field keep_parents? boolean keep parent symbols when filtering ---@field filter table? symbol kind filter ---@field workspace? boolean show workspace symbols M.lsp_symbols = { diff --git a/lua/snacks/picker/core/matcher.lua b/lua/snacks/picker/core/matcher.lua index 944913ad..490f3c44 100644 --- a/lua/snacks/picker/core/matcher.lua +++ b/lua/snacks/picker/core/matcher.lua @@ -8,6 +8,8 @@ local Async = require("snacks.picker.util.async") ---@field regex? boolean used internally for positions of sources that use regex ---@field on_match? fun(matcher: snacks.picker.Matcher, item: snacks.picker.Item) ---@field on_done? fun(matcher: snacks.picker.Matcher) +---@field keep_parents? boolean +---@field sort? boolean ---@class snacks.picker.Matcher ---@field opts snacks.picker.matcher.Config @@ -80,21 +82,64 @@ function M:close() self.task = Async.nop() end +---@param picker snacks.Picker +---@param item snacks.picker.Item +function M:on_match(picker, item) + if self.opts.on_match then + self.opts.on_match(self, item) + end + + if not self.opts.keep_parents or item.score == 0 then + return + end + + local parent = item.parent + item.child_match_only = false + while parent and not parent.root do + if parent.score == 0 or parent.match_tick ~= self.tick then + parent.score = 1 + parent.child_match_only = true + parent.match_tick = self.tick + parent.match_topk = nil + picker.list:add(parent, self.sorting) + else + break + end + parent = parent.parent + end +end + +---@param picker snacks.Picker +function M:on_done(picker) + vim.schedule(function() + if self.opts.on_done then + self.opts.on_done(self) + end + if not self.opts.keep_parents or picker.closed then + return + end + for item, idx in picker:iter() do + if not item.child_match_only then + picker.list:view(idx) + return + end + end + end) +end + ---@param picker snacks.Picker function M:run(picker) self.task:abort() picker.list:clear() self.cwd = svim.fs.normalize(picker.opts.cwd or (vim.uv or vim.loop).cwd() or ".") - self.sorting = not self:empty() or picker.opts.matcher.sort_empty + self.sorting = self.opts.sort ~= false and (not self:empty() or picker.opts.matcher.sort_empty) -- PERF: fast path for empty pattern - if not (self.sorting or picker.finder.task:running()) then + if not self.sorting and not picker.finder.task:running() and self:empty() then picker.list.items = picker.finder.items picker:update({ force = true }) - if self.opts.on_done then - self.opts.on_done(self) - end + self:on_done(picker) return end @@ -105,7 +150,7 @@ function M:run(picker) ---@async ---@param item snacks.picker.Item local function check(item) - if self:update(item) then + if self:update(picker, item) then picker.list:add(item, self.sorting) end yield() @@ -161,11 +206,7 @@ function M:run(picker) until idx >= #picker.finder.items and not picker.finder.task:running() picker:update({ force = true }) - if self.opts.on_done then - vim.schedule(function() - self.opts.on_done(self) - end) - end + self:on_done(picker) end) end @@ -307,9 +348,10 @@ function M:_prepare(pattern) return mods end +---@param picker snacks.Picker ---@param item snacks.picker.Item ---@return boolean matched -function M:update(item) +function M:update(picker, item) if item.match_pos then item.pos = nil end @@ -339,9 +381,7 @@ function M:update(item) end end item.score = score - if self.opts.on_match then - self.opts.on_match(self, item) - end + self:on_match(picker, item) else item.score = 0 end diff --git a/lua/snacks/picker/source/explorer.lua b/lua/snacks/picker/source/explorer.lua index 69e74620..5b241b5b 100644 --- a/lua/snacks/picker/source/explorer.lua +++ b/lua/snacks/picker/source/explorer.lua @@ -143,7 +143,6 @@ end ---@param opts snacks.picker.explorer.Config function M.setup(opts) local searching = false - local ref ---@type snacks.Picker.ref return Snacks.config.merge(opts, { actions = { confirm = Actions.actions.confirm, @@ -153,7 +152,6 @@ function M.setup(opts) ---@param picker snacks.Picker ---@param filter snacks.picker.Filter transform = function(picker, filter) - ref = picker:ref() local s = not filter:is_empty() if searching ~= s then searching = s @@ -162,46 +160,6 @@ function M.setup(opts) end end, }, - matcher = { - --- Add parent dirs to matching items - ---@param matcher snacks.picker.Matcher - ---@param item snacks.picker.explorer.Item - on_match = function(matcher, item) - if not searching then - return - end - local picker = ref.value - if picker and item.score > 0 then - local parent = item.parent - while parent do - if parent.score == 0 or parent.match_tick ~= matcher.tick then - parent.score = 1 - parent.match_tick = matcher.tick - parent.match_topk = nil - picker.list:add(parent) - else - break - end - parent = parent.parent - end - end - end, - on_done = function() - if not searching then - return - end - local picker = ref.value - if not picker or picker.closed then - return - end - for item, idx in picker:iter() do - if not item.dir then - picker.list:view(idx) - return - end - end - end, - }, formatters = { file = { filename_only = opts.tree, @@ -223,7 +181,9 @@ end function M.explorer(opts, ctx) local state = M.get_state(ctx.picker) + ctx.picker.matcher.opts.keep_parents = false if state:setup(ctx) then + ctx.picker.matcher.opts.keep_parents = true return M.search(opts, ctx) end diff --git a/lua/snacks/picker/source/lsp/init.lua b/lua/snacks/picker/source/lsp/init.lua index 823f7d63..1ad5babe 100644 --- a/lua/snacks/picker/source/lsp/init.lua +++ b/lua/snacks/picker/source/lsp/init.lua @@ -315,7 +315,7 @@ function M.results_to_items(client, results, opts) result.children = nil end - local root = { text = "" } ---@type snacks.picker.finder.Item + local root = { text = "", root = true } ---@type snacks.picker.finder.Item ---@type snacks.picker.finder.Item for _, result in ipairs(results) do add(result, root) @@ -330,6 +330,10 @@ end ---@param opts snacks.picker.lsp.symbols.Config ---@type snacks.picker.finder function M.symbols(opts, ctx) + if opts.keep_parents then + ctx.picker.matcher.opts.keep_parents = true + ctx.picker.matcher.opts.sort = false + end local buf = ctx.filter.current_buf -- For unloaded buffers, load the buffer and -- refresh the picker on every LspAttach event