Merge pull request #19170 from zeertzjq/vim-8.0.1558

vim-patch:8.0.{1558,1570,1574,1588},8.1.{0487,0695,1274}: menu features
This commit is contained in:
zeertzjq
2022-07-01 10:49:14 +08:00
committed by GitHub
22 changed files with 809 additions and 101 deletions

View File

@@ -5,6 +5,7 @@
" Last Change: 2019 Dec 10
aunmenu *
tlunmenu *
unlet! g:did_install_default_menus
unlet! g:did_install_syntax_menu

View File

@@ -727,13 +727,14 @@ MenuPopup Just before showing the popup menu (under the
right mouse button). Useful for adjusting the
menu for what is under the cursor or mouse
pointer.
The pattern is matched against a single
character representing the mode:
The pattern is matched against one or two
characters representing the mode:
n Normal
v Visual
o Operator-pending
i Insert
c Command line
tl Terminal
*ModeChanged*
ModeChanged After changing the mode. The pattern is
matched against `'old_mode:new_mode'`, for

View File

@@ -195,6 +195,10 @@ the mouse button down on this will pop up a menu containing the item
"Big Changes", which is a sub-menu containing the item "Delete All Spaces",
which when selected, performs the operation.
To create a menu for terminal mode, use |:tlmenu| instead of |:tmenu| unlike
key mapping (|:tmap|). This is because |:tmenu| is already used for defining
tooltips for menus. See |terminal-input|.
Special characters in a menu name:
& The next character is the shortcut key. Make sure each
@@ -214,9 +218,9 @@ this menu can be used. The second part is shown as "Open :e". The ":e"
is right aligned, and the "O" is underlined, to indicate it is the shortcut.
*:am* *:amenu* *:an* *:anoremenu*
The ":amenu" command can be used to define menu entries for all modes at once.
To make the command work correctly, a character is automatically inserted for
some modes:
The ":amenu" command can be used to define menu entries for all modes at once,
expect for Terminal mode. To make the command work correctly, a character is
automatically inserted for some modes:
mode inserted appended ~
Normal nothing nothing
Visual <C-C> <C-\><C-G>
@@ -469,6 +473,16 @@ Executing Menus *execute-menus*
insert-mode menu Eg: >
:emenu File.Exit
:[range]em[enu] {mode} {menu} Like above, but execute the menu for {mode}:
'n': |:nmenu| Normal mode
'v': |:vmenu| Visual mode
's': |:smenu| Select mode
'o': |:omenu| Operator-pending mode
't': |:tlmenu| Terminal mode
'i': |:imenu| Insert mode
'c': |:cmenu| Cmdline mode
You can use :emenu to access useful menu items you may have got used to from
GUI mode. See 'wildmenu' for an option that works well with this. See
|console-menus| for an example.
@@ -494,7 +508,9 @@ may be used to complete the name of the menu item for the appropriate mode.
To remove all menus use: *:unmenu-all* >
:unmenu * " remove all menus in Normal and visual mode
:unmenu! * " remove all menus in Insert and Command-line mode
:aunmenu * " remove all menus in all modes
:aunmenu * " remove all menus in all modes, except for Terminal
" mode
:tlunmenu * " remove all menus in Terminal mode
If you want to get rid of the menu bar: >
:set guioptions-=m
@@ -547,6 +563,8 @@ See section |42.4| in the user manual.
:tu[nmenu] {menupath} Remove a tip for a menu or tool.
{only in X11 and Win32 GUI}
Note: To create menus for terminal mode, use |:tlmenu| instead.
When a tip is defined for a menu item, it appears in the command-line area
when the mouse is over that item, much like a standard Windows menu hint in
the status bar. (Except when Vim is in Command-line mode, when of course
@@ -577,8 +595,8 @@ a menu item - you don't need to do a :tunmenu as well.
5.9 Popup Menus
In the Win32 GUI, you can cause a menu to popup at the cursor. This behaves
similarly to the PopUp menus except that any menu tree can be popped up.
You can cause a menu to popup at the cursor. This behaves similarly to the
PopUp menus except that any menu tree can be popped up.
This command is for backwards compatibility, using it is discouraged, because
it behaves in a strange way.
@@ -587,7 +605,6 @@ it behaves in a strange way.
:popu[p] {name} Popup the menu {name}. The menu named must
have at least one subentry, but need not
appear on the menu-bar (see |hidden-menus|).
{only available for Win32 GUI}
:popu[p]! {name} Like above, but use the position of the mouse
pointer instead of the cursor.

View File

@@ -1622,17 +1622,20 @@ tag command action ~
|:tjump| :tj[ump] like ":tselect", but jump directly when there
is only one match
|:tlast| :tl[ast] jump to last matching tag
|:tmapclear| :tmapc[lear] remove all mappings for Terminal-Job mode
|:tmap| :tma[p] like ":map" but for Terminal-Job mode
|:tlmenu| :tlm[enu] add menu for |Terminal-mode|
|:tlnoremenu| :tln[oremenu] like ":noremenu" but for |Terminal-mode|
|:tlunmenu| :tlu[nmenu] remove menu for |Terminal-mode|
|:tmapclear| :tmapc[lear] remove all mappings for |Terminal-mode|
|:tmap| :tma[p] like ":map" but for |Terminal-mode|
|:tmenu| :tm[enu] define menu tooltip
|:tnext| :tn[ext] jump to next matching tag
|:tnoremap| :tno[remap] like ":noremap" but for Terminal-Job mode
|:tnoremap| :tno[remap] like ":noremap" but for |Terminal-mode|
|:topleft| :to[pleft] make split window appear at top or far left
|:tprevious| :tp[revious] jump to previous matching tag
|:trewind| :tr[ewind] jump to first matching tag
|:try| :try execute commands, abort on error or exception
|:tselect| :ts[elect] list matching tags and select one
|:tunmap| :tunma[p] like ":unmap" but for Terminal-Job mode
|:tunmap| :tunma[p] like ":unmap" but for |Terminal-mode|
|:tunmenu| :tu[nmenu] remove menu tooltip
|:undo| :u[ndo] undo last change(s)
|:undojoin| :undoj[oin] join next change with previous undo block

View File

@@ -80,6 +80,9 @@ To use `ALT+{h,j,k,l}` to navigate windows from any mode: >
:nnoremap <A-k> <C-w>k
:nnoremap <A-l> <C-w>l
You can also create menus similar to terminal mode mappings, but you have to
use |:tlmenu| instead of |:tmenu|.
Mouse input has the following behavior:
- If the program has enabled mouse events, the corresponding events will be

View File

@@ -150,7 +150,8 @@ like the variations on the ":map" command:
:menu! Insert and Command-line mode
:imenu Insert mode
:cmenu Command-line mode
:amenu All modes
:tlmenu Terminal mode
:amenu All modes (except for Terminal mode)
To avoid that the commands of a menu item are being mapped, use the command
":noremenu", ":nnoremenu", ":anoremenu", etc.

View File

@@ -164,6 +164,9 @@ if exists(':tlmenu')
endif
nnoremenu 20.360 &Edit.&Paste<Tab>"+gP "+gP
cnoremenu &Edit.&Paste<Tab>"+gP <C-R>+
if exists(':tlmenu')
tlnoremenu &Edit.&Paste<Tab>"+gP <C-W>"+
endif
exe 'vnoremenu <script> &Edit.&Paste<Tab>"+gP ' . paste#paste_cmd['v']
exe 'inoremenu <script> &Edit.&Paste<Tab>"+gP ' . paste#paste_cmd['i']
nnoremenu 20.370 &Edit.Put\ &Before<Tab>[p [p

View File

@@ -411,7 +411,7 @@ syn case match
" Menus: {{{2
" =====
syn cluster vimMenuList contains=vimMenuBang,vimMenuPriority,vimMenuName,vimMenuMod
syn keyword vimCommand am[enu] an[oremenu] aun[menu] cme[nu] cnoreme[nu] cunme[nu] ime[nu] inoreme[nu] iunme[nu] me[nu] nme[nu] nnoreme[nu] noreme[nu] nunme[nu] ome[nu] onoreme[nu] ounme[nu] unme[nu] vme[nu] vnoreme[nu] vunme[nu] skipwhite nextgroup=@vimMenuList
syn keyword vimCommand am[enu] an[oremenu] aun[menu] cme[nu] cnoreme[nu] cunme[nu] ime[nu] inoreme[nu] iunme[nu] me[nu] nme[nu] nnoreme[nu] noreme[nu] nunme[nu] ome[nu] onoreme[nu] ounme[nu] tlm[enu] tln[oremenu] tlu[nmenu] unme[nu] vme[nu] vnoreme[nu] vunme[nu] skipwhite nextgroup=@vimMenuList
syn match vimMenuName "[^ \t\\<]\+" contained nextgroup=vimMenuNameMore,vimMenuMap
syn match vimMenuPriority "\d\+\(\.\d\+\)*" contained skipwhite nextgroup=vimMenuName
syn match vimMenuNameMore "\c\\\s\|<tab>\|\\\." contained nextgroup=vimMenuName,vimMenuNameMore contains=vimNotation

View File

@@ -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;

View File

@@ -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',
@@ -2878,6 +2878,24 @@ module.cmds = {
addr_type='ADDR_NONE',
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),

View File

@@ -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) {

View File

@@ -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"));

View File

@@ -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)
static int get_menu_mode(void)
{
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;
}
/// 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);
apply_autocmds(EVENT_MENUPOPUP, mode, NULL, false, curbuf);
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 = -1;
char *mode;
int idx = mode_idx;
// 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;
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;
mode = "Visual";
idx = MENU_INDEX_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;
}
// 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;
// Activate visual mode
VIsual_active = true;
VIsual_reselect = true;
check_cursor();
VIsual = curwin->w_cursor;
curwin->w_cursor = tpos;
check_cursor();
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++;
// 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;
}
/*

View File

@@ -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)
/// @}

View File

@@ -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)
{

View File

@@ -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))) {

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -3748,7 +3748,7 @@ describe('API', function()
eq("foo", meths.cmd({ cmd = "Foo" }, { output = true }))
end)
it('errors if command is not implemented', function()
eq("Command not implemented: popup", pcall_err(meths.cmd, { cmd = "popup" }, {}))
eq("Command not implemented: winpos", pcall_err(meths.cmd, { cmd = "winpos" }, {}))
end)
it('works with empty arguments list', function()
meths.cmd({ cmd = "update" }, {})

View File

@@ -11,6 +11,7 @@ local get_pathsep = helpers.get_pathsep
local eq = helpers.eq
local pcall_err = helpers.pcall_err
local exec_lua = helpers.exec_lua
local exec = helpers.exec
describe('ui/ext_popupmenu', function()
local screen
@@ -2359,6 +2360,74 @@ describe('builtin popupmenu', function()
{2:-- INSERT --} |
]])
end)
it('supports mousemodel=popup', function()
screen:try_resize(32, 6)
exec([[
call setline(1, 'popup menu test')
set mouse=a mousemodel=popup
menu PopUp.foo :let g:menustr = 'foo'<CR>
menu PopUp.bar :let g:menustr = 'bar'<CR>
menu PopUp.baz :let g:menustr = 'baz'<CR>
]])
meths.input_mouse('right', 'press', '', 0, 0, 4)
screen:expect([[
^popup menu test |
{1:~ }{n: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: baz }{1: }|
{1:~ }|
|
]])
feed('<Down>')
screen:expect([[
^popup menu test |
{1:~ }{s: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: baz }{1: }|
{1:~ }|
|
]])
feed('<Down>')
screen:expect([[
^popup menu test |
{1:~ }{n: foo }{1: }|
{1:~ }{s: bar }{1: }|
{1:~ }{n: baz }{1: }|
{1:~ }|
|
]])
feed('<CR>')
screen:expect([[
^popup menu test |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
:let g:menustr = 'bar' |
]])
eq('bar', meths.get_var('menustr'))
meths.input_mouse('right', 'press', '', 0, 1, 20)
screen:expect([[
^popup menu test |
{1:~ }|
{1:~ }{n: foo }{1: }|
{1:~ }{n: bar }{1: }|
{1:~ }{n: baz }{1: }|
:let g:menustr = 'bar' |
]])
meths.input_mouse('left', 'press', '', 0, 4, 22)
screen:expect([[
^popup menu test |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
:let g:menustr = 'baz' |
]])
eq('baz', meths.get_var('menustr'))
end)
end)
describe('builtin popupmenu with ui/ext_multigrid', function()
@@ -2450,4 +2519,57 @@ describe('builtin popupmenu with ui/ext_multigrid', function()
{n: 哦哦哦哦哦哦哦哦哦>}{s: }|
]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 11, false, 100}}})
end)
it('supports mousemodel=popup', function()
screen:try_resize(32, 6)
exec([[
call setline(1, 'popup menu test')
set mouse=a mousemodel=popup
menu PopUp.foo :let g:menustr = 'foo'<CR>
menu PopUp.bar :let g:menustr = 'bar'<CR>
menu PopUp.baz :let g:menustr = 'baz'<CR>
]])
meths.input_mouse('right', 'press', '', 2, 1, 20)
screen:expect({grid=[[
## grid 1
[2:--------------------------------]|
[2:--------------------------------]|
[2:--------------------------------]|
[2:--------------------------------]|
[2:--------------------------------]|
[3:--------------------------------]|
## grid 2
^popup menu test |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
## grid 3
|
## grid 4
{n: foo }|
{n: bar }|
{n: baz }|
]], float_pos={[4] = {{id = -1}, 'NW', 2, 2, 19, false, 100}}})
meths.input_mouse('left', 'press', '', 4, 2, 2)
screen:expect({grid=[[
## grid 1
[2:--------------------------------]|
[2:--------------------------------]|
[2:--------------------------------]|
[2:--------------------------------]|
[2:--------------------------------]|
[3:--------------------------------]|
## grid 2
^popup menu test |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
## grid 3
:let g:menustr = 'baz' |
]]})
eq('baz', meths.get_var('menustr'))
end)
end)