blob: 106236201188eeff758e618cd25fc253a3283a8d [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 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +000063 emsg(_(e_dont_panic));
Bram Moolenaarf868ba82020-07-21 21:07:20 +020064 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)
Bram Moolenaara6f79292022-01-04 21:30:47 +0000107 semsg(_(e_sorry_no_str_help_for_str), lang, arg);
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200108 else
109#endif
Bram Moolenaareb822a22021-12-31 15:09:27 +0000110 semsg(_(e_sorry_no_help_for_str), arg);
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200111 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".
Bram Moolenaare1004402020-10-24 20:49:43 +0200126 if (!bt_help(curwin->w_buffer) || cmdmod.cmod_tab != 0)
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200127 {
Bram Moolenaare1004402020-10-24 20:49:43 +0200128 if (cmdmod.cmod_tab != 0)
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200129 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;
Bram Moolenaare1004402020-10-24 20:49:43 +0200151 if (cmdmod.cmod_split == 0 && curwin->w_width != Columns
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200152 && curwin->w_width < 80)
Bram Moolenaar28f7e702022-10-09 15:54:53 +0100153 n |= p_sb ? WSP_BOT : WSP_TOP;
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200154 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
Bram Moolenaare1004402020-10-24 20:49:43 +0200167 if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0)
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200168 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
Bram Moolenaare1004402020-10-24 20:49:43 +0200196 if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum
197 && (cmdmod.cmod_flags & CMOD_KEEPALT) == 0)
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200198 curwin->w_alt_fnum = alt_fnum;
199
200erret:
201 vim_free(tag);
202}
203
204/*
205 * ":helpclose": Close one help window
206 */
207 void
208ex_helpclose(exarg_T *eap UNUSED)
209{
210 win_T *win;
211
212 FOR_ALL_WINDOWS(win)
213 {
214 if (bt_help(win->w_buffer))
215 {
216 win_close(win, FALSE);
217 return;
218 }
219 }
220}
221
222#if defined(FEAT_MULTI_LANG) || defined(PROTO)
223/*
224 * In an argument search for a language specifiers in the form "@xx".
225 * Changes the "@" to NUL if found, and returns a pointer to "xx".
226 * Returns NULL if not found.
227 */
228 char_u *
229check_help_lang(char_u *arg)
230{
231 int len = (int)STRLEN(arg);
232
233 if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2])
234 && ASCII_ISALPHA(arg[len - 1]))
235 {
236 arg[len - 3] = NUL; // remove the '@'
237 return arg + len - 2;
238 }
239 return NULL;
240}
241#endif
242
243/*
244 * Return a heuristic indicating how well the given string matches. The
245 * smaller the number, the better the match. This is the order of priorities,
246 * from best match to worst match:
247 * - Match with least alphanumeric characters is better.
248 * - Match with least total characters is better.
249 * - Match towards the start is better.
250 * - Match starting with "+" is worse (feature instead of command)
251 * Assumption is made that the matched_string passed has already been found to
252 * match some string for which help is requested. webb.
253 */
254 int
255help_heuristic(
256 char_u *matched_string,
257 int offset, // offset for match
258 int wrong_case) // no matching case
259{
260 int num_letters;
261 char_u *p;
262
263 num_letters = 0;
264 for (p = matched_string; *p; p++)
265 if (ASCII_ISALNUM(*p))
266 num_letters++;
267
268 // Multiply the number of letters by 100 to give it a much bigger
269 // weighting than the number of characters.
270 // If there only is a match while ignoring case, add 5000.
271 // If the match starts in the middle of a word, add 10000 to put it
272 // somewhere in the last half.
273 // If the match is more than 2 chars from the start, multiply by 200 to
274 // put it after matches at the start.
275 if (ASCII_ISALNUM(matched_string[offset]) && offset > 0
276 && ASCII_ISALNUM(matched_string[offset - 1]))
277 offset += 10000;
278 else if (offset > 2)
279 offset *= 200;
280 if (wrong_case)
281 offset += 5000;
282 // Features are less interesting than the subjects themselves, but "+"
283 // alone is not a feature.
284 if (matched_string[0] == '+' && matched_string[1] != NUL)
285 offset += 100;
286 return (int)(100 * num_letters + STRLEN(matched_string) + offset);
287}
288
289/*
290 * Compare functions for qsort() below, that checks the help heuristics number
291 * that has been put after the tagname by find_tags().
292 */
293 static int
294help_compare(const void *s1, const void *s2)
295{
296 char *p1;
297 char *p2;
298 int cmp;
299
300 p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
301 p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
302
303 // Compare by help heuristic number first.
304 cmp = strcmp(p1, p2);
305 if (cmp != 0)
306 return cmp;
307
308 // Compare by strings as tie-breaker when same heuristic number.
309 return strcmp(*(char **)s1, *(char **)s2);
310}
311
312/*
313 * Find all help tags matching "arg", sort them and return in matches[], with
314 * the number of matches in num_matches.
315 * The matches will be sorted with a "best" match algorithm.
316 * When "keep_lang" is TRUE try keeping the language of the current buffer.
317 */
318 int
319find_help_tags(
320 char_u *arg,
321 int *num_matches,
322 char_u ***matches,
323 int keep_lang)
324{
325 char_u *s, *d;
326 int i;
Bram Moolenaar6eb36ad2020-10-11 19:08:33 +0200327 // Specific tags that either have a specific replacement or won't go
Bram Moolenaar8e7d6222020-12-18 19:49:56 +0100328 // through the generic rules.
Bram Moolenaar6eb36ad2020-10-11 19:08:33 +0200329 static char *(except_tbl[][2]) = {
330 {"*", "star"},
331 {"g*", "gstar"},
332 {"[*", "[star"},
333 {"]*", "]star"},
334 {":*", ":star"},
335 {"/*", "/star"},
336 {"/\\*", "/\\\\star"},
337 {"\"*", "quotestar"},
338 {"**", "starstar"},
339 {"cpo-*", "cpo-star"},
340 {"/\\(\\)", "/\\\\(\\\\)"},
341 {"/\\%(\\)", "/\\\\%(\\\\)"},
342 {"?", "?"},
343 {"??", "??"},
344 {":?", ":?"},
345 {"?<CR>", "?<CR>"},
346 {"g?", "g?"},
347 {"g?g?", "g?g?"},
348 {"g??", "g??"},
349 {"-?", "-?"},
350 {"q?", "q?"},
351 {"v_g?", "v_g?"},
352 {"/\\?", "/\\\\?"},
353 {"/\\z(\\)", "/\\\\z(\\\\)"},
354 {"\\=", "\\\\="},
355 {":s\\=", ":s\\\\="},
356 {"[count]", "\\[count]"},
357 {"[quotex]", "\\[quotex]"},
358 {"[range]", "\\[range]"},
359 {":[range]", ":\\[range]"},
360 {"[pattern]", "\\[pattern]"},
361 {"\\|", "\\\\bar"},
362 {"\\%$", "/\\\\%\\$"},
363 {"s/\\~", "s/\\\\\\~"},
364 {"s/\\U", "s/\\\\U"},
365 {"s/\\L", "s/\\\\L"},
366 {"s/\\1", "s/\\\\1"},
367 {"s/\\2", "s/\\\\2"},
368 {"s/\\3", "s/\\\\3"},
369 {"s/\\9", "s/\\\\9"},
370 {NULL, NULL}
371 };
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200372 static char *(expr_table[]) = {"!=?", "!~?", "<=?", "<?", "==?", "=~?",
Bram Moolenaar6eb36ad2020-10-11 19:08:33 +0200373 ">=?", ">?", "is?", "isnot?"};
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200374 int flags;
375
376 d = IObuff; // assume IObuff is long enough!
Bram Moolenaar6eb36ad2020-10-11 19:08:33 +0200377 d[0] = NUL;
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200378
379 if (STRNICMP(arg, "expr-", 5) == 0)
380 {
381 // When the string starting with "expr-" and containing '?' and matches
382 // the table, it is taken literally (but ~ is escaped). Otherwise '?'
383 // is recognized as a wildcard.
K.Takataeeec2542021-06-02 13:28:16 +0200384 for (i = (int)ARRAY_LENGTH(expr_table); --i >= 0; )
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200385 if (STRCMP(arg + 5, expr_table[i]) == 0)
386 {
387 int si = 0, di = 0;
388
389 for (;;)
390 {
391 if (arg[si] == '~')
392 d[di++] = '\\';
393 d[di++] = arg[si];
394 if (arg[si] == NUL)
395 break;
396 ++si;
397 }
398 break;
399 }
400 }
401 else
402 {
403 // Recognize a few exceptions to the rule. Some strings that contain
Bram Moolenaar6eb36ad2020-10-11 19:08:33 +0200404 // '*'are changed to "star", otherwise '*' is recognized as a wildcard.
405 for (i = 0; except_tbl[i][0] != NULL; ++i)
406 if (STRCMP(arg, except_tbl[i][0]) == 0)
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200407 {
Bram Moolenaar6eb36ad2020-10-11 19:08:33 +0200408 STRCPY(d, except_tbl[i][1]);
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200409 break;
410 }
411 }
412
Bram Moolenaar6eb36ad2020-10-11 19:08:33 +0200413 if (d[0] == NUL) // no match in table
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200414 {
415 // Replace "\S" with "/\\S", etc. Otherwise every tag is matched.
416 // Also replace "\%^" and "\%(", they match every tag too.
417 // Also "\zs", "\z1", etc.
418 // Also "\@<", "\@=", "\@<=", etc.
419 // And also "\_$" and "\_^".
420 if (arg[0] == '\\'
421 && ((arg[1] != NUL && arg[2] == NUL)
422 || (vim_strchr((char_u *)"%_z@", arg[1]) != NULL
423 && arg[2] != NUL)))
424 {
Bram Moolenaarbd228fd2021-11-25 10:50:12 +0000425 vim_snprintf((char *)d, IOSIZE, "/\\\\%s", arg + 1);
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200426 // 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 {
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200484 *d++ = *s + '@';
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200485 if (d[-1] == '\\')
486 *d++ = '\\'; // double a backslash
487 }
488 else
489 *d++ = *++s;
490 if (s[1] != NUL && s[1] != '_')
491 *d++ = '_'; // append a '_'
492 continue;
493 }
494 else if (*s == '^') // "^" or "CTRL-^" or "^_"
495 *d++ = '\\';
496
497 // Insert a backslash before a backslash after a slash, for search
498 // pattern tags: "/\|" --> "/\\|".
499 else if (s[0] == '\\' && s[1] != '\\'
500 && *arg == '/' && s == arg + 1)
501 *d++ = '\\';
502
503 // "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in
504 // "CTRL-\_CTRL-N"
505 if (STRNICMP(s, "CTRL-\\_", 7) == 0)
506 {
507 STRCPY(d, "CTRL-\\\\");
508 d += 7;
509 s += 6;
510 }
511
512 *d++ = *s;
513
514 // If tag contains "({" or "([", tag terminates at the "(".
515 // This is for help on functions, e.g.: abs({expr}).
516 if (*s == '(' && (s[1] == '{' || s[1] =='['))
517 break;
518
519 // If tag starts with ', toss everything after a second '. Fixes
520 // CTRL-] on 'option'. (would include the trailing '.').
521 if (*s == '\'' && s > arg && *arg == '\'')
522 break;
523 // Also '{' and '}'.
524 if (*s == '}' && s > arg && *arg == '{')
525 break;
526 }
527 *d = NUL;
528
529 if (*IObuff == '`')
530 {
531 if (d > IObuff + 2 && d[-1] == '`')
532 {
533 // remove the backticks from `command`
534 mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff));
535 d[-2] = NUL;
536 }
537 else if (d > IObuff + 3 && d[-2] == '`' && d[-1] == ',')
538 {
539 // remove the backticks and comma from `command`,
540 mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff));
541 d[-3] = NUL;
542 }
543 else if (d > IObuff + 4 && d[-3] == '`'
544 && d[-2] == '\\' && d[-1] == '.')
545 {
546 // remove the backticks and dot from `command`\.
547 mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff));
548 d[-4] = NUL;
549 }
550 }
551 }
552 }
553
554 *matches = (char_u **)"";
555 *num_matches = 0;
556 flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC;
557 if (keep_lang)
558 flags |= TAG_KEEP_LANG;
559 if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK
560 && *num_matches > 0)
561 {
562 // Sort the matches found on the heuristic number that is after the
563 // tag name.
564 qsort((void *)*matches, (size_t)*num_matches,
565 sizeof(char_u *), help_compare);
566 // Delete more than TAG_MANY to reduce the size of the listing.
567 while (*num_matches > TAG_MANY)
568 vim_free((*matches)[--*num_matches]);
569 }
570 return OK;
571}
572
573#ifdef FEAT_MULTI_LANG
574/*
575 * Cleanup matches for help tags:
576 * Remove "@ab" if the top of 'helplang' is "ab" and the language of the first
577 * tag matches it. Otherwise remove "@en" if "en" is the only language.
578 */
579 void
580cleanup_help_tags(int num_file, char_u **file)
581{
582 int i, j;
583 int len;
584 char_u buf[4];
585 char_u *p = buf;
586
587 if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n'))
588 {
589 *p++ = '@';
590 *p++ = p_hlg[0];
591 *p++ = p_hlg[1];
592 }
593 *p = NUL;
594
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, "@en") == 0)
601 {
602 // Sorting on priority means the same item in another language may
603 // be anywhere. Search all items for a match up to the "@en".
604 for (j = 0; j < num_file; ++j)
605 if (j != i && (int)STRLEN(file[j]) == len + 3
606 && STRNCMP(file[i], file[j], len + 1) == 0)
607 break;
608 if (j == num_file)
609 // item only exists with @en, remove it
610 file[i][len] = NUL;
611 }
612 }
613
614 if (*buf != NUL)
615 for (i = 0; i < num_file; ++i)
616 {
617 len = (int)STRLEN(file[i]) - 3;
618 if (len <= 0)
619 continue;
620 if (STRCMP(file[i] + len, buf) == 0)
621 {
622 // remove the default language
623 file[i][len] = NUL;
624 }
625 }
626}
627#endif
628
629/*
630 * Called when starting to edit a buffer for a help file.
631 */
632 void
633prepare_help_buffer(void)
634{
635 char_u *p;
636
637 curbuf->b_help = TRUE;
638#ifdef FEAT_QUICKFIX
639 set_string_option_direct((char_u *)"buftype", -1,
640 (char_u *)"help", OPT_FREE|OPT_LOCAL, 0);
641#endif
642
643 // Always set these options after jumping to a help tag, because the
644 // user may have an autocommand that gets in the way.
Bram Moolenaar8e7d6222020-12-18 19:49:56 +0100645 // When adding an option here, also update the help file helphelp.txt.
646
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200647 // Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and
648 // latin1 word characters (for translated help files).
649 // Only set it when needed, buf_init_chartab() is some work.
Bram Moolenaar424bcae2022-01-31 14:59:41 +0000650 p = (char_u *)"!-~,^*,^|,^\",192-255";
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200651 if (STRCMP(curbuf->b_p_isk, p) != 0)
652 {
653 set_string_option_direct((char_u *)"isk", -1, p, OPT_FREE|OPT_LOCAL, 0);
654 check_buf_options(curbuf);
655 (void)buf_init_chartab(curbuf, FALSE);
656 }
657
658#ifdef FEAT_FOLDING
659 // Don't use the global foldmethod.
660 set_string_option_direct((char_u *)"fdm", -1, (char_u *)"manual",
661 OPT_FREE|OPT_LOCAL, 0);
662#endif
663
664 curbuf->b_p_ts = 8; // 'tabstop' is 8
665 curwin->w_p_list = FALSE; // no list mode
666
667 curbuf->b_p_ma = FALSE; // not modifiable
668 curbuf->b_p_bin = FALSE; // reset 'bin' before reading file
669 curwin->w_p_nu = 0; // no line numbers
670 curwin->w_p_rnu = 0; // no relative line numbers
671 RESET_BINDING(curwin); // no scroll or cursor binding
672#ifdef FEAT_ARABIC
673 curwin->w_p_arab = FALSE; // no arabic mode
674#endif
675#ifdef FEAT_RIGHTLEFT
676 curwin->w_p_rl = FALSE; // help window is left-to-right
677#endif
678#ifdef FEAT_FOLDING
679 curwin->w_p_fen = FALSE; // No folding in the help window
680#endif
681#ifdef FEAT_DIFF
682 curwin->w_p_diff = FALSE; // No 'diff'
683#endif
684#ifdef FEAT_SPELL
685 curwin->w_p_spell = FALSE; // No spell checking
686#endif
687
688 set_buflisted(FALSE);
689}
690
691/*
692 * After reading a help file: May cleanup a help buffer when syntax
693 * highlighting is not used.
694 */
695 void
696fix_help_buffer(void)
697{
698 linenr_T lnum;
699 char_u *line;
700 int in_example = FALSE;
701 int len;
702 char_u *fname;
703 char_u *p;
704 char_u *rt;
705 int mustfree;
706
707 // Set filetype to "help" if still needed.
708 if (STRCMP(curbuf->b_p_ft, "help") != 0)
709 {
710 ++curbuf_lock;
Bram Moolenaar31e5c602022-04-15 13:53:33 +0100711 set_option_value_give_err((char_u *)"ft",
712 0L, (char_u *)"help", OPT_LOCAL);
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200713 --curbuf_lock;
714 }
715
716#ifdef FEAT_SYN_HL
717 if (!syntax_present(curwin))
718#endif
719 {
720 for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum)
721 {
722 line = ml_get_buf(curbuf, lnum, FALSE);
zeertzjq94b7c322024-03-12 21:50:32 +0100723 len = ml_get_buf_len(curbuf, lnum);
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200724 if (in_example && len > 0 && !VIM_ISWHITE(line[0]))
725 {
726 // End of example: non-white or '<' in first column.
727 if (line[0] == '<')
728 {
729 // blank-out a '<' in the first column
730 line = ml_get_buf(curbuf, lnum, TRUE);
731 line[0] = ' ';
732 }
733 in_example = FALSE;
734 }
735 if (!in_example && len > 0)
736 {
737 if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' '))
738 {
739 // blank-out a '>' in the last column (start of example)
740 line = ml_get_buf(curbuf, lnum, TRUE);
741 line[len - 1] = ' ';
742 in_example = TRUE;
743 }
744 else if (line[len - 1] == '~')
745 {
746 // blank-out a '~' at the end of line (header marker)
747 line = ml_get_buf(curbuf, lnum, TRUE);
748 line[len - 1] = ' ';
749 }
750 }
751 }
752 }
753
754 // In the "help.txt" and "help.abx" file, add the locally added help
755 // files. This uses the very first line in the help file.
756 fname = gettail(curbuf->b_fname);
757 if (fnamecmp(fname, "help.txt") == 0
758#ifdef FEAT_MULTI_LANG
759 || (fnamencmp(fname, "help.", 5) == 0
760 && ASCII_ISALPHA(fname[5])
761 && ASCII_ISALPHA(fname[6])
762 && TOLOWER_ASC(fname[7]) == 'x'
763 && fname[8] == NUL)
764#endif
765 )
766 {
767 for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; ++lnum)
768 {
769 line = ml_get_buf(curbuf, lnum, FALSE);
770 if (strstr((char *)line, "*local-additions*") == NULL)
771 continue;
772
773 // Go through all directories in 'runtimepath', skipping
774 // $VIMRUNTIME.
775 p = p_rtp;
776 while (*p != NUL)
777 {
778 copy_option_part(&p, NameBuff, MAXPATHL, ",");
779 mustfree = FALSE;
780 rt = vim_getenv((char_u *)"VIMRUNTIME", &mustfree);
781 if (rt != NULL &&
782 fullpathcmp(rt, NameBuff, FALSE, TRUE) != FPC_SAME)
783 {
784 int fcount;
785 char_u **fnames;
786 FILE *fd;
787 char_u *s;
788 int fi;
789 vimconv_T vc;
790 char_u *cp;
791
792 // Find all "doc/ *.txt" files in this directory.
793 add_pathsep(NameBuff);
794#ifdef FEAT_MULTI_LANG
795 STRCAT(NameBuff, "doc/*.??[tx]");
796#else
797 STRCAT(NameBuff, "doc/*.txt");
798#endif
799 if (gen_expand_wildcards(1, &NameBuff, &fcount,
800 &fnames, EW_FILE|EW_SILENT) == OK
801 && fcount > 0)
802 {
803#ifdef FEAT_MULTI_LANG
804 int i1, i2;
805 char_u *f1, *f2;
806 char_u *t1, *t2;
807 char_u *e1, *e2;
808
809 // If foo.abx is found use it instead of foo.txt in
810 // the same directory.
811 for (i1 = 0; i1 < fcount; ++i1)
812 {
h-east0e2508d2022-01-03 12:53:24 +0000813 f1 = fnames[i1];
814 t1 = gettail(f1);
815 e1 = vim_strrchr(t1, '.');
zeertzjq62d86172024-08-03 14:52:00 +0200816 if (e1 == NULL)
817 continue;
h-east0e2508d2022-01-03 12:53:24 +0000818 if (fnamecmp(e1, ".txt") != 0
819 && fnamecmp(e1, fname + 4) != 0)
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200820 {
h-east0e2508d2022-01-03 12:53:24 +0000821 // Not .txt and not .abx, remove it.
822 VIM_CLEAR(fnames[i1]);
823 continue;
824 }
825
826 for (i2 = i1 + 1; i2 < fcount; ++i2)
827 {
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200828 f2 = fnames[i2];
h-east0e2508d2022-01-03 12:53:24 +0000829 if (f2 == NULL)
830 continue;
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200831 t2 = gettail(f2);
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200832 e2 = vim_strrchr(t2, '.');
zeertzjq62d86172024-08-03 14:52:00 +0200833 if (e2 == NULL)
834 continue;
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200835 if (e1 - f1 != e2 - f2
836 || fnamencmp(f1, f2, e1 - f1) != 0)
837 continue;
838 if (fnamecmp(e1, ".txt") == 0
h-east0e2508d2022-01-03 12:53:24 +0000839 && fnamecmp(e2, fname + 4) == 0)
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200840 // use .abx instead of .txt
841 VIM_CLEAR(fnames[i1]);
842 }
843 }
844#endif
845 for (fi = 0; fi < fcount; ++fi)
846 {
847 if (fnames[fi] == NULL)
848 continue;
849 fd = mch_fopen((char *)fnames[fi], "r");
850 if (fd != NULL)
851 {
852 vim_fgets(IObuff, IOSIZE, fd);
853 if (IObuff[0] == '*'
854 && (s = vim_strchr(IObuff + 1, '*'))
855 != NULL)
856 {
857 int this_utf = MAYBE;
858
859 // Change tag definition to a
860 // reference and remove <CR>/<NL>.
861 IObuff[0] = '|';
862 *s = '|';
863 while (*s != NUL)
864 {
865 if (*s == '\r' || *s == '\n')
866 *s = NUL;
867 // The text is utf-8 when a byte
868 // above 127 is found and no
869 // illegal byte sequence is found.
870 if (*s >= 0x80 && this_utf != FALSE)
871 {
872 int l;
873
874 this_utf = TRUE;
875 l = utf_ptr2len(s);
876 if (l == 1)
877 this_utf = FALSE;
878 s += l - 1;
879 }
880 ++s;
881 }
882
883 // The help file is latin1 or utf-8;
884 // conversion to the current
885 // 'encoding' may be required.
886 vc.vc_type = CONV_NONE;
887 convert_setup(&vc, (char_u *)(
888 this_utf == TRUE ? "utf-8"
889 : "latin1"), p_enc);
890 if (vc.vc_type == CONV_NONE)
891 // No conversion needed.
892 cp = IObuff;
893 else
894 {
895 // Do the conversion. If it fails
896 // use the unconverted text.
897 cp = string_convert(&vc, IObuff,
898 NULL);
899 if (cp == NULL)
900 cp = IObuff;
901 }
902 convert_setup(&vc, NULL, NULL);
903
904 ml_append(lnum, cp, (colnr_T)0, FALSE);
905 if (cp != IObuff)
906 vim_free(cp);
907 ++lnum;
908 }
909 fclose(fd);
910 }
911 }
912 FreeWild(fcount, fnames);
913 }
914 }
915 if (mustfree)
916 vim_free(rt);
917 }
918 break;
919 }
920 }
921}
922
923/*
924 * ":exusage"
925 */
926 void
927ex_exusage(exarg_T *eap UNUSED)
928{
929 do_cmdline_cmd((char_u *)"help ex-cmd-index");
930}
931
932/*
933 * ":viusage"
934 */
935 void
936ex_viusage(exarg_T *eap UNUSED)
937{
938 do_cmdline_cmd((char_u *)"help normal-index");
939}
940
941/*
942 * Generate tags in one help directory.
943 */
944 static void
945helptags_one(
946 char_u *dir, // doc directory
947 char_u *ext, // suffix, ".txt", ".itx", ".frx", etc.
948 char_u *tagfname, // "tags" for English, "tags-fr" for French.
949 int add_help_tags, // add "help-tags" tag
950 int ignore_writeerr) // ignore write error
951{
952 FILE *fd_tags;
953 FILE *fd;
954 garray_T ga;
Bram Moolenaar90da27b2022-03-25 14:54:18 +0000955 int res;
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200956 int filecount;
957 char_u **files;
958 char_u *p1, *p2;
959 int fi;
960 char_u *s;
961 int i;
962 char_u *fname;
963 int dirlen;
964 int utf8 = MAYBE;
965 int this_utf8;
966 int firstline;
Carlo Teubnerddab3ce2022-07-30 12:03:16 +0100967 int in_example;
968 int len;
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200969 int mix = FALSE; // detected mixed encodings
970
971 // Find all *.txt files.
972 dirlen = (int)STRLEN(dir);
973 STRCPY(NameBuff, dir);
974 STRCAT(NameBuff, "/**/*");
975 STRCAT(NameBuff, ext);
Bram Moolenaar90da27b2022-03-25 14:54:18 +0000976 res = gen_expand_wildcards(1, &NameBuff, &filecount, &files,
977 EW_FILE|EW_SILENT);
978 if (res == FAIL || filecount == 0)
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200979 {
980 if (!got_int)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +0000981 semsg(_(e_no_match_str_1), NameBuff);
Bram Moolenaar90da27b2022-03-25 14:54:18 +0000982 if (res != FAIL)
983 FreeWild(filecount, files);
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200984 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)
Bram Moolenaar1a992222021-12-31 17:25:48 +0000996 semsg(_(e_cannot_open_str_for_writing_1), NameBuff);
Bram Moolenaarf868ba82020-07-21 21:07:20 +0200997 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.
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001003 ga_init2(&ga, sizeof(char_u *), 100);
Bram Moolenaarf868ba82020-07-21 21:07:20 +02001004 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 {
Bram Moolenaareb822a22021-12-31 15:09:27 +00001029 semsg(_(e_unable_to_open_str_for_reading), files[fi]);
Bram Moolenaarf868ba82020-07-21 21:07:20 +02001030 continue;
1031 }
1032 fname = files[fi] + dirlen + 1;
1033
Carlo Teubnerddab3ce2022-07-30 12:03:16 +01001034 in_example = FALSE;
Bram Moolenaarf868ba82020-07-21 21:07:20 +02001035 firstline = TRUE;
1036 while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int)
1037 {
1038 if (firstline)
1039 {
1040 // Detect utf-8 file by a non-ASCII char in the first line.
1041 this_utf8 = MAYBE;
1042 for (s = IObuff; *s != NUL; ++s)
1043 if (*s >= 0x80)
1044 {
1045 int l;
1046
1047 this_utf8 = TRUE;
1048 l = utf_ptr2len(s);
1049 if (l == 1)
1050 {
1051 // Illegal UTF-8 byte sequence.
1052 this_utf8 = FALSE;
1053 break;
1054 }
1055 s += l - 1;
1056 }
1057 if (this_utf8 == MAYBE) // only ASCII characters found
1058 this_utf8 = FALSE;
1059 if (utf8 == MAYBE) // first file
1060 utf8 = this_utf8;
1061 else if (utf8 != this_utf8)
1062 {
Bram Moolenaara6f79292022-01-04 21:30:47 +00001063 semsg(_(e_mix_of_help_file_encodings_within_language_str), files[fi]);
Bram Moolenaarf868ba82020-07-21 21:07:20 +02001064 mix = !got_int;
1065 got_int = TRUE;
1066 }
1067 firstline = FALSE;
1068 }
Carlo Teubnerddab3ce2022-07-30 12:03:16 +01001069 if (in_example)
1070 {
1071 // skip over example; a non-white in the first column ends it
1072 if (vim_strchr((char_u *)" \t\n\r", IObuff[0]))
1073 continue;
1074 in_example = FALSE;
1075 }
Bram Moolenaarf868ba82020-07-21 21:07:20 +02001076 p1 = vim_strchr(IObuff, '*'); // find first '*'
1077 while (p1 != NULL)
1078 {
1079 // Use vim_strbyte() instead of vim_strchr() so that when
1080 // 'encoding' is dbcs it still works, don't find '*' in the
1081 // second byte.
1082 p2 = vim_strbyte(p1 + 1, '*'); // find second '*'
1083 if (p2 != NULL && p2 > p1 + 1) // skip "*" and "**"
1084 {
1085 for (s = p1 + 1; s < p2; ++s)
1086 if (*s == ' ' || *s == '\t' || *s == '|')
1087 break;
1088
1089 // Only accept a *tag* when it consists of valid
1090 // characters, there is white space before it and is
1091 // followed by a white character or end-of-line.
1092 if (s == p2
1093 && (p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t')
1094 && (vim_strchr((char_u *)" \t\n\r", s[1]) != NULL
1095 || s[1] == '\0'))
1096 {
1097 *p2 = '\0';
1098 ++p1;
1099 if (ga_grow(&ga, 1) == FAIL)
1100 {
1101 got_int = TRUE;
1102 break;
1103 }
1104 s = alloc(p2 - p1 + STRLEN(fname) + 2);
1105 if (s == NULL)
1106 {
1107 got_int = TRUE;
1108 break;
1109 }
1110 ((char_u **)ga.ga_data)[ga.ga_len] = s;
1111 ++ga.ga_len;
1112 sprintf((char *)s, "%s\t%s", p1, fname);
1113
1114 // find next '*'
1115 p2 = vim_strchr(p2 + 1, '*');
1116 }
1117 }
1118 p1 = p2;
1119 }
Carlo Teubnerddab3ce2022-07-30 12:03:16 +01001120 len = (int)STRLEN(IObuff);
1121 if ((len == 2 && STRCMP(&IObuff[len - 2], ">\n") == 0)
1122 || (len >= 3 && STRCMP(&IObuff[len - 3], " >\n") == 0))
1123 in_example = TRUE;
Bram Moolenaarf868ba82020-07-21 21:07:20 +02001124 line_breakcheck();
1125 }
1126
1127 fclose(fd);
1128 }
1129
1130 FreeWild(filecount, files);
1131
1132 if (!got_int)
1133 {
1134 // Sort the tags.
1135 if (ga.ga_data != NULL)
1136 sort_strings((char_u **)ga.ga_data, ga.ga_len);
1137
1138 // Check for duplicates.
1139 for (i = 1; i < ga.ga_len; ++i)
1140 {
1141 p1 = ((char_u **)ga.ga_data)[i - 1];
1142 p2 = ((char_u **)ga.ga_data)[i];
1143 while (*p1 == *p2)
1144 {
1145 if (*p2 == '\t')
1146 {
1147 *p2 = NUL;
1148 vim_snprintf((char *)NameBuff, MAXPATHL,
Bram Moolenaareb822a22021-12-31 15:09:27 +00001149 _(e_duplicate_tag_str_in_file_str_str),
Bram Moolenaarf868ba82020-07-21 21:07:20 +02001150 ((char_u **)ga.ga_data)[i], dir, p2 + 1);
1151 emsg((char *)NameBuff);
1152 *p2 = '\t';
1153 break;
1154 }
1155 ++p1;
1156 ++p2;
1157 }
1158 }
1159
1160 if (utf8 == TRUE)
1161 fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n");
1162
1163 // Write the tags into the file.
1164 for (i = 0; i < ga.ga_len; ++i)
1165 {
1166 s = ((char_u **)ga.ga_data)[i];
1167 if (STRNCMP(s, "help-tags\t", 10) == 0)
1168 // help-tags entry was added in formatted form
1169 fputs((char *)s, fd_tags);
1170 else
1171 {
1172 fprintf(fd_tags, "%s\t/*", s);
1173 for (p1 = s; *p1 != '\t'; ++p1)
1174 {
1175 // insert backslash before '\\' and '/'
1176 if (*p1 == '\\' || *p1 == '/')
1177 putc('\\', fd_tags);
1178 putc(*p1, fd_tags);
1179 }
1180 fprintf(fd_tags, "*\n");
1181 }
1182 }
1183 }
1184 if (mix)
1185 got_int = FALSE; // continue with other languages
1186
1187 for (i = 0; i < ga.ga_len; ++i)
1188 vim_free(((char_u **)ga.ga_data)[i]);
1189 ga_clear(&ga);
1190 fclose(fd_tags); // there is no check for an error...
1191}
1192
1193/*
1194 * Generate tags in one help directory, taking care of translations.
1195 */
1196 static void
1197do_helptags(char_u *dirname, int add_help_tags, int ignore_writeerr)
1198{
1199#ifdef FEAT_MULTI_LANG
1200 int len;
1201 int i, j;
1202 garray_T ga;
1203 char_u lang[2];
1204 char_u ext[5];
1205 char_u fname[8];
1206 int filecount;
1207 char_u **files;
1208
1209 // Get a list of all files in the help directory and in subdirectories.
1210 STRCPY(NameBuff, dirname);
1211 add_pathsep(NameBuff);
1212 STRCAT(NameBuff, "**");
1213 if (gen_expand_wildcards(1, &NameBuff, &filecount, &files,
1214 EW_FILE|EW_SILENT) == FAIL
1215 || filecount == 0)
1216 {
Bram Moolenaard82a47d2022-01-05 20:24:39 +00001217 semsg(_(e_no_match_str_1), NameBuff);
Bram Moolenaarf868ba82020-07-21 21:07:20 +02001218 return;
1219 }
1220
1221 // Go over all files in the directory to find out what languages are
1222 // present.
1223 ga_init2(&ga, 1, 10);
1224 for (i = 0; i < filecount; ++i)
1225 {
1226 len = (int)STRLEN(files[i]);
zeertzjq101d57b2022-07-31 18:34:32 +01001227 if (len <= 4)
1228 continue;
Bram Moolenaarf868ba82020-07-21 21:07:20 +02001229
zeertzjq101d57b2022-07-31 18:34:32 +01001230 if (STRICMP(files[i] + len - 4, ".txt") == 0)
1231 {
1232 // ".txt" -> language "en"
1233 lang[0] = 'e';
1234 lang[1] = 'n';
1235 }
1236 else if (files[i][len - 4] == '.'
1237 && ASCII_ISALPHA(files[i][len - 3])
1238 && ASCII_ISALPHA(files[i][len - 2])
1239 && TOLOWER_ASC(files[i][len - 1]) == 'x')
1240 {
1241 // ".abx" -> language "ab"
1242 lang[0] = TOLOWER_ASC(files[i][len - 3]);
1243 lang[1] = TOLOWER_ASC(files[i][len - 2]);
1244 }
1245 else
1246 continue;
1247
1248 // Did we find this language already?
1249 for (j = 0; j < ga.ga_len; j += 2)
1250 if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0)
1251 break;
1252 if (j == ga.ga_len)
1253 {
1254 // New language, add it.
1255 if (ga_grow(&ga, 2) == FAIL)
1256 break;
1257 ((char_u *)ga.ga_data)[ga.ga_len++] = lang[0];
1258 ((char_u *)ga.ga_data)[ga.ga_len++] = lang[1];
Bram Moolenaarf868ba82020-07-21 21:07:20 +02001259 }
1260 }
1261
1262 // Loop over the found languages to generate a tags file for each one.
1263 for (j = 0; j < ga.ga_len; j += 2)
1264 {
1265 STRCPY(fname, "tags-xx");
1266 fname[5] = ((char_u *)ga.ga_data)[j];
1267 fname[6] = ((char_u *)ga.ga_data)[j + 1];
1268 if (fname[5] == 'e' && fname[6] == 'n')
1269 {
1270 // English is an exception: use ".txt" and "tags".
1271 fname[4] = NUL;
1272 STRCPY(ext, ".txt");
1273 }
1274 else
1275 {
1276 // Language "ab" uses ".abx" and "tags-ab".
1277 STRCPY(ext, ".xxx");
1278 ext[1] = fname[5];
1279 ext[2] = fname[6];
1280 }
1281 helptags_one(dirname, ext, fname, add_help_tags, ignore_writeerr);
1282 }
1283
1284 ga_clear(&ga);
1285 FreeWild(filecount, files);
1286
1287#else
1288 // No language support, just use "*.txt" and "tags".
1289 helptags_one(dirname, (char_u *)".txt", (char_u *)"tags", add_help_tags,
1290 ignore_writeerr);
1291#endif
1292}
1293
1294 static void
1295helptags_cb(char_u *fname, void *cookie)
1296{
1297 do_helptags(fname, *(int *)cookie, TRUE);
1298}
1299
1300/*
1301 * ":helptags"
1302 */
1303 void
1304ex_helptags(exarg_T *eap)
1305{
1306 expand_T xpc;
1307 char_u *dirname;
1308 int add_help_tags = FALSE;
1309
1310 // Check for ":helptags ++t {dir}".
1311 if (STRNCMP(eap->arg, "++t", 3) == 0 && VIM_ISWHITE(eap->arg[3]))
1312 {
1313 add_help_tags = TRUE;
1314 eap->arg = skipwhite(eap->arg + 3);
1315 }
1316
1317 if (STRCMP(eap->arg, "ALL") == 0)
1318 {
zeertzjq008c9152023-08-17 23:08:53 +02001319 do_in_path(p_rtp, "", (char_u *)"doc", DIP_ALL + DIP_DIR,
Bram Moolenaarf868ba82020-07-21 21:07:20 +02001320 helptags_cb, &add_help_tags);
1321 }
1322 else
1323 {
1324 ExpandInit(&xpc);
1325 xpc.xp_context = EXPAND_DIRECTORIES;
1326 dirname = ExpandOne(&xpc, eap->arg, NULL,
1327 WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE);
1328 if (dirname == NULL || !mch_isdir(dirname))
Bram Moolenaareb822a22021-12-31 15:09:27 +00001329 semsg(_(e_not_a_directory_str), eap->arg);
Bram Moolenaarf868ba82020-07-21 21:07:20 +02001330 else
1331 do_helptags(dirname, add_help_tags, FALSE);
1332 vim_free(dirname);
1333 }
1334}