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