vim-patch:8.1.0487: no menus specifically for the terminal window

Problem:    No menus specifically for the terminal window.
Solution:   Add :tlmenu. (Yee Cheng Chin, closes vim/vim#3439) Add a menu test.
4c5d815256

ADDR_OHTER comes from patch 8.1.1241, which has already been ported.
This commit is contained in:
zeertzjq
2022-06-30 17:17:27 +08:00
parent 5551a29d06
commit 015778a381
15 changed files with 243 additions and 87 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.
@@ -547,6 +561,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

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',
},
@@ -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

@@ -4060,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:

View File

@@ -38,7 +38,7 @@
#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");
@@ -725,7 +725,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 {
@@ -861,7 +861,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) {
@@ -1215,6 +1215,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
@@ -1261,9 +1266,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;
}
@@ -1359,9 +1368,13 @@ static int menu_is_hidden(char *name)
static int get_menu_mode(void)
{
if (State & MODE_TERMINAL) {
return MENU_INDEX_TERMINAL;
}
if (VIsual_active) {
if (VIsual_select)
if (VIsual_select) {
return MENU_INDEX_SELECT;
}
return MENU_INDEX_VISUAL;
}
if (State & MODE_INSERT) {
@@ -1397,21 +1410,19 @@ int get_menu_mode_flag(void)
/// etc.
void show_popupmenu(void)
{
int mode = get_menu_mode();
if (mode == MENU_INDEX_INVALID) {
int menu_mode = get_menu_mode();
if (menu_mode == MENU_INDEX_INVALID) {
return;
}
mode = menu_mode_chars[mode];
char *mode = menu_mode_chars[menu_mode];
size_t mode_len = strlen(mode);
char ename[2];
ename[0] = (char)mode;
ename[1] = NUL;
apply_autocmds(EVENT_MENUPOPUP, ename, NULL, false, curbuf);
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 && menu->name[5] == mode) {
if (STRNCMP("PopUp", menu->name, 5) == 0 && STRNCMP(menu->name + 5, mode, mode_len) == 0) {
break;
}
}
@@ -1422,72 +1433,71 @@ void show_popupmenu(void)
}
}
// Execute "menu". Use by ":emenu" and the window toolbar.
// "eap" is NULL for the window toolbar.
void execute_menu(const exarg_T *eap, vimmenu_T *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;
}
@@ -1511,6 +1521,30 @@ 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);
}
}
@@ -1519,9 +1553,43 @@ 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);
@@ -1530,6 +1598,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));
@@ -1547,12 +1616,14 @@ 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.

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

@@ -989,7 +989,7 @@ static void pum_execute_menu(vimmenu_T *menu, int mode)
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);
execute_menu(&ea, mp, -1);
break;
}
}

View File

@@ -36,3 +36,37 @@ 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)
aunmenu Test.FooBar
tlunmenu 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