diff options
author | zeertzjq <zeertzjq@outlook.com> | 2022-06-30 14:21:43 +0800 |
---|---|---|
committer | zeertzjq <zeertzjq@outlook.com> | 2022-07-01 10:13:06 +0800 |
commit | cf8df141f3c7e706c86aadba404ae8ad54d5c795 (patch) | |
tree | 43af0c86cfb67bb9ac6491f579b13b5ab51c884c /src | |
parent | 5a490d838ed3288abcf45e16e6ab79c326a67c17 (diff) | |
download | rneovim-cf8df141f3c7e706c86aadba404ae8ad54d5c795.tar.gz rneovim-cf8df141f3c7e706c86aadba404ae8ad54d5c795.tar.bz2 rneovim-cf8df141f3c7e706c86aadba404ae8ad54d5c795.zip |
vim-patch:8.0.1558: no right-click menu in a terminal
Problem: No right-click menu in a terminal.
Solution: Implement the right click menu for the terminal.
https://github.com/vim/vim/commit/aef8c3da2ba59285b7cfde559ae21cdce6ba6919
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/menu.c | 59 | ||||
-rw-r--r-- | src/nvim/mouse.c | 43 | ||||
-rw-r--r-- | src/nvim/normal.c | 53 | ||||
-rw-r--r-- | src/nvim/popupmnu.c | 151 | ||||
-rw-r--r-- | src/nvim/screen.c | 7 |
5 files changed, 305 insertions, 8 deletions
diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 80f8406ab0..5e4dc12e40 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -11,6 +11,7 @@ #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/eval.h" @@ -22,6 +23,7 @@ #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" +#include "nvim/popupmnu.h" #include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -1355,9 +1357,64 @@ static int menu_is_hidden(char *name) || (menu_is_popup(name) && name[5] != NUL); } +static int get_menu_mode(void) +{ + if (VIsual_active) { + if (VIsual_select) + return MENU_INDEX_SELECT; + return MENU_INDEX_VISUAL; + } + if (State & MODE_INSERT) { + return MENU_INDEX_INSERT; + } + if ((State & MODE_CMDLINE) || State == MODE_ASKMORE || State == MODE_HITRETURN) { + return MENU_INDEX_CMDLINE; + } + if (finish_op) { + return MENU_INDEX_OP_PENDING; + } + if (State & MODE_NORMAL) { + return MENU_INDEX_NORMAL; + } + if (State & MODE_LANGMAP) { // must be a "r" command, like Insert mode + return MENU_INDEX_INSERT; + } + return MENU_INDEX_INVALID; +} + +/// Display the Special "PopUp" menu as a pop-up at the current mouse +/// position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode, +/// etc. +void show_popupmenu(void) +{ + int mode = get_menu_mode(); + if (mode == MENU_INDEX_INVALID) { + return; + } + mode = menu_mode_chars[mode]; + + char ename[2]; + ename[0] = (char)mode; + ename[1] = NUL; + apply_autocmds(EVENT_MENUPOPUP, ename, NULL, false, curbuf); + + vimmenu_T *menu; + + for (menu = root_menu; menu != NULL; menu = menu->next) { + if (STRNCMP("PopUp", menu->name, 5) == 0 && menu->name[5] == mode) { + break; + } + } + + // Only show a popup when it is defined and has entries + if (menu != NULL && menu->children != NULL) { + pum_show_popupmenu(menu); + } +} + // Execute "menu". Use by ":emenu" and the window toolbar. // "eap" is NULL for the window toolbar. -static void execute_menu(const exarg_T *eap, vimmenu_T *menu) +void execute_menu(const exarg_T *eap, vimmenu_T *menu) FUNC_ATTR_NONNULL_ARG(2) { int idx = -1; diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index fc5ecbc6a0..3aee20dc7b 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -30,6 +30,49 @@ static linenr_T orig_topline = 0; static int orig_topfill = 0; +/// Translate window coordinates to buffer position without any side effects +int get_fpos_of_mouse(pos_T *mpos) +{ + int grid = mouse_grid; + int row = mouse_row; + int col = mouse_col; + + if (row < 0 || col < 0) { // check if it makes sense + return IN_UNKNOWN; + } + + // find the window where the row is in + win_T *wp = mouse_find_win(&grid, &row, &col); + if (wp == NULL) { + return IN_UNKNOWN; + } + + // winpos and height may change in win_enter()! + if (row + wp->w_winbar_height >= wp->w_height) { // In (or below) status line + return IN_STATUS_LINE; + } + if (col >= wp->w_width) { // In vertical separator line + return IN_SEP_LINE; + } + + if (wp != curwin) { + return IN_UNKNOWN; + } + + // compute the position in the buffer line from the posn on the screen + if (mouse_comp_pos(curwin, &row, &col, &mpos->lnum)) { + return IN_STATUS_LINE; // past bottom + } + + mpos->col = vcol2col(wp, mpos->lnum, col); + + if (mpos->col > 0) { + mpos->col--; + } + mpos->coladd = 0; + return IN_BUFFER; +} + /// Return true if "c" is a mouse key. bool is_mouse_key(int c) { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index d491a0ce84..beeb49ea66 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1493,12 +1493,12 @@ static void call_click_def_func(StlClickDefinition *click_defs, int col, int whi /// Do the appropriate action for the current mouse click in the current mode. /// Not used for Command-line mode. /// -/// Normal Mode: +/// Normal and Visual Mode: /// event modi- position visual change action /// fier cursor window /// left press - yes end yes /// left press C yes end yes "^]" (2) -/// left press S yes end yes "*" (2) +/// left press S yes end (popup: extend) yes "*" (2) /// left drag - yes start if moved no /// left relse - yes start if moved no /// middle press - yes if not active no put register @@ -1787,9 +1787,52 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) if (mouse_model_popup()) { if (which_button == MOUSE_RIGHT && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { - // NOTE: Ignore right button down and drag mouse events. Windows only - // shows the popup menu on the button up event. - return false; + if (!is_click) { + // Ignore right button release events, only shows the popup + // menu on the button down event. + return false; + } + jump_flags = 0; + if (STRCMP(p_mousem, "popup_setpos") == 0) { + // First set the cursor position before showing the popup + // menu. + if (VIsual_active) { + pos_T m_pos; + // set MOUSE_MAY_STOP_VIS if we are outside the + // selection or the current window (might have false + // negative here) + if (mouse_row < curwin->w_winrow + || mouse_row > (curwin->w_winrow + curwin->w_height)) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else if (get_fpos_of_mouse(&m_pos) != IN_BUFFER) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else { + if ((lt(curwin->w_cursor, VIsual) + && (lt(m_pos, curwin->w_cursor) || lt(VIsual, m_pos))) + || (lt(VIsual, curwin->w_cursor) + && (lt(m_pos, VIsual) || lt(curwin->w_cursor, m_pos)))) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else if (VIsual_mode == Ctrl_V) { + getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); + getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL); + if (m_pos.col < leftcol || m_pos.col > rightcol) { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } + } + } else { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } + if (jump_flags) { + jump_flags = jump_to_mouse(jump_flags, NULL, which_button); + update_curbuf(VIsual_active ? INVERTED : VALID); + setcursor(); + ui_flush(); // Update before showing popup menu + } + show_popupmenu(); + got_click = false; // ignore release events + return (jump_flags & CURSOR_MOVED) != 0; } if (which_button == MOUSE_LEFT && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT))) { diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index ecaeca005d..cf87f469b9 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -18,6 +18,7 @@ #include "nvim/ex_cmds.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/menu.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/popupmnu.h" @@ -512,9 +513,13 @@ void pum_redraw(void) char_u *st; char_u saved = *p; - *p = NUL; + if (saved != NUL) { + *p = NUL; + } st = (char_u *)transstr((const char *)s, true); - *p = saved; + if (saved != NUL) { + *p = saved; + } if (pum_rl) { char *rt = (char *)reverse_text(st); @@ -932,3 +937,145 @@ void pum_set_event_info(dict_T *dict) (void)tv_dict_add_bool(dict, S_LEN("scrollbar"), pum_scrollbar ? kBoolVarTrue : kBoolVarFalse); } + +static void pum_position_at_mouse(int min_width) +{ + if (Rows - mouse_row > pum_size) { + // Enough space below the mouse row. + pum_row = mouse_row + 1; + if (pum_height > Rows - pum_row) { + pum_height = Rows - pum_row; + } + } else { + // Show above the mouse row, reduce height if it does not fit. + pum_row = mouse_row - pum_size; + if (pum_row < 0) { + pum_height += pum_row; + pum_row = 0; + } + } + if (Columns - mouse_col >= pum_base_width || Columns - mouse_col > min_width) { + // Enough space to show at mouse column. + pum_col = mouse_col; + } else { + // Not enough space, right align with window. + pum_col = Columns - (pum_base_width > min_width ? min_width : pum_base_width); + } + + pum_width = Columns - pum_col; + if (pum_width > pum_base_width + 1) { + pum_width = pum_base_width + 1; + } +} + +/// Select the pum entry at the mouse position. +static void pum_select_mouse_pos(void) +{ + int idx = mouse_row - pum_row; + + if (idx < 0 || idx >= pum_size) { + pum_selected = -1; + } else if (*pum_array[idx].pum_text != NUL) { + pum_selected = idx; + } +} + +/// Execute the currently selected popup menu item. +static void pum_execute_menu(vimmenu_T *menu) +{ + int idx = 0; + exarg_T ea; + + for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) { + if (idx++ == pum_selected) { + memset(&ea, 0, sizeof(ea)); + execute_menu(&ea, mp); + break; + } + } +} + +/// Open the terminal version of the popup menu and don't return until it is closed. +void pum_show_popupmenu(vimmenu_T *menu) +{ + pum_undisplay(true); + pum_size = 0; + + for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) { + pum_size++; + } + + int idx = 0; + pumitem_T *array = (pumitem_T *)xcalloc((size_t)pum_size, sizeof(pumitem_T)); + + for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) { + if (menu_is_separator(mp->dname)) { + array[idx++].pum_text = (char_u *)""; + } else { + array[idx++].pum_text = (char_u *)mp->dname; + } + } + + pum_array = array; + pum_compute_size(); + pum_scrollbar = 0; + pum_height = pum_size; + pum_position_at_mouse(20); + + pum_selected = -1; + pum_first = 0; + + for (;;) { + pum_is_visible = true; + pum_is_drawn = true; + pum_redraw(); + setcursor(); + ui_flush(); + + int c = vgetc(); + if (c == ESC) { + break; + } else if (c == CAR || c == NL) { + // enter: select current item, if any, and close + pum_execute_menu(menu); + break; + } else if (c == 'k' || c == K_UP || c == K_MOUSEUP) { + // cursor up: select previous item + while (pum_selected > 0) { + pum_selected--; + if (*array[pum_selected].pum_text != NUL) { + break; + } + } + } else if (c == 'j' || c == K_DOWN || c == K_MOUSEDOWN) { + // cursor down: select next item + while (pum_selected < pum_size - 1) { + pum_selected++; + if (*array[pum_selected].pum_text != NUL) { + break; + } + } + } else if (c == K_RIGHTMOUSE) { + // Right mouse down: reposition the menu. + vungetc(c); + break; + } else if (c == K_LEFTDRAG || c == K_RIGHTDRAG || c == K_MOUSEMOVE) { + // mouse moved: select item in the mouse row + pum_select_mouse_pos(); + } else if (c == K_LEFTMOUSE || c == K_LEFTMOUSE_NM || c == K_RIGHTRELEASE) { + // left mouse click: select clicked item, if any, and close; + // right mouse release: select clicked item, close if any + pum_select_mouse_pos(); + if (pum_selected >= 0) { + pum_execute_menu(menu); + break; + } + if (c == K_LEFTMOUSE || c == K_LEFTMOUSE_NM) { + break; + } + } + } + + xfree(array); + pum_undisplay(true); +} diff --git a/src/nvim/screen.c b/src/nvim/screen.c index bc11883fd8..78b0d6b841 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -298,6 +298,13 @@ void redraw_win_signcol(win_T *wp) } } +/// Update all windows that are editing the current buffer. +void update_curbuf(int type) +{ + redraw_curbuf_later(type); + update_screen(type); +} + /// Redraw the parts of the screen that is marked for redraw. /// /// Most code shouldn't call this directly, rather use redraw_later() and |