summaryrefslogtreecommitdiffstats
path: root/src/ex_cmds.c
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2020-07-21 21:07:20 +0200
committerBram Moolenaar <Bram@vim.org>2020-07-21 21:07:20 +0200
commitf868ba89039045b25efe83d12ca501d657e170e8 (patch)
treef2b8b5d777fbb259111a5d5fabe8a0c69358fca1 /src/ex_cmds.c
parentc7db57788b661a5da0b375d4fffdf10721550141 (diff)
patch 8.2.1262: src/ex_cmds.c file is too bigv8.2.1262
Problem: src/ex_cmds.c file is too big. Solution: Move help related code to src/help.c. (Yegappan Lakshmanan, closes #6506)
Diffstat (limited to 'src/ex_cmds.c')
-rw-r--r--src/ex_cmds.c1274
1 files changed, 0 insertions, 1274 deletions
diff --git a/src/ex_cmds.c b/src/ex_cmds.c
index 11894f6387..59c562b917 100644
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -23,8 +23,6 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd,
static int not_writing(void);
static int check_readonly(int *forceit, buf_T *buf);
static void delbuf_msg(char_u *name);
-static int help_compare(const void *s1, const void *s2);
-static void prepare_help_buffer(void);
/*
* ":ascii" and "ga".
@@ -5035,1278 +5033,6 @@ prepare_tagpreview(
#endif
-
-/*
- * ":help": open a read-only window on a help file
- */
- void
-ex_help(exarg_T *eap)
-{
- char_u *arg;
- char_u *tag;
- FILE *helpfd; // file descriptor of help file
- int n;
- int i;
- win_T *wp;
- int num_matches;
- char_u **matches;
- char_u *p;
- int empty_fnum = 0;
- int alt_fnum = 0;
- buf_T *buf;
-#ifdef FEAT_MULTI_LANG
- int len;
- char_u *lang;
-#endif
-#ifdef FEAT_FOLDING
- int old_KeyTyped = KeyTyped;
-#endif
-
- if (eap != NULL)
- {
- /*
- * A ":help" command ends at the first LF, or at a '|' that is
- * followed by some text. Set nextcmd to the following command.
- */
- for (arg = eap->arg; *arg; ++arg)
- {
- if (*arg == '\n' || *arg == '\r'
- || (*arg == '|' && arg[1] != NUL && arg[1] != '|'))
- {
- *arg++ = NUL;
- eap->nextcmd = arg;
- break;
- }
- }
- arg = eap->arg;
-
- if (eap->forceit && *arg == NUL && !curbuf->b_help)
- {
- emsg(_("E478: Don't panic!"));
- return;
- }
-
- if (eap->skip) // not executing commands
- return;
- }
- else
- arg = (char_u *)"";
-
- // remove trailing blanks
- p = arg + STRLEN(arg) - 1;
- while (p > arg && VIM_ISWHITE(*p) && p[-1] != '\\')
- *p-- = NUL;
-
-#ifdef FEAT_MULTI_LANG
- // Check for a specified language
- lang = check_help_lang(arg);
-#endif
-
- // When no argument given go to the index.
- if (*arg == NUL)
- arg = (char_u *)"help.txt";
-
- /*
- * Check if there is a match for the argument.
- */
- n = find_help_tags(arg, &num_matches, &matches,
- eap != NULL && eap->forceit);
-
- i = 0;
-#ifdef FEAT_MULTI_LANG
- if (n != FAIL && lang != NULL)
- // Find first item with the requested language.
- for (i = 0; i < num_matches; ++i)
- {
- len = (int)STRLEN(matches[i]);
- if (len > 3 && matches[i][len - 3] == '@'
- && STRICMP(matches[i] + len - 2, lang) == 0)
- break;
- }
-#endif
- if (i >= num_matches || n == FAIL)
- {
-#ifdef FEAT_MULTI_LANG
- if (lang != NULL)
- semsg(_("E661: Sorry, no '%s' help for %s"), lang, arg);
- else
-#endif
- semsg(_("E149: Sorry, no help for %s"), arg);
- if (n != FAIL)
- FreeWild(num_matches, matches);
- return;
- }
-
- // The first match (in the requested language) is the best match.
- tag = vim_strsave(matches[i]);
- FreeWild(num_matches, matches);
-
-#ifdef FEAT_GUI
- need_mouse_correct = TRUE;
-#endif
-
- /*
- * Re-use an existing help window or open a new one.
- * Always open a new one for ":tab help".
- */
- if (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)
- {
- if (cmdmod.tab != 0)
- wp = NULL;
- else
- FOR_ALL_WINDOWS(wp)
- if (bt_help(wp->w_buffer))
- break;
- if (wp != NULL && wp->w_buffer->b_nwindows > 0)
- win_enter(wp, TRUE);
- else
- {
- /*
- * There is no help window yet.
- * Try to open the file specified by the "helpfile" option.
- */
- if ((helpfd = mch_fopen((char *)p_hf, READBIN)) == NULL)
- {
- smsg(_("Sorry, help file \"%s\" not found"), p_hf);
- goto erret;
- }
- fclose(helpfd);
-
- // Split off help window; put it at far top if no position
- // specified, the current window is vertically split and
- // narrow.
- n = WSP_HELP;
- if (cmdmod.split == 0 && curwin->w_width != Columns
- && curwin->w_width < 80)
- n |= WSP_TOP;
- if (win_split(0, n) == FAIL)
- goto erret;
-
- if (curwin->w_height < p_hh)
- win_setheight((int)p_hh);
-
- /*
- * Open help file (do_ecmd() will set b_help flag, readfile() will
- * set b_p_ro flag).
- * Set the alternate file to the previously edited file.
- */
- alt_fnum = curbuf->b_fnum;
- (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL,
- ECMD_HIDE + ECMD_SET_HELP,
- NULL); // buffer is still open, don't store info
- if (!cmdmod.keepalt)
- curwin->w_alt_fnum = alt_fnum;
- empty_fnum = curbuf->b_fnum;
- }
- }
-
- if (!p_im)
- restart_edit = 0; // don't want insert mode in help file
-
-#ifdef FEAT_FOLDING
- // Restore KeyTyped, setting 'filetype=help' may reset it.
- // It is needed for do_tag top open folds under the cursor.
- KeyTyped = old_KeyTyped;
-#endif
-
- if (tag != NULL)
- do_tag(tag, DT_HELP, 1, FALSE, TRUE);
-
- // Delete the empty buffer if we're not using it. Careful: autocommands
- // may have jumped to another window, check that the buffer is not in a
- // window.
- if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum)
- {
- buf = buflist_findnr(empty_fnum);
- if (buf != NULL && buf->b_nwindows == 0)
- wipe_buffer(buf, TRUE);
- }
-
- // keep the previous alternate file
- if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum && !cmdmod.keepalt)
- curwin->w_alt_fnum = alt_fnum;
-
-erret:
- vim_free(tag);
-}
-
-/*
- * ":helpclose": Close one help window
- */
- void
-ex_helpclose(exarg_T *eap UNUSED)
-{
- win_T *win;
-
- FOR_ALL_WINDOWS(win)
- {
- if (bt_help(win->w_buffer))
- {
- win_close(win, FALSE);
- return;
- }
- }
-}
-
-#if defined(FEAT_MULTI_LANG) || defined(PROTO)
-/*
- * In an argument search for a language specifiers in the form "@xx".
- * Changes the "@" to NUL if found, and returns a pointer to "xx".
- * Returns NULL if not found.
- */
- char_u *
-check_help_lang(char_u *arg)
-{
- int len = (int)STRLEN(arg);
-
- if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2])
- && ASCII_ISALPHA(arg[len - 1]))
- {
- arg[len - 3] = NUL; // remove the '@'
- return arg + len - 2;
- }
- return NULL;
-}
-#endif
-
-/*
- * Return a heuristic indicating how well the given string matches. The
- * smaller the number, the better the match. This is the order of priorities,
- * from best match to worst match:
- * - Match with least alphanumeric characters is better.
- * - Match with least total characters is better.
- * - Match towards the start is better.
- * - Match starting with "+" is worse (feature instead of command)
- * Assumption is made that the matched_string passed has already been found to
- * match some string for which help is requested. webb.
- */
- int
-help_heuristic(
- char_u *matched_string,
- int offset, // offset for match
- int wrong_case) // no matching case
-{
- int num_letters;
- char_u *p;
-
- num_letters = 0;
- for (p = matched_string; *p; p++)
- if (ASCII_ISALNUM(*p))
- num_letters++;
-
- /*
- * Multiply the number of letters by 100 to give it a much bigger
- * weighting than the number of characters.
- * If there only is a match while ignoring case, add 5000.
- * If the match starts in the middle of a word, add 10000 to put it
- * somewhere in the last half.
- * If the match is more than 2 chars from the start, multiply by 200 to
- * put it after matches at the start.
- */
- if (ASCII_ISALNUM(matched_string[offset]) && offset > 0
- && ASCII_ISALNUM(matched_string[offset - 1]))
- offset += 10000;
- else if (offset > 2)
- offset *= 200;
- if (wrong_case)
- offset += 5000;
- // Features are less interesting than the subjects themselves, but "+"
- // alone is not a feature.
- if (matched_string[0] == '+' && matched_string[1] != NUL)
- offset += 100;
- return (int)(100 * num_letters + STRLEN(matched_string) + offset);
-}
-
-/*
- * Compare functions for qsort() below, that checks the help heuristics number
- * that has been put after the tagname by find_tags().
- */
- static int
-help_compare(const void *s1, const void *s2)
-{
- char *p1;
- char *p2;
- int cmp;
-
- p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
- p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
-
- // Compare by help heuristic number first.
- cmp = strcmp(p1, p2);
- if (cmp != 0)
- return cmp;
-
- // Compare by strings as tie-breaker when same heuristic number.
- return strcmp(*(char **)s1, *(char **)s2);
-}
-
-/*
- * Find all help tags matching "arg", sort them and return in matches[], with
- * the number of matches in num_matches.
- * The matches will be sorted with a "best" match algorithm.
- * When "keep_lang" is TRUE try keeping the language of the current buffer.
- */
- int
-find_help_tags(
- char_u *arg,
- int *num_matches,
- char_u ***matches,
- int keep_lang)
-{
- char_u *s, *d;
- int i;
- static char *(mtable[]) = {"*", "g*", "[*", "]*", ":*",
- "/*", "/\\*", "\"*", "**",
- "cpo-*", "/\\(\\)", "/\\%(\\)",
- "?", ":?", "?<CR>", "g?", "g?g?", "g??",
- "-?", "q?", "v_g?",
- "/\\?", "/\\z(\\)", "\\=", ":s\\=",
- "[count]", "[quotex]",
- "[range]", ":[range]",
- "[pattern]", "\\|", "\\%$",
- "s/\\~", "s/\\U", "s/\\L",
- "s/\\1", "s/\\2", "s/\\3", "s/\\9"};
- static char *(rtable[]) = {"star", "gstar", "[star", "]star", ":star",
- "/star", "/\\\\star", "quotestar", "starstar",
- "cpo-star", "/\\\\(\\\\)", "/\\\\%(\\\\)",
- "?", ":?", "?<CR>", "g?", "g?g?", "g??",
- "-?", "q?", "v_g?",
- "/\\\\?", "/\\\\z(\\\\)", "\\\\=", ":s\\\\=",
- "\\[count]", "\\[quotex]",
- "\\[range]", ":\\[range]",
- "\\[pattern]", "\\\\bar", "/\\\\%\\$",
- "s/\\\\\\~", "s/\\\\U", "s/\\\\L",
- "s/\\\\1", "s/\\\\2", "s/\\\\3", "s/\\\\9"};
- static char *(expr_table[]) = {"!=?", "!~?", "<=?", "<?", "==?", "=~?",
- ">=?", ">?", "is?", "isnot?"};
- int flags;
-
- d = IObuff; // assume IObuff is long enough!
-
- if (STRNICMP(arg, "expr-", 5) == 0)
- {
- // When the string starting with "expr-" and containing '?' and matches
- // the table, it is taken literally (but ~ is escaped). Otherwise '?'
- // is recognized as a wildcard.
- for (i = (int)(sizeof(expr_table) / sizeof(char *)); --i >= 0; )
- if (STRCMP(arg + 5, expr_table[i]) == 0)
- {
- int si = 0, di = 0;
-
- for (;;)
- {
- if (arg[si] == '~')
- d[di++] = '\\';
- d[di++] = arg[si];
- if (arg[si] == NUL)
- break;
- ++si;
- }
- break;
- }
- }
- else
- {
- // Recognize a few exceptions to the rule. Some strings that contain
- // '*' with "star". Otherwise '*' is recognized as a wildcard.
- for (i = (int)(sizeof(mtable) / sizeof(char *)); --i >= 0; )
- if (STRCMP(arg, mtable[i]) == 0)
- {
- STRCPY(d, rtable[i]);
- break;
- }
- }
-
- if (i < 0) // no match in table
- {
- // Replace "\S" with "/\\S", etc. Otherwise every tag is matched.
- // Also replace "\%^" and "\%(", they match every tag too.
- // Also "\zs", "\z1", etc.
- // Also "\@<", "\@=", "\@<=", etc.
- // And also "\_$" and "\_^".
- if (arg[0] == '\\'
- && ((arg[1] != NUL && arg[2] == NUL)
- || (vim_strchr((char_u *)"%_z@", arg[1]) != NULL
- && arg[2] != NUL)))
- {
- STRCPY(d, "/\\\\");
- STRCPY(d + 3, arg + 1);
- // Check for "/\\_$", should be "/\\_\$"
- if (d[3] == '_' && d[4] == '$')
- STRCPY(d + 4, "\\$");
- }
- else
- {
- // Replace:
- // "[:...:]" with "\[:...:]"
- // "[++...]" with "\[++...]"
- // "\{" with "\\{" -- matching "} \}"
- if ((arg[0] == '[' && (arg[1] == ':'
- || (arg[1] == '+' && arg[2] == '+')))
- || (arg[0] == '\\' && arg[1] == '{'))
- *d++ = '\\';
-
- /*
- * If tag starts with "('", skip the "(". Fixes CTRL-] on ('option'.
- */
- if (*arg == '(' && arg[1] == '\'')
- arg++;
- for (s = arg; *s; ++s)
- {
- /*
- * Replace "|" with "bar" and '"' with "quote" to match the name of
- * the tags for these commands.
- * Replace "*" with ".*" and "?" with "." to match command line
- * completion.
- * Insert a backslash before '~', '$' and '.' to avoid their
- * special meaning.
- */
- if (d - IObuff > IOSIZE - 10) // getting too long!?
- break;
- switch (*s)
- {
- case '|': STRCPY(d, "bar");
- d += 3;
- continue;
- case '"': STRCPY(d, "quote");
- d += 5;
- continue;
- case '*': *d++ = '.';
- break;
- case '?': *d++ = '.';
- continue;
- case '$':
- case '.':
- case '~': *d++ = '\\';
- break;
- }
-
- /*
- * Replace "^x" by "CTRL-X". Don't do this for "^_" to make
- * ":help i_^_CTRL-D" work.
- * Insert '-' before and after "CTRL-X" when applicable.
- */
- if (*s < ' ' || (*s == '^' && s[1] && (ASCII_ISALPHA(s[1])
- || vim_strchr((char_u *)"?@[\\]^", s[1]) != NULL)))
- {
- if (d > IObuff && d[-1] != '_' && d[-1] != '\\')
- *d++ = '_'; // prepend a '_' to make x_CTRL-x
- STRCPY(d, "CTRL-");
- d += 5;
- if (*s < ' ')
- {
-#ifdef EBCDIC
- *d++ = CtrlChar(*s);
-#else
- *d++ = *s + '@';
-#endif
- if (d[-1] == '\\')
- *d++ = '\\'; // double a backslash
- }
- else
- *d++ = *++s;
- if (s[1] != NUL && s[1] != '_')
- *d++ = '_'; // append a '_'
- continue;
- }
- else if (*s == '^') // "^" or "CTRL-^" or "^_"
- *d++ = '\\';
-
- /*
- * Insert a backslash before a backslash after a slash, for search
- * pattern tags: "/\|" --> "/\\|".
- */
- else if (s[0] == '\\' && s[1] != '\\'
- && *arg == '/' && s == arg + 1)
- *d++ = '\\';
-
- // "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in
- // "CTRL-\_CTRL-N"
- if (STRNICMP(s, "CTRL-\\_", 7) == 0)
- {
- STRCPY(d, "CTRL-\\\\");
- d += 7;
- s += 6;
- }
-
- *d++ = *s;
-
- /*
- * If tag contains "({" or "([", tag terminates at the "(".
- * This is for help on functions, e.g.: abs({expr}).
- */
- if (*s == '(' && (s[1] == '{' || s[1] =='['))
- break;
-
- /*
- * If tag starts with ', toss everything after a second '. Fixes
- * CTRL-] on 'option'. (would include the trailing '.').
- */
- if (*s == '\'' && s > arg && *arg == '\'')
- break;
- // Also '{' and '}'.
- if (*s == '}' && s > arg && *arg == '{')
- break;
- }
- *d = NUL;
-
- if (*IObuff == '`')
- {
- if (d > IObuff + 2 && d[-1] == '`')
- {
- // remove the backticks from `command`
- mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff));
- d[-2] = NUL;
- }
- else if (d > IObuff + 3 && d[-2] == '`' && d[-1] == ',')
- {
- // remove the backticks and comma from `command`,
- mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff));
- d[-3] = NUL;
- }
- else if (d > IObuff + 4 && d[-3] == '`'
- && d[-2] == '\\' && d[-1] == '.')
- {
- // remove the backticks and dot from `command`\.
- mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff));
- d[-4] = NUL;
- }
- }
- }
- }
-
- *matches = (char_u **)"";
- *num_matches = 0;
- flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE | TAG_NO_TAGFUNC;
- if (keep_lang)
- flags |= TAG_KEEP_LANG;
- if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK
- && *num_matches > 0)
- {
- // Sort the matches found on the heuristic number that is after the
- // tag name.
- qsort((void *)*matches, (size_t)*num_matches,
- sizeof(char_u *), help_compare);
- // Delete more than TAG_MANY to reduce the size of the listing.
- while (*num_matches > TAG_MANY)
- vim_free((*matches)[--*num_matches]);
- }
- return OK;
-}
-
-/*
- * Called when starting to edit a buffer for a help file.
- */
- static void
-prepare_help_buffer(void)
-{
- char_u *p;
-
- curbuf->b_help = TRUE;
-#ifdef FEAT_QUICKFIX
- set_string_option_direct((char_u *)"buftype", -1,
- (char_u *)"help", OPT_FREE|OPT_LOCAL, 0);
-#endif
-
- /*
- * Always set these options after jumping to a help tag, because the
- * user may have an autocommand that gets in the way.
- * Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and
- * latin1 word characters (for translated help files).
- * Only set it when needed, buf_init_chartab() is some work.
- */
- p =
-#ifdef EBCDIC
- (char_u *)"65-255,^*,^|,^\"";
-#else
- (char_u *)"!-~,^*,^|,^\",192-255";
-#endif
- if (STRCMP(curbuf->b_p_isk, p) != 0)
- {
- set_string_option_direct((char_u *)"isk", -1, p, OPT_FREE|OPT_LOCAL, 0);
- check_buf_options(curbuf);
- (void)buf_init_chartab(curbuf, FALSE);
- }
-
-#ifdef FEAT_FOLDING
- // Don't use the global foldmethod.
- set_string_option_direct((char_u *)"fdm", -1, (char_u *)"manual",
- OPT_FREE|OPT_LOCAL, 0);
-#endif
-
- curbuf->b_p_ts = 8; // 'tabstop' is 8
- curwin->w_p_list = FALSE; // no list mode
-
- curbuf->b_p_ma = FALSE; // not modifiable
- curbuf->b_p_bin = FALSE; // reset 'bin' before reading file
- curwin->w_p_nu = 0; // no line numbers
- curwin->w_p_rnu = 0; // no relative line numbers
- RESET_BINDING(curwin); // no scroll or cursor binding
-#ifdef FEAT_ARABIC
- curwin->w_p_arab = FALSE; // no arabic mode
-#endif
-#ifdef FEAT_RIGHTLEFT
- curwin->w_p_rl = FALSE; // help window is left-to-right
-#endif
-#ifdef FEAT_FOLDING
- curwin->w_p_fen = FALSE; // No folding in the help window
-#endif
-#ifdef FEAT_DIFF
- curwin->w_p_diff = FALSE; // No 'diff'
-#endif
-#ifdef FEAT_SPELL
- curwin->w_p_spell = FALSE; // No spell checking
-#endif
-
- set_buflisted(FALSE);
-}
-
-/*
- * After reading a help file: May cleanup a help buffer when syntax
- * highlighting is not used.
- */
- void
-fix_help_buffer(void)
-{
- linenr_T lnum;
- char_u *line;
- int in_example = FALSE;
- int len;
- char_u *fname;
- char_u *p;
- char_u *rt;
- int mustfree;
-
- // Set filetype to "help" if still needed.
- if (STRCMP(curbuf->b_p_ft, "help") != 0)
- {
- ++curbuf_lock;
- set_option_value((char_u *)"ft", 0L, (char_u *)"help", OPT_LOCAL);
- --curbuf_lock;
- }
-
-#ifdef FEAT_SYN_HL
- if (!syntax_present(curwin))
-#endif
- {
- for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum)
- {
- line = ml_get_buf(curbuf, lnum, FALSE);
- len = (int)STRLEN(line);
- if (in_example && len > 0 && !VIM_ISWHITE(line[0]))
- {
- // End of example: non-white or '<' in first column.
- if (line[0] == '<')
- {
- // blank-out a '<' in the first column
- line = ml_get_buf(curbuf, lnum, TRUE);
- line[0] = ' ';
- }
- in_example = FALSE;
- }
- if (!in_example && len > 0)
- {
- if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' '))
- {
- // blank-out a '>' in the last column (start of example)
- line = ml_get_buf(curbuf, lnum, TRUE);
- line[len - 1] = ' ';
- in_example = TRUE;
- }
- else if (line[len - 1] == '~')
- {
- // blank-out a '~' at the end of line (header marker)
- line = ml_get_buf(curbuf, lnum, TRUE);
- line[len - 1] = ' ';
- }
- }
- }
- }
-
- /*
- * In the "help.txt" and "help.abx" file, add the locally added help
- * files. This uses the very first line in the help file.
- */
- fname = gettail(curbuf->b_fname);
- if (fnamecmp(fname, "help.txt") == 0
-#ifdef FEAT_MULTI_LANG
- || (fnamencmp(fname, "help.", 5) == 0
- && ASCII_ISALPHA(fname[5])
- && ASCII_ISALPHA(fname[6])
- && TOLOWER_ASC(fname[7]) == 'x'
- && fname[8] == NUL)
-#endif
- )
- {
- for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; ++lnum)
- {
- line = ml_get_buf(curbuf, lnum, FALSE);
- if (strstr((char *)line, "*local-additions*") == NULL)
- continue;
-
- // Go through all directories in 'runtimepath', skipping
- // $VIMRUNTIME.
- p = p_rtp;
- while (*p != NUL)
- {
- copy_option_part(&p, NameBuff, MAXPATHL, ",");
- mustfree = FALSE;
- rt = vim_getenv((char_u *)"VIMRUNTIME", &mustfree);
- if (rt != NULL &&
- fullpathcmp(rt, NameBuff, FALSE, TRUE) != FPC_SAME)
- {
- int fcount;
- char_u **fnames;
- FILE *fd;
- char_u *s;
- int fi;
- vimconv_T vc;
- char_u *cp;
-
- // Find all "doc/ *.txt" files in this directory.
- add_pathsep(NameBuff);
-#ifdef FEAT_MULTI_LANG
- STRCAT(NameBuff, "doc/*.??[tx]");
-#else
- STRCAT(NameBuff, "doc/*.txt");
-#endif
- if (gen_expand_wildcards(1, &NameBuff, &fcount,
- &fnames, EW_FILE|EW_SILENT) == OK
- && fcount > 0)
- {
-#ifdef FEAT_MULTI_LANG
- int i1, i2;
- char_u *f1, *f2;
- char_u *t1, *t2;
- char_u *e1, *e2;
-
- // If foo.abx is found use it instead of foo.txt in
- // the same directory.
- for (i1 = 0; i1 < fcount; ++i1)
- {
- for (i2 = 0; i2 < fcount; ++i2)
- {
- if (i1 == i2)
- continue;
- if (fnames[i1] == NULL || fnames[i2] == NULL)
- continue;
- f1 = fnames[i1];
- f2 = fnames[i2];
- t1 = gettail(f1);
- t2 = gettail(f2);
- e1 = vim_strrchr(t1, '.');
- e2 = vim_strrchr(t2, '.');
- if (e1 == NULL || e2 == NULL)
- continue;
- if (fnamecmp(e1, ".txt") != 0
- && fnamecmp(e1, fname + 4) != 0)
- {
- // Not .txt and not .abx, remove it.
- VIM_CLEAR(fnames[i1]);
- continue;
- }
- if (e1 - f1 != e2 - f2
- || fnamencmp(f1, f2, e1 - f1) != 0)
- continue;
- if (fnamecmp(e1, ".txt") == 0
- && fnamecmp(e2, fname + 4) == 0)
- // use .abx instead of .txt
- VIM_CLEAR(fnames[i1]);
- }
- }
-#endif
- for (fi = 0; fi < fcount; ++fi)
- {
- if (fnames[fi] == NULL)
- continue;
- fd = mch_fopen((char *)fnames[fi], "r");
- if (fd != NULL)
- {
- vim_fgets(IObuff, IOSIZE, fd);
- if (IObuff[0] == '*'
- && (s = vim_strchr(IObuff + 1, '*'))
- != NULL)
- {
- int this_utf = MAYBE;
-
- // Change tag definition to a
- // reference and remove <CR>/<NL>.
- IObuff[0] = '|';
- *s = '|';
- while (*s != NUL)
- {
- if (*s == '\r' || *s == '\n')
- *s = NUL;
- // The text is utf-8 when a byte
- // above 127 is found and no
- // illegal byte sequence is found.
- if (*s >= 0x80 && this_utf != FALSE)
- {
- int l;
-
- this_utf = TRUE;
- l = utf_ptr2len(s);
- if (l == 1)
- this_utf = FALSE;
- s += l - 1;
- }
- ++s;
- }
-
- // The help file is latin1 or utf-8;
- // conversion to the current
- // 'encoding' may be required.
- vc.vc_type = CONV_NONE;
- convert_setup(&vc, (char_u *)(
- this_utf == TRUE ? "utf-8"
- : "latin1"), p_enc);
- if (vc.vc_type == CONV_NONE)
- // No conversion needed.
- cp = IObuff;
- else
- {
- // Do the conversion. If it fails
- // use the unconverted text.
- cp = string_convert(&vc, IObuff,
- NULL);
- if (cp == NULL)
- cp = IObuff;
- }
- convert_setup(&vc, NULL, NULL);
-
- ml_append(lnum, cp, (colnr_T)0, FALSE);
- if (cp != IObuff)
- vim_free(cp);
- ++lnum;
- }
- fclose(fd);
- }
- }
- FreeWild(fcount, fnames);
- }
- }
- if (mustfree)
- vim_free(rt);
- }
- break;
- }
- }
-}
-
-/*
- * ":exusage"
- */
- void
-ex_exusage(exarg_T *eap UNUSED)
-{
- do_cmdline_cmd((char_u *)"help ex-cmd-index");
-}
-
-/*
- * ":viusage"
- */
- void
-ex_viusage(exarg_T *eap UNUSED)
-{
- do_cmdline_cmd((char_u *)"help normal-index");
-}
-
-/*
- * Generate tags in one help directory.
- */
- static void
-helptags_one(
- char_u *dir, // doc directory
- char_u *ext, // suffix, ".txt", ".itx", ".frx", etc.
- char_u *tagfname, // "tags" for English, "tags-fr" for French.
- int add_help_tags, // add "help-tags" tag
- int ignore_writeerr) // ignore write error
-{
- FILE *fd_tags;
- FILE *fd;
- garray_T ga;
- int filecount;
- char_u **files;
- char_u *p1, *p2;
- int fi;
- char_u *s;
- int i;
- char_u *fname;
- int dirlen;
- int utf8 = MAYBE;
- int this_utf8;
- int firstline;
- int mix = FALSE; // detected mixed encodings
-
- /*
- * Find all *.txt files.
- */
- dirlen = (int)STRLEN(dir);
- STRCPY(NameBuff, dir);
- STRCAT(NameBuff, "/**/*");
- STRCAT(NameBuff, ext);
- if (gen_expand_wildcards(1, &NameBuff, &filecount, &files,
- EW_FILE|EW_SILENT) == FAIL
- || filecount == 0)
- {
- if (!got_int)
- semsg(_("E151: No match: %s"), NameBuff);
- return;
- }
-
- /*
- * Open the tags file for writing.
- * Do this before scanning through all the files.
- */
- STRCPY(NameBuff, dir);
- add_pathsep(NameBuff);
- STRCAT(NameBuff, tagfname);
- fd_tags = mch_fopen((char *)NameBuff, "w");
- if (fd_tags == NULL)
- {
- if (!ignore_writeerr)
- semsg(_("E152: Cannot open %s for writing"), NameBuff);
- FreeWild(filecount, files);
- return;
- }
-
- /*
- * If using the "++t" argument or generating tags for "$VIMRUNTIME/doc"
- * add the "help-tags" tag.
- */
- ga_init2(&ga, (int)sizeof(char_u *), 100);
- if (add_help_tags || fullpathcmp((char_u *)"$VIMRUNTIME/doc",
- dir, FALSE, TRUE) == FPC_SAME)
- {
- if (ga_grow(&ga, 1) == FAIL)
- got_int = TRUE;
- else
- {
- s = alloc(18 + (unsigned)STRLEN(tagfname));
- if (s == NULL)
- got_int = TRUE;
- else
- {
- sprintf((char *)s, "help-tags\t%s\t1\n", tagfname);
- ((char_u **)ga.ga_data)[ga.ga_len] = s;
- ++ga.ga_len;
- }
- }
- }
-
- /*
- * Go over all the files and extract the tags.
- */
- for (fi = 0; fi < filecount && !got_int; ++fi)
- {
- fd = mch_fopen((char *)files[fi], "r");
- if (fd == NULL)
- {
- semsg(_("E153: Unable to open %s for reading"), files[fi]);
- continue;
- }
- fname = files[fi] + dirlen + 1;
-
- firstline = TRUE;
- while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int)
- {
- if (firstline)
- {
- // Detect utf-8 file by a non-ASCII char in the first line.
- this_utf8 = MAYBE;
- for (s = IObuff; *s != NUL; ++s)
- if (*s >= 0x80)
- {
- int l;
-
- this_utf8 = TRUE;
- l = utf_ptr2len(s);
- if (l == 1)
- {
- // Illegal UTF-8 byte sequence.
- this_utf8 = FALSE;
- break;
- }
- s += l - 1;
- }
- if (this_utf8 == MAYBE) // only ASCII characters found
- this_utf8 = FALSE;
- if (utf8 == MAYBE) // first file
- utf8 = this_utf8;
- else if (utf8 != this_utf8)
- {
- semsg(_("E670: Mix of help file encodings within a language: %s"), files[fi]);
- mix = !got_int;
- got_int = TRUE;
- }
- firstline = FALSE;
- }
- p1 = vim_strchr(IObuff, '*'); // find first '*'
- while (p1 != NULL)
- {
- // Use vim_strbyte() instead of vim_strchr() so that when
- // 'encoding' is dbcs it still works, don't find '*' in the
- // second byte.
- p2 = vim_strbyte(p1 + 1, '*'); // find second '*'
- if (p2 != NULL && p2 > p1 + 1) // skip "*" and "**"
- {
- for (s = p1 + 1; s < p2; ++s)
- if (*s == ' ' || *s == '\t' || *s == '|')
- break;
-
- /*
- * Only accept a *tag* when it consists of valid
- * characters, there is white space before it and is
- * followed by a white character or end-of-line.
- */
- if (s == p2
- && (p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t')
- && (vim_strchr((char_u *)" \t\n\r", s[1]) != NULL
- || s[1] == '\0'))
- {
- *p2 = '\0';
- ++p1;
- if (ga_grow(&ga, 1) == FAIL)
- {
- got_int = TRUE;
- break;
- }
- s = alloc(p2 - p1 + STRLEN(fname) + 2);
- if (s == NULL)
- {
- got_int = TRUE;
- break;
- }
- ((char_u **)ga.ga_data)[ga.ga_len] = s;
- ++ga.ga_len;
- sprintf((char *)s, "%s\t%s", p1, fname);
-
- // find next '*'
- p2 = vim_strchr(p2 + 1, '*');
- }
- }
- p1 = p2;
- }
- line_breakcheck();
- }
-
- fclose(fd);
- }
-
- FreeWild(filecount, files);
-
- if (!got_int)
- {
- /*
- * Sort the tags.
- */
- if (ga.ga_data != NULL)
- sort_strings((char_u **)ga.ga_data, ga.ga_len);
-
- /*
- * Check for duplicates.
- */
- for (i = 1; i < ga.ga_len; ++i)
- {
- p1 = ((char_u **)ga.ga_data)[i - 1];
- p2 = ((char_u **)ga.ga_data)[i];
- while (*p1 == *p2)
- {
- if (*p2 == '\t')
- {
- *p2 = NUL;
- vim_snprintf((char *)NameBuff, MAXPATHL,
- _("E154: Duplicate tag \"%s\" in file %s/%s"),
- ((char_u **)ga.ga_data)[i], dir, p2 + 1);
- emsg((char *)NameBuff);
- *p2 = '\t';
- break;
- }
- ++p1;
- ++p2;
- }
- }
-
- if (utf8 == TRUE)
- fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n");
-
- /*
- * Write the tags into the file.
- */
- for (i = 0; i < ga.ga_len; ++i)
- {
- s = ((char_u **)ga.ga_data)[i];
- if (STRNCMP(s, "help-tags\t", 10) == 0)
- // help-tags entry was added in formatted form
- fputs((char *)s, fd_tags);
- else
- {
- fprintf(fd_tags, "%s\t/*", s);
- for (p1 = s; *p1 != '\t'; ++p1)
- {
- // insert backslash before '\\' and '/'
- if (*p1 == '\\' || *p1 == '/')
- putc('\\', fd_tags);
- putc(*p1, fd_tags);
- }
- fprintf(fd_tags, "*\n");
- }
- }
- }
- if (mix)
- got_int = FALSE; // continue with other languages
-
- for (i = 0; i < ga.ga_len; ++i)
- vim_free(((char_u **)ga.ga_data)[i]);
- ga_clear(&ga);
- fclose(fd_tags); // there is no check for an error...
-}
-
-/*
- * Generate tags in one help directory, taking care of translations.
- */
- static void
-do_helptags(char_u *dirname, int add_help_tags, int ignore_writeerr)
-{
-#ifdef FEAT_MULTI_LANG
- int len;
- int i, j;
- garray_T ga;
- char_u lang[2];
- char_u ext[5];
- char_u fname[8];
- int filecount;
- char_u **files;
-
- // Get a list of all files in the help directory and in subdirectories.
- STRCPY(NameBuff, dirname);
- add_pathsep(NameBuff);
- STRCAT(NameBuff, "**");
- if (gen_expand_wildcards(1, &NameBuff, &filecount, &files,
- EW_FILE|EW_SILENT) == FAIL
- || filecount == 0)
- {
- semsg(_("E151: No match: %s"), NameBuff);
- return;
- }
-
- // Go over all files in the directory to find out what languages are
- // present.
- ga_init2(&ga, 1, 10);
- for (i = 0; i < filecount; ++i)
- {
- len = (int)STRLEN(files[i]);
- if (len > 4)
- {
- if (STRICMP(files[i] + len - 4, ".txt") == 0)
- {
- // ".txt" -> language "en"
- lang[0] = 'e';
- lang[1] = 'n';
- }
- else if (files[i][len - 4] == '.'
- && ASCII_ISALPHA(files[i][len - 3])
- && ASCII_ISALPHA(files[i][len - 2])
- && TOLOWER_ASC(files[i][len - 1]) == 'x')
- {
- // ".abx" -> language "ab"
- lang[0] = TOLOWER_ASC(files[i][len - 3]);
- lang[1] = TOLOWER_ASC(files[i][len - 2]);
- }
- else
- continue;