feat(picker.lsp): added option keep_parents to lsp_symbols (default false). See #2083. closes #2266

This commit is contained in:
Folke Lemaitre 2025-10-22 11:23:55 +02:00
parent 471eb036c4
commit 2b9d52258d
No known key found for this signature in database
GPG key ID: 9B52594D560070AB
4 changed files with 63 additions and 58 deletions

View file

@ -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<string, string[]|boolean>? symbol kind filter
---@field workspace? boolean show workspace symbols
M.lsp_symbols = {

View file

@ -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

View file

@ -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

View file

@ -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