aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/auevents.lua8
-rw-r--r--src/nvim/buffer.c1004
-rw-r--r--src/nvim/edit.c8
-rw-r--r--src/nvim/eval.c532
-rw-r--r--src/nvim/eval.h41
-rw-r--r--src/nvim/eval_defs.h2
-rw-r--r--src/nvim/ex_cmds.c1
-rw-r--r--src/nvim/ex_cmds.lua34
-rw-r--r--src/nvim/ex_cmds2.c95
-rw-r--r--src/nvim/ex_docmd.c51
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/keymap.c2
-rw-r--r--src/nvim/log.h2
-rw-r--r--src/nvim/main.c7
-rw-r--r--src/nvim/memory.c2
-rw-r--r--src/nvim/misc1.c1
-rw-r--r--src/nvim/normal.c42
-rw-r--r--src/nvim/po/CMakeLists.txt2
-rw-r--r--src/nvim/regexp_nfa.c2
-rw-r--r--src/nvim/terminal.h9
-rw-r--r--src/nvim/testdir/test49.vim2
-rw-r--r--src/nvim/tui/input.c32
-rw-r--r--src/nvim/tui/tui.c12
-rw-r--r--src/nvim/undo.c2
-rw-r--r--src/nvim/version.c139
-rw-r--r--src/nvim/version.h1
26 files changed, 1426 insertions, 609 deletions
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 3d8a75febd..7624dd2303 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -77,8 +77,9 @@ return {
'TabNew', -- when creating a new tab
'TabNewEntered', -- after entering a new tab
'TermChanged', -- after changing 'term'
- 'TermResponse', -- after setting "v:termresponse"
+ 'TermClose', -- after the processs exits
'TermOpen', -- after opening a terminal buffer
+ 'TermResponse', -- after setting "v:termresponse"
'TextChanged', -- text was modified
'TextChangedI', -- text was modified in Insert mode
'User', -- user defined autocommand
@@ -98,9 +99,10 @@ return {
-- List of neovim-specific events or aliases for the purpose of generating
-- syntax file
neovim_specific = {
+ TabClosed=true,
TabNew=true,
TabNewEntered=true,
- TabClosed=true,
- TermEnter=true,
+ TermClose=true,
+ TermOpen=true,
},
}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index b3eba4f5f6..6fc08643af 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -2159,9 +2159,23 @@ void buflist_list(exarg_T *eap)
int i;
for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next) {
- /* skip unlisted buffers, unless ! was used */
- if (!buf->b_p_bl && !eap->forceit)
+ // skip unspecified buffers
+ if ((!buf->b_p_bl && !eap->forceit && !strchr((char *)eap->arg, 'u'))
+ || (strchr((char *)eap->arg, 'u') && buf->b_p_bl)
+ || (strchr((char *)eap->arg, '+')
+ && ((buf->b_flags & BF_READERR) || !bufIsChanged(buf)))
+ || (strchr((char *)eap->arg, 'a')
+ && (buf->b_ml.ml_mfp == NULL || buf->b_nwindows == 0))
+ || (strchr((char *)eap->arg, 'h')
+ && (buf->b_ml.ml_mfp == NULL || buf->b_nwindows != 0))
+ || (strchr((char *)eap->arg, '-') && buf->b_p_ma)
+ || (strchr((char *)eap->arg, '=') && !buf->b_p_ro)
+ || (strchr((char *)eap->arg, 'x') && !(buf->b_flags & BF_READERR))
+ || (strchr((char *)eap->arg, '%') && buf != curbuf)
+ || (strchr((char *)eap->arg, '#')
+ && (buf == curbuf || curwin->w_alt_fnum != buf->b_fnum))) {
continue;
+ }
msg_putchar('\n');
if (buf_spname(buf) != NULL)
STRLCPY(NameBuff, buf_spname(buf), MAXPATHL);
@@ -2789,58 +2803,59 @@ void free_titles(void)
# endif
+/// Enumeration specifying the valid numeric bases that can
+/// be used when printing numbers in the status line.
+typedef enum {
+ kNumBaseDecimal = 10,
+ kNumBaseOctal = 8,
+ kNumBaseHexadecimal = 16
+} NumberBase;
-/*
- * Build a string from the status line items in "fmt".
- * Return length of string in screen cells.
- *
- * Normally works for window "wp", except when working for 'tabline' then it
- * is "curwin".
- *
- * Items are drawn interspersed with the text that surrounds it
- * Specials: %-<wid>(xxx%) => group, %= => middle marker, %< => truncation
- * Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional
- *
- * If maxwidth is not zero, the string will be filled at any middle marker
- * or truncated if too long, fillchar is used for all whitespace.
- */
-int
-build_stl_str_hl (
+
+/// Build a string from the status line items in "fmt".
+/// Return length of string in screen cells.
+///
+/// Normally works for window "wp", except when working for 'tabline' then it
+/// is "curwin".
+///
+/// Items are drawn interspersed with the text that surrounds it
+/// Specials: %-<wid>(xxx%) => group, %= => middle marker, %< => truncation
+/// Item: %-<minwid>.<maxwid><itemch> All but <itemch> are optional
+///
+/// If maxwidth is not zero, the string will be filled at any middle marker
+/// or truncated if too long, fillchar is used for all whitespace.
+///
+/// @param wp The window to build a statusline for
+/// @param out The output buffer to write the statusline to
+/// Note: This should not be NameBuff
+/// @param outlen The length of the output buffer
+/// @param fmt The statusline format string
+/// @param use_sandbox Use a sandboxed environment when evaluating fmt
+/// @param fillchar Character to use when filling empty space in the statusline
+/// @param maxwidth The maximum width to make the statusline
+/// @param hltab HL attributes (can be NULL)
+/// @param tabtab tab page nrs (can be NULL)
+///
+/// @return The final width of the statusline
+int build_stl_str_hl(
win_T *wp,
- char_u *out, /* buffer to write into != NameBuff */
- size_t outlen, /* length of out[] */
+ char_u *out,
+ size_t outlen,
char_u *fmt,
- int use_sandbox, /* "fmt" was set insecurely, use sandbox */
+ int use_sandbox,
int fillchar,
int maxwidth,
- struct stl_hlrec *hltab, /* return: HL attributes (can be NULL) */
- struct stl_hlrec *tabtab /* return: tab page nrs (can be NULL) */
+ struct stl_hlrec *hltab,
+ struct stl_hlrec *tabtab
)
{
- char_u *p;
- char_u *s;
- char_u *t;
- int byteval;
- win_T *o_curwin;
- buf_T *o_curbuf;
- int empty_line;
- colnr_T virtcol;
- long l;
- long n;
- int prevchar_isflag;
- int prevchar_isitem;
- int itemisflag;
- int fillable;
- char_u *str;
- long num;
- int width;
- int itemcnt;
- int curitem;
int groupitem[STL_MAX_ITEM];
- int groupdepth;
struct stl_item {
+ // Where the item starts in the status line output buffer
char_u *start;
+ // The minimum width of the item
int minwid;
+ // The maximum width of the item
int maxwid;
enum {
Normal,
@@ -2852,20 +2867,13 @@ build_stl_str_hl (
Trunc
} type;
} item[STL_MAX_ITEM];
- int minwid;
- int maxwid;
- int zeropad;
- char_u base;
- char_u opt;
+
#define TMPLEN 70
char_u tmp[TMPLEN];
char_u *usefmt = fmt;
- struct stl_hlrec *sp;
- /*
- * When the format starts with "%!" then evaluate it as an expression and
- * use the result as the actual format string.
- */
+ // When the format starts with "%!" then evaluate it as an expression and
+ // use the result as the actual format string.
if (fmt[0] == '%' && fmt[1] == '!') {
usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox);
if (usefmt == NULL)
@@ -2874,175 +2882,280 @@ build_stl_str_hl (
if (fillchar == 0)
fillchar = ' ';
- /* Can't handle a multi-byte fill character yet. */
+ // Can't handle a multi-byte fill character yet.
else if (mb_char2len(fillchar) > 1)
fillchar = '-';
- /* Get line & check if empty (cursorpos will show "0-1"). Note that
- * p will become invalid when getting another buffer line. */
- p = ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, FALSE);
- empty_line = (*p == NUL);
+ // Get line & check if empty (cursorpos will show "0-1").
+ char_u *line_ptr = ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false);
+ bool empty_line = (*line_ptr == NUL);
- /* Get the byte value now, in case we need it below. This is more
- * efficient than making a copy of the line. */
- if (wp->w_cursor.col > (colnr_T)STRLEN(p))
+ // Get the byte value now, in case we need it below. This is more
+ // efficient than making a copy of the line.
+ int byteval;
+ if (wp->w_cursor.col > (colnr_T)STRLEN(line_ptr))
byteval = 0;
else
- byteval = (*mb_ptr2char)(p + wp->w_cursor.col);
-
- groupdepth = 0;
- p = out;
- curitem = 0;
- prevchar_isflag = TRUE;
- prevchar_isitem = FALSE;
- for (s = usefmt; *s; ) {
+ byteval = (*mb_ptr2char)(line_ptr + wp->w_cursor.col);
+
+ int groupdepth = 0;
+
+ int curitem = 0;
+ bool prevchar_isflag = true;
+ bool prevchar_isitem = false;
+
+ // out_p is the current position in the output buffer
+ char_u *out_p = out;
+
+ // out_end_p is the last valid character in the output buffer
+ // Note: The null termination character must occur here or earlier,
+ // so any user-visible characters must occur before here.
+ char_u *out_end_p = (out + outlen) - 1;
+
+
+ // Proceed character by character through the statusline format string
+ // fmt_p is the current positon in the input buffer
+ for (char_u *fmt_p = usefmt; *fmt_p; ) {
if (curitem == STL_MAX_ITEM) {
- /* There are too many items. Add the error code to the statusline
- * to give the user a hint about what went wrong. */
- if (p + 6 < out + outlen) {
- memmove(p, " E541", (size_t)5);
- p += 5;
+ // There are too many items. Add the error code to the statusline
+ // to give the user a hint about what went wrong.
+ if (out_p + 5 < out_end_p) {
+ memmove(out_p, " E541", (size_t)5);
+ out_p += 5;
}
break;
}
- if (*s != NUL && *s != '%')
- prevchar_isflag = prevchar_isitem = FALSE;
+ if (*fmt_p != NUL && *fmt_p != '%') {
+ prevchar_isflag = prevchar_isitem = false;
+ }
- /*
- * Handle up to the next '%' or the end.
- */
- while (*s != NUL && *s != '%' && p + 1 < out + outlen)
- *p++ = *s++;
- if (*s == NUL || p + 1 >= out + outlen)
+ // Copy the formatting verbatim until we reach the end of the string
+ // or find a formatting item (denoted by `%`)
+ // or run out of room in our output buffer.
+ while (*fmt_p != NUL && *fmt_p != '%' && out_p < out_end_p)
+ *out_p++ = *fmt_p++;
+
+ // If we have processed the entire format string or run out of
+ // room in our output buffer, exit the loop.
+ if (*fmt_p == NUL || out_p >= out_end_p)
break;
- /*
- * Handle one '%' item.
- */
- s++;
- if (*s == NUL) /* ignore trailing % */
+ // The rest of this loop will handle a single `%` item.
+ // Note: We increment here to skip over the `%` character we are currently
+ // on so we can process the item's contents.
+ fmt_p++;
+
+ // Ignore `%` at the end of the format string
+ if (*fmt_p == NUL) {
break;
- if (*s == '%') {
- if (p + 1 >= out + outlen)
+ }
+
+ // Two `%` in a row is the escape sequence to print a
+ // single `%` in the output buffer.
+ if (*fmt_p == '%') {
+ // Ignore the character if we're out of room in the output buffer.
+ if (out_p >= out_end_p)
break;
- *p++ = *s++;
- prevchar_isflag = prevchar_isitem = FALSE;
+ *out_p++ = *fmt_p++;
+ prevchar_isflag = prevchar_isitem = false;
continue;
}
- if (*s == STL_MIDDLEMARK) {
- s++;
- if (groupdepth > 0)
+
+ // STL_MIDDLEMARK: Separation place between left and right aligned items.
+ if (*fmt_p == STL_MIDDLEMARK) {
+ fmt_p++;
+ // Ignored when we are inside of a grouping
+ if (groupdepth > 0) {
continue;
+ }
item[curitem].type = Middle;
- item[curitem++].start = p;
+ item[curitem++].start = out_p;
continue;
}
- if (*s == STL_TRUNCMARK) {
- s++;
+
+ // STL_TRUNCMARK: Where to begin truncating if the statusline is too long.
+ if (*fmt_p == STL_TRUNCMARK) {
+ fmt_p++;
item[curitem].type = Trunc;
- item[curitem++].start = p;
+ item[curitem++].start = out_p;
continue;
}
- if (*s == ')') {
- s++;
- if (groupdepth < 1)
+
+ // The end of a grouping
+ if (*fmt_p == ')') {
+ fmt_p++;
+ // Ignore if we are not actually inside a group currently
+ if (groupdepth < 1) {
continue;
+ }
groupdepth--;
- t = item[groupitem[groupdepth]].start;
- *p = NUL;
- l = vim_strsize(t);
+ // Determine how long the group is.
+ // Note: We set the current output position to null
+ // so `vim_strsize` will work.
+ char_u *t = item[groupitem[groupdepth]].start;
+ *out_p = NUL;
+ long group_len = vim_strsize(t);
+
+ // If the group contained internal items
+ // and the group did not have a minimum width,
+ // and if there were no normal items in the group,
+ // move the output pointer back to where the group started.
+ // Note: This erases any non-item characters that were in the group.
+ // Otherwise there would be no reason to do this step.
if (curitem > groupitem[groupdepth] + 1
&& item[groupitem[groupdepth]].minwid == 0) {
- /* remove group if all items are empty */
- for (n = groupitem[groupdepth] + 1; n < curitem; n++)
- if (item[n].type == Normal)
+ bool has_normal_items = false;
+ for (long n = groupitem[groupdepth] + 1; n < curitem; n++) {
+ if (item[n].type == Normal) {
+ has_normal_items = true;
break;
- if (n == curitem) {
- p = t;
- l = 0;
+ }
+ }
+
+ if (!has_normal_items) {
+ out_p = t;
+ group_len = 0;
}
}
- if (l > item[groupitem[groupdepth]].maxwid) {
- /* truncate, remove n bytes of text at the start */
+
+ // If the group is longer than it is allowed to be
+ // truncate by removing bytes from the start of the group text.
+ if (group_len > item[groupitem[groupdepth]].maxwid) {
+ // { Determine the number of bytes to remove
+ long n;
if (has_mbyte) {
/* Find the first character that should be included. */
n = 0;
- while (l >= item[groupitem[groupdepth]].maxwid) {
- l -= ptr2cells(t + n);
+ while (group_len >= item[groupitem[groupdepth]].maxwid) {
+ group_len -= ptr2cells(t + n);
n += (*mb_ptr2len)(t + n);
}
- } else
- n = (long)(p - t) - item[groupitem[groupdepth]].maxwid + 1;
+ } else {
+ n = (long)(out_p - t) - item[groupitem[groupdepth]].maxwid + 1;
+ }
+ // }
+ // Prepend the `<` to indicate that the output was truncated.
*t = '<';
- memmove(t + 1, t + n, (size_t)(p - (t + n)));
- p = p - n + 1;
+
+ // { Move the truncated output
+ memmove(t + 1, t + n, (size_t)(out_p - (t + n)));
+ out_p = out_p - n + 1;
/* Fill up space left over by half a double-wide char. */
- while (++l < item[groupitem[groupdepth]].minwid)
- *p++ = fillchar;
+ while (++group_len < item[groupitem[groupdepth]].minwid)
+ *out_p++ = fillchar;
+ // }
/* correct the start of the items for the truncation */
- for (l = groupitem[groupdepth] + 1; l < curitem; l++) {
- item[l].start -= n;
- if (item[l].start < t)
- item[l].start = t;
+ for (int idx = groupitem[groupdepth] + 1; idx < curitem; idx++) {
+ // Shift everything back by the number of removed bytes
+ item[idx].start -= n;
+
+ // If the item was partially or completely truncated, set its
+ // start to the start of the group
+ if (item[idx].start < t) {
+ item[idx].start = t;
+ }
}
- } else if (abs(item[groupitem[groupdepth]].minwid) > l) {
- /* fill */
- n = item[groupitem[groupdepth]].minwid;
- if (n < 0) {
- /* fill by appending characters */
- n = 0 - n;
- while (l++ < n && p + 1 < out + outlen)
- *p++ = fillchar;
+ // If the group is shorter than the minimum width, add padding characters.
+ } else if (abs(item[groupitem[groupdepth]].minwid) > group_len) {
+ long min_group_width = item[groupitem[groupdepth]].minwid;
+ // If the group is left-aligned, add characters to the right.
+ if (min_group_width < 0) {
+ min_group_width = 0 - min_group_width;
+ while (group_len++ < min_group_width && out_p < out_end_p)
+ *out_p++ = fillchar;
+ // If the group is right-aligned, shift everything to the right and
+ // prepend with filler characters.
} else {
- /* fill by inserting characters */
- memmove(t + n - l, t, (size_t)(p - t));
- l = n - l;
- if (p + l >= out + outlen)
- l = (long)((out + outlen) - p - 1);
- p += l;
- for (n = groupitem[groupdepth] + 1; n < curitem; n++)
- item[n].start += l;
- for (; l > 0; l--)
+ // { Move the group to the right
+ memmove(t + min_group_width - group_len, t, (size_t)(out_p - t));
+ group_len = min_group_width - group_len;
+ if (out_p + group_len >= (out_end_p + 1)) {
+ group_len = (long)(out_end_p - out_p);
+ }
+ out_p += group_len;
+ // }
+
+ // Adjust item start positions
+ for (int n = groupitem[groupdepth] + 1; n < curitem; n++) {
+ item[n].start += group_len;
+ }
+
+ // Prepend the fill characters
+ for (; group_len > 0; group_len--) {
*t++ = fillchar;
+ }
}
}
continue;
}
- minwid = 0;
- maxwid = 9999;
- zeropad = FALSE;
- l = 1;
- if (*s == '0') {
- s++;
- zeropad = TRUE;
+ int minwid = 0;
+ int maxwid = 9999;
+ bool left_align = false;
+
+ // Denotes that numbers should be left-padded with zeros
+ bool zeropad = (*fmt_p == '0');
+ if (zeropad) {
+ fmt_p++;
}
- if (*s == '-') {
- s++;
- l = -1;
+
+ // Denotes that the item should be left-aligned.
+ // This is tracked by using a negative length.
+ if (*fmt_p == '-') {
+ fmt_p++;
+ left_align = true;
}
- if (ascii_isdigit(*s)) {
- minwid = getdigits_int(&s);
+
+ // The first digit group is the item's min width
+ if (ascii_isdigit(*fmt_p)) {
+ minwid = getdigits_int(&fmt_p);
if (minwid < 0) /* overflow */
minwid = 0;
}
- if (*s == STL_USER_HL) {
+
+ // User highlight groups override the min width field
+ // to denote the styling to use.
+ if (*fmt_p == STL_USER_HL) {
item[curitem].type = Highlight;
- item[curitem].start = p;
+ item[curitem].start = out_p;
item[curitem].minwid = minwid > 9 ? 1 : minwid;
- s++;
+ fmt_p++;
curitem++;
continue;
}
- if (*s == STL_TABPAGENR || *s == STL_TABCLOSENR) {
- if (*s == STL_TABCLOSENR) {
+
+ // TABPAGE pairs are used to denote a region that when clicked will
+ // either switch to or close a tab.
+ //
+ // Ex: tabline=%0Ttab\ zero%X
+ // This tabline has a TABPAGENR item with minwid `0`,
+ // which is then closed with a TABCLOSENR item.
+ // Clicking on this region with mouse enabled will switch to tab 0.
+ // Setting the minwid to a different value will switch
+ // to that tab, if it exists
+ //
+ // Ex: tabline=%1Xtab\ one%X
+ // This tabline has a TABCLOSENR item with minwid `1`,
+ // which is then closed with a TABCLOSENR item.
+ // Clicking on this region with mouse enabled will close tab 0.
+ // This is determined by the following formula:
+ // tab to close = (1 - minwid)
+ // This is because for TABPAGENR we use `minwid` = `tab number`.
+ // For TABCLOSENR we store the tab number as a negative value.
+ // Because 0 is a valid TABPAGENR value, we have to
+ // start our numbering at `-1`.
+ // So, `-1` corresponds to us wanting to close tab `0`
+ //
+ // Note: These options are only valid when creating a tabline.
+ if (*fmt_p == STL_TABPAGENR || *fmt_p == STL_TABCLOSENR) {
+ if (*fmt_p == STL_TABCLOSENR) {
if (minwid == 0) {
/* %X ends the close label, go back to the previously
* define tab label nr. */
- for (n = curitem - 1; n >= 0; --n)
+ for (long n = curitem - 1; n >= 0; --n)
if (item[n].type == TabPage && item[n].minwid >= 0) {
minwid = item[n].minwid;
break;
@@ -3052,54 +3165,70 @@ build_stl_str_hl (
minwid = -minwid;
}
item[curitem].type = TabPage;
- item[curitem].start = p;
+ item[curitem].start = out_p;
item[curitem].minwid = minwid;
- s++;
+ fmt_p++;
curitem++;
continue;
}
- if (*s == '.') {
- s++;
- if (ascii_isdigit(*s)) {
- maxwid = getdigits_int(&s);
+
+ // Denotes the end of the minwid
+ // the maxwid may follow immediately after
+ if (*fmt_p == '.') {
+ fmt_p++;
+ if (ascii_isdigit(*fmt_p)) {
+ maxwid = getdigits_int(&fmt_p);
if (maxwid <= 0) /* overflow */
maxwid = 50;
}
}
- minwid = (minwid > 50 ? 50 : minwid) * l;
- if (*s == '(') {
+
+ // Bound the minimum width at 50.
+ // Make the number negative to denote left alignment of the item
+ minwid = (minwid > 50 ? 50 : minwid) * (left_align ? -1 : 1);
+
+ // Denotes the start of a new group
+ if (*fmt_p == '(') {
groupitem[groupdepth++] = curitem;
item[curitem].type = Group;
- item[curitem].start = p;
+ item[curitem].start = out_p;
item[curitem].minwid = minwid;
item[curitem].maxwid = maxwid;
- s++;
+ fmt_p++;
curitem++;
continue;
}
- if (vim_strchr(STL_ALL, *s) == NULL) {
- s++;
+
+ // An invalid item was specified.
+ // Continue processing on the next character of the format string.
+ if (vim_strchr(STL_ALL, *fmt_p) == NULL) {
+ fmt_p++;
continue;
}
- opt = *s++;
-
- /* OK - now for the real work */
- base = 'D';
- itemisflag = FALSE;
- fillable = TRUE;
- num = -1;
- str = NULL;
+
+ // The status line item type
+ char_u opt = *fmt_p++;
+
+ // OK - now for the real work
+ NumberBase base = kNumBaseDecimal;
+ bool itemisflag = false;
+ bool fillable = true;
+ long num = -1;
+ char_u *str = NULL;
switch (opt) {
case STL_FILEPATH:
case STL_FULLPATH:
case STL_FILENAME:
- fillable = FALSE; /* don't change ' ' to fillchar */
- if (buf_spname(wp->w_buffer) != NULL)
+ {
+ // Set fillable to false to that ' ' in the filename will not
+ // get replaced with the fillchar
+ fillable = false;
+ if (buf_spname(wp->w_buffer) != NULL) {
STRLCPY(NameBuff, buf_spname(wp->w_buffer), MAXPATHL);
- else {
- t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname
- : wp->w_buffer->b_fname;
- home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, TRUE);
+ } else {
+ char_u *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname
+ : wp->w_buffer->b_fname;
+ home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, true);
}
trans_characters(NameBuff, MAXPATHL);
if (opt != STL_FILENAME)
@@ -3107,42 +3236,59 @@ build_stl_str_hl (
else
str = path_tail(NameBuff);
break;
-
+ }
case STL_VIM_EXPR: /* '{' */
- itemisflag = TRUE;
- t = p;
- while (*s != '}' && *s != NUL && p + 1 < out + outlen)
- *p++ = *s++;
- if (*s != '}') /* missing '}' or out of space */
+ {
+ itemisflag = true;
+
+ // Attempt to copy the expression to evaluate into
+ // the output buffer as a null-terminated string.
+ char_u *t = out_p;
+ while (*fmt_p != '}' && *fmt_p != NUL && out_p < out_end_p)
+ *out_p++ = *fmt_p++;
+ if (*fmt_p != '}') /* missing '}' or out of space */
break;
- s++;
- *p = 0;
- p = t;
+ fmt_p++;
+ *out_p = 0;
+
+ // Move our position in the output buffer
+ // to the beginning of the expression
+ out_p = t;
+
+ // { Evaluate the expression
+ // Store the current buffer number as a string variable
vim_snprintf((char *)tmp, sizeof(tmp), "%d", curbuf->b_fnum);
set_internal_string_var((char_u *)"actual_curbuf", tmp);
- o_curbuf = curbuf;
- o_curwin = curwin;
+ buf_T *o_curbuf = curbuf;
+ win_T *o_curwin = curwin;
curwin = wp;
curbuf = wp->w_buffer;
- str = eval_to_string_safe(p, &t, use_sandbox);
+ // Note: The result stored in `t` is unused.
+ str = eval_to_string_safe(out_p, &t, use_sandbox);
curwin = o_curwin;
curbuf = o_curbuf;
- do_unlet((char_u *)"g:actual_curbuf", TRUE);
+ // Remove the variable we just stored
+ do_unlet((char_u *)"g:actual_curbuf", true);
+
+ // }
+
+ // Check if the evaluated result is a number.
+ // If so, convert the number to an int and free the string.
if (str != NULL && *str != 0) {
if (*skipdigits(str) == NUL) {
num = atoi((char *)str);
xfree(str);
str = NULL;
- itemisflag = FALSE;
+ itemisflag = false;
}
}
break;
-
+ }
case STL_LINE:
num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY)
? 0L : (long)(wp->w_cursor.lnum);
@@ -3159,21 +3305,23 @@ build_stl_str_hl (
case STL_VIRTCOL:
case STL_VIRTCOL_ALT:
- /* In list mode virtcol needs to be recomputed */
- virtcol = wp->w_virtcol;
+ {
+ // In list mode virtcol needs to be recomputed
+ colnr_T virtcol = wp->w_virtcol;
if (wp->w_p_list && lcs_tab1 == NUL) {
wp->w_p_list = FALSE;
getvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL);
wp->w_p_list = TRUE;
}
++virtcol;
- /* Don't display %V if it's the same as %c. */
+ // Don't display %V if it's the same as %c.
if (opt == STL_VIRTCOL_ALT
&& (virtcol == (colnr_T)(!(State & INSERT) && empty_line
? 0 : (int)wp->w_cursor.col + 1)))
break;
num = (long)virtcol;
break;
+ }
case STL_PERCENTAGE:
num = (int)(((long)wp->w_cursor.lnum * 100L) /
@@ -3181,19 +3329,31 @@ build_stl_str_hl (
break;
case STL_ALTPERCENT:
+ // Store the position percentage in our temporary buffer.
+ // Note: We cannot store the value in `num` because
+ // `get_rel_pos` can return a named position. Ex: "Top"
+ get_rel_pos(wp, tmp, TMPLEN);
str = tmp;
- get_rel_pos(wp, str, TMPLEN);
break;
case STL_ARGLISTSTAT:
- fillable = FALSE;
+ fillable = false;
+
+ // Note: This is important because `append_arg_number` starts appending
+ // at the end of the null-terminated string.
+ // Setting the first byte to null means it will place the argument
+ // number string at the beginning of the buffer.
tmp[0] = 0;
- if (append_arg_number(wp, tmp, (int)sizeof(tmp), FALSE))
+
+ // Note: The call will only return true if it actually
+ // appended data to the `tmp` buffer.
+ if (append_arg_number(wp, tmp, (int)sizeof(tmp), false)) {
str = tmp;
+ }
break;
case STL_KEYMAP:
- fillable = FALSE;
+ fillable = false;
if (get_keymap_str(wp, tmp, TMPLEN))
str = tmp;
break;
@@ -3206,16 +3366,17 @@ build_stl_str_hl (
break;
case STL_OFFSET_X:
- base = 'X';
+ base = kNumBaseHexadecimal;
case STL_OFFSET:
- l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL);
+ {
+ long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL);
num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ?
0L : l + 1 + (!(State & INSERT) && empty_line ?
0 : (int)wp->w_cursor.col);
break;
-
+ }
case STL_BYTEVAL_X:
- base = 'X';
+ base = kNumBaseHexadecimal;
case STL_BYTEVAL:
num = byteval;
if (num == NL)
@@ -3226,20 +3387,23 @@ build_stl_str_hl (
case STL_ROFLAG:
case STL_ROFLAG_ALT:
- itemisflag = TRUE;
+ itemisflag = true;
if (wp->w_buffer->b_p_ro)
str = (char_u *)((opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"));
break;
case STL_HELPFLAG:
case STL_HELPFLAG_ALT:
- itemisflag = TRUE;
+ itemisflag = true;
if (wp->w_buffer->b_help)
str = (char_u *)((opt == STL_HELPFLAG_ALT) ? ",HLP"
: _("[Help]"));
break;
case STL_FILETYPE:
+ // Copy the filetype if it is not null and the formatted string will fit
+ // in the temporary buffer
+ // (including the brackets and null terminating character)
if (*wp->w_buffer->b_p_ft != NUL
&& STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) {
vim_snprintf((char *)tmp, sizeof(tmp), "[%s]",
@@ -3249,20 +3413,26 @@ build_stl_str_hl (
break;
case STL_FILETYPE_ALT:
- itemisflag = TRUE;
+ {
+ itemisflag = true;
+ // Copy the filetype if it is not null and the formatted string will fit
+ // in the temporary buffer
+ // (including the comma and null terminating character)
if (*wp->w_buffer->b_p_ft != NUL
&& STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) {
vim_snprintf((char *)tmp, sizeof(tmp), ",%s",
wp->w_buffer->b_p_ft);
- for (t = tmp; *t != 0; t++)
+ // Uppercase the file extension
+ for (char_u *t = tmp; *t != 0; t++) {
*t = TOUPPER_LOC(*t);
+ }
str = tmp;
}
break;
-
+ }
case STL_PREVIEWFLAG:
case STL_PREVIEWFLAG_ALT:
- itemisflag = TRUE;
+ itemisflag = true;
if (wp->w_p_pvw)
str = (char_u *)((opt == STL_PREVIEWFLAG_ALT) ? ",PRV"
: _("[Preview]"));
@@ -3277,7 +3447,7 @@ build_stl_str_hl (
case STL_MODIFIED:
case STL_MODIFIED_ALT:
- itemisflag = TRUE;
+ itemisflag = true;
switch ((opt == STL_MODIFIED_ALT)
+ bufIsChanged(wp->w_buffer) * 2
+ (!MODIFIABLE(wp->w_buffer)) * 4) {
@@ -3291,212 +3461,360 @@ build_stl_str_hl (
break;
case STL_HIGHLIGHT:
- t = s;
- while (*s != '#' && *s != NUL)
- ++s;
- if (*s == '#') {
+ {
+ // { The name of the highlight is surrounded by `#`
+ char_u *t = fmt_p;
+ while (*fmt_p != '#' && *fmt_p != NUL) {
+ ++fmt_p;
+ }
+ // }
+
+ // Create a highlight item based on the name
+ if (*fmt_p == '#') {
item[curitem].type = Highlight;
- item[curitem].start = p;
- item[curitem].minwid = -syn_namen2id(t, (int)(s - t));
+ item[curitem].start = out_p;
+ item[curitem].minwid = -syn_namen2id(t, (int)(fmt_p - t));
curitem++;
+ fmt_p++;
}
- if (*s != NUL)
- ++s;
continue;
}
+ }
- item[curitem].start = p;
+ // If we made it this far, the item is normal and starts at
+ // our current position in the output buffer.
+ // Non-normal items would have `continued`.
+ item[curitem].start = out_p;
item[curitem].type = Normal;
+
+ // Copy the item string into the output buffer
if (str != NULL && *str) {
- t = str;
+ // { Skip the leading `,` or ` ` if the item is a flag
+ // and the proper conditions are met
+ char_u *t = str;
if (itemisflag) {
if ((t[0] && t[1])
&& ((!prevchar_isitem && *t == ',')
|| (prevchar_isflag && *t == ' ')))
t++;
- prevchar_isflag = TRUE;
+ prevchar_isflag = true;
+ }
+ // }
+
+ long l = vim_strsize(t);
+
+ // If this item is non-empty, record that the last thing
+ // we put in the output buffer was an item
+ if (l > 0) {
+ prevchar_isitem = true;
}
- l = vim_strsize(t);
- if (l > 0)
- prevchar_isitem = TRUE;
+
+ // If the item is too wide, truncate it from the beginning
if (l > maxwid) {
while (l >= maxwid)
if (has_mbyte) {
l -= ptr2cells(t);
t += (*mb_ptr2len)(t);
- } else
+ } else {
l -= byte2cells(*t++);
- if (p + 1 >= out + outlen)
+ }
+
+ // Early out if there isn't enough room for the truncation marker
+ if (out_p >= out_end_p)
break;
- *p++ = '<';
+
+ // Add the truncation marker
+ *out_p++ = '<';
}
+
+ // If the item is right aligned and not wide enough,
+ // pad with fill characters.
if (minwid > 0) {
- for (; l < minwid && p + 1 < out + outlen; l++) {
- /* Don't put a "-" in front of a digit. */
+ for (; l < minwid && out_p < out_end_p; l++) {
+ // Don't put a "-" in front of a digit.
if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t))
- *p++ = ' ';
+ *out_p++ = ' ';
else
- *p++ = fillchar;
+ *out_p++ = fillchar;
}
minwid = 0;
- } else
+ } else {
+ // Note: The negative value denotes a left aligned item.
+ // Here we switch the minimum width back to a positive value.
minwid *= -1;
- while (*t && p + 1 < out + outlen) {
- *p++ = *t++;
- /* Change a space by fillchar, unless fillchar is '-' and a
- * digit follows. */
- if (fillable && p[-1] == ' '
+ }
+
+ // { Copy the string text into the output buffer
+ while (*t && out_p < out_end_p) {
+ *out_p++ = *t++;
+ // Change a space by fillchar, unless fillchar is '-' and a
+ // digit follows.
+ if (fillable && out_p[-1] == ' '
&& (!ascii_isdigit(*t) || fillchar != '-'))
- p[-1] = fillchar;
+ out_p[-1] = fillchar;
+ }
+ // }
+
+ // For left-aligned items, fill any remaining space with the fillchar
+ for (; l < minwid && out_p < out_end_p; l++) {
+ *out_p++ = fillchar;
}
- for (; l < minwid && p + 1 < out + outlen; l++)
- *p++ = fillchar;
- } else if (num >= 0) {
- int nbase = (base == 'D' ? 10 : (base == 'O' ? 8 : 16));
- char_u nstr[20];
- if (p + 20 >= out + outlen)
+ // Otherwise if the item is a number, copy that to the output buffer.
+ } else if (num >= 0) {
+ if (out_p + 20 > out_end_p)
break; /* not sufficient space */
- prevchar_isitem = TRUE;
- t = nstr;
+ prevchar_isitem = true;
+
+ // { Build the formatting string
+ char_u nstr[20];
+ char_u *t = nstr;
if (opt == STL_VIRTCOL_ALT) {
*t++ = '-';
minwid--;
}
*t++ = '%';
- if (zeropad)
+ if (zeropad) {
*t++ = '0';
+ }
+
+ // Note: The `*` means we take the width as one of the arguments
*t++ = '*';
- *t++ = nbase == 16 ? base : (char_u)(nbase == 8 ? 'o' : 'd');
+ *t++ = (char_u) (base == kNumBaseHexadecimal ? 'X'
+ : (base == kNumBaseOctal ? 'o'
+ : 'd'));
*t = 0;
+ // }
+
+ // { Determine how many characters the number will take up when printed
+ // Note: We have to cast the base because the compiler uses
+ // unsigned ints for the enum values.
+ long num_chars = 1;
+ for (long n = num; n >= (int) base; n /= (int) base) {
+ num_chars++;
+ }
- for (n = num, l = 1; n >= nbase; n /= nbase)
- l++;
- if (opt == STL_VIRTCOL_ALT)
- l++;
- if (l > maxwid) {
- l += 2;
- n = l - maxwid;
- while (l-- > maxwid)
- num /= nbase;
+ // VIRTCOL_ALT takes up an extra character because
+ // of the `-` we added above.
+ if (opt == STL_VIRTCOL_ALT) {
+ num_chars++;
+ }
+ // }
+
+ size_t remaining_buf_len = (out_end_p - out_p) + 1;
+
+ // If the number is going to take up too much room
+ // Figure out the approximate number in "scientific" type notation.
+ // Ex: 14532 with maxwid of 4 -> '14>3'
+ if (num_chars > maxwid) {
+ // Add two to the width because the power piece will take
+ // two extra characters
+ num_chars += 2;
+
+ // How many extra characters there are
+ long n = num_chars - maxwid;
+
+ // { Reduce the number by base^n
+ while (num_chars-- > maxwid) {
+ num /= base;
+ }
+ // }
+
+ // { Add the format string for the exponent bit
*t++ = '>';
*t++ = '%';
+ // Use the same base as the first number
*t = t[-3];
*++t = 0;
- vim_snprintf((char *)p, outlen - (p - out), (char *)nstr,
+ // }
+
+ vim_snprintf((char *)out_p, remaining_buf_len, (char *)nstr,
0, num, n);
- } else
- vim_snprintf((char *)p, outlen - (p - out), (char *)nstr,
+ } else {
+ vim_snprintf((char *)out_p, remaining_buf_len, (char *)nstr,
minwid, num);
- p += STRLEN(p);
- } else
+ }
+
+ // Advance the output buffer position to the end of the
+ // number we just printed
+ out_p += STRLEN(out_p);
+
+ // Otherwise, there was nothing to print so mark the item as empty
+ } else {
item[curitem].type = Empty;
+ }
- if (opt == STL_VIM_EXPR)
+ // Only free the string buffer if we allocated it.
+ // Note: This is not needed if `str` is pointing at `tmp`
+ if (opt == STL_VIM_EXPR) {
xfree(str);
+ }
if (num >= 0 || (!itemisflag && str && *str))
- prevchar_isflag = FALSE; /* Item not NULL, but not a flag */
+ prevchar_isflag = false; /* Item not NULL, but not a flag */
+
+ // Item processed, move to the next
curitem++;
}
- *p = NUL;
- itemcnt = curitem;
- if (usefmt != fmt)
+ *out_p = NUL;
+ int itemcnt = curitem;
+
+ // Free the format buffer if we allocated it internally
+ if (usefmt != fmt) {
xfree(usefmt);
+ }
+
+ // We have now processed the entire statusline format string.
+ // What follows is post-processing to handle alignment and
+ // highlighting factors.
- width = vim_strsize(out);
+ int width = vim_strsize(out);
if (maxwidth > 0 && width > maxwidth) {
- /* Result is too long, must truncate somewhere. */
- l = 0;
- if (itemcnt == 0)
- s = out;
- else {
- for (; l < itemcnt; l++)
- if (item[l].type == Trunc) {
- /* Truncate at %< item. */
- s = item[l].start;
+ // Result is too long, must truncate somewhere.
+ int item_idx = 0;
+ char_u *trunc_p;
+
+ // If there are no items, truncate from beginning
+ if (itemcnt == 0) {
+ trunc_p = out;
+
+ // Otherwise, look for the truncation item
+ } else {
+ // Default to truncating at the first item
+ trunc_p = item[0].start;
+ item_idx = 0;
+
+ for (int i = 0; i < itemcnt; i++)
+ if (item[i].type == Trunc) {
+ // Truncate at %< item.
+ trunc_p = item[i].start;
+ item_idx = i;
break;
}
- if (l == itemcnt) {
- /* No %< item, truncate first item. */
- s = item[0].start;
- l = 0;
- }
}
- if (width - vim_strsize(s) >= maxwidth) {
- /* Truncation mark is beyond max length */
+ // If the truncation point we found is beyond the maximum
+ // length of the string, truncate the end of the string.
+ if (width - vim_strsize(trunc_p) >= maxwidth) {
+ // If we are using a multi-byte encoding, walk from the beginning of the
+ // string to find the last character that will fit.
if (has_mbyte) {
- s = out;
+ trunc_p = out;
width = 0;
for (;; ) {
- width += ptr2cells(s);
+ width += ptr2cells(trunc_p);
if (width >= maxwidth)
break;
- s += (*mb_ptr2len)(s);
+
+ // Note: Only advance the pointer if the next
+ // character will fit in the available output space
+ trunc_p += (*mb_ptr2len)(trunc_p);
}
- /* Fill up for half a double-wide character. */
- while (++width < maxwidth)
- *s++ = fillchar;
- } else
- s = out + maxwidth - 1;
- for (l = 0; l < itemcnt; l++)
- if (item[l].start > s)
+
+ // Otherwise put the truncation point at the end, leaving enough room
+ // for a single-character truncation marker
+ } else {
+ trunc_p = out + maxwidth - 1;
+ }
+
+ // Ignore any items in the statusline that occur after
+ // the truncation point
+ for (int i = 0; i < itemcnt; i++) {
+ if (item[i].start > trunc_p) {
+ itemcnt = i;
break;
- itemcnt = l;
- *s++ = '>';
- *s = 0;
+ }
+ }
+
+ // Truncate the output
+ *trunc_p++ = '>';
+ *trunc_p = 0;
+
+ // Truncate at the truncation point we found
} else {
+ // { Determine how many bytes to remove
+ long trunc_len;
if (has_mbyte) {
- n = 0;
+ trunc_len = 0;
while (width >= maxwidth) {
- width -= ptr2cells(s + n);
- n += (*mb_ptr2len)(s + n);
+ width -= ptr2cells(trunc_p + trunc_len);
+ trunc_len += (*mb_ptr2len)(trunc_p + trunc_len);
}
- } else
- n = width - maxwidth + 1;
- p = s + n;
- STRMOVE(s + 1, p);
- *s = '<';
+ } else {
+ // Truncate an extra character so we can insert our `<`.
+ trunc_len = (width - maxwidth) + 1;
+ }
+ // }
- /* Fill up for half a double-wide character. */
+ // { Truncate the string
+ char_u *trunc_end_p = trunc_p + trunc_len;
+ STRMOVE(trunc_p + 1, trunc_end_p);
+
+ // Put a `<` to mark where we truncated at
+ *trunc_p = '<';
+
+ // Advance the pointer to the end of the string
+ trunc_p = trunc_p + STRLEN(trunc_p);
+
+ // Fill up for half a double-wide character.
while (++width < maxwidth) {
- s = s + STRLEN(s);
- *s++ = fillchar;
- *s = NUL;
+ *trunc_p++ = fillchar;
+ *trunc_p = NUL;
}
-
- --n; /* count the '<' */
- for (; l < itemcnt; l++) {
- if (item[l].start - n >= s)
- item[l].start -= n;
- else
- item[l].start = s;
+ // }
+
+ // { Change the start point for items based on
+ // their position relative to our truncation point
+
+ // Note: The offset is one less than the truncation length because
+ // the truncation marker `<` is not counted.
+ long item_offset = trunc_len - 1;
+
+ for (int i = item_idx; i < itemcnt; i++) {
+ // Items starting at or after the end of the truncated section need
+ // to be moved backwards.
+ if (item[i].start >= trunc_end_p) {
+ item[i].start -= item_offset;
+ // Anything inside the truncated area is set to start
+ // at the `<` truncation character.
+ } else {
+ item[i].start = trunc_p;
+ }
}
+ // }
}
width = maxwidth;
- } else if (width < maxwidth && STRLEN(out) + maxwidth - width + 1 <
- outlen) {
- /* Apply STL_MIDDLE if any */
- for (l = 0; l < itemcnt; l++)
- if (item[l].type == Middle)
+
+ // If there is room left in our statusline, and room left in our buffer,
+ // add characters at the middle marker (if there is one) to
+ // fill up the available space.
+ } else if (width < maxwidth
+ && STRLEN(out) + maxwidth - width + 1 < outlen) {
+ for (int item_idx = 0; item_idx < itemcnt; item_idx++) {
+ if (item[item_idx].type == Middle) {
+ // Move the statusline to make room for the middle characters
+ char_u *middle_end = item[item_idx].start + (maxwidth - width);
+ STRMOVE(middle_end, item[item_idx].start);
+
+ // Fill the middle section with our fill character
+ for (char_u *s = item[item_idx].start; s < middle_end; s++)
+ *s = fillchar;
+
+ // Adjust the offset of any items after the middle
+ for (item_idx++; item_idx < itemcnt; item_idx++)
+ item[item_idx].start += maxwidth - width;
+
+ width = maxwidth;
break;
- if (l < itemcnt) {
- p = item[l].start + maxwidth - width;
- STRMOVE(p, item[l].start);
- for (s = item[l].start; s < p; s++)
- *s = fillchar;
- for (l++; l < itemcnt; l++)
- item[l].start += maxwidth - width;
- width = maxwidth;
+ }
}
}
- /* Store the info about highlighting. */
+ // Store the info about highlighting.
if (hltab != NULL) {
- sp = hltab;
- for (l = 0; l < itemcnt; l++) {
+ struct stl_hlrec *sp = hltab;
+ for (long l = 0; l < itemcnt; l++) {
if (item[l].type == Highlight) {
sp->start = item[l].start;
sp->userhl = item[l].minwid;
@@ -3507,10 +3825,10 @@ build_stl_str_hl (
sp->userhl = 0;
}
- /* Store the info about tab pages labels. */
+ // Store the info about tab pages labels.
if (tabtab != NULL) {
- sp = tabtab;
- for (l = 0; l < itemcnt; l++) {
+ struct stl_hlrec *sp = tabtab;
+ for (long l = 0; l < itemcnt; l++) {
if (item[l].type == TabPage) {
sp->start = item[l].start;
sp->userhl = item[l].minwid;
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index abd16e57ae..208e41946b 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -977,6 +977,14 @@ static int insert_handle_key(InsertState *s)
queue_process_events(loop.events);
break;
+ case K_FOCUSGAINED: // Neovim has been given focus
+ apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf);
+ break;
+
+ case K_FOCUSLOST: // Neovim has lost focus
+ apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf);
+ break;
+
case K_HOME: // <Home>
case K_KHOME:
case K_S_HOME:
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 7685e422fc..9581b81456 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -98,6 +98,7 @@
#include "nvim/os/input.h"
#include "nvim/event/loop.h"
#include "nvim/lib/kvec.h"
+#include "nvim/lib/queue.h"
#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */
@@ -228,50 +229,11 @@ static int echo_attr = 0; /* attributes used for ":echo" */
#define GLV_QUIET TFN_QUIET /* no error messages */
#define GLV_NO_AUTOLOAD TFN_NO_AUTOLOAD /* do not use script autoloading */
-/*
- * Structure to hold info for a user function.
- */
-typedef struct ufunc ufunc_T;
-
-struct ufunc {
- int uf_varargs; /* variable nr of arguments */
- int uf_flags;
- int uf_calls; /* nr of active calls */
- garray_T uf_args; /* arguments */
- garray_T uf_lines; /* function lines */
- int uf_profiling; /* TRUE when func is being profiled */
- /* profiling the function as a whole */
- int uf_tm_count; /* nr of calls */
- proftime_T uf_tm_total; /* time spent in function + children */
- proftime_T uf_tm_self; /* time spent in function itself */
- proftime_T uf_tm_children; /* time spent in children this call */
- /* profiling the function per line */
- int *uf_tml_count; /* nr of times line was executed */
- proftime_T *uf_tml_total; /* time spent in a line + children */
- proftime_T *uf_tml_self; /* time spent in a line itself */
- proftime_T uf_tml_start; /* start time for current line */
- proftime_T uf_tml_children; /* time spent in children for this line */
- proftime_T uf_tml_wait; /* start wait time for current line */
- int uf_tml_idx; /* index of line being timed; -1 if none */
- int uf_tml_execed; /* line being timed was executed */
- scid_T uf_script_ID; /* ID of script where function was defined,
- used for s: variables */
- int uf_refcount; /* for numbered function: reference count */
- char_u uf_name[1]; /* name of function (actually longer); can
- start with <SNR>123_ (<SNR> is K_SPECIAL
- KS_EXTRA KE_SNR) */
-};
-
/* function flags */
#define FC_ABORT 1 /* abort function on error */
#define FC_RANGE 2 /* function accepts range */
#define FC_DICT 4 /* Dict function, uses "self" */
-/*
- * All user-defined functions are found in this hashtable.
- */
-static hashtab_T func_hashtab;
-
/* The names of packages that once were loaded are remembered. */
static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL};
@@ -279,12 +241,6 @@ static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL};
static dict_T *first_dict = NULL; /* list of all dicts */
static list_T *first_list = NULL; /* list of all lists */
-/* From user function to hashitem and back. */
-static ufunc_T dumuf;
-#define UF2HIKEY(fp) ((fp)->uf_name)
-#define HIKEY2UF(p) ((ufunc_T *)(p - (dumuf.uf_name - (char_u *)&dumuf)))
-#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
-
#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]
#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j]
@@ -459,6 +415,13 @@ typedef struct {
Queue *events;
} TerminalJobData;
+typedef struct dict_watcher {
+ ufunc_T *callback;
+ char *key_pattern;
+ QUEUE node;
+ bool busy; // prevent recursion if the dict is changed in the callback
+} DictWatcher;
+
/// Structure representing current VimL to messagepack conversion state
typedef struct {
enum {
@@ -2376,6 +2339,14 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch
: lp->ll_n1 != lp->ll_n2)
EMSG(_("E711: List value has not enough items"));
} else {
+ typval_T oldtv;
+ dict_T *dict = lp->ll_dict;
+ bool watched = is_watched(dict);
+
+ if (watched) {
+ init_tv(&oldtv);
+ }
+
/*
* Assign to a List or Dictionary item.
*/
@@ -2392,22 +2363,38 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch
return;
}
lp->ll_tv = &di->di_tv;
- } else if (op != NULL && *op != '=') {
- tv_op(lp->ll_tv, rettv, op);
- return;
- } else
- clear_tv(lp->ll_tv);
+ } else {
+ if (watched) {
+ copy_tv(lp->ll_tv, &oldtv);
+ }
- /*
- * Assign the value to the variable or list item.
- */
- if (copy)
+ if (op != NULL && *op != '=') {
+ tv_op(lp->ll_tv, rettv, op);
+ goto notify;
+ } else {
+ clear_tv(lp->ll_tv);
+ }
+ }
+
+ // Assign the value to the variable or list item.
+ if (copy) {
copy_tv(rettv, lp->ll_tv);
- else {
+ } else {
*lp->ll_tv = *rettv;
lp->ll_tv->v_lock = 0;
init_tv(rettv);
}
+
+notify:
+ if (watched) {
+ if (oldtv.v_type == VAR_UNKNOWN) {
+ dictwatcher_notify(dict, (char *)lp->ll_newkey, lp->ll_tv, NULL);
+ } else {
+ dictitem_T *di = lp->ll_di;
+ dictwatcher_notify(dict, (char *)di->di_key, lp->ll_tv, &oldtv);
+ clear_tv(&oldtv);
+ }
+ }
}
}
@@ -2932,12 +2919,31 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit)
++lp->ll_n1;
}
} else {
- if (lp->ll_list != NULL)
- /* unlet a List item. */
+ if (lp->ll_list != NULL) {
+ // unlet a List item.
listitem_remove(lp->ll_list, lp->ll_li);
- else
- /* unlet a Dictionary item. */
- dictitem_remove(lp->ll_dict, lp->ll_di);
+ } else {
+ // unlet a Dictionary item.
+ dict_T *d = lp->ll_dict;
+ dictitem_T *di = lp->ll_di;
+ bool watched = is_watched(d);
+ char *key = NULL;
+ typval_T oldtv;
+
+ if (watched) {
+ copy_tv(&di->di_tv, &oldtv);
+ // need to save key because dictitem_remove will free it
+ key = xstrdup((char *)di->di_key);
+ }
+
+ dictitem_remove(d, di);
+
+ if (watched) {
+ dictwatcher_notify(d, key, NULL, &oldtv);
+ clear_tv(&oldtv);
+ xfree(key);
+ }
+ }
}
return ret;
@@ -2953,8 +2959,9 @@ int do_unlet(char_u *name, int forceit)
hashitem_T *hi;
char_u *varname;
dictitem_T *di;
+ dict_T *dict;
+ ht = find_var_ht_dict(name, &varname, &dict);
- ht = find_var_ht(name, &varname);
if (ht != NULL && *varname != NUL) {
hi = hash_find(ht, varname);
if (!HASHITEM_EMPTY(hi)) {
@@ -2962,7 +2969,19 @@ int do_unlet(char_u *name, int forceit)
if (var_check_fixed(di->di_flags, name)
|| var_check_ro(di->di_flags, name))
return FAIL;
+ typval_T oldtv;
+ bool watched = is_watched(dict);
+
+ if (watched) {
+ copy_tv(&di->di_tv, &oldtv);
+ }
+
delete_var(ht, hi);
+
+ if (watched) {
+ dictwatcher_notify(dict, (char *)varname, NULL, &oldtv);
+ clear_tv(&oldtv);
+ }
return OK;
}
}
@@ -5959,6 +5978,7 @@ dict_T *dict_alloc(void) FUNC_ATTR_NONNULL_RET
d->dv_refcount = 0;
d->dv_copyID = 0;
d->internal_refcount = 0;
+ QUEUE_INIT(&d->watchers);
return d;
}
@@ -6025,6 +6045,14 @@ dict_free (
--todo;
}
}
+
+ while (!QUEUE_EMPTY(&d->watchers)) {
+ QUEUE *w = QUEUE_HEAD(&d->watchers);
+ DictWatcher *watcher = dictwatcher_node_data(w);
+ dictwatcher_free(watcher);
+ QUEUE_REMOVE(w);
+ }
+
hash_clear(&d->dv_hashtab);
xfree(d);
}
@@ -6066,10 +6094,11 @@ static void dictitem_remove(dict_T *dict, dictitem_T *item)
hashitem_T *hi;
hi = hash_find(&dict->dv_hashtab, item->di_key);
- if (HASHITEM_EMPTY(hi))
+ if (HASHITEM_EMPTY(hi)) {
EMSG2(_(e_intern2), "dictitem_remove()");
- else
+ } else {
hash_remove(&dict->dv_hashtab, hi);
+ }
dictitem_free(item);
}
@@ -6264,7 +6293,16 @@ static bool get_dict_callback(dict_T *d, char *key, ufunc_T **result)
return false;
}
- uint8_t *name = di->di_tv.vval.v_string;
+ if ((*result = find_ufunc(di->di_tv.vval.v_string)) == NULL) {
+ return false;
+ }
+
+ (*result)->uf_refcount++;
+ return true;
+}
+
+static ufunc_T *find_ufunc(uint8_t *name)
+{
uint8_t *n = name;
ufunc_T *rv = NULL;
if (*n > '9' || *n < '0') {
@@ -6279,13 +6317,10 @@ static bool get_dict_callback(dict_T *d, char *key, ufunc_T **result)
if (!rv) {
EMSG2(_("Function %s doesn't exist"), name);
- *result = NULL;
- return false;
+ return NULL;
}
- rv->uf_refcount++;
- *result = rv;
- return true;
+ return rv;
}
/*
@@ -7098,6 +7133,8 @@ static struct fst {
{"cursor", 1, 3, f_cursor},
{"deepcopy", 1, 2, f_deepcopy},
{"delete", 1, 1, f_delete},
+ {"dictwatcheradd", 3, 3, f_dictwatcheradd},
+ {"dictwatcherdel", 3, 3, f_dictwatcherdel},
{"did_filetype", 0, 0, f_did_filetype},
{"diff_filler", 1, 1, f_diff_filler},
{"diff_hlID", 2, 2, f_diff_hlID},
@@ -8666,6 +8703,110 @@ static void f_delete(typval_T *argvars, typval_T *rettv)
rettv->vval.v_number = os_remove((char *)get_tv_string(&argvars[0]));
}
+// dictwatcheradd(dict, key, funcref) function
+static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG2(e_invarg2, "dict");
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) {
+ EMSG2(e_invarg2, "key");
+ return;
+ }
+
+ if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) {
+ EMSG2(e_invarg2, "funcref");
+ return;
+ }
+
+ char *key_pattern = (char *)get_tv_string_chk(argvars + 1);
+ assert(key_pattern);
+ const size_t key_len = STRLEN(argvars[1].vval.v_string);
+
+ if (key_len == 0) {
+ EMSG(_(e_emptykey));
+ return;
+ }
+
+ ufunc_T *func = find_ufunc(argvars[2].vval.v_string);
+ if (!func) {
+ // Invalid function name. Error already reported by `find_ufunc`.
+ return;
+ }
+
+ func->uf_refcount++;
+ DictWatcher *watcher = xmalloc(sizeof(DictWatcher));
+ watcher->key_pattern = xmemdupz(key_pattern, key_len);
+ watcher->callback = func;
+ watcher->busy = false;
+ QUEUE_INSERT_TAIL(&argvars[0].vval.v_dict->watchers, &watcher->node);
+}
+
+// dictwatcherdel(dict, key, funcref) function
+static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG2(e_invarg2, "dict");
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) {
+ EMSG2(e_invarg2, "key");
+ return;
+ }
+
+ if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) {
+ EMSG2(e_invarg2, "funcref");
+ return;
+ }
+
+ char *key_pattern = (char *)get_tv_string_chk(argvars + 1);
+ assert(key_pattern);
+ const size_t key_len = STRLEN(argvars[1].vval.v_string);
+
+ if (key_len == 0) {
+ EMSG(_(e_emptykey));
+ return;
+ }
+
+ ufunc_T *func = find_ufunc(argvars[2].vval.v_string);
+ if (!func) {
+ // Invalid function name. Error already reported by `find_ufunc`.
+ return;
+ }
+
+ dict_T *dict = argvars[0].vval.v_dict;
+ QUEUE *w = NULL;
+ DictWatcher *watcher = NULL;
+ bool matched = false;
+ QUEUE_FOREACH(w, &dict->watchers) {
+ watcher = dictwatcher_node_data(w);
+ if (func == watcher->callback
+ && !strcmp(watcher->key_pattern, key_pattern)) {
+ matched = true;
+ break;
+ }
+ }
+
+ if (!matched) {
+ EMSG("Couldn't find a watcher matching key and callback");
+ return;
+ }
+
+ QUEUE_REMOVE(w);
+ dictwatcher_free(watcher);
+}
+
/*
* "did_filetype()" function
*/
@@ -8972,6 +9113,7 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action)
dictitem_T *di1;
hashitem_T *hi2;
int todo;
+ bool watched = is_watched(d1);
todo = (int)d2->dv_hashtab.ht_used;
for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2) {
@@ -8992,14 +9134,30 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action)
}
if (di1 == NULL) {
di1 = dictitem_copy(HI2DI(hi2));
- if (dict_add(d1, di1) == FAIL)
+ if (dict_add(d1, di1) == FAIL) {
dictitem_free(di1);
+ }
+
+ if (watched) {
+ dictwatcher_notify(d1, (char *)di1->di_key, &di1->di_tv, NULL);
+ }
} else if (*action == 'e') {
EMSG2(_("E737: Key already exists: %s"), hi2->hi_key);
break;
} else if (*action == 'f' && HI2DI(hi2) != di1) {
+ typval_T oldtv;
+
+ if (watched) {
+ copy_tv(&di1->di_tv, &oldtv);
+ }
+
clear_tv(&di1->di_tv);
copy_tv(&HI2DI(hi2)->di_tv, &di1->di_tv);
+
+ if (watched) {
+ dictwatcher_notify(d1, (char *)di1->di_key, &di1->di_tv, &oldtv);
+ clear_tv(&oldtv);
+ }
}
}
}
@@ -10556,9 +10714,7 @@ static void f_has(typval_T *argvars, typval_T *rettv)
#endif
"arabic",
"autocmd",
-#ifdef FEAT_BROWSE
"browsefilter",
-#endif
"byte_offset",
"cindent",
"cmdline_compl",
@@ -13519,6 +13675,9 @@ static void f_remove(typval_T *argvars, typval_T *rettv)
*rettv = di->di_tv;
init_tv(&di->di_tv);
dictitem_remove(d, di);
+ if (is_watched(d)) {
+ dictwatcher_notify(d, (char *)key, NULL, rettv);
+ }
}
}
}
@@ -18111,53 +18270,67 @@ static dictitem_T *find_var_in_ht(hashtab_T *ht, int htname, char_u *varname, in
return HI2DI(hi);
}
-/*
- * Find the hashtab used for a variable name.
- * Set "varname" to the start of name without ':'.
- */
-static hashtab_T *find_var_ht(char_u *name, char_u **varname)
+// Find the dict and hashtable used for a variable name. Set "varname" to the
+// start of name without ':'.
+static hashtab_T *find_var_ht_dict(char_u *name, uint8_t **varname, dict_T **d)
{
hashitem_T *hi;
+ *d = NULL;
if (name[1] != ':') {
- /* The name must not start with a colon or #. */
- if (name[0] == ':' || name[0] == AUTOLOAD_CHAR)
+ // name has implicit scope
+ if (name[0] == ':' || name[0] == AUTOLOAD_CHAR) {
+ // The name must not start with a colon or #.
return NULL;
+ }
*varname = name;
- /* "version" is "v:version" in all scopes */
+ // "version" is "v:version" in all scopes
hi = hash_find(&compat_hashtab, name);
- if (!HASHITEM_EMPTY(hi))
+ if (!HASHITEM_EMPTY(hi)) {
return &compat_hashtab;
+ }
- if (current_funccal == NULL)
- return &globvarht; /* global variable */
- return &current_funccal->l_vars.dv_hashtab; /* l: variable */
+ *d = current_funccal ? &current_funccal->l_vars : &globvardict;
+ goto end;
}
+
*varname = name + 2;
- if (*name == 'g') /* global variable */
- return &globvarht;
- /* There must be no ':' or '#' in the rest of the name, unless g: is used
- */
- if (vim_strchr(name + 2, ':') != NULL
- || vim_strchr(name + 2, AUTOLOAD_CHAR) != NULL)
+ if (*name == 'g') { // global variable
+ *d = &globvardict;
+ } else if (vim_strchr(name + 2, ':') != NULL
+ || vim_strchr(name + 2, AUTOLOAD_CHAR) != NULL) {
+ // There must be no ':' or '#' in the rest of the name if g: was not used
return NULL;
- if (*name == 'b') /* buffer variable */
- return &curbuf->b_vars->dv_hashtab;
- if (*name == 'w') /* window variable */
- return &curwin->w_vars->dv_hashtab;
- if (*name == 't') /* tab page variable */
- return &curtab->tp_vars->dv_hashtab;
- if (*name == 'v') /* v: variable */
- return &vimvarht;
- if (*name == 'a' && current_funccal != NULL) /* function argument */
- return &current_funccal->l_avars.dv_hashtab;
- if (*name == 'l' && current_funccal != NULL) /* local function variable */
- return &current_funccal->l_vars.dv_hashtab;
- if (*name == 's' /* script variable */
- && current_SID > 0 && current_SID <= ga_scripts.ga_len)
- return &SCRIPT_VARS(current_SID);
- return NULL;
+ }
+
+ if (*name == 'b') { // buffer variable
+ *d = curbuf->b_vars;
+ } else if (*name == 'w') { // window variable
+ *d = curwin->w_vars;
+ } else if (*name == 't') { // tab page variable
+ *d = curtab->tp_vars;
+ } else if (*name == 'v') { // v: variable
+ *d = &vimvardict;
+ } else if (*name == 'a' && current_funccal != NULL) { // function argument
+ *d = &current_funccal->l_avars;
+ } else if (*name == 'l' && current_funccal != NULL) { // local variable
+ *d = &current_funccal->l_vars;
+ } else if (*name == 's' // script variable
+ && current_SID > 0 && current_SID <= ga_scripts.ga_len) {
+ *d = &SCRIPT_SV(current_SID)->sv_dict;
+ }
+
+end:
+ return *d ? &(*d)->dv_hashtab : NULL;
+}
+
+// Find the hashtab used for a variable name.
+// Set "varname" to the start of name without ':'.
+static hashtab_T *find_var_ht(uint8_t *name, uint8_t **varname)
+{
+ dict_T *d;
+ return find_var_ht_dict(name, varname, &d);
}
/*
@@ -18221,6 +18394,7 @@ void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope)
dict_var->di_tv.v_lock = VAR_FIXED;
dict_var->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
dict_var->di_key[0] = NUL;
+ QUEUE_INIT(&dict->watchers);
}
/*
@@ -18353,8 +18527,16 @@ set_var (
dictitem_T *v;
char_u *varname;
hashtab_T *ht;
+ typval_T oldtv;
+ dict_T *dict;
+
+ ht = find_var_ht_dict(name, &varname, &dict);
+ bool watched = is_watched(dict);
+
+ if (watched) {
+ init_tv(&oldtv);
+ }
- ht = find_var_ht(name, &varname);
if (ht == NULL || *varname == NUL) {
EMSG2(_(e_illvar), name);
return;
@@ -18411,6 +18593,9 @@ set_var (
return;
}
+ if (watched) {
+ copy_tv(&v->di_tv, &oldtv);
+ }
clear_tv(&v->di_tv);
} else { /* add a new variable */
/* Can't add "v:" variable. */
@@ -18432,13 +18617,22 @@ set_var (
v->di_flags = 0;
}
- if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT)
+ if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) {
copy_tv(tv, &v->di_tv);
- else {
+ } else {
v->di_tv = *tv;
v->di_tv.v_lock = 0;
init_tv(tv);
}
+
+ if (watched) {
+ if (oldtv.v_type == VAR_UNKNOWN) {
+ dictwatcher_notify(dict, (char *)v->di_key, &v->di_tv, NULL);
+ } else {
+ dictwatcher_notify(dict, (char *)v->di_key, &v->di_tv, &oldtv);
+ clear_tv(&oldtv);
+ }
+ }
}
/*
@@ -21014,9 +21208,9 @@ void last_set_msg(scid_T scriptID)
*/
void ex_oldfiles(exarg_T *eap)
{
- list_T *l = vimvars[VV_OLDFILES].vv_list;
+ list_T *l = get_vim_var_list(VV_OLDFILES);
listitem_T *li;
- int nr = 0;
+ long nr = 0;
if (l == NULL)
msg((char_u *)_("No old files"));
@@ -21024,7 +21218,7 @@ void ex_oldfiles(exarg_T *eap)
msg_start();
msg_scroll = TRUE;
for (li = l->lv_first; li != NULL && !got_int; li = li->li_next) {
- msg_outnum((long)++nr);
+ msg_outnum(++nr);
MSG_PUTS(": ");
msg_outtrans(get_tv_string(&li->li_tv));
msg_putchar('\n');
@@ -21034,6 +21228,23 @@ void ex_oldfiles(exarg_T *eap)
/* Assume "got_int" was set to truncate the listing. */
got_int = FALSE;
+ // File selection prompt on ":oldfiles!"
+ if (eap->forceit) {
+ quit_more = false;
+ nr = prompt_for_number(false);
+ msg_starthere();
+ if (nr > 0 && nr <= l->lv_len) {
+ char_u *p = list_find_str(l, nr);
+ if (p == NULL) {
+ return;
+ }
+ p = expand_env_save(p);
+ eap->arg = p;
+ eap->cmdidx = CMD_edit;
+ do_exedit(eap, NULL);
+ xfree(p);
+ }
+ }
}
}
@@ -21570,8 +21781,10 @@ static void on_process_exit(Process *proc, int status, void *d)
TerminalJobData *data = d;
if (data->term && !data->exited) {
data->exited = true;
- terminal_close(data->term,
- _("\r\n[Program exited, press any key to close]"));
+ char msg[22];
+ snprintf(msg, sizeof msg, "\r\n[Process exited %d]", proc->status);
+ terminal_close(data->term, msg);
+ apply_autocmds(EVENT_TERMCLOSE, NULL, NULL, false, curbuf);
}
if (data->status_ptr) {
@@ -21759,3 +21972,94 @@ bool eval_has_provider(char *name)
return false;
}
+
+// Compute the `DictWatcher` address from a QUEUE node. This only exists because
+// ASAN doesn't handle `QUEUE_DATA` pointer arithmetic, and we blacklist this
+// function on .asan-blacklist.
+static DictWatcher *dictwatcher_node_data(QUEUE *q)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ return QUEUE_DATA(q, DictWatcher, node);
+}
+
+// Send a change notification to all `dict` watchers that match `key`.
+static void dictwatcher_notify(dict_T *dict, const char *key, typval_T *newtv,
+ typval_T *oldtv)
+ FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(2)
+{
+ typval_T argv[3];
+ for (size_t i = 0; i < ARRAY_SIZE(argv); i++) {
+ init_tv(argv + i);
+ }
+
+ argv[0].v_type = VAR_DICT;
+ argv[0].vval.v_dict = dict;
+ argv[1].v_type = VAR_STRING;
+ argv[1].vval.v_string = (char_u *)xstrdup(key);
+ argv[2].v_type = VAR_DICT;
+ argv[2].vval.v_dict = dict_alloc();
+ argv[2].vval.v_dict->dv_refcount++;
+
+ if (newtv) {
+ dictitem_T *v = dictitem_alloc((char_u *)"new");
+ copy_tv(newtv, &v->di_tv);
+ dict_add(argv[2].vval.v_dict, v);
+ }
+
+ if (oldtv) {
+ dictitem_T *v = dictitem_alloc((char_u *)"old");
+ copy_tv(oldtv, &v->di_tv);
+ dict_add(argv[2].vval.v_dict, v);
+ }
+
+ typval_T rettv;
+
+ QUEUE *w;
+ QUEUE_FOREACH(w, &dict->watchers) {
+ DictWatcher *watcher = dictwatcher_node_data(w);
+ if (!watcher->busy && dictwatcher_matches(watcher, key)) {
+ init_tv(&rettv);
+ watcher->busy = true;
+ call_user_func(watcher->callback, ARRAY_SIZE(argv), argv, &rettv,
+ curwin->w_cursor.lnum, curwin->w_cursor.lnum, NULL);
+ watcher->busy = false;
+ clear_tv(&rettv);
+ }
+ }
+
+ for (size_t i = 1; i < ARRAY_SIZE(argv); i++) {
+ clear_tv(argv + i);
+ }
+}
+
+// Test if `key` matches with with `watcher->key_pattern`
+static bool dictwatcher_matches(DictWatcher *watcher, const char *key)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // For now only allow very simple globbing in key patterns: a '*' at the end
+ // of the string means it should match everything up to the '*' instead of the
+ // whole string.
+ char *nul = strchr(watcher->key_pattern, NUL);
+ size_t len = nul - watcher->key_pattern;
+ if (*(nul - 1) == '*') {
+ return !strncmp(key, watcher->key_pattern, len - 1);
+ } else {
+ return !strcmp(key, watcher->key_pattern);
+ }
+}
+
+// Perform all necessary cleanup for a `DictWatcher` instance.
+static void dictwatcher_free(DictWatcher *watcher)
+ FUNC_ATTR_NONNULL_ALL
+{
+ user_func_unref(watcher->callback);
+ xfree(watcher->key_pattern);
+ xfree(watcher);
+}
+
+// Check if `d` has at least one watcher.
+static bool is_watched(dict_T *d)
+{
+ return d && !QUEUE_EMPTY(&d->watchers);
+}
+
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 864daed716..8ccf71068c 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -5,6 +5,47 @@
#include "nvim/profile.h"
+// All user-defined functions are found in this hashtable.
+EXTERN hashtab_T func_hashtab;
+
+// Structure to hold info for a user function.
+typedef struct ufunc ufunc_T;
+
+struct ufunc {
+ int uf_varargs; ///< variable nr of arguments
+ int uf_flags;
+ int uf_calls; ///< nr of active calls
+ garray_T uf_args; ///< arguments
+ garray_T uf_lines; ///< function lines
+ int uf_profiling; ///< true when func is being profiled
+ // Profiling the function as a whole.
+ int uf_tm_count; ///< nr of calls
+ proftime_T uf_tm_total; ///< time spent in function + children
+ proftime_T uf_tm_self; ///< time spent in function itself
+ proftime_T uf_tm_children; ///< time spent in children this call
+ // Profiling the function per line.
+ int *uf_tml_count; ///< nr of times line was executed
+ proftime_T *uf_tml_total; ///< time spent in a line + children
+ proftime_T *uf_tml_self; ///< time spent in a line itself
+ proftime_T uf_tml_start; ///< start time for current line
+ proftime_T uf_tml_children; ///< time spent in children for this line
+ proftime_T uf_tml_wait; ///< start wait time for current line
+ int uf_tml_idx; ///< index of line being timed; -1 if none
+ int uf_tml_execed; ///< line being timed was executed
+ scid_T uf_script_ID; ///< ID of script where function was defined,
+ // used for s: variables
+ int uf_refcount; ///< for numbered function: reference count
+ char_u uf_name[1]; ///< name of function (actually longer); can
+ // start with <SNR>123_ (<SNR> is K_SPECIAL
+ // KS_EXTRA KE_SNR)
+};
+
+// From user function to hashitem and back.
+EXTERN ufunc_T dumuf;
+#define UF2HIKEY(fp) ((fp)->uf_name)
+#define HIKEY2UF(p) ((ufunc_T *)(p - (dumuf.uf_name - (char_u *)&dumuf)))
+#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
+
/* Defines for Vim variables. These must match vimvars[] in eval.c! */
enum {
VV_COUNT,
diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h
index 373f1e6278..bd50d6b829 100644
--- a/src/nvim/eval_defs.h
+++ b/src/nvim/eval_defs.h
@@ -5,6 +5,7 @@
#include <stddef.h>
#include "nvim/hashtab.h"
+#include "nvim/lib/queue.h"
typedef int varnumber_T;
typedef double float_T;
@@ -119,6 +120,7 @@ struct dictvar_S {
dict_T *dv_used_prev; /* previous dict in used dicts list */
int internal_refcount; // number of internal references to
// prevent garbage collection
+ QUEUE watchers; // dictionary key watchers set by user code
};
// structure used for explicit stack while garbage collecting hash tables
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 5db3880026..c4fffcda71 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -62,7 +62,6 @@
#include "nvim/tempfile.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/version.h"
#include "nvim/window.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 77f7dba81b..b7a3505c99 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -267,7 +267,7 @@ return {
},
{
command='buffers',
- flags=bit.bor(BANG, TRLBAR, CMDWIN),
+ flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
func='buflist_list',
},
@@ -885,7 +885,7 @@ return {
},
{
command='files',
- flags=bit.bor(BANG, TRLBAR, CMDWIN),
+ flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
func='buflist_list',
},
@@ -989,13 +989,13 @@ return {
command='gui',
flags=bit.bor(BANG, FILES, EDITCMD, ARGOPT, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_gui',
+ func='ex_nogui',
},
{
command='gvim',
flags=bit.bor(BANG, FILES, EDITCMD, ARGOPT, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_gui',
+ func='ex_nogui',
},
{
command='help',
@@ -1013,7 +1013,7 @@ return {
command='helpfind',
flags=bit.bor(EXTRA, NOTRLCOM),
addr_type=ADDR_LINES,
- func='ex_helpfind',
+ func='ex_ni',
},
{
command='helpgrep',
@@ -1521,7 +1521,7 @@ return {
},
{
command='ls',
- flags=bit.bor(BANG, TRLBAR, CMDWIN),
+ flags=bit.bor(BANG, EXTRA, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
func='buflist_list',
},
@@ -1643,19 +1643,19 @@ return {
command='nbkey',
flags=bit.bor(EXTRA, NOTADR, NEEDARG),
addr_type=ADDR_LINES,
- func='ex_nbkey',
+ func='ex_ni',
},
{
command='nbclose',
flags=bit.bor(TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_nbclose',
+ func='ex_ni',
},
{
command='nbstart',
flags=bit.bor(WORD1, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_nbstart',
+ func='ex_ni',
},
{
command='new',
@@ -1754,12 +1754,6 @@ return {
func='ex_menu',
},
{
- command='open',
- flags=bit.bor(RANGE, BANG, EXTRA),
- addr_type=ADDR_LINES,
- func='ex_open',
- },
- {
command='oldfiles',
flags=bit.bor(BANG, TRLBAR, SBOXOK, CMDWIN),
addr_type=ADDR_LINES,
@@ -1865,7 +1859,7 @@ return {
command='popup',
flags=bit.bor(NEEDARG, EXTRA, BANG, TRLBAR, NOTRLCOM, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_popup',
+ func='ex_ni',
},
{
command='ppop',
@@ -1889,13 +1883,13 @@ return {
command='promptfind',
flags=bit.bor(EXTRA, NOTRLCOM, CMDWIN),
addr_type=ADDR_LINES,
- func='gui_mch_find_dialog',
+ func='ex_ni',
},
{
command='promptrepl',
flags=bit.bor(EXTRA, NOTRLCOM, CMDWIN),
addr_type=ADDR_LINES,
- func='gui_mch_replace_dialog',
+ func='ex_ni',
},
{
command='profile',
@@ -2309,7 +2303,7 @@ return {
command='simalt',
flags=bit.bor(NEEDARG, WORD1, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_simalt',
+ func='ex_ni',
},
{
command='sign',
@@ -3035,7 +3029,7 @@ return {
command='wsverb',
flags=bit.bor(EXTRA, NOTADR, NEEDARG),
addr_type=ADDR_LINES,
- func='ex_wsverb',
+ func='ex_ni',
},
{
command='wshada',
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index f172ea54c2..a632a3cce4 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -21,7 +21,6 @@
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
-#include "nvim/version.h"
#include "nvim/ex_cmds2.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
@@ -762,9 +761,14 @@ void ex_profile(exarg_T *eap)
do_profiling = PROF_YES;
profile_set_wait(profile_zero());
set_vim_var_nr(VV_PROFILING, 1L);
- } else if (do_profiling == PROF_NONE)
+ } else if (do_profiling == PROF_NONE) {
EMSG(_("E750: First use \":profile start {fname}\""));
- else if (STRCMP(eap->arg, "pause") == 0) {
+ } else if (STRCMP(eap->arg, "stop") == 0) {
+ profile_dump();
+ do_profiling = PROF_NONE;
+ set_vim_var_nr(VV_PROFILING, 0L);
+ profile_reset();
+ } else if (STRCMP(eap->arg, "pause") == 0) {
if (do_profiling == PROF_YES)
pause_time = profile_start();
do_profiling = PROF_PAUSED;
@@ -774,6 +778,8 @@ void ex_profile(exarg_T *eap)
profile_set_wait(profile_add(profile_get_wait(), pause_time));
}
do_profiling = PROF_YES;
+ } else if (STRCMP(eap->arg, "dump") == 0) {
+ profile_dump();
} else {
/* The rest is similar to ":breakadd". */
ex_breakadd(eap);
@@ -817,18 +823,14 @@ static enum {
} pexpand_what;
static char *pexpand_cmds[] = {
- "start",
-#define PROFCMD_START 0
- "pause",
-#define PROFCMD_PAUSE 1
"continue",
-#define PROFCMD_CONTINUE 2
- "func",
-#define PROFCMD_FUNC 3
+ "dump",
"file",
-#define PROFCMD_FILE 4
+ "func",
+ "pause",
+ "start",
+ "stop",
NULL
-#define PROFCMD_LAST 5
};
/*
@@ -891,10 +893,63 @@ void profile_dump(void)
}
}
-/*
- * Start profiling script "fp".
- */
-static void script_do_profile(scriptitem_T *si)
+/// Reset all profiling information.
+static void profile_reset(void)
+{
+ // Reset sourced files.
+ for (int id = 1; id <= script_items.ga_len; id++) {
+ scriptitem_T *si = &SCRIPT_ITEM(id);
+ if (si->sn_prof_on) {
+ si->sn_prof_on = 0;
+ si->sn_pr_force = 0;
+ si->sn_pr_child = profile_zero();
+ si->sn_pr_nest = 0;
+ si->sn_pr_count = 0;
+ si->sn_pr_total = profile_zero();
+ si->sn_pr_self = profile_zero();
+ si->sn_pr_start = profile_zero();
+ si->sn_pr_children = profile_zero();
+ ga_clear(&si->sn_prl_ga);
+ si->sn_prl_start = profile_zero();
+ si->sn_prl_children = profile_zero();
+ si->sn_prl_wait = profile_zero();
+ si->sn_prl_idx = -1;
+ si->sn_prl_execed = 0;
+ }
+ }
+
+ // Reset functions.
+ size_t n = func_hashtab.ht_used;
+ hashitem_T *hi = func_hashtab.ht_array;
+
+ for (; n > (size_t)0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ n--;
+ ufunc_T *uf = HI2UF(hi);
+ if (uf->uf_profiling) {
+ uf->uf_profiling = 0;
+ uf->uf_tm_count = 0;
+ uf->uf_tm_total = profile_zero();
+ uf->uf_tm_self = profile_zero();
+ uf->uf_tm_children = profile_zero();
+ uf->uf_tml_count = NULL;
+ uf->uf_tml_total = NULL;
+ uf->uf_tml_self = NULL;
+ uf->uf_tml_start = profile_zero();
+ uf->uf_tml_children = profile_zero();
+ uf->uf_tml_wait = profile_zero();
+ uf->uf_tml_idx = -1;
+ uf->uf_tml_execed = 0;
+ }
+ }
+ }
+
+ xfree(profile_fname);
+ profile_fname = NULL;
+}
+
+/// Start profiling a script.
+static void profile_init(scriptitem_T *si)
{
si->sn_pr_count = 0;
si->sn_pr_total = profile_zero();
@@ -2357,9 +2412,7 @@ do_source (
*/
p = path_tail(fname_exp);
if ((*p == '.' || *p == '_')
- && (STRICMP(p + 1, "nvimrc") == 0
- || STRICMP(p + 1, "ngvimrc") == 0
- || STRICMP(p + 1, "exrc") == 0)) {
+ && (STRICMP(p + 1, "nvimrc") == 0 || STRICMP(p + 1, "exrc") == 0)) {
if (*p == '_')
*p = '.';
else
@@ -2510,8 +2563,8 @@ do_source (
int forceit;
/* Check if we do profiling for this script. */
- if (!si->sn_prof_on && has_profiling(TRUE, si->sn_name, &forceit)) {
- script_do_profile(si);
+ if (!si->sn_prof_on && has_profiling(true, si->sn_name, &forceit)) {
+ profile_init(si);
si->sn_pr_force = forceit;
}
if (si->sn_prof_on) {
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 70cf5fd029..4d9f8b5769 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -80,6 +80,9 @@
static int quitmore = 0;
static int ex_pressedreturn = FALSE;
+/* whether ":lcd" was produced for a session */
+static int did_lcd;
+
typedef struct ucmd {
char_u *uc_name; /* The command name */
uint32_t uc_argt; /* The argument type */
@@ -144,23 +147,9 @@ struct dbg_stuff {
# include "ex_docmd.c.generated.h"
#endif
-# define ex_gui ex_nogui
-# define ex_popup ex_ni
-# define ex_simalt ex_ni
-# define gui_mch_find_dialog ex_ni
-# define gui_mch_replace_dialog ex_ni
-# define ex_helpfind ex_ni
-static int did_lcd; /* whether ":lcd" was produced for a session */
#ifndef HAVE_WORKING_LIBINTL
# define ex_language ex_ni
#endif
-# define ex_wsverb ex_ni
-# define ex_nbclose ex_ni
-# define ex_nbkey ex_ni
-# define ex_nbstart ex_ni
-
-
-
/*
* Declare cmdnames[].
@@ -6467,40 +6456,6 @@ static void ex_find(exarg_T *eap)
}
/*
- * ":open" simulation: for now just work like ":visual".
- */
-static void ex_open(exarg_T *eap)
-{
- regmatch_T regmatch;
- char_u *p;
-
- curwin->w_cursor.lnum = eap->line2;
- beginline(BL_SOL | BL_FIX);
- if (*eap->arg == '/') {
- /* ":open /pattern/": put cursor in column found with pattern */
- ++eap->arg;
- p = skip_regexp(eap->arg, '/', p_magic, NULL);
- *p = NUL;
- regmatch.regprog = vim_regcomp(eap->arg, p_magic ? RE_MAGIC : 0);
- if (regmatch.regprog != NULL) {
- regmatch.rm_ic = p_ic;
- p = get_cursor_line_ptr();
- if (vim_regexec(&regmatch, p, (colnr_T)0))
- curwin->w_cursor.col = (colnr_T)(regmatch.startp[0] - p);
- else
- EMSG(_(e_nomatch));
- vim_regfree(regmatch.regprog);
- }
- /* Move to the NUL, ignore any other arguments. */
- eap->arg += STRLEN(eap->arg);
- }
- check_cursor();
-
- eap->cmdidx = CMD_visual;
- do_exedit(eap, NULL);
-}
-
-/*
* ":edit", ":badd", ":visual".
*/
static void ex_edit(exarg_T *eap)
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 0ef0a12889..7c42238d20 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -633,7 +633,7 @@ EXTERN int silent_mode INIT(= FALSE);
/* set to TRUE when "-s" commandline argument
* used for ex */
-// Set to true when sourcing of startup scripts (nvimrc) is done.
+// Set to true when sourcing of startup scripts (init.vim) is done.
// Used for options that cannot be changed after startup scripts.
EXTERN bool did_source_startup_scripts INIT(= false);
diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c
index 9d656276ec..85380ba173 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keymap.c
@@ -284,6 +284,8 @@ static struct key_name_entry {
{K_SNR, (char_u *)"SNR"},
{K_PLUG, (char_u *)"Plug"},
{K_PASTE, (char_u *)"Paste"},
+ {K_FOCUSGAINED, (char_u *)"FocusGained"},
+ {K_FOCUSLOST, (char_u *)"FocusLost"},
{0, NULL}
};
diff --git a/src/nvim/log.h b/src/nvim/log.h
index 152e90760e..32b7276f14 100644
--- a/src/nvim/log.h
+++ b/src/nvim/log.h
@@ -19,7 +19,7 @@
#define ELOGN(...)
// Logging is disabled if NDEBUG or DISABLE_LOG is defined.
-#ifdef NDEBUG
+#if !defined(DISABLE_LOG) && defined(NDEBUG)
# define DISABLE_LOG
#endif
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 43723ff363..83fe32cccb 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -935,9 +935,6 @@ static void command_line_scan(mparm_T *parmp)
want_argument = TRUE;
break;
- case 'X': /* "-X" don't connect to X server */
- break;
-
case 'Z': /* "-Z" restricted mode */
restricted = TRUE;
break;
@@ -1833,7 +1830,7 @@ static void usage(void)
mch_msg(_(" -n No swap file, use memory only\n"));
mch_msg(_(" -r, -L List swap files and exit\n"));
mch_msg(_(" -r <file> Recover crashed session\n"));
- mch_msg(_(" -u <nvimrc> Use <nvimrc> instead of the default\n"));
+ mch_msg(_(" -u <vimrc> Use <vimrc> instead of the default\n"));
mch_msg(_(" -i <shada> Use <shada> instead of the default\n"));
mch_msg(_(" --noplugin Don't load plugin scripts\n"));
mch_msg(_(" -o[N] Open N windows (default: one for each file)\n"));
@@ -1842,7 +1839,7 @@ static void usage(void)
mch_msg(_(" + Start at end of file\n"));
mch_msg(_(" +<linenum> Start at line <linenum>\n"));
mch_msg(_(" +/<pattern> Start at first occurrence of <pattern>\n"));
- mch_msg(_(" --cmd <command> Execute <command> before loading any nvimrc\n"));
+ mch_msg(_(" --cmd <command> Execute <command> before loading any vimrc\n"));
mch_msg(_(" -c <command> Execute <command> after loading the first file\n"));
mch_msg(_(" -S <session> Source <session> after loading the first file\n"));
mch_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n"));
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index d25dc7c941..6d386f3599 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -42,8 +42,6 @@ void try_to_free_memory(void)
clear_sb_text();
// Try to save all buffers and release as many blocks as possible
mf_release_all();
- // cleanup recursive lists/dicts
- garbage_collect();
trying_to_free = false;
}
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index 6829e4988c..5097255880 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -19,7 +19,6 @@
#include "nvim/vim.h"
#include "nvim/ascii.h"
-#include "nvim/version.h"
#include "nvim/misc1.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index de575c0234..78df4ea7ea 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -349,6 +349,8 @@ static const struct nv_cmd {
{K_F8, farsi_fkey, 0, 0},
{K_F9, farsi_fkey, 0, 0},
{K_EVENT, nv_event, NV_KEEPREG, 0},
+ {K_FOCUSGAINED, nv_focusgained, NV_KEEPREG, 0},
+ {K_FOCUSLOST, nv_focuslost, NV_KEEPREG, 0},
};
/* Number of commands in nv_cmds[]. */
@@ -4253,8 +4255,13 @@ dozet:
break;
/* "zm": fold more */
- case 'm': if (curwin->w_p_fdl > 0)
- --curwin->w_p_fdl;
+ case 'm':
+ if (curwin->w_p_fdl > 0) {
+ curwin->w_p_fdl -= cap->count1;
+ if (curwin->w_p_fdl < 0) {
+ curwin->w_p_fdl = 0;
+ }
+ }
old_fdl = -1; /* force an update */
curwin->w_p_fen = true;
break;
@@ -4266,7 +4273,14 @@ dozet:
break;
/* "zr": reduce folding */
- case 'r': ++curwin->w_p_fdl;
+ case 'r':
+ curwin->w_p_fdl += cap->count1;
+ {
+ int d = getDeepestNesting();
+ if (curwin->w_p_fdl >= d) {
+ curwin->w_p_fdl = d;
+ }
+ }
break;
/* "zR": open all folds */
@@ -7689,10 +7703,32 @@ static void nv_open(cmdarg_T *cap)
// Handle an arbitrary event in normal mode
static void nv_event(cmdarg_T *cap)
{
+ // Garbage collection should have been executed before blocking for events in
+ // the `os_inchar` in `state_enter`, but we also disable it here in case the
+ // `os_inchar` branch was not executed(!queue_empty(loop.events), which could
+ // have `may_garbage_collect` set to true in `normal_check`).
+ //
+ // That is because here we may run code that calls `os_inchar`
+ // later(`f_confirm` or `get_keystroke` for example), but in these cases it is
+ // not safe to perform garbage collection because there could be unreferenced
+ // lists or dicts being used.
+ may_garbage_collect = false;
queue_process_events(loop.events);
cap->retval |= CA_COMMAND_BUSY; // don't call edit() now
}
+/// Trigger FocusGained event.
+static void nv_focusgained(cmdarg_T *cap)
+{
+ apply_autocmds(EVENT_FOCUSGAINED, NULL, NULL, false, curbuf);
+}
+
+/// Trigger FocusLost event.
+static void nv_focuslost(cmdarg_T *cap)
+{
+ apply_autocmds(EVENT_FOCUSLOST, NULL, NULL, false, curbuf);
+}
+
/*
* Return TRUE when 'mousemodel' is set to "popup" or "popup_setpos".
*/
diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt
index 243ac19b33..6687918df4 100644
--- a/src/nvim/po/CMakeLists.txt
+++ b/src/nvim/po/CMakeLists.txt
@@ -82,7 +82,7 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG)
set(poFile ${CMAKE_CURRENT_SOURCE_DIR}/${name}.po)
add_custom_target(check-po-${name}
- COMMAND $<TARGET_FILE:nvim> -u NONE -n -e -X
+ COMMAND $<TARGET_FILE:nvim> -u NONE -n -e
-S ${CMAKE_CURRENT_SOURCE_DIR}/check.vim
-c "if error == 0 | q | endif" -c cq ${poFile} ||
${CMAKE_COMMAND} -E echo "${name}.po failed the check."
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 4cd422400f..fa356da5b9 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -5761,7 +5761,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
// Bail out quickly when there can't be a match, avoid the overhead of
// win_linetabsize() on long lines.
- if (op != 1 && col > t->state->val) {
+ if (op != 1 && col > t->state->val * (has_mbyte ? MB_MAXBYTES : 1)) {
break;
}
diff --git a/src/nvim/terminal.h b/src/nvim/terminal.h
index 6e0b062fbd..25e609fb68 100644
--- a/src/nvim/terminal.h
+++ b/src/nvim/terminal.h
@@ -18,15 +18,6 @@ typedef struct {
terminal_close_cb close_cb;
} TerminalOptions;
-#define TERMINAL_OPTIONS_INIT ((TerminalOptions) { \
- .data = NULL, \
- .width = 80, \
- .height = 24, \
- .write_cb = NULL, \
- .resize_cb = NULL, \
- .close_cb = NULL \
- })
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "terminal.h.generated.h"
#endif
diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim
index 4cb500292d..afee9d882c 100644
--- a/src/nvim/testdir/test49.vim
+++ b/src/nvim/testdir/test49.vim
@@ -456,7 +456,7 @@ function! ExtraVim(...)
" messing up the user's viminfo file.
let redirect = a:0 ?
\ " -c 'au VimLeave * redir END' -c 'redir\\! >" . a:1 . "'" : ""
- exec "!echo '" . debug_quits . "q' | ../../../build/bin/nvim -u NONE -N -Xes" . redirect .
+ exec "!echo '" . debug_quits . "q' | ../../../build/bin/nvim -u NONE -N -es" . redirect .
\ " -c 'debuggreedy|set viminfo+=nviminfo'" .
\ " -c 'let ExtraVimBegin = " . extra_begin . "'" .
\ " -c 'let ExtraVimResult = \"" . resultfile . "\"'" . breakpoints .
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 654b857301..b41e4d2fba 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -10,6 +10,8 @@
#include "nvim/event/rstream.h"
#define PASTETOGGLE_KEY "<Paste>"
+#define FOCUSGAINED_KEY "<FocusGained>"
+#define FOCUSLOST_KEY "<FocusLost>"
#define KEY_BUFFER_SIZE 0xfff
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -252,6 +254,32 @@ static void timer_cb(TimeWatcher *watcher, void *data)
flush_input(data, true);
}
+/// Handle focus events.
+///
+/// If the upcoming sequence of bytes in the input stream matches either the
+/// escape code for focus gained `<ESC>[I` or focus lost `<ESC>[O` then consume
+/// that sequence and push the appropriate event into the input queue
+///
+/// @param input the input stream
+/// @return true iff handle_focus_event consumed some input
+static bool handle_focus_event(TermInput *input)
+{
+ if (rbuffer_size(input->read_stream.buffer) > 2
+ && (!rbuffer_cmp(input->read_stream.buffer, "\x1b[I", 3)
+ || !rbuffer_cmp(input->read_stream.buffer, "\x1b[O", 3))) {
+ // Advance past the sequence
+ bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I';
+ rbuffer_consumed(input->read_stream.buffer, 3);
+ if (focus_gained) {
+ enqueue_input(input, FOCUSGAINED_KEY, sizeof(FOCUSGAINED_KEY) - 1);
+ } else {
+ enqueue_input(input, FOCUSLOST_KEY, sizeof(FOCUSLOST_KEY) - 1);
+ }
+ return true;
+ }
+ return false;
+}
+
static bool handle_bracketed_paste(TermInput *input)
{
if (rbuffer_size(input->read_stream.buffer) > 5 &&
@@ -314,7 +342,9 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data,
}
do {
- if (handle_bracketed_paste(input) || handle_forced_escape(input)) {
+ if (handle_focus_event(input)
+ || handle_bracketed_paste(input)
+ || handle_forced_escape(input)) {
continue;
}
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 02efa1f8df..7f7d138358 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -66,6 +66,7 @@ typedef struct {
int enable_bracketed_paste, disable_bracketed_paste;
int enter_insert_mode, enter_replace_mode, exit_insert_mode;
int set_rgb_foreground, set_rgb_background;
+ int enable_focus_reporting, disable_focus_reporting;
} unibi_ext;
} TUIData;
@@ -120,6 +121,8 @@ static void terminfo_start(UI *ui)
data->unibi_ext.enter_insert_mode = -1;
data->unibi_ext.enter_replace_mode = -1;
data->unibi_ext.exit_insert_mode = -1;
+ data->unibi_ext.enable_focus_reporting = -1;
+ data->unibi_ext.disable_focus_reporting = -1;
data->out_fd = 1;
data->out_isatty = os_isatty(data->out_fd);
// setup unibilium
@@ -135,6 +138,8 @@ static void terminfo_start(UI *ui)
unibi_out(ui, unibi_clear_screen);
// Enable bracketed paste
unibi_out(ui, data->unibi_ext.enable_bracketed_paste);
+ // Enable focus reporting
+ unibi_out(ui, data->unibi_ext.enable_focus_reporting);
uv_loop_init(&data->write_loop);
if (data->out_isatty) {
uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0);
@@ -157,6 +162,8 @@ static void terminfo_stop(UI *ui)
unibi_out(ui, unibi_exit_ca_mode);
// Disable bracketed paste
unibi_out(ui, data->unibi_ext.disable_bracketed_paste);
+ // Disable focus reporting
+ unibi_out(ui, data->unibi_ext.disable_focus_reporting);
flush_buf(ui);
uv_tty_reset_mode();
uv_close((uv_handle_t *)&data->output_handle, NULL);
@@ -807,6 +814,11 @@ static void fix_terminfo(TUIData *data)
data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL,
"\x1b[?2004l");
+ data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, NULL,
+ "\x1b[?1004h");
+ data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, NULL,
+ "\x1b[?1004l");
+
#define XTERM_SETAF \
"\x1b[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m"
#define XTERM_SETAB \
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index d8158bf7cd..b0c49cbf8e 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -650,7 +650,7 @@ void u_compute_hash(char_u *hash)
///
/// @return [allocated] File name to read from/write to or NULL.
char *u_get_undo_file_name(const char *const buf_ffname, const bool reading)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
char *dirp;
char dir_name[MAXPATHL + 1];
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 7cc72705b6..d5bbd734f4 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -25,18 +25,21 @@
#define STR_(x) #x
#define STR(x) STR_(x)
-// for the startup-screen ( ":intro" command )
-#define NVIM_VERSION_MEDIUM STR(NVIM_VERSION_MAJOR) "." STR(NVIM_VERSION_MINOR)
-
-// for the ":version" command and "nvim --version"
-#define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM "." STR(NVIM_VERSION_PATCH) NVIM_VERSION_PRERELEASE NVIM_VERSION_BUILD
+// for ":version", ":intro", and "nvim --version"
+#ifndef NVIM_VERSION_MEDIUM
+#define NVIM_VERSION_MEDIUM STR(NVIM_VERSION_MAJOR) "." STR(NVIM_VERSION_MINOR)\
+ "." STR(NVIM_VERSION_PATCH) NVIM_VERSION_PRERELEASE
+#endif
+#define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM
char *Version = VIM_VERSION_SHORT;
char *longVersion = NVIM_VERSION_LONG;
-char *longVersionWithDate = NVIM_VERSION_LONG " (compiled " __DATE__ " " __TIME__ ")";
-char *mediumVersion = NVIM_VERSION_MEDIUM;
+char *longVersionWithDate = NVIM_VERSION_LONG \
+ " (compiled " __DATE__ " " __TIME__ ")";
+#ifdef NVIM_VERSION_COMMIT
char *version_commit = "Commit: " NVIM_VERSION_COMMIT;
+#endif
char *version_buildtype = "Build type: " NVIM_VERSION_BUILD_TYPE;
char *version_cflags = "Compilation: " NVIM_VERSION_CFLAGS;
@@ -71,20 +74,92 @@ static char *features[] = {
// clang-format off
static int included_patches[] = {
- // 850,
- 849,
+ // 922,
+ // 921 NA
+ // 920 NA
+ // 919 NA
+ // 918 NA
+ // 917 NA
+ 916,
+ // 915,
+ // 914,
+ // 913 NA
+ // 912,
+ // 911 NA
+ // 910 NA
+ // 909,
+ // 908 NA
+ // 907 NA
+ // 906 NA
+ // 905,
+ // 904,
+ // 903,
+ // 902 NA
+ // 901,
+ // 900 NA
+ // 899 NA
+ // 898,
+ // 897,
+ // 896,
+ // 895,
+ // 894 NA
+ // 893,
+ // 892,
+ // 891,
+ // 890 NA
+ // 889,
+ // 888,
+ // 887,
+ // 886 NA
+ // 885,
+ // 884 NA
+ // 883,
+ // 882,
+ // 881,
+ // 880 NA
+ // 879,
+ // 878,
+ // 877,
+ // 876 NA
+ // 875 NA
+ // 874 NA
+ // 873,
+ // 872 NA
+ // 871,
+ // 870,
+ // 869 NA
+ // 868,
+ // 867 NA
+ // 866,
+ // 865,
+ // 864,
+ // 863,
+ // 862 NA
+ // 861 NA
+ // 860,
+ // 859,
+ // 858,
+ // 857,
+ // 856,
+ // 855 NA
+ // 854,
+ // 853,
+ // 852 NA
+ // 851 NA
+ // 850 NA
+ 849,
// 848,
// 847,
- // 846,
+ // 846 NA
// 845,
// 844,
// 843,
// 842,
// 841,
- // 840,
+ // 840 NA
// 839,
// 838,
- // 837,
+ // 837 NA
836,
// 835,
// 834,
@@ -92,7 +167,7 @@ static int included_patches[] = {
// 832,
// 831,
// 830,
- // 829,
+ // 829 NA
// 828,
// 827,
826,
@@ -112,14 +187,14 @@ static int included_patches[] = {
// 812,
// 811,
// 810,
- // 809,
+ 809,
// 808,
// 807,
// 806,
// 805,
// 804,
// 803,
- // 802,
+ 802,
// 801,
// 800,
799,
@@ -130,7 +205,7 @@ static int included_patches[] = {
// 794 NA
793,
// 792,
- // 791,
+ 791,
// 790,
// 789,
// 788 NA
@@ -149,35 +224,35 @@ static int included_patches[] = {
775,
774,
773,
- // 772,
+ // 772 NA
// 771,
- // 770,
+ // 770 NA
// 769,
// 768,
// 767,
- // 766,
+ // 766 NA
// 765,
// 764,
- // 763,
- // 762,
- // 761,
+ // 763 NA
+ // 762 NA
+ // 761 NA
// 760,
- // 759,
+ // 759 NA
// 758,
- // 757,
- // 756,
+ // 757 NA
+ // 756 NA
// 755,
// 754,
// 753,
// 752,
- // 751,
- // 750,
+ // 751 NA
+ // 750 NA
// 749,
// 748,
// 747,
// 746,
// 745,
- // 744,
+ // 744 NA
// 743,
// 742,
// 741,
@@ -235,7 +310,7 @@ static int included_patches[] = {
// 689,
// 688,
// 687 NA
- // 686,
+ 686,
// 685,
// 684,
// 683 NA
@@ -269,8 +344,8 @@ static int included_patches[] = {
// 655,
// 654,
653,
- // 652,
- // 651,
+ // 652 NA
+ 651,
// 650 NA
// 649,
// 648 NA
@@ -1024,7 +1099,9 @@ void list_version(void)
// When adding features here, don't forget to update the list of
// internal variables in eval.c!
MSG(longVersionWithDate);
+#ifdef NVIM_VERSION_COMMIT
MSG(version_commit);
+#endif
MSG(version_buildtype);
MSG(version_cflags);
diff --git a/src/nvim/version.h b/src/nvim/version.h
index c1881250f1..1de809e539 100644
--- a/src/nvim/version.h
+++ b/src/nvim/version.h
@@ -3,7 +3,6 @@
// defined in version.c
extern char* Version;
-extern char* mediumVersion;
extern char* longVersion;
//