diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/auevents.lua | 8 | ||||
-rw-r--r-- | src/nvim/buffer.c | 1004 | ||||
-rw-r--r-- | src/nvim/edit.c | 8 | ||||
-rw-r--r-- | src/nvim/eval.c | 532 | ||||
-rw-r--r-- | src/nvim/eval.h | 41 | ||||
-rw-r--r-- | src/nvim/eval_defs.h | 2 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 1 | ||||
-rw-r--r-- | src/nvim/ex_cmds.lua | 34 | ||||
-rw-r--r-- | src/nvim/ex_cmds2.c | 95 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 51 | ||||
-rw-r--r-- | src/nvim/globals.h | 2 | ||||
-rw-r--r-- | src/nvim/keymap.c | 2 | ||||
-rw-r--r-- | src/nvim/log.h | 2 | ||||
-rw-r--r-- | src/nvim/main.c | 7 | ||||
-rw-r--r-- | src/nvim/memory.c | 2 | ||||
-rw-r--r-- | src/nvim/misc1.c | 1 | ||||
-rw-r--r-- | src/nvim/normal.c | 42 | ||||
-rw-r--r-- | src/nvim/po/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/nvim/regexp_nfa.c | 2 | ||||
-rw-r--r-- | src/nvim/terminal.h | 9 | ||||
-rw-r--r-- | src/nvim/testdir/test49.vim | 2 | ||||
-rw-r--r-- | src/nvim/tui/input.c | 32 | ||||
-rw-r--r-- | src/nvim/tui/tui.c | 12 | ||||
-rw-r--r-- | src/nvim/undo.c | 2 | ||||
-rw-r--r-- | src/nvim/version.c | 139 | ||||
-rw-r--r-- | src/nvim/version.h | 1 |
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 ¤t_funccal->l_vars.dv_hashtab; /* l: variable */ + *d = current_funccal ? ¤t_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 ¤t_funccal->l_avars.dv_hashtab; - if (*name == 'l' && current_funccal != NULL) /* local function variable */ - return ¤t_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 = ¤t_funccal->l_avars; + } else if (*name == 'l' && current_funccal != NULL) { // local variable + *d = ¤t_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(®match, 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; // |