1 /* vi:set ts=8 sts=4 sw=4: 2 * 3 * VIM - Vi IMproved by Bram Moolenaar 4 * GUI/Motif support by Robert Webb 5 * 6 * Do ":help uganda" in Vim to read copying and usage conditions. 7 * Do ":help credits" in Vim to see a list of people who contributed. 8 * See README.txt for an overview of the Vim source code. 9 */ 10 11 /* 12 * Code for menus. Used for the GUI and 'wildmenu'. 13 */ 14 15 #include "vim.h" 16 17 #if defined(FEAT_MENU) || defined(PROTO) 18 19 #define MENUDEPTH 10 /* maximum depth of menus */ 20 21 #ifdef FEAT_GUI_W32 22 static int add_menu_path __ARGS((char_u *, vimmenu_T *, int *, char_u *, int)); 23 #else 24 static int add_menu_path __ARGS((char_u *, vimmenu_T *, int *, char_u *)); 25 #endif 26 static int menu_nable_recurse __ARGS((vimmenu_T *menu, char_u *name, int modes, int enable)); 27 static int remove_menu __ARGS((vimmenu_T **, char_u *, int, int silent)); 28 static void free_menu __ARGS((vimmenu_T **menup)); 29 static void free_menu_string __ARGS((vimmenu_T *, int)); 30 static int show_menus __ARGS((char_u *, int)); 31 static void show_menus_recursive __ARGS((vimmenu_T *, int, int)); 32 static int menu_name_equal __ARGS((char_u *name, vimmenu_T *menu)); 33 static int menu_namecmp __ARGS((char_u *name, char_u *mname)); 34 static int get_menu_cmd_modes __ARGS((char_u *, int, int *, int *)); 35 static char_u *popup_mode_name __ARGS((char_u *name, int idx)); 36 static char_u *menu_text __ARGS((char_u *text, int *mnemonic, char_u **actext)); 37 #ifdef FEAT_GUI 38 static int get_menu_mode __ARGS((void)); 39 static void gui_update_menus_recurse __ARGS((vimmenu_T *, int)); 40 #endif 41 42 #if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF) 43 static void gui_create_tearoffs_recurse __ARGS((vimmenu_T *menu, const char_u *pname, int *pri_tab, int pri_idx)); 44 static void gui_add_tearoff __ARGS((char_u *tearpath, int *pri_tab, int pri_idx)); 45 static void gui_destroy_tearoffs_recurse __ARGS((vimmenu_T *menu)); 46 static int s_tearoffs = FALSE; 47 #endif 48 49 static int menu_is_hidden __ARGS((char_u *name)); 50 #if defined(FEAT_CMDL_COMPL) || (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)) 51 static int menu_is_tearoff __ARGS((char_u *name)); 52 #endif 53 54 #if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR) 55 static char_u *menu_skip_part __ARGS((char_u *p)); 56 #endif 57 #ifdef FEAT_MULTI_LANG 58 static char_u *menutrans_lookup __ARGS((char_u *name, int len)); 59 #endif 60 61 /* The character for each menu mode */ 62 static char_u menu_mode_chars[] = {'n', 'v', 'o', 'i', 'c', 't'}; 63 64 static char_u e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu"); 65 static char_u e_othermode[] = N_("E328: Menu only exists in another mode"); 66 static char_u e_nomenu[] = N_("E329: No menu \"%s\""); 67 68 #ifdef FEAT_TOOLBAR 69 static const char *toolbar_names[] = 70 { 71 /* 0 */ "New", "Open", "Save", "Undo", "Redo", 72 /* 5 */ "Cut", "Copy", "Paste", "Print", "Help", 73 /* 10 */ "Find", "SaveAll", "SaveSesn", "NewSesn", "LoadSesn", 74 /* 15 */ "RunScript", "Replace", "WinClose", "WinMax", "WinMin", 75 /* 20 */ "WinSplit", "Shell", "FindPrev", "FindNext", "FindHelp", 76 /* 25 */ "Make", "TagJump", "RunCtags", "WinVSplit", "WinMaxWidth", 77 /* 30 */ "WinMinWidth", "Exit" 78 }; 79 # define TOOLBAR_NAME_COUNT (sizeof(toolbar_names) / sizeof(char *)) 80 #endif 81 82 /* 83 * Do the :menu command and relatives. 84 */ 85 void 86 ex_menu(eap) 87 exarg_T *eap; /* Ex command arguments */ 88 { 89 char_u *menu_path; 90 int modes; 91 char_u *map_to; 92 int noremap; 93 int silent = FALSE; 94 int unmenu; 95 char_u *map_buf; 96 char_u *arg; 97 char_u *p; 98 int i; 99 #if defined(FEAT_GUI) && !defined(FEAT_GUI_GTK) 100 int old_menu_height; 101 # if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32) && !defined(FEAT_GUI_W16) 102 int old_toolbar_height; 103 # endif 104 #endif 105 int pri_tab[MENUDEPTH + 1]; 106 int enable = MAYBE; /* TRUE for "menu enable", FALSE for "menu 107 * disable */ 108 #ifdef FEAT_MULTI_LANG 109 char_u *tofree = NULL; 110 char_u *new_cmd; 111 #endif 112 #ifdef FEAT_TOOLBAR 113 char_u *icon = NULL; 114 #endif 115 vimmenu_T menuarg; 116 117 modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu); 118 arg = eap->arg; 119 120 for (;;) 121 { 122 if (STRNCMP(arg, "<script>", 8) == 0) 123 { 124 noremap = REMAP_SCRIPT; 125 arg = skipwhite(arg + 8); 126 continue; 127 } 128 if (STRNCMP(arg, "<silent>", 8) == 0) 129 { 130 silent = TRUE; 131 arg = skipwhite(arg + 8); 132 continue; 133 } 134 break; 135 } 136 137 138 /* Locate an optional "icon=filename" argument. */ 139 if (STRNCMP(arg, "icon=", 5) == 0) 140 { 141 arg += 5; 142 #ifdef FEAT_TOOLBAR 143 icon = arg; 144 #endif 145 while (*arg != NUL && *arg != ' ') 146 { 147 if (*arg == '\\') 148 mch_memmove(arg, arg + 1, STRLEN(arg)); 149 mb_ptr_adv(arg); 150 } 151 if (*arg != NUL) 152 { 153 *arg++ = NUL; 154 arg = skipwhite(arg); 155 } 156 } 157 158 /* 159 * Fill in the priority table. 160 */ 161 for (p = arg; *p; ++p) 162 if (!VIM_ISDIGIT(*p) && *p != '.') 163 break; 164 if (vim_iswhite(*p)) 165 { 166 for (i = 0; i < MENUDEPTH && !vim_iswhite(*arg); ++i) 167 { 168 pri_tab[i] = getdigits(&arg); 169 if (pri_tab[i] == 0) 170 pri_tab[i] = 500; 171 if (*arg == '.') 172 ++arg; 173 } 174 arg = skipwhite(arg); 175 } 176 else if (eap->addr_count && eap->line2 != 0) 177 { 178 pri_tab[0] = eap->line2; 179 i = 1; 180 } 181 else 182 i = 0; 183 while (i < MENUDEPTH) 184 pri_tab[i++] = 500; 185 pri_tab[MENUDEPTH] = -1; /* mark end of the table */ 186 187 /* 188 * Check for "disable" or "enable" argument. 189 */ 190 if (STRNCMP(arg, "enable", 6) == 0 && vim_iswhite(arg[6])) 191 { 192 enable = TRUE; 193 arg = skipwhite(arg + 6); 194 } 195 else if (STRNCMP(arg, "disable", 7) == 0 && vim_iswhite(arg[7])) 196 { 197 enable = FALSE; 198 arg = skipwhite(arg + 7); 199 } 200 201 /* 202 * If there is no argument, display all menus. 203 */ 204 if (*arg == NUL) 205 { 206 show_menus(arg, modes); 207 return; 208 } 209 210 #ifdef FEAT_TOOLBAR 211 /* 212 * Need to get the toolbar icon index before doing the translation. 213 */ 214 menuarg.iconidx = -1; 215 menuarg.icon_builtin = FALSE; 216 if (menu_is_toolbar(arg)) 217 { 218 menu_path = menu_skip_part(arg); 219 if (*menu_path == '.') 220 { 221 p = menu_skip_part(++menu_path); 222 if (STRNCMP(menu_path, "BuiltIn", 7) == 0) 223 { 224 if (skipdigits(menu_path + 7) == p) 225 { 226 menuarg.iconidx = atoi((char *)menu_path + 7); 227 if (menuarg.iconidx >= TOOLBAR_NAME_COUNT) 228 menuarg.iconidx = -1; 229 else 230 menuarg.icon_builtin = TRUE; 231 } 232 } 233 else 234 { 235 for (i = 0; i < TOOLBAR_NAME_COUNT; ++i) 236 if (STRNCMP(toolbar_names[i], menu_path, p - menu_path) 237 == 0) 238 { 239 menuarg.iconidx = i; 240 break; 241 } 242 } 243 } 244 } 245 #endif 246 247 #ifdef FEAT_MULTI_LANG 248 /* 249 * Translate menu names as specified with ":menutrans" commands. 250 */ 251 menu_path = arg; 252 while (*menu_path) 253 { 254 /* find the end of one part and check if it should be translated */ 255 p = menu_skip_part(menu_path); 256 map_to = menutrans_lookup(menu_path, (int)(p - menu_path)); 257 if (map_to != NULL) 258 { 259 /* found a match: replace with the translated part */ 260 i = (int)STRLEN(map_to); 261 new_cmd = alloc((unsigned)STRLEN(arg) + i + 1); 262 if (new_cmd == NULL) 263 break; 264 mch_memmove(new_cmd, arg, menu_path - arg); 265 mch_memmove(new_cmd + (menu_path - arg), map_to, (size_t)i); 266 STRCPY(new_cmd + (menu_path - arg) + i, p); 267 p = new_cmd + (menu_path - arg) + i; 268 vim_free(tofree); 269 tofree = new_cmd; 270 arg = new_cmd; 271 } 272 if (*p != '.') 273 break; 274 menu_path = p + 1; 275 } 276 #endif 277 278 /* 279 * Isolate the menu name. 280 * Skip the menu name, and translate <Tab> into a real TAB. 281 */ 282 menu_path = arg; 283 if (*menu_path == '.') 284 { 285 EMSG2(_(e_invarg2), menu_path); 286 goto theend; 287 } 288 289 while (*arg && !vim_iswhite(*arg)) 290 { 291 if ((*arg == '\\' || *arg == Ctrl_V) && arg[1] != NUL) 292 arg++; 293 else if (STRNICMP(arg, "<TAB>", 5) == 0) 294 { 295 *arg = TAB; 296 mch_memmove(arg + 1, arg + 5, STRLEN(arg + 4)); 297 } 298 arg++; 299 } 300 if (*arg != NUL) 301 *arg++ = NUL; 302 arg = skipwhite(arg); 303 map_to = arg; 304 305 /* 306 * If there is only a menu name, display menus with that name. 307 */ 308 if (*map_to == NUL && !unmenu && enable == MAYBE) 309 { 310 show_menus(menu_path, modes); 311 goto theend; 312 } 313 else if (*map_to != NUL && (unmenu || enable != MAYBE)) 314 { 315 EMSG(_(e_trailing)); 316 goto theend; 317 } 318 #if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON)) 319 old_menu_height = gui.menu_height; 320 # if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32) && !defined(FEAT_GUI_W16) 321 old_toolbar_height = gui.toolbar_height; 322 # endif 323 #endif 324 325 if (enable != MAYBE) 326 { 327 /* 328 * Change sensitivity of the menu. 329 * For the PopUp menu, remove a menu for each mode separately. 330 * Careful: menu_nable_recurse() changes menu_path. 331 */ 332 if (STRCMP(menu_path, "*") == 0) /* meaning: do all menus */ 333 menu_path = (char_u *)""; 334 335 if (menu_is_popup(menu_path)) 336 { 337 for (i = 0; i < MENU_INDEX_TIP; ++i) 338 if (modes & (1 << i)) 339 { 340 p = popup_mode_name(menu_path, i); 341 if (p != NULL) 342 { 343 menu_nable_recurse(root_menu, p, MENU_ALL_MODES, 344 enable); 345 vim_free(p); 346 } 347 } 348 } 349 menu_nable_recurse(root_menu, menu_path, modes, enable); 350 } 351 else if (unmenu) 352 { 353 /* 354 * Delete menu(s). 355 */ 356 if (STRCMP(menu_path, "*") == 0) /* meaning: remove all menus */ 357 menu_path = (char_u *)""; 358 359 /* 360 * For the PopUp menu, remove a menu for each mode separately. 361 */ 362 if (menu_is_popup(menu_path)) 363 { 364 for (i = 0; i < MENU_INDEX_TIP; ++i) 365 if (modes & (1 << i)) 366 { 367 p = popup_mode_name(menu_path, i); 368 if (p != NULL) 369 { 370 remove_menu(&root_menu, p, MENU_ALL_MODES, TRUE); 371 vim_free(p); 372 } 373 } 374 } 375 376 /* Careful: remove_menu() changes menu_path */ 377 remove_menu(&root_menu, menu_path, modes, FALSE); 378 } 379 else 380 { 381 /* 382 * Add menu(s). 383 * Replace special key codes. 384 */ 385 if (STRICMP(map_to, "<nop>") == 0) /* "<Nop>" means nothing */ 386 { 387 map_to = (char_u *)""; 388 map_buf = NULL; 389 } 390 else if (modes & MENU_TIP_MODE) 391 map_buf = NULL; /* Menu tips are plain text. */ 392 else 393 map_to = replace_termcodes(map_to, &map_buf, FALSE, TRUE); 394 menuarg.modes = modes; 395 #ifdef FEAT_TOOLBAR 396 menuarg.iconfile = icon; 397 #endif 398 menuarg.noremap[0] = noremap; 399 menuarg.silent[0] = silent; 400 add_menu_path(menu_path, &menuarg, pri_tab, map_to 401 #ifdef FEAT_GUI_W32 402 , TRUE 403 #endif 404 ); 405 406 /* 407 * For the PopUp menu, add a menu for each mode separately. 408 */ 409 if (menu_is_popup(menu_path)) 410 { 411 for (i = 0; i < MENU_INDEX_TIP; ++i) 412 if (modes & (1 << i)) 413 { 414 p = popup_mode_name(menu_path, i); 415 if (p != NULL) 416 { 417 /* Include all modes, to make ":amenu" work */ 418 menuarg.modes = modes; 419 #ifdef FEAT_TOOLBAR 420 menuarg.iconfile = NULL; 421 menuarg.iconidx = -1; 422 menuarg.icon_builtin = FALSE; 423 #endif 424 add_menu_path(p, &menuarg, pri_tab, map_to 425 #ifdef FEAT_GUI_W32 426 , TRUE 427 #endif 428 ); 429 vim_free(p); 430 } 431 } 432 } 433 434 vim_free(map_buf); 435 } 436 437 #if defined(FEAT_GUI) && !(defined(FEAT_GUI_GTK)) 438 /* If the menubar height changed, resize the window */ 439 if (gui.in_use 440 && (gui.menu_height != old_menu_height 441 # if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32) && !defined(FEAT_GUI_W16) 442 || gui.toolbar_height != old_toolbar_height 443 # endif 444 )) 445 gui_set_shellsize(FALSE, FALSE); 446 #endif 447 448 theend: 449 #ifdef FEAT_MULTI_LANG 450 vim_free(tofree); 451 #else 452 ; 453 #endif 454 } 455 456 /* 457 * Add the menu with the given name to the menu hierarchy 458 */ 459 static int 460 add_menu_path(menu_path, menuarg, pri_tab, call_data 461 #ifdef FEAT_GUI_W32 462 , addtearoff 463 #endif 464 ) 465 char_u *menu_path; 466 vimmenu_T *menuarg; /* passes modes, iconfile, iconidx, 467 icon_builtin, silent[0], noremap[0] */ 468 int *pri_tab; 469 char_u *call_data; 470 #ifdef FEAT_GUI_W32 471 int addtearoff; /* may add tearoff item */ 472 #endif 473 { 474 char_u *path_name; 475 int modes = menuarg->modes; 476 vimmenu_T **menup; 477 vimmenu_T *menu = NULL; 478 vimmenu_T *parent; 479 vimmenu_T **lower_pri; 480 char_u *p; 481 char_u *name; 482 char_u *dname; 483 char_u *next_name; 484 int i; 485 int c; 486 #ifdef FEAT_GUI 487 int idx; 488 int new_idx; 489 #endif 490 int pri_idx = 0; 491 int old_modes = 0; 492 int amenu; 493 494 /* Make a copy so we can stuff around with it, since it could be const */ 495 path_name = vim_strsave(menu_path); 496 if (path_name == NULL) 497 return FAIL; 498 menup = &root_menu; 499 parent = NULL; 500 name = path_name; 501 while (*name) 502 { 503 /* Get name of this element in the menu hierarchy, and the simplified 504 * name (without mnemonic and accelerator text). */ 505 next_name = menu_name_skip(name); 506 dname = menu_text(name, NULL, NULL); 507 508 /* See if it's already there */ 509 lower_pri = menup; 510 #ifdef FEAT_GUI 511 idx = 0; 512 new_idx = 0; 513 #endif 514 menu = *menup; 515 while (menu != NULL) 516 { 517 if (menu_name_equal(name, menu) || menu_name_equal(dname, menu)) 518 { 519 if (*next_name == NUL && menu->children != NULL) 520 { 521 if (!sys_menu) 522 EMSG(_("E330: Menu path must not lead to a sub-menu")); 523 goto erret; 524 } 525 if (*next_name != NUL && menu->children == NULL 526 #ifdef FEAT_GUI_W32 527 && addtearoff 528 #endif 529 ) 530 { 531 if (!sys_menu) 532 EMSG(_(e_notsubmenu)); 533 goto erret; 534 } 535 break; 536 } 537 menup = &menu->next; 538 539 /* Count menus, to find where this one needs to be inserted. 540 * Ignore menus that are not in the menubar (PopUp and Toolbar) */ 541 if (parent != NULL || menu_is_menubar(menu->name)) 542 { 543 #ifdef FEAT_GUI 544 ++idx; 545 #endif 546 if (menu->priority <= pri_tab[pri_idx]) 547 { 548 lower_pri = menup; 549 #ifdef FEAT_GUI 550 new_idx = idx; 551 #endif 552 } 553 } 554 menu = menu->next; 555 } 556 557 if (menu == NULL) 558 { 559 if (*next_name == NUL && parent == NULL) 560 { 561 EMSG(_("E331: Must not add menu items directly to menu bar")); 562 goto erret; 563 } 564 565 if (menu_is_separator(dname) && *next_name != NUL) 566 { 567 EMSG(_("E332: Separator cannot be part of a menu path")); 568 goto erret; 569 } 570 571 /* Not already there, so lets add it */ 572 menu = (vimmenu_T *)alloc_clear((unsigned)sizeof(vimmenu_T)); 573 if (menu == NULL) 574 goto erret; 575 576 menu->modes = modes; 577 menu->enabled = MENU_ALL_MODES; 578 menu->name = vim_strsave(name); 579 /* separate mnemonic and accelerator text from actual menu name */ 580 menu->dname = menu_text(name, &menu->mnemonic, &menu->actext); 581 menu->priority = pri_tab[pri_idx]; 582 menu->parent = parent; 583 #ifdef FEAT_GUI_MOTIF 584 menu->sensitive = TRUE; /* the default */ 585 #endif 586 #ifdef FEAT_BEVAL_TIP 587 menu->tip = NULL; 588 #endif 589 #ifdef FEAT_GUI_ATHENA 590 menu->image = None; /* X-Windows definition for NULL*/ 591 #endif 592 593 /* 594 * Add after menu that has lower priority. 595 */ 596 menu->next = *lower_pri; 597 *lower_pri = menu; 598 599 old_modes = 0; 600 601 #ifdef FEAT_TOOLBAR 602 menu->iconidx = menuarg->iconidx; 603 menu->icon_builtin = menuarg->icon_builtin; 604 if (*next_name == NUL && menuarg->iconfile != NULL) 605 menu->iconfile = vim_strsave(menuarg->iconfile); 606 #endif 607 #if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF) 608 /* the tearoff item must be present in the modes of each item. */ 609 if (parent != NULL && menu_is_tearoff(parent->children->dname)) 610 parent->children->modes |= modes; 611 #endif 612 } 613 else 614 { 615 old_modes = menu->modes; 616 617 /* 618 * If this menu option was previously only available in other 619 * modes, then make sure it's available for this one now 620 * Also enable a menu when it's created or changed. 621 */ 622 #ifdef FEAT_GUI_W32 623 /* If adding a tearbar (addtearoff == FALSE) don't update modes */ 624 if (addtearoff) 625 #endif 626 { 627 menu->modes |= modes; 628 menu->enabled |= modes; 629 } 630 } 631 632 #ifdef FEAT_GUI 633 /* 634 * Add the menu item when it's used in one of the modes, but not when 635 * only a tooltip is defined. 636 */ 637 if ((old_modes & MENU_ALL_MODES) == 0 638 && (menu->modes & MENU_ALL_MODES) != 0) 639 { 640 if (gui.in_use) /* Otherwise it will be added when GUI starts */ 641 { 642 if (*next_name == NUL) 643 { 644 /* Real menu item, not sub-menu */ 645 gui_mch_add_menu_item(menu, new_idx); 646 647 /* Want to update menus now even if mode not changed */ 648 force_menu_update = TRUE; 649 } 650 else 651 { 652 /* Sub-menu (not at end of path yet) */ 653 gui_mch_add_menu(menu, new_idx); 654 } 655 } 656 657 # if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF) 658 /* When adding a new submenu, may add a tearoff item */ 659 if ( addtearoff 660 && *next_name 661 && vim_strchr(p_go, GO_TEAROFF) != NULL 662 && menu_is_menubar(name)) 663 { 664 char_u *tearpath; 665 666 /* 667 * The pointers next_name & path_name refer to a string with 668 * \'s and ^V's stripped out. But menu_path is a "raw" 669 * string, so we must correct for special characters. 670 */ 671 tearpath = alloc((unsigned int)STRLEN(menu_path) + TEAR_LEN + 2); 672 if (tearpath != NULL) 673 { 674 char_u *s; 675 int idx; 676 677 STRCPY(tearpath, menu_path); 678 idx = (int)(next_name - path_name - 1); 679 for (s = tearpath; *s && s < tearpath + idx; mb_ptr_adv(s)) 680 { 681 if ((*s == '\\' || *s == Ctrl_V) && s[1]) 682 { 683 ++idx; 684 ++s; 685 } 686 } 687 tearpath[idx] = NUL; 688 gui_add_tearoff(tearpath, pri_tab, pri_idx); 689 vim_free(tearpath); 690 } 691 } 692 # endif 693 } 694 #endif /* FEAT_GUI */ 695 696 menup = &menu->children; 697 parent = menu; 698 name = next_name; 699 vim_free(dname); 700 if (pri_tab[pri_idx + 1] != -1) 701 ++pri_idx; 702 } 703 vim_free(path_name); 704 705 /* 706 * Only add system menu items which have not been defined yet. 707 * First check if this was an ":amenu". 708 */ 709 amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) == 710 (MENU_NORMAL_MODE | MENU_INSERT_MODE)); 711 if (sys_menu) 712 modes &= ~old_modes; 713 714 if (menu != NULL && modes) 715 { 716 #ifdef FEAT_GUI 717 menu->cb = gui_menu_cb; 718 #endif 719 p = (call_data == NULL) ? NULL : vim_strsave(call_data); 720 721 /* loop over all modes, may add more than one */ 722 for (i = 0; i < MENU_MODES; ++i) 723 { 724 if (modes & (1 << i)) 725 { 726 /* free any old menu */ 727 free_menu_string(menu, i); 728 729 /* For "amenu", may insert an extra character. 730 * Don't do this if adding a tearbar (addtearoff == FALSE). 731 * Don't do this for "<Nop>". */ 732 c = 0; 733 if (amenu && call_data != NULL && *call_data != NUL 734 #ifdef FEAT_GUI_W32 735 && addtearoff 736 #endif 737 ) 738 { 739 switch (1 << i) 740 { 741 case MENU_VISUAL_MODE: 742 case MENU_SELECT_MODE: 743 case MENU_OP_PENDING_MODE: 744 case MENU_CMDLINE_MODE: 745 c = Ctrl_C; 746 break; 747 case MENU_INSERT_MODE: 748 c = Ctrl_O; 749 break; 750 } 751 } 752 753 if (c) 754 { 755 menu->strings[i] = alloc((unsigned)(STRLEN(call_data) + 4)); 756 if (menu->strings[i] != NULL) 757 { 758 menu->strings[i][0] = c; 759 STRCPY(menu->strings[i] + 1, call_data); 760 if (c == Ctrl_C) 761 { 762 int len = STRLEN(menu->strings[i]); 763 764 /* Append CTRL-\ CTRL-G to obey 'insertmode'. */ 765 menu->strings[i][len] = Ctrl_BSL; 766 menu->strings[i][len + 1] = Ctrl_G; 767 menu->strings[i][len + 2] = NUL; 768 } 769 } 770 } 771 else 772 menu->strings[i] = p; 773 menu->noremap[i] = menuarg->noremap[0]; 774 menu->silent[i] = menuarg->silent[0]; 775 } 776 } 777 #if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32) \ 778 && (defined(FEAT_BEVAL) || defined(FEAT_GUI_GTK)) 779 /* Need to update the menu tip. */ 780 if (modes & MENU_TIP_MODE) 781 gui_mch_menu_set_tip(menu); 782 #endif 783 } 784 return OK; 785 786 erret: 787 vim_free(path_name); 788 vim_free(dname); 789 return FAIL; 790 } 791 792 /* 793 * Set the (sub)menu with the given name to enabled or disabled. 794 * Called recursively. 795 */ 796 static int 797 menu_nable_recurse(menu, name, modes, enable) 798 vimmenu_T *menu; 799 char_u *name; 800 int modes; 801 int enable; 802 { 803 char_u *p; 804 805 if (menu == NULL) 806 return OK; /* Got to bottom of hierarchy */ 807 808 /* Get name of this element in the menu hierarchy */ 809 p = menu_name_skip(name); 810 811 /* Find the menu */ 812 while (menu != NULL) 813 { 814 if (*name == NUL || *name == '*' || menu_name_equal(name, menu)) 815 { 816 if (*p != NUL) 817 { 818 if (menu->children == NULL) 819 { 820 EMSG(_(e_notsubmenu)); 821 return FAIL; 822 } 823 if (menu_nable_recurse(menu->children, p, modes, enable) 824 == FAIL) 825 return FAIL; 826 } 827 else 828 if (enable) 829 menu->enabled |= modes; 830 else 831 menu->enabled &= ~modes; 832 833 /* 834 * When name is empty, we are doing all menu items for the given 835 * modes, so keep looping, otherwise we are just doing the named 836 * menu item (which has been found) so break here. 837 */ 838 if (*name != NUL && *name != '*') 839 break; 840 } 841 menu = menu->next; 842 } 843 if (*name != NUL && *name != '*' && menu == NULL) 844 { 845 EMSG2(_(e_nomenu), name); 846 return FAIL; 847 } 848 849 #ifdef FEAT_GUI 850 /* Want to update menus now even if mode not changed */ 851 force_menu_update = TRUE; 852 #endif 853 854 return OK; 855 } 856 857 /* 858 * Remove the (sub)menu with the given name from the menu hierarchy 859 * Called recursively. 860 */ 861 static int 862 remove_menu(menup, name, modes, silent) 863 vimmenu_T **menup; 864 char_u *name; 865 int modes; 866 int silent; /* don't give error messages */ 867 { 868 vimmenu_T *menu; 869 vimmenu_T *child; 870 char_u *p; 871 872 if (*menup == NULL) 873 return OK; /* Got to bottom of hierarchy */ 874 875 /* Get name of this element in the menu hierarchy */ 876 p = menu_name_skip(name); 877 878 /* Find the menu */ 879 while ((menu = *menup) != NULL) 880 { 881 if (*name == NUL || menu_name_equal(name, menu)) 882 { 883 if (*p != NUL && menu->children == NULL) 884 { 885 if (!silent) 886 EMSG(_(e_notsubmenu)); 887 return FAIL; 888 } 889 if ((menu->modes & modes) != 0x0) 890 { 891 #if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF) 892 /* 893 * If we are removing all entries for this menu,MENU_ALL_MODES, 894 * Then kill any tearoff before we start 895 */ 896 if (*p == NUL && modes == MENU_ALL_MODES) 897 { 898 if (IsWindow(menu->tearoff_handle)) 899 DestroyWindow(menu->tearoff_handle); 900 } 901 #endif 902 if (remove_menu(&menu->children, p, modes, silent) == FAIL) 903 return FAIL; 904 } 905 else if (*name != NUL) 906 { 907 if (!silent) 908 EMSG(_(e_othermode)); 909 return FAIL; 910 } 911 912 /* 913 * When name is empty, we are removing all menu items for the given 914 * modes, so keep looping, otherwise we are just removing the named 915 * menu item (which has been found) so break here. 916 */ 917 if (*name != NUL) 918 break; 919 920 /* Remove the menu item for the given mode[s]. If the menu item 921 * is no longer valid in ANY mode, delete it */ 922 menu->modes &= ~modes; 923 if (modes & MENU_TIP_MODE) 924 free_menu_string(menu, MENU_INDEX_TIP); 925 if ((menu->modes & MENU_ALL_MODES) == 0) 926 free_menu(menup); 927 else 928 menup = &menu->next; 929 } 930 else 931 menup = &menu->next; 932 } 933 if (*name != NUL) 934 { 935 if (menu == NULL) 936 { 937 if (!silent) 938 EMSG2(_(e_nomenu), name); 939 return FAIL; 940 } 941 942 943 /* Recalculate modes for menu based on the new updated children */ 944 menu->modes &= ~modes; 945 #if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF) 946 if ((s_tearoffs) && (menu->children != NULL)) /* there's a tear bar.. */ 947 child = menu->children->next; /* don't count tearoff bar */ 948 else 949 #endif 950 child = menu->children; 951 for ( ; child != NULL; child = child->next) 952 menu->modes |= child->modes; 953 if (modes & MENU_TIP_MODE) 954 { 955 free_menu_string(menu, MENU_INDEX_TIP); 956 #if defined(FEAT_TOOLBAR) && !defined(FEAT_GUI_W32) \ 957 && (defined(FEAT_BEVAL) || defined(FEAT_GUI_GTK)) 958 /* Need to update the menu tip. */ 959 if (gui.in_use) 960 gui_mch_menu_set_tip(menu); 961 #endif 962 } 963 if ((menu->modes & MENU_ALL_MODES) == 0) 964 { 965 /* The menu item is no longer valid in ANY mode, so delete it */ 966 #if defined(FEAT_GUI_W32) & defined(FEAT_TEAROFF) 967 if (s_tearoffs && menu->children != NULL) /* there's a tear bar.. */ 968 free_menu(&menu->children); 969 #endif 970 *menup = menu; 971 free_menu(menup); 972 } 973 } 974 975 return OK; 976 } 977 978 /* 979 * Free the given menu structure and remove it from the linked list. 980 */ 981 static void 982 free_menu(menup) 983 vimmenu_T **menup; 984 { 985 int i; 986 vimmenu_T *menu; 987 988 menu = *menup; 989 990 #ifdef FEAT_GUI 991 /* Free machine specific menu structures (only when already created) */ 992 /* Also may rebuild a tearoff'ed menu */ 993 if (gui.in_use) 994 gui_mch_destroy_menu(menu); 995 #endif 996 997 /* Don't change *menup until after calling gui_mch_destroy_menu(). The 998 * MacOS code needs the original structure to properly delete the menu. */ 999 *menup = menu->next; 1000 vim_free(menu->name); 1001 vim_free(menu->dname); 1002 vim_free(menu->actext); 1003 #ifdef FEAT_TOOLBAR 1004 vim_free(menu->iconfile); 1005 # ifdef FEAT_GUI_MOTIF 1006 vim_free(menu->xpm_fname); 1007 # endif 1008 #endif 1009 for (i = 0; i < MENU_MODES; i++) 1010 free_menu_string(menu, i); 1011 vim_free(menu); 1012 1013 #ifdef FEAT_GUI 1014 /* Want to update menus now even if mode not changed */ 1015 force_menu_update = TRUE; 1016 #endif 1017 } 1018 1019 /* 1020 * Free the menu->string with the given index. 1021 */ 1022 static void 1023 free_menu_string(menu, idx) 1024 vimmenu_T *menu; 1025 int idx; 1026 { 1027 int count = 0; 1028 int i; 1029 1030 for (i = 0; i < MENU_MODES; i++) 1031 if (menu->strings[i] == menu->strings[idx]) 1032 count++; 1033 if (count == 1) 1034 vim_free(menu->strings[idx]); 1035 menu->strings[idx] = NULL; 1036 } 1037 1038 /* 1039 * Show the mapping associated with a menu item or hierarchy in a sub-menu. 1040 */ 1041 static int 1042 show_menus(path_name, modes) 1043 char_u *path_name; 1044 int modes; 1045 { 1046 char_u *p; 1047 char_u *name; 1048 vimmenu_T *menu; 1049 vimmenu_T *parent = NULL; 1050 1051 menu = root_menu; 1052 name = path_name = vim_strsave(path_name); 1053 if (path_name == NULL) 1054 return FAIL; 1055 1056 /* First, find the (sub)menu with the given name */ 1057 while (*name) 1058 { 1059 p = menu_name_skip(name); 1060 while (menu != NULL) 1061 { 1062 if (menu_name_equal(name, menu)) 1063 { 1064 /* Found menu */ 1065 if (*p != NUL && menu->children == NULL) 1066 { 1067 EMSG(_(e_notsubmenu)); 1068 vim_free(path_name); 1069 return FAIL; 1070 } 1071 else if ((menu->modes & modes) == 0x0) 1072 { 1073 EMSG(_(e_othermode)); 1074 vim_free(path_name); 1075 return FAIL; 1076 } 1077 break; 1078 } 1079 menu = menu->next; 1080 } 1081 if (menu == NULL) 1082 { 1083 EMSG2(_(e_nomenu), name); 1084 vim_free(path_name); 1085 return FAIL; 1086 } 1087 name = p; 1088 parent = menu; 1089 menu = menu->children; 1090 } 1091 1092 /* Now we have found the matching menu, and we list the mappings */ 1093 /* Highlight title */ 1094 MSG_PUTS_TITLE(_("\n--- Menus ---")); 1095 1096 show_menus_recursive(parent, modes, 0); 1097 return OK; 1098 } 1099 1100 /* 1101 * Recursively show the mappings associated with the menus under the given one 1102 */ 1103 static void 1104 show_menus_recursive(menu, modes, depth) 1105 vimmenu_T *menu; 1106 int modes; 1107 int depth; 1108 { 1109 int i; 1110 int bit; 1111 1112 if (menu != NULL && (menu->modes & modes) == 0x0) 1113 return; 1114 1115 if (menu != NULL) 1116 { 1117 msg_putchar('\n'); 1118 if (got_int) /* "q" hit for "--more--" */ 1119 return; 1120 for (i = 0; i < depth; i++) 1121 MSG_PUTS(" "); 1122 if (menu->priority) 1123 { 1124 msg_outnum((long)menu->priority); 1125 MSG_PUTS(" "); 1126 } 1127 /* Same highlighting as for directories!? */ 1128 msg_outtrans_attr(menu->name, hl_attr(HLF_D)); 1129 } 1130 1131 if (menu != NULL && menu->children == NULL) 1132 { 1133 for (bit = 0; bit < MENU_MODES; bit++) 1134 if ((menu->modes & modes & (1 << bit)) != 0) 1135 { 1136 msg_putchar('\n'); 1137 if (got_int) /* "q" hit for "--more--" */ 1138 return; 1139 for (i = 0; i < depth + 2; i++) 1140 MSG_PUTS(" "); 1141 msg_putchar(menu_mode_chars[bit]); 1142 if (menu->noremap[bit] == REMAP_NONE) 1143 msg_putchar('*'); 1144 else if (menu->noremap[bit] == REMAP_SCRIPT) 1145 msg_putchar('&'); 1146 else 1147 msg_putchar(' '); 1148 if (menu->silent[bit]) 1149 msg_putchar('s'); 1150 else 1151 msg_putchar(' '); 1152 if ((menu->modes & menu->enabled & (1 << bit)) == 0) 1153 msg_putchar('-'); 1154 else 1155 msg_putchar(' '); 1156 MSG_PUTS(" "); 1157 if (*menu->strings[bit] == NUL) 1158 msg_puts_attr((char_u *)"<Nop>", hl_attr(HLF_8)); 1159 else 1160 msg_outtrans_special(menu->strings[bit], FALSE); 1161 } 1162 } 1163 else 1164 { 1165 if (menu == NULL) 1166 { 1167 menu = root_menu; 1168 depth--; 1169 } 1170 else 1171 menu = menu->children; 1172 1173 /* recursively show all children. Skip PopUp[nvoci]. */ 1174 for (; menu != NULL && !got_int; menu = menu->next) 1175 if (!menu_is_hidden(menu->dname)) 1176 show_menus_recursive(menu, modes, depth + 1); 1177 } 1178 } 1179 1180 #ifdef FEAT_CMDL_COMPL 1181 1182 /* 1183 * Used when expanding menu names. 1184 */ 1185 static vimmenu_T *expand_menu = NULL; 1186 static int expand_modes = 0x0; 1187 static int expand_emenu; /* TRUE for ":emenu" command */ 1188 1189 /* 1190 * Work out what to complete when doing command line completion of menu names. 1191 */ 1192 char_u * 1193 set_context_in_menu_cmd(xp, cmd, arg, forceit) 1194 expand_T *xp; 1195 char_u *cmd; 1196 char_u *arg; 1197 int forceit; 1198 { 1199 char_u *after_dot; 1200 char_u *p; 1201 char_u *path_name = NULL; 1202 char_u *name; 1203 int unmenu; 1204 vimmenu_T *menu; 1205 int expand_menus; 1206 1207 xp->xp_context = EXPAND_UNSUCCESSFUL; 1208 1209 1210 /* Check for priority numbers, enable and disable */ 1211 for (p = arg; *p; ++p) 1212 if (!VIM_ISDIGIT(*p) && *p != '.') 1213 break; 1214 1215 if (!vim_iswhite(*p)) 1216 { 1217 if (STRNCMP(arg, "enable", 6) == 0 1218 && (arg[6] == NUL || vim_iswhite(arg[6]))) 1219 p = arg + 6; 1220 else if (STRNCMP(arg, "disable", 7) == 0 1221 && (arg[7] == NUL || vim_iswhite(arg[7]))) 1222 p = arg + 7; 1223 else 1224 p = arg; 1225 } 1226 1227 while (*p != NUL && vim_iswhite(*p)) 1228 ++p; 1229 1230 arg = after_dot = p; 1231 1232 for (; *p && !vim_iswhite(*p); ++p) 1233 { 1234 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL) 1235 p++; 1236 else if (*p == '.') 1237 after_dot = p + 1; 1238 } 1239 1240 /* ":tearoff" and ":popup" only use menus, not entries */ 1241 expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p'); 1242 expand_emenu = (*cmd == 'e'); 1243 if (expand_menus && vim_iswhite(*p)) 1244 return NULL; /* TODO: check for next command? */ 1245 if (*p == NUL) /* Complete the menu name */ 1246 { 1247 /* 1248 * With :unmenu, you only want to match menus for the appropriate mode. 1249 * With :menu though you might want to add a menu with the same name as 1250 * one in another mode, so match menus from other modes too. 1251 */ 1252 expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu); 1253 if (!unmenu) 1254 expand_modes = MENU_ALL_MODES; 1255 1256 menu = root_menu; 1257 if (after_dot != arg) 1258 { 1259 path_name = alloc((unsigned)(after_dot - arg)); 1260 if (path_name == NULL) 1261 return NULL; 1262 vim_strncpy(path_name, arg, after_dot - arg - 1); 1263 } 1264 name = path_name; 1265 while (name != NULL && *name) 1266 { 1267 p = menu_name_skip(name); 1268 while (menu != NULL) 1269 { 1270 if (menu_name_equal(name, menu)) 1271 { 1272 /* Found menu */ 1273 if ((*p != NUL && menu->children == NULL) 1274 || ((menu->modes & expand_modes) == 0x0)) 1275 { 1276 /* 1277 * Menu path continues, but we have reached a leaf. 1278 * Or menu exists only in another mode. 1279 */ 1280 vim_free(path_name); 1281 return NULL; 1282 } 1283 break; 1284 } 1285 menu = menu->next; 1286 } 1287 if (menu == NULL) 1288 { 1289 /* No menu found with the name we were looking for */ 1290 vim_free(path_name); 1291 return NULL; 1292 } 1293 name = p; 1294 menu = menu->children; 1295 } 1296 1297 xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS; 1298 xp->xp_pattern = after_dot; 1299 expand_menu = menu; 1300 } 1301 else /* We're in the mapping part */ 1302 xp->xp_context = EXPAND_NOTHING; 1303 return NULL; 1304 } 1305 1306 /* 1307 * Function given to ExpandGeneric() to obtain the list of (sub)menus (not 1308 * entries). 1309 */ 1310 /*ARGSUSED*/ 1311 char_u * 1312 get_menu_name(xp, idx) 1313 expand_T *xp; 1314 int idx; 1315 { 1316 static vimmenu_T *menu = NULL; 1317 char_u *str; 1318 1319 if (idx == 0) /* first call: start at first item */ 1320 menu = expand_menu; 1321 1322 /* Skip PopUp[nvoci]. */ 1323 while (menu != NULL && (menu_is_hidden(menu->dname) 1324 || menu_is_separator(menu->dname) 1325 || menu_is_tearoff(menu->dname) 1326 || menu->children == NULL)) 1327 menu = menu->next; 1328 1329 if (menu == NULL) /* at end of linked list */ 1330 return NULL; 1331 1332 if (menu->modes & expand_modes) 1333 str = menu->dname; 1334 else 1335 str = (char_u *)""; 1336 1337 /* Advance to next menu entry. */ 1338 menu = menu->next; 1339 1340 return str; 1341 } 1342 1343 /* 1344 * Function given to ExpandGeneric() to obtain the list of menus and menu 1345 * entries. 1346 */ 1347 /*ARGSUSED*/ 1348 char_u * 1349 get_menu_names(xp, idx) 1350 expand_T *xp; 1351 int idx; 1352 { 1353 static vimmenu_T *menu = NULL; 1354 static char_u tbuffer[256]; /*hack*/ 1355 char_u *str; 1356 1357 if (idx == 0) /* first call: start at first item */ 1358 menu = expand_menu; 1359 1360 /* Skip Browse-style entries, popup menus and separators. */ 1361 while (menu != NULL 1362 && ( menu_is_hidden(menu->dname) 1363 || (expand_emenu && menu_is_separator(menu->dname)) 1364 || menu_is_tearoff(menu->dname) 1365 #ifndef FEAT_BROWSE 1366 || menu->dname[STRLEN(menu->dname) - 1] == '.' 1367 #endif 1368 )) 1369 menu = menu->next; 1370 1371 if (menu == NULL) /* at end of linked list */ 1372 return NULL; 1373 1374 if (menu->modes & expand_modes) 1375 { 1376 if (menu->children != NULL) 1377 { 1378 STRCPY(tbuffer, menu->dname); 1379 /* hack on menu separators: use a 'magic' char for the separator 1380 * so that '.' in names gets escaped properly */ 1381 STRCAT(tbuffer, "\001"); 1382 str = tbuffer; 1383 } 1384 else 1385 str = menu->dname; 1386 } 1387 else 1388 str = (char_u *)""; 1389 1390 /* Advance to next menu entry. */ 1391 menu = menu->next; 1392 1393 return str; 1394 } 1395 #endif /* FEAT_CMDL_COMPL */ 1396 1397 /* 1398 * Skip over this element of the menu path and return the start of the next 1399 * element. Any \ and ^Vs are removed from the current element. 1400 * "name" may be modified. 1401 */ 1402 char_u * 1403 menu_name_skip(name) 1404 char_u *name; 1405 { 1406 char_u *p; 1407 1408 for (p = name; *p && *p != '.'; mb_ptr_adv(p)) 1409 { 1410 if (*p == '\\' || *p == Ctrl_V) 1411 { 1412 mch_memmove(p, p + 1, STRLEN(p)); 1413 if (*p == NUL) 1414 break; 1415 } 1416 } 1417 if (*p) 1418 *p++ = NUL; 1419 return p; 1420 } 1421 1422 /* 1423 * Return TRUE when "name" matches with menu "menu". The name is compared in 1424 * two ways: raw menu name and menu name without '&'. ignore part after a TAB. 1425 */ 1426 static int 1427 menu_name_equal(name, menu) 1428 char_u *name; 1429 vimmenu_T *menu; 1430 { 1431 return (menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname)); 1432 } 1433 1434 static int 1435 menu_namecmp(name, mname) 1436 char_u *name; 1437 char_u *mname; 1438 { 1439 int i; 1440 1441 for (i = 0; name[i] != NUL && name[i] != TAB; ++i) 1442 if (name[i] != mname[i]) 1443 break; 1444 return ((name[i] == NUL || name[i] == TAB) 1445 && (mname[i] == NUL || mname[i] == TAB)); 1446 } 1447 1448 /* 1449 * Return the modes specified by the given menu command (eg :menu! returns 1450 * MENU_CMDLINE_MODE | MENU_INSERT_MODE). 1451 * If "noremap" is not NULL, then the flag it points to is set according to 1452 * whether the command is a "nore" command. 1453 * If "unmenu" is not NULL, then the flag it points to is set according to 1454 * whether the command is an "unmenu" command. 1455 */ 1456 static int 1457 get_menu_cmd_modes(cmd, forceit, noremap, unmenu) 1458 char_u *cmd; 1459 int forceit; /* Was there a "!" after the command? */ 1460 int *noremap; 1461 int *unmenu; 1462 { 1463 int modes; 1464 1465 switch (*cmd++) 1466 { 1467 case 'v': /* vmenu, vunmenu, vnoremenu */ 1468 modes = MENU_VISUAL_MODE | MENU_SELECT_MODE; 1469 break; 1470 case 'x': /* xmenu, xunmenu, xnoremenu */ 1471 modes = MENU_VISUAL_MODE; 1472 break; 1473 case 's': /* smenu, sunmenu, snoremenu */ 1474 modes = MENU_SELECT_MODE; 1475 break; 1476 case 'o': /* omenu */ 1477 modes = MENU_OP_PENDING_MODE; 1478 break; 1479 case 'i': /* imenu */ 1480 modes = MENU_INSERT_MODE; 1481 break; 1482 case 't': 1483 modes = MENU_TIP_MODE; /* tmenu */ 1484 break; 1485 case 'c': /* cmenu */ 1486 modes = MENU_CMDLINE_MODE; 1487 break; 1488 case 'a': /* amenu */ 1489 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE 1490 | MENU_VISUAL_MODE | MENU_SELECT_MODE 1491 | MENU_OP_PENDING_MODE; 1492 break; 1493 case 'n': 1494 if (*cmd != 'o') /* nmenu, not noremenu */ 1495 { 1496 modes = MENU_NORMAL_MODE; 1497 break; 1498 } 1499 /* FALLTHROUGH */ 1500 default: 1501 --cmd; 1502 if (forceit) /* menu!! */ 1503 modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE; 1504 else /* menu */ 1505 modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE | MENU_SELECT_MODE 1506 | MENU_OP_PENDING_MODE; 1507 } 1508 1509 if (noremap != NULL) 1510 *noremap = (*cmd == 'n' ? REMAP_NONE : REMAP_YES); 1511 if (unmenu != NULL) 1512 *unmenu = (*cmd == 'u'); 1513 return modes; 1514 } 1515 1516 /* 1517 * Modify a menu name starting with "PopUp" to include the mode character. 1518 * Returns the name in allocated memory (NULL for failure). 1519 */ 1520 static char_u * 1521 popup_mode_name(name, idx) 1522 char_u *name; 1523 int idx; 1524 { 1525 char_u *p; 1526 int len = (int)STRLEN(name); 1527 1528 p = vim_strnsave(name, len + 1); 1529 if (p != NULL) 1530 { 1531 mch_memmove(p + 6, p + 5, (size_t)(len - 4)); 1532 p[5] = menu_mode_chars[idx]; 1533 } 1534 return p; 1535 } 1536 1537 #if defined(FEAT_GUI) || defined(PROTO) 1538 /* 1539 * Return the index into the menu->strings or menu->noremap arrays for the 1540 * current state. Returns MENU_INDEX_INVALID if there is no mapping for the 1541 * given menu in the current mode. 1542 */ 1543 int 1544 get_menu_index(menu, state) 1545 vimmenu_T *menu; 1546 int state; 1547 { 1548 int idx; 1549 1550 if ((state & INSERT)) 1551 idx = MENU_INDEX_INSERT; 1552 else if (state & CMDLINE) 1553 idx = MENU_INDEX_CMDLINE; 1554 #ifdef FEAT_VISUAL 1555 else if (VIsual_active) 1556 { 1557 if (VIsual_select) 1558 idx = MENU_INDEX_SELECT; 1559 else 1560 idx = MENU_INDEX_VISUAL; 1561 } 1562 #endif 1563 else if (state == HITRETURN || state == ASKMORE) 1564 idx = MENU_INDEX_CMDLINE; 1565 else if (finish_op) 1566 idx = MENU_INDEX_OP_PENDING; 1567 else if ((state & NORMAL)) 1568 idx = MENU_INDEX_NORMAL; 1569 else 1570 idx = MENU_INDEX_INVALID; 1571 1572 if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL) 1573 idx = MENU_INDEX_INVALID; 1574 return idx; 1575 } 1576 #endif 1577 1578 /* 1579 * Duplicate the menu item text and then process to see if a mnemonic key 1580 * and/or accelerator text has been identified. 1581 * Returns a pointer to allocated memory, or NULL for failure. 1582 * If mnemonic != NULL, *mnemonic is set to the character after the first '&'. 1583 * If actext != NULL, *actext is set to the text after the first TAB. 1584 */ 1585 static char_u * 1586 menu_text(str, mnemonic, actext) 1587 char_u *str; 1588 int *mnemonic; 1589 char_u **actext; 1590 { 1591 char_u *p; 1592 char_u *text; 1593 1594 /* Locate accelerator text, after the first TAB */ 1595 p = vim_strchr(str, TAB); 1596 if (p != NULL) 1597 { 1598 if (actext != NULL) 1599 *actext = vim_strsave(p + 1); 1600 text = vim_strnsave(str, (int)(p - str)); 1601 } 1602 else 1603 text = vim_strsave(str); 1604 1605 /* Find mnemonic characters "&a" and reduce "&&" to "&". */ 1606 for (p = text; p != NULL; ) 1607 { 1608 p = vim_strchr(p, '&'); 1609 if (p != NULL) 1610 { 1611 if (p[1] == NUL) /* trailing "&" */ 1612 break; 1613 if (mnemonic != NULL && p[1] != '&') 1614 #if !defined(__MVS__) || defined(MOTIF390_MNEMONIC_FIXED) 1615 *mnemonic = p[1]; 1616 #else 1617 { 1618 /* 1619 * Well there is a bug in the Motif libraries on OS390 Unix. 1620 * The mnemonic keys needs to be converted to ASCII values 1621 * first. 1622 * This behavior has been seen in 2.8 and 2.9. 1623 */ 1624 char c = p[1]; 1625 __etoa_l(&c, 1); 1626 *mnemonic = c; 1627 } 1628 #endif 1629 mch_memmove(p, p + 1, STRLEN(p)); 1630 p = p + 1; 1631 } 1632 } 1633 return text; 1634 } 1635 1636 /* 1637 * Return TRUE if "name" can be a menu in the MenuBar. 1638 */ 1639 int 1640 menu_is_menubar(name) 1641 char_u *name; 1642 { 1643 return (!menu_is_popup(name) 1644 && !menu_is_toolbar(name) 1645 && *name != MNU_HIDDEN_CHAR); 1646 } 1647 1648 /* 1649 * Return TRUE if "name" is a popup menu name. 1650 */ 1651 int 1652 menu_is_popup(name) 1653 char_u *name; 1654 { 1655 return (STRNCMP(name, "PopUp", 5) == 0); 1656 } 1657 1658 #if (defined(FEAT_GUI_MOTIF) && (XmVersion <= 1002)) || defined(PROTO) 1659 /* 1660 * Return TRUE if "name" is part of a popup menu. 1661 */ 1662 int 1663 menu_is_child_of_popup(menu) 1664 vimmenu_T *menu; 1665 { 1666 while (menu->parent != NULL) 1667 menu = menu->parent; 1668 return menu_is_popup(menu->name); 1669 } 1670 #endif 1671 1672 /* 1673 * Return TRUE if "name" is a toolbar menu name. 1674 */ 1675 int 1676 menu_is_toolbar(name) 1677 char_u *name; 1678 { 1679 return (STRNCMP(name, "ToolBar", 7) == 0); 1680 } 1681 1682 /* 1683 * Return TRUE if the name is a menu separator identifier: Starts and ends 1684 * with '-' 1685 */ 1686 int 1687 menu_is_separator(name) 1688 char_u *name; 1689 { 1690 return (name[0] == '-' && name[STRLEN(name) - 1] == '-'); 1691 } 1692 1693 /* 1694 * Return TRUE if the menu is hidden: Starts with ']' 1695 */ 1696 static int 1697 menu_is_hidden(name) 1698 char_u *name; 1699 { 1700 return (name[0] == ']') || (menu_is_popup(name) && name[5] != NUL); 1701 } 1702 1703 #if defined(FEAT_CMDL_COMPL) \ 1704 || (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)) 1705 /* 1706 * Return TRUE if the menu is the tearoff menu. 1707 */ 1708 /*ARGSUSED*/ 1709 static int 1710 menu_is_tearoff(name) 1711 char_u *name; 1712 { 1713 #ifdef FEAT_GUI 1714 return (STRCMP(name, TEAR_STRING) == 0); 1715 #else 1716 return FALSE; 1717 #endif 1718 } 1719 #endif 1720 1721 #ifdef FEAT_GUI 1722 1723 static int 1724 get_menu_mode() 1725 { 1726 #ifdef FEAT_VISUAL 1727 if (VIsual_active) 1728 { 1729 if (VIsual_select) 1730 return MENU_INDEX_SELECT; 1731 return MENU_INDEX_VISUAL; 1732 } 1733 #endif 1734 if (State & INSERT) 1735 return MENU_INDEX_INSERT; 1736 if ((State & CMDLINE) || State == ASKMORE || State == HITRETURN) 1737 return MENU_INDEX_CMDLINE; 1738 if (finish_op) 1739 return MENU_INDEX_OP_PENDING; 1740 if (State & NORMAL) 1741 return MENU_INDEX_NORMAL; 1742 if (State & LANGMAP) /* must be a "r" command, like Insert mode */ 1743 return MENU_INDEX_INSERT; 1744 return MENU_INDEX_INVALID; 1745 } 1746 1747 /* 1748 * After we have started the GUI, then we can create any menus that have been 1749 * defined. This is done once here. add_menu_path() may have already been 1750 * called to define these menus, and may be called again. This function calls 1751 * itself recursively. Should be called at the top level with: 1752 * gui_create_initial_menus(root_menu, NULL); 1753 */ 1754 void 1755 gui_create_initial_menus(menu) 1756 vimmenu_T *menu; 1757 { 1758 int idx = 0; 1759 1760 while (menu != NULL) 1761 { 1762 /* Don't add a menu when only a tip was defined. */ 1763 if (menu->modes & MENU_ALL_MODES) 1764 { 1765 if (menu->children != NULL) 1766 { 1767 gui_mch_add_menu(menu, idx); 1768 gui_create_initial_menus(menu->children); 1769 } 1770 else 1771 gui_mch_add_menu_item(menu, idx); 1772 } 1773 menu = menu->next; 1774 ++idx; 1775 } 1776 } 1777 1778 /* 1779 * Used recursively by gui_update_menus (see below) 1780 */ 1781 static void 1782 gui_update_menus_recurse(menu, mode) 1783 vimmenu_T *menu; 1784 int mode; 1785 { 1786 int grey; 1787 1788 while (menu) 1789 { 1790 if ((menu->modes & menu->enabled & mode) 1791 #if defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF) 1792 || menu_is_tearoff(menu->dname) 1793 #endif 1794 ) 1795 grey = FALSE; 1796 else 1797 grey = TRUE; 1798 #ifdef FEAT_GUI_ATHENA 1799 /* Hiding menus doesn't work for Athena, it can cause a crash. */ 1800 gui_mch_menu_grey(menu, grey); 1801 #else 1802 /* Never hide a toplevel menu, it may make the menubar resize or 1803 * disappear. Same problem for ToolBar items. */ 1804 if (vim_strchr(p_go, GO_GREY) != NULL || menu->parent == NULL 1805 # ifdef FEAT_TOOLBAR 1806 || menu_is_toolbar(menu->parent->name) 1807 # endif 1808 ) 1809 gui_mch_menu_grey(menu, grey); 1810 else 1811 gui_mch_menu_hidden(menu, grey); 1812 #endif 1813 gui_update_menus_recurse(menu->children, mode); 1814 menu = menu->next; 1815 } 1816 } 1817 1818 /* 1819 * Make sure only the valid menu items appear for this mode. If 1820 * force_menu_update is not TRUE, then we only do this if the mode has changed 1821 * since last time. If "modes" is not 0, then we use these modes instead. 1822 */ 1823 void 1824 gui_update_menus(modes) 1825 int modes; 1826 { 1827 static int prev_mode = -1; 1828 int mode = 0; 1829 1830 if (modes != 0x0) 1831 mode = modes; 1832 else 1833 { 1834 mode = get_menu_mode(); 1835 if (mode == MENU_INDEX_INVALID) 1836 mode = 0; 1837 else 1838 mode = (1 << mode); 1839 } 1840 1841 if (force_menu_update || mode != prev_mode) 1842 { 1843 gui_update_menus_recurse(root_menu, mode); 1844 gui_mch_draw_menubar(); 1845 prev_mode = mode; 1846 force_menu_update = FALSE; 1847 #ifdef FEAT_GUI_W32 1848 /* This can leave a tearoff as active window - make sure we 1849 * have the focus <negri>*/ 1850 gui_mch_activate_window(); 1851 #endif 1852 } 1853 } 1854 1855 #if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_MOTIF) \ 1856 || defined(FEAT_GUI_GTK) || defined(FEAT_GUI_PHOTON) || defined(PROTO) 1857 /* 1858 * Check if a key is used as a mnemonic for a toplevel menu. 1859 * Case of the key is ignored. 1860 */ 1861 int 1862 gui_is_menu_shortcut(key) 1863 int key; 1864 { 1865 vimmenu_T *menu; 1866 1867 if (key < 256) 1868 key = TOLOWER_LOC(key); 1869 for (menu = root_menu; menu != NULL; menu = menu->next) 1870 if (menu->mnemonic == key 1871 || (menu->mnemonic < 256 && TOLOWER_LOC(menu->mnemonic) == key)) 1872 return TRUE; 1873 return FALSE; 1874 } 1875 #endif 1876 1877 /* 1878 * Display the Special "PopUp" menu as a pop-up at the current mouse 1879 * position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode, 1880 * etc. 1881 */ 1882 void 1883 gui_show_popupmenu() 1884 { 1885 vimmenu_T *menu; 1886 int mode; 1887 1888 mode = get_menu_mode(); 1889 if (mode == MENU_INDEX_INVALID) 1890 return; 1891 mode = menu_mode_chars[mode]; 1892 1893 #ifdef FEAT_AUTOCMD 1894 { 1895 char_u ename[2]; 1896 1897 ename[0] = mode; 1898 ename[1] = NUL; 1899 apply_autocmds(EVENT_MENUPOPUP, ename, NULL, FALSE, curbuf); 1900 } 1901 #endif 1902 1903 for (menu = root_menu; menu != NULL; menu = menu->next) 1904 if (STRNCMP("PopUp", menu->name, 5) == 0 && menu->name[5] == mode) 1905 break; 1906 1907 /* Only show a popup when it is defined and has entries */ 1908 if (menu != NULL && menu->children != NULL) 1909 gui_mch_show_popupmenu(menu); 1910 } 1911 #endif /* FEAT_GUI */ 1912 1913 #if (defined(FEAT_GUI_W32) && defined(FEAT_TEAROFF)) || defined(PROTO) 1914 1915 /* 1916 * Deal with tearoff items that are added like a menu item. 1917 * Currently only for Win32 GUI. Others may follow later. 1918 */ 1919 1920 void 1921 gui_mch_toggle_tearoffs(int enable) 1922 { 1923 int pri_tab[MENUDEPTH + 1]; 1924 int i; 1925 1926 if (enable) 1927 { 1928 for (i = 0; i < MENUDEPTH; ++i) 1929 pri_tab[i] = 500; 1930 pri_tab[MENUDEPTH] = -1; 1931 gui_create_tearoffs_recurse(root_menu, (char_u *)"", pri_tab, 0); 1932 } 1933 else 1934 gui_destroy_tearoffs_recurse(root_menu); 1935 s_tearoffs = enable; 1936 } 1937 1938 /* 1939 * Recursively add tearoff items 1940 */ 1941 static void 1942 gui_create_tearoffs_recurse(menu, pname, pri_tab, pri_idx) 1943 vimmenu_T *menu; 1944 const char_u *pname; 1945 int *pri_tab; 1946 int pri_idx; 1947 { 1948 char_u *newpname = NULL; 1949 int len; 1950 char_u *s; 1951 char_u *d; 1952 1953 if (pri_tab[pri_idx + 1] != -1) 1954 ++pri_idx; 1955 while (menu != NULL) 1956 { 1957 if (menu->children != NULL && menu_is_menubar(menu->name)) 1958 { 1959 /* Add the menu name to the menu path. Insert a backslash before 1960 * dots (it's used to separate menu names). */ 1961 len = (int)STRLEN(pname) + (int)STRLEN(menu->name); 1962 for (s = menu->name; *s; ++s) 1963 if (*s == '.' || *s == '\\') 1964 ++len; 1965 newpname = alloc(len + TEAR_LEN + 2); 1966 if (newpname != NULL) 1967 { 1968 STRCPY(newpname, pname); 1969 d = newpname + STRLEN(newpname); 1970 for (s = menu->name; *s; ++s) 1971 { 1972 if (*s == '.' || *s == '\\') 1973 *d++ = '\\'; 1974 *d++ = *s; 1975 } 1976 *d = NUL; 1977 1978 /* check if tearoff already exists */ 1979 if (STRCMP(menu->children->name, TEAR_STRING) != 0) 1980 { 1981 gui_add_tearoff(newpname, pri_tab, pri_idx - 1); 1982 *d = NUL; /* remove TEAR_STRING */ 1983 } 1984 1985 STRCAT(newpname, "."); 1986 gui_create_tearoffs_recurse(menu->children, newpname, 1987 pri_tab, pri_idx); 1988 vim_free(newpname); 1989 } 1990 } 1991 menu = menu->next; 1992 } 1993 } 1994 1995 /* 1996 * Add tear-off menu item for a submenu. 1997 * "tearpath" is the menu path, and must have room to add TEAR_STRING. 1998 */ 1999 static void 2000 gui_add_tearoff(tearpath, pri_tab, pri_idx) 2001 char_u *tearpath; 2002 int *pri_tab; 2003 int pri_idx; 2004 { 2005 char_u *tbuf; 2006 int t; 2007 vimmenu_T menuarg; 2008 2009 tbuf = alloc(5 + (unsigned int)STRLEN(tearpath)); 2010 if (tbuf != NULL) 2011 { 2012 tbuf[0] = K_SPECIAL; 2013 tbuf[1] = K_SECOND(K_TEAROFF); 2014 tbuf[2] = K_THIRD(K_TEAROFF); 2015 STRCPY(tbuf + 3, tearpath); 2016 STRCAT(tbuf + 3, "\r"); 2017 2018 STRCAT(tearpath, "."); 2019 STRCAT(tearpath, TEAR_STRING); 2020 2021 /* Priority of tear-off is always 1 */ 2022 t = pri_tab[pri_idx + 1]; 2023 pri_tab[pri_idx + 1] = 1; 2024 2025 #ifdef FEAT_TOOLBAR 2026 menuarg.iconfile = NULL; 2027 menuarg.iconidx = -1; 2028 menuarg.icon_builtin = FALSE; 2029 #endif 2030 menuarg.noremap[0] = REMAP_NONE; 2031 menuarg.silent[0] = TRUE; 2032 2033 menuarg.modes = MENU_ALL_MODES; 2034 add_menu_path(tearpath, &menuarg, pri_tab, tbuf, FALSE); 2035 2036 menuarg.modes = MENU_TIP_MODE; 2037 add_menu_path(tearpath, &menuarg, pri_tab, 2038 (char_u *)_("Tear off this menu"), FALSE); 2039 2040 pri_tab[pri_idx + 1] = t; 2041 vim_free(tbuf); 2042 } 2043 } 2044 2045 /* 2046 * Recursively destroy tearoff items 2047 */ 2048 static void 2049 gui_destroy_tearoffs_recurse(menu) 2050 vimmenu_T *menu; 2051 { 2052 while (menu) 2053 { 2054 if (menu->children) 2055 { 2056 /* check if tearoff exists */ 2057 if (STRCMP(menu->children->name, TEAR_STRING) == 0) 2058 { 2059 /* Disconnect the item and free the memory */ 2060 free_menu(&menu->children); 2061 } 2062 if (menu->children != NULL) /* if not the last one */ 2063 gui_destroy_tearoffs_recurse(menu->children); 2064 } 2065 menu = menu->next; 2066 } 2067 } 2068 2069 #endif /* FEAT_GUI_W32 && FEAT_TEAROFF */ 2070 2071 /* 2072 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and 2073 * execute it. 2074 */ 2075 void 2076 ex_emenu(eap) 2077 exarg_T *eap; 2078 { 2079 vimmenu_T *menu; 2080 char_u *name; 2081 char_u *saved_name; 2082 char_u *p; 2083 int idx; 2084 char_u *mode; 2085 2086 saved_name = vim_strsave(eap->arg); 2087 if (saved_name == NULL) 2088 return; 2089 2090 menu = root_menu; 2091 name = saved_name; 2092 while (*name) 2093 { 2094 /* Find in the menu hierarchy */ 2095 p = menu_name_skip(name); 2096 2097 while (menu != NULL) 2098 { 2099 if (menu_name_equal(name, menu)) 2100 { 2101 if (*p == NUL && menu->children != NULL) 2102 { 2103 EMSG(_("E333: Menu path must lead to a menu item")); 2104 menu = NULL; 2105 } 2106 else if (*p != NUL && menu->children == NULL) 2107 { 2108 EMSG(_(e_notsubmenu)); 2109 menu = NULL; 2110 } 2111 break; 2112 } 2113 menu = menu->next; 2114 } 2115 if (menu == NULL || *p == NUL) 2116 break; 2117 menu = menu->children; 2118 name = p; 2119 } 2120 vim_free(saved_name); 2121 if (menu == NULL) 2122 { 2123 EMSG2(_("E334: Menu not found: %s"), eap->arg); 2124 return; 2125 } 2126 2127 /* Found the menu, so execute. 2128 * Use the Insert mode entry when returning to Insert mode. */ 2129 if (restart_edit 2130 #ifdef FEAT_EVAL 2131 && !current_SID 2132 #endif 2133 ) 2134 { 2135 mode = (char_u *)"Insert"; 2136 idx = MENU_INDEX_INSERT; 2137 } 2138 else if (eap->addr_count) 2139 { 2140 pos_T tpos; 2141 2142 mode = (char_u *)"Visual"; 2143 idx = MENU_INDEX_VISUAL; 2144 2145 /* GEDDES: This is not perfect - but it is a 2146 * quick way of detecting whether we are doing this from a 2147 * selection - see if the range matches up with the visual 2148 * select start and end. */ 2149 if ((curbuf->b_visual.vi_start.lnum == eap->line1) 2150 && (curbuf->b_visual.vi_end.lnum) == eap->line2) 2151 { 2152 /* Set it up for visual mode - equivalent to gv. */ 2153 VIsual_mode = curbuf->b_visual.vi_mode; 2154 tpos = curbuf->b_visual.vi_end; 2155 curwin->w_cursor = curbuf->b_visual.vi_start; 2156 curwin->w_curswant = curbuf->b_visual.vi_curswant; 2157 } 2158 else 2159 { 2160 /* Set it up for line-wise visual mode */ 2161 VIsual_mode = 'V'; 2162 curwin->w_cursor.lnum = eap->line1; 2163 curwin->w_cursor.col = 1; 2164 tpos.lnum = eap->line2; 2165 tpos.col = MAXCOL; 2166 #ifdef FEAT_VIRTUALEDIT 2167 tpos.coladd = 0; 2168 #endif 2169 } 2170 2171 /* Activate visual mode */ 2172 VIsual_active = TRUE; 2173 VIsual_reselect = TRUE; 2174 check_cursor(); 2175 VIsual = curwin->w_cursor; 2176 curwin->w_cursor = tpos; 2177 2178 check_cursor(); 2179 2180 /* Adjust the cursor to make sure it is in the correct pos 2181 * for exclusive mode */ 2182 if (*p_sel == 'e' && gchar_cursor() != NUL) 2183 ++curwin->w_cursor.col; 2184 } 2185 else 2186 { 2187 mode = (char_u *)"Normal"; 2188 idx = MENU_INDEX_NORMAL; 2189 } 2190 2191 if (idx != MENU_INDEX_INVALID && menu->strings[idx] != NULL) 2192 { 2193 /* When executing a script or function execute the commands right now. 2194 * Otherwise put them in the typeahead buffer. */ 2195 #ifdef FEAT_En 2196 if (current_SID != 0) 2197 exec_normal_cmd(menu->strings[idx], menu->noremap[idx], 2198 menu->silent[idx]); 2199 else 2200 #endif 2201 ins_typebuf(menu->strings[idx], menu->noremap[idx], 0, 2202 TRUE, menu->silent[idx]); 2203 } 2204 else 2205 EMSG2(_("E335: Menu not defined for %s mode"), mode); 2206 } 2207 2208 #if defined(FEAT_GUI_MSWIN) \ 2209 || (defined(FEAT_GUI_GTK) && defined(FEAT_MENU)) \ 2210 || defined(FEAT_BEVAL_TIP) || defined(PROTO) 2211 /* 2212 * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy. 2213 */ 2214 vimmenu_T * 2215 gui_find_menu(path_name) 2216 char_u *path_name; 2217 { 2218 vimmenu_T *menu = NULL; 2219 char_u *name; 2220 char_u *saved_name; 2221 char_u *p; 2222 2223 menu = root_menu; 2224 2225 saved_name = vim_strsave(path_name); 2226 if (saved_name == NULL) 2227 return NULL; 2228 2229 name = saved_name; 2230 while (*name) 2231 { 2232 /* find the end of one dot-separated name and put a NUL at the dot */ 2233 p = menu_name_skip(name); 2234 2235 while (menu != NULL) 2236 { 2237 if (STRCMP(name, menu->name) == 0 || STRCMP(name, menu->dname) == 0) 2238 { 2239 if (menu->children == NULL) 2240 { 2241 /* found a menu item instead of a sub-menu */ 2242 if (*p == NUL) 2243 EMSG(_("E336: Menu path must lead to a sub-menu")); 2244 else 2245 EMSG(_(e_notsubmenu)); 2246 menu = NULL; 2247 goto theend; 2248 } 2249 if (*p == NUL) /* found a full match */ 2250 goto theend; 2251 break; 2252 } 2253 menu = menu->next; 2254 } 2255 if (menu == NULL) /* didn't find it */ 2256 break; 2257 2258 /* Found a match, search the sub-menu. */ 2259 menu = menu->children; 2260 name = p; 2261 } 2262 2263 if (menu == NULL) 2264 EMSG(_("E337: Menu not found - check menu names")); 2265 theend: 2266 vim_free(saved_name); 2267 return menu; 2268 } 2269 #endif 2270 2271 #ifdef FEAT_MULTI_LANG 2272 /* 2273 * Translation of menu names. Just a simple lookup table. 2274 */ 2275 2276 typedef struct 2277 { 2278 char_u *from; /* English name */ 2279 char_u *from_noamp; /* same, without '&' */ 2280 char_u *to; /* translated name */ 2281 } menutrans_T; 2282 2283 static garray_T menutrans_ga = {0, 0, 0, 0, NULL}; 2284 #endif 2285 2286 /* 2287 * ":menutrans". 2288 * This function is also defined without the +multi_lang feature, in which 2289 * case the commands are ignored. 2290 */ 2291 /*ARGSUSED*/ 2292 void 2293 ex_menutranslate(eap) 2294 exarg_T *eap; 2295 { 2296 #ifdef FEAT_MULTI_LANG 2297 char_u *arg = eap->arg; 2298 menutrans_T *tp; 2299 int i; 2300 char_u *from, *from_noamp, *to; 2301 2302 if (menutrans_ga.ga_itemsize == 0) 2303 ga_init2(&menutrans_ga, (int)sizeof(menutrans_T), 5); 2304 2305 /* 2306 * ":menutrans clear": clear all translations. 2307 */ 2308 if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5))) 2309 { 2310 tp = (menutrans_T *)menutrans_ga.ga_data; 2311 for (i = 0; i < menutrans_ga.ga_len; ++i) 2312 { 2313 vim_free(tp[i].from); 2314 vim_free(tp[i].from_noamp); 2315 vim_free(tp[i].to); 2316 } 2317 ga_clear(&menutrans_ga); 2318 # ifdef FEAT_EVAL 2319 /* Delete all "menutrans_" global variables. */ 2320 del_menutrans_vars(); 2321 # endif 2322 } 2323 else 2324 { 2325 /* ":menutrans from to": add translation */ 2326 from = arg; 2327 arg = menu_skip_part(arg); 2328 to = skipwhite(arg); 2329 *arg = NUL; 2330 arg = menu_skip_part(to); 2331 if (arg == to) 2332 EMSG(_(e_invarg)); 2333 else 2334 { 2335 if (ga_grow(&menutrans_ga, 1) == OK) 2336 { 2337 tp = (menutrans_T *)menutrans_ga.ga_data; 2338 from = vim_strsave(from); 2339 from_noamp = menu_text(from, NULL, NULL); 2340 to = vim_strnsave(to, (int)(arg - to)); 2341 if (from != NULL && from_noamp != NULL && to != NULL) 2342 { 2343 tp[menutrans_ga.ga_len].from = from; 2344 tp[menutrans_ga.ga_len].from_noamp = from_noamp; 2345 tp[menutrans_ga.ga_len].to = to; 2346 ++menutrans_ga.ga_len; 2347 } 2348 else 2349 { 2350 vim_free(from); 2351 vim_free(from_noamp); 2352 vim_free(to); 2353 } 2354 } 2355 } 2356 } 2357 #endif 2358 } 2359 2360 #if defined(FEAT_MULTI_LANG) || defined(FEAT_TOOLBAR) 2361 /* 2362 * Find the character just after one part of a menu name. 2363 */ 2364 static char_u * 2365 menu_skip_part(p) 2366 char_u *p; 2367 { 2368 while (*p != NUL && *p != '.' && !vim_iswhite(*p)) 2369 { 2370 if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL) 2371 ++p; 2372 ++p; 2373 } 2374 return p; 2375 } 2376 #endif 2377 2378 #ifdef FEAT_MULTI_LANG 2379 /* 2380 * Lookup part of a menu name in the translations. 2381 * Return a pointer to the translation or NULL if not found. 2382 */ 2383 static char_u * 2384 menutrans_lookup(name, len) 2385 char_u *name; 2386 int len; 2387 { 2388 menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data; 2389 int i; 2390 char_u *dname; 2391 2392 for (i = 0; i < menutrans_ga.ga_len; ++i) 2393 if (STRNCMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL) 2394 return tp[i].to; 2395 2396 /* Now try again while ignoring '&' characters. */ 2397 i = name[len]; 2398 name[len] = NUL; 2399 dname = menu_text(name, NULL, NULL); 2400 name[len] = i; 2401 if (dname != NULL) 2402 { 2403 for (i = 0; i < menutrans_ga.ga_len; ++i) 2404 if (STRCMP(dname, tp[i].from_noamp) == 0) 2405 { 2406 vim_free(dname); 2407 return tp[i].to; 2408 } 2409 vim_free(dname); 2410 } 2411 2412 return NULL; 2413 } 2414 #endif /* FEAT_MULTI_LANG */ 2415 2416 #endif /* FEAT_MENU */ 2417