diff options
36 files changed, 654 insertions, 276 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 201bb07cd2..392047b565 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2468,7 +2468,8 @@ tolower({expr}) String the String {expr} switched to lowercase toupper({expr}) String the String {expr} switched to uppercase tr({src}, {fromstr}, {tostr}) String translate chars of {src} in {fromstr} to chars in {tostr} -trim({text} [, {mask}]) String trim characters in {mask} from {text} +trim({text} [, {mask} [, {dir}]]) + String trim characters in {mask} from {text} trunc({expr}) Float truncate Float {expr} type({name}) Number type of variable {name} undofile({name}) String undo file name for {name} @@ -9048,21 +9049,28 @@ tr({src}, {fromstr}, {tostr}) *tr()* echo tr("<blob>", "<>", "{}") < returns "{blob}" -trim({text} [, {mask}]) *trim()* +trim({text} [, {mask} [, {dir}]]) *trim()* Return {text} as a String where any character in {mask} is - removed from the beginning and end of {text}. + removed from the beginning and/or end of {text}. If {mask} is not given, {mask} is all characters up to 0x20, which includes Tab, space, NL and CR, plus the non-breaking space character 0xa0. - This code deals with multibyte characters properly. - + The optional {dir} argument specifies where to remove the + characters: + 0 remove from the beginning and end of {text} + 1 remove only at the beginning of {text} + 2 remove only at the end of {text} + When omitted both ends are trimmed. + This function deals with multibyte characters properly. Examples: > echo trim(" some text ") < returns "some text" > echo trim(" \r\t\t\r RESERVE \t\n\x0B\xA0") . "_TAIL" < returns "RESERVE_TAIL" > echo trim("rm<Xrm<>X>rrm", "rm<>") -< returns "Xrm<>X" (characters in the middle are not removed) +< returns "Xrm<>X" (characters in the middle are not removed) > + echo trim(" vim ", " ", 2) +< returns " vim" trunc({expr}) *trunc()* Return the largest integral value with magnitude less than or diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 53f88dea7d..b5f171a985 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -807,7 +807,7 @@ function M.fancy_floating_markdown(contents, opts) h.start = h.start + i - 1 h.finish = h.finish + i - 1 if h.finish + 1 <= #stripped then - table.insert(stripped, h.finish + 1, string.rep("─", math.min(width, opts.wrap_at))) + table.insert(stripped, h.finish + 1, string.rep("─", math.min(width, opts.wrap_at or width))) height = height + 1 end end diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 5c89c63f7b..995c52e8ed 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -477,48 +477,77 @@ end --- 2. (arg_value, fn, msg) --- - arg_value: argument value --- - fn: any function accepting one argument, returns true if and ---- only if the argument is valid +--- only if the argument is valid. Can optionally return an additional +--- informative error message as the second returned value. --- - msg: (optional) error string if validation fails function vim.validate(opt) end -- luacheck: no unused -vim.validate = (function() + +do local type_names = { - t='table', s='string', n='number', b='boolean', f='function', c='callable', - ['table']='table', ['string']='string', ['number']='number', - ['boolean']='boolean', ['function']='function', ['callable']='callable', - ['nil']='nil', ['thread']='thread', ['userdata']='userdata', + ['table'] = 'table', t = 'table', + ['string'] = 'string', s = 'string', + ['number'] = 'number', n = 'number', + ['boolean'] = 'boolean', b = 'boolean', + ['function'] = 'function', f = 'function', + ['callable'] = 'callable', c = 'callable', + ['nil'] = 'nil', + ['thread'] = 'thread', + ['userdata'] = 'userdata', } - local function _type_name(t) - local tname = type_names[t] - if tname == nil then - error(string.format('invalid type name: %s', tostring(t))) - end - return tname - end + local function _is_type(val, t) return t == 'callable' and vim.is_callable(val) or type(val) == t end - return function(opt) - assert(type(opt) == 'table', string.format('opt: expected table, got %s', type(opt))) + local function is_valid(opt) + if type(opt) ~= 'table' then + return false, string.format('opt: expected table, got %s', type(opt)) + end + for param_name, spec in pairs(opt) do - assert(type(spec) == 'table', string.format('%s: expected table, got %s', param_name, type(spec))) + if type(spec) ~= 'table' then + return false, string.format('opt[%s]: expected table, got %s', param_name, type(spec)) + end local val = spec[1] -- Argument value. local t = spec[2] -- Type name, or callable. local optional = (true == spec[3]) - if not vim.is_callable(t) then -- Check type name. - if (not optional or val ~= nil) and not _is_type(val, _type_name(t)) then - error(string.format("%s: expected %s, got %s", param_name, _type_name(t), type(val))) + if type(t) == 'string' then + local t_name = type_names[t] + if not t_name then + return false, string.format('invalid type name: %s', t) + end + + if (not optional or val ~= nil) and not _is_type(val, t_name) then + return false, string.format("%s: expected %s, got %s", param_name, t_name, type(val)) + end + elseif vim.is_callable(t) then + -- Check user-provided validation function. + local valid, optional_message = t(val) + if not valid then + local error_message = string.format("%s: expected %s, got %s", param_name, (spec[3] or '?'), val) + if optional_message ~= nil then + error_message = error_message .. string.format(". Info: %s", optional_message) + end + + return false, error_message end - elseif not t(val) then -- Check user-provided validation function. - error(string.format("%s: expected %s, got %s", param_name, (spec[3] or '?'), val)) + else + return false, string.format("invalid type name: %s", tostring(t)) end end - return true + + return true, nil end -end)() + function vim.validate(opt) + local ok, err_msg = is_valid(opt) + if not ok then + error(debug.traceback(err_msg, 2), 2) + end + end +end --- Returns true if object `f` can be called as a function. --- --@param f Any object diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 5e5a20e8f2..1223f2bdab 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -91,6 +91,7 @@ typedef struct { #define BF_READERR 0x40 // got errors while reading the file #define BF_DUMMY 0x80 // dummy buffer, only used internally #define BF_PRESERVED 0x100 // ":preserve" was used +#define BF_SYN_SET 0x200 // 'syntax' option was set // Mask to check for flags that prevent normal writing #define BF_WRITE_MASK (BF_NOTEDITED + BF_NEW + BF_READERR) diff --git a/src/nvim/charset.c b/src/nvim/charset.c index f9d5adbc12..fb158f377a 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -509,7 +509,7 @@ char_u* str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) // Does NOT work for multi-byte characters, c must be <= 255. // Also doesn't work for the first byte of a multi-byte, "c" must be a // character! -static char_u transchar_buf[11]; +static char_u transchar_charbuf[11]; /// Translate a character into a printable one, leaving printable ASCII intact /// @@ -520,11 +520,17 @@ static char_u transchar_buf[11]; /// @return translated character into a static buffer. char_u *transchar(int c) { + return transchar_buf(curbuf, c); +} + +char_u *transchar_buf(const buf_T *buf, int c) + FUNC_ATTR_NONNULL_ALL +{ int i = 0; if (IS_SPECIAL(c)) { // special key code, display as ~@ char - transchar_buf[0] = '~'; - transchar_buf[1] = '@'; + transchar_charbuf[0] = '~'; + transchar_charbuf[1] = '@'; i = 2; c = K_SECOND(c); } @@ -532,14 +538,14 @@ char_u *transchar(int c) if ((!chartab_initialized && (((c >= ' ') && (c <= '~')))) || ((c <= 0xFF) && vim_isprintc_strict(c))) { // printable character - transchar_buf[i] = (char_u)c; - transchar_buf[i + 1] = NUL; + transchar_charbuf[i] = (char_u)c; + transchar_charbuf[i + 1] = NUL; } else if (c <= 0xFF) { - transchar_nonprint(transchar_buf + i, c); + transchar_nonprint(buf, transchar_charbuf + i, c); } else { - transchar_hex((char *)transchar_buf + i, c); + transchar_hex((char *)transchar_charbuf + i, c); } - return transchar_buf; + return transchar_charbuf; } /// Like transchar(), but called with a byte instead of a character @@ -548,13 +554,13 @@ char_u *transchar(int c) /// /// @param[in] c Byte to translate. /// -/// @return pointer to translated character in transchar_buf. +/// @return pointer to translated character in transchar_charbuf. char_u *transchar_byte(const int c) FUNC_ATTR_WARN_UNUSED_RESULT { if (c >= 0x80) { - transchar_nonprint(transchar_buf, c); - return transchar_buf; + transchar_nonprint(curbuf, transchar_charbuf, c); + return transchar_charbuf; } return transchar(c); } @@ -563,16 +569,18 @@ char_u *transchar_byte(const int c) /// /// @warning Does not work for multi-byte characters, c must be <= 255. /// -/// @param[out] buf Buffer to store result in, must be able to hold at least -/// 5 bytes (conversion result + NUL). +/// @param[in] buf Required to check the file format +/// @param[out] charbuf Buffer to store result in, must be able to hold +/// at least 5 bytes (conversion result + NUL). /// @param[in] c Character to convert. NUL is assumed to be NL according to /// `:h NL-used-for-NUL`. -void transchar_nonprint(char_u *buf, int c) +void transchar_nonprint(const buf_T *buf, char_u *charbuf, int c) + FUNC_ATTR_NONNULL_ALL { if (c == NL) { // we use newline in place of a NUL c = NUL; - } else if ((c == CAR) && (get_fileformat(curbuf) == EOL_MAC)) { + } else if ((c == CAR) && (get_fileformat(buf) == EOL_MAC)) { // we use CR in place of NL in this case c = NL; } @@ -580,14 +588,14 @@ void transchar_nonprint(char_u *buf, int c) if (dy_flags & DY_UHEX || c > 0x7f) { // 'display' has "uhex" - transchar_hex((char *)buf, c); + transchar_hex((char *)charbuf, c); } else { // 0x00 - 0x1f and 0x7f - buf[0] = '^'; + charbuf[0] = '^'; // DEL displayed as ^? - buf[1] = (char_u)(c ^ 0x40); + charbuf[1] = (char_u)(c ^ 0x40); - buf[2] = NUL; + charbuf[2] = NUL; } } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 372c950825..11f6a350e4 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -371,7 +371,7 @@ return { tolower={args=1}, toupper={args=1}, tr={args=3}, - trim={args={1,2}}, + trim={args={1,3}}, trunc={args=1, func="float_op_wrapper", data="&trunc"}, type={args=1}, undofile={args=1}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 6c3802143f..861743eb7d 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -10823,52 +10823,72 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char_u *prev; const char_u *p; int c1; + int dir = 0; rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; if (head == NULL) { - rettv->vval.v_string = NULL; return; } if (argvars[1].v_type == VAR_STRING) { mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2); + if (argvars[2].v_type != VAR_UNKNOWN) { + bool error = false; + // leading or trailing characters to trim + dir = (int)tv_get_number_chk(&argvars[2], &error); + if (error) { + return; + } + if (dir < 0 || dir > 2) { + emsgf(_(e_invarg2), tv_get_string(&argvars[2])); + return; + } + } } - while (*head != NUL) { - c1 = PTR2CHAR(head); - if (mask == NULL) { - if (c1 > ' ' && c1 != 0xa0) { - break; - } - } else { - for (p = mask; *p != NUL; MB_PTR_ADV(p)) { - if (c1 == PTR2CHAR(p)) { + if (dir == 0 || dir == 1) { + // Trim leading characters + while (*head != NUL) { + c1 = PTR2CHAR(head); + if (mask == NULL) { + if (c1 > ' ' && c1 != 0xa0) { + break; + } + } else { + for (p = mask; *p != NUL; MB_PTR_ADV(p)) { + if (c1 == PTR2CHAR(p)) { + break; + } + } + if (*p == NUL) { break; } } - if (*p == NUL) { - break; - } + MB_PTR_ADV(head); } - MB_PTR_ADV(head); } - for (tail = head + STRLEN(head); tail > head; tail = prev) { - prev = tail; - MB_PTR_BACK(head, prev); - c1 = PTR2CHAR(prev); - if (mask == NULL) { - if (c1 > ' ' && c1 != 0xa0) { - break; - } - } else { - for (p = mask; *p != NUL; MB_PTR_ADV(p)) { - if (c1 == PTR2CHAR(p)) { + tail = head + STRLEN(head); + if (dir == 0 || dir == 2) { + // Trim trailing characters + for (; tail > head; tail = prev) { + prev = tail; + MB_PTR_BACK(head, prev); + c1 = PTR2CHAR(prev); + if (mask == NULL) { + if (c1 > ' ' && c1 != 0xa0) { + break; + } + } else { + for (p = mask; *p != NUL; MB_PTR_ADV(p)) { + if (c1 == PTR2CHAR(p)) { + break; + } + } + if (*p == NUL) { break; } - } - if (*p == NUL) { - break; } } } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index bb4e92efc0..3669cbbd2d 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -135,7 +135,7 @@ void do_ascii(const exarg_T *const eap) char buf1[20]; if (vim_isprintc_strict(c) && (c < ' ' || c > '~')) { char_u buf3[7]; - transchar_nonprint(buf3, c); + transchar_nonprint(curbuf, buf3, c); vim_snprintf(buf1, sizeof(buf1), " <%s>", (char *)buf3); } else { buf1[0] = NUL; @@ -2240,11 +2240,9 @@ int do_ecmd( goto theend; } - /* - * if the file was changed we may not be allowed to abandon it - * - if we are going to re-edit the same file - * - or if we are the only window on this file and if ECMD_HIDE is FALSE - */ + // If the file was changed we may not be allowed to abandon it: + // - if we are going to re-edit the same file + // - or if we are the only window on this file and if ECMD_HIDE is FALSE if ( ((!other_file && !(flags & ECMD_OLDBUF)) || (curbuf->b_nwindows == 1 && !(flags & (ECMD_HIDE | ECMD_ADDBUF)))) diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 3e169f7a4e..503fd8e0d0 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2058,6 +2058,10 @@ void ex_listdo(exarg_T *eap) // Don't do syntax HL autocommands. Skipping the syntax file is a // great speed improvement. save_ei = au_event_disable(",Syntax"); + + FOR_ALL_BUFFERS(buf) { + buf->b_flags &= ~BF_SYN_SET; + } } if (eap->cmdidx == CMD_windo @@ -2252,9 +2256,32 @@ void ex_listdo(exarg_T *eap) } if (save_ei != NULL) { + buf_T *bnext; + aco_save_T aco; + au_event_restore(save_ei); - apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, - curbuf->b_fname, true, curbuf); + + for (buf_T *buf = firstbuf; buf != NULL; buf = bnext) { + bnext = buf->b_next; + if (buf->b_nwindows > 0 && (buf->b_flags & BF_SYN_SET)) { + buf->b_flags &= ~BF_SYN_SET; + + // buffer was opened while Syntax autocommands were disabled, + // need to trigger them now. + if (buf == curbuf) { + apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, + curbuf->b_fname, true, curbuf); + } else { + aucmd_prepbuf(&aco, buf); + apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, + buf->b_fname, true, buf); + aucmd_restbuf(&aco); + } + + // start over, in case autocommands messed things up. + bnext = firstbuf; + } + } } } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index b4c634292c..71ac542323 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7138,14 +7138,14 @@ static void ex_resize(exarg_T *eap) n = atol((char *)eap->arg); if (cmdmod.split & WSP_VERT) { if (*eap->arg == '-' || *eap->arg == '+') { - n += curwin->w_width; + n += wp->w_width; } else if (n == 0 && eap->arg[0] == NUL) { // default is very wide n = Columns; } win_setwidth_win(n, wp); } else { if (*eap->arg == '-' || *eap->arg == '+') { - n += curwin->w_height; + n += wp->w_height; } else if (n == 0 && eap->arg[0] == NUL) { // default is very high n = Rows-1; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 1b4a89fb6c..ad856b588a 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3593,7 +3593,7 @@ restore_backup: * the backup file our 'original' file. */ if (*p_pm && dobackup) { - char *org = modname((char *)fname, (char *)p_pm, FALSE); + char *const org = modname((char *)fname, (char *)p_pm, false); if (backup != NULL) { /* diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index cbd9582f8b..456979be00 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -4204,7 +4204,6 @@ int put_escstr(FILE *fd, char_u *strstart, int what) { char_u *str = strstart; int c; - int modifiers; // :map xx <Nop> if (*str == NUL && what == 1) { @@ -4231,7 +4230,7 @@ int put_escstr(FILE *fd, char_u *strstart, int what) * when they are read back. */ if (c == K_SPECIAL && what != 2) { - modifiers = 0x0; + int modifiers = 0; if (str[1] == KS_MODIFIER) { modifiers = str[2]; str += 3; diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index e67be60aa6..6d188c6cd0 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -512,7 +512,7 @@ int utf_ptr2cells(const char_u *p) { int c; - /* Need to convert to a wide character. */ + // Need to convert to a character number. if (*p >= 0x80) { c = utf_ptr2char(p); /* An illegal byte is displayed as <xx>. */ @@ -582,7 +582,7 @@ size_t mb_string2cells_len(const char_u *str, size_t size) return clen; } -/// Convert a UTF-8 byte sequence to a wide character +/// Convert a UTF-8 byte sequence to a character number. /// /// If the sequence is illegal or truncated by a NUL then the first byte is /// returned. diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 2a75f13cc2..57ed0d6588 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1360,7 +1360,7 @@ recover_names ( * Try finding a swap file by simply adding ".swp" to the file name. */ if (*dirp == NUL && file_count + num_files == 0 && fname != NULL) { - char_u *swapname = (char_u *)modname((char *)fname_res, ".swp", TRUE); + char_u *swapname = (char_u *)modname((char *)fname_res, ".swp", true); if (swapname != NULL) { if (os_path_exists(swapname)) { files = xmalloc(sizeof(char_u *)); @@ -1638,10 +1638,11 @@ static int recov_file_names(char_u **names, char_u *path, int prepend_dot) // May also add the file name with a dot prepended, for swap file in same // dir as original file. if (prepend_dot) { - names[num_names] = (char_u *)modname((char *)path, ".sw?", TRUE); - if (names[num_names] == NULL) + names[num_names] = (char_u *)modname((char *)path, ".sw?", true); + if (names[num_names] == NULL) { return num_names; - ++num_names; + } + num_names++; } // Form the normal swap file name pattern by appending ".sw?". @@ -2422,17 +2423,17 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy) return ml_replace_buf(curbuf, lnum, line, copy); } -/* - * Replace line lnum, with buffering, in current buffer. - * - * If "copy" is TRUE, make a copy of the line, otherwise the line has been - * copied to allocated memory already. - * - * Check: The caller of this function should probably also call - * changed_lines(), unless update_screen(NOT_VALID) is used. - * - * return FAIL for failure, OK otherwise - */ +// Replace line "lnum", with buffering, in current buffer. +// +// If "copy" is true, make a copy of the line, otherwise the line has been +// copied to allocated memory already. +// If "copy" is false the "line" may be freed to add text properties! +// Do not use it after calling ml_replace(). +// +// Check: The caller of this function should probably also call +// changed_lines(), unless update_screen(NOT_VALID) is used. +// +// return FAIL for failure, OK otherwise int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy) { if (line == NULL) /* just checking... */ @@ -3193,6 +3194,12 @@ char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name char_u *fname_res = fname; #ifdef HAVE_READLINK char_u fname_buf[MAXPATHL]; + + // Expand symlink in the file name, so that we put the swap file with the + // actual file instead of with the symlink. + if (resolve_symlink(fname, fname_buf) == OK) { + fname_res = fname_buf; + } #endif int len = (int)STRLEN(dir_name); @@ -3201,20 +3208,14 @@ char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name && len > 1 && s[-1] == s[-2]) { // Ends with '//', Use Full path r = NULL; - if ((s = (char_u *)make_percent_swname((char *)dir_name, (char *)fname)) != NULL) { - r = (char_u *)modname((char *)s, ".swp", FALSE); + s = (char_u *)make_percent_swname((char *)dir_name, (char *)fname_res); + if (s != NULL) { + r = (char_u *)modname((char *)s, ".swp", false); xfree(s); } return r; } -#ifdef HAVE_READLINK - /* Expand symlink in the file name, so that we put the swap file with the - * actual file instead of with the symlink. */ - if (resolve_symlink(fname, fname_buf) == OK) - fname_res = fname_buf; -#endif - // Prepend a '.' to the swap file name for the current directory. r = (char_u *)modname((char *)fname_res, ".swp", dir_name[0] == '.' && dir_name[1] == NUL); diff --git a/src/nvim/message.c b/src/nvim/message.c index e4f47917ec..6cd5616acf 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1621,7 +1621,7 @@ const char *str2special(const char **const sp, const bool replace_spaces, // Check for an illegal byte. if (MB_BYTE2LEN((uint8_t)(*str)) > len) { - transchar_nonprint((char_u *)buf, c); + transchar_nonprint(curbuf, (char_u *)buf, c); *sp = str + 1; return buf; } @@ -1886,6 +1886,7 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr) // wait-return prompt later. Needed when scrolling, resetting // need_wait_return after some prompt, and then outputting something // without scrolling + // Not needed when only using CR to move the cursor. bool overflow = false; if (ui_has(kUIMessages)) { int count = msg_ext_visible + (msg_ext_overwrite ? 0 : 1); @@ -1897,7 +1898,7 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr) overflow = msg_scrolled != 0; } - if (overflow && !msg_scrolled_ign) { + if (overflow && !msg_scrolled_ign && strcmp(str, "\r") != 0) { need_wait_return = true; } msg_didany = true; // remember that something was outputted @@ -1999,7 +2000,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, || (*s == TAB && msg_col <= 7) || (utf_ptr2cells(s) > 1 && msg_col <= 2)) - : (msg_col + t_col >= Columns - 1 + : ((*s != '\r' && msg_col + t_col >= Columns - 1) || (*s == TAB && msg_col + t_col >= ((Columns - 1) & ~7)) || (utf_ptr2cells(s) > 1 diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 6dafbafb3e..1cd9ff2c4d 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -983,7 +983,7 @@ void preserve_exit(void) FOR_ALL_BUFFERS(buf) { if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { - mch_errmsg((uint8_t *)"Vim: preserving files...\n"); + mch_errmsg("Vim: preserving files...\r\n"); ui_flush(); ml_sync_all(false, false, true); // preserve all swap files break; @@ -992,7 +992,7 @@ void preserve_exit(void) ml_close_all(false); // close all memfiles, without deleting - mch_errmsg("Vim: Finished.\n"); + mch_errmsg("Vim: Finished.\r\n"); getout(1); } diff --git a/src/nvim/option.c b/src/nvim/option.c index 6ae9378236..484d9da3a1 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3438,6 +3438,7 @@ ambw_end: // recursively, to avoid endless recurrence. apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname, value_changed || syn_recursive == 1, curbuf); + curbuf->b_flags |= BF_SYN_SET; syn_recursive--; } else if (varp == &(curbuf->b_p_ft)) { // 'filetype' is set, trigger the FileType autocommand @@ -7269,7 +7270,8 @@ unsigned int get_bkc_value(buf_T *buf) } /// Return the current end-of-line type: EOL_DOS, EOL_UNIX or EOL_MAC. -int get_fileformat(buf_T *buf) +int get_fileformat(const buf_T *buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { int c = *buf->b_p_ff; diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index bfe230b521..bc774b8ebc 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -161,8 +161,8 @@ static void deadly_signal(int signum) WLOG("got signal %d (%s)", signum, signal_name(signum)); - snprintf((char *)IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\n", - signal_name(signum)); + snprintf((char *)IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\r\n", + signal_name(signum)); // Preserve files and exit. preserve_exit(); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 5ade59d224..4d3d0f5fdf 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -5068,6 +5068,20 @@ static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy, } } +// Return true if "buf" had an existing swap file, the current swap file does +// not end in ".swp". +static bool existing_swapfile(const buf_T *buf) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { + const char_u *const fname = buf->b_ml.ml_mfp->mf_fname; + const size_t len = STRLEN(fname); + + return fname[len - 1] != 'p' || fname[len - 2] != 'w'; + } + return false; +} + // ":vimgrep {pattern} file(s)" // ":vimgrepadd {pattern} file(s)" // ":lvimgrep {pattern} file(s)" @@ -5225,7 +5239,9 @@ void ex_vimgrep(exarg_T *eap) if (!found_match) { wipe_dummy_buffer(buf, dirname_start); buf = NULL; - } else if (buf != first_match_buf || (flags & VGR_NOJUMP)) { + } else if (buf != first_match_buf + || (flags & VGR_NOJUMP) + || existing_swapfile(buf)) { unload_dummy_buffer(buf, dirname_start); // Keeping the buffer, remove the dummy flag. buf->b_flags &= ~BF_DUMMY; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 1191886888..9f450888a2 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -3634,7 +3634,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, mb_utf8 = false; // don't draw as UTF-8 } } else if (c != NUL) { - p_extra = transchar(c); + p_extra = transchar_buf(wp->w_buffer, c); if (n_extra == 0) { n_extra = byte2cells(c) - 1; } diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 765ba2cbb6..b02514143c 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -78,6 +78,9 @@ set encoding=utf-8 let s:test_script_fname = expand('%') au! SwapExists * call HandleSwapExists() func HandleSwapExists() + if exists('g:ignoreSwapExists') + return + endif " Ignore finding a swap file for the test script (the user might be " editing it and do ":make test_name") and the output file. " Report finding another swap file and chose 'q' to avoid getting stuck. diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index e853b046dc..66b016c8b2 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -185,6 +185,47 @@ func Test_scroll_CursorLineNr_update() call delete(filename) endfunc +" check a long file name does not result in the hit-enter prompt +func Test_edit_long_file_name() + CheckScreendump + + let longName = 'x'->repeat(&columns) + call writefile([], longName) + let buf = RunVimInTerminal('-N -u NONE ' .. longName, #{rows: 8}) + + call VerifyScreenDump(buf, 'Test_long_file_name_1', {}) + + call term_sendkeys(buf, ":q\<cr>") + + " clean up + call StopVimInTerminal(buf) + call delete(longName) +endfunc + +func Test_unprintable_fileformats() + CheckScreendump + + call writefile(["unix\r", "two"], 'Xunix.txt') + call writefile(["mac\r", "two"], 'Xmac.txt') + let lines =<< trim END + edit Xunix.txt + split Xmac.txt + edit ++ff=mac + END + let filename = 'Xunprintable' + call writefile(lines, filename) + let buf = RunVimInTerminal('-S '.filename, #{rows: 9, cols: 50}) + call VerifyScreenDump(buf, 'Test_display_unprintable_01', {}) + call term_sendkeys(buf, "\<C-W>\<C-W>\<C-L>") + call VerifyScreenDump(buf, 'Test_display_unprintable_02', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xunix.txt') + call delete('Xmac.txt') + call delete(filename) +endfunc + " Test for scrolling that modifies buffer during visual block func Test_visual_block_scroll() " See test/functional/legacy/visual_mode_spec.lua diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 8fa70a5313..00f0126582 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1083,6 +1083,12 @@ func Test_trim() call assert_equal("", trim("", "")) call assert_equal("a", trim("a", "")) call assert_equal("", trim("", "a")) + call assert_equal("vim", trim(" vim ", " ", 0)) + call assert_equal("vim ", trim(" vim ", " ", 1)) + call assert_equal(" vim", trim(" vim ", " ", 2)) + call assert_fails('call trim(" vim ", " ", [])', 'E745:') + call assert_fails('call trim(" vim ", " ", -1)', 'E475:') + call assert_fails('call trim(" vim ", " ", 3)', 'E475:') let chars = join(map(range(1, 0x20) + [0xa0], {n -> nr2char(n)}), '') call assert_equal("x", trim(chars . "x" . chars)) diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim index 884ada7e88..15745d5619 100644 --- a/src/nvim/testdir/test_put.vim +++ b/src/nvim/testdir/test_put.vim @@ -22,12 +22,21 @@ endfunc func Test_put_char_block2() new - let a = [ getreg('a'), getregtype('a') ] call setreg('a', ' one ', 'v') call setline(1, ['Line 1', '', 'Line 3', '']) " visually select the first 3 lines and put register a over it exe "norm! ggl\<c-v>2j2l\"ap" - call assert_equal(['L one 1', '', 'L one 3', ''], getline(1,4)) + call assert_equal(['L one 1', '', 'L one 3', ''], getline(1, 4)) + " clean up + bw! +endfunc + +func Test_put_lines() + new + let a = [ getreg('a'), getregtype('a') ] + call setline(1, ['Line 1', 'Line2', 'Line 3', '']) + exe 'norm! gg"add"AddG""p' + call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1, '$')) " clean up bw! call setreg('a', a[0], a[1]) @@ -42,21 +51,10 @@ func Test_put_expr() exec "4norm! \"=\<cr>P" norm! j0. norm! j0. - call assert_equal(['A1','A2','A3','4A','5A','6A'], getline(1,'$')) + call assert_equal(['A1','A2','A3','4A','5A','6A'], getline(1, '$')) bw! endfunc -func Test_put_lines() - new - let a = [ getreg('a'), getregtype('a') ] - call setline(1, ['Line 1', 'Line2', 'Line 3', '']) - exe 'norm! gg"add"AddG""p' - call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1,'$')) - " clean up - bw! - call setreg('a', a[0], a[1]) -endfunc - func Test_put_fails_when_nomodifiable() new setlocal nomodifiable diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index f9e79919e9..d46233991c 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1,8 +1,7 @@ " Test for the quickfix commands. -if !has('quickfix') - finish -endif +source check.vim +CheckFeature quickfix set encoding=utf-8 @@ -2568,6 +2567,41 @@ func Test_vimgrep_incsearch() set noincsearch endfunc +" Test vimgrep without swap file +func Test_vimgrep_without_swap_file() + let lines =<< trim [SCRIPT] + vimgrep grep test_c* + call writefile(['done'], 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -n -S Xscript Xscript') + call assert_equal(['done'], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + +func Test_vimgrep_existing_swapfile() + call writefile(['match apple with apple'], 'Xapple') + call writefile(['swapfile'], '.Xapple.swp') + let g:foundSwap = 0 + let g:ignoreSwapExists = 1 + augroup grep + au SwapExists * let foundSwap = 1 | let v:swapchoice = 'e' + augroup END + vimgrep apple Xapple + call assert_equal(1, g:foundSwap) + call assert_match('.Xapple.swo', swapname('')) + + call delete('Xapple') + call delete('Xapple.swp') + augroup grep + au! SwapExists + augroup END + unlet g:ignoreSwapExists +endfunc + func XfreeTests(cchar) call s:setup_commands(a:cchar) diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim index e072e9ed7f..f27920d20f 100644 --- a/src/nvim/testdir/test_swap.vim +++ b/src/nvim/testdir/test_swap.vim @@ -1,5 +1,7 @@ " Tests for the swap feature +source check.vim + func s:swapname() return trim(execute('swapname')) endfunc @@ -305,3 +307,61 @@ func Test_swap_recover_ext() augroup END augroup! test_swap_recover_ext endfunc + +" Test for selecting 'q' in the attention prompt +func Test_swap_prompt_splitwin() + CheckRunVimInTerminal + + call writefile(['foo bar'], 'Xfile1') + edit Xfile1 + preserve " should help to make sure the swap file exists + + let buf = RunVimInTerminal('', {'rows': 20}) + call term_sendkeys(buf, ":set nomore\n") + call term_sendkeys(buf, ":set noruler\n") + call term_sendkeys(buf, ":split Xfile1\n") + call term_wait(buf) + call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: $', term_getline(buf, 20))}) + call term_sendkeys(buf, "q") + call term_wait(buf) + call term_sendkeys(buf, ":\<CR>") + call WaitForAssert({-> assert_match('^:$', term_getline(buf, 20))}) + call term_sendkeys(buf, ":echomsg winnr('$')\<CR>") + call term_wait(buf) + call WaitForAssert({-> assert_match('^1$', term_getline(buf, 20))}) + call StopVimInTerminal(buf) + %bwipe! + call delete('Xfile1') +endfunc + +func Test_swap_symlink() + if !has("unix") + return + endif + + call writefile(['text'], 'Xtestfile') + silent !ln -s -f Xtestfile Xtestlink + + set dir=. + + " Test that swap file uses the name of the file when editing through a + " symbolic link (so that editing the file twice is detected) + edit Xtestlink + call assert_match('Xtestfile\.swp$', s:swapname()) + bwipe! + + call mkdir('Xswapdir') + exe 'set dir=' . getcwd() . '/Xswapdir//' + + " Check that this also works when 'directory' ends with '//' + edit Xtestlink + call assert_match('Xtestfile\.swp$', s:swapname()) + bwipe! + + set dir& + call delete('Xtestfile') + call delete('Xtestlink') + call delete('Xswapdir', 'rf') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 2404f113d9..2617aa3945 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -475,6 +475,40 @@ func Test_bg_detection() hi Normal ctermbg=NONE endfunc +func Test_syntax_hangs() + if !has('reltime') || !has('float') || !has('syntax') + return + endif + + " This pattern takes a long time to match, it should timeout. + new + call setline(1, ['aaa', repeat('abc ', 1000), 'ccc']) + let start = reltime() + set nolazyredraw redrawtime=101 + syn match Error /\%#=1a*.*X\@<=b*/ + redraw + let elapsed = reltimefloat(reltime(start)) + call assert_true(elapsed > 0.1) + call assert_true(elapsed < 1.0) + + " second time syntax HL is disabled + let start = reltime() + redraw + let elapsed = reltimefloat(reltime(start)) + call assert_true(elapsed < 0.1) + + " after CTRL-L the timeout flag is reset + let start = reltime() + exe "normal \<C-L>" + redraw + let elapsed = reltimefloat(reltime(start)) + call assert_true(elapsed > 0.1) + call assert_true(elapsed < 1.0) + + set redrawtime& + bwipe! +endfunc + func Test_synstack_synIDtrans() new setfiletype c @@ -550,38 +584,42 @@ func Test_syn_wrong_z_one() bwipe! endfunc -func Test_syntax_hangs() - if !has('reltime') || !has('float') || !has('syntax') - return - endif +func Test_syntax_after_bufdo() + call writefile(['/* aaa comment */'], 'Xaaa.c') + call writefile(['/* bbb comment */'], 'Xbbb.c') + call writefile(['/* ccc comment */'], 'Xccc.c') + call writefile(['/* ddd comment */'], 'Xddd.c') + + let bnr = bufnr('%') + new Xaaa.c + badd Xbbb.c + badd Xccc.c + badd Xddd.c + exe "bwipe " . bnr + let l = [] + bufdo call add(l, bufnr('%')) + call assert_equal(4, len(l)) - " This pattern takes a long time to match, it should timeout. - new - call setline(1, ['aaa', repeat('abc ', 1000), 'ccc']) - let start = reltime() - set nolazyredraw redrawtime=101 - syn match Error /\%#=1a*.*X\@<=b*/ - redraw - let elapsed = reltimefloat(reltime(start)) - call assert_true(elapsed > 0.1) - call assert_true(elapsed < 1.0) - - " second time syntax HL is disabled - let start = reltime() - redraw - let elapsed = reltimefloat(reltime(start)) - call assert_true(elapsed < 0.1) - - " after CTRL-L the timeout flag is reset - let start = reltime() - exe "normal \<C-L>" - redraw - let elapsed = reltimefloat(reltime(start)) - call assert_true(elapsed > 0.1) - call assert_true(elapsed < 1.0) + syntax on - set redrawtime& - bwipe! + " This used to only enable syntax HL in the last buffer. + bufdo tab split + tabrewind + for tab in range(1, 4) + norm fm + call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) + tabnext + endfor + + bwipe! Xaaa.c + bwipe! Xbbb.c + bwipe! Xccc.c + bwipe! Xddd.c + syntax off + call delete('Xaaa.c') + call delete('Xbbb.c') + call delete('Xccc.c') + call delete('Xddd.c') endfunc func Test_syntax_foldlevel() diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 9f47ee2904..500e3ff088 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -879,6 +879,10 @@ func Test_window_resize() exe other_winnr .. 'resize 10' call assert_equal(10, winheight(other_winnr)) call assert_equal(&lines - 10 - 3, winheight(0)) + exe other_winnr .. 'resize +1' + exe other_winnr .. 'resize +1' + call assert_equal(12, winheight(other_winnr)) + call assert_equal(&lines - 10 - 3 -2, winheight(0)) %bwipe! endfunc diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index f35508fd36..0b52d06df9 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -449,19 +449,19 @@ describe('API', function() end) it('reports errors', function() - eq([[Error loading lua: [string "<nvim>"]:1: '=' expected near '+']], + eq([[Error loading lua: [string "<nvim>"]:0: '=' expected near '+']], pcall_err(meths.exec_lua, 'a+*b', {})) - eq([[Error loading lua: [string "<nvim>"]:1: unexpected symbol near '1']], + eq([[Error loading lua: [string "<nvim>"]:0: unexpected symbol near '1']], pcall_err(meths.exec_lua, '1+2', {})) - eq([[Error loading lua: [string "<nvim>"]:1: unexpected symbol]], + eq([[Error loading lua: [string "<nvim>"]:0: unexpected symbol]], pcall_err(meths.exec_lua, 'aa=bb\0', {})) - eq([[Error executing lua: [string "<nvim>"]:1: attempt to call global 'bork' (a nil value)]], + eq([[Error executing lua: [string "<nvim>"]:0: attempt to call global 'bork' (a nil value)]], pcall_err(meths.exec_lua, 'bork()', {})) - eq('Error executing lua: [string "<nvim>"]:1: did\nthe\nfail', + eq('Error executing lua: [string "<nvim>"]:0: did\nthe\nfail', pcall_err(meths.exec_lua, 'error("did\\nthe\\nfail")', {})) end) @@ -605,7 +605,7 @@ describe('API', function() end) it('vim.paste() failure', function() nvim('exec_lua', 'vim.paste = (function(lines, phase) error("fake fail") end)', {}) - eq([[Error executing lua: [string "<nvim>"]:1: fake fail]], + eq([[Error executing lua: [string "<nvim>"]:0: fake fail]], pcall_err(request, 'nvim_paste', 'line 1\nline 2\nline 3', false, 1)) end) end) diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua index cbc3aee557..f2a1b7dede 100644 --- a/test/functional/lua/commands_spec.lua +++ b/test/functional/lua/commands_spec.lua @@ -43,7 +43,7 @@ describe(':lua command', function() eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false)) end) it('throws catchable errors', function() - eq([[Vim(lua):E5107: Error loading lua [string ":lua"]:1: unexpected symbol near ')']], + eq([[Vim(lua):E5107: Error loading lua [string ":lua"]:0: unexpected symbol near ')']], pcall_err(command, 'lua ()')) eq([[Vim(lua):E5108: Error executing lua [string ":lua"]:1: TEST]], exc_exec('lua error("TEST")')) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 75966393b1..2ec48777fd 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -477,14 +477,14 @@ describe('v:lua', function() eq(NIL, eval('v:lua.mymod.noisy("eval")')) eq("hey eval", meths.get_current_line()) - eq("Vim:E5108: Error executing lua [string \"<nvim>\"]:10: attempt to call global 'nonexistent' (a nil value)", + eq("Vim:E5108: Error executing lua [string \"<nvim>\"]:0: attempt to call global 'nonexistent' (a nil value)", pcall_err(eval, 'v:lua.mymod.crashy()')) end) it('works in :call', function() command(":call v:lua.mymod.noisy('command')") eq("hey command", meths.get_current_line()) - eq("Vim(call):E5108: Error executing lua [string \"<nvim>\"]:10: attempt to call global 'nonexistent' (a nil value)", + eq("Vim(call):E5108: Error executing lua [string \"<nvim>\"]:0: attempt to call global 'nonexistent' (a nil value)", pcall_err(command, 'call v:lua.mymod.crashy()')) end) diff --git a/test/functional/lua/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua index de2d6456ba..3526b64395 100644 --- a/test/functional/lua/treesitter_spec.lua +++ b/test/functional/lua/treesitter_spec.lua @@ -15,14 +15,14 @@ before_each(clear) describe('treesitter API', function() -- error tests not requiring a parser library it('handles missing language', function() - eq("Error executing lua: .../language.lua: no parser for 'borklang' language, see :help treesitter-parsers", + eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", pcall_err(exec_lua, "parser = vim.treesitter.get_parser(0, 'borklang')")) -- actual message depends on platform matches("Error executing lua: Failed to load parser: uv_dlopen: .+", pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')")) - eq("Error executing lua: .../language.lua: no parser for 'borklang' language, see :help treesitter-parsers", + eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')")) end) end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 63e48a18ca..1cbf6b21e3 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -4,13 +4,14 @@ local Screen = require('test.functional.ui.screen') local funcs = helpers.funcs local meths = helpers.meths +local dedent = helpers.dedent local command = helpers.command local clear = helpers.clear local eq = helpers.eq local ok = helpers.ok local eval = helpers.eval local feed = helpers.feed -local pcall_err = helpers.pcall_err +local pcall_err_withfile = helpers.pcall_err_withfile local exec_lua = helpers.exec_lua local matches = helpers.matches local source = helpers.source @@ -128,8 +129,8 @@ describe('lua stdlib', function() eq(false, funcs.luaeval('vim.startswith("123", "2")')) eq(false, funcs.luaeval('vim.startswith("123", "1234")')) - eq("string", type(pcall_err(funcs.luaeval, 'vim.startswith("123", nil)'))) - eq("string", type(pcall_err(funcs.luaeval, 'vim.startswith(nil, "123")'))) + eq("string", type(pcall_err_withfile(funcs.luaeval, 'vim.startswith("123", nil)'))) + eq("string", type(pcall_err_withfile(funcs.luaeval, 'vim.startswith(nil, "123")'))) end) it('vim.endswith', function() @@ -142,8 +143,8 @@ describe('lua stdlib', function() eq(false, funcs.luaeval('vim.endswith("123", "2")')) eq(false, funcs.luaeval('vim.endswith("123", "1234")')) - eq("string", type(pcall_err(funcs.luaeval, 'vim.endswith("123", nil)'))) - eq("string", type(pcall_err(funcs.luaeval, 'vim.endswith(nil, "123")'))) + eq("string", type(pcall_err_withfile(funcs.luaeval, 'vim.endswith("123", nil)'))) + eq("string", type(pcall_err_withfile(funcs.luaeval, 'vim.endswith(nil, "123")'))) end) it("vim.str_utfindex/str_byteindex", function() @@ -182,10 +183,10 @@ describe('lua stdlib', function() eq({"yy","xx"}, exec_lua("return test_table")) -- Validates args. - eq('Error executing lua: vim.schedule: expected function', - pcall_err(exec_lua, "vim.schedule('stringly')")) - eq('Error executing lua: vim.schedule: expected function', - pcall_err(exec_lua, "vim.schedule()")) + eq('.../helpers.lua:0: Error executing lua: vim.schedule: expected function', + pcall_err_withfile(exec_lua, "vim.schedule('stringly')")) + eq('.../helpers.lua:0: Error executing lua: vim.schedule: expected function', + pcall_err_withfile(exec_lua, "vim.schedule()")) exec_lua([[ vim.schedule(function() @@ -257,17 +258,29 @@ describe('lua stdlib', function() } for _, t in ipairs(loops) do - matches(".*Infinite loop detected", pcall_err(split, t[1], t[2])) + matches(".*Infinite loop detected", pcall_err_withfile(split, t[1], t[2])) end -- Validates args. eq(true, pcall(split, 'string', 'string')) - eq('Error executing lua: .../shared.lua: s: expected string, got number', - pcall_err(split, 1, 'string')) - eq('Error executing lua: .../shared.lua: sep: expected string, got number', - pcall_err(split, 'string', 1)) - eq('Error executing lua: .../shared.lua: plain: expected boolean, got number', - pcall_err(split, 'string', 'string', 1)) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: shared.lua:0: s: expected string, got number + stack traceback: + shared.lua:0: in function 'gsplit' + shared.lua:0: in function <shared.lua:0>]]), + pcall_err_withfile(split, 1, 'string')) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: shared.lua:0: sep: expected string, got number + stack traceback: + shared.lua:0: in function 'gsplit' + shared.lua:0: in function <shared.lua:0>]]), + pcall_err_withfile(split, 'string', 1)) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: shared.lua:0: plain: expected boolean, got number + stack traceback: + shared.lua:0: in function 'gsplit' + shared.lua:0: in function <shared.lua:0>]]), + pcall_err_withfile(split, 'string', 'string', 1)) end) it('vim.trim', function() @@ -287,8 +300,11 @@ describe('lua stdlib', function() end -- Validates args. - eq('Error executing lua: .../shared.lua: s: expected string, got number', - pcall_err(trim, 2)) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: shared.lua:0: s: expected string, got number + stack traceback: + shared.lua:0: in function <shared.lua:0>]]), + pcall_err_withfile(trim, 2)) end) it('vim.inspect', function() @@ -353,8 +369,8 @@ describe('lua stdlib', function() return t1.f() ~= t2.f() ]])) - eq('Error executing lua: .../shared.lua: Cannot deepcopy object of type thread', - pcall_err(exec_lua, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: Cannot deepcopy object of type thread', + pcall_err_withfile(exec_lua, [[ local thread = coroutine.create(function () return 0 end) local t = {thr = thread} vim.deepcopy(t) @@ -366,8 +382,11 @@ describe('lua stdlib', function() eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]])) -- Validates args. - eq('Error executing lua: .../shared.lua: s: expected string, got number', - pcall_err(exec_lua, [[return vim.pesc(2)]])) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: shared.lua:0: s: expected string, got number + stack traceback: + shared.lua:0: in function <shared.lua:0>]]), + pcall_err_withfile(exec_lua, [[return vim.pesc(2)]])) end) it('vim.tbl_keys', function() @@ -491,20 +510,20 @@ describe('lua stdlib', function() return c.x.a == 1 and c.x.b == 2 and c.x.c == nil and count == 1 ]])) - eq('Error executing lua: .../shared.lua: invalid "behavior": nil', - pcall_err(exec_lua, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: invalid "behavior": nil', + pcall_err_withfile(exec_lua, [[ return vim.tbl_extend() ]]) ) - eq('Error executing lua: .../shared.lua: wrong number of arguments (given 1, expected at least 3)', - pcall_err(exec_lua, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: wrong number of arguments (given 1, expected at least 3)', + pcall_err_withfile(exec_lua, [[ return vim.tbl_extend("keep") ]]) ) - eq('Error executing lua: .../shared.lua: wrong number of arguments (given 2, expected at least 3)', - pcall_err(exec_lua, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: wrong number of arguments (given 2, expected at least 3)', + pcall_err_withfile(exec_lua, [[ return vim.tbl_extend("keep", {}) ]]) ) @@ -579,20 +598,20 @@ describe('lua stdlib', function() return vim.tbl_islist(c) and count == 0 ]])) - eq('Error executing lua: .../shared.lua: invalid "behavior": nil', - pcall_err(exec_lua, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: invalid "behavior": nil', + pcall_err_withfile(exec_lua, [[ return vim.tbl_deep_extend() ]]) ) - eq('Error executing lua: .../shared.lua: wrong number of arguments (given 1, expected at least 3)', - pcall_err(exec_lua, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: wrong number of arguments (given 1, expected at least 3)', + pcall_err_withfile(exec_lua, [[ return vim.tbl_deep_extend("keep") ]]) ) - eq('Error executing lua: .../shared.lua: wrong number of arguments (given 2, expected at least 3)', - pcall_err(exec_lua, [[ + eq('.../helpers.lua:0: Error executing lua: shared.lua:0: wrong number of arguments (given 2, expected at least 3)', + pcall_err_withfile(exec_lua, [[ return vim.tbl_deep_extend("keep", {}) ]]) ) @@ -624,8 +643,11 @@ describe('lua stdlib', function() it('vim.list_extend', function() eq({1,2,3}, exec_lua [[ return vim.list_extend({1}, {2,3}) ]]) - eq('Error executing lua: .../shared.lua: src: expected table, got nil', - pcall_err(exec_lua, [[ return vim.list_extend({1}, nil) ]])) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: shared.lua:0: src: expected table, got nil + stack traceback: + shared.lua:0: in function <shared.lua:0>]]), + pcall_err_withfile(exec_lua, [[ return vim.list_extend({1}, nil) ]])) eq({1,2}, exec_lua [[ return vim.list_extend({1}, {2;a=1}) ]]) eq(true, exec_lua [[ local a = {1} return vim.list_extend(a, {2;a=1}) == a ]]) eq({2}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 1) ]]) @@ -648,8 +670,8 @@ describe('lua stdlib', function() assert(vim.deep_equal(a, { A = 1; [1] = 'A'; })) vim.tbl_add_reverse_lookup(a) ]] - matches('Error executing lua: .../shared.lua: The reverse lookup found an existing value for "[1A]" while processing key "[1A]"', - pcall_err(exec_lua, code)) + matches('.../helpers.lua:0: Error executing lua: shared.lua:0: The reverse lookup found an existing value for "[1A]" while processing key "[1A]"', + pcall_err_withfile(exec_lua, code)) end) it('vim.call, vim.fn', function() @@ -820,34 +842,77 @@ describe('lua stdlib', function() exec_lua("vim.validate{arg1={{}, 't' }, arg2={ 'foo', 's' }}") exec_lua("vim.validate{arg1={2, function(a) return (a % 2) == 0 end, 'even number' }}") - eq("Error executing lua: .../shared.lua: 1: expected table, got number", - pcall_err(exec_lua, "vim.validate{ 1, 'x' }")) - eq("Error executing lua: .../shared.lua: invalid type name: x", - pcall_err(exec_lua, "vim.validate{ arg1={ 1, 'x' }}")) - eq("Error executing lua: .../shared.lua: invalid type name: 1", - pcall_err(exec_lua, "vim.validate{ arg1={ 1, 1 }}")) - eq("Error executing lua: .../shared.lua: invalid type name: nil", - pcall_err(exec_lua, "vim.validate{ arg1={ 1 }}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: opt[1]: expected table, got number + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{ 1, 'x' }")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: invalid type name: x + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{ arg1={ 1, 'x' }}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: invalid type name: 1 + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{ arg1={ 1, 1 }}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: invalid type name: nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{ arg1={ 1 }}")) -- Validated parameters are required by default. - eq("Error executing lua: .../shared.lua: arg1: expected string, got nil", - pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's' }}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg1: expected string, got nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{ arg1={ nil, 's' }}")) -- Explicitly required. - eq("Error executing lua: .../shared.lua: arg1: expected string, got nil", - pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's', false }}")) - - eq("Error executing lua: .../shared.lua: arg1: expected table, got number", - pcall_err(exec_lua, "vim.validate{arg1={1, 't'}}")) - eq("Error executing lua: .../shared.lua: arg2: expected string, got number", - pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={1, 's'}}")) - eq("Error executing lua: .../shared.lua: arg2: expected string, got nil", - pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}")) - eq("Error executing lua: .../shared.lua: arg2: expected string, got nil", - pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}")) - eq("Error executing lua: .../shared.lua: arg1: expected even number, got 3", - pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end, 'even number'}}")) - eq("Error executing lua: .../shared.lua: arg1: expected ?, got 3", - pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end}}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg1: expected string, got nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{ arg1={ nil, 's', false }}")) + + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg1: expected table, got number + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={1, 't'}}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg2: expected string, got number + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={1, 's'}}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg2: expected string, got nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg2: expected string, got nil + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg1: expected even number, got 3 + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end, 'even number'}}")) + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg1: expected ?, got 3 + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end}}")) + + -- Pass an additional message back. + eq(dedent([[ + .../helpers.lua:0: Error executing lua: [string "<nvim>"]:0: arg1: expected ?, got 3. Info: TEST_MSG + stack traceback: + [string "<nvim>"]:0: in main chunk]]), + pcall_err_withfile(exec_lua, "vim.validate{arg1={3, function(a) return a == 1, 'TEST_MSG' end}}")) end) it('vim.is_callable', function() @@ -992,10 +1057,10 @@ describe('lua stdlib', function() ]] eq('', funcs.luaeval "vim.bo.filetype") eq(true, funcs.luaeval "vim.bo[BUF].modifiable") - matches("^Error executing lua: .*: Invalid option name: 'nosuchopt'$", - pcall_err(exec_lua, 'return vim.bo.nosuchopt')) - matches("^Error executing lua: .*: Expected lua string$", - pcall_err(exec_lua, 'return vim.bo[0][0].autoread')) + matches("^.../helpers.lua:0: Error executing lua: .*: Invalid option name: 'nosuchopt'$", + pcall_err_withfile(exec_lua, 'return vim.bo.nosuchopt')) + matches("^.../helpers.lua:0: Error executing lua: .*: Expected lua string$", + pcall_err_withfile(exec_lua, 'return vim.bo[0][0].autoread')) end) it('vim.wo', function() @@ -1011,10 +1076,10 @@ describe('lua stdlib', function() eq(0, funcs.luaeval "vim.wo.cole") eq(0, funcs.luaeval "vim.wo[0].cole") eq(0, funcs.luaeval "vim.wo[1001].cole") - matches("^Error executing lua: .*: Invalid option name: 'notanopt'$", - pcall_err(exec_lua, 'return vim.wo.notanopt')) - matches("^Error executing lua: .*: Expected lua string$", - pcall_err(exec_lua, 'return vim.wo[0][0].list')) + matches("^.../helpers.lua:0: Error executing lua: .*: Invalid option name: 'notanopt'$", + pcall_err_withfile(exec_lua, 'return vim.wo.notanopt')) + matches("^.../helpers.lua:0: Error executing lua: .*: Expected lua string$", + pcall_err_withfile(exec_lua, 'return vim.wo[0][0].list')) eq(2, funcs.luaeval "vim.wo[1000].cole") exec_lua [[ vim.wo[1000].cole = 0 diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index f514f4ea6f..f07a2d18a2 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -747,8 +747,16 @@ describe('LSP', function() end) it('should invalid cmd argument', function() - eq('Error executing lua: .../shared.lua: cmd: expected list, got nvim', pcall_err(_cmd_parts, "nvim")) - eq('Error executing lua: .../shared.lua: cmd argument: expected string, got number', pcall_err(_cmd_parts, {"nvim", 1})) + eq(dedent([[ + Error executing lua: .../lsp.lua:0: cmd: expected list, got nvim + stack traceback: + .../lsp.lua:0: in function .../lsp.lua:0>]]), + pcall_err(_cmd_parts, 'nvim')) + eq(dedent([[ + Error executing lua: .../lsp.lua:0: cmd argument: expected string, got number + stack traceback: + .../lsp.lua:0: in function .../lsp.lua:0>]]), + pcall_err(_cmd_parts, {'nvim', 1})) end) end) end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 11fe861de8..eec8eb93d4 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -59,7 +59,7 @@ describe('floatwin', function() end) it('closed immediately by autocmd #11383', function() - eq('Error executing lua: [string "<nvim>"]:4: Window was closed immediately', + eq('Error executing lua: [string "<nvim>"]:0: Window was closed immediately', pcall_err(exec_lua, [[ local a = vim.api local function crashes(contents) diff --git a/test/helpers.lua b/test/helpers.lua index 5acd2ea0bd..68f0c92244 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -116,8 +116,12 @@ function module.assert_log(pat, logfile) pat, nrlines, logfile, logtail)) end --- Invokes `fn` and returns the error string (may truncate full paths), or --- raises an error if `fn` succeeds. +-- Invokes `fn` and returns the error string (with truncated paths), or raises +-- an error if `fn` succeeds. +-- +-- Replaces line/column numbers with zero: +-- shared.lua:0: in function 'gsplit' +-- shared.lua:0: in function <shared.lua:0>' -- -- Usage: -- -- Match exact string. @@ -125,29 +129,36 @@ end -- -- Match Lua pattern. -- matches('e[or]+$', pcall_err(function(a, b) error('some error') end, 'arg1', 'arg2')) -- -function module.pcall_err(fn, ...) +function module.pcall_err_withfile(fn, ...) assert(type(fn) == 'function') local status, rv = pcall(fn, ...) if status == true then error('expected failure, but got success') end - -- From this: - -- /home/foo/neovim/runtime/lua/vim/shared.lua:186: Expected string, got number - -- to this: - -- Expected string, got number - local errmsg = tostring(rv):gsub('^[^:]+:%d+: ', '') - -- From this: - -- Error executing lua: /very/long/foo.lua:186: Expected string, got number - -- to this: - -- Error executing lua: .../foo.lua:186: Expected string, got number - errmsg = errmsg:gsub([[lua: [a-zA-Z]?:?[^:]-[/\]([^:/\]+):%d+: ]], 'lua: .../%1: ') - -- Compiled modules will not have a path and will just be a name like - -- shared.lua:186, so strip the number. - errmsg = errmsg:gsub([[lua: ([^:/\ ]+):%d+: ]], 'lua: .../%1: ') - -- ^ Windows drive-letter (C:) + -- From: + -- C:/long/path/foo.lua:186: Expected string, got number + -- to: + -- .../foo.lua:0: Expected string, got number + local errmsg = tostring(rv):gsub('[^%s]-[/\\]([^%s:/\\]+):%d+', '.../%1:0') + -- Scrub numbers in paths/stacktraces: + -- shared.lua:0: in function 'gsplit' + -- shared.lua:0: in function <shared.lua:0>' + errmsg = errmsg:gsub('([^%s]):%d+', '%1:0') + -- Scrub tab chars: + errmsg = errmsg:gsub('\t', ' ') + -- In Lua 5.1, we sometimes get a "(tail call): ?" on the last line. + -- We remove this so that the tests are not lua dependent. + errmsg = errmsg:gsub('%s*%(tail call%): %?', '') + return errmsg end +function module.pcall_err(fn, ...) + local errmsg = module.pcall_err_withfile(fn, ...) + + return errmsg:gsub('.../helpers.lua:0: ', '') +end + -- initial_path: directory to recurse into -- re: include pattern (string) -- exc_re: exclude pattern(s) (string or table) |