menu.c (54038B)
1 // Code for menus. Used for the GUI and 'wildmenu'. 2 // GUI/Motif support by Robert Webb 3 4 #include <assert.h> 5 #include <stdbool.h> 6 #include <stdint.h> 7 #include <string.h> 8 9 #include "nvim/ascii_defs.h" 10 #include "nvim/autocmd.h" 11 #include "nvim/autocmd_defs.h" 12 #include "nvim/buffer_defs.h" 13 #include "nvim/charset.h" 14 #include "nvim/cmdexpand_defs.h" 15 #include "nvim/cursor.h" 16 #include "nvim/errors.h" 17 #include "nvim/eval/typval.h" 18 #include "nvim/eval/typval_defs.h" 19 #include "nvim/eval/vars.h" 20 #include "nvim/ex_cmds_defs.h" 21 #include "nvim/ex_docmd.h" 22 #include "nvim/garray.h" 23 #include "nvim/garray_defs.h" 24 #include "nvim/getchar.h" 25 #include "nvim/getchar_defs.h" 26 #include "nvim/gettext_defs.h" 27 #include "nvim/globals.h" 28 #include "nvim/highlight_defs.h" 29 #include "nvim/keycodes.h" 30 #include "nvim/macros_defs.h" 31 #include "nvim/mbyte.h" 32 #include "nvim/mbyte_defs.h" 33 #include "nvim/memory.h" 34 #include "nvim/menu.h" 35 #include "nvim/menu_defs.h" 36 #include "nvim/message.h" 37 #include "nvim/option_vars.h" 38 #include "nvim/popupmenu.h" 39 #include "nvim/pos_defs.h" 40 #include "nvim/state.h" 41 #include "nvim/state_defs.h" 42 #include "nvim/strings.h" 43 #include "nvim/types_defs.h" 44 #include "nvim/ui.h" 45 #include "nvim/vim_defs.h" 46 47 #define MENUDEPTH 10 // maximum depth of menus 48 49 #include "menu.c.generated.h" 50 51 /// When non-zero no menu must be added or cleared. Prevents the list of menus 52 /// changing while listing them. 53 static int menus_locked = 0; 54 55 /// The character for each menu mode 56 static char *menu_mode_chars[] = { "n", "v", "s", "o", "i", "c", "tl", "t" }; 57 58 static const char e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu"); 59 static const char e_nomenu[] = N_("E329: No menu \"%s\""); 60 61 // Return true if "name" is a window toolbar menu name. 62 static bool menu_is_winbar(const char *const name) 63 FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL 64 { 65 return (strncmp(name, "WinBar", 6) == 0); 66 } 67 68 static vimmenu_T **get_root_menu(const char *const name) 69 FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL 70 { 71 return &root_menu; 72 } 73 74 /// If "menus_locked" is set then give an error and return true. 75 /// Otherwise return false. 76 static int is_menus_locked(void) 77 { 78 if (menus_locked > 0) { 79 emsg(_(e_cannot_change_menus_while_listing)); 80 return true; 81 } 82 return false; 83 } 84 85 /// Do the :menu command and relatives. 86 /// @param eap Ex command arguments 87 void ex_menu(exarg_T *eap) 88 { 89 char *map_to; // command mapped to the menu entry 90 int noremap; 91 bool silent = false; 92 bool unmenu; 93 char *map_buf; 94 char *p; 95 int i; 96 int pri_tab[MENUDEPTH + 1]; 97 TriState enable = kNone; // kTrue for "menu enable", 98 // kFalse for "menu disable 99 vimmenu_T menuarg; 100 101 int modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu); 102 char *arg = eap->arg; 103 104 while (true) { 105 if (strncmp(arg, "<script>", 8) == 0) { 106 noremap = REMAP_SCRIPT; 107 arg = skipwhite(arg + 8); 108 continue; 109 } 110 if (strncmp(arg, "<silent>", 8) == 0) { 111 silent = true; 112 arg = skipwhite(arg + 8); 113 continue; 114 } 115 if (strncmp(arg, "<special>", 9) == 0) { 116 // Ignore obsolete "<special>" modifier. 117 arg = skipwhite(arg + 9); 118 continue; 119 } 120 break; 121 } 122 123 // Locate an optional "icon=filename" argument 124 // TODO(nvim): Currently this is only parsed. Should expose it to UIs. 125 if (strncmp(arg, "icon=", 5) == 0) { 126 arg += 5; 127 while (*arg != NUL && *arg != ' ') { 128 if (*arg == '\\') { 129 STRMOVE(arg, arg + 1); 130 } 131 MB_PTR_ADV(arg); 132 } 133 if (*arg != NUL) { 134 *arg++ = NUL; 135 arg = skipwhite(arg); 136 } 137 } 138 139 // Fill in the priority table. 140 for (p = arg; *p; p++) { 141 if (!ascii_isdigit(*p) && *p != '.') { 142 break; 143 } 144 } 145 if (ascii_iswhite(*p)) { 146 for (i = 0; i < MENUDEPTH && !ascii_iswhite(*arg); i++) { 147 pri_tab[i] = getdigits_int(&arg, false, 0); 148 if (pri_tab[i] == 0) { 149 pri_tab[i] = 500; 150 } 151 if (*arg == '.') { 152 arg++; 153 } 154 } 155 arg = skipwhite(arg); 156 } else if (eap->addr_count && eap->line2 != 0) { 157 pri_tab[0] = eap->line2; 158 i = 1; 159 } else { 160 i = 0; 161 } 162 while (i < MENUDEPTH) { 163 pri_tab[i++] = 500; 164 } 165 pri_tab[MENUDEPTH] = -1; // mark end of the table 166 167 // Check for "disable" or "enable" argument. 168 if (strncmp(arg, "enable", 6) == 0 && ascii_iswhite(arg[6])) { 169 enable = kTrue; 170 arg = skipwhite(arg + 6); 171 } else if (strncmp(arg, "disable", 7) == 0 && ascii_iswhite(arg[7])) { 172 enable = kFalse; 173 arg = skipwhite(arg + 7); 174 } 175 176 // If there is no argument, display all menus. 177 if (*arg == NUL) { 178 show_menus(arg, modes); 179 return; 180 } 181 182 char *menu_path = arg; 183 if (*menu_path == '.') { 184 semsg(_(e_invarg2), menu_path); 185 goto theend; 186 } 187 188 map_to = menu_translate_tab_and_shift(arg); 189 190 // If there is only a menu name, display menus with that name. 191 if (*map_to == NUL && !unmenu && enable == kNone) { 192 show_menus(menu_path, modes); 193 goto theend; 194 } else if (*map_to != NUL && (unmenu || enable != kNone)) { 195 semsg(_(e_trailing_arg), map_to); 196 goto theend; 197 } 198 199 vimmenu_T **root_menu_ptr = get_root_menu(menu_path); 200 201 if (enable != kNone) { 202 // Change sensitivity of the menu. 203 // For the PopUp menu, remove a menu for each mode separately. 204 // Careful: menu_enable_recurse() changes menu_path. 205 if (strcmp(menu_path, "*") == 0) { // meaning: do all menus 206 menu_path = ""; 207 } 208 209 if (menu_is_popup(menu_path)) { 210 for (i = 0; i < MENU_INDEX_TIP; i++) { 211 if (modes & (1 << i)) { 212 p = popup_mode_name(menu_path, i); 213 menu_enable_recurse(*root_menu_ptr, p, MENU_ALL_MODES, enable); 214 xfree(p); 215 } 216 } 217 } 218 menu_enable_recurse(*root_menu_ptr, menu_path, modes, enable); 219 } else if (unmenu) { 220 if (is_menus_locked()) { 221 goto theend; 222 } 223 224 // Delete menu(s). 225 if (strcmp(menu_path, "*") == 0) { // meaning: remove all menus 226 menu_path = ""; 227 } 228 229 // For the PopUp menu, remove a menu for each mode separately. 230 if (menu_is_popup(menu_path)) { 231 for (i = 0; i < MENU_INDEX_TIP; i++) { 232 if (modes & (1 << i)) { 233 p = popup_mode_name(menu_path, i); 234 remove_menu(root_menu_ptr, p, MENU_ALL_MODES, true); 235 xfree(p); 236 } 237 } 238 } 239 240 // Careful: remove_menu() changes menu_path 241 remove_menu(root_menu_ptr, menu_path, modes, false); 242 } else { 243 if (is_menus_locked()) { 244 goto theend; 245 } 246 247 // Add menu(s). 248 // Replace special key codes. 249 if (STRICMP(map_to, "<nop>") == 0) { // "<Nop>" means nothing 250 map_to = ""; 251 map_buf = NULL; 252 } else if (modes & MENU_TIP_MODE) { 253 map_buf = NULL; // Menu tips are plain text. 254 } else { 255 map_buf = NULL; 256 map_to = replace_termcodes(map_to, strlen(map_to), &map_buf, 0, 257 REPTERM_DO_LT, NULL, p_cpo); 258 } 259 menuarg.modes = modes; 260 menuarg.noremap[0] = noremap; 261 menuarg.silent[0] = silent; 262 add_menu_path(menu_path, &menuarg, pri_tab, map_to); 263 264 // For the PopUp menu, add a menu for each mode separately. 265 if (menu_is_popup(menu_path)) { 266 for (i = 0; i < MENU_INDEX_TIP; i++) { 267 if (modes & (1 << i)) { 268 p = popup_mode_name(menu_path, i); 269 // Include all modes, to make ":amenu" work 270 menuarg.modes = modes; 271 add_menu_path(p, &menuarg, pri_tab, map_to); 272 xfree(p); 273 } 274 } 275 } 276 277 xfree(map_buf); 278 } 279 280 ui_call_update_menu(); 281 282 theend: 283 ; 284 } 285 286 /// Add the menu with the given name to the menu hierarchy 287 /// 288 /// @param[out] menuarg menu entry 289 /// @param[] pri_tab priority table 290 /// @param[in] call_data Right hand side command 291 static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const int *const pri_tab, 292 const char *const call_data) 293 { 294 int modes = menuarg->modes; 295 vimmenu_T *menu = NULL; 296 vimmenu_T **lower_pri; 297 char *dname; 298 int pri_idx = 0; 299 int old_modes = 0; 300 char *en_name; 301 302 // Make a copy so we can stuff around with it, since it could be const 303 char *path_name = xstrdup(menu_path); 304 vimmenu_T **root_menu_ptr = get_root_menu(menu_path); 305 vimmenu_T **menup = root_menu_ptr; 306 vimmenu_T *parent = NULL; 307 char *name = path_name; 308 while (*name) { 309 // Get name of this element in the menu hierarchy, and the simplified 310 // name (without mnemonic and accelerator text). 311 char *next_name = menu_name_skip(name); 312 char *map_to = menutrans_lookup(name, (int)strlen(name)); 313 if (map_to != NULL) { 314 en_name = name; 315 name = map_to; 316 } else { 317 en_name = NULL; 318 } 319 dname = menu_text(name, NULL, NULL); 320 if (*dname == NUL) { 321 // Only a mnemonic or accelerator is not valid. 322 emsg(_("E792: Empty menu name")); 323 goto erret; 324 } 325 326 // See if it's already there 327 lower_pri = menup; 328 menu = *menup; 329 while (menu != NULL) { 330 if (menu_name_equal(name, menu) || menu_name_equal(dname, menu)) { 331 if (*next_name == NUL && menu->children != NULL) { 332 if (!sys_menu) { 333 emsg(_("E330: Menu path must not lead to a sub-menu")); 334 } 335 goto erret; 336 } 337 if (*next_name != NUL && menu->children == NULL) { 338 if (!sys_menu) { 339 emsg(_(e_notsubmenu)); 340 } 341 goto erret; 342 } 343 break; 344 } 345 menup = &menu->next; 346 347 // Count menus, to find where this one needs to be inserted. 348 // Ignore menus that are not in the menubar (PopUp and Toolbar) 349 if (parent != NULL || menu_is_menubar(menu->name)) { 350 if (menu->priority <= pri_tab[pri_idx]) { 351 lower_pri = menup; 352 } 353 } 354 menu = menu->next; 355 } 356 357 if (menu == NULL) { 358 if (*next_name == NUL && parent == NULL) { 359 emsg(_("E331: Must not add menu items directly to menu bar")); 360 goto erret; 361 } 362 363 if (menu_is_separator(dname) && *next_name != NUL) { 364 emsg(_("E332: Separator cannot be part of a menu path")); 365 goto erret; 366 } 367 368 // Not already there, so let's add it 369 menu = xcalloc(1, sizeof(vimmenu_T)); 370 371 menu->modes = modes; 372 menu->enabled = MENU_ALL_MODES; 373 menu->name = xstrdup(name); 374 // separate mnemonic and accelerator text from actual menu name 375 menu->dname = menu_text(name, &menu->mnemonic, &menu->actext); 376 if (en_name != NULL) { 377 menu->en_name = xstrdup(en_name); 378 menu->en_dname = menu_text(en_name, NULL, NULL); 379 } else { 380 menu->en_name = NULL; 381 menu->en_dname = NULL; 382 } 383 menu->priority = pri_tab[pri_idx]; 384 menu->parent = parent; 385 386 // Add after menu that has lower priority. 387 menu->next = *lower_pri; 388 *lower_pri = menu; 389 390 old_modes = 0; 391 } else { 392 old_modes = menu->modes; 393 394 // If this menu option was previously only available in other 395 // modes, then make sure it's available for this one now 396 // Also enable a menu when it's created or changed. 397 { 398 menu->modes |= modes; 399 menu->enabled |= modes; 400 } 401 } 402 403 menup = &menu->children; 404 parent = menu; 405 name = next_name; 406 XFREE_CLEAR(dname); 407 if (pri_tab[pri_idx + 1] != -1) { 408 pri_idx++; 409 } 410 } 411 xfree(path_name); 412 413 // Only add system menu items which have not been defined yet. 414 // First check if this was an ":amenu". 415 int amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) == 416 (MENU_NORMAL_MODE | MENU_INSERT_MODE)); 417 if (sys_menu) { 418 modes &= ~old_modes; 419 } 420 421 if (menu != NULL && modes) { 422 char *p = (call_data == NULL) ? NULL : xstrdup(call_data); 423 424 // loop over all modes, may add more than one 425 for (int i = 0; i < MENU_MODES; i++) { 426 if (modes & (1 << i)) { 427 // free any old menu 428 free_menu_string(menu, i); 429 430 // For "amenu", may insert an extra character. 431 // Don't do this for "<Nop>". 432 char c = 0; 433 char d = 0; 434 if (amenu && call_data != NULL && *call_data != NUL) { 435 switch (1 << i) { 436 case MENU_VISUAL_MODE: 437 case MENU_SELECT_MODE: 438 case MENU_OP_PENDING_MODE: 439 case MENU_CMDLINE_MODE: 440 c = Ctrl_C; 441 break; 442 case MENU_INSERT_MODE: 443 c = Ctrl_BSL; 444 d = Ctrl_O; 445 break; 446 } 447 } 448 449 if (c != 0) { 450 menu->strings[i] = xmalloc(strlen(call_data) + 5); 451 menu->strings[i][0] = c; 452 if (d == 0) { 453 STRCPY(menu->strings[i] + 1, call_data); 454 } else { 455 menu->strings[i][1] = d; 456 STRCPY(menu->strings[i] + 2, call_data); 457 } 458 if (c == Ctrl_C) { 459 int len = (int)strlen(menu->strings[i]); 460 461 menu->strings[i][len] = Ctrl_BSL; 462 menu->strings[i][len + 1] = Ctrl_G; 463 menu->strings[i][len + 2] = NUL; 464 } 465 } else { 466 menu->strings[i] = p; 467 } 468 menu->noremap[i] = menuarg->noremap[0]; 469 menu->silent[i] = menuarg->silent[0]; 470 } 471 } 472 } 473 return OK; 474 475 erret: 476 xfree(path_name); 477 xfree(dname); 478 479 // Delete any empty submenu we added before discovering the error. Repeat 480 // for higher levels. 481 while (parent != NULL && parent->children == NULL) { 482 if (parent->parent == NULL) { 483 menup = root_menu_ptr; 484 } else { 485 menup = &parent->parent->children; 486 } 487 for (; *menup != NULL && *menup != parent; menup = &((*menup)->next)) {} 488 if (*menup == NULL) { // safety check 489 break; 490 } 491 parent = parent->parent; 492 free_menu(menup); 493 } 494 return FAIL; 495 } 496 497 // Set the (sub)menu with the given name to enabled or disabled. 498 // Called recursively. 499 static int menu_enable_recurse(vimmenu_T *menu, char *name, int modes, int enable) 500 { 501 if (menu == NULL) { 502 return OK; // Got to bottom of hierarchy 503 } 504 // Get name of this element in the menu hierarchy 505 char *p = menu_name_skip(name); 506 507 // Find the menu 508 while (menu != NULL) { 509 if (*name == NUL || *name == '*' || menu_name_equal(name, menu)) { 510 if (*p != NUL) { 511 if (menu->children == NULL) { 512 emsg(_(e_notsubmenu)); 513 return FAIL; 514 } 515 if (menu_enable_recurse(menu->children, p, modes, enable) == FAIL) { 516 return FAIL; 517 } 518 } else if (enable) { 519 menu->enabled |= modes; 520 } else { 521 menu->enabled &= ~modes; 522 } 523 524 // When name is empty, we are doing all menu items for the given 525 // modes, so keep looping, otherwise we are just doing the named 526 // menu item (which has been found) so break here. 527 if (*name != NUL && *name != '*') { 528 break; 529 } 530 } 531 menu = menu->next; 532 } 533 if (*name != NUL && *name != '*' && menu == NULL) { 534 semsg(_(e_nomenu), name); 535 return FAIL; 536 } 537 538 return OK; 539 } 540 541 /// Remove the (sub)menu with the given name from the menu hierarchy 542 /// Called recursively. 543 /// 544 /// @param silent don't give error messages 545 static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent) 546 { 547 vimmenu_T *menu; 548 549 if (*menup == NULL) { 550 return OK; // Got to bottom of hierarchy 551 } 552 // Get name of this element in the menu hierarchy 553 char *p = menu_name_skip(name); 554 555 // Find the menu 556 while ((menu = *menup) != NULL) { 557 if (*name == NUL || menu_name_equal(name, menu)) { 558 if (*p != NUL && menu->children == NULL) { 559 if (!silent) { 560 emsg(_(e_notsubmenu)); 561 } 562 return FAIL; 563 } 564 if ((menu->modes & modes) != 0x0) { 565 if (remove_menu(&menu->children, p, modes, silent) == FAIL) { 566 return FAIL; 567 } 568 } else if (*name != NUL) { 569 if (!silent) { 570 emsg(_(e_menu_only_exists_in_another_mode)); 571 } 572 return FAIL; 573 } 574 575 // When name is empty, we are removing all menu items for the given 576 // modes, so keep looping, otherwise we are just removing the named 577 // menu item (which has been found) so break here. 578 if (*name != NUL) { 579 break; 580 } 581 582 // Remove the menu item for the given mode[s]. If the menu item 583 // is no longer valid in ANY mode, delete it 584 menu->modes &= ~modes; 585 if (modes & MENU_TIP_MODE) { 586 free_menu_string(menu, MENU_INDEX_TIP); 587 } 588 if ((menu->modes & MENU_ALL_MODES) == 0) { 589 free_menu(menup); 590 } else { 591 menup = &menu->next; 592 } 593 } else { 594 menup = &menu->next; 595 } 596 } 597 if (*name != NUL) { 598 if (menu == NULL) { 599 if (!silent) { 600 semsg(_(e_nomenu), name); 601 } 602 return FAIL; 603 } 604 605 // Recalculate modes for menu based on the new updated children 606 menu->modes &= ~modes; 607 vimmenu_T *child = menu->children; 608 for (; child != NULL; child = child->next) { 609 menu->modes |= child->modes; 610 } 611 if (modes & MENU_TIP_MODE) { 612 free_menu_string(menu, MENU_INDEX_TIP); 613 } 614 if ((menu->modes & MENU_ALL_MODES) == 0) { 615 // The menu item is no longer valid in ANY mode, so delete it 616 *menup = menu; 617 free_menu(menup); 618 } 619 } 620 621 return OK; 622 } 623 624 // Free the given menu structure and remove it from the linked list. 625 static void free_menu(vimmenu_T **menup) 626 { 627 vimmenu_T *menu = *menup; 628 629 *menup = menu->next; 630 xfree(menu->name); 631 xfree(menu->dname); 632 xfree(menu->en_name); 633 xfree(menu->en_dname); 634 xfree(menu->actext); 635 for (int i = 0; i < MENU_MODES; i++) { 636 free_menu_string(menu, i); 637 } 638 xfree(menu); 639 } 640 641 // Free the menu->string with the given index. 642 static void free_menu_string(vimmenu_T *menu, int idx) 643 { 644 int count = 0; 645 646 for (int i = 0; i < MENU_MODES; i++) { 647 if (menu->strings[i] == menu->strings[idx]) { 648 count++; 649 } 650 } 651 if (count == 1) { 652 xfree(menu->strings[idx]); 653 } 654 menu->strings[idx] = NULL; 655 } 656 657 /// Export menus 658 /// 659 /// @param[in] menu if null, starts from root_menu 660 /// @param modes, a choice of \ref MENU_MODES 661 /// @return dict with name/commands 662 /// @see show_menus_recursive 663 /// @see menu_get 664 static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) 665 { 666 if (!menu || (menu->modes & modes) == 0x0) { 667 return NULL; 668 } 669 670 dict_T *dict = tv_dict_alloc(); 671 tv_dict_add_str(dict, S_LEN("name"), menu->dname); 672 tv_dict_add_nr(dict, S_LEN("priority"), menu->priority); 673 tv_dict_add_nr(dict, S_LEN("hidden"), menu_is_hidden(menu->dname)); 674 675 if (menu->mnemonic) { 676 char buf[MB_MAXCHAR + 1] = { 0 }; // > max value of utf8_char2bytes 677 utf_char2bytes(menu->mnemonic, buf); 678 tv_dict_add_str(dict, S_LEN("shortcut"), buf); 679 } 680 681 if (menu->actext) { 682 tv_dict_add_str(dict, S_LEN("actext"), menu->actext); 683 } 684 685 if (menu->modes & MENU_TIP_MODE && menu->strings[MENU_INDEX_TIP]) { 686 tv_dict_add_str(dict, S_LEN("tooltip"), 687 menu->strings[MENU_INDEX_TIP]); 688 } 689 690 if (!menu->children) { 691 // leaf menu 692 dict_T *commands = tv_dict_alloc(); 693 tv_dict_add_dict(dict, S_LEN("mappings"), commands); 694 695 for (int bit = 0; bit < MENU_MODES; bit++) { 696 if ((menu->modes & modes & (1 << bit)) != 0) { 697 dict_T *impl = tv_dict_alloc(); 698 tv_dict_add_allocated_str(impl, S_LEN("rhs"), 699 str2special_save(menu->strings[bit], false, false)); 700 tv_dict_add_nr(impl, S_LEN("silent"), menu->silent[bit]); 701 tv_dict_add_nr(impl, S_LEN("enabled"), 702 (menu->enabled & (1 << bit)) ? 1 : 0); 703 tv_dict_add_nr(impl, S_LEN("noremap"), 704 (menu->noremap[bit] & REMAP_NONE) ? 1 : 0); 705 tv_dict_add_nr(impl, S_LEN("sid"), 706 (menu->noremap[bit] & REMAP_SCRIPT) ? 1 : 0); 707 tv_dict_add_dict(commands, menu_mode_chars[bit], 1, impl); 708 } 709 } 710 } else { 711 // visit recursively all children 712 list_T *const children_list = tv_list_alloc(kListLenMayKnow); 713 for (menu = menu->children; menu != NULL; menu = menu->next) { 714 dict_T *d = menu_get_recursive(menu, modes); 715 if (tv_dict_len(d) > 0) { 716 tv_list_append_dict(children_list, d); 717 } 718 } 719 tv_dict_add_list(dict, S_LEN("submenus"), children_list); 720 } 721 return dict; 722 } 723 724 /// Export menus matching path \p path_name 725 /// 726 /// @param path_name 727 /// @param modes supported modes, see \ref MENU_MODES 728 /// @param[in,out] list must be allocated 729 /// @return false if could not find path_name 730 bool menu_get(char *const path_name, int modes, list_T *list) 731 { 732 vimmenu_T *menu = *get_root_menu(path_name); 733 if (*path_name != NUL) { 734 menu = find_menu(menu, path_name, modes); 735 if (!menu) { 736 return false; 737 } 738 } 739 for (; menu != NULL; menu = menu->next) { 740 dict_T *d = menu_get_recursive(menu, modes); 741 if (d && tv_dict_len(d) > 0) { 742 tv_list_append_dict(list, d); 743 } 744 if (*path_name != NUL) { 745 // If a (non-empty) path query was given, only the first node in the 746 // find_menu() result is relevant. Else we want all nodes. 747 break; 748 } 749 } 750 return true; 751 } 752 753 /// Find menu matching `name` and `modes`. Does not handle empty `name`. 754 /// 755 /// @param menu top menu to start looking from 756 /// @param path_name path towards the menu 757 /// @return found menu or NULL 758 static vimmenu_T *find_menu(vimmenu_T *menu, const char *path_name, int modes) 759 { 760 assert(*path_name); 761 char *const saved_name = xstrdup(path_name); 762 char *name = saved_name; 763 764 while (*name) { 765 // find the end of one dot-separated name and put a NUL at the dot 766 char *p = menu_name_skip(name); 767 while (menu != NULL) { 768 if (menu_name_equal(name, menu)) { 769 // Found menu 770 if (*p != NUL && menu->children == NULL) { 771 emsg(_(e_notsubmenu)); 772 menu = NULL; 773 goto theend; 774 } else if ((menu->modes & modes) == 0x0) { 775 emsg(_(e_menu_only_exists_in_another_mode)); 776 menu = NULL; 777 goto theend; 778 } else if (*p == NUL) { // found a full match 779 goto theend; 780 } 781 break; 782 } 783 menu = menu->next; 784 } 785 786 if (menu == NULL) { 787 semsg(_(e_nomenu), name); 788 break; 789 } 790 // Found a match, search the sub-menu. 791 name = p; 792 assert(*name); 793 menu = menu->children; 794 } 795 796 theend: 797 xfree(saved_name); 798 return menu; 799 } 800 801 /// Show the mapping associated with a menu item or hierarchy in a sub-menu. 802 static int show_menus(char *const path_name, int modes) 803 { 804 vimmenu_T *menu = NULL; 805 if (*path_name != NUL) { 806 // First, find the (sub)menu with the given name 807 menu = find_menu(*get_root_menu(path_name), path_name, modes); 808 if (menu == NULL) { 809 return FAIL; 810 } 811 } 812 813 // make sure the list of menus doesn't change while listing them 814 menus_locked++; 815 816 // list the matching menu mappings 817 msg_puts_title(_("\n--- Menus ---")); 818 show_menus_recursive(menu, modes, 0); 819 820 menus_locked--; 821 return OK; 822 } 823 824 /// Recursively show the mappings associated with the menus under the given one 825 static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) 826 { 827 if (menu != NULL && (menu->modes & modes) == 0x0) { 828 return; 829 } 830 831 if (menu != NULL) { 832 msg_putchar('\n'); 833 if (got_int) { // "q" hit for "--more--" 834 return; 835 } 836 for (int i = 0; i < depth; i++) { 837 msg_puts(" "); 838 } 839 if (menu->priority) { 840 msg_outnum(menu->priority); 841 msg_puts(" "); 842 } 843 // Same highlighting as for directories!? 844 msg_outtrans(menu->name, HLF_D, false); 845 } 846 847 if (menu != NULL && menu->children == NULL) { 848 for (int bit = 0; bit < MENU_MODES; bit++) { 849 if ((menu->modes & modes & (1 << bit)) != 0) { 850 msg_putchar('\n'); 851 if (got_int) { // "q" hit for "--more--" 852 return; 853 } 854 for (int i = 0; i < depth + 2; i++) { 855 msg_puts(" "); 856 } 857 msg_puts(menu_mode_chars[bit]); 858 if (menu->noremap[bit] == REMAP_NONE) { 859 msg_putchar('*'); 860 } else if (menu->noremap[bit] == REMAP_SCRIPT) { 861 msg_putchar('&'); 862 } else { 863 msg_putchar(' '); 864 } 865 if (menu->silent[bit]) { 866 msg_putchar('s'); 867 } else { 868 msg_putchar(' '); 869 } 870 if ((menu->modes & menu->enabled & (1 << bit)) == 0) { 871 msg_putchar('-'); 872 } else { 873 msg_putchar(' '); 874 } 875 msg_puts(" "); 876 if (*menu->strings[bit] == NUL) { 877 msg_puts_hl("<Nop>", HLF_8, false); 878 } else { 879 msg_outtrans_special(menu->strings[bit], false, 0); 880 } 881 } 882 } 883 } else { 884 if (menu == NULL) { 885 menu = root_menu; 886 depth--; 887 } else { 888 menu = menu->children; 889 } 890 891 // recursively show all children. Skip PopUp[nvoci]. 892 for (; menu != NULL && !got_int; menu = menu->next) { 893 if (!menu_is_hidden(menu->dname)) { 894 show_menus_recursive(menu, modes, depth + 1); 895 } 896 } 897 } 898 } 899 900 // Used when expanding menu names. 901 static vimmenu_T *expand_menu = NULL; 902 static int expand_modes = 0x0; 903 static int expand_emenu; // true for ":emenu" command 904 905 // Work out what to complete when doing command line completion of menu names. 906 char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool forceit) 907 FUNC_ATTR_NONNULL_ALL 908 { 909 char *after_dot; 910 char *p; 911 char *path_name = NULL; 912 bool unmenu; 913 vimmenu_T *menu; 914 915 xp->xp_context = EXPAND_UNSUCCESSFUL; 916 917 // Check for priority numbers, enable and disable 918 for (p = arg; *p; p++) { 919 if (!ascii_isdigit(*p) && *p != '.') { 920 break; 921 } 922 } 923 924 if (!ascii_iswhite(*p)) { 925 if (strncmp(arg, "enable", 6) == 0 926 && (arg[6] == NUL || ascii_iswhite(arg[6]))) { 927 p = arg + 6; 928 } else if (strncmp(arg, "disable", 7) == 0 929 && (arg[7] == NUL || ascii_iswhite(arg[7]))) { 930 p = arg + 7; 931 } else { 932 p = arg; 933 } 934 } 935 936 while (*p != NUL && ascii_iswhite(*p)) { 937 p++; 938 } 939 940 arg = after_dot = p; 941 942 for (; *p && !ascii_iswhite(*p); p++) { 943 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL) { 944 p++; 945 } else if (*p == '.') { 946 after_dot = p + 1; 947 } 948 } 949 950 // ":popup" only uses menus, not entries 951 int expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p'); 952 expand_emenu = (*cmd == 'e'); 953 if (expand_menus && ascii_iswhite(*p)) { 954 return NULL; // TODO(vim): check for next command? 955 } 956 if (*p == NUL) { // Complete the menu name 957 // With :unmenu, you only want to match menus for the appropriate mode. 958 // With :menu though you might want to add a menu with the same name as 959 // one in another mode, so match menus from other modes too. 960 expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu); 961 if (!unmenu) { 962 expand_modes = MENU_ALL_MODES; 963 } 964 965 menu = root_menu; 966 if (after_dot > arg) { 967 size_t path_len = (size_t)(after_dot - arg); 968 path_name = xmalloc(path_len); 969 xstrlcpy(path_name, arg, path_len); 970 } 971 char *name = path_name; 972 while (name != NULL && *name) { 973 p = menu_name_skip(name); 974 while (menu != NULL) { 975 if (menu_name_equal(name, menu)) { 976 // Found menu 977 if ((*p != NUL && menu->children == NULL) 978 || ((menu->modes & expand_modes) == 0x0)) { 979 // Menu path continues, but we have reached a leaf. 980 // Or menu exists only in another mode. 981 xfree(path_name); 982 return NULL; 983 } 984 break; 985 } 986 menu = menu->next; 987 } 988 if (menu == NULL) { 989 // No menu found with the name we were looking for 990 xfree(path_name); 991 return NULL; 992 } 993 name = p; 994 menu = menu->children; 995 } 996 xfree(path_name); 997 998 xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS; 999 xp->xp_pattern = after_dot; 1000 expand_menu = menu; 1001 } else { // We're in the mapping part 1002 xp->xp_context = EXPAND_NOTHING; 1003 } 1004 return NULL; 1005 } 1006 1007 // Function given to ExpandGeneric() to obtain the list of (sub)menus (not 1008 // entries). 1009 char *get_menu_name(expand_T *xp, int idx) 1010 { 1011 static vimmenu_T *menu = NULL; 1012 char *str; 1013 static bool should_advance = false; 1014 1015 if (idx == 0) { // first call: start at first item 1016 menu = expand_menu; 1017 should_advance = false; 1018 } 1019 1020 // Skip PopUp[nvoci]. 1021 while (menu != NULL && (menu_is_hidden(menu->dname) 1022 || menu_is_separator(menu->dname) 1023 || menu->children == NULL)) { 1024 menu = menu->next; 1025 } 1026 1027 if (menu == NULL) { // at end of linked list 1028 return NULL; 1029 } 1030 1031 if (menu->modes & expand_modes) { 1032 if (should_advance) { 1033 str = menu->en_dname; 1034 } else { 1035 str = menu->dname; 1036 if (menu->en_dname == NULL) { 1037 should_advance = true; 1038 } 1039 } 1040 } else { 1041 str = ""; 1042 } 1043 1044 if (should_advance) { 1045 // Advance to next menu entry. 1046 menu = menu->next; 1047 } 1048 1049 should_advance = !should_advance; 1050 1051 return str; 1052 } 1053 1054 // Function given to ExpandGeneric() to obtain the list of menus and menu 1055 // entries. 1056 char *get_menu_names(expand_T *xp, int idx) 1057 { 1058 static vimmenu_T *menu = NULL; 1059 #define TBUFFER_LEN 256 1060 static char tbuffer[TBUFFER_LEN]; // hack 1061 char *str; 1062 static bool should_advance = false; 1063 1064 if (idx == 0) { // first call: start at first item 1065 menu = expand_menu; 1066 should_advance = false; 1067 } 1068 1069 // Skip Browse-style entries, popup menus and separators. 1070 while (menu != NULL 1071 && (menu_is_hidden(menu->dname) 1072 || (expand_emenu && menu_is_separator(menu->dname)) 1073 || menu->dname[strlen(menu->dname) - 1] == '.')) { 1074 menu = menu->next; 1075 } 1076 1077 if (menu == NULL) { // at end of linked list 1078 return NULL; 1079 } 1080 1081 if (menu->modes & expand_modes) { 1082 if (menu->children != NULL) { 1083 if (should_advance) { 1084 xstrlcpy(tbuffer, menu->en_dname, TBUFFER_LEN); 1085 } else { 1086 xstrlcpy(tbuffer, menu->dname, TBUFFER_LEN); 1087 if (menu->en_dname == NULL) { 1088 should_advance = true; 1089 } 1090 } 1091 // hack on menu separators: use a 'magic' char for the separator 1092 // so that '.' in names gets escaped properly 1093 strcat(tbuffer, "\001"); 1094 str = tbuffer; 1095 } else { 1096 if (should_advance) { 1097 str = menu->en_dname; 1098 } else { 1099 str = menu->dname; 1100 if (menu->en_dname == NULL) { 1101 should_advance = true; 1102 } 1103 } 1104 } 1105 } else { 1106 str = ""; 1107 } 1108 1109 if (should_advance) { 1110 // Advance to next menu entry. 1111 menu = menu->next; 1112 } 1113 1114 should_advance = !should_advance; 1115 1116 return str; 1117 } 1118 1119 /// Skip over this element of the menu path and return the start of the next 1120 /// element. Any \ and ^Vs are removed from the current element. 1121 /// 1122 /// @param name may be modified. 1123 /// @return start of the next element 1124 static char *menu_name_skip(char *const name) 1125 { 1126 char *p; 1127 1128 for (p = name; *p && *p != '.'; MB_PTR_ADV(p)) { 1129 if (*p == '\\' || *p == Ctrl_V) { 1130 STRMOVE(p, p + 1); 1131 if (*p == NUL) { 1132 break; 1133 } 1134 } 1135 } 1136 if (*p) { 1137 *p++ = NUL; 1138 } 1139 return p; 1140 } 1141 1142 /// Return true when "name" matches with menu "menu". The name is compared in 1143 /// two ways: raw menu name and menu name without '&'. ignore part after a TAB. 1144 static bool menu_name_equal(const char *const name, const vimmenu_T *const menu) 1145 { 1146 if (menu->en_name != NULL 1147 && (menu_namecmp(name, menu->en_name) 1148 || menu_namecmp(name, menu->en_dname))) { 1149 return true; 1150 } 1151 return menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname); 1152 } 1153 1154 static bool menu_namecmp(const char *const name, const char *const mname) 1155 { 1156 int i; 1157 1158 for (i = 0; name[i] != NUL && name[i] != TAB; i++) { 1159 if (name[i] != mname[i]) { 1160 break; 1161 } 1162 } 1163 return (name[i] == NUL || name[i] == TAB) 1164 && (mname[i] == NUL || mname[i] == TAB); 1165 } 1166 1167 /// Returns the \ref MENU_MODES specified by menu command `cmd`. 1168 /// (eg :menu! returns MENU_CMDLINE_MODE | MENU_INSERT_MODE) 1169 /// 1170 /// @param[in] cmd string like "nmenu", "vmenu", etc. 1171 /// @param[in] forceit bang (!) was given after the command 1172 /// @param[out] noremap If not NULL, the flag it points to is set according 1173 /// to whether the command is a "nore" command. 1174 /// @param[out] unmenu If not NULL, the flag it points to is set according 1175 /// to whether the command is an "unmenu" command. 1176 int get_menu_cmd_modes(const char *cmd, bool forceit, int *noremap, bool *unmenu) 1177 { 1178 int modes; 1179 1180 switch (*cmd++) { 1181 case 'v': // vmenu, vunmenu, vnoremenu 1182 modes = MENU_VISUAL_MODE | MENU_SELECT_MODE; 1183 break; 1184 case 'x': // xmenu, xunmenu, xnoremenu 1185 modes = MENU_VISUAL_MODE; 1186 break; 1187 case 's': // smenu, sunmenu, snoremenu 1188 modes = MENU_SELECT_MODE; 1189 break; 1190 case 'o': // omenu 1191 modes = MENU_OP_PENDING_MODE; 1192 break; 1193 case 'i': // imenu 1194 modes = MENU_INSERT_MODE; 1195 break; 1196 case 't': 1197 if (*cmd == 'l') { // tlmenu, tlunmenu, tlnoremenu 1198 modes = MENU_TERMINAL_MODE; 1199 cmd++; 1200 break; 1201 } 1202 modes = MENU_TIP_MODE; // tmenu 1203 break; 1204 case 'c': // cmenu 1205 modes = MENU_CMDLINE_MODE; 1206 break; 1207 case 'a': // amenu 1208 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE 1209 | MENU_VISUAL_MODE | MENU_SELECT_MODE 1210 | MENU_OP_PENDING_MODE; 1211 break; 1212 case 'n': 1213 if (*cmd != 'o') { // nmenu, not noremenu 1214 modes = MENU_NORMAL_MODE; 1215 break; 1216 } 1217 FALLTHROUGH; 1218 default: 1219 cmd--; 1220 if (forceit) { 1221 // menu!! 1222 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE; 1223 } else { 1224 // menu 1225 modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE 1226 | MENU_OP_PENDING_MODE; 1227 } 1228 } 1229 1230 if (noremap != NULL) { 1231 *noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES); 1232 } 1233 if (unmenu != NULL) { 1234 *unmenu = (*cmd == 'u'); 1235 } 1236 return modes; 1237 } 1238 1239 /// Return the string representation of the menu modes. Does the opposite 1240 /// of get_menu_cmd_modes(). 1241 static char *get_menu_mode_str(int modes) 1242 { 1243 if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE | 1244 MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE)) 1245 == (MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE | 1246 MENU_VISUAL_MODE | MENU_SELECT_MODE | MENU_OP_PENDING_MODE)) { 1247 return "a"; 1248 } 1249 if ((modes & (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE | 1250 MENU_OP_PENDING_MODE)) 1251 == (MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE | 1252 MENU_OP_PENDING_MODE)) { 1253 return " "; 1254 } 1255 if ((modes & (MENU_INSERT_MODE | MENU_CMDLINE_MODE)) 1256 == (MENU_INSERT_MODE | MENU_CMDLINE_MODE)) { 1257 return "!"; 1258 } 1259 if ((modes & (MENU_VISUAL_MODE | MENU_SELECT_MODE)) 1260 == (MENU_VISUAL_MODE | MENU_SELECT_MODE)) { 1261 return "v"; 1262 } 1263 if (modes & MENU_VISUAL_MODE) { 1264 return "x"; 1265 } 1266 if (modes & MENU_SELECT_MODE) { 1267 return "s"; 1268 } 1269 if (modes & MENU_OP_PENDING_MODE) { 1270 return "o"; 1271 } 1272 if (modes & MENU_INSERT_MODE) { 1273 return "i"; 1274 } 1275 if (modes & MENU_TERMINAL_MODE) { 1276 return "tl"; 1277 } 1278 if (modes & MENU_CMDLINE_MODE) { 1279 return "c"; 1280 } 1281 if (modes & MENU_NORMAL_MODE) { 1282 return "n"; 1283 } 1284 if (modes & MENU_TIP_MODE) { 1285 return "t"; 1286 } 1287 1288 return ""; 1289 } 1290 1291 // Modify a menu name starting with "PopUp" to include the mode character. 1292 // Returns the name in allocated memory. 1293 static char *popup_mode_name(char *name, int idx) 1294 { 1295 size_t len = strlen(name); 1296 assert(len >= 4); 1297 1298 char *mode_chars = menu_mode_chars[idx]; 1299 size_t mode_chars_len = strlen(mode_chars); 1300 char *p = xstrnsave(name, len + mode_chars_len); 1301 memmove(p + 5 + mode_chars_len, p + 5, len - 4); 1302 for (size_t i = 0; i < mode_chars_len; i++) { 1303 p[5 + i] = menu_mode_chars[idx][i]; 1304 } 1305 1306 return p; 1307 } 1308 1309 /// Duplicate the menu item text and then process to see if a mnemonic key 1310 /// and/or accelerator text has been identified. 1311 /// 1312 /// @param str The menu item text. 1313 /// @param[out] mnemonic If non-NULL, *mnemonic is set to the character after 1314 /// the first '&'. 1315 /// @param[out] actext If non-NULL, *actext is set to the text after the first 1316 /// TAB, but only if a TAB was found. Memory pointed to is newly 1317 /// allocated. 1318 /// 1319 /// @return a pointer to allocated memory. 1320 static char *menu_text(const char *str, int *mnemonic, char **actext) 1321 FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT 1322 FUNC_ATTR_NONNULL_ARG(1) 1323 { 1324 char *text; 1325 1326 // Locate accelerator text, after the first TAB 1327 char *p = vim_strchr(str, TAB); 1328 if (p != NULL) { 1329 if (actext != NULL) { 1330 *actext = xstrdup(p + 1); 1331 } 1332 assert(p >= str); 1333 text = xmemdupz(str, (size_t)(p - str)); 1334 } else { 1335 text = xstrdup(str); 1336 } 1337 1338 // Find mnemonic characters "&a" and reduce "&&" to "&". 1339 for (p = text; p != NULL;) { 1340 p = vim_strchr(p, '&'); 1341 if (p != NULL) { 1342 if (p[1] == NUL) { // trailing "&" 1343 break; 1344 } 1345 if (mnemonic != NULL && p[1] != '&') { 1346 *mnemonic = (uint8_t)p[1]; 1347 } 1348 STRMOVE(p, p + 1); 1349 p = p + 1; 1350 } 1351 } 1352 return text; 1353 } 1354 1355 // Return true if "name" can be a menu in the MenuBar. 1356 bool menu_is_menubar(const char *const name) 1357 FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL 1358 { 1359 return !menu_is_popup(name) 1360 && !menu_is_toolbar(name) 1361 && !menu_is_winbar(name) 1362 && *name != MNU_HIDDEN_CHAR; 1363 } 1364 1365 // Return true if "name" is a popup menu name. 1366 bool menu_is_popup(const char *const name) 1367 FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL 1368 { 1369 return strncmp(name, "PopUp", 5) == 0; 1370 } 1371 1372 // Return true if "name" is a toolbar menu name. 1373 bool menu_is_toolbar(const char *const name) 1374 FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL 1375 { 1376 return strncmp(name, "ToolBar", 7) == 0; 1377 } 1378 1379 /// @return true if the name is a menu separator identifier: Starts and ends 1380 /// with '-' 1381 bool menu_is_separator(char *name) 1382 { 1383 return name[0] == '-' && name[strlen(name) - 1] == '-'; 1384 } 1385 1386 /// True if a popup menu or starts with \ref MNU_HIDDEN_CHAR 1387 /// 1388 /// @return true if the menu is hidden 1389 static bool menu_is_hidden(char *name) 1390 { 1391 return (name[0] == MNU_HIDDEN_CHAR) 1392 || (menu_is_popup(name) && name[5] != NUL); 1393 } 1394 1395 static int get_menu_mode(void) 1396 { 1397 if (State & MODE_TERMINAL) { 1398 return MENU_INDEX_TERMINAL; 1399 } 1400 if (VIsual_active) { 1401 if (VIsual_select) { 1402 return MENU_INDEX_SELECT; 1403 } 1404 return MENU_INDEX_VISUAL; 1405 } 1406 if (State & MODE_INSERT) { 1407 return MENU_INDEX_INSERT; 1408 } 1409 if ((State & MODE_CMDLINE) || State == MODE_ASKMORE || State == MODE_HITRETURN) { 1410 return MENU_INDEX_CMDLINE; 1411 } 1412 if (finish_op) { 1413 return MENU_INDEX_OP_PENDING; 1414 } 1415 if (State & MODE_NORMAL) { 1416 return MENU_INDEX_NORMAL; 1417 } 1418 if (State & MODE_LANGMAP) { // must be a "r" command, like Insert mode 1419 return MENU_INDEX_INSERT; 1420 } 1421 return MENU_INDEX_INVALID; 1422 } 1423 1424 int get_menu_mode_flag(void) 1425 { 1426 int mode = get_menu_mode(); 1427 1428 if (mode == MENU_INDEX_INVALID) { 1429 return 0; 1430 } 1431 return 1 << mode; 1432 } 1433 1434 /// Display the Special "PopUp" menu as a pop-up at the current mouse 1435 /// position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode, 1436 /// etc. 1437 void show_popupmenu(void) 1438 { 1439 int menu_mode = get_menu_mode(); 1440 if (menu_mode == MENU_INDEX_INVALID) { 1441 return; 1442 } 1443 char *mode = menu_mode_chars[menu_mode]; 1444 size_t mode_len = strlen(mode); 1445 1446 apply_autocmds(EVENT_MENUPOPUP, mode, NULL, false, curbuf); 1447 1448 vimmenu_T *menu; 1449 1450 for (menu = root_menu; menu != NULL; menu = menu->next) { 1451 if (strncmp("PopUp", menu->name, 5) == 0 && strncmp(menu->name + 5, mode, mode_len) == 0) { 1452 break; 1453 } 1454 } 1455 1456 // Only show a popup when it is defined and has entries 1457 if (menu == NULL || menu->children == NULL) { 1458 return; 1459 } 1460 1461 pum_show_popupmenu(menu); 1462 } 1463 1464 /// Execute "menu". Use by ":emenu" and the window toolbar. 1465 /// @param eap NULL for the window toolbar. 1466 /// @param mode_idx specify a MENU_INDEX_ value, 1467 /// use MENU_INDEX_INVALID to depend on the current state 1468 void execute_menu(const exarg_T *eap, vimmenu_T *menu, int mode_idx) 1469 FUNC_ATTR_NONNULL_ARG(2) 1470 { 1471 int idx = mode_idx; 1472 1473 if (idx < 0) { 1474 // Use the Insert mode entry when returning to Insert mode. 1475 if (((State & MODE_INSERT) || restart_edit) && current_sctx.sc_sid == 0) { 1476 idx = MENU_INDEX_INSERT; 1477 } else if (State & MODE_CMDLINE) { 1478 idx = MENU_INDEX_CMDLINE; 1479 } else if (State & MODE_TERMINAL) { 1480 idx = MENU_INDEX_TERMINAL; 1481 } else if (get_real_state() & MODE_VISUAL) { 1482 // Detect real visual mode -- if we are really in visual mode we 1483 // don't need to do any guesswork to figure out what the selection 1484 // is. Just execute the visual binding for the menu. 1485 idx = MENU_INDEX_VISUAL; 1486 } else if (eap != NULL && eap->addr_count) { 1487 pos_T tpos; 1488 1489 idx = MENU_INDEX_VISUAL; 1490 1491 // GEDDES: This is not perfect - but it is a 1492 // quick way of detecting whether we are doing this from a 1493 // selection - see if the range matches up with the visual 1494 // select start and end. 1495 if ((curbuf->b_visual.vi_start.lnum == eap->line1) 1496 && (curbuf->b_visual.vi_end.lnum) == eap->line2) { 1497 // Set it up for visual mode - equivalent to gv. 1498 VIsual_mode = curbuf->b_visual.vi_mode; 1499 tpos = curbuf->b_visual.vi_end; 1500 curwin->w_cursor = curbuf->b_visual.vi_start; 1501 curwin->w_curswant = curbuf->b_visual.vi_curswant; 1502 } else { 1503 // Set it up for line-wise visual mode 1504 VIsual_mode = 'V'; 1505 curwin->w_cursor.lnum = eap->line1; 1506 curwin->w_cursor.col = 1; 1507 tpos.lnum = eap->line2; 1508 tpos.col = MAXCOL; 1509 tpos.coladd = 0; 1510 } 1511 1512 // Activate visual mode 1513 VIsual_active = true; 1514 VIsual_reselect = true; 1515 check_cursor(curwin); 1516 VIsual = curwin->w_cursor; 1517 curwin->w_cursor = tpos; 1518 1519 check_cursor(curwin); 1520 1521 // Adjust the cursor to make sure it is in the correct pos 1522 // for exclusive mode 1523 if (*p_sel == 'e' && gchar_cursor() != NUL) { 1524 curwin->w_cursor.col++; 1525 } 1526 } 1527 } 1528 1529 if (idx == MENU_INDEX_INVALID || eap == NULL) { 1530 idx = MENU_INDEX_NORMAL; 1531 } 1532 1533 if (menu->strings[idx] != NULL && (menu->modes & (1 << idx))) { 1534 // When executing a script or function execute the commands right now. 1535 // Also for the window toolbar 1536 // Otherwise put them in the typeahead buffer. 1537 if (eap == NULL || current_sctx.sc_sid != 0) { 1538 save_state_T save_state; 1539 1540 ex_normal_busy++; 1541 if (save_current_state(&save_state)) { 1542 exec_normal_cmd(menu->strings[idx], menu->noremap[idx], 1543 menu->silent[idx]); 1544 } 1545 restore_current_state(&save_state); 1546 ex_normal_busy--; 1547 } else { 1548 ins_typebuf(menu->strings[idx], menu->noremap[idx], 0, true, 1549 menu->silent[idx]); 1550 } 1551 } else if (eap != NULL) { 1552 char *mode; 1553 switch (idx) { 1554 case MENU_INDEX_VISUAL: 1555 mode = "Visual"; 1556 break; 1557 case MENU_INDEX_SELECT: 1558 mode = "Select"; 1559 break; 1560 case MENU_INDEX_OP_PENDING: 1561 mode = "Op-pending"; 1562 break; 1563 case MENU_INDEX_TERMINAL: 1564 mode = "Terminal"; 1565 break; 1566 case MENU_INDEX_INSERT: 1567 mode = "Insert"; 1568 break; 1569 case MENU_INDEX_CMDLINE: 1570 mode = "Cmdline"; 1571 break; 1572 // case MENU_INDEX_TIP: cannot happen 1573 default: 1574 mode = "Normal"; 1575 } 1576 semsg(_("E335: Menu not defined for %s mode"), mode); 1577 } 1578 } 1579 1580 /// Lookup a menu by the descriptor name e.g. "File.New" 1581 /// Returns NULL if the menu is not found 1582 static vimmenu_T *menu_getbyname(char *name_arg) 1583 FUNC_ATTR_NONNULL_ALL 1584 { 1585 char *saved_name = xstrdup(name_arg); 1586 vimmenu_T *menu = *get_root_menu(saved_name); 1587 char *name = saved_name; 1588 bool gave_emsg = false; 1589 while (*name) { 1590 // Find in the menu hierarchy 1591 char *p = menu_name_skip(name); 1592 1593 while (menu != NULL) { 1594 if (menu_name_equal(name, menu)) { 1595 if (*p == NUL && menu->children != NULL) { 1596 emsg(_("E333: Menu path must lead to a menu item")); 1597 gave_emsg = true; 1598 menu = NULL; 1599 } else if (*p != NUL && menu->children == NULL) { 1600 emsg(_(e_notsubmenu)); 1601 menu = NULL; 1602 } 1603 break; 1604 } 1605 menu = menu->next; 1606 } 1607 if (menu == NULL || *p == NUL) { 1608 break; 1609 } 1610 menu = menu->children; 1611 name = p; 1612 } 1613 xfree(saved_name); 1614 if (menu == NULL) { 1615 if (!gave_emsg) { 1616 semsg(_("E334: Menu not found: %s"), name_arg); 1617 } 1618 return NULL; 1619 } 1620 1621 return menu; 1622 } 1623 1624 /// Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and 1625 /// execute it. 1626 void ex_emenu(exarg_T *eap) 1627 { 1628 char *arg = eap->arg; 1629 int mode_idx = MENU_INDEX_INVALID; 1630 1631 if (arg[0] && ascii_iswhite(arg[1])) { 1632 switch (arg[0]) { 1633 case 'n': 1634 mode_idx = MENU_INDEX_NORMAL; break; 1635 case 'v': 1636 mode_idx = MENU_INDEX_VISUAL; break; 1637 case 's': 1638 mode_idx = MENU_INDEX_SELECT; break; 1639 case 'o': 1640 mode_idx = MENU_INDEX_OP_PENDING; break; 1641 case 't': 1642 mode_idx = MENU_INDEX_TERMINAL; break; 1643 case 'i': 1644 mode_idx = MENU_INDEX_INSERT; break; 1645 case 'c': 1646 mode_idx = MENU_INDEX_CMDLINE; break; 1647 default: 1648 semsg(_(e_invarg2), arg); 1649 return; 1650 } 1651 arg = skipwhite(arg + 2); 1652 } 1653 1654 vimmenu_T *menu = menu_getbyname(arg); 1655 if (menu == NULL) { 1656 return; 1657 } 1658 1659 // Found the menu, so execute. 1660 execute_menu(eap, menu, mode_idx); 1661 } 1662 1663 /// Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy. 1664 vimmenu_T *menu_find(const char *path_name) 1665 { 1666 vimmenu_T *menu = *get_root_menu(path_name); 1667 char *saved_name = xstrdup(path_name); 1668 char *name = saved_name; 1669 while (*name) { 1670 // find the end of one dot-separated name and put a NUL at the dot 1671 char *p = menu_name_skip(name); 1672 1673 while (menu != NULL) { 1674 if (menu_name_equal(name, menu)) { 1675 if (menu->children == NULL) { 1676 // found a menu item instead of a sub-menu 1677 if (*p == NUL) { 1678 emsg(_("E336: Menu path must lead to a sub-menu")); 1679 } else { 1680 emsg(_(e_notsubmenu)); 1681 } 1682 menu = NULL; 1683 goto theend; 1684 } 1685 if (*p == NUL) { // found a full match 1686 goto theend; 1687 } 1688 break; 1689 } 1690 menu = menu->next; 1691 } 1692 if (menu == NULL) { // didn't find it 1693 break; 1694 } 1695 1696 // Found a match, search the sub-menu. 1697 menu = menu->children; 1698 name = p; 1699 } 1700 1701 if (menu == NULL) { 1702 emsg(_("E337: Menu not found - check menu names")); 1703 } 1704 theend: 1705 xfree(saved_name); 1706 return menu; 1707 } 1708 1709 // Translation of menu names. Just a simple lookup table. 1710 1711 typedef struct { 1712 char *from; // English name 1713 char *from_noamp; // same, without '&' 1714 char *to; // translated name 1715 } menutrans_T; 1716 1717 static garray_T menutrans_ga = GA_EMPTY_INIT_VALUE; 1718 1719 #define FREE_MENUTRANS(mt) \ 1720 menutrans_T *_mt = (mt); \ 1721 xfree(_mt->from); \ 1722 xfree(_mt->from_noamp); \ 1723 xfree(_mt->to) 1724 1725 // ":menutrans". 1726 // This function is also defined without the +multi_lang feature, in which 1727 // case the commands are ignored. 1728 void ex_menutranslate(exarg_T *eap) 1729 { 1730 char *arg = eap->arg; 1731 1732 if (menutrans_ga.ga_itemsize == 0) { 1733 ga_init(&menutrans_ga, (int)sizeof(menutrans_T), 5); 1734 } 1735 1736 // ":menutrans clear": clear all translations. 1737 if (strncmp(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5))) { 1738 GA_DEEP_CLEAR(&menutrans_ga, menutrans_T, FREE_MENUTRANS); 1739 1740 // Delete all "menutrans_" global variables. 1741 del_menutrans_vars(); 1742 } else { 1743 // ":menutrans from to": add translation 1744 char *from = arg; 1745 arg = menu_skip_part(arg); 1746 char *to = skipwhite(arg); 1747 *arg = NUL; 1748 arg = menu_skip_part(to); 1749 if (arg == to) { 1750 emsg(_(e_invarg)); 1751 } else { 1752 from = xstrdup(from); 1753 char *from_noamp = menu_text(from, NULL, NULL); 1754 assert(arg >= to); 1755 to = xmemdupz(to, (size_t)(arg - to)); 1756 menu_translate_tab_and_shift(from); 1757 menu_translate_tab_and_shift(to); 1758 menu_unescape_name(from); 1759 menu_unescape_name(to); 1760 menutrans_T *tp = GA_APPEND_VIA_PTR(menutrans_T, &menutrans_ga); 1761 tp->from = from; 1762 tp->from_noamp = from_noamp; 1763 tp->to = to; 1764 } 1765 } 1766 } 1767 1768 // Find the character just after one part of a menu name. 1769 static char *menu_skip_part(char *p) 1770 { 1771 while (*p != NUL && *p != '.' && !ascii_iswhite(*p)) { 1772 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL) { 1773 p++; 1774 } 1775 p++; 1776 } 1777 return p; 1778 } 1779 1780 // Lookup part of a menu name in the translations. 1781 // Return a pointer to the translation or NULL if not found. 1782 static char *menutrans_lookup(char *name, int len) 1783 { 1784 menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data; 1785 1786 for (int i = 0; i < menutrans_ga.ga_len; i++) { 1787 if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL) { 1788 return tp[i].to; 1789 } 1790 } 1791 1792 // Now try again while ignoring '&' characters. 1793 char c = name[len]; 1794 name[len] = NUL; 1795 char *dname = menu_text(name, NULL, NULL); 1796 name[len] = c; 1797 for (int i = 0; i < menutrans_ga.ga_len; i++) { 1798 if (STRICMP(dname, tp[i].from_noamp) == 0) { 1799 xfree(dname); 1800 return tp[i].to; 1801 } 1802 } 1803 xfree(dname); 1804 1805 return NULL; 1806 } 1807 1808 // Unescape the name in the translate dictionary table. 1809 static void menu_unescape_name(char *name) 1810 { 1811 for (char *p = name; *p && *p != '.'; MB_PTR_ADV(p)) { 1812 if (*p == '\\') { 1813 STRMOVE(p, p + 1); 1814 } 1815 } 1816 } 1817 1818 // Isolate the menu name. 1819 // Skip the menu name, and translate <Tab> into a real TAB. 1820 static char *menu_translate_tab_and_shift(char *arg_start) 1821 { 1822 char *arg = arg_start; 1823 1824 while (*arg && !ascii_iswhite(*arg)) { 1825 if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL) { 1826 arg++; 1827 } else if (STRNICMP(arg, "<TAB>", 5) == 0) { 1828 *arg = TAB; 1829 STRMOVE(arg + 1, arg + 5); 1830 } 1831 arg++; 1832 } 1833 if (*arg != NUL) { 1834 *arg++ = NUL; 1835 } 1836 arg = skipwhite(arg); 1837 1838 return arg; 1839 } 1840 1841 /// Get the information about a menu item in mode 'which' 1842 static void menuitem_getinfo(const char *menu_name, const vimmenu_T *menu, int modes, dict_T *dict) 1843 FUNC_ATTR_NONNULL_ALL 1844 { 1845 if (*menu_name == NUL) { 1846 // Return all the top-level menus 1847 list_T *const l = tv_list_alloc(kListLenMayKnow); 1848 tv_dict_add_list(dict, S_LEN("submenus"), l); 1849 // get all the children. Skip PopUp[nvoci]. 1850 for (const vimmenu_T *topmenu = menu; topmenu != NULL; topmenu = topmenu->next) { 1851 if (!menu_is_hidden(topmenu->dname)) { 1852 tv_list_append_string(l, topmenu->dname, -1); 1853 } 1854 } 1855 return; 1856 } 1857 1858 tv_dict_add_str(dict, S_LEN("name"), menu->name); 1859 tv_dict_add_str(dict, S_LEN("display"), menu->dname); 1860 if (menu->actext != NULL) { 1861 tv_dict_add_str(dict, S_LEN("accel"), menu->actext); 1862 } 1863 tv_dict_add_nr(dict, S_LEN("priority"), menu->priority); 1864 tv_dict_add_str(dict, S_LEN("modes"), get_menu_mode_str(menu->modes)); 1865 1866 char buf[NUMBUFLEN]; 1867 buf[utf_char2bytes(menu->mnemonic, buf)] = NUL; 1868 tv_dict_add_str(dict, S_LEN("shortcut"), buf); 1869 1870 if (menu->children == NULL) { // leaf menu 1871 int bit; 1872 1873 // Get the first mode in which the menu is available 1874 for (bit = 0; (bit < MENU_MODES) && !((1 << bit) & modes); bit++) {} 1875 1876 if (bit < MENU_MODES) { // just in case, avoid Coverity warning 1877 if (menu->strings[bit] != NULL) { 1878 tv_dict_add_allocated_str(dict, S_LEN("rhs"), 1879 *menu->strings[bit] == NUL 1880 ? xstrdup("<Nop>") 1881 : str2special_save(menu->strings[bit], false, false)); 1882 } 1883 tv_dict_add_bool(dict, S_LEN("noremenu"), menu->noremap[bit] == REMAP_NONE); 1884 tv_dict_add_bool(dict, S_LEN("script"), menu->noremap[bit] == REMAP_SCRIPT); 1885 tv_dict_add_bool(dict, S_LEN("silent"), menu->silent[bit]); 1886 tv_dict_add_bool(dict, S_LEN("enabled"), (menu->enabled & (1 << bit)) != 0); 1887 } 1888 } else { 1889 // If there are submenus, add all the submenu display names 1890 list_T *const l = tv_list_alloc(kListLenMayKnow); 1891 tv_dict_add_list(dict, S_LEN("submenus"), l); 1892 const vimmenu_T *child = menu->children; 1893 while (child != NULL) { 1894 tv_list_append_string(l, child->dname, -1); 1895 child = child->next; 1896 } 1897 } 1898 } 1899 1900 /// "menu_info()" function 1901 /// Return information about a menu (including all the child menus) 1902 void f_menu_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1903 { 1904 tv_dict_alloc_ret(rettv); 1905 dict_T *const retdict = rettv->vval.v_dict; 1906 1907 const char *const menu_name = tv_get_string_chk(&argvars[0]); 1908 if (menu_name == NULL) { 1909 return; 1910 } 1911 1912 // menu mode 1913 const char *which; 1914 if (argvars[1].v_type != VAR_UNKNOWN) { 1915 which = tv_get_string_chk(&argvars[1]); 1916 } else { 1917 which = ""; // Default is modes for "menu" 1918 } 1919 if (which == NULL) { 1920 return; 1921 } 1922 1923 const int modes = get_menu_cmd_modes(which, *which == '!', NULL, NULL); 1924 1925 // Locate the specified menu or menu item 1926 const vimmenu_T *menu = *get_root_menu(menu_name); 1927 char *const saved_name = xstrdup(menu_name); 1928 if (*saved_name != NUL) { 1929 char *name = saved_name; 1930 while (*name) { 1931 // Find in the menu hierarchy 1932 char *p = menu_name_skip(name); 1933 while (menu != NULL) { 1934 if (menu_name_equal(name, menu)) { 1935 break; 1936 } 1937 menu = menu->next; 1938 } 1939 if (menu == NULL || *p == NUL) { 1940 break; 1941 } 1942 menu = menu->children; 1943 name = p; 1944 } 1945 } 1946 xfree(saved_name); 1947 1948 if (menu == NULL) { // specified menu not found 1949 return; 1950 } 1951 1952 if (menu->modes & modes) { 1953 menuitem_getinfo(menu_name, menu, modes, retdict); 1954 } 1955 }