mirror of
https://github.com/folke/snacks.nvim
synced 2025-08-06 11:48:23 +00:00
fix(picker.lsp): lazy resolve item lsp locations. Fixes #650
This commit is contained in:
parent
f7fddf8dfb
commit
d0a0046e37
5 changed files with 101 additions and 39 deletions
|
@ -6,6 +6,7 @@ local M = {}
|
|||
---@alias snacks.picker.format fun(item:snacks.picker.Item, picker:snacks.Picker):snacks.picker.Highlight[]
|
||||
---@alias snacks.picker.preview fun(ctx: snacks.picker.preview.ctx):boolean?
|
||||
---@alias snacks.picker.sort fun(a:snacks.picker.Item, b:snacks.picker.Item):boolean
|
||||
---@alias snacks.picker.Pos {[1]:number, [2]:number}
|
||||
|
||||
--- Generic filter used by finders to pre-filter items
|
||||
---@class snacks.picker.filter.Config
|
||||
|
@ -29,9 +30,11 @@ local M = {}
|
|||
---@field score_add? number
|
||||
---@field score_mul? number
|
||||
---@field match_tick? number
|
||||
---@field file? string
|
||||
---@field text string
|
||||
---@field pos? {[1]:number, [2]:number}
|
||||
---@field end_pos? {[1]:number, [2]:number}
|
||||
---@field pos? snacks.picker.Pos
|
||||
---@field loc? snacks.picker.lsp.Loc
|
||||
---@field end_pos? snacks.picker.Pos
|
||||
---@field highlights? snacks.picker.Highlight[][]
|
||||
---@field preview? snacks.picker.Item.preview
|
||||
---@field resolve? fun(item:snacks.picker.Item)
|
||||
|
|
|
@ -334,6 +334,16 @@ function M:show()
|
|||
end
|
||||
end
|
||||
|
||||
---@param item snacks.picker.Item?
|
||||
function M:resolve(item)
|
||||
if not item then
|
||||
return
|
||||
end
|
||||
Snacks.picker.util.resolve(item)
|
||||
Snacks.picker.util.resolve_loc(item)
|
||||
return item
|
||||
end
|
||||
|
||||
--- Returns an iterator over the filtered items in the picker.
|
||||
--- Items will be in sorted order.
|
||||
---@return fun():snacks.picker.Item?
|
||||
|
@ -343,7 +353,7 @@ function M:iter()
|
|||
return function()
|
||||
i = i + 1
|
||||
if i <= n then
|
||||
return self.list:get(i)
|
||||
return self:resolve(self.list:get(i))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -358,20 +368,29 @@ function M:items()
|
|||
end
|
||||
|
||||
--- Get the current item at the cursor
|
||||
function M:current()
|
||||
return self.list:current()
|
||||
---@param opts? {resolve?: boolean} default is `true`
|
||||
function M:current(opts)
|
||||
opts = opts or {}
|
||||
local ret = self.list:current()
|
||||
if ret and opts.resolve ~= false then
|
||||
ret = self:resolve(ret)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
--- Get the selected items.
|
||||
--- If `fallback=true` and there is no selection, return the current item.
|
||||
---@param opts? {fallback?: boolean} default is `false`
|
||||
---@return snacks.picker.Item[]
|
||||
function M:selected(opts)
|
||||
opts = opts or {}
|
||||
local ret = vim.deepcopy(self.list.selected)
|
||||
if #ret == 0 and opts.fallback then
|
||||
return { self:current() }
|
||||
ret = { self:current() }
|
||||
end
|
||||
return ret
|
||||
return vim.tbl_map(function(item)
|
||||
return self:resolve(item)
|
||||
end, ret)
|
||||
end
|
||||
|
||||
--- Total number of items in the picker
|
||||
|
|
|
@ -93,7 +93,7 @@ end
|
|||
|
||||
---@param picker snacks.Picker
|
||||
function M:show(picker)
|
||||
local item, prev = picker:current(), self.item
|
||||
local item, prev = picker:current({ resolve = false }), self.item
|
||||
if self.item == item then
|
||||
return
|
||||
end
|
||||
|
@ -210,7 +210,10 @@ function M:loc()
|
|||
if not self.item then
|
||||
return
|
||||
end
|
||||
|
||||
local line_count = vim.api.nvim_buf_line_count(self.win.buf)
|
||||
Snacks.picker.util.resolve_loc(self.item, self.win.buf)
|
||||
|
||||
if self.item.pos and self.item.pos[1] > 0 and self.item.pos[1] <= line_count then
|
||||
vim.api.nvim_win_set_cursor(self.win.win, { self.item.pos[1], 0 })
|
||||
vim.api.nvim_win_call(self.win.win, function()
|
||||
|
|
|
@ -15,6 +15,10 @@ local islist = vim.islist or vim.tbl_islist
|
|||
---@alias lsp.Symbol lsp.SymbolInformation|lsp.DocumentSymbol
|
||||
---@alias lsp.Loc lsp.Location|lsp.LocationLink
|
||||
|
||||
---@class snacks.picker.lsp.Loc: lsp.Location
|
||||
---@field encoding string
|
||||
---@field resolved? boolean
|
||||
|
||||
local kinds = nil ---@type table<lsp.SymbolKind, string>
|
||||
|
||||
--- Gets the original symbol kind name from its number.
|
||||
|
@ -66,6 +70,23 @@ local function wrap(client)
|
|||
})
|
||||
end
|
||||
|
||||
---@param item snacks.picker.finder.Item
|
||||
---@param result lsp.Loc
|
||||
---@param client vim.lsp.Client
|
||||
function M.add_loc(item, result, client)
|
||||
---@type snacks.picker.lsp.Loc
|
||||
local loc = {
|
||||
uri = result.uri or result.targetUri,
|
||||
range = result.range or result.targetSelectionRange,
|
||||
encoding = client.offset_encoding,
|
||||
}
|
||||
item.loc = loc
|
||||
item.pos = { loc.range.start.line + 1, loc.range.start.character }
|
||||
item.end_pos = { loc.range["end"].line + 1, loc.range["end"].character }
|
||||
item.file = vim.uri_to_fname(loc.uri)
|
||||
return item
|
||||
end
|
||||
|
||||
---@param buf number
|
||||
---@param method string
|
||||
---@return vim.lsp.Client[]
|
||||
|
@ -212,7 +233,6 @@ function M.results_to_items(client, results, opts)
|
|||
opts = opts or {}
|
||||
local items = {} ---@type snacks.picker.finder.Item[]
|
||||
local locs = {} ---@type lsp.Loc[]
|
||||
local processed = {} ---@type table<lsp.ResultItem, {uri:string, loc:lsp.Loc, range?:lsp.Loc}>
|
||||
|
||||
---@param result lsp.ResultItem
|
||||
local function process(result)
|
||||
|
@ -222,7 +242,6 @@ function M.results_to_items(client, results, opts)
|
|||
if not loc.uri then
|
||||
assert(loc.uri, "missing uri in result:\n" .. vim.inspect(result))
|
||||
end
|
||||
processed[result] = { uri = uri, loc = loc }
|
||||
if not opts.filter or opts.filter(result) then
|
||||
locs[#locs + 1] = loc
|
||||
end
|
||||
|
@ -235,43 +254,35 @@ function M.results_to_items(client, results, opts)
|
|||
process(result)
|
||||
end
|
||||
|
||||
local loc_items = vim.lsp.util.locations_to_items(locs, client.offset_encoding)
|
||||
M.fix_locs(loc_items)
|
||||
local ranges = {} ---@type table<lsp.Loc, vim.quickfix.entry>
|
||||
for _, i in ipairs(loc_items) do
|
||||
local loc = i.user_data ---@type lsp.Loc
|
||||
ranges[loc] = i
|
||||
end
|
||||
|
||||
local last = {} ---@type table<snacks.picker.finder.Item, snacks.picker.finder.Item>
|
||||
---@param result lsp.ResultItem
|
||||
---@param parent snacks.picker.finder.Item
|
||||
local function add(result, parent)
|
||||
local loc = processed[result].loc
|
||||
local sym = ranges[loc]
|
||||
---@type snacks.picker.finder.Item?
|
||||
local item
|
||||
if sym then
|
||||
local text = table.concat({ M.symbol_kind(result.kind), result.name, result.detail or "" }, " ")
|
||||
if opts.text_with_file and sym.filename then
|
||||
text = text .. " " .. sym.filename
|
||||
end
|
||||
item = {
|
||||
---@type snacks.picker.finder.Item
|
||||
local item = {
|
||||
kind = M.symbol_kind(result.kind),
|
||||
parent = parent,
|
||||
depth = (parent.depth or 0) + 1,
|
||||
detail = result.detail,
|
||||
name = result.name,
|
||||
text = text,
|
||||
file = sym.filename,
|
||||
buf = sym.bufnr,
|
||||
pos = { sym.lnum, sym.col - 1 },
|
||||
end_pos = sym.end_lnum and sym.end_col and { sym.end_lnum, sym.end_col - 1 } or nil,
|
||||
text = "",
|
||||
}
|
||||
local uri = result.location and result.location.uri or result.uri or opts.default_uri
|
||||
local loc = result.location or { range = result.selectionRange or result.range, uri = uri }
|
||||
loc.uri = loc.uri or uri
|
||||
M.add_loc(item, loc, client)
|
||||
local text = table.concat({ M.symbol_kind(result.kind), result.name, result.detail or "" }, " ")
|
||||
if opts.text_with_file and item.file then
|
||||
text = text .. " " .. item.file
|
||||
end
|
||||
item.text = text
|
||||
|
||||
if not opts.filter or opts.filter(result) then
|
||||
items[#items + 1] = item
|
||||
last[parent] = item
|
||||
parent = item
|
||||
end
|
||||
|
||||
for _, child in ipairs(result.children or {}) do
|
||||
add(child, parent)
|
||||
end
|
||||
|
|
|
@ -181,6 +181,7 @@ function M.parse(str)
|
|||
return t, args
|
||||
end
|
||||
|
||||
--- Resolves the item if it has a resolve function
|
||||
---@param item snacks.picker.Item
|
||||
function M.resolve(item)
|
||||
if item and item.resolve then
|
||||
|
@ -190,6 +191,31 @@ function M.resolve(item)
|
|||
return item
|
||||
end
|
||||
|
||||
--- Resolves the location of an item to byte positions
|
||||
---@param item snacks.picker.Item
|
||||
---@param buf? number
|
||||
function M.resolve_loc(item, buf)
|
||||
if not item or not item.loc or item.loc.resolved then
|
||||
return item
|
||||
end
|
||||
|
||||
local lines = {} ---@type string[]
|
||||
if buf and vim.api.nvim_buf_is_valid(buf) then
|
||||
lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||
else
|
||||
lines = vim.fn.readfile(item.file)
|
||||
end
|
||||
|
||||
---@param pos lsp.Position?
|
||||
local function resolve(pos)
|
||||
return pos and { pos.line + 1, vim.str_byteindex(lines[pos.line + 1], item.loc.encoding, pos.character) } or nil
|
||||
end
|
||||
item.pos = resolve(item.loc.range["start"])
|
||||
item.end_pos = resolve(item.loc.range["end"]) or item.end_pos
|
||||
item.loc.resolved = true
|
||||
return item
|
||||
end
|
||||
|
||||
--- Returns the relative time from a given time
|
||||
--- as ... ago
|
||||
---@param time number in seconds
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue