diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index c77f38a693..b44b86db7b 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3198,10 +3198,15 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()* entered by |nvim_set_current_win()|, or, when the `mouse` field is set to true, by mouse events. • mouse: Specify how this window interacts with mouse - events. Defaults to `focusable` value. + events. May be a boolean or a Lua callback. Defaults to + `focusable` value. • If false, mouse events pass through this window. • If true, mouse events interact with this window normally. + • If a Lua callback, mouse events interact with this + window, but the callback is called in place of the + default handling. The default handling will be used + again if callback returns `true`. • external: GUI should display the window as an external top-level window. Currently accepts no other positioning configuration together with this. diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 8236cc7cf0..797d7ce66d 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1770,9 +1770,12 @@ function vim.api.nvim_open_term(buffer, opts) end --- `nvim_set_current_win()`, or, when the `mouse` field is set to true, --- by mouse events. --- - mouse: Specify how this window interacts with mouse events. ---- Defaults to `focusable` value. +--- May be a boolean or a Lua callback. Defaults to `focusable` value. --- - If false, mouse events pass through this window. --- - If true, mouse events interact with this window normally. +--- - If a Lua callback, mouse events interact with this window, +--- but the callback is called in place of the default handling. +--- The default handling will be used again if callback returns `true`. --- - external: GUI should display the window as an external --- top-level window. Currently accepts no other positioning --- configuration together with this. diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index bf184dee2d..508d230265 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -295,7 +295,7 @@ error('Cannot require a meta file') --- @field bufpos? any[] --- @field external? boolean --- @field focusable? boolean ---- @field mouse? boolean +--- @field mouse? any --- @field vertical? boolean --- @field zindex? integer --- @field border? any diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 96aabb851f..4aa3a9a487 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -119,7 +119,7 @@ typedef struct { Array bufpos; Boolean external; Boolean focusable; - Boolean mouse; + Object mouse; Boolean vertical; Integer zindex; Object border; diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 16811e0cd9..2a064063dc 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -1,3 +1,4 @@ +#include #include #include @@ -23,6 +24,7 @@ #include "nvim/globals.h" #include "nvim/grid_defs.h" #include "nvim/highlight_group.h" +#include "nvim/lua/executor.h" #include "nvim/macros_defs.h" #include "nvim/mbyte.h" #include "nvim/memory.h" @@ -132,9 +134,12 @@ /// |nvim_set_current_win()|, or, when the `mouse` field is set to true, /// by mouse events. /// - mouse: Specify how this window interacts with mouse events. -/// Defaults to `focusable` value. +/// May be a boolean or a Lua callback. Defaults to `focusable` value. /// - If false, mouse events pass through this window. /// - If true, mouse events interact with this window normally. +/// - If a Lua callback, mouse events interact with this window, +/// but the callback is called in place of the default handling. +/// The default handling will be used again if callback returns `true`. /// - external: GUI should display the window as an external /// top-level window. Currently accepts no other positioning /// configuration together with this. @@ -719,7 +724,18 @@ Dict(win_config) nvim_win_get_config(Window window, Arena *arena, Error *err) PUT_KEY_X(rv, focusable, config->focusable); PUT_KEY_X(rv, external, config->external); PUT_KEY_X(rv, hide, config->hide); - PUT_KEY_X(rv, mouse, config->mouse); + + switch (config->mouse) { + case kWinMouseIgnore: + PUT_KEY_X(rv, mouse, BOOLEAN_OBJ(false)); + break; + case kWinMouseDefault: + PUT_KEY_X(rv, mouse, BOOLEAN_OBJ(true)); + break; + case kWinMouseCallback: + PUT_KEY_X(rv, mouse, LUAREF_OBJ(api_new_luaref(config->mouse_cb))); + break; + } if (wp->w_floating) { PUT_KEY_X(rv, width, config->width); @@ -1208,11 +1224,22 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco if (HAS_KEY_X(config, focusable)) { fconfig->focusable = config->focusable; - fconfig->mouse = config->focusable; + if (fconfig->mouse != kWinMouseCallback) { + fconfig->mouse = config->focusable ? kWinMouseDefault : kWinMouseIgnore; + } } if (HAS_KEY_X(config, mouse)) { - fconfig->mouse = config->mouse; + if (config->mouse.type == kObjectTypeLuaRef) { + fconfig->mouse = kWinMouseCallback; + fconfig->mouse_cb = config->mouse.data.luaref; + config->mouse.data.luaref = LUA_NOREF; + } else if (config->mouse.type == kObjectTypeBoolean) { + fconfig->mouse = config->mouse.data.boolean ? kWinMouseDefault : kWinMouseIgnore; + fconfig->mouse_cb = LUA_NOREF; + } else { + api_set_error(err, kErrorTypeValidation, "invalid type for 'mouse'"); + } } if (HAS_KEY_X(config, zindex)) { diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 1fe5512708..b428551e52 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -904,6 +905,12 @@ typedef enum { kFloatRelativeMouse = 3, } FloatRelative; +typedef enum { + kWinMouseIgnore = 0, + kWinMouseDefault = 1, + kWinMouseCallback = 2, +} WinMouseEvent; + /// Keep in sync with win_split_str[] in nvim_win_get_config() (api/win_config.c) typedef enum { kWinSplitLeft = 0, @@ -938,7 +945,8 @@ typedef struct { FloatRelative relative; bool external; bool focusable; - bool mouse; + WinMouseEvent mouse; + LuaRef mouse_cb; WinSplit split; int zindex; WinStyle style; @@ -965,7 +973,8 @@ typedef struct { .row = 0, .col = 0, .anchor = 0, \ .relative = 0, .external = false, \ .focusable = true, \ - .mouse = true, \ + .mouse = kWinMouseDefault, \ + .mouse_cb = LUA_NOREF, \ .split = 0, \ .zindex = kZIndexFloatDefault, \ .style = kWinStyleUnused, \ diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 1289adfabb..b1e9a5ead9 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -4,6 +4,8 @@ #include #include +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" #include "nvim/ascii_defs.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" @@ -13,6 +15,7 @@ #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/window.h" #include "nvim/ex_docmd.h" #include "nvim/fold.h" #include "nvim/getchar.h" @@ -20,6 +23,7 @@ #include "nvim/grid.h" #include "nvim/grid_defs.h" #include "nvim/keycodes.h" +#include "nvim/lua/executor.h" #include "nvim/macros_defs.h" #include "nvim/mark_defs.h" #include "nvim/mbyte.h" @@ -222,6 +226,45 @@ static void call_click_def_func(StlClickDefinition *click_defs, int col, int whi got_click = false; } +/// When mouse events happen in a window with a mouse callback, invoke it. +/// +/// @param button !is_scroll: return value of get_mouse_button() +/// is_scroll: a MSCR_* enum +/// +/// @return whether to proceeded with mouse handling. +static bool handle_mouse_cb(bool is_click, bool is_drag, bool is_scroll, int button) +{ + static handle_T last_click_win = 0; + int grid = mouse_grid; + int row = mouse_row; + int col = mouse_col; + + win_T *wp = NULL; + if (!is_click && !is_scroll && button != MOUSE_RELEASE) { // drag or release + if (last_click_win > 0) { + wp = win_id2wp(last_click_win); + } + } else { // click, scroll or move + wp = mouse_find_win(&grid, &row, &col); + if (is_click) { + last_click_win = wp != NULL ? wp->handle : 0; + } + } + if (wp == NULL || wp->w_config.mouse != kWinMouseCallback) { + return true; + } + + Error err = ERROR_INIT; + Object res = nlua_call_ref(wp->w_config.mouse_cb, NULL, (Array)ARRAY_DICT_INIT, + kRetNilBool, NULL, &err); + bool retval = LUARET_TRUTHY(res); + if (ERROR_SET(&err)) { + semsg_multiline("E5108: %s", err.msg); + api_clear_error(&err); + } + return retval; +} + /// Translate window coordinates to buffer position without any side effects. /// Returns IN_BUFFER and sets "mpos->col" to the column when in buffer text. /// The column is one for the first column. @@ -371,6 +414,10 @@ bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) break; } + if (!handle_mouse_cb(is_click, is_drag, false, which_button)) { + return false; + } + if (c == K_MOUSEMOVE) { // Mouse moved without a button pressed. return false; @@ -1063,6 +1110,10 @@ void do_mousescroll(cmdarg_T *cap) /// of the MSCR_ values. void ins_mousescroll(int dir) { + if (!handle_mouse_cb(false, false, true, dir)) { + return; + } + cmdarg_T cap; oparg_T oa; CLEAR_FIELD(cap); @@ -1572,6 +1623,10 @@ static bool do_mousescroll_horiz(colnr_T leftcol) /// "cap->arg", which is one of the MSCR_ values. void nv_mousescroll(cmdarg_T *cap) { + if (!handle_mouse_cb(false, false, true, cap->arg)) { + return; + } + win_T *const old_curwin = curwin; if (mouse_row >= 0 && mouse_col >= 0) { @@ -1740,7 +1795,7 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) } else if (*gridp > 1) { win_T *wp = get_win_by_grid_handle(*gridp); if (wp && wp->w_grid_alloc.chars - && !(wp->w_floating && !wp->w_config.mouse)) { + && !(wp->w_floating && wp->w_config.mouse == kWinMouseIgnore)) { *rowp = MIN(*rowp - wp->w_grid.row_offset, wp->w_grid.rows - 1); *colp = MIN(*colp - wp->w_grid.col_offset, wp->w_grid.cols - 1); return wp; diff --git a/src/nvim/window.c b/src/nvim/window.c index 91a69c3ec4..18a835159d 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -45,6 +45,7 @@ #include "nvim/grid_defs.h" #include "nvim/hashtab.h" #include "nvim/keycodes.h" +#include "nvim/lua/executor.h" #include "nvim/macros_defs.h" #include "nvim/main.h" #include "nvim/map_defs.h" @@ -802,6 +803,9 @@ int win_fdccol_count(win_T *wp) void merge_win_config(WinConfig *dst, const WinConfig src) FUNC_ATTR_NONNULL_ALL { + if (dst->mouse_cb != src.mouse_cb) { + api_free_luaref(dst->mouse_cb); + } if (dst->title_chunks.items != src.title_chunks.items) { clear_virttext(&dst->title_chunks); } @@ -857,7 +861,7 @@ void ui_ext_win_position(win_T *wp, bool validate) String anchor = cstr_as_string(float_anchor_str[c.anchor]); if (!c.hide) { ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor, - grid->handle, row, col, c.mouse, + grid->handle, row, col, c.mouse != kWinMouseIgnore, wp->w_grid_alloc.zindex); } else { ui_call_win_hide(wp->w_grid_alloc.handle); @@ -889,7 +893,7 @@ void ui_ext_win_position(win_T *wp, bool validate) ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col, wp->w_height_outer, wp->w_width_outer, valid, false); ui_check_cursor_grid(wp->w_grid_alloc.handle); - wp->w_grid_alloc.mouse_enabled = wp->w_config.mouse; + wp->w_grid_alloc.mouse_enabled = wp->w_config.mouse != kWinMouseIgnore; if (!valid) { wp->w_grid_alloc.valid = false; redraw_later(wp, UPD_NOT_VALID); @@ -4044,7 +4048,7 @@ void win_alloc_aucmd_win(int idx) fconfig.width = Columns; fconfig.height = 5; fconfig.focusable = false; - fconfig.mouse = false; + fconfig.mouse = kWinMouseIgnore; aucmd_win[idx].auc_win = win_new_float(NULL, true, fconfig, &err); aucmd_win[idx].auc_win->w_buffer->b_nwindows--; RESET_BINDING(aucmd_win[idx].auc_win); @@ -5263,9 +5267,8 @@ void win_free(win_T *wp, tabpage_T *tp) } } - // free the border text - clear_virttext(&wp->w_config.title_chunks); - clear_virttext(&wp->w_config.footer_chunks); + // free allocated resources in w_config + merge_win_config(&wp->w_config, WIN_CONFIG_INIT); clear_matches(wp); diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c index 054ef07fc5..c12399954f 100644 --- a/src/nvim/winfloat.c +++ b/src/nvim/winfloat.c @@ -389,7 +389,7 @@ win_T *win_float_create(bool enter, bool new_buf) config.row = curwin->w_wrow; config.relative = kFloatRelativeEditor; config.focusable = false; - config.mouse = false; + config.mouse = kWinMouseIgnore; config.anchor = 0; // NW config.noautocmd = true; config.hide = true;