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