comparison src/ex_cmds.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 9064044fd4f6
children 4dfd00f481fb
comparison
equal deleted inserted replaced
21422:af29e2470306 21423:5db63c2c6929
21 static int linelen(int *has_tab); 21 static int linelen(int *has_tab);
22 static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd, int do_in, int do_out); 22 static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd, int do_in, int do_out);
23 static int not_writing(void); 23 static int not_writing(void);
24 static int check_readonly(int *forceit, buf_T *buf); 24 static int check_readonly(int *forceit, buf_T *buf);
25 static void delbuf_msg(char_u *name); 25 static void delbuf_msg(char_u *name);
26 static int help_compare(const void *s1, const void *s2);
27 static void prepare_help_buffer(void);
28 26
29 /* 27 /*
30 * ":ascii" and "ga". 28 * ":ascii" and "ga".
31 */ 29 */
32 void 30 void
5033 return FALSE; 5031 return FALSE;
5034 } 5032 }
5035 5033
5036 #endif 5034 #endif
5037 5035
5038
5039 /*
5040 * ":help": open a read-only window on a help file
5041 */
5042 void
5043 ex_help(exarg_T *eap)
5044 {
5045 char_u *arg;
5046 char_u *tag;
5047 FILE *helpfd; // file descriptor of help file
5048 int n;
5049 int i;
5050 win_T *wp;
5051 int num_matches;
5052 char_u **matches;
5053 char_u *p;
5054 int empty_fnum = 0;
5055 int alt_fnum = 0;
5056 buf_T *buf;
5057 #ifdef FEAT_MULTI_LANG
5058 int len;
5059 char_u *lang;
5060 #endif
5061 #ifdef FEAT_FOLDING
5062 int old_KeyTyped = KeyTyped;
5063 #endif
5064
5065 if (eap != NULL)
5066 {
5067 /*
5068 * A ":help" command ends at the first LF, or at a '|' that is
5069 * followed by some text. Set nextcmd to the following command.
5070 */
5071 for (arg = eap->arg; *arg; ++arg)
5072 {
5073 if (*arg == '\n' || *arg == '\r'
5074 || (*arg == '|' && arg[1] != NUL && arg[1] != '|'))
5075 {
5076 *arg++ = NUL;
5077 eap->nextcmd = arg;
5078 break;
5079 }
5080 }
5081 arg = eap->arg;
5082
5083 if (eap->forceit && *arg == NUL && !curbuf->b_help)
5084 {
5085 emsg(_("E478: Don't panic!"));
5086 return;
5087 }
5088
5089 if (eap->skip) // not executing commands
5090 return;
5091 }
5092 else
5093 arg = (char_u *)"";
5094
5095 // remove trailing blanks
5096 p = arg + STRLEN(arg) - 1;
5097 while (p > arg && VIM_ISWHITE(*p) && p[-1] != '\\')
5098 *p-- = NUL;
5099
5100 #ifdef FEAT_MULTI_LANG
5101 // Check for a specified language
5102 lang = check_help_lang(arg);
5103 #endif
5104
5105 // When no argument given go to the index.
5106 if (*arg == NUL)
5107 arg = (char_u *)"help.txt";
5108
5109 /*
5110 * Check if there is a match for the argument.
5111 */
5112 n = find_help_tags(arg, &num_matches, &matches,
5113 eap != NULL && eap->forceit);
5114
5115 i = 0;
5116 #ifdef FEAT_MULTI_LANG
5117 if (n != FAIL && lang != NULL)
5118 // Find first item with the requested language.
5119 for (i = 0; i < num_matches; ++i)
5120 {
5121 len = (int)STRLEN(matches[i]);
5122 if (len > 3 && matches[i][len - 3] == '@'
5123 && STRICMP(matches[i] + len - 2, lang) == 0)
5124 break;
5125 }
5126 #endif
5127 if (i >= num_matches || n == FAIL)
5128 {
5129 #ifdef FEAT_MULTI_LANG
5130 if (lang != NULL)
5131 semsg(_("E661: Sorry, no '%s' help for %s"), lang, arg);
5132 else
5133 #endif
5134 semsg(_("E149: Sorry, no help for %s"), arg);
5135 if (n != FAIL)
5136 FreeWild(num_matches, matches);
5137 return;
5138 }
5139
5140 // The first match (in the requested language) is the best match.
5141 tag = vim_strsave(matches[i]);
5142 FreeWild(num_matches, matches);
5143
5144 #ifdef FEAT_GUI
5145 need_mouse_correct = TRUE;
5146 #endif
5147
5148 /*
5149 * Re-use an existing help window or open a new one.
5150 * Always open a new one for ":tab help".
5151 */
5152 if (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)
5153 {
5154 if (cmdmod.tab != 0)
5155 wp = NULL;
5156 else
5157 FOR_ALL_WINDOWS(wp)
5158 if (bt_help(wp->w_buffer))
5159 break;
5160 if (wp != NULL && wp->w_buffer->b_nwindows > 0)
5161 win_enter(wp, TRUE);
5162 else
5163 {
5164 /*
5165 * There is no help window yet.
5166 * Try to open the file specified by the "helpfile" option.
5167 */
5168 if ((helpfd = mch_fopen((char *)p_hf, READBIN)) == NULL)
5169 {
5170 smsg(_("Sorry, help file \"%s\" not found"), p_hf);
5171 goto erret;
5172 }
5173 fclose(helpfd);
5174
5175 // Split off help window; put it at far top if no position
5176 // specified, the current window is vertically split and
5177 // narrow.
5178 n = WSP_HELP;
5179 if (cmdmod.split == 0 && curwin->w_width != Columns
5180 && curwin->w_width < 80)
5181 n |= WSP_TOP;
5182 if (win_split(0, n) == FAIL)
5183 goto erret;
5184
5185 if (curwin->w_height < p_hh)
5186 win_setheight((int)p_hh);
5187
5188 /*
5189 * Open help file (do_ecmd() will set b_help flag, readfile() will
5190 * set b_p_ro flag).
5191 * Set the alternate file to the previously edited file.
5192 */
5193 alt_fnum = curbuf->b_fnum;
5194 (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL,
5195 ECMD_HIDE + ECMD_SET_HELP,
5196 NULL); // buffer is still open, don't store info
5197 if (!cmdmod.keepalt)
5198 curwin->w_alt_fnum = alt_fnum;
5199 empty_fnum = curbuf->b_fnum;
5200 }
5201 }
5202
5203 if (!p_im)
5204 restart_edit = 0; // don't want insert mode in help file
5205
5206 #ifdef FEAT_FOLDING
5207 // Restore KeyTyped, setting 'filetype=help' may reset it.
5208 // It is needed for do_tag top open folds under the cursor.
5209 KeyTyped = old_KeyTyped;
5210 #endif
5211
5212 if (tag != NULL)
5213 do_tag(tag, DT_HELP, 1, FALSE, TRUE);
5214
5215 // Delete the empty buffer if we're not using it. Careful: autocommands
5216 // may have jumped to another window, check that the buffer is not in a
5217 // window.
5218 if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum)
5219 {
5220 buf = buflist_findnr(empty_fnum);
5221 if (buf != NULL && buf->b_nwindows == 0)
5222 wipe_buffer(buf, TRUE);
5223 }
5224
5225 // keep the previous alternate file
5226 if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum && !cmdmod.keepalt)
5227 curwin->w_alt_fnum = alt_fnum;
5228
5229 erret:
5230 vim_free(tag);
5231 }
5232
5233 /*
5234 * ":helpclose": Close one help window
5235 */
5236 void
5237 ex_helpclose(exarg_T *eap UNUSED)
5238 {
5239 win_T *win;
5240
5241 FOR_ALL_WINDOWS(win)
5242 {
5243 if (bt_help(win->w_buffer))
5244 {
5245 win_close(win, FALSE);
5246 return;
5247 }
5248 }
5249 }
5250
5251 #if defined(FEAT_MULTI_LANG) || defined(PROTO)
5252 /*
5253 * In an argument search for a language specifiers in the form "@xx".
5254 * Changes the "@" to NUL if found, and returns a pointer to "xx".
5255 * Returns NULL if not found.
5256 */
5257 char_u *
5258 check_help_lang(char_u *arg)
5259 {
5260 int len = (int)STRLEN(arg);
5261
5262 if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2])
5263 && ASCII_ISALPHA(arg[len - 1]))
5264 {
5265 arg[len - 3] = NUL; // remove the '@'
5266 return arg + len - 2;
5267 }
5268 return NULL;
5269 }
5270 #endif
5271
5272 /*
5273 * Return a heuristic indicating how well the given string matches. The
5274 * smaller the number, the better the match. This is the order of priorities,
5275 * from best match to worst match:
5276 * - Match with least alphanumeric characters is better.
5277 * - Match with least total characters is better.
5278 * - Match towards the start is better.
5279 * - Match starting with "+" is worse (feature instead of command)
5280 * Assumption is made that the matched_string passed has already been found to
5281 * match some string for which help is requested. webb.
5282 */
5283 int
5284 help_heuristic(
5285 char_u *matched_string,
5286 int offset, // offset for match
5287 int wrong_case) // no matching case
5288 {
5289 int num_letters;
5290 char_u *p;
5291
5292 num_letters = 0;
5293 for (p = matched_string; *p; p++)
5294 if (ASCII_ISALNUM(*p))
5295 num_letters++;
5296
5297 /*
5298 * Multiply the number of letters by 100 to give it a much bigger
5299 * weighting than the number of characters.
5300 * If there only is a match while ignoring case, add 5000.
5301 * If the match starts in the middle of a word, add 10000 to put it
5302 * somewhere in the last half.
5303 * If the match is more than 2 chars from the start, multiply by 200 to
5304 * put it after matches at the start.
5305 */
5306 if (ASCII_ISALNUM(matched_string[offset]) && offset > 0
5307 && ASCII_ISALNUM(matched_string[offset - 1]))
5308 offset += 10000;
5309 else if (offset > 2)
5310 offset *= 200;
5311 if (wrong_case)
5312 offset += 5000;
5313 // Features are less interesting than the subjects themselves, but "+"
5314 // alone is not a feature.
5315 if (matched_string[0] == '+' && matched_string[1] != NUL)
5316 offset += 100;
5317 return (int)(100 * num_letters + STRLEN(matched_string) + offset);
5318 }
5319
5320 /*
5321 * Compare functions for qsort() below, that checks the help heuristics number
5322 * that has been put after the tagname by find_tags().
5323 */
5324 static int
5325 help_compare(const void *s1, const void *s2)
5326 {
5327 char *p1;
5328 char *p2;
5329 int cmp;
5330
5331 p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
5332 p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
5333
5334 // Compare by help heuristic number first.
5335 cmp = strcmp(p1, p2);
5336 if (cmp != 0)
5337 return cmp;
5338
5339 // Compare by strings as tie-breaker when same heuristic number.
5340 return strcmp(*(char **)s1, *(char **)s2);
5341 }
5342
5343 /*
5344 * Find all help tags matching "arg", sort them and return in matches[], with
5345 * the number of matches in num_matches.
5346 * The matches will be sorted with a "best" match algorithm.
5347 * When "keep_lang" is TRUE try keeping the language of the current buffer.
5348 */
5349 int
5350 find_help_tags(
5351 char_u *arg,
5352 int *num_matches,
5353 char_u ***matches,
5354 int keep_lang)
5355 {
5356 char_u *s, *d;
5357 int i;
5358 static char *(mtable[]) = {"*", "g*", "[*", "]*", ":*",
5359 "/*", "/\\*", "\"*", "**",
5360 "cpo-*", "/\\(\\)", "/\\%(\\)",
5361 "?", ":?", "?<CR>", "g?", "g?g?", "g??",
5362 "-?", "q?", "v_g?",
5363 "/\\?", "/\\z(\\)", "\\=", ":s\\=",
5364 "[count]", "[quotex]",
5365 "[range]", ":[range]",
5366 "[pattern]", "\\|", "\\%$",
5367 "s/\\~", "s/\\U", "s/\\L",
5368 "s/\\1", "s/\\2", "s/\\3", "s/\\9"};
5369 static char *(rtable[]) = {"star", "gstar", "[star", "]star", ":star",
5370 "/star", "/\\\\star", "quotestar", "starstar",
5371 "cpo-star", "/\\\\(\\\\)", "/\\\\%(\\\\)",
5372 "?", ":?", "?<CR>", "g?", "g?g?", "g??",
5373 "-?", "q?", "v_g?",
5374 "/\\\\?", "/\\\\z(\\\\)", "\\\\=", ":s\\\\=",
5375 "\\[count]", "\\[quotex]",
5376 "\\[range]", ":\\[range]",
5377 "\\[pattern]", "\\\\bar", "/\\\\%\\$",
5378 "s/\\\\\\~", "s/\\\\U", "s/\\\\L",
5379 "s/\\\\1", "s/\\\\2", "s/\\\\3", "s/\\\\9"};
5380 static char *(expr_table[]) = {"!=?", "!~?", "<=?", "<?", "==?", "=~?",
5381 ">=?", ">?", "is?", "isnot?"};
5382 int flags;
5383
5384 d = IObuff; // assume IObuff is long enough!
5385
5386 if (STRNICMP(arg, "expr-", 5) == 0)
5387 {
5388 // When the string starting with "expr-" and containing '?' and matches
5389 // the table, it is taken literally (but ~ is escaped). Otherwise '?'
5390 // is recognized as a wildcard.
5391 for (i = (int)(sizeof(expr_table) / sizeof(char *)); --i >= 0; )
5392 if (STRCMP(arg + 5, expr_table[i]) == 0)
5393 {
5394 int si = 0, di = 0;
5395
5396 for (;;)
5397 {
5398 if (arg[si] == '~')
5399 d[di++] = '\\';
5400 d[di++] = arg[si];
5401 if (arg[si] == NUL)
5402 break;
5403 ++si;
5404 }
5405 break;
5406 }
5407 }
5408 else
5409 {
5410 // Recognize a few exceptions to the rule. Some strings that contain
5411 // '*' with "star". Otherwise '*' is recognized as a wildcard.
5412 for (i = (int)(sizeof(mtable) / sizeof(char *)); --i >= 0; )
5413 if (STRCMP(arg, mtable[i]) == 0)
5414 {
5415 STRCPY(d, rtable[i]);
5416 break;
5417 }
5418 }
5419
5420 if (i < 0) // no match in table
5421 {
5422 // Replace "\S" with "/\\S", etc. Otherwise every tag is matched.
5423 // Also replace "\%^" and "\%(", they match every tag too.
5424 // Also "\zs", "\z1", etc.
5425 // Also "\@<", "\@=", "\@<=", etc.
5426 // And also "\_$" and "\_^".
5427 if (arg[0] == '\\'
5428 && ((arg[1] != NUL && arg[2] == NUL)
5429 || (vim_strchr((char_u *)"%_z@", arg[1]) != NULL
5430 && arg[2] != NUL)))
5431 {
5432 STRCPY(d, "/\\\\");
5433 STRCPY(d + 3, arg + 1);
5434 // Check for "/\\_$", should be "/\\_\$"
5435 if (d[3] == '_' && d[4] == '$')
5436 STRCPY(d + 4, "\\$");
5437 }
5438 else
5439 {
5440 // Replace:
5441 // "[:...:]" with "\[:...:]"
5442 // "[++...]" with "\[++...]"
5443 // "\{" with "\\{" -- matching "} \}"
5444 if ((arg[0] == '[' && (arg[1] == ':'
5445 || (arg[1] == '+' && arg[2] == '+')))
5446 || (arg[0] == '\\' && arg[1] == '{'))
5447 *d++ = '\\';
5448
5449 /*
5450 * If tag starts with "('", skip the "(". Fixes CTRL-] on ('option'.
5451 */
5452 if (*arg == '(' && arg[1] == '\'')
5453 arg++;
5454 for (s = arg; *s; ++s)
5455 {
5456 /*
5457 * Replace "|" with "bar" and '"' with "quote" to match the name of
5458 * the tags for these commands.
5459 * Replace "*" with ".*" and "?" with "." to match command line
5460 * completion.
5461 * Insert a backslash before '~', '$' and '.' to avoid their
5462 * special meaning.
5463 */
5464 if (d - IObuff > IOSIZE - 10) // getting too long!?
5465 break;
5466 switch (*s)
5467 {
5468 case '|': STRCPY(d, "bar");
5469 d += 3;
5470 continue;
5471 case '"': STRCPY(d, "quote");
5472 d += 5;
5473 continue;
5474 case '*': *d++ = '.';
5475 break;
5476 case '?': *d++ = '.';
5477 continue;
5478 case '$':
5479 case '.':
5480 case '~': *d++ = '\\';
5481 break;
5482 }
5483
5484 /*
5485 * Replace "^x" by "CTRL-X". Don't do this for "^_" to make
5486 * ":help i_^_CTRL-D" work.
5487 * Insert '-' before and after "CTRL-X" when applicable.
5488 */
5489 if (*s < ' ' || (*s == '^' && s[1] && (ASCII_ISALPHA(s[1])
5490 || vim_strchr((char_u *)"?@[\\]^", s[1]) != NULL)))
5491 {
5492 if (d > IObuff && d[-1] != '_' && d[-1] != '\\')
5493 *d++ = '_'; // prepend a '_' to make x_CTRL-x
5494 STRCPY(d, "CTRL-");
5495 d += 5;
5496 if (*s < ' ')
5497 {
5498 #ifdef EBCDIC
5499 *d++ = CtrlChar(*s);
5500 #else
5501 *d++ = *s + '@';
5502 #endif
5503 if (d[-1] == '\\')
5504 *d++ = '\\'; // double a backslash
5505 }
5506 else
5507 *d++ = *++s;
5508 if (s[1] != NUL && s[1] != '_')
5509 *d++ = '_'; // append a '_'
5510 continue;
5511 }
5512 else if (*s == '^') // "^" or "CTRL-^" or "^_"
5513 *d++ = '\\';
5514
5515 /*
5516 * Insert a backslash before a backslash after a slash, for search
5517 * pattern tags: "/\|" --> "/\\|".
5518 */
5519 else if (s[0] == '\\' && s[1] != '\\'
5520 && *arg == '/' && s == arg + 1)
5521 *d++ = '\\';
5522
5523 // "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in
5524 // "CTRL-\_CTRL-N"
5525 if (STRNICMP(s, "CTRL-\\_", 7) == 0)
5526 {
5527 STRCPY(d, "CTRL-\\\\");
5528 d += 7;
5529 s += 6;
5530 }
5531
5532 *d++ = *s;
5533
5534 /*
5535 * If tag contains "({" or "([", tag terminates at the "(".
5536 * This is for help on functions, e.g.: abs({expr}).
5537 */
5538 if (*s == '(' && (s[1] == '{' || s[1] =='['))
5539 break;
5540
5541 /*
5542 * If tag starts with ', toss everything after a second '. Fixes
5543 * CTRL-] on 'option'. (would include the trailing '.').
5544 */
5545 if (*s == '\'' && s > arg && *arg == '\'')
5546 break;
5547 // Also '{' and '}'.
5548 if (*s == '}' && s > arg && *arg == '{')
5549 break;
5550 }
5551 *d = NUL;
5552
5553 if (*IObuff == '`')
5554 {
5555 if (d > IObuff + 2 && d[-1] == '`')
5556 {
5557 // remove the backticks from `command`
5558 mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff));
5559 d[-2] = NUL;
5560 }
5561 else if (d > IObuff + 3 && d[-2] == '`' && d[-1] == ',')
5562 {
5563 // remove the backticks and comma from `command`,
5564 mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff));
5565 d[-3] = NUL;
5566 }
5567 else if (d > IObuff + 4 && d[-3] == '`'
5568 && d[-2] == '\\' && d[-1] == '.')
5569 {
5570 // remove the backticks and dot from `command`\.
5571 mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff));
5572 d[-4] = NUL;
5573 }
5574 }
5575 }
5576 }
5577
5578 *matches = (char_u **)"";
5579 *num_matches = 0;
5580 flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC;
5581 if (keep_lang)
5582 flags |= TAG_KEEP_LANG;
5583 if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK
5584 && *num_matches > 0)
5585 {
5586 // Sort the matches found on the heuristic number that is after the
5587 // tag name.
5588 qsort((void *)*matches, (size_t)*num_matches,
5589 sizeof(char_u *), help_compare);
5590 // Delete more than TAG_MANY to reduce the size of the listing.
5591 while (*num_matches > TAG_MANY)
5592 vim_free((*matches)[--*num_matches]);
5593 }
5594 return OK;
5595 }
5596
5597 /*
5598 * Called when starting to edit a buffer for a help file.
5599 */
5600 static void
5601 prepare_help_buffer(void)
5602 {
5603 char_u *p;
5604
5605 curbuf->b_help = TRUE;
5606 #ifdef FEAT_QUICKFIX
5607 set_string_option_direct((char_u *)"buftype", -1,
5608 (char_u *)"help", OPT_FREE|OPT_LOCAL, 0);
5609 #endif
5610
5611 /*
5612 * Always set these options after jumping to a help tag, because the
5613 * user may have an autocommand that gets in the way.
5614 * Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and
5615 * latin1 word characters (for translated help files).
5616 * Only set it when needed, buf_init_chartab() is some work.
5617 */
5618 p =
5619 #ifdef EBCDIC
5620 (char_u *)"65-255,^*,^|,^\"";
5621 #else
5622 (char_u *)"!-~,^*,^|,^\",192-255";
5623 #endif
5624 if (STRCMP(curbuf->b_p_isk, p) != 0)
5625 {
5626 set_string_option_direct((char_u *)"isk", -1, p, OPT_FREE|OPT_LOCAL, 0);
5627 check_buf_options(curbuf);
5628 (void)buf_init_chartab(curbuf, FALSE);
5629 }
5630
5631 #ifdef FEAT_FOLDING
5632 // Don't use the global foldmethod.
5633 set_string_option_direct((char_u *)"fdm", -1, (char_u *)"manual",
5634 OPT_FREE|OPT_LOCAL, 0);
5635 #endif
5636
5637 curbuf->b_p_ts = 8; // 'tabstop' is 8
5638 curwin->w_p_list = FALSE; // no list mode
5639
5640 curbuf->b_p_ma = FALSE; // not modifiable
5641 curbuf->b_p_bin = FALSE; // reset 'bin' before reading file
5642 curwin->w_p_nu = 0; // no line numbers
5643 curwin->w_p_rnu = 0; // no relative line numbers
5644 RESET_BINDING(curwin); // no scroll or cursor binding
5645 #ifdef FEAT_ARABIC
5646 curwin->w_p_arab = FALSE; // no arabic mode
5647 #endif
5648 #ifdef FEAT_RIGHTLEFT
5649 curwin->w_p_rl = FALSE; // help window is left-to-right
5650 #endif
5651 #ifdef FEAT_FOLDING
5652 curwin->w_p_fen = FALSE; // No folding in the help window
5653 #endif
5654 #ifdef FEAT_DIFF
5655 curwin->w_p_diff = FALSE; // No 'diff'
5656 #endif
5657 #ifdef FEAT_SPELL
5658 curwin->w_p_spell = FALSE; // No spell checking
5659 #endif
5660
5661 set_buflisted(FALSE);
5662 }
5663
5664 /*
5665 * After reading a help file: May cleanup a help buffer when syntax
5666 * highlighting is not used.
5667 */
5668 void
5669 fix_help_buffer(void)
5670 {
5671 linenr_T lnum;
5672 char_u *line;
5673 int in_example = FALSE;
5674 int len;
5675 char_u *fname;
5676 char_u *p;
5677 char_u *rt;
5678 int mustfree;
5679
5680 // Set filetype to "help" if still needed.
5681 if (STRCMP(curbuf->b_p_ft, "help") != 0)
5682 {
5683 ++curbuf_lock;
5684 set_option_value((char_u *)"ft", 0L, (char_u *)"help", OPT_LOCAL);
5685 --curbuf_lock;
5686 }
5687
5688 #ifdef FEAT_SYN_HL
5689 if (!syntax_present(curwin))
5690 #endif
5691 {
5692 for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum)
5693 {
5694 line = ml_get_buf(curbuf, lnum, FALSE);
5695 len = (int)STRLEN(line);
5696 if (in_example && len > 0 && !VIM_ISWHITE(line[0]))
5697 {
5698 // End of example: non-white or '<' in first column.
5699 if (line[0] == '<')
5700 {
5701 // blank-out a '<' in the first column
5702 line = ml_get_buf(curbuf, lnum, TRUE);
5703 line[0] = ' ';
5704 }
5705 in_example = FALSE;
5706 }
5707 if (!in_example && len > 0)
5708 {
5709 if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' '))
5710 {
5711 // blank-out a '>' in the last column (start of example)
5712 line = ml_get_buf(curbuf, lnum, TRUE);
5713 line[len - 1] = ' ';
5714 in_example = TRUE;
5715 }
5716 else if (line[len - 1] == '~')
5717 {
5718 // blank-out a '~' at the end of line (header marker)
5719 line = ml_get_buf(curbuf, lnum, TRUE);
5720 line[len - 1] = ' ';
5721 }
5722 }
5723 }
5724 }
5725
5726 /*
5727 * In the "help.txt" and "help.abx" file, add the locally added help
5728 * files. This uses the very first line in the help file.
5729 */
5730 fname = gettail(curbuf->b_fname);
5731 if (fnamecmp(fname, "help.txt") == 0
5732 #ifdef FEAT_MULTI_LANG
5733 || (fnamencmp(fname, "help.", 5) == 0
5734 && ASCII_ISALPHA(fname[5])
5735 && ASCII_ISALPHA(fname[6])
5736 && TOLOWER_ASC(fname[7]) == 'x'
5737 && fname[8] == NUL)
5738 #endif
5739 )
5740 {
5741 for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; ++lnum)
5742 {
5743 line = ml_get_buf(curbuf, lnum, FALSE);
5744 if (strstr((char *)line, "*local-additions*") == NULL)
5745 continue;
5746
5747 // Go through all directories in 'runtimepath', skipping
5748 // $VIMRUNTIME.
5749 p = p_rtp;
5750 while (*p != NUL)
5751 {
5752 copy_option_part(&p, NameBuff, MAXPATHL, ",");
5753 mustfree = FALSE;
5754 rt = vim_getenv((char_u *)"VIMRUNTIME", &mustfree);
5755 if (rt != NULL &&
5756 fullpathcmp(rt, NameBuff, FALSE, TRUE) != FPC_SAME)
5757 {
5758 int fcount;
5759 char_u **fnames;
5760 FILE *fd;
5761 char_u *s;
5762 int fi;
5763 vimconv_T vc;
5764 char_u *cp;
5765
5766 // Find all "doc/ *.txt" files in this directory.
5767 add_pathsep(NameBuff);
5768 #ifdef FEAT_MULTI_LANG
5769 STRCAT(NameBuff, "doc/*.??[tx]");
5770 #else
5771 STRCAT(NameBuff, "doc/*.txt");
5772 #endif
5773 if (gen_expand_wildcards(1, &NameBuff, &fcount,
5774 &fnames, EW_FILE|EW_SILENT) == OK
5775 && fcount > 0)
5776 {
5777 #ifdef FEAT_MULTI_LANG
5778 int i1, i2;
5779 char_u *f1, *f2;
5780 char_u *t1, *t2;
5781 char_u *e1, *e2;
5782
5783 // If foo.abx is found use it instead of foo.txt in
5784 // the same directory.
5785 for (i1 = 0; i1 < fcount; ++i1)
5786 {
5787 for (i2 = 0; i2 < fcount; ++i2)
5788 {
5789 if (i1 == i2)
5790 continue;
5791 if (fnames[i1] == NULL || fnames[i2] == NULL)
5792 continue;
5793 f1 = fnames[i1];
5794 f2 = fnames[i2];
5795 t1 = gettail(f1);
5796 t2 = gettail(f2);
5797 e1 = vim_strrchr(t1, '.');
5798 e2 = vim_strrchr(t2, '.');
5799 if (e1 == NULL || e2 == NULL)
5800 continue;
5801 if (fnamecmp(e1, ".txt") != 0
5802 && fnamecmp(e1, fname + 4) != 0)
5803 {
5804 // Not .txt and not .abx, remove it.
5805 VIM_CLEAR(fnames[i1]);
5806 continue;
5807 }
5808 if (e1 - f1 != e2 - f2
5809 || fnamencmp(f1, f2, e1 - f1) != 0)
5810 continue;
5811 if (fnamecmp(e1, ".txt") == 0
5812 && fnamecmp(e2, fname + 4) == 0)
5813 // use .abx instead of .txt
5814 VIM_CLEAR(fnames[i1]);
5815 }
5816 }
5817 #endif
5818 for (fi = 0; fi < fcount; ++fi)
5819 {
5820 if (fnames[fi] == NULL)
5821 continue;
5822 fd = mch_fopen((char *)fnames[fi], "r");
5823 if (fd != NULL)
5824 {
5825 vim_fgets(IObuff, IOSIZE, fd);
5826 if (IObuff[0] == '*'
5827 && (s = vim_strchr(IObuff + 1, '*'))
5828 != NULL)
5829 {
5830 int this_utf = MAYBE;
5831
5832 // Change tag definition to a
5833 // reference and remove <CR>/<NL>.
5834 IObuff[0] = '|';
5835 *s = '|';
5836 while (*s != NUL)
5837 {
5838 if (*s == '\r' || *s == '\n')
5839 *s = NUL;
5840 // The text is utf-8 when a byte
5841 // above 127 is found and no
5842 // illegal byte sequence is found.
5843 if (*s >= 0x80 && this_utf != FALSE)
5844 {
5845 int l;
5846
5847 this_utf = TRUE;
5848 l = utf_ptr2len(s);
5849 if (l == 1)
5850 this_utf = FALSE;
5851 s += l - 1;
5852 }
5853 ++s;
5854 }
5855
5856 // The help file is latin1 or utf-8;
5857 // conversion to the current
5858 // 'encoding' may be required.
5859 vc.vc_type = CONV_NONE;
5860 convert_setup(&vc, (char_u *)(
5861 this_utf == TRUE ? "utf-8"
5862 : "latin1"), p_enc);
5863 if (vc.vc_type == CONV_NONE)
5864 // No conversion needed.
5865 cp = IObuff;
5866 else
5867 {
5868 // Do the conversion. If it fails
5869 // use the unconverted text.
5870 cp = string_convert(&vc, IObuff,
5871 NULL);
5872 if (cp == NULL)
5873 cp = IObuff;
5874 }
5875 convert_setup(&vc, NULL, NULL);
5876
5877 ml_append(lnum, cp, (colnr_T)0, FALSE);
5878 if (cp != IObuff)
5879 vim_free(cp);
5880 ++lnum;
5881 }
5882 fclose(fd);
5883 }
5884 }
5885 FreeWild(fcount, fnames);
5886 }
5887 }
5888 if (mustfree)
5889 vim_free(rt);
5890 }
5891 break;
5892 }
5893 }
5894 }
5895
5896 /*
5897 * ":exusage"
5898 */
5899 void
5900 ex_exusage(exarg_T *eap UNUSED)
5901 {
5902 do_cmdline_cmd((char_u *)"help ex-cmd-index");
5903 }
5904
5905 /*
5906 * ":viusage"
5907 */
5908 void
5909 ex_viusage(exarg_T *eap UNUSED)
5910 {
5911 do_cmdline_cmd((char_u *)"help normal-index");
5912 }
5913
5914 /*
5915 * Generate tags in one help directory.
5916 */
5917 static void
5918 helptags_one(
5919 char_u *dir, // doc directory
5920 char_u *ext, // suffix, ".txt", ".itx", ".frx", etc.
5921 char_u *tagfname, // "tags" for English, "tags-fr" for French.
5922 int add_help_tags, // add "help-tags" tag
5923 int ignore_writeerr) // ignore write error
5924 {
5925 FILE *fd_tags;
5926 FILE *fd;
5927 garray_T ga;
5928 int filecount;
5929 char_u **files;
5930 char_u *p1, *p2;
5931 int fi;
5932 char_u *s;
5933 int i;
5934 char_u *fname;
5935 int dirlen;
5936 int utf8 = MAYBE;
5937 int this_utf8;
5938 int firstline;
5939 int mix = FALSE; // detected mixed encodings
5940
5941 /*
5942 * Find all *.txt files.
5943 */
5944 dirlen = (int)STRLEN(dir);
5945 STRCPY(NameBuff, dir);
5946 STRCAT(NameBuff, "/**/*");
5947 STRCAT(NameBuff, ext);
5948 if (gen_expand_wildcards(1, &NameBuff, &filecount, &files,
5949 EW_FILE|EW_SILENT) == FAIL
5950 || filecount == 0)
5951 {
5952 if (!got_int)
5953 semsg(_("E151: No match: %s"), NameBuff);
5954 return;
5955 }
5956
5957 /*
5958 * Open the tags file for writing.
5959 * Do this before scanning through all the files.
5960 */
5961 STRCPY(NameBuff, dir);
5962 add_pathsep(NameBuff);
5963 STRCAT(NameBuff, tagfname);
5964 fd_tags = mch_fopen((char *)NameBuff, "w");
5965 if (fd_tags == NULL)
5966 {
5967 if (!ignore_writeerr)
5968 semsg(_("E152: Cannot open %s for writing"), NameBuff);
5969 FreeWild(filecount, files);
5970 return;
5971 }
5972
5973 /*
5974 * If using the "++t" argument or generating tags for "$VIMRUNTIME/doc"
5975 * add the "help-tags" tag.
5976 */
5977 ga_init2(&ga, (int)sizeof(char_u *), 100);
5978 if (add_help_tags || fullpathcmp((char_u *)"$VIMRUNTIME/doc",
5979 dir, FALSE, TRUE) == FPC_SAME)
5980 {
5981 if (ga_grow(&ga, 1) == FAIL)
5982 got_int = TRUE;
5983 else
5984 {
5985 s = alloc(18 + (unsigned)STRLEN(tagfname));
5986 if (s == NULL)
5987 got_int = TRUE;
5988 else
5989 {
5990 sprintf((char *)s, "help-tags\t%s\t1\n", tagfname);
5991 ((char_u **)ga.ga_data)[ga.ga_len] = s;
5992 ++ga.ga_len;
5993 }
5994 }
5995 }
5996
5997 /*
5998 * Go over all the files and extract the tags.
5999 */
6000 for (fi = 0; fi < filecount && !got_int; ++fi)
6001 {
6002 fd = mch_fopen((char *)files[fi], "r");
6003 if (fd == NULL)
6004 {
6005 semsg(_("E153: Unable to open %s for reading"), files[fi]);
6006 continue;
6007 }
6008 fname = files[fi] + dirlen + 1;
6009
6010 firstline = TRUE;
6011 while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int)
6012 {
6013 if (firstline)
6014 {
6015 // Detect utf-8 file by a non-ASCII char in the first line.
6016 this_utf8 = MAYBE;
6017 for (s = IObuff; *s != NUL; ++s)
6018 if (*s >= 0x80)
6019 {
6020 int l;
6021
6022 this_utf8 = TRUE;
6023 l = utf_ptr2len(s);
6024 if (l == 1)
6025 {
6026 // Illegal UTF-8 byte sequence.
6027 this_utf8 = FALSE;
6028 break;
6029 }
6030 s += l - 1;
6031 }
6032 if (this_utf8 == MAYBE) // only ASCII characters found
6033 this_utf8 = FALSE;
6034 if (utf8 == MAYBE) // first file
6035 utf8 = this_utf8;
6036 else if (utf8 != this_utf8)
6037 {
6038 semsg(_("E670: Mix of help file encodings within a language: %s"), files[fi]);
6039 mix = !got_int;
6040 got_int = TRUE;
6041 }
6042 firstline = FALSE;
6043 }
6044 p1 = vim_strchr(IObuff, '*'); // find first '*'
6045 while (p1 != NULL)
6046 {
6047 // Use vim_strbyte() instead of vim_strchr() so that when
6048 // 'encoding' is dbcs it still works, don't find '*' in the
6049 // second byte.
6050 p2 = vim_strbyte(p1 + 1, '*'); // find second '*'
6051 if (p2 != NULL && p2 > p1 + 1) // skip "*" and "**"
6052 {
6053 for (s = p1 + 1; s < p2; ++s)
6054 if (*s == ' ' || *s == '\t' || *s == '|')
6055 break;
6056
6057 /*
6058 * Only accept a *tag* when it consists of valid
6059 * characters, there is white space before it and is
6060 * followed by a white character or end-of-line.
6061 */
6062 if (s == p2
6063 && (p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t')
6064 && (vim_strchr((char_u *)" \t\n\r", s[1]) != NULL
6065 || s[1] == '\0'))
6066 {
6067 *p2 = '\0';
6068 ++p1;
6069 if (ga_grow(&ga, 1) == FAIL)
6070 {
6071 got_int = TRUE;
6072 break;
6073 }
6074 s = alloc(p2 - p1 + STRLEN(fname) + 2);
6075 if (s == NULL)
6076 {
6077 got_int = TRUE;
6078 break;
6079 }
6080 ((char_u **)ga.ga_data)[ga.ga_len] = s;
6081 ++ga.ga_len;
6082 sprintf((char *)s, "%s\t%s", p1, fname);
6083
6084 // find next '*'
6085 p2 = vim_strchr(p2 + 1, '*');
6086 }
6087 }
6088 p1 = p2;
6089 }
6090 line_breakcheck();
6091 }
6092
6093 fclose(fd);
6094 }
6095
6096 FreeWild(filecount, files);
6097
6098 if (!got_int)
6099 {
6100 /*
6101 * Sort the tags.
6102 */
6103 if (ga.ga_data != NULL)
6104 sort_strings((char_u **)ga.ga_data, ga.ga_len);
6105
6106 /*
6107 * Check for duplicates.
6108 */
6109 for (i = 1; i < ga.ga_len; ++i)
6110 {
6111 p1 = ((char_u **)ga.ga_data)[i - 1];
6112 p2 = ((char_u **)ga.ga_data)[i];
6113 while (*p1 == *p2)
6114 {
6115 if (*p2 == '\t')
6116 {
6117 *p2 = NUL;
6118 vim_snprintf((char *)NameBuff, MAXPATHL,
6119 _("E154: Duplicate tag \"%s\" in file %s/%s"),
6120 ((char_u **)ga.ga_data)[i], dir, p2 + 1);
6121 emsg((char *)NameBuff);
6122 *p2 = '\t';
6123 break;
6124 }
6125 ++p1;
6126 ++p2;
6127 }
6128 }
6129
6130 if (utf8 == TRUE)
6131 fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n");
6132
6133 /*
6134 * Write the tags into the file.
6135 */
6136 for (i = 0; i < ga.ga_len; ++i)
6137 {
6138 s = ((char_u **)ga.ga_data)[i];
6139 if (STRNCMP(s, "help-tags\t", 10) == 0)
6140 // help-tags entry was added in formatted form
6141 fputs((char *)s, fd_tags);
6142 else
6143 {
6144 fprintf(fd_tags, "%s\t/*", s);
6145 for (p1 = s; *p1 != '\t'; ++p1)
6146 {
6147 // insert backslash before '\\' and '/'
6148 if (*p1 == '\\' || *p1 == '/')
6149 putc('\\', fd_tags);
6150 putc(*p1, fd_tags);
6151 }
6152 fprintf(fd_tags, "*\n");
6153 }
6154 }
6155 }
6156 if (mix)
6157 got_int = FALSE; // continue with other languages
6158
6159 for (i = 0; i < ga.ga_len; ++i)
6160 vim_free(((char_u **)ga.ga_data)[i]);
6161 ga_clear(&ga);
6162 fclose(fd_tags); // there is no check for an error...
6163 }
6164
6165 /*
6166 * Generate tags in one help directory, taking care of translations.
6167 */
6168 static void
6169 do_helptags(char_u *dirname, int add_help_tags, int ignore_writeerr)
6170 {
6171 #ifdef FEAT_MULTI_LANG
6172 int len;
6173 int i, j;
6174 garray_T ga;
6175 char_u lang[2];
6176 char_u ext[5];
6177 char_u fname[8];
6178 int filecount;
6179 char_u **files;
6180
6181 // Get a list of all files in the help directory and in subdirectories.
6182 STRCPY(NameBuff, dirname);
6183 add_pathsep(NameBuff);
6184 STRCAT(NameBuff, "**");
6185 if (gen_expand_wildcards(1, &NameBuff, &filecount, &files,
6186 EW_FILE|EW_SILENT) == FAIL
6187 || filecount == 0)
6188 {
6189 semsg(_("E151: No match: %s"), NameBuff);
6190 return;
6191 }
6192
6193 // Go over all files in the directory to find out what languages are
6194 // present.
6195 ga_init2(&ga, 1, 10);
6196 for (i = 0; i < filecount; ++i)
6197 {
6198 len = (int)STRLEN(files[i]);
6199 if (len > 4)
6200 {
6201 if (STRICMP(files[i] + len - 4, ".txt") == 0)
6202 {
6203 // ".txt" -> language "en"
6204 lang[0] = 'e';
6205 lang[1] = 'n';
6206 }
6207 else if (files[i][len - 4] == '.'
6208 && ASCII_ISALPHA(files[i][len - 3])
6209 && ASCII_ISALPHA(files[i][len - 2])
6210 && TOLOWER_ASC(files[i][len - 1]) == 'x')
6211 {
6212 // ".abx" -> language "ab"
6213 lang[0] = TOLOWER_ASC(files[i][len - 3]);
6214 lang[1] = TOLOWER_ASC(files[i][len - 2]);
6215 }
6216 else
6217 continue;
6218
6219 // Did we find this language already?
6220 for (j = 0; j < ga.ga_len; j += 2)
6221 if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0)
6222 break;
6223 if (j == ga.ga_len)
6224 {
6225 // New language, add it.
6226 if (ga_grow(&ga, 2) == FAIL)
6227 break;
6228 ((char_u *)ga.ga_data)[ga.ga_len++] = lang[0];
6229 ((char_u *)ga.ga_data)[ga.ga_len++] = lang[1];
6230 }
6231 }
6232 }
6233
6234 /*
6235 * Loop over the found languages to generate a tags file for each one.
6236 */
6237 for (j = 0; j < ga.ga_len; j += 2)
6238 {
6239 STRCPY(fname, "tags-xx");
6240 fname[5] = ((char_u *)ga.ga_data)[j];
6241 fname[6] = ((char_u *)ga.ga_data)[j + 1];
6242 if (fname[5] == 'e' && fname[6] == 'n')
6243 {
6244 // English is an exception: use ".txt" and "tags".
6245 fname[4] = NUL;
6246 STRCPY(ext, ".txt");
6247 }
6248 else
6249 {
6250 // Language "ab" uses ".abx" and "tags-ab".
6251 STRCPY(ext, ".xxx");
6252 ext[1] = fname[5];
6253 ext[2] = fname[6];
6254 }
6255 helptags_one(dirname, ext, fname, add_help_tags, ignore_writeerr);
6256 }
6257
6258 ga_clear(&ga);
6259 FreeWild(filecount, files);
6260
6261 #else
6262 // No language support, just use "*.txt" and "tags".
6263 helptags_one(dirname, (char_u *)".txt", (char_u *)"tags", add_help_tags,
6264 ignore_writeerr);
6265 #endif
6266 }
6267
6268 static void
6269 helptags_cb(char_u *fname, void *cookie)
6270 {
6271 do_helptags(fname, *(int *)cookie, TRUE);
6272 }
6273
6274 /*
6275 * ":helptags"
6276 */
6277 void
6278 ex_helptags(exarg_T *eap)
6279 {
6280 expand_T xpc;
6281 char_u *dirname;
6282 int add_help_tags = FALSE;
6283
6284 // Check for ":helptags ++t {dir}".
6285 if (STRNCMP(eap->arg, "++t", 3) == 0 && VIM_ISWHITE(eap->arg[3]))
6286 {
6287 add_help_tags = TRUE;
6288 eap->arg = skipwhite(eap->arg + 3);
6289 }
6290
6291 if (STRCMP(eap->arg, "ALL") == 0)
6292 {
6293 do_in_path(p_rtp, (char_u *)"doc", DIP_ALL + DIP_DIR,
6294 helptags_cb, &add_help_tags);
6295 }
6296 else
6297 {
6298 ExpandInit(&xpc);
6299 xpc.xp_context = EXPAND_DIRECTORIES;
6300 dirname = ExpandOne(&xpc, eap->arg, NULL,
6301 WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE);
6302 if (dirname == NULL || !mch_isdir(dirname))
6303 semsg(_("E150: Not a directory: %s"), eap->arg);
6304 else
6305 do_helptags(dirname, add_help_tags, FALSE);
6306 vim_free(dirname);
6307 }
6308 }
6309
6310 /* 5036 /*
6311 * Make the user happy. 5037 * Make the user happy.
6312 */ 5038 */
6313 void 5039 void
6314 ex_smile(exarg_T *eap UNUSED) 5040 ex_smile(exarg_T *eap UNUSED)