aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/ex_cmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/ex_cmds.c')
-rw-r--r--src/nvim/ex_cmds.c1163
1 files changed, 25 insertions, 1138 deletions
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 9d1ac185d4..bc8e823797 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -15,15 +15,18 @@
#include "nvim/api/buffer.h"
#include "nvim/api/private/defs.h"
+#include "nvim/arglist.h"
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/buffer_updates.h"
#include "nvim/change.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/diff.h"
#include "nvim/digraph.h"
+#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
@@ -36,6 +39,7 @@
#include "nvim/fold.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
+#include "nvim/help.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
@@ -59,9 +63,9 @@
#include "nvim/os_unix.h"
#include "nvim/path.h"
#include "nvim/plines.h"
+#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/strings.h"
@@ -300,7 +304,7 @@ void ex_align(exarg_T *eap)
new_indent--;
break;
}
- --new_indent;
+ new_indent--;
}
}
}
@@ -1091,12 +1095,12 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
if (line1 == n) {
line1 = curwin->w_cursor.lnum;
}
- ++line1;
+ line1++;
if (curwin->w_cursor.lnum < line1) {
- ++line1;
+ line1++;
}
if (curwin->w_cursor.lnum < line2) {
- ++line2;
+ line2++;
}
++curwin->w_cursor.lnum;
}
@@ -1198,7 +1202,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
break;
}
}
- ++p;
+ p++;
}
} while (trailarg != NULL);
@@ -1442,7 +1446,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
}
beginline(BL_WHITE | BL_FIX); // cursor on first non-blank
- --no_wait_return;
+ no_wait_return--;
if (linecount > p_report) {
if (do_in) {
@@ -1460,8 +1464,8 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
error:
// put cursor back in same position for ":w !cmd"
curwin->w_cursor = cursor_save;
- --no_wait_return;
- wait_return(FALSE);
+ no_wait_return--;
+ wait_return(false);
}
filterend:
@@ -2111,7 +2115,7 @@ void do_wqall(exarg_T *eap)
* 4. if overwriting is allowed (even after a dialog)
*/
if (not_writing()) {
- ++error;
+ error++;
break;
}
if (buf->b_ffname == NULL) {
@@ -2246,7 +2250,7 @@ int getfile(int fnum, char *ffname_arg, char *sfname_arg, int setpm, linenr_T ln
}
}
if (other) {
- --no_wait_return;
+ no_wait_return--;
}
if (setpm) {
setpcmark();
@@ -2959,7 +2963,7 @@ void ex_append(exarg_T *eap)
}
if (eap->cmdidx != CMD_append) {
- --lnum;
+ lnum--;
}
// when the buffer is empty need to delete the dummy line
@@ -3015,7 +3019,7 @@ void ex_append(exarg_T *eap)
vcol = 0;
for (p = theline; indent > vcol; ++p) {
if (*p == ' ') {
- ++vcol;
+ vcol++;
} else if (*p == TAB) {
vcol += 8 - vcol % 8;
} else {
@@ -3044,7 +3048,7 @@ void ex_append(exarg_T *eap)
}
xfree(theline);
- ++lnum;
+ lnum++;
if (empty) {
ml_delete(2L, false);
@@ -3136,10 +3140,10 @@ void ex_z(exarg_T *eap)
kind = x;
if (*kind == '-' || *kind == '+' || *kind == '='
|| *kind == '^' || *kind == '.') {
- ++x;
+ x++;
}
while (*x == '-' || *x == '+') {
- ++x;
+ x++;
}
if (*x != 0) {
@@ -3196,7 +3200,7 @@ void ex_z(exarg_T *eap)
if (*kind == '+') {
start += (linenr_T)bigness * (linenr_T)(x - kind - 1) + 1;
} else if (eap->addr_count == 0) {
- ++start;
+ start++;
}
end = start + (linenr_T)bigness - 1;
curs = end;
@@ -3343,6 +3347,7 @@ static bool sub_joining_lines(exarg_T *eap, char *pat, char *sub, char *cmd, boo
if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) {
save_re_pat(RE_SUBST, (char_u *)pat, p_magic);
}
+ // put pattern in history
add_to_history(HIST_SEARCH, (char_u *)pat, true, NUL);
}
@@ -3541,7 +3546,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
which_pat = RE_LAST; // use last used regexp
delimiter = (char_u)(*cmd++); // remember delimiter character
pat = cmd; // remember start of search pat
- cmd = (char *)skip_regexp((char_u *)cmd, delimiter, p_magic, (char_u **)&eap->arg);
+ cmd = (char *)skip_regexp((char_u *)cmd, delimiter, p_magic, &eap->arg);
if (cmd[0] == delimiter) { // end delimiter found
*cmd++ = NUL; // replace it with a NUL
has_second_delim = true;
@@ -4296,7 +4301,7 @@ skip:
* has been appended to new_start, we don't need
* it in the buffer.
*/
- ++lnum;
+ lnum++;
if (u_savedel(lnum, nmatch_tl) != OK) {
break;
}
@@ -4626,7 +4631,7 @@ void ex_global(exarg_T *eap)
delim = *cmd; // get the delimiter
cmd++; // skip delimiter if there is one
pat = cmd; // remember start of pattern
- cmd = (char *)skip_regexp((char_u *)cmd, delim, p_magic, (char_u **)&eap->arg);
+ cmd = (char *)skip_regexp((char_u *)cmd, delim, p_magic, &eap->arg);
if (cmd[0] == delim) { // end delimiter found
*cmd++ = NUL; // replace it with a NUL
}
@@ -4775,1124 +4780,6 @@ bool prepare_tagpreview(bool undo_sync)
return false;
}
-/// ":help": open a read-only window on a help file
-void ex_help(exarg_T *eap)
-{
- char *arg;
- char *tag;
- FILE *helpfd; // file descriptor of help file
- int n;
- int i;
- win_T *wp;
- int num_matches;
- char **matches;
- char *p;
- int empty_fnum = 0;
- int alt_fnum = 0;
- buf_T *buf;
- int len;
- char *lang;
- const bool old_KeyTyped = KeyTyped;
-
- 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 = "";
- }
-
- // remove trailing blanks
- p = arg + STRLEN(arg) - 1;
- while (p > arg && ascii_iswhite(*p) && p[-1] != '\\') {
- *p-- = NUL;
- }
-
- // Check for a specified language
- lang = check_help_lang(arg);
-
- // When no argument given go to the index.
- if (*arg == NUL) {
- arg = "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;
- 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;
- }
- }
- }
- if (i >= num_matches || n == FAIL) {
- if (lang != NULL) {
- semsg(_("E661: Sorry, no '%s' help for %s"), lang, arg);
- } else {
- 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 = xstrdup(matches[i]);
- FreeWild(num_matches, matches);
-
- /*
- * 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.cmod_tab != 0) {
- if (cmdmod.cmod_tab != 0) {
- wp = NULL;
- } else {
- wp = NULL;
- FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) {
- if (bt_help(wp2->w_buffer)) {
- wp = wp2;
- 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 = os_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.cmod_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.cmod_flags & CMOD_KEEPALT) == 0) {
- curwin->w_alt_fnum = alt_fnum;
- }
- empty_fnum = curbuf->b_fnum;
- }
- }
-
- restart_edit = 0; // don't want insert mode in help file
-
- // Restore KeyTyped, setting 'filetype=help' may reset it.
- // It is needed for do_tag top open folds under the cursor.
- KeyTyped = old_KeyTyped;
-
- do_tag((char_u *)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.cmod_flags & CMOD_KEEPALT) == 0) {
- curwin->w_alt_fnum = alt_fnum;
- }
-
-erret:
- xfree(tag);
-}
-
-/// In an argument search for a language specifiers in the form "@xx".
-/// Changes the "@" to NUL if found, and returns a pointer to "xx".
-///
-/// @return NULL if not found.
-char *check_help_lang(char *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;
-}
-
-/// 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.
-///
-/// @param offset offset for match
-/// @param wrong_case no matching case
-///
-/// @return a heuristic indicating how well the given string matches.
-int help_heuristic(char *matched_string, int offset, int wrong_case)
- FUNC_ATTR_PURE
-{
- int num_letters;
- char *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 (offset > 0
- && ASCII_ISALNUM(matched_string[offset])
- && 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 100 * num_letters + (int)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;
-
- p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
- p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
-
- // Compare by help heuristic number first.
- int 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(const char *arg, int *num_matches, char ***matches, bool keep_lang)
-{
- int i;
-
- // Specific tags that either have a specific replacement or won't go
- // through the generic rules.
- static char *(except_tbl[][2]) = {
- { "*", "star" },
- { "g*", "gstar" },
- { "[*", "[star" },
- { "]*", "]star" },
- { ":*", ":star" },
- { "/*", "/star" }, // NOLINT
- { "/\\*", "/\\\\star" },
- { "\"*", "quotestar" },
- { "**", "starstar" },
- { "cpo-*", "cpo-star" },
- { "/\\(\\)", "/\\\\(\\\\)" },
- { "/\\%(\\)", "/\\\\%(\\\\)" },
- { "?", "?" },
- { "??", "??" },
- { ":?", ":?" },
- { "?<CR>", "?<CR>" },
- { "g?", "g?" },
- { "g?g?", "g?g?" },
- { "g??", "g??" },
- { "-?", "-?" },
- { "q?", "q?" },
- { "v_g?", "v_g?" },
- { "/\\?", "/\\\\?" },
- { "/\\z(\\)", "/\\\\z(\\\\)" },
- { "\\=", "\\\\=" },
- { ":s\\=", ":s\\\\=" },
- { "[count]", "\\[count]" },
- { "[quotex]", "\\[quotex]" },
- { "[range]", "\\[range]" },
- { ":[range]", ":\\[range]" },
- { "[pattern]", "\\[pattern]" },
- { "\\|", "\\\\bar" },
- { "\\%$", "/\\\\%\\$" },
- { "s/\\~", "s/\\\\\\~" },
- { "s/\\U", "s/\\\\U" },
- { "s/\\L", "s/\\\\L" },
- { "s/\\1", "s/\\\\1" },
- { "s/\\2", "s/\\\\2" },
- { "s/\\3", "s/\\\\3" },
- { "s/\\9", "s/\\\\9" },
- { NULL, NULL }
- };
-
- static const char *(expr_table[]) = {
- "!=?", "!~?", "<=?", "<?", "==?", "=~?",
- ">=?", ">?", "is?", "isnot?"
- };
- char *d = (char *)IObuff; // assume IObuff is long enough!
- d[0] = NUL;
-
- 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)ARRAY_SIZE(expr_table); --i >= 0;) {
- if (STRCMP(arg + 5, expr_table[i]) == 0) {
- for (int si = 0, di = 0;; si++) {
- if (arg[si] == '~') {
- d[di++] = '\\';
- }
- d[di++] = arg[si];
- if (arg[si] == NUL) {
- break;
- }
- }
- break;
- }
- }
- } else {
- // Recognize a few exceptions to the rule. Some strings that contain
- // '*'are changed to "star", otherwise '*' is recognized as a wildcard.
- for (i = 0; except_tbl[i][0] != NULL; i++) {
- if (STRCMP(arg, except_tbl[i][0]) == 0) {
- STRCPY(d, except_tbl[i][1]);
- break;
- }
- }
- }
-
- if (d[0] == NUL) { // 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("%_z@", arg[1]) != NULL
- && arg[2] != NUL))) {
- vim_snprintf(d, IOSIZE, "/\\\\%s", 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 (const char *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 ((char_u *)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("?@[\\]^", s[1]) != NULL))) {
- if ((char_u *)d > IObuff && d[-1] != '_' && d[-1] != '\\') {
- *d++ = '_'; // prepend a '_' to make x_CTRL-x
- }
- STRCPY(d, "CTRL-");
- d += 5;
- if (*s < ' ') {
- *d++ = (char)(*s + '@');
- 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 '}'. Fixes CTRL-] on '{address}'.
- if (*s == '}' && s > arg && *arg == '{') {
- break;
- }
- }
- *d = NUL;
-
- if (*IObuff == '`') {
- if ((char_u *)d > IObuff + 2 && d[-1] == '`') {
- // remove the backticks from `command`
- memmove(IObuff, IObuff + 1, STRLEN(IObuff));
- d[-2] = NUL;
- } else if ((char_u *)d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') {
- // remove the backticks and comma from `command`,
- memmove(IObuff, IObuff + 1, STRLEN(IObuff));
- d[-3] = NUL;
- } else if ((char_u *)d > IObuff + 4 && d[-3] == '`'
- && d[-2] == '\\' && d[-1] == '.') {
- // remove the backticks and dot from `command`\.
- memmove(IObuff, IObuff + 1, STRLEN(IObuff));
- d[-4] = NUL;
- }
- }
- }
- }
-
- *matches = NULL;
- *num_matches = 0;
- int 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, 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) {
- xfree((*matches)[--*num_matches]);
- }
- }
- return OK;
-}
-
-/// Called when starting to edit a buffer for a help file.
-static void prepare_help_buffer(void)
-{
- curbuf->b_help = true;
- set_string_option_direct("buftype", -1, "help", OPT_FREE|OPT_LOCAL, 0);
-
- // 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.
- char *p = "!-~,^*,^|,^\",192-255";
- if (STRCMP(curbuf->b_p_isk, p) != 0) {
- set_string_option_direct("isk", -1, p, OPT_FREE|OPT_LOCAL, 0);
- check_buf_options(curbuf);
- (void)buf_init_chartab(curbuf, FALSE);
- }
-
- // Don't use the global foldmethod.
- set_string_option_direct("fdm", -1, "manual", OPT_FREE|OPT_LOCAL, 0);
-
- 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.
- curwin->w_p_arab = FALSE; // No arabic mode.
- curwin->w_p_rl = FALSE; // Help window is left-to-right.
- curwin->w_p_fen = FALSE; // No folding in the help window.
- curwin->w_p_diff = FALSE; // No 'diff'.
- curwin->w_p_spell = FALSE; // No spell checking.
-
- 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 *line;
- bool in_example = false;
-
- // Set filetype to "help".
- if (STRCMP(curbuf->b_p_ft, "help") != 0) {
- curbuf->b_ro_locked++;
- set_option_value("ft", 0L, "help", OPT_LOCAL);
- curbuf->b_ro_locked--;
- }
-
- if (!syntax_present(curwin)) {
- for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
- line = (char *)ml_get_buf(curbuf, lnum, false);
- const size_t len = STRLEN(line);
- if (in_example && len > 0 && !ascii_iswhite(line[0])) {
- // End of example: non-white or '<' in first column.
- if (line[0] == '<') {
- // blank-out a '<' in the first column
- line = (char *)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 = (char *)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 = (char *)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.
- */
- char *const fname = path_tail(curbuf->b_fname);
- if (FNAMECMP(fname, "help.txt") == 0
- || (FNAMENCMP(fname, "help.", 5) == 0
- && ASCII_ISALPHA(fname[5])
- && ASCII_ISALPHA(fname[6])
- && TOLOWER_ASC(fname[7]) == 'x'
- && fname[8] == NUL)) {
- for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; lnum++) {
- line = (char *)ml_get_buf(curbuf, lnum, false);
- if (strstr(line, "*local-additions*") == NULL) {
- continue;
- }
-
- // Go through all directories in 'runtimepath', skipping
- // $VIMRUNTIME.
- char *p = (char *)p_rtp;
- while (*p != NUL) {
- copy_option_part(&p, (char *)NameBuff, MAXPATHL, ",");
- char *const rt = vim_getenv("VIMRUNTIME");
- if (rt != NULL
- && path_full_compare(rt, (char *)NameBuff, false, true) != kEqualFiles) {
- int fcount;
- char **fnames;
- char *s;
- vimconv_T vc;
- char *cp;
-
- // Find all "doc/ *.txt" files in this directory.
- if (!add_pathsep((char *)NameBuff)
- || STRLCAT(NameBuff, "doc/*.??[tx]",
- sizeof(NameBuff)) >= MAXPATHL) {
- emsg(_(e_fnametoolong));
- continue;
- }
-
- // Note: We cannot just do `&NameBuff` because it is a statically sized array
- // so `NameBuff == &NameBuff` according to C semantics.
- char *buff_list[1] = { (char *)NameBuff };
- if (gen_expand_wildcards(1, buff_list, &fcount,
- &fnames, EW_FILE|EW_SILENT) == OK
- && fcount > 0) {
- // If foo.abx is found use it instead of foo.txt in
- // the same directory.
- for (int i1 = 0; i1 < fcount; i1++) {
- for (int i2 = 0; i2 < fcount; i2++) {
- if (i1 == i2) {
- continue;
- }
- if (fnames[i1] == NULL || fnames[i2] == NULL) {
- continue;
- }
- const char *const f1 = fnames[i1];
- const char *const f2 = fnames[i2];
- const char *const t1 = path_tail(f1);
- const char *const t2 = path_tail(f2);
- const char *const e1 = (char *)STRRCHR(t1, '.');
- const char *const e2 = (char *)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.
- XFREE_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
- XFREE_CLEAR(fnames[i1]);
- }
- }
- }
- for (int fi = 0; fi < fcount; fi++) {
- if (fnames[fi] == NULL) {
- continue;
- }
-
- FILE *const fd = os_fopen(fnames[fi], "r");
- if (fd == NULL) {
- continue;
- }
- vim_fgets(IObuff, IOSIZE, fd);
- if (IObuff[0] == '*'
- && (s = vim_strchr((char *)IObuff + 1, '*'))
- != NULL) {
- TriState this_utf = kNone;
- // 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 ((char_u)(*s) >= 0x80 && this_utf != kFalse) {
- this_utf = kTrue;
- const int l = utf_ptr2len(s);
- if (l == 1) {
- this_utf = kFalse;
- }
- 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 == kTrue ? "utf-8" : "latin1"),
- p_enc);
- if (vc.vc_type == CONV_NONE) {
- // No conversion needed.
- cp = (char *)IObuff;
- } else {
- // Do the conversion. If it fails
- // use the unconverted text.
- cp = (char *)string_convert(&vc, IObuff, NULL);
- if (cp == NULL) {
- cp = (char *)IObuff;
- }
- }
- convert_setup(&vc, NULL, NULL);
-
- ml_append(lnum, cp, (colnr_T)0, false);
- if ((char_u *)cp != IObuff) {
- xfree(cp);
- }
- lnum++;
- }
- fclose(fd);
- }
- FreeWild(fcount, fnames);
- }
- }
- xfree(rt);
- }
- break;
- }
- }
-}
-
-/// ":exusage"
-void ex_exusage(exarg_T *eap)
-{
- do_cmdline_cmd("help ex-cmd-index");
-}
-
-/// ":viusage"
-void ex_viusage(exarg_T *eap)
-{
- do_cmdline_cmd("help normal-index");
-}
-
-/// Generate tags in one help directory
-///
-/// @param dir Path to the doc directory
-/// @param ext Suffix of the help files (".txt", ".itx", ".frx", etc.)
-/// @param tagname Name of the tags file ("tags" for English, "tags-fr" for
-/// French)
-/// @param add_help_tags Whether to add the "help-tags" tag
-/// @param ignore_writeerr ignore write error
-static void helptags_one(char *dir, const char *ext, const char *tagfname, bool add_help_tags,
- bool ignore_writeerr)
- FUNC_ATTR_NONNULL_ALL
-{
- garray_T ga;
- int filecount;
- char **files;
- char *p1, *p2;
- char *s;
- TriState utf8 = kNone;
- bool mix = false; // detected mixed encodings
-
- // Find all *.txt files.
- size_t dirlen = STRLCPY(NameBuff, dir, sizeof(NameBuff));
- if (dirlen >= MAXPATHL
- || STRLCAT(NameBuff, "/**/*", sizeof(NameBuff)) >= MAXPATHL // NOLINT
- || STRLCAT(NameBuff, ext, sizeof(NameBuff)) >= MAXPATHL) {
- emsg(_(e_fnametoolong));
- return;
- }
-
- // Note: We cannot just do `&NameBuff` because it is a statically sized array
- // so `NameBuff == &NameBuff` according to C semantics.
- char *buff_list[1] = { (char *)NameBuff };
- const int res = gen_expand_wildcards(1, buff_list, &filecount, &files,
- EW_FILE|EW_SILENT);
- if (res == FAIL || filecount == 0) {
- if (!got_int) {
- semsg(_("E151: No match: %s"), NameBuff);
- }
- if (res != FAIL) {
- FreeWild(filecount, files);
- }
- return;
- }
-
- //
- // Open the tags file for writing.
- // Do this before scanning through all the files.
- //
- memcpy(NameBuff, dir, dirlen + 1);
- if (!add_pathsep((char *)NameBuff)
- || STRLCAT(NameBuff, tagfname, sizeof(NameBuff)) >= MAXPATHL) {
- emsg(_(e_fnametoolong));
- return;
- }
-
- FILE *const fd_tags = os_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_init(&ga, (int)sizeof(char_u *), 100);
- if (add_help_tags
- || path_full_compare("$VIMRUNTIME/doc", dir, false, true) == kEqualFiles) {
- size_t s_len = 18 + STRLEN(tagfname);
- s = xmalloc(s_len);
- snprintf(s, s_len, "help-tags\t%s\t1\n", tagfname);
- GA_APPEND(char *, &ga, s);
- }
-
- // Go over all the files and extract the tags.
- for (int fi = 0; fi < filecount && !got_int; fi++) {
- FILE *const fd = os_fopen(files[fi], "r");
- if (fd == NULL) {
- semsg(_("E153: Unable to open %s for reading"), files[fi]);
- continue;
- }
- const char *const fname = files[fi] + dirlen + 1;
-
- bool 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.
- TriState this_utf8 = kNone;
- for (s = (char *)IObuff; *s != NUL; s++) {
- if ((char_u)(*s) >= 0x80) {
- this_utf8 = kTrue;
- const int l = utf_ptr2len(s);
- if (l == 1) {
- // Illegal UTF-8 byte sequence.
- this_utf8 = kFalse;
- break;
- }
- s += l - 1;
- }
- }
- if (this_utf8 == kNone) { // only ASCII characters found
- this_utf8 = kFalse;
- }
- if (utf8 == kNone) { // 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((char *)IObuff, '*'); // find first '*'
- while (p1 != NULL) {
- p2 = strchr((const char *)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
- && ((char_u *)p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t')
- && (vim_strchr(" \t\n\r", s[1]) != NULL
- || s[1] == '\0')) {
- *p2 = '\0';
- p1++;
- size_t s_len= (size_t)(p2 - p1) + STRLEN(fname) + 2;
- s = xmalloc(s_len);
- GA_APPEND(char *, &ga, s);
- snprintf(s, s_len, "%s\t%s", p1, fname);
-
- // find next '*'
- p2 = vim_strchr(p2 + 1, '*');
- }
- }
- p1 = p2;
- }
- line_breakcheck();
- }
-
- fclose(fd);
- }
-
- FreeWild(filecount, files);
-
- if (!got_int && ga.ga_data != NULL) {
- // Sort the tags.
- sort_strings(ga.ga_data, ga.ga_len);
-
- // Check for duplicates.
- for (int i = 1; i < ga.ga_len; i++) {
- p1 = ((char **)ga.ga_data)[i - 1];
- p2 = ((char **)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 == kTrue) {
- fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n");
- }
-
- // Write the tags into the file.
- for (int i = 0; i < ga.ga_len; i++) {
- s = ((char **)ga.ga_data)[i];
- if (STRNCMP(s, "help-tags\t", 10) == 0) {
- // help-tags entry was added in formatted form
- fputs(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
- }
-
- GA_DEEP_CLEAR_PTR(&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 *dirname, bool add_help_tags, bool ignore_writeerr)
- FUNC_ATTR_NONNULL_ALL
-{
- int len;
- garray_T ga;
- char lang[2];
- char ext[5];
- char fname[8];
- int filecount;
- char **files;
-
- // Get a list of all files in the help directory and in subdirectories.
- STRLCPY(NameBuff, dirname, sizeof(NameBuff));
- if (!add_pathsep((char *)NameBuff)
- || STRLCAT(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) {
- emsg(_(e_fnametoolong));
- return;
- }
-
- // Note: We cannot just do `&NameBuff` because it is a statically sized array
- // so `NameBuff == &NameBuff` according to C semantics.
- char *buff_list[1] = { (char *)NameBuff };
- if (gen_expand_wildcards(1, buff_list, &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.
- int j;
- ga_init(&ga, 1, 10);
- for (int i = 0; i < filecount; i++) {
- len = (int)STRLEN(files[i]);
- if (len <= 4) {
- continue;
- }
-
- 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] = (char)TOLOWER_ASC(files[i][len - 3]);
- lang[1] = (char)TOLOWER_ASC(files[i][len - 2]);
- } else {
- continue;
- }
-
- // Did we find this language already?
- for (j = 0; j < ga.ga_len; j += 2) {
- if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0) {
- break;
- }
- }
- if (j == ga.ga_len) {
- // New language, add it.
- ga_grow(&ga, 2);
- ((char *)ga.ga_data)[ga.ga_len++] = lang[0];
- ((char *)ga.ga_data)[ga.ga_len++] = lang[1];
- }
- }
-
- /*
- * Loop over the found languages to generate a tags file for each one.
- */
- for (j = 0; j < ga.ga_len; j += 2) {
- STRCPY(fname, "tags-xx");
- fname[5] = ((char *)ga.ga_data)[j];
- fname[6] = ((char *)ga.ga_data)[j + 1];
- if (fname[5] == 'e' && fname[6] == 'n') {
- // English is an exception: use ".txt" and "tags".
- fname[4] = NUL;
- STRCPY(ext, ".txt");
- } else {
- // Language "ab" uses ".abx" and "tags-ab".
- STRCPY(ext, ".xxx");
- ext[1] = fname[5];
- ext[2] = fname[6];
- }
- helptags_one(dirname, (char *)ext, (char *)fname, add_help_tags, ignore_writeerr);
- }
-
- ga_clear(&ga);
- FreeWild(filecount, files);
-}
-
-static void helptags_cb(char *fname, void *cookie)
- FUNC_ATTR_NONNULL_ALL
-{
- do_helptags(fname, *(bool *)cookie, true);
-}
-
-/// ":helptags"
-void ex_helptags(exarg_T *eap)
-{
- expand_T xpc;
- char *dirname;
- bool add_help_tags = false;
-
- // Check for ":helptags ++t {dir}".
- if (STRNCMP(eap->arg, "++t", 3) == 0 && ascii_iswhite(eap->arg[3])) {
- add_help_tags = true;
- eap->arg = skipwhite(eap->arg + 3);
- }
-
- if (STRCMP(eap->arg, "ALL") == 0) {
- do_in_path(p_rtp, "doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags);
- } else {
- ExpandInit(&xpc);
- xpc.xp_context = EXPAND_DIRECTORIES;
- dirname = (char *)ExpandOne(&xpc, (char_u *)eap->arg, NULL,
- WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE);
- if (dirname == NULL || !os_isdir((char_u *)dirname)) {
- semsg(_("E150: Not a directory: %s"), eap->arg);
- } else {
- do_helptags(dirname, add_help_tags, false);
- }
- xfree(dirname);
- }
-}
-
-/// ":helpclose": Close one help window
-void ex_helpclose(exarg_T *eap)
-{
- FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
- if (bt_help(win->w_buffer)) {
- win_close(win, false, eap->forceit);
- return;
- }
- }
-}
-
/// Shows the effects of the :substitute command being typed ('inccommand').
/// If inccommand=split, shows a preview window and later restores the layout.
///