feat(lsp): respect 'switchbuf' for jump commands, drop reuse_win #38510

Problem:
LSP jump operations such as `buf.definition`/`buf.type_definition` do
not follow the 'switchbuf' option. Instead their behavior is controlled
by `vim.lsp.LocationOpts.reuse_win`. When `reuse_win=true`, the effect
is very similar to `set switchbuf=useopen`.

Note that functions like `buf.definition` open the quickfix
window when there are multiple results, and jumping between quickfix
entries already follows 'switchbuf', so unifying the behavior is more
intuitive.

Solution:
Follow the 'switchbuf' option and drop `reuse_win`.

We can achieve this behavior by using :cfirst when the quickfix list has
only one item, rather than customizing the jump logic as before.
This commit is contained in:
Yi Ming
2026-03-30 22:54:55 +08:00
committed by GitHub
parent f062c6fd43
commit 57797ed7d4
4 changed files with 45 additions and 61 deletions

View File

@@ -1329,10 +1329,10 @@ the current buffer.
*vim.lsp.ListOpts*
Fields: ~
• {on_list}? (`fun(t: vim.lsp.LocationOpts.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
• {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()
@@ -1347,15 +1347,7 @@ the current buffer.
vim.lsp.buf.references(nil, { loclist = false })
<
*vim.lsp.LocationOpts*
Extends: |vim.lsp.ListOpts|
Fields: ~
• {reuse_win}? (`boolean`) Jump to existing window if buffer is already
open.
*vim.lsp.LocationOpts.OnList*
*vim.lsp.ListOpts.OnList*
Fields: ~
• {items} (`vim.quickfix.entry[]`) See |setqflist-what|
@@ -1429,13 +1421,13 @@ declaration({opts}) *vim.lsp.buf.declaration()*
|vim.lsp.buf.definition()| instead.
Parameters: ~
• {opts} (`vim.lsp.LocationOpts?`) See |vim.lsp.LocationOpts|.
• {opts} (`vim.lsp.ListOpts?`) See |vim.lsp.ListOpts|.
definition({opts}) *vim.lsp.buf.definition()*
Jumps to the definition of the symbol under the cursor.
Parameters: ~
• {opts} (`vim.lsp.LocationOpts?`) See |vim.lsp.LocationOpts|.
• {opts} (`vim.lsp.ListOpts?`) See |vim.lsp.ListOpts|.
document_highlight() *vim.lsp.buf.document_highlight()*
Send request to the server to resolve document highlights for the current
@@ -1521,7 +1513,7 @@ implementation({opts}) *vim.lsp.buf.implementation()*
quickfix window.
Parameters: ~
• {opts} (`vim.lsp.LocationOpts?`) See |vim.lsp.LocationOpts|.
• {opts} (`vim.lsp.ListOpts?`) See |vim.lsp.ListOpts|.
incoming_calls() *vim.lsp.buf.incoming_calls()*
Lists all the call sites of the symbol under the cursor in the |quickfix|
@@ -1598,7 +1590,7 @@ type_definition({opts}) *vim.lsp.buf.type_definition()*
Jumps to the definition of the type of the symbol under the cursor.
Parameters: ~
• {opts} (`vim.lsp.LocationOpts?`) See |vim.lsp.LocationOpts|.
• {opts} (`vim.lsp.ListOpts?`) See |vim.lsp.ListOpts|.
typehierarchy({kind}) *vim.lsp.buf.typehierarchy()*
Lists all the subtypes or supertypes of the symbol under the cursor in the

View File

@@ -58,7 +58,8 @@ The following new features were added.
API
todo
|vim.lsp.buf.declaration()|, |vim.lsp.buf.definition()|, |vim.lsp.buf.definition()|,
and |vim.lsp.buf.implementation()| now follows 'switchbuf'.
BUILD
@@ -145,7 +146,8 @@ REMOVED FEATURES *news-removed*
These deprecated features were removed.
todo
|vim.lsp.buf.declaration()|, |vim.lsp.buf.definition()|, |vim.lsp.buf.definition()|,
and |vim.lsp.buf.implementation()| no longer accept the `reuse_win` option.
==============================================================================
DEPRECATIONS *news-deprecations*

View File

@@ -184,9 +184,18 @@ local function request_with_opts(name, params, opts)
end
---@param method vim.lsp.protocol.Method.ClientToServer.Request
---@param opts? vim.lsp.LocationOpts
---@param opts? vim.lsp.ListOpts
local function get_locations(method, opts)
opts = opts or {}
---@diagnostic disable-next-line: undefined-field
if opts.reuse_win then
vim.deprecate(
'vim.lsp.buf.<method>({ reuse_win = true })',
"vim.lsp.buf.<method>() and the value of 'switchbuf' of preference",
'0.13'
)
end
local bufnr = api.nvim_get_current_buf()
local win = api.nvim_get_current_win()
@@ -232,39 +241,24 @@ local function get_locations(method, opts)
return
end
if #all_items == 1 then
local item = all_items[1]
local b = item.bufnr or vim.fn.bufadd(item.filename)
-- Save position in jumplist
vim.cmd("normal! m'")
-- Push a new item into tagstack
local tagstack = { { tagname = tagname, from = from } }
vim.fn.settagstack(vim.fn.win_getid(win), { items = tagstack }, 't')
vim.bo[b].buflisted = true
local w = win
if opts.reuse_win and api.nvim_win_get_buf(w) ~= b then
w = vim.fn.bufwinid(b)
w = w >= 0 and w or vim.fn.win_findbuf(b)[1] or win
if w ~= win then
api.nvim_set_current_win(w)
end
end
api.nvim_win_set_buf(w, b)
api.nvim_win_set_cursor(w, { item.lnum, item.col - 1 })
vim._with({ win = w }, function()
-- Open folds under the cursor
vim.cmd('normal! zv')
end)
return
end
if opts.loclist then
vim.fn.setloclist(0, {}, ' ', { title = title, items = all_items })
vim.cmd.lopen()
if #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
else
vim.fn.setqflist({}, ' ', { title = title, items = all_items })
vim.cmd('botright copen')
if #all_items == 1 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
end)
end
@@ -283,7 +277,7 @@ 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.LocationOpts.OnList)
--- @field on_list? fun(t: vim.lsp.ListOpts.OnList)
---
--- Whether to use the |location-list| or the |quickfix| list in the default handler.
--- ```lua
@@ -292,33 +286,28 @@ end
--- ```
--- @field loclist? boolean
--- @class vim.lsp.LocationOpts.OnList
--- @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|.
--- @class vim.lsp.LocationOpts: vim.lsp.ListOpts
---
--- Jump to existing window if buffer is already open.
--- @field reuse_win? boolean
--- 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.LocationOpts
--- @param opts? vim.lsp.ListOpts
function M.declaration(opts)
validate('opts', opts, 'table', true)
get_locations('textDocument/declaration', opts)
end
--- Jumps to the definition of the symbol under the cursor.
--- @param opts? vim.lsp.LocationOpts
--- @param opts? vim.lsp.ListOpts
function M.definition(opts)
validate('opts', opts, 'table', true)
get_locations('textDocument/definition', opts)
end
--- Jumps to the definition of the type of the symbol under the cursor.
--- @param opts? vim.lsp.LocationOpts
--- @param opts? vim.lsp.ListOpts
function M.type_definition(opts)
validate('opts', opts, 'table', true)
get_locations('textDocument/typeDefinition', opts)
@@ -326,7 +315,7 @@ end
--- Lists all the implementations for the symbol under the cursor in the
--- quickfix window.
--- @param opts? vim.lsp.LocationOpts
--- @param opts? vim.lsp.ListOpts
function M.implementation(opts)
validate('opts', opts, 'table', true)
get_locations('textDocument/implementation', opts)

View File

@@ -5326,7 +5326,8 @@ describe('LSP', function()
n.feed(':vnew<CR>')
api.nvim_win_set_buf(0, result.bufnr)
api.nvim_win_set_cursor(0, { 3, 6 })
n.feed(':=vim.lsp.buf.definition({ reuse_win = true })<CR>')
n.feed(':set switchbuf=usetab<CR>')
n.feed(':=vim.lsp.buf.definition()<CR>')
eq(displayed_result_win, api.nvim_get_current_win())
exec_lua(function()
vim.lsp.get_client_by_id(result.client_id):stop()