refactor(lsp): provide a default list handler example #39005

Problem:
Difficult for us to provide default handlers for functions like
`vim.lsp.buf.definition`. When users wanted to fine-tune the default behavior,
they don't know how.

Solution:
- Document an example providing `on_list` boilerplate to make it easier for
  users to modify and override.
- Also, considering that the parameters of the previous
  `on_list`(`vim.lsp.ListOpts.OnList`) are compatible with the parameters of
  `setqflist`, remove that custom type in favor of passing
  `vim.fn.setqflist.what`.
This commit is contained in:
Yi Ming
2026-04-17 03:15:04 +08:00
committed by GitHub
parent bb2284d75e
commit a61a0bf407
2 changed files with 60 additions and 45 deletions

View File

@@ -1403,32 +1403,37 @@ the current buffer.
*vim.lsp.ListOpts*
Fields: ~
• {on_list}? (`fun(t: vim.lsp.ListOpts.OnList)`) list-handler replacing
the default handler. Called for any non-empty result. This
table can be used with |setqflist()| or |setloclist()|.
E.g.: >lua
local function on_list(options)
vim.fn.setqflist({}, ' ', options)
vim.cmd.cfirst()
• {on_list}? (`fun(what: vim.fn.setqflist.what)`) list-handler
replacing the default handler. Called for any non-empty
result. When `loclist == false` (the default), the default
handler is as follows: >lua
local function on_list(what)
vim.fn.setqflist({}, ' ', what)
if
#what.items == 1
and what.context.method ~= 'textDocument/implementation'
and what.context.method ~= 'textDocument/references'
then
local tagstack = { { tagname = tagname, from = from } }
vim.fn.settagstack(vim.fn.win_getid(win), { items = tagstack }, 't')
vim.cmd('cfirst')
else
vim.cmd('botright copen')
end
end
vim.lsp.buf.definition({ on_list = on_list })
vim.lsp.buf.references(nil, { on_list = on_list })
<
See |setqflist-what| for the structure of the `what`
parameter.
• {loclist}? (`boolean`) Whether to use the |location-list| or the
|quickfix| list in the default handler. >lua
vim.lsp.buf.definition({ loclist = true })
vim.lsp.buf.references(nil, { loclist = false })
<
*vim.lsp.ListOpts.OnList*
Fields: ~
• {items} (`vim.quickfix.entry[]`) See |setqflist-what|
• {title}? (`string`) Title for the list.
• {context}? (`{ bufnr: integer, method: string }`) Subset of `ctx`
from |lsp-handler|.
*vim.lsp.buf.hover.Opts*
Extends: |vim.lsp.util.open_floating_preview.Opts|

View File

@@ -264,35 +264,39 @@ local function get_locations(method, context, opts)
end
---@type vim.fn.setqflist.what
local list = {
local what = {
title = name:gsub('^%l', string.upper),
items = all_items,
context = { bufnr = bufnr, method = method },
}
if opts.on_list then
assert(vim.is_callable(opts.on_list), 'on_list is not a function')
---@cast list vim.lsp.ListOpts.OnList
opts.on_list(list)
return
end
if opts.loclist then
vim.fn.setloclist(0, {}, ' ', list)
if method ~= 'textDocument/references' and #all_items == 1 then
local tagstack = { { tagname = tagname, from = from } }
vim.fn.settagstack(vim.fn.win_getid(win), { items = tagstack }, 't')
vim.cmd('lfirst')
else
vim.cmd.lopen()
end
validate('opts.on_list', opts.on_list, 'function')
opts.on_list(what)
else
vim.fn.setqflist({}, ' ', list)
if method ~= 'textDocument/references' and #all_items == 1 then
if opts.loclist then
vim.fn.setloclist(0, {}, ' ', what)
else
vim.fn.setqflist({}, ' ', what)
end
if
#all_items == 1
and method ~= 'textDocument/implementation'
and method ~= 'textDocument/references'
then
local tagstack = { { tagname = tagname, from = from } }
vim.fn.settagstack(vim.fn.win_getid(win), { items = tagstack }, 't')
vim.cmd('cfirst')
if opts.loclist then
vim.cmd('lfirst')
else
vim.cmd('cfirst')
end
else
vim.cmd('botright copen')
if opts.loclist then
vim.cmd('botright lopen')
else
vim.cmd('botright copen')
end
end
end
end)
@@ -302,17 +306,28 @@ end
---
--- list-handler replacing the default handler.
--- Called for any non-empty result.
--- This table can be used with |setqflist()| or |setloclist()|. E.g.:
--- When `loclist == false` (the default), the default handler is as follows:
--- ```lua
--- local function on_list(options)
--- vim.fn.setqflist({}, ' ', options)
--- vim.cmd.cfirst()
--- local function on_list(what)
--- vim.fn.setqflist({}, ' ', what)
--- if
--- #what.items == 1
--- and what.context.method ~= 'textDocument/implementation'
--- and what.context.method ~= 'textDocument/references'
--- then
--- local tagstack = { { tagname = tagname, from = from } }
--- vim.fn.settagstack(vim.fn.win_getid(win), { items = tagstack }, 't')
--- vim.cmd('cfirst')
--- else
--- vim.cmd('botright copen')
--- end
--- end
---
--- vim.lsp.buf.definition({ on_list = on_list })
--- vim.lsp.buf.references(nil, { on_list = on_list })
--- ```
--- @field on_list? fun(t: vim.lsp.ListOpts.OnList)
--- See |setqflist-what| for the structure of the `what` parameter.
--- @field on_list? fun(what: vim.fn.setqflist.what)
---
--- Whether to use the |location-list| or the |quickfix| list in the default handler.
--- ```lua
@@ -321,11 +336,6 @@ end
--- ```
--- @field loclist? boolean
--- @class vim.lsp.ListOpts.OnList
--- @field items vim.quickfix.entry[] See |setqflist-what|
--- @field title? string Title for the list.
--- @field context? { bufnr: integer, method: string } Subset of `ctx` from |lsp-handler|.
--- Jumps to the declaration of the symbol under the cursor.
--- @note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
--- @param opts? vim.lsp.ListOpts