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