comparison src/help.c @ 21423:5db63c2c6929 v8.2.1262

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