mirror of
https://github.com/folke/snacks.nvim
synced 2025-08-06 03:38:16 +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.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.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.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
|
--- Generic filter used by finders to pre-filter items
|
||||||
---@class snacks.picker.filter.Config
|
---@class snacks.picker.filter.Config
|
||||||
|
@ -29,9 +30,11 @@ local M = {}
|
||||||
---@field score_add? number
|
---@field score_add? number
|
||||||
---@field score_mul? number
|
---@field score_mul? number
|
||||||
---@field match_tick? number
|
---@field match_tick? number
|
||||||
|
---@field file? string
|
||||||
---@field text string
|
---@field text string
|
||||||
---@field pos? {[1]:number, [2]:number}
|
---@field pos? snacks.picker.Pos
|
||||||
---@field end_pos? {[1]:number, [2]:number}
|
---@field loc? snacks.picker.lsp.Loc
|
||||||
|
---@field end_pos? snacks.picker.Pos
|
||||||
---@field highlights? snacks.picker.Highlight[][]
|
---@field highlights? snacks.picker.Highlight[][]
|
||||||
---@field preview? snacks.picker.Item.preview
|
---@field preview? snacks.picker.Item.preview
|
||||||
---@field resolve? fun(item:snacks.picker.Item)
|
---@field resolve? fun(item:snacks.picker.Item)
|
||||||
|
|
|
@ -334,6 +334,16 @@ function M:show()
|
||||||
end
|
end
|
||||||
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.
|
--- Returns an iterator over the filtered items in the picker.
|
||||||
--- Items will be in sorted order.
|
--- Items will be in sorted order.
|
||||||
---@return fun():snacks.picker.Item?
|
---@return fun():snacks.picker.Item?
|
||||||
|
@ -343,7 +353,7 @@ function M:iter()
|
||||||
return function()
|
return function()
|
||||||
i = i + 1
|
i = i + 1
|
||||||
if i <= n then
|
if i <= n then
|
||||||
return self.list:get(i)
|
return self:resolve(self.list:get(i))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -358,20 +368,29 @@ function M:items()
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get the current item at the cursor
|
--- Get the current item at the cursor
|
||||||
function M:current()
|
---@param opts? {resolve?: boolean} default is `true`
|
||||||
return self.list:current()
|
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
|
end
|
||||||
|
|
||||||
--- Get the selected items.
|
--- Get the selected items.
|
||||||
--- If `fallback=true` and there is no selection, return the current item.
|
--- If `fallback=true` and there is no selection, return the current item.
|
||||||
---@param opts? {fallback?: boolean} default is `false`
|
---@param opts? {fallback?: boolean} default is `false`
|
||||||
|
---@return snacks.picker.Item[]
|
||||||
function M:selected(opts)
|
function M:selected(opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local ret = vim.deepcopy(self.list.selected)
|
local ret = vim.deepcopy(self.list.selected)
|
||||||
if #ret == 0 and opts.fallback then
|
if #ret == 0 and opts.fallback then
|
||||||
return { self:current() }
|
ret = { self:current() }
|
||||||
end
|
end
|
||||||
return ret
|
return vim.tbl_map(function(item)
|
||||||
|
return self:resolve(item)
|
||||||
|
end, ret)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Total number of items in the picker
|
--- Total number of items in the picker
|
||||||
|
|
|
@ -93,7 +93,7 @@ end
|
||||||
|
|
||||||
---@param picker snacks.Picker
|
---@param picker snacks.Picker
|
||||||
function M:show(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
|
if self.item == item then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -210,7 +210,10 @@ function M:loc()
|
||||||
if not self.item then
|
if not self.item then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local line_count = vim.api.nvim_buf_line_count(self.win.buf)
|
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
|
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_set_cursor(self.win.win, { self.item.pos[1], 0 })
|
||||||
vim.api.nvim_win_call(self.win.win, function()
|
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.Symbol lsp.SymbolInformation|lsp.DocumentSymbol
|
||||||
---@alias lsp.Loc lsp.Location|lsp.LocationLink
|
---@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>
|
local kinds = nil ---@type table<lsp.SymbolKind, string>
|
||||||
|
|
||||||
--- Gets the original symbol kind name from its number.
|
--- Gets the original symbol kind name from its number.
|
||||||
|
@ -66,6 +70,23 @@ local function wrap(client)
|
||||||
})
|
})
|
||||||
end
|
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 buf number
|
||||||
---@param method string
|
---@param method string
|
||||||
---@return vim.lsp.Client[]
|
---@return vim.lsp.Client[]
|
||||||
|
@ -212,7 +233,6 @@ function M.results_to_items(client, results, opts)
|
||||||
opts = opts or {}
|
opts = opts or {}
|
||||||
local items = {} ---@type snacks.picker.finder.Item[]
|
local items = {} ---@type snacks.picker.finder.Item[]
|
||||||
local locs = {} ---@type lsp.Loc[]
|
local locs = {} ---@type lsp.Loc[]
|
||||||
local processed = {} ---@type table<lsp.ResultItem, {uri:string, loc:lsp.Loc, range?:lsp.Loc}>
|
|
||||||
|
|
||||||
---@param result lsp.ResultItem
|
---@param result lsp.ResultItem
|
||||||
local function process(result)
|
local function process(result)
|
||||||
|
@ -222,7 +242,6 @@ function M.results_to_items(client, results, opts)
|
||||||
if not loc.uri then
|
if not loc.uri then
|
||||||
assert(loc.uri, "missing uri in result:\n" .. vim.inspect(result))
|
assert(loc.uri, "missing uri in result:\n" .. vim.inspect(result))
|
||||||
end
|
end
|
||||||
processed[result] = { uri = uri, loc = loc }
|
|
||||||
if not opts.filter or opts.filter(result) then
|
if not opts.filter or opts.filter(result) then
|
||||||
locs[#locs + 1] = loc
|
locs[#locs + 1] = loc
|
||||||
end
|
end
|
||||||
|
@ -235,43 +254,35 @@ function M.results_to_items(client, results, opts)
|
||||||
process(result)
|
process(result)
|
||||||
end
|
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>
|
local last = {} ---@type table<snacks.picker.finder.Item, snacks.picker.finder.Item>
|
||||||
---@param result lsp.ResultItem
|
---@param result lsp.ResultItem
|
||||||
---@param parent snacks.picker.finder.Item
|
---@param parent snacks.picker.finder.Item
|
||||||
local function add(result, parent)
|
local function add(result, parent)
|
||||||
local loc = processed[result].loc
|
---@type snacks.picker.finder.Item
|
||||||
local sym = ranges[loc]
|
local item = {
|
||||||
---@type snacks.picker.finder.Item?
|
kind = M.symbol_kind(result.kind),
|
||||||
local item
|
parent = parent,
|
||||||
if sym then
|
depth = (parent.depth or 0) + 1,
|
||||||
local text = table.concat({ M.symbol_kind(result.kind), result.name, result.detail or "" }, " ")
|
detail = result.detail,
|
||||||
if opts.text_with_file and sym.filename then
|
name = result.name,
|
||||||
text = text .. " " .. sym.filename
|
text = "",
|
||||||
end
|
}
|
||||||
item = {
|
local uri = result.location and result.location.uri or result.uri or opts.default_uri
|
||||||
kind = M.symbol_kind(result.kind),
|
local loc = result.location or { range = result.selectionRange or result.range, uri = uri }
|
||||||
parent = parent,
|
loc.uri = loc.uri or uri
|
||||||
depth = (parent.depth or 0) + 1,
|
M.add_loc(item, loc, client)
|
||||||
detail = result.detail,
|
local text = table.concat({ M.symbol_kind(result.kind), result.name, result.detail or "" }, " ")
|
||||||
name = result.name,
|
if opts.text_with_file and item.file then
|
||||||
text = text,
|
text = text .. " " .. item.file
|
||||||
file = sym.filename,
|
end
|
||||||
buf = sym.bufnr,
|
item.text = text
|
||||||
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,
|
if not opts.filter or opts.filter(result) then
|
||||||
}
|
|
||||||
items[#items + 1] = item
|
items[#items + 1] = item
|
||||||
last[parent] = item
|
last[parent] = item
|
||||||
parent = item
|
parent = item
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, child in ipairs(result.children or {}) do
|
for _, child in ipairs(result.children or {}) do
|
||||||
add(child, parent)
|
add(child, parent)
|
||||||
end
|
end
|
||||||
|
|
|
@ -181,6 +181,7 @@ function M.parse(str)
|
||||||
return t, args
|
return t, args
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Resolves the item if it has a resolve function
|
||||||
---@param item snacks.picker.Item
|
---@param item snacks.picker.Item
|
||||||
function M.resolve(item)
|
function M.resolve(item)
|
||||||
if item and item.resolve then
|
if item and item.resolve then
|
||||||
|
@ -190,6 +191,31 @@ function M.resolve(item)
|
||||||
return item
|
return item
|
||||||
end
|
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
|
--- Returns the relative time from a given time
|
||||||
--- as ... ago
|
--- as ... ago
|
||||||
---@param time number in seconds
|
---@param time number in seconds
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue