diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/buffer_defs.h | 5 | ||||
-rw-r--r-- | src/nvim/ex_cmds.lua | 22 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 9 | ||||
-rw-r--r-- | src/nvim/globals.h | 1 | ||||
-rw-r--r-- | src/nvim/menu.c | 323 | ||||
-rw-r--r-- | src/nvim/menu.h | 1 | ||||
-rw-r--r-- | src/nvim/mouse.c | 43 | ||||
-rw-r--r-- | src/nvim/normal.c | 53 | ||||
-rw-r--r-- | src/nvim/popupmnu.c | 187 | ||||
-rw-r--r-- | src/nvim/screen.c | 20 | ||||
-rw-r--r-- | src/nvim/testdir/test_menu.vim | 49 | ||||
-rw-r--r-- | src/nvim/testdir/test_popup.vim | 20 |
12 files changed, 645 insertions, 88 deletions
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index a32d2b10a9..e5142f714e 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1144,8 +1144,9 @@ enum { MENU_INDEX_OP_PENDING = 3, MENU_INDEX_INSERT = 4, MENU_INDEX_CMDLINE = 5, - MENU_INDEX_TIP = 6, - MENU_MODES = 7, + MENU_INDEX_TERMINAL = 6, + MENU_INDEX_TIP = 7, + MENU_MODES = 8, }; typedef struct VimMenu vimmenu_T; diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index b18bdefc2a..69ff37f23c 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -191,7 +191,7 @@ module.cmds = { }, { command='behave', - flags=bit.bor(NEEDARG, WORD1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN), addr_type='ADDR_NONE', func='ex_behave', }, @@ -1991,7 +1991,7 @@ module.cmds = { command='popup', flags=bit.bor(NEEDARG, EXTRA, BANG, TRLBAR, NOTRLCOM, CMDWIN), addr_type='ADDR_NONE', - func='ex_ni', + func='ex_popup', }, { command='ppop', @@ -2879,6 +2879,24 @@ module.cmds = { func='ex_tag', }, { + command='tlmenu', + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + addr_type='ADDR_OTHER', + func='ex_menu', + }, + { + command='tlnoremenu', + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + addr_type='ADDR_OTHER', + func='ex_menu', + }, + { + command='tlunmenu', + flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), + addr_type='ADDR_OTHER', + func='ex_menu', + }, + { command='tmenu', flags=bit.bor(RANGE, ZEROR, EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN), addr_type='ADDR_OTHER', diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 4c9c1665fd..8fe62ae424 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -62,6 +62,7 @@ #include "nvim/os/time.h" #include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/popupmnu.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/screen.h" @@ -4059,6 +4060,9 @@ const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_cmenu: case CMD_cnoremenu: case CMD_cunmenu: + case CMD_tlmenu: + case CMD_tlnoremenu: + case CMD_tlunmenu: case CMD_tmenu: case CMD_tunmenu: case CMD_popup: @@ -7985,6 +7989,11 @@ static void ex_nogui(exarg_T *eap) eap->errmsg = N_("E25: Nvim does not have a built-in GUI"); } +static void ex_popup(exarg_T *eap) +{ + pum_make_popup(eap->arg, eap->forceit); +} + static void ex_swapname(exarg_T *eap) { if (curbuf->b_ml.ml_mfp == NULL || curbuf->b_ml.ml_mfp->mf_fname == NULL) { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index a2862edd6b..d2f56c739b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -986,6 +986,7 @@ EXTERN char e_notset[] INIT(= N_("E764: Option '%s' is not set")); EXTERN char e_invalidreg[] INIT(= N_("E850: Invalid register name")); EXTERN char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\"")); EXTERN char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior")); +EXTERN char e_menuothermode[] INIT(= N_("E328: Menu only exists in another mode")); EXTERN char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window")); EXTERN char e_listarg[] INIT(= N_("E686: Argument of %s must be a List")); EXTERN char e_unsupportedoption[] INIT(= N_("E519: Option not supported")); diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 80f8406ab0..1c9a84bbbc 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" @@ -36,10 +38,9 @@ #endif /// The character for each menu mode -static char menu_mode_chars[] = { 'n', 'v', 's', 'o', 'i', 'c', 't' }; +static char *menu_mode_chars[] = { "n", "v", "s", "o", "i", "c", "tl", "t" }; static char e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu"); -static char e_othermode[] = N_("E328: Menu only exists in another mode"); static char e_nomenu[] = N_("E329: No menu \"%s\""); // Return true if "name" is a window toolbar menu name. @@ -571,7 +572,7 @@ static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent) } } else if (*name != NUL) { if (!silent) { - emsg(_(e_othermode)); + emsg(_(e_menuothermode)); } return FAIL; } @@ -723,7 +724,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) (menu->noremap[bit] & REMAP_NONE) ? 1 : 0); tv_dict_add_nr(impl, S_LEN("sid"), (menu->noremap[bit] & REMAP_SCRIPT) ? 1 : 0); - tv_dict_add_dict(commands, &menu_mode_chars[bit], 1, impl); + tv_dict_add_dict(commands, menu_mode_chars[bit], 1, impl); } } } else { @@ -785,7 +786,7 @@ static vimmenu_T *find_menu(vimmenu_T *menu, char *name, int modes) emsg(_(e_notsubmenu)); return NULL; } else if ((menu->modes & modes) == 0x0) { - emsg(_(e_othermode)); + emsg(_(e_menuothermode)); return NULL; } else if (*p == NUL) { // found a full match return menu; @@ -859,7 +860,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) for (i = 0; i < depth + 2; i++) { msg_puts(" "); } - msg_putchar(menu_mode_chars[bit]); + msg_puts(menu_mode_chars[bit]); if (menu->noremap[bit] == REMAP_NONE) { msg_putchar('*'); } else if (menu->noremap[bit] == REMAP_SCRIPT) { @@ -1213,6 +1214,11 @@ int get_menu_cmd_modes(const char *cmd, bool forceit, int *noremap, int *unmenu) modes = MENU_INSERT_MODE; break; case 't': + if (*cmd == 'l') { // tlmenu, tlunmenu, tlnoremenu + modes = MENU_TERMINAL_MODE; + cmd++; + break; + } modes = MENU_TIP_MODE; // tmenu break; case 'c': // cmenu @@ -1259,9 +1265,13 @@ static char *popup_mode_name(char *name, int idx) size_t len = STRLEN(name); assert(len >= 4); - char *p = xstrnsave(name, len + 1); - memmove(p + 6, p + 5, len - 4); - p[5] = menu_mode_chars[idx]; + char *mode_chars = menu_mode_chars[idx]; + size_t mode_chars_len = strlen(mode_chars); + char *p = xstrnsave(name, len + mode_chars_len); + memmove(p + 5 + mode_chars_len, p + 5, len - 4); + for (size_t i = 0; i < mode_chars_len; i++) { + p[5 + i] = menu_mode_chars[idx][i]; + } return p; } @@ -1355,77 +1365,143 @@ static int menu_is_hidden(char *name) || (menu_is_popup(name) && name[5] != NUL); } -// 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) - FUNC_ATTR_NONNULL_ARG(2) +static int get_menu_mode(void) { - int idx = -1; - char *mode; - - // Use the Insert mode entry when returning to Insert mode. - if (((State & MODE_INSERT) || restart_edit) && !current_sctx.sc_sid) { - mode = "Insert"; - idx = MENU_INDEX_INSERT; - } else if (State & MODE_CMDLINE) { - mode = "Command"; - idx = MENU_INDEX_CMDLINE; - } else if (get_real_state() & MODE_VISUAL) { - // Detect real visual mode -- if we are really in visual mode we - // don't need to do any guesswork to figure out what the selection - // is. Just execute the visual binding for the menu. - mode = "Visual"; - idx = MENU_INDEX_VISUAL; - } else if (eap != NULL && eap->addr_count) { - pos_T tpos; - - mode = "Visual"; - idx = MENU_INDEX_VISUAL; - - // GEDDES: This is not perfect - but it is a - // quick way of detecting whether we are doing this from a - // selection - see if the range matches up with the visual - // select start and end. - if ((curbuf->b_visual.vi_start.lnum == eap->line1) - && (curbuf->b_visual.vi_end.lnum) == eap->line2) { - // Set it up for visual mode - equivalent to gv. - VIsual_mode = curbuf->b_visual.vi_mode; - tpos = curbuf->b_visual.vi_end; - curwin->w_cursor = curbuf->b_visual.vi_start; - curwin->w_curswant = curbuf->b_visual.vi_curswant; - } else { - // Set it up for line-wise visual mode - VIsual_mode = 'V'; - curwin->w_cursor.lnum = eap->line1; - curwin->w_cursor.col = 1; - tpos.lnum = eap->line2; - tpos.col = MAXCOL; - tpos.coladd = 0; + if (State & MODE_TERMINAL) { + return MENU_INDEX_TERMINAL; + } + 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; +} + +int get_menu_mode_flag(void) +{ + int mode = get_menu_mode(); + + if (mode == MENU_INDEX_INVALID) { + return 0; + } + return 1 << mode; +} - // Activate visual mode - VIsual_active = TRUE; - VIsual_reselect = TRUE; - check_cursor(); - VIsual = curwin->w_cursor; - curwin->w_cursor = tpos; +/// 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 menu_mode = get_menu_mode(); + if (menu_mode == MENU_INDEX_INVALID) { + return; + } + char *mode = menu_mode_chars[menu_mode]; + size_t mode_len = strlen(mode); - check_cursor(); + apply_autocmds(EVENT_MENUPOPUP, mode, NULL, false, curbuf); - // Adjust the cursor to make sure it is in the correct pos - // for exclusive mode - if (*p_sel == 'e' && gchar_cursor() != NUL) { - curwin->w_cursor.col++; + vimmenu_T *menu; + + for (menu = root_menu; menu != NULL; menu = menu->next) { + if (STRNCMP("PopUp", menu->name, 5) == 0 && STRNCMP(menu->name + 5, mode, mode_len) == 0) { + 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. +/// @param eap NULL for the window toolbar. +/// @param mode_idx specify a MENU_INDEX_ value, use -1 to depend on the current state +void execute_menu(const exarg_T *eap, vimmenu_T *menu, int mode_idx) + FUNC_ATTR_NONNULL_ARG(2) +{ + int idx = mode_idx; + + if (idx < 0) { + // Use the Insert mode entry when returning to Insert mode. + if (((State & MODE_INSERT) || restart_edit) && !current_sctx.sc_sid) { + idx = MENU_INDEX_INSERT; + } else if (State & MODE_CMDLINE) { + idx = MENU_INDEX_CMDLINE; + } else if (State & MODE_TERMINAL) { + idx = MENU_INDEX_TERMINAL; + } else if (get_real_state() & MODE_VISUAL) { + // Detect real visual mode -- if we are really in visual mode we + // don't need to do any guesswork to figure out what the selection + // is. Just execute the visual binding for the menu. + idx = MENU_INDEX_VISUAL; + } else if (eap != NULL && eap->addr_count) { + pos_T tpos; + + idx = MENU_INDEX_VISUAL; + + // GEDDES: This is not perfect - but it is a + // quick way of detecting whether we are doing this from a + // selection - see if the range matches up with the visual + // select start and end. + if ((curbuf->b_visual.vi_start.lnum == eap->line1) + && (curbuf->b_visual.vi_end.lnum) == eap->line2) { + // Set it up for visual mode - equivalent to gv. + VIsual_mode = curbuf->b_visual.vi_mode; + tpos = curbuf->b_visual.vi_end; + curwin->w_cursor = curbuf->b_visual.vi_start; + curwin->w_curswant = curbuf->b_visual.vi_curswant; + } else { + // Set it up for line-wise visual mode + VIsual_mode = 'V'; + curwin->w_cursor.lnum = eap->line1; + curwin->w_cursor.col = 1; + tpos.lnum = eap->line2; + tpos.col = MAXCOL; + tpos.coladd = 0; + } + + // Activate visual mode + VIsual_active = true; + VIsual_reselect = true; + check_cursor(); + VIsual = curwin->w_cursor; + curwin->w_cursor = tpos; + + check_cursor(); + + // Adjust the cursor to make sure it is in the correct pos + // for exclusive mode + if (*p_sel == 'e' && gchar_cursor() != NUL) { + curwin->w_cursor.col++; + } } } if (idx == -1 || eap == NULL) { - mode = "Normal"; idx = MENU_INDEX_NORMAL; } assert(idx != MENU_INDEX_INVALID); - if (menu->strings[idx] != NULL) { + if (menu->strings[idx] != NULL && (menu->modes & (1 << idx))) { // When executing a script or function execute the commands right now. // Also for the window toolbar // Otherwise put them in the typeahead buffer. @@ -1444,6 +1520,30 @@ static void execute_menu(const exarg_T *eap, vimmenu_T *menu) menu->silent[idx]); } } else if (eap != NULL) { + char *mode; + switch (idx) { + case MENU_INDEX_VISUAL: + mode = "Visual"; + break; + case MENU_INDEX_SELECT: + mode = "Select"; + break; + case MENU_INDEX_OP_PENDING: + mode = "Op-pending"; + break; + case MENU_INDEX_TERMINAL: + mode = "Terminal"; + break; + case MENU_INDEX_INSERT: + mode = "Insert"; + break; + case MENU_INDEX_CMDLINE: + mode = "Cmdline"; + break; + // case MENU_INDEX_TIP: cannot happen + default: + mode = "Normal"; + } semsg(_("E335: Menu not defined for %s mode"), mode); } } @@ -1452,9 +1552,43 @@ static void execute_menu(const exarg_T *eap, vimmenu_T *menu) // execute it. void ex_emenu(exarg_T *eap) { - char *saved_name = xstrdup(eap->arg); + char *arg = eap->arg; + int mode_idx = -1; + + if (arg[0] && ascii_iswhite(arg[1])) { + switch (arg[0]) { + case 'n': + mode_idx = MENU_INDEX_NORMAL; + break; + case 'v': + mode_idx = MENU_INDEX_VISUAL; + break; + case 's': + mode_idx = MENU_INDEX_SELECT; + break; + case 'o': + mode_idx = MENU_INDEX_OP_PENDING; + break; + case 't': + mode_idx = MENU_INDEX_TERMINAL; + break; + case 'i': + mode_idx = MENU_INDEX_INSERT; + break; + case 'c': + mode_idx = MENU_INDEX_CMDLINE; + break; + default: + semsg(_(e_invarg2), arg); + return; + } + arg = skipwhite(arg + 2); + } + + char *saved_name = xstrdup(arg); vimmenu_T *menu = *get_root_menu(saved_name); char *name = saved_name; + bool gave_emsg = false; while (*name) { // Find in the menu hierarchy char *p = menu_name_skip(name); @@ -1463,6 +1597,7 @@ void ex_emenu(exarg_T *eap) if (menu_name_equal(name, menu)) { if (*p == NUL && menu->children != NULL) { emsg(_("E333: Menu path must lead to a menu item")); + gave_emsg = true; menu = NULL; } else if (*p != NUL && menu->children == NULL) { emsg(_(e_notsubmenu)); @@ -1480,12 +1615,60 @@ void ex_emenu(exarg_T *eap) } xfree(saved_name); if (menu == NULL) { - semsg(_("E334: Menu not found: %s"), eap->arg); + if (!gave_emsg) { + semsg(_("E334: Menu not found: %s"), arg); + } return; } // Found the menu, so execute. - execute_menu(eap, menu); + execute_menu(eap, menu, mode_idx); +} + +/// Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy. +vimmenu_T *menu_find(const char *path_name) +{ + vimmenu_T *menu = *get_root_menu(path_name); + char *saved_name = xstrdup(path_name); + char *name = saved_name; + while (*name) { + // find the end of one dot-separated name and put a NUL at the dot + char *p = menu_name_skip(name); + + while (menu != NULL) { + if (menu_name_equal(name, menu)) { + if (menu->children == NULL) { + // found a menu item instead of a sub-menu + if (*p == NUL) { + emsg(_("E336: Menu path must lead to a sub-menu")); + } else { + emsg(_(e_notsubmenu)); + } + menu = NULL; + goto theend; + } + if (*p == NUL) { // found a full match + goto theend; + } + break; + } + menu = menu->next; + } + if (menu == NULL) { // didn't find it + break; + } + + // Found a match, search the sub-menu. + menu = menu->children; + name = p; + } + + if (menu == NULL) { + emsg(_("E337: Menu not found - check menu names")); + } +theend: + xfree(saved_name); + return menu; } /* diff --git a/src/nvim/menu.h b/src/nvim/menu.h index 5c65918d79..9a60ebfb83 100644 --- a/src/nvim/menu.h +++ b/src/nvim/menu.h @@ -18,6 +18,7 @@ #define MENU_OP_PENDING_MODE (1 << MENU_INDEX_OP_PENDING) #define MENU_INSERT_MODE (1 << MENU_INDEX_INSERT) #define MENU_CMDLINE_MODE (1 << MENU_INDEX_CMDLINE) +#define MENU_TERMINAL_MODE (1 << MENU_INDEX_TERMINAL) #define MENU_TIP_MODE (1 << MENU_INDEX_TIP) #define MENU_ALL_MODES ((1 << MENU_INDEX_TIP) - 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..ba151b5ce2 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,181 @@ 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) +{ + pum_anchor_grid = mouse_grid; + if (Rows - mouse_row > pum_size) { + // Enough space below the mouse row. + pum_above = false; + 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_above = true; + 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) +{ + if (mouse_grid == pum_grid.handle) { + pum_selected = mouse_row; + return; + } else if (mouse_grid > 1) { + pum_selected = -1; + return; + } + + 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 mode) +{ + int idx = 0; + exarg_T ea; + + for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) { + if ((mp->modes & mp->enabled & mode) && idx++ == pum_selected) { + memset(&ea, 0, sizeof(ea)); + execute_menu(&ea, mp, -1); + 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; + int mode = get_menu_mode_flag(); + + for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) { + if (menu_is_separator(mp->dname) || (mp->modes & mp->enabled & mode)) { + pum_size++; + } + } + + // When there are only Terminal mode menus, using "popup Edit" results in + // pum_size being zero. + if (pum_size <= 0) { + emsg(e_menuothermode); + return; + } + + 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 if (mp->modes & mp->enabled & mode) { + 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_mayforce(true); + ui_flush(); + + int c = vgetc(); + if (c == ESC || c == Ctrl_C) { + break; + } else if (c == CAR || c == NL) { + // enter: select current item, if any, and close + pum_execute_menu(menu, mode); + 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, mode); + break; + } + if (c == K_LEFTMOUSE || c == K_LEFTMOUSE_NM) { + break; + } + } + } + + xfree(array); + pum_undisplay(true); +} + +void pum_make_popup(const char *path_name, int use_mouse_pos) +{ + if (!use_mouse_pos) { + // Hack: set mouse position at the cursor so that the menu pops up + // around there. + mouse_row = curwin->w_winrow + curwin->w_wrow; + mouse_col = curwin->w_wincol + curwin->w_wcol; + } + + vimmenu_T *menu = menu_find(path_name); + if (menu != NULL) { + pum_show_popupmenu(menu); + } +} diff --git a/src/nvim/screen.c b/src/nvim/screen.c index bc11883fd8..c2a88bb4a6 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 @@ -5750,12 +5757,17 @@ static void linecopy(ScreenGrid *grid, int to, int from, int col, int width) width * sizeof(sattr_T)); } -/* - * Set cursor to its position in the current window. - */ +/// Set cursor to its position in the current window. void setcursor(void) { - if (redrawing()) { + setcursor_mayforce(false); +} + +/// Set cursor to its position in the current window. +/// @param force when true, also when not redrawing. +void setcursor_mayforce(bool force) +{ + if (force || redrawing()) { validate_cursor(); ScreenGrid *grid = &curwin->w_grid; diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim index de6d4aa359..eb25e41990 100644 --- a/src/nvim/testdir/test_menu.vim +++ b/src/nvim/testdir/test_menu.vim @@ -36,3 +36,52 @@ func Test_translate_menu() source $VIMRUNTIME/delmenu.vim endfunc + +func Test_menu_commands() + nmenu 2 Test.FooBar :let g:did_menu = 'normal'<CR> + vmenu 2 Test.FooBar :let g:did_menu = 'visual'<CR> + smenu 2 Test.FooBar :let g:did_menu = 'select'<CR> + omenu 2 Test.FooBar :let g:did_menu = 'op-pending'<CR> + tlmenu 2 Test.FooBar :let g:did_menu = 'terminal'<CR> + imenu 2 Test.FooBar :let g:did_menu = 'insert'<CR> + cmenu 2 Test.FooBar :let g:did_menu = 'cmdline'<CR> + emenu n Test.FooBar + call assert_equal('normal', g:did_menu) + emenu v Test.FooBar + call assert_equal('visual', g:did_menu) + emenu s Test.FooBar + call assert_equal('select', g:did_menu) + emenu o Test.FooBar + call assert_equal('op-pending', g:did_menu) + emenu t Test.FooBar + call assert_equal('terminal', g:did_menu) + emenu i Test.FooBar + call assert_equal('insert', g:did_menu) + emenu c Test.FooBar + call assert_equal('cmdline', g:did_menu) + + nunmenu Test.FooBar + call assert_fails('emenu n Test.FooBar', 'E335: Menu not defined for Normal mode') + vunmenu Test.FooBar + call assert_fails('emenu v Test.FooBar', 'E335: Menu not defined for Visual mode') + vmenu 2 Test.FooBar :let g:did_menu = 'visual'<CR> + sunmenu Test.FooBar + call assert_fails('emenu s Test.FooBar', 'E335: Menu not defined for Select mode') + ounmenu Test.FooBar + call assert_fails('emenu o Test.FooBar', 'E335: Menu not defined for Op-pending mode') + iunmenu Test.FooBar + call assert_fails('emenu i Test.FooBar', 'E335: Menu not defined for Insert mode') + cunmenu Test.FooBar + call assert_fails('emenu c Test.FooBar', 'E335: Menu not defined for Cmdline mode') + tlunmenu Test.FooBar + call assert_fails('emenu t Test.FooBar', 'E335: Menu not defined for Terminal mode') + + aunmenu Test.FooBar + call assert_fails('emenu n Test.FooBar', 'E334:') + + nmenu 2 Test.FooBar.Child :let g:did_menu = 'foobar'<CR> + call assert_fails('emenu n Test.FooBar', 'E333:') + nunmenu Test.FooBar.Child + + unlet g:did_menu +endfun diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 9a31f61582..48358f71b8 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -913,7 +913,7 @@ func Test_popup_complete_backwards_ctrl_p() bwipe! endfunc -fun! Test_complete_o_tab() +func Test_complete_o_tab() CheckFunction test_override let s:o_char_pressed = 0 @@ -922,7 +922,7 @@ fun! Test_complete_o_tab() let s:o_char_pressed = 0 call feedkeys("\<c-x>\<c-n>", 'i') endif - endf + endfunc set completeopt=menu,noselect new @@ -941,7 +941,21 @@ fun! Test_complete_o_tab() bwipe! set completeopt& delfunc s:act_on_text_changed -endf +endfunc + +func Test_menu_only_exists_in_terminal() + if !exists(':tlmenu') || has('gui_running') + return + endif + tlnoremenu &Edit.&Paste<Tab>"+gP <C-W>"+ + aunmenu * + try + popup Edit + call assert_false(1, 'command should have failed') + catch + call assert_exception('E328:') + endtry +endfunc func Test_popup_complete_info_01() new |