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