1 /* vi:set ts=8 sts=4 sw=4 noet: 2 * 3 * VIM - Vi IMproved by Bram Moolenaar 4 * 5 * Do ":help uganda" in Vim to read copying and usage conditions. 6 * Do ":help credits" in Vim to see a list of people who contributed. 7 * See README.txt for an overview of the Vim source code. 8 */ 9 10 /* 11 * help.c: functions for Vim help 12 */ 13 14 #include "vim.h" 15 16 /* 17 * ":help": open a read-only window on a help file 18 */ 19 void 20 ex_help(exarg_T *eap) 21 { 22 char_u *arg; 23 char_u *tag; 24 FILE *helpfd; // file descriptor of help file 25 int n; 26 int i; 27 win_T *wp; 28 int num_matches; 29 char_u **matches; 30 char_u *p; 31 int empty_fnum = 0; 32 int alt_fnum = 0; 33 buf_T *buf; 34 #ifdef FEAT_MULTI_LANG 35 int len; 36 char_u *lang; 37 #endif 38 #ifdef FEAT_FOLDING 39 int old_KeyTyped = KeyTyped; 40 #endif 41 42 if (ERROR_IF_ANY_POPUP_WINDOW) 43 return; 44 45 if (eap != NULL) 46 { 47 // A ":help" command ends at the first LF, or at a '|' that is 48 // followed by some text. Set nextcmd to the following command. 49 for (arg = eap->arg; *arg; ++arg) 50 { 51 if (*arg == '\n' || *arg == '\r' 52 || (*arg == '|' && arg[1] != NUL && arg[1] != '|')) 53 { 54 *arg++ = NUL; 55 eap->nextcmd = arg; 56 break; 57 } 58 } 59 arg = eap->arg; 60 61 if (eap->forceit && *arg == NUL && !curbuf->b_help) 62 { 63 emsg(_("E478: Don't panic!")); 64 return; 65 } 66 67 if (eap->skip) // not executing commands 68 return; 69 } 70 else 71 arg = (char_u *)""; 72 73 // remove trailing blanks 74 p = arg + STRLEN(arg) - 1; 75 while (p > arg && VIM_ISWHITE(*p) && p[-1] != '\\') 76 *p-- = NUL; 77 78 #ifdef FEAT_MULTI_LANG 79 // Check for a specified language 80 lang = check_help_lang(arg); 81 #endif 82 83 // When no argument given go to the index. 84 if (*arg == NUL) 85 arg = (char_u *)"help.txt"; 86 87 // Check if there is a match for the argument. 88 n = find_help_tags(arg, &num_matches, &matches, 89 eap != NULL && eap->forceit); 90 91 i = 0; 92 #ifdef FEAT_MULTI_LANG 93 if (n != FAIL && lang != NULL) 94 // Find first item with the requested language. 95 for (i = 0; i < num_matches; ++i) 96 { 97 len = (int)STRLEN(matches[i]); 98 if (len > 3 && matches[i][len - 3] == '@' 99 && STRICMP(matches[i] + len - 2, lang) == 0) 100 break; 101 } 102 #endif 103 if (i >= num_matches || n == FAIL) 104 { 105 #ifdef FEAT_MULTI_LANG 106 if (lang != NULL) 107 semsg(_("E661: Sorry, no '%s' help for %s"), lang, arg); 108 else 109 #endif 110 semsg(_("E149: Sorry, no help for %s"), arg); 111 if (n != FAIL) 112 FreeWild(num_matches, matches); 113 return; 114 } 115 116 // The first match (in the requested language) is the best match. 117 tag = vim_strsave(matches[i]); 118 FreeWild(num_matches, matches); 119 120 #ifdef FEAT_GUI 121 need_mouse_correct = TRUE; 122 #endif 123 124 // Re-use an existing help window or open a new one. 125 // Always open a new one for ":tab help". 126 if (!bt_help(curwin->w_buffer) || cmdmod.cmod_tab != 0) 127 { 128 if (cmdmod.cmod_tab != 0) 129 wp = NULL; 130 else 131 FOR_ALL_WINDOWS(wp) 132 if (bt_help(wp->w_buffer)) 133 break; 134 if (wp != NULL && wp->w_buffer->b_nwindows > 0) 135 win_enter(wp, TRUE); 136 else 137 { 138 // There is no help window yet. 139 // Try to open the file specified by the "helpfile" option. 140 if ((helpfd = mch_fopen((char *)p_hf, READBIN)) == NULL) 141 { 142 smsg(_("Sorry, help file \"%s\" not found"), p_hf); 143 goto erret; 144 } 145 fclose(helpfd); 146 147 // Split off help window; put it at far top if no position 148 // specified, the current window is vertically split and 149 // narrow. 150 n = WSP_HELP; 151 if (cmdmod.cmod_split == 0 && curwin->w_width != Columns 152 && curwin->w_width < 80) 153 n |= WSP_TOP; 154 if (win_split(0, n) == FAIL) 155 goto erret; 156 157 if (curwin->w_height < p_hh) 158 win_setheight((int)p_hh); 159 160 // Open help file (do_ecmd() will set b_help flag, readfile() will 161 // set b_p_ro flag). 162 // Set the alternate file to the previously edited file. 163 alt_fnum = curbuf->b_fnum; 164 (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL, 165 ECMD_HIDE + ECMD_SET_HELP, 166 NULL); // buffer is still open, don't store info 167 if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) 168 curwin->w_alt_fnum = alt_fnum; 169 empty_fnum = curbuf->b_fnum; 170 } 171 } 172 173 if (!p_im) 174 restart_edit = 0; // don't want insert mode in help file 175 176 #ifdef FEAT_FOLDING 177 // Restore KeyTyped, setting 'filetype=help' may reset it. 178 // It is needed for do_tag top open folds under the cursor. 179 KeyTyped = old_KeyTyped; 180 #endif 181 182 if (tag != NULL) 183 do_tag(tag, DT_HELP, 1, FALSE, TRUE); 184 185 // Delete the empty buffer if we're not using it. Careful: autocommands 186 // may have jumped to another window, check that the buffer is not in a 187 // window. 188 if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) 189 { 190 buf = buflist_findnr(empty_fnum); 191 if (buf != NULL && buf->b_nwindows == 0) 192 wipe_buffer(buf, TRUE); 193 } 194 195 // keep the previous alternate file 196 if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum 197 && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0) 198 curwin->w_alt_fnum = alt_fnum; 199 200 erret: 201 vim_free(tag); 202 } 203 204 /* 205 * ":helpclose": Close one help window 206 */ 207 void 208 ex_helpclose(exarg_T *eap UNUSED) 209 { 210 win_T *win; 211 212 FOR_ALL_WINDOWS(win) 213 { 214 if (bt_help(win->w_buffer)) 215 { 216 win_close(win, FALSE); 217 return; 218 } 219 } 220 } 221 222 #if defined(FEAT_MULTI_LANG) || defined(PROTO) 223 /* 224 * In an argument search for a language specifiers in the form "@xx". 225 * Changes the "@" to NUL if found, and returns a pointer to "xx". 226 * Returns NULL if not found. 227 */ 228 char_u * 229 check_help_lang(char_u *arg) 230 { 231 int len = (int)STRLEN(arg); 232 233 if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2]) 234 && ASCII_ISALPHA(arg[len - 1])) 235 { 236 arg[len - 3] = NUL; // remove the '@' 237 return arg + len - 2; 238 } 239 return NULL; 240 } 241 #endif 242 243 /* 244 * Return a heuristic indicating how well the given string matches. The 245 * smaller the number, the better the match. This is the order of priorities, 246 * from best match to worst match: 247 * - Match with least alphanumeric characters is better. 248 * - Match with least total characters is better. 249 * - Match towards the start is better. 250 * - Match starting with "+" is worse (feature instead of command) 251 * Assumption is made that the matched_string passed has already been found to 252 * match some string for which help is requested. webb. 253 */ 254 int 255 help_heuristic( 256 char_u *matched_string, 257 int offset, // offset for match 258 int wrong_case) // no matching case 259 { 260 int num_letters; 261 char_u *p; 262 263 num_letters = 0; 264 for (p = matched_string; *p; p++) 265 if (ASCII_ISALNUM(*p)) 266 num_letters++; 267 268 // Multiply the number of letters by 100 to give it a much bigger 269 // weighting than the number of characters. 270 // If there only is a match while ignoring case, add 5000. 271 // If the match starts in the middle of a word, add 10000 to put it 272 // somewhere in the last half. 273 // If the match is more than 2 chars from the start, multiply by 200 to 274 // put it after matches at the start. 275 if (ASCII_ISALNUM(matched_string[offset]) && offset > 0 276 && ASCII_ISALNUM(matched_string[offset - 1])) 277 offset += 10000; 278 else if (offset > 2) 279 offset *= 200; 280 if (wrong_case) 281 offset += 5000; 282 // Features are less interesting than the subjects themselves, but "+" 283 // alone is not a feature. 284 if (matched_string[0] == '+' && matched_string[1] != NUL) 285 offset += 100; 286 return (int)(100 * num_letters + STRLEN(matched_string) + offset); 287 } 288 289 /* 290 * Compare functions for qsort() below, that checks the help heuristics number 291 * that has been put after the tagname by find_tags(). 292 */ 293 static int 294 help_compare(const void *s1, const void *s2) 295 { 296 char *p1; 297 char *p2; 298 int cmp; 299 300 p1 = *(char **)s1 + strlen(*(char **)s1) + 1; 301 p2 = *(char **)s2 + strlen(*(char **)s2) + 1; 302 303 // Compare by help heuristic number first. 304 cmp = strcmp(p1, p2); 305 if (cmp != 0) 306 return cmp; 307 308 // Compare by strings as tie-breaker when same heuristic number. 309 return strcmp(*(char **)s1, *(char **)s2); 310 } 311 312 /* 313 * Find all help tags matching "arg", sort them and return in matches[], with 314 * the number of matches in num_matches. 315 * The matches will be sorted with a "best" match algorithm. 316 * When "keep_lang" is TRUE try keeping the language of the current buffer. 317 */ 318 int 319 find_help_tags( 320 char_u *arg, 321 int *num_matches, 322 char_u ***matches, 323 int keep_lang) 324 { 325 char_u *s, *d; 326 int i; 327 // Specific tags that either have a specific replacement or won't go 328 // through the generic rules. 329 static char *(except_tbl[][2]) = { 330 {"*", "star"}, 331 {"g*", "gstar"}, 332 {"[*", "[star"}, 333 {"]*", "]star"}, 334 {":*", ":star"}, 335 {"/*", "/star"}, 336 {"/\\*", "/\\\\star"}, 337 {"\"*", "quotestar"}, 338 {"**", "starstar"}, 339 {"cpo-*", "cpo-star"}, 340 {"/\\(\\)", "/\\\\(\\\\)"}, 341 {"/\\%(\\)", "/\\\\%(\\\\)"}, 342 {"?", "?"}, 343 {"??", "??"}, 344 {":?", ":?"}, 345 {"?<CR>", "?<CR>"}, 346 {"g?", "g?"}, 347 {"g?g?", "g?g?"}, 348 {"g??", "g??"}, 349 {"-?", "-?"}, 350 {"q?", "q?"}, 351 {"v_g?", "v_g?"}, 352 {"/\\?", "/\\\\?"}, 353 {"/\\z(\\)", "/\\\\z(\\\\)"}, 354 {"\\=", "\\\\="}, 355 {":s\\=", ":s\\\\="}, 356 {"[count]", "\\[count]"}, 357 {"[quotex]", "\\[quotex]"}, 358 {"[range]", "\\[range]"}, 359 {":[range]", ":\\[range]"}, 360 {"[pattern]", "\\[pattern]"}, 361 {"\\|", "\\\\bar"}, 362 {"\\%$", "/\\\\%\\$"}, 363 {"s/\\~", "s/\\\\\\~"}, 364 {"s/\\U", "s/\\\\U"}, 365 {"s/\\L", "s/\\\\L"}, 366 {"s/\\1", "s/\\\\1"}, 367 {"s/\\2", "s/\\\\2"}, 368 {"s/\\3", "s/\\\\3"}, 369 {"s/\\9", "s/\\\\9"}, 370 {NULL, NULL} 371 }; 372 static char *(expr_table[]) = {"!=?", "!~?", "<=?", "<?", "==?", "=~?", 373 ">=?", ">?", "is?", "isnot?"}; 374 int flags; 375 376 d = IObuff; // assume IObuff is long enough! 377 d[0] = NUL; 378 379 if (STRNICMP(arg, "expr-", 5) == 0) 380 { 381 // When the string starting with "expr-" and containing '?' and matches 382 // the table, it is taken literally (but ~ is escaped). Otherwise '?' 383 // is recognized as a wildcard. 384 for (i = (int)(sizeof(expr_table) / sizeof(char *)); --i >= 0; ) 385 if (STRCMP(arg + 5, expr_table[i]) == 0) 386 { 387 int si = 0, di = 0; 388 389 for (;;) 390 { 391 if (arg[si] == '~') 392 d[di++] = '\\'; 393 d[di++] = arg[si]; 394 if (arg[si] == NUL) 395 break; 396 ++si; 397 } 398 break; 399 } 400 } 401 else 402 { 403 // Recognize a few exceptions to the rule. Some strings that contain 404 // '*'are changed to "star", otherwise '*' is recognized as a wildcard. 405 for (i = 0; except_tbl[i][0] != NULL; ++i) 406 if (STRCMP(arg, except_tbl[i][0]) == 0) 407 { 408 STRCPY(d, except_tbl[i][1]); 409 break; 410 } 411 } 412 413 if (d[0] == NUL) // no match in table 414 { 415 // Replace "\S" with "/\\S", etc. Otherwise every tag is matched. 416 // Also replace "\%^" and "\%(", they match every tag too. 417 // Also "\zs", "\z1", etc. 418 // Also "\@<", "\@=", "\@<=", etc. 419 // And also "\_$" and "\_^". 420 if (arg[0] == '\\' 421 && ((arg[1] != NUL && arg[2] == NUL) 422 || (vim_strchr((char_u *)"%_z@", arg[1]) != NULL 423 && arg[2] != NUL))) 424 { 425 STRCPY(d, "/\\\\"); 426 STRCPY(d + 3, arg + 1); 427 // Check for "/\\_$", should be "/\\_\$" 428 if (d[3] == '_' && d[4] == '$') 429 STRCPY(d + 4, "\\$"); 430 } 431 else 432 { 433 // Replace: 434 // "[:...:]" with "\[:...:]" 435 // "[++...]" with "\[++...]" 436 // "\{" with "\\{" -- matching "} \}" 437 if ((arg[0] == '[' && (arg[1] == ':' 438 || (arg[1] == '+' && arg[2] == '+'))) 439 || (arg[0] == '\\' && arg[1] == '{')) 440 *d++ = '\\'; 441 442 // If tag starts with "('", skip the "(". Fixes CTRL-] on ('option'. 443 if (*arg == '(' && arg[1] == '\'') 444 arg++; 445 for (s = arg; *s; ++s) 446 { 447 // Replace "|" with "bar" and '"' with "quote" to match the name of 448 // the tags for these commands. 449 // Replace "*" with ".*" and "?" with "." to match command line 450 // completion. 451 // Insert a backslash before '~', '$' and '.' to avoid their 452 // special meaning. 453 if (d - IObuff > IOSIZE - 10) // getting too long!? 454 break; 455 switch (*s) 456 { 457 case '|': STRCPY(d, "bar"); 458 d += 3; 459 continue; 460 case '"': STRCPY(d, "quote"); 461 d += 5; 462 continue; 463 case '*': *d++ = '.'; 464 break; 465 case '?': *d++ = '.'; 466 continue; 467 case '$': 468 case '.': 469 case '~': *d++ = '\\'; 470 break; 471 } 472 473 // Replace "^x" by "CTRL-X". Don't do this for "^_" to make 474 // ":help i_^_CTRL-D" work. 475 // Insert '-' before and after "CTRL-X" when applicable. 476 if (*s < ' ' || (*s == '^' && s[1] && (ASCII_ISALPHA(s[1]) 477 || vim_strchr((char_u *)"?@[\\]^", s[1]) != NULL))) 478 { 479 if (d > IObuff && d[-1] != '_' && d[-1] != '\\') 480 *d++ = '_'; // prepend a '_' to make x_CTRL-x 481 STRCPY(d, "CTRL-"); 482 d += 5; 483 if (*s < ' ') 484 { 485 #ifdef EBCDIC 486 *d++ = CtrlChar(*s); 487 #else 488 *d++ = *s + '@'; 489 #endif 490 if (d[-1] == '\\') 491 *d++ = '\\'; // double a backslash 492 } 493 else 494 *d++ = *++s; 495 if (s[1] != NUL && s[1] != '_') 496 *d++ = '_'; // append a '_' 497 continue; 498 } 499 else if (*s == '^') // "^" or "CTRL-^" or "^_" 500 *d++ = '\\'; 501 502 // Insert a backslash before a backslash after a slash, for search 503 // pattern tags: "/\|" --> "/\\|". 504 else if (s[0] == '\\' && s[1] != '\\' 505 && *arg == '/' && s == arg + 1) 506 *d++ = '\\'; 507 508 // "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in 509 // "CTRL-\_CTRL-N" 510 if (STRNICMP(s, "CTRL-\\_", 7) == 0) 511 { 512 STRCPY(d, "CTRL-\\\\"); 513 d += 7; 514 s += 6; 515 } 516 517 *d++ = *s; 518 519 // If tag contains "({" or "([", tag terminates at the "(". 520 // This is for help on functions, e.g.: abs({expr}). 521 if (*s == '(' && (s[1] == '{' || s[1] =='[')) 522 break; 523 524 // If tag starts with ', toss everything after a second '. Fixes 525 // CTRL-] on 'option'. (would include the trailing '.'). 526 if (*s == '\'' && s > arg && *arg == '\'') 527 break; 528 // Also '{' and '}'. 529 if (*s == '}' && s > arg && *arg == '{') 530 break; 531 } 532 *d = NUL; 533 534 if (*IObuff == '`') 535 { 536 if (d > IObuff + 2 && d[-1] == '`') 537 { 538 // remove the backticks from `command` 539 mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff)); 540 d[-2] = NUL; 541 } 542 else if (d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') 543 { 544 // remove the backticks and comma from `command`, 545 mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff)); 546 d[-3] = NUL; 547 } 548 else if (d > IObuff + 4 && d[-3] == '`' 549 && d[-2] == '\\' && d[-1] == '.') 550 { 551 // remove the backticks and dot from `command`\. 552 mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff)); 553 d[-4] = NUL; 554 } 555 } 556 } 557 } 558 559 *matches = (char_u **)""; 560 *num_matches = 0; 561 flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC; 562 if (keep_lang) 563 flags |= TAG_KEEP_LANG; 564 if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK 565 && *num_matches > 0) 566 { 567 // Sort the matches found on the heuristic number that is after the 568 // tag name. 569 qsort((void *)*matches, (size_t)*num_matches, 570 sizeof(char_u *), help_compare); 571 // Delete more than TAG_MANY to reduce the size of the listing. 572 while (*num_matches > TAG_MANY) 573 vim_free((*matches)[--*num_matches]); 574 } 575 return OK; 576 } 577 578 #ifdef FEAT_MULTI_LANG 579 /* 580 * Cleanup matches for help tags: 581 * Remove "@ab" if the top of 'helplang' is "ab" and the language of the first 582 * tag matches it. Otherwise remove "@en" if "en" is the only language. 583 */ 584 void 585 cleanup_help_tags(int num_file, char_u **file) 586 { 587 int i, j; 588 int len; 589 char_u buf[4]; 590 char_u *p = buf; 591 592 if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) 593 { 594 *p++ = '@'; 595 *p++ = p_hlg[0]; 596 *p++ = p_hlg[1]; 597 } 598 *p = NUL; 599 600 for (i = 0; i < num_file; ++i) 601 { 602 len = (int)STRLEN(file[i]) - 3; 603 if (len <= 0) 604 continue; 605 if (STRCMP(file[i] + len, "@en") == 0) 606 { 607 // Sorting on priority means the same item in another language may 608 // be anywhere. Search all items for a match up to the "@en". 609 for (j = 0; j < num_file; ++j) 610 if (j != i && (int)STRLEN(file[j]) == len + 3 611 && STRNCMP(file[i], file[j], len + 1) == 0) 612 break; 613 if (j == num_file) 614 // item only exists with @en, remove it 615 file[i][len] = NUL; 616 } 617 } 618 619 if (*buf != NUL) 620 for (i = 0; i < num_file; ++i) 621 { 622 len = (int)STRLEN(file[i]) - 3; 623 if (len <= 0) 624 continue; 625 if (STRCMP(file[i] + len, buf) == 0) 626 { 627 // remove the default language 628 file[i][len] = NUL; 629 } 630 } 631 } 632 #endif 633 634 /* 635 * Called when starting to edit a buffer for a help file. 636 */ 637 void 638 prepare_help_buffer(void) 639 { 640 char_u *p; 641 642 curbuf->b_help = TRUE; 643 #ifdef FEAT_QUICKFIX 644 set_string_option_direct((char_u *)"buftype", -1, 645 (char_u *)"help", OPT_FREE|OPT_LOCAL, 0); 646 #endif 647 648 // Always set these options after jumping to a help tag, because the 649 // user may have an autocommand that gets in the way. 650 // When adding an option here, also update the help file helphelp.txt. 651 652 // Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and 653 // latin1 word characters (for translated help files). 654 // Only set it when needed, buf_init_chartab() is some work. 655 p = 656 #ifdef EBCDIC 657 (char_u *)"65-255,^*,^|,^\""; 658 #else 659 (char_u *)"!-~,^*,^|,^\",192-255"; 660 #endif 661 if (STRCMP(curbuf->b_p_isk, p) != 0) 662 { 663 set_string_option_direct((char_u *)"isk", -1, p, OPT_FREE|OPT_LOCAL, 0); 664 check_buf_options(curbuf); 665 (void)buf_init_chartab(curbuf, FALSE); 666 } 667 668 #ifdef FEAT_FOLDING 669 // Don't use the global foldmethod. 670 set_string_option_direct((char_u *)"fdm", -1, (char_u *)"manual", 671 OPT_FREE|OPT_LOCAL, 0); 672 #endif 673 674 curbuf->b_p_ts = 8; // 'tabstop' is 8 675 curwin->w_p_list = FALSE; // no list mode 676 677 curbuf->b_p_ma = FALSE; // not modifiable 678 curbuf->b_p_bin = FALSE; // reset 'bin' before reading file 679 curwin->w_p_nu = 0; // no line numbers 680 curwin->w_p_rnu = 0; // no relative line numbers 681 RESET_BINDING(curwin); // no scroll or cursor binding 682 #ifdef FEAT_ARABIC 683 curwin->w_p_arab = FALSE; // no arabic mode 684 #endif 685 #ifdef FEAT_RIGHTLEFT 686 curwin->w_p_rl = FALSE; // help window is left-to-right 687 #endif 688 #ifdef FEAT_FOLDING 689 curwin->w_p_fen = FALSE; // No folding in the help window 690 #endif 691 #ifdef FEAT_DIFF 692 curwin->w_p_diff = FALSE; // No 'diff' 693 #endif 694 #ifdef FEAT_SPELL 695 curwin->w_p_spell = FALSE; // No spell checking 696 #endif 697 698 set_buflisted(FALSE); 699 } 700 701 /* 702 * After reading a help file: May cleanup a help buffer when syntax 703 * highlighting is not used. 704 */ 705 void 706 fix_help_buffer(void) 707 { 708 linenr_T lnum; 709 char_u *line; 710 int in_example = FALSE; 711 int len; 712 char_u *fname; 713 char_u *p; 714 char_u *rt; 715 int mustfree; 716 717 // Set filetype to "help" if still needed. 718 if (STRCMP(curbuf->b_p_ft, "help") != 0) 719 { 720 ++curbuf_lock; 721 set_option_value((char_u *)"ft", 0L, (char_u *)"help", OPT_LOCAL); 722 --curbuf_lock; 723 } 724 725 #ifdef FEAT_SYN_HL 726 if (!syntax_present(curwin)) 727 #endif 728 { 729 for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) 730 { 731 line = ml_get_buf(curbuf, lnum, FALSE); 732 len = (int)STRLEN(line); 733 if (in_example && len > 0 && !VIM_ISWHITE(line[0])) 734 { 735 // End of example: non-white or '<' in first column. 736 if (line[0] == '<') 737 { 738 // blank-out a '<' in the first column 739 line = ml_get_buf(curbuf, lnum, TRUE); 740 line[0] = ' '; 741 } 742 in_example = FALSE; 743 } 744 if (!in_example && len > 0) 745 { 746 if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) 747 { 748 // blank-out a '>' in the last column (start of example) 749 line = ml_get_buf(curbuf, lnum, TRUE); 750 line[len - 1] = ' '; 751 in_example = TRUE; 752 } 753 else if (line[len - 1] == '~') 754 { 755 // blank-out a '~' at the end of line (header marker) 756 line = ml_get_buf(curbuf, lnum, TRUE); 757 line[len - 1] = ' '; 758 } 759 } 760 } 761 } 762 763 // In the "help.txt" and "help.abx" file, add the locally added help 764 // files. This uses the very first line in the help file. 765 fname = gettail(curbuf->b_fname); 766 if (fnamecmp(fname, "help.txt") == 0 767 #ifdef FEAT_MULTI_LANG 768 || (fnamencmp(fname, "help.", 5) == 0 769 && ASCII_ISALPHA(fname[5]) 770 && ASCII_ISALPHA(fname[6]) 771 && TOLOWER_ASC(fname[7]) == 'x' 772 && fname[8] == NUL) 773 #endif 774 ) 775 { 776 for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; ++lnum) 777 { 778 line = ml_get_buf(curbuf, lnum, FALSE); 779 if (strstr((char *)line, "*local-additions*") == NULL) 780 continue; 781 782 // Go through all directories in 'runtimepath', skipping 783 // $VIMRUNTIME. 784 p = p_rtp; 785 while (*p != NUL) 786 { 787 copy_option_part(&p, NameBuff, MAXPATHL, ","); 788 mustfree = FALSE; 789 rt = vim_getenv((char_u *)"VIMRUNTIME", &mustfree); 790 if (rt != NULL && 791 fullpathcmp(rt, NameBuff, FALSE, TRUE) != FPC_SAME) 792 { 793 int fcount; 794 char_u **fnames; 795 FILE *fd; 796 char_u *s; 797 int fi; 798 vimconv_T vc; 799 char_u *cp; 800 801 // Find all "doc/ *.txt" files in this directory. 802 add_pathsep(NameBuff); 803 #ifdef FEAT_MULTI_LANG 804 STRCAT(NameBuff, "doc/*.??[tx]"); 805 #else 806 STRCAT(NameBuff, "doc/*.txt"); 807 #endif 808 if (gen_expand_wildcards(1, &NameBuff, &fcount, 809 &fnames, EW_FILE|EW_SILENT) == OK 810 && fcount > 0) 811 { 812 #ifdef FEAT_MULTI_LANG 813 int i1, i2; 814 char_u *f1, *f2; 815 char_u *t1, *t2; 816 char_u *e1, *e2; 817 818 // If foo.abx is found use it instead of foo.txt in 819 // the same directory. 820 for (i1 = 0; i1 < fcount; ++i1) 821 { 822 for (i2 = 0; i2 < fcount; ++i2) 823 { 824 if (i1 == i2) 825 continue; 826 if (fnames[i1] == NULL || fnames[i2] == NULL) 827 continue; 828 f1 = fnames[i1]; 829 f2 = fnames[i2]; 830 t1 = gettail(f1); 831 t2 = gettail(f2); 832 e1 = vim_strrchr(t1, '.'); 833 e2 = vim_strrchr(t2, '.'); 834 if (e1 == NULL || e2 == NULL) 835 continue; 836 if (fnamecmp(e1, ".txt") != 0 837 && fnamecmp(e1, fname + 4) != 0) 838 { 839 // Not .txt and not .abx, remove it. 840 VIM_CLEAR(fnames[i1]); 841 continue; 842 } 843 if (e1 - f1 != e2 - f2 844 || fnamencmp(f1, f2, e1 - f1) != 0) 845 continue; 846 if (fnamecmp(e1, ".txt") == 0 847 && fnamecmp(e2, fname + 4) == 0) 848 // use .abx instead of .txt 849 VIM_CLEAR(fnames[i1]); 850 } 851 } 852 #endif 853 for (fi = 0; fi < fcount; ++fi) 854 { 855 if (fnames[fi] == NULL) 856 continue; 857 fd = mch_fopen((char *)fnames[fi], "r"); 858 if (fd != NULL) 859 { 860 vim_fgets(IObuff, IOSIZE, fd); 861 if (IObuff[0] == '*' 862 && (s = vim_strchr(IObuff + 1, '*')) 863 != NULL) 864 { 865 int this_utf = MAYBE; 866 867 // Change tag definition to a 868 // reference and remove <CR>/<NL>. 869 IObuff[0] = '|'; 870 *s = '|'; 871 while (*s != NUL) 872 { 873 if (*s == '\r' || *s == '\n') 874 *s = NUL; 875 // The text is utf-8 when a byte 876 // above 127 is found and no 877 // illegal byte sequence is found. 878 if (*s >= 0x80 && this_utf != FALSE) 879 { 880 int l; 881 882 this_utf = TRUE; 883 l = utf_ptr2len(s); 884 if (l == 1) 885 this_utf = FALSE; 886 s += l - 1; 887 } 888 ++s; 889 } 890 891 // The help file is latin1 or utf-8; 892 // conversion to the current 893 // 'encoding' may be required. 894 vc.vc_type = CONV_NONE; 895 convert_setup(&vc, (char_u *)( 896 this_utf == TRUE ? "utf-8" 897 : "latin1"), p_enc); 898 if (vc.vc_type == CONV_NONE) 899 // No conversion needed. 900 cp = IObuff; 901 else 902 { 903 // Do the conversion. If it fails 904 // use the unconverted text. 905 cp = string_convert(&vc, IObuff, 906 NULL); 907 if (cp == NULL) 908 cp = IObuff; 909 } 910 convert_setup(&vc, NULL, NULL); 911 912 ml_append(lnum, cp, (colnr_T)0, FALSE); 913 if (cp != IObuff) 914 vim_free(cp); 915 ++lnum; 916 } 917 fclose(fd); 918 } 919 } 920 FreeWild(fcount, fnames); 921 } 922 } 923 if (mustfree) 924 vim_free(rt); 925 } 926 break; 927 } 928 } 929 } 930 931 /* 932 * ":exusage" 933 */ 934 void 935 ex_exusage(exarg_T *eap UNUSED) 936 { 937 do_cmdline_cmd((char_u *)"help ex-cmd-index"); 938 } 939 940 /* 941 * ":viusage" 942 */ 943 void 944 ex_viusage(exarg_T *eap UNUSED) 945 { 946 do_cmdline_cmd((char_u *)"help normal-index"); 947 } 948 949 /* 950 * Generate tags in one help directory. 951 */ 952 static void 953 helptags_one( 954 char_u *dir, // doc directory 955 char_u *ext, // suffix, ".txt", ".itx", ".frx", etc. 956 char_u *tagfname, // "tags" for English, "tags-fr" for French. 957 int add_help_tags, // add "help-tags" tag 958 int ignore_writeerr) // ignore write error 959 { 960 FILE *fd_tags; 961 FILE *fd; 962 garray_T ga; 963 int filecount; 964 char_u **files; 965 char_u *p1, *p2; 966 int fi; 967 char_u *s; 968 int i; 969 char_u *fname; 970 int dirlen; 971 int utf8 = MAYBE; 972 int this_utf8; 973 int firstline; 974 int mix = FALSE; // detected mixed encodings 975 976 // Find all *.txt files. 977 dirlen = (int)STRLEN(dir); 978 STRCPY(NameBuff, dir); 979 STRCAT(NameBuff, "/**/*"); 980 STRCAT(NameBuff, ext); 981 if (gen_expand_wildcards(1, &NameBuff, &filecount, &files, 982 EW_FILE|EW_SILENT) == FAIL 983 || filecount == 0) 984 { 985 if (!got_int) 986 semsg(_("E151: No match: %s"), NameBuff); 987 return; 988 } 989 990 // Open the tags file for writing. 991 // Do this before scanning through all the files. 992 STRCPY(NameBuff, dir); 993 add_pathsep(NameBuff); 994 STRCAT(NameBuff, tagfname); 995 fd_tags = mch_fopen((char *)NameBuff, "w"); 996 if (fd_tags == NULL) 997 { 998 if (!ignore_writeerr) 999 semsg(_("E152: Cannot open %s for writing"), NameBuff); 1000 FreeWild(filecount, files); 1001 return; 1002 } 1003 1004 // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc" 1005 // add the "help-tags" tag. 1006 ga_init2(&ga, (int)sizeof(char_u *), 100); 1007 if (add_help_tags || fullpathcmp((char_u *)"$VIMRUNTIME/doc", 1008 dir, FALSE, TRUE) == FPC_SAME) 1009 { 1010 if (ga_grow(&ga, 1) == FAIL) 1011 got_int = TRUE; 1012 else 1013 { 1014 s = alloc(18 + (unsigned)STRLEN(tagfname)); 1015 if (s == NULL) 1016 got_int = TRUE; 1017 else 1018 { 1019 sprintf((char *)s, "help-tags\t%s\t1\n", tagfname); 1020 ((char_u **)ga.ga_data)[ga.ga_len] = s; 1021 ++ga.ga_len; 1022 } 1023 } 1024 } 1025 1026 // Go over all the files and extract the tags. 1027 for (fi = 0; fi < filecount && !got_int; ++fi) 1028 { 1029 fd = mch_fopen((char *)files[fi], "r"); 1030 if (fd == NULL) 1031 { 1032 semsg(_("E153: Unable to open %s for reading"), files[fi]); 1033 continue; 1034 } 1035 fname = files[fi] + dirlen + 1; 1036 1037 firstline = TRUE; 1038 while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) 1039 { 1040 if (firstline) 1041 { 1042 // Detect utf-8 file by a non-ASCII char in the first line. 1043 this_utf8 = MAYBE; 1044 for (s = IObuff; *s != NUL; ++s) 1045 if (*s >= 0x80) 1046 { 1047 int l; 1048 1049 this_utf8 = TRUE; 1050 l = utf_ptr2len(s); 1051 if (l == 1) 1052 { 1053 // Illegal UTF-8 byte sequence. 1054 this_utf8 = FALSE; 1055 break; 1056 } 1057 s += l - 1; 1058 } 1059 if (this_utf8 == MAYBE) // only ASCII characters found 1060 this_utf8 = FALSE; 1061 if (utf8 == MAYBE) // first file 1062 utf8 = this_utf8; 1063 else if (utf8 != this_utf8) 1064 { 1065 semsg(_("E670: Mix of help file encodings within a language: %s"), files[fi]); 1066 mix = !got_int; 1067 got_int = TRUE; 1068 } 1069 firstline = FALSE; 1070 } 1071 p1 = vim_strchr(IObuff, '*'); // find first '*' 1072 while (p1 != NULL) 1073 { 1074 // Use vim_strbyte() instead of vim_strchr() so that when 1075 // 'encoding' is dbcs it still works, don't find '*' in the 1076 // second byte. 1077 p2 = vim_strbyte(p1 + 1, '*'); // find second '*' 1078 if (p2 != NULL && p2 > p1 + 1) // skip "*" and "**" 1079 { 1080 for (s = p1 + 1; s < p2; ++s) 1081 if (*s == ' ' || *s == '\t' || *s == '|') 1082 break; 1083 1084 // Only accept a *tag* when it consists of valid 1085 // characters, there is white space before it and is 1086 // followed by a white character or end-of-line. 1087 if (s == p2 1088 && (p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t') 1089 && (vim_strchr((char_u *)" \t\n\r", s[1]) != NULL 1090 || s[1] == '\0')) 1091 { 1092 *p2 = '\0'; 1093 ++p1; 1094 if (ga_grow(&ga, 1) == FAIL) 1095 { 1096 got_int = TRUE; 1097 break; 1098 } 1099 s = alloc(p2 - p1 + STRLEN(fname) + 2); 1100 if (s == NULL) 1101 { 1102 got_int = TRUE; 1103 break; 1104 } 1105 ((char_u **)ga.ga_data)[ga.ga_len] = s; 1106 ++ga.ga_len; 1107 sprintf((char *)s, "%s\t%s", p1, fname); 1108 1109 // find next '*' 1110 p2 = vim_strchr(p2 + 1, '*'); 1111 } 1112 } 1113 p1 = p2; 1114 } 1115 line_breakcheck(); 1116 } 1117 1118 fclose(fd); 1119 } 1120 1121 FreeWild(filecount, files); 1122 1123 if (!got_int) 1124 { 1125 // Sort the tags. 1126 if (ga.ga_data != NULL) 1127 sort_strings((char_u **)ga.ga_data, ga.ga_len); 1128 1129 // Check for duplicates. 1130 for (i = 1; i < ga.ga_len; ++i) 1131 { 1132 p1 = ((char_u **)ga.ga_data)[i - 1]; 1133 p2 = ((char_u **)ga.ga_data)[i]; 1134 while (*p1 == *p2) 1135 { 1136 if (*p2 == '\t') 1137 { 1138 *p2 = NUL; 1139 vim_snprintf((char *)NameBuff, MAXPATHL, 1140 _("E154: Duplicate tag \"%s\" in file %s/%s"), 1141 ((char_u **)ga.ga_data)[i], dir, p2 + 1); 1142 emsg((char *)NameBuff); 1143 *p2 = '\t'; 1144 break; 1145 } 1146 ++p1; 1147 ++p2; 1148 } 1149 } 1150 1151 if (utf8 == TRUE) 1152 fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n"); 1153 1154 // Write the tags into the file. 1155 for (i = 0; i < ga.ga_len; ++i) 1156 { 1157 s = ((char_u **)ga.ga_data)[i]; 1158 if (STRNCMP(s, "help-tags\t", 10) == 0) 1159 // help-tags entry was added in formatted form 1160 fputs((char *)s, fd_tags); 1161 else 1162 { 1163 fprintf(fd_tags, "%s\t/*", s); 1164 for (p1 = s; *p1 != '\t'; ++p1) 1165 { 1166 // insert backslash before '\\' and '/' 1167 if (*p1 == '\\' || *p1 == '/') 1168 putc('\\', fd_tags); 1169 putc(*p1, fd_tags); 1170 } 1171 fprintf(fd_tags, "*\n"); 1172 } 1173 } 1174 } 1175 if (mix) 1176 got_int = FALSE; // continue with other languages 1177 1178 for (i = 0; i < ga.ga_len; ++i) 1179 vim_free(((char_u **)ga.ga_data)[i]); 1180 ga_clear(&ga); 1181 fclose(fd_tags); // there is no check for an error... 1182 } 1183 1184 /* 1185 * Generate tags in one help directory, taking care of translations. 1186 */ 1187 static void 1188 do_helptags(char_u *dirname, int add_help_tags, int ignore_writeerr) 1189 { 1190 #ifdef FEAT_MULTI_LANG 1191 int len; 1192 int i, j; 1193 garray_T ga; 1194 char_u lang[2]; 1195 char_u ext[5]; 1196 char_u fname[8]; 1197 int filecount; 1198 char_u **files; 1199 1200 // Get a list of all files in the help directory and in subdirectories. 1201 STRCPY(NameBuff, dirname); 1202 add_pathsep(NameBuff); 1203 STRCAT(NameBuff, "**"); 1204 if (gen_expand_wildcards(1, &NameBuff, &filecount, &files, 1205 EW_FILE|EW_SILENT) == FAIL 1206 || filecount == 0) 1207 { 1208 semsg(_("E151: No match: %s"), NameBuff); 1209 return; 1210 } 1211 1212 // Go over all files in the directory to find out what languages are 1213 // present. 1214 ga_init2(&ga, 1, 10); 1215 for (i = 0; i < filecount; ++i) 1216 { 1217 len = (int)STRLEN(files[i]); 1218 if (len > 4) 1219 { 1220 if (STRICMP(files[i] + len - 4, ".txt") == 0) 1221 { 1222 // ".txt" -> language "en" 1223 lang[0] = 'e'; 1224 lang[1] = 'n'; 1225 } 1226 else if (files[i][len - 4] == '.' 1227 && ASCII_ISALPHA(files[i][len - 3]) 1228 && ASCII_ISALPHA(files[i][len - 2]) 1229 && TOLOWER_ASC(files[i][len - 1]) == 'x') 1230 { 1231 // ".abx" -> language "ab" 1232 lang[0] = TOLOWER_ASC(files[i][len - 3]); 1233 lang[1] = TOLOWER_ASC(files[i][len - 2]); 1234 } 1235 else 1236 continue; 1237 1238 // Did we find this language already? 1239 for (j = 0; j < ga.ga_len; j += 2) 1240 if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0) 1241 break; 1242 if (j == ga.ga_len) 1243 { 1244 // New language, add it. 1245 if (ga_grow(&ga, 2) == FAIL) 1246 break; 1247 ((char_u *)ga.ga_data)[ga.ga_len++] = lang[0]; 1248 ((char_u *)ga.ga_data)[ga.ga_len++] = lang[1]; 1249 } 1250 } 1251 } 1252 1253 // Loop over the found languages to generate a tags file for each one. 1254 for (j = 0; j < ga.ga_len; j += 2) 1255 { 1256 STRCPY(fname, "tags-xx"); 1257 fname[5] = ((char_u *)ga.ga_data)[j]; 1258 fname[6] = ((char_u *)ga.ga_data)[j + 1]; 1259 if (fname[5] == 'e' && fname[6] == 'n') 1260 { 1261 // English is an exception: use ".txt" and "tags". 1262 fname[4] = NUL; 1263 STRCPY(ext, ".txt"); 1264 } 1265 else 1266 { 1267 // Language "ab" uses ".abx" and "tags-ab". 1268 STRCPY(ext, ".xxx"); 1269 ext[1] = fname[5]; 1270 ext[2] = fname[6]; 1271 } 1272 helptags_one(dirname, ext, fname, add_help_tags, ignore_writeerr); 1273 } 1274 1275 ga_clear(&ga); 1276 FreeWild(filecount, files); 1277 1278 #else 1279 // No language support, just use "*.txt" and "tags". 1280 helptags_one(dirname, (char_u *)".txt", (char_u *)"tags", add_help_tags, 1281 ignore_writeerr); 1282 #endif 1283 } 1284 1285 static void 1286 helptags_cb(char_u *fname, void *cookie) 1287 { 1288 do_helptags(fname, *(int *)cookie, TRUE); 1289 } 1290 1291 /* 1292 * ":helptags" 1293 */ 1294 void 1295 ex_helptags(exarg_T *eap) 1296 { 1297 expand_T xpc; 1298 char_u *dirname; 1299 int add_help_tags = FALSE; 1300 1301 // Check for ":helptags ++t {dir}". 1302 if (STRNCMP(eap->arg, "++t", 3) == 0 && VIM_ISWHITE(eap->arg[3])) 1303 { 1304 add_help_tags = TRUE; 1305 eap->arg = skipwhite(eap->arg + 3); 1306 } 1307 1308 if (STRCMP(eap->arg, "ALL") == 0) 1309 { 1310 do_in_path(p_rtp, (char_u *)"doc", DIP_ALL + DIP_DIR, 1311 helptags_cb, &add_help_tags); 1312 } 1313 else 1314 { 1315 ExpandInit(&xpc); 1316 xpc.xp_context = EXPAND_DIRECTORIES; 1317 dirname = ExpandOne(&xpc, eap->arg, NULL, 1318 WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE); 1319 if (dirname == NULL || !mch_isdir(dirname)) 1320 semsg(_("E150: Not a directory: %s"), eap->arg); 1321 else 1322 do_helptags(dirname, add_help_tags, FALSE); 1323 vim_free(dirname); 1324 } 1325 } 1326