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