feat(float): open float relative to mouse #21531

Problem:
No easy way to position a LSP hover window relative to mouse.

Solution:
Introduce another option to the `relative` key in `nvim_open_win()`.

With this PR it should be possible to override the handler and do something
similar to this https://github.com/neovim/neovim/pull/19481#issuecomment-1193248674
to have hover information displayed from the mouse.

Test case:

    ```lua
    local util = require('vim.lsp.util')

    local function make_position_param(window, offset_encoding)
        window = window or 0
        local buf = vim.api.nvim_win_get_buf(window)
        local row, col

        local mouse = vim.fn.getmousepos()
        row = mouse.line
        col = mouse.column

        offset_encoding = offset_encoding or util._get_offset_encoding(buf)
        row = row - 1
        local line = vim.api.nvim_buf_get_lines(buf, row, row + 1, true)[1]
        if not line then
            return { line = 0, character = 0 }
        end
        if #line < col then
            return { line = 0, character = 0 }
        end

        col = util._str_utfindex_enc(line, col, offset_encoding)

        return { line = row, character = col }
    end

    local make_params = function(window, offset_encoding)
        window = window or 0
        local buf = vim.api.nvim_win_get_buf(window)
        offset_encoding = offset_encoding or util._get_offset_encoding(buf)
        return {
            textDocument = util.make_text_document_params(buf),
            position = make_position_param(window, offset_encoding),
        }
    end

    local hover_timer = nil
    vim.o.mousemoveevent = true

    vim.keymap.set({ '', 'i' }, '<MouseMove>', function()
        if hover_timer then
            hover_timer:close()
        end
        hover_timer = vim.defer_fn(function()
            hover_timer = nil
            local params = make_params()
            vim.lsp.buf_request(
                0,
                'textDocument/hover',
                params,
                vim.lsp.with(vim.lsp.handlers.hover, {
                    silent = true,
                    focusable = false,
                    relative = 'mouse',
                })
            )
        end, 500)
        return '<MouseMove>'
    end, { expr = true })
    ```
This commit is contained in:
Sebastian Lyng Johansen
2023-01-10 11:22:41 +01:00
committed by GitHub
parent d6cb3328f7
commit 870ca1de52
9 changed files with 53 additions and 5 deletions

View File

@@ -2995,6 +2995,7 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()*
• "win" Window given by the `win` field, or current • "win" Window given by the `win` field, or current
window. window.
• "cursor" Cursor position in current window. • "cursor" Cursor position in current window.
• "mouse" Mouse position
• win: |window-ID| for relative="win". • win: |window-ID| for relative="win".
• anchor: Decides which corner of the float to place at • anchor: Decides which corner of the float to place at

View File

@@ -1612,6 +1612,7 @@ make_floating_popup_options({width}, {height}, {opts})
• border (string or table) override `border` • border (string or table) override `border`
• focusable (string or table) override `focusable` • focusable (string or table) override `focusable`
• zindex (string or table) override `zindex`, defaults to 50 • zindex (string or table) override `zindex`, defaults to 50
• relative ("mouse"|"cursor") defaults to "cursor"
Return: ~ Return: ~
(table) Options (table) Options

View File

@@ -50,6 +50,10 @@ NEW FEATURES *news-features*
The following new APIs or features were added. The following new APIs or features were added.
• |nvim_open_win()| now accepts a relative `mouse` option to open a floating win
relative to the mouse. Note that the mouse doesn't update frequently without
setting `vim.o.mousemoveevent = true`
• EditorConfig support is now builtin. This is enabled by default and happens • EditorConfig support is now builtin. This is enabled by default and happens
automatically. To disable it, users should add >lua automatically. To disable it, users should add >lua

View File

@@ -335,7 +335,9 @@ function M.hover(_, result, ctx, config)
return return
end end
if not (result and result.contents) then if not (result and result.contents) then
vim.notify('No information available') if config.silent ~= true then
vim.notify('No information available')
end
return return
end end
local markdown_lines = util.convert_input_to_markdown_lines(result.contents) local markdown_lines = util.convert_input_to_markdown_lines(result.contents)

View File

@@ -1015,6 +1015,7 @@ end
--- - border (string or table) override `border` --- - border (string or table) override `border`
--- - focusable (string or table) override `focusable` --- - focusable (string or table) override `focusable`
--- - zindex (string or table) override `zindex`, defaults to 50 --- - zindex (string or table) override `zindex`, defaults to 50
--- - relative ("mouse"|"cursor") defaults to "cursor"
---@returns (table) Options ---@returns (table) Options
function M.make_floating_popup_options(width, height, opts) function M.make_floating_popup_options(width, height, opts)
validate({ validate({
@@ -1029,7 +1030,8 @@ function M.make_floating_popup_options(width, height, opts)
local anchor = '' local anchor = ''
local row, col local row, col
local lines_above = vim.fn.winline() - 1 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_below = vim.fn.winheight(0) - lines_above
if lines_above < lines_below then if lines_above < lines_below then
@@ -1042,7 +1044,9 @@ function M.make_floating_popup_options(width, height, opts)
row = 0 row = 0
end end
if vim.fn.wincol() + width + (opts.offset_x or 0) <= api.nvim_get_option('columns') then local wincol = opts.relative == 'mouse' and vim.fn.getmousepos().column or vim.fn.wincol()
if wincol + width + (opts.offset_x or 0) <= api.nvim_get_option('columns') then
anchor = anchor .. 'W' anchor = anchor .. 'W'
col = 0 col = 0
else else
@@ -1062,7 +1066,7 @@ function M.make_floating_popup_options(width, height, opts)
col = col + (opts.offset_x or 0), col = col + (opts.offset_x or 0),
height = height, height = height,
focusable = opts.focusable, focusable = opts.focusable,
relative = 'cursor', relative = opts.relative == 'mouse' and 'mouse' or 'cursor',
row = row + (opts.offset_y or 0), row = row + (opts.offset_y or 0),
style = 'minimal', style = 'minimal',
width = width, width = width,

View File

@@ -74,6 +74,7 @@
/// - "editor" The global editor grid /// - "editor" The global editor grid
/// - "win" Window given by the `win` field, or current window. /// - "win" Window given by the `win` field, or current window.
/// - "cursor" Cursor position in current window. /// - "cursor" Cursor position in current window.
/// - "mouse" Mouse position
/// - win: |window-ID| for relative="win". /// - win: |window-ID| for relative="win".
/// - anchor: Decides which corner of the float to place at (row,col): /// - anchor: Decides which corner of the float to place at (row,col):
/// - "NW" northwest (default) /// - "NW" northwest (default)
@@ -349,6 +350,8 @@ static bool parse_float_relative(String relative, FloatRelative *out)
*out = kFloatRelativeWindow; *out = kFloatRelativeWindow;
} else if (striequal(str, "cursor")) { } else if (striequal(str, "cursor")) {
*out = kFloatRelativeCursor; *out = kFloatRelativeCursor;
} else if (striequal(str, "mouse")) {
*out = kFloatRelativeMouse;
} else { } else {
return false; return false;
} }

View File

@@ -1028,10 +1028,11 @@ typedef enum {
kFloatRelativeEditor = 0, kFloatRelativeEditor = 0,
kFloatRelativeWindow = 1, kFloatRelativeWindow = 1,
kFloatRelativeCursor = 2, kFloatRelativeCursor = 2,
kFloatRelativeMouse = 3,
} FloatRelative; } FloatRelative;
EXTERN const char *const float_relative_str[] INIT(= { "editor", "win", EXTERN const char *const float_relative_str[] INIT(= { "editor", "win",
"cursor" }); "cursor", "mouse" });
typedef enum { typedef enum {
kWinStyleUnused = 0, kWinStyleUnused = 0,

View File

@@ -804,6 +804,15 @@ void win_config_float(win_T *wp, FloatConfig fconfig)
fconfig.row += curwin->w_wrow; fconfig.row += curwin->w_wrow;
fconfig.col += curwin->w_wcol; fconfig.col += curwin->w_wcol;
fconfig.window = curwin->handle; fconfig.window = curwin->handle;
} else if (fconfig.relative == kFloatRelativeMouse) {
int row = mouse_row, col = mouse_col, grid = mouse_grid;
win_T *mouse_win = mouse_find_win(&grid, &row, &col);
if (mouse_win != NULL) {
fconfig.relative = kFloatRelativeWindow;
fconfig.row += row;
fconfig.col += col;
fconfig.window = mouse_win->handle;
}
} }
bool change_external = fconfig.external != wp->w_float_config.external; bool change_external = fconfig.external != wp->w_float_config.external;

View File

@@ -168,6 +168,29 @@ describe('float window', function()
eq(7, pos[2]) eq(7, pos[2])
end) end)
it('opened with correct position relative to the mouse', function()
meths.input_mouse('left', 'press', '', 0, 10, 10)
local pos = exec_lua([[
local bufnr = vim.api.nvim_create_buf(false, true)
local opts = {
width = 10,
height = 10,
col = 1,
row = 2,
relative = 'mouse',
style = 'minimal'
}
local win_id = vim.api.nvim_open_win(bufnr, false, opts)
return vim.api.nvim_win_get_position(win_id)
]])
eq(12, pos[1])
eq(11, pos[2])
end)
it('opened with correct position relative to the cursor', function() it('opened with correct position relative to the cursor', function()
local pos = exec_lua([[ local pos = exec_lua([[
local bufnr = vim.api.nvim_create_buf(false, true) local bufnr = vim.api.nvim_create_buf(false, true)