From a61a0bf407a83690ffaa0fd6098cbd4390323f0b Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Fri, 17 Apr 2026 03:15:04 +0800 Subject: [PATCH] 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`. --- runtime/doc/lsp.txt | 35 +++++++++++-------- runtime/lua/vim/lsp/buf.lua | 70 +++++++++++++++++++++---------------- 2 files changed, 60 insertions(+), 45 deletions(-) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 60551cf693..bf7489814e 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -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| diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 4432f4fce6..52fee0db4a 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -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