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