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