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