aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/buffer_defs.h5
-rw-r--r--src/nvim/ex_cmds.lua22
-rw-r--r--src/nvim/ex_docmd.c9
-rw-r--r--src/nvim/globals.h1
-rw-r--r--src/nvim/menu.c323
-rw-r--r--src/nvim/menu.h1
-rw-r--r--src/nvim/mouse.c43
-rw-r--r--src/nvim/normal.c53
-rw-r--r--src/nvim/popupmnu.c187
-rw-r--r--src/nvim/screen.c20
-rw-r--r--src/nvim/testdir/test_menu.vim49
-rw-r--r--src/nvim/testdir/test_popup.vim20
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