mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-03 17:24:29 +00:00 
			
		
		
		
	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:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							d6cb3328f7
						
					
				
				
					commit
					870ca1de52
				
			@@ -2995,6 +2995,7 @@ nvim_open_win({buffer}, {enter}, {*config})                  *nvim_open_win()*
 | 
			
		||||
                    • "win" Window given by the `win` field, or current
 | 
			
		||||
                      window.
 | 
			
		||||
                    • "cursor" Cursor position in current window.
 | 
			
		||||
                    • "mouse" Mouse position
 | 
			
		||||
 | 
			
		||||
                  • win: |window-ID| for relative="win".
 | 
			
		||||
                  • anchor: Decides which corner of the float to place at
 | 
			
		||||
 
 | 
			
		||||
@@ -1612,6 +1612,7 @@ make_floating_popup_options({width}, {height}, {opts})
 | 
			
		||||
                  • border (string or table) override `border`
 | 
			
		||||
                  • focusable (string or table) override `focusable`
 | 
			
		||||
                  • zindex (string or table) override `zindex`, defaults to 50
 | 
			
		||||
                  • relative ("mouse"|"cursor") defaults to "cursor"
 | 
			
		||||
 | 
			
		||||
    Return: ~
 | 
			
		||||
        (table) Options
 | 
			
		||||
 
 | 
			
		||||
@@ -50,6 +50,10 @@ NEW FEATURES                                                    *news-features*
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
  automatically. To disable it, users should add >lua
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -335,7 +335,9 @@ function M.hover(_, result, ctx, config)
 | 
			
		||||
    return
 | 
			
		||||
  end
 | 
			
		||||
  if not (result and result.contents) then
 | 
			
		||||
    if config.silent ~= true then
 | 
			
		||||
      vim.notify('No information available')
 | 
			
		||||
    end
 | 
			
		||||
    return
 | 
			
		||||
  end
 | 
			
		||||
  local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
 | 
			
		||||
 
 | 
			
		||||
@@ -1015,6 +1015,7 @@ end
 | 
			
		||||
---        - border (string or table) override `border`
 | 
			
		||||
---        - focusable (string or table) override `focusable`
 | 
			
		||||
---        - zindex (string or table) override `zindex`, defaults to 50
 | 
			
		||||
---        - relative ("mouse"|"cursor") defaults to "cursor"
 | 
			
		||||
---@returns (table) Options
 | 
			
		||||
function M.make_floating_popup_options(width, height, opts)
 | 
			
		||||
  validate({
 | 
			
		||||
@@ -1029,7 +1030,8 @@ function M.make_floating_popup_options(width, height, opts)
 | 
			
		||||
  local anchor = ''
 | 
			
		||||
  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
 | 
			
		||||
 | 
			
		||||
  if lines_above < lines_below then
 | 
			
		||||
@@ -1042,7 +1044,9 @@ function M.make_floating_popup_options(width, height, opts)
 | 
			
		||||
    row = 0
 | 
			
		||||
  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'
 | 
			
		||||
    col = 0
 | 
			
		||||
  else
 | 
			
		||||
@@ -1062,7 +1066,7 @@ function M.make_floating_popup_options(width, height, opts)
 | 
			
		||||
    col = col + (opts.offset_x or 0),
 | 
			
		||||
    height = height,
 | 
			
		||||
    focusable = opts.focusable,
 | 
			
		||||
    relative = 'cursor',
 | 
			
		||||
    relative = opts.relative == 'mouse' and 'mouse' or 'cursor',
 | 
			
		||||
    row = row + (opts.offset_y or 0),
 | 
			
		||||
    style = 'minimal',
 | 
			
		||||
    width = width,
 | 
			
		||||
 
 | 
			
		||||
@@ -74,6 +74,7 @@
 | 
			
		||||
///      - "editor" The global editor grid
 | 
			
		||||
///      - "win"    Window given by the `win` field, or current window.
 | 
			
		||||
///      - "cursor" Cursor position in current window.
 | 
			
		||||
///      - "mouse"  Mouse position
 | 
			
		||||
///   - win: |window-ID| for relative="win".
 | 
			
		||||
///   - anchor: Decides which corner of the float to place at (row,col):
 | 
			
		||||
///      - "NW" northwest (default)
 | 
			
		||||
@@ -349,6 +350,8 @@ static bool parse_float_relative(String relative, FloatRelative *out)
 | 
			
		||||
    *out = kFloatRelativeWindow;
 | 
			
		||||
  } else if (striequal(str, "cursor")) {
 | 
			
		||||
    *out = kFloatRelativeCursor;
 | 
			
		||||
  } else if (striequal(str, "mouse")) {
 | 
			
		||||
    *out = kFloatRelativeMouse;
 | 
			
		||||
  } else {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -1028,10 +1028,11 @@ typedef enum {
 | 
			
		||||
  kFloatRelativeEditor = 0,
 | 
			
		||||
  kFloatRelativeWindow = 1,
 | 
			
		||||
  kFloatRelativeCursor = 2,
 | 
			
		||||
  kFloatRelativeMouse = 3,
 | 
			
		||||
} FloatRelative;
 | 
			
		||||
 | 
			
		||||
EXTERN const char *const float_relative_str[] INIT(= { "editor", "win",
 | 
			
		||||
                                                       "cursor" });
 | 
			
		||||
                                                       "cursor", "mouse" });
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
  kWinStyleUnused = 0,
 | 
			
		||||
 
 | 
			
		||||
@@ -804,6 +804,15 @@ void win_config_float(win_T *wp, FloatConfig fconfig)
 | 
			
		||||
    fconfig.row += curwin->w_wrow;
 | 
			
		||||
    fconfig.col += curwin->w_wcol;
 | 
			
		||||
    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;
 | 
			
		||||
 
 | 
			
		||||
@@ -168,6 +168,29 @@ describe('float window', function()
 | 
			
		||||
    eq(7, pos[2])
 | 
			
		||||
  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()
 | 
			
		||||
    local pos = exec_lua([[
 | 
			
		||||
      local bufnr = vim.api.nvim_create_buf(false, true)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user