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