From 8308544fe588f34f5e1985d1fb37f64b41f91873 Mon Sep 17 00:00:00 2001 From: Daigo Yamashita Date: Sun, 26 Apr 2026 12:41:13 -0700 Subject: [PATCH] fix(lsp): handle relative='editor' in make_floating_popup_options() #39320 Problem: With `vim.g.health = { style = 'float' }`, running `:checkhealth` from a `:help` buffer placed the float in the top-left corner instead of centered. make_floating_popup_options() picks the NW/NE/SW/SE anchor and the available height from cursor-relative metrics (winline(), wincol(), winheight()). When the caller passes relative='editor', those metrics are meaningless, so the function could flip to an 'E' anchor and clamp the float off-screen. Solution: When relative='editor', treat the whole editor area as available space (lines_above=0, lines_below=&lines, wincol=0). This makes the NW anchor the natural choice and keeps the float position stable regardless of where the cursor is in the current window. AI-assisted: Claude Code --- runtime/lua/vim/lsp/util.lua | 25 ++++++++++++++++++++---- test/functional/plugin/lsp/util_spec.lua | 8 ++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 4198cfcc97..9e8f661ac7 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -922,9 +922,19 @@ function M.make_floating_popup_options(width, height, opts) local anchor = '' - local lines_above = opts.relative == 'mouse' and vim.fn.getmousepos().line - 1 - or vim.fn.winline() - 1 - local lines_below = vim.fn.winheight(0) - lines_above + local lines_above --- @type integer + local lines_below --- @type integer + if opts.relative == 'mouse' then + lines_above = vim.fn.getmousepos().line - 1 + lines_below = vim.fn.winheight(0) - lines_above + elseif opts.relative == 'editor' then + -- No cursor to anchor against; treat the whole editor as space below. + lines_above = 0 + lines_below = vim.o.lines + else + lines_above = vim.fn.winline() - 1 + lines_below = vim.fn.winheight(0) - lines_above + end local anchor_bias = opts.anchor_bias or 'auto' @@ -951,7 +961,14 @@ function M.make_floating_popup_options(width, height, opts) row = 0 end - local wincol = opts.relative == 'mouse' and vim.fn.getmousepos().column or vim.fn.wincol() + local wincol --- @type integer + if opts.relative == 'mouse' then + wincol = vim.fn.getmousepos().column + elseif opts.relative == 'editor' then + wincol = 0 + else + wincol = vim.fn.wincol() + end if wincol + width + (opts.offset_x or 0) <= vim.o.columns then anchor = anchor .. 'W' diff --git a/test/functional/plugin/lsp/util_spec.lua b/test/functional/plugin/lsp/util_spec.lua index 8aa78602c5..db7c56495f 100644 --- a/test/functional/plugin/lsp/util_spec.lua +++ b/test/functional/plugin/lsp/util_spec.lua @@ -1393,6 +1393,14 @@ describe('vim.lsp.util', function() eq('Title', opts.title) end) end) + + it('anchor is NW for relative = "editor" regardless of cursor #39306', function() + feed('G') -- without the fix, cursor on the last line picks an 'S*' anchor + local opts = exec_lua(function() + return vim.lsp.util.make_floating_popup_options(30, 10, { relative = 'editor' }) + end) + eq('NW', opts.anchor) + end) end) describe('open_floating_preview', function()