diff options
Diffstat (limited to 'src')
53 files changed, 1300 insertions, 510 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 2d5403d4b8..922d288da1 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -287,8 +287,8 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, } bool oob = false; - start = normalize_index(buf, start, &oob); - end = normalize_index(buf, end, &oob); + start = normalize_index(buf, start, true, &oob); + end = normalize_index(buf, end, true, &oob); if (strict_indexing && oob) { api_set_error(err, kErrorTypeValidation, "Index out of bounds"); @@ -374,15 +374,14 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ } bool oob = false; - start = normalize_index(buf, start, &oob); - end = normalize_index(buf, end, &oob); + start = normalize_index(buf, start, true, &oob); + end = normalize_index(buf, end, true, &oob); if (strict_indexing && oob) { api_set_error(err, kErrorTypeValidation, "Index out of bounds"); return; } - if (start > end) { api_set_error(err, kErrorTypeValidation, @@ -554,13 +553,13 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In // check range is ordered and everything! // start_row, end_row within buffer len (except add text past the end?) - start_row = normalize_index(buf, start_row, &oob); + start_row = normalize_index(buf, start_row, false, &oob); if (oob || start_row == buf->b_ml.ml_line_count + 1) { api_set_error(err, kErrorTypeValidation, "start_row out of bounds"); return; } - end_row = normalize_index(buf, end_row, &oob); + end_row = normalize_index(buf, end_row, false, &oob); if (oob || end_row == buf->b_ml.ml_line_count + 1) { api_set_error(err, kErrorTypeValidation, "end_row out of bounds"); return; @@ -757,6 +756,108 @@ end: try_end(err); } +/// Gets a range from the buffer. +/// +/// This differs from |nvim_buf_get_lines()| in that it allows retrieving only +/// portions of a line. +/// +/// Indexing is zero-based. Column indices are end-exclusive. +/// +/// Prefer |nvim_buf_get_lines()| when retrieving entire lines. +/// +/// @param channel_id +/// @param buffer Buffer handle, or 0 for current buffer +/// @param start_row First line index +/// @param start_col Starting byte offset of first line +/// @param end_row Last line index +/// @param end_col Ending byte offset of last line (exclusive) +/// @param opts Optional parameters. Currently unused. +/// @param[out] err Error details, if any +/// @return Array of lines, or empty array for unloaded buffer. +ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, + Integer start_row, Integer start_col, + Integer end_row, Integer end_col, + Dictionary opts, Error *err) + FUNC_API_SINCE(9) +{ + Array rv = ARRAY_DICT_INIT; + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + return rv; + } + + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return rv; + } + + // return sentinel value if the buffer isn't loaded + if (buf->b_ml.ml_mfp == NULL) { + return rv; + } + + bool oob = false; + start_row = normalize_index(buf, start_row, false, &oob); + end_row = normalize_index(buf, end_row, false, &oob); + + if (oob) { + api_set_error(err, kErrorTypeValidation, "Index out of bounds"); + return rv; + } + + // nvim_buf_get_lines doesn't care if the start row is greater than the end + // row (it will just return an empty array), but nvim_buf_get_text does in + // order to maintain symmetry with nvim_buf_set_text. + if (start_row > end_row) { + api_set_error(err, kErrorTypeValidation, "start is higher than end"); + return rv; + } + + bool replace_nl = (channel_id != VIML_INTERNAL_CALL); + + if (start_row == end_row) { + String line = buf_get_text(buf, start_row, start_col, end_col, replace_nl, err); + if (ERROR_SET(err)) { + return rv; + } + + ADD(rv, STRING_OBJ(line)); + return rv; + } + + rv.size = (size_t)(end_row - start_row) + 1; + rv.items = xcalloc(rv.size, sizeof(Object)); + + rv.items[0] = STRING_OBJ(buf_get_text(buf, start_row, start_col, MAXCOL-1, replace_nl, err)); + if (ERROR_SET(err)) { + goto end; + } + + if (rv.size > 2) { + Array tmp = ARRAY_DICT_INIT; + tmp.items = &rv.items[1]; + if (!buf_collect_lines(buf, rv.size - 2, start_row + 1, replace_nl, &tmp, err)) { + goto end; + } + } + + rv.items[rv.size-1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err)); + if (ERROR_SET(err)) { + goto end; + } + +end: + if (ERROR_SET(err)) { + api_free_array(rv); + rv.size = 0; + rv.items = NULL; + } + + return rv; +} + /// Returns the byte offset of a line (0-indexed). |api-indexing| /// /// Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte. @@ -1386,11 +1487,11 @@ static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) } // Normalizes 0-based indexes to buffer line numbers -static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob) +static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob) { int64_t line_count = buf->b_ml.ml_line_count; // Fix if < 0 - index = index < 0 ? line_count + index +1 : index; + index = index < 0 ? line_count + index + (int)end_exclusive : index; // Check for oob if (index > line_count) { diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index f6dce1905e..45a57b9257 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -97,6 +97,7 @@ return { "special"; "sp"; "link"; "fallback"; + "blend"; "temp"; }; highlight_cterm = { diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index ddcfff0097..971fa1cb0f 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -411,7 +411,6 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object current_sctx = save_current_sctx; } - buf_T *find_buffer_by_handle(Buffer buffer, Error *err) { if (buffer == 0) { @@ -758,6 +757,52 @@ bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, Arr return true; } +/// Returns a substring of a buffer line +/// +/// @param buf Buffer handle +/// @param lnum Line number (1-based) +/// @param start_col Starting byte offset into line (0-based) +/// @param end_col Ending byte offset into line (0-based, exclusive) +/// @param replace_nl Replace newlines ('\n') with null ('\0') +/// @param err Error object +/// @return The text between start_col and end_col on line lnum of buffer buf +String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, bool replace_nl, + Error *err) +{ + String rv = STRING_INIT; + + if (lnum >= MAXLNUM) { + api_set_error(err, kErrorTypeValidation, "Line index is too high"); + return rv; + } + + const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); + size_t line_length = strlen(bufstr); + + start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col; + end_col = end_col < 0 ? (int64_t)line_length + end_col + 1 : end_col; + + if (start_col >= MAXCOL || end_col >= MAXCOL) { + api_set_error(err, kErrorTypeValidation, "Column index is too high"); + return rv; + } + + if (start_col > end_col) { + api_set_error(err, kErrorTypeValidation, "start_col must be less than end_col"); + return rv; + } + + if ((size_t)start_col >= line_length) { + return rv; + } + + rv = cstrn_to_string(&bufstr[start_col], (size_t)(end_col - start_col)); + if (replace_nl) { + strchrsub(rv.data, '\n', '\0'); + } + + return rv; +} void api_free_string(String value) { @@ -1384,6 +1429,11 @@ void add_user_command(String name, Object command, Dict(user_command) *opts, int LuaRef luaref = LUA_NOREF; LuaRef compl_luaref = LUA_NOREF; + if (!uc_validate_name(name.data)) { + api_set_error(err, kErrorTypeValidation, "Invalid command name"); + goto err; + } + if (mb_islower(name.data[0])) { api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter"); goto err; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f4909b0801..4dc599564f 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -124,6 +124,10 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// Set a highlight group. /// +/// Note: unlike the `:highlight` command which can update a highlight group, +/// this function completely replaces the definition. For example: +/// `nvim_set_hl(0, 'Visual', {})` will clear the highlight group 'Visual'. +/// /// @param ns_id number of namespace for this highlight. Use value 0 /// to set a highlight group in the global (`:highlight`) /// namespace. @@ -497,8 +501,12 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err) int flags = DIP_DIRFILE | (all ? DIP_ALL : 0); - do_in_runtimepath((char_u *)(name.size ? name.data : ""), - flags, find_runtime_cb, &rv); + TRY_WRAP({ + try_start(); + do_in_runtimepath((char_u *)(name.size ? name.data : ""), + flags, find_runtime_cb, &rv); + try_end(err); + }); return rv; } @@ -1584,7 +1592,7 @@ ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) /// @param rhs Right-hand-side |{rhs}| of the mapping. /// @param opts Optional parameters map. Accepts all |:map-arguments| /// as keys excluding |<buffer>| but including |noremap| and "desc". -/// |desc| can be used to give a description to keymap. +/// "desc" can be used to give a description to keymap. /// When called from Lua, also accepts a "callback" key that takes /// a Lua function to call when the mapping is executed. /// Values are Booleans. Unknown key is an error. diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 8fe623fc96..518d0b52b2 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -40,6 +40,7 @@ return { 'DiagnosticChanged', -- diagnostics in a buffer were modified 'DiffUpdated', -- diffs have been updated 'DirChanged', -- directory changed + 'DirChangedPre', -- directory is going to change 'EncodingChanged', -- after changing the 'encoding' option 'ExitPre', -- before exiting 'FileAppendCmd', -- append to a file using command @@ -132,18 +133,14 @@ return { nvim_specific = { BufModifiedSet=true, DiagnosticChanged=true, - DirChanged=true, RecordingEnter=true, RecordingLeave=true, Signal=true, - TabClosed=true, - TabNew=true, TabNewEntered=true, TermClose=true, TermOpen=true, UIEnter=true, UILeave=true, - WinClosed=true, WinScrolled=true, }, } diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index dfba18b11d..9117dde089 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1517,12 +1517,13 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, || event == EVENT_CMDLINELEAVE || event == EVENT_CMDWINENTER || event == EVENT_CMDWINLEAVE || event == EVENT_CMDUNDEFINED || event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE - || event == EVENT_DIRCHANGED || event == EVENT_FILETYPE - || event == EVENT_FUNCUNDEFINED || event == EVENT_MODECHANGED - || event == EVENT_OPTIONSET || event == EVENT_QUICKFIXCMDPOST - || event == EVENT_QUICKFIXCMDPRE || event == EVENT_REMOTEREPLY - || event == EVENT_SPELLFILEMISSING || event == EVENT_SYNTAX - || event == EVENT_SIGNAL || event == EVENT_TABCLOSED + || event == EVENT_DIRCHANGED || event == EVENT_DIRCHANGEDPRE + || event == EVENT_FILETYPE || event == EVENT_FUNCUNDEFINED + || event == EVENT_MODECHANGED || event == EVENT_OPTIONSET + || event == EVENT_QUICKFIXCMDPOST || event == EVENT_QUICKFIXCMDPRE + || event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING + || event == EVENT_SYNTAX || event == EVENT_SIGNAL + || event == EVENT_TABCLOSED || event == EVENT_USER || event == EVENT_WINCLOSED) { fname = vim_strsave(fname); } else { diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 38b045b31c..9e82b4e80b 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1441,7 +1441,7 @@ void set_curbuf(buf_T *buf, int action) set_bufref(&prevbufref, prevbuf); set_bufref(&newbufref, buf); - // Autocommands may delete the curren buffer and/or the buffer we want to go + // Autocommands may delete the current buffer and/or the buffer we want to go // to. In those cases don't close the buffer. if (!apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf) || (bufref_valid(&prevbufref) && bufref_valid(&newbufref) @@ -1454,6 +1454,7 @@ void set_curbuf(buf_T *buf, int action) } if (bufref_valid(&prevbufref) && !aborting()) { win_T *previouswin = curwin; + // Do not sync when in Insert mode and the buffer is open in // another window, might be a timer doing something in another // window. @@ -3438,8 +3439,12 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use if (stl_items == NULL) { stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); stl_groupitems = xmalloc(sizeof(int) * stl_items_len); - stl_hltab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len); - stl_tabtab = xmalloc(sizeof(StlClickRecord) * stl_items_len); + + // Allocate one more, because the last element is used to indicate the + // end of the list. + stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1)); + stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1)); + stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); } @@ -3514,8 +3519,8 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len); stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len); - stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * new_len); - stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * new_len); + stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * (new_len + 1)); + stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * (new_len + 1)); stl_separator_locations = xrealloc(stl_separator_locations, sizeof(int) * new_len); @@ -5490,11 +5495,19 @@ static int buf_signcols_inner(buf_T *buf, int maximum) int buf_signcols(buf_T *buf, int maximum) { + // The maximum can be determined from 'signcolumn' which is window scoped so + // need to invalidate signcols if the maximum is greater than the previous + // maximum. + if (maximum > buf->b_signcols_max) { + buf->b_signcols_valid = false; + } + if (!buf->b_signcols_valid) { int signcols = buf_signcols_inner(buf, maximum); // Check if we need to redraw if (signcols != buf->b_signcols) { buf->b_signcols = signcols; + buf->b_signcols_max = maximum; redraw_buf_later(buf, NOT_VALID); } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 7ae5df164f..1e0c837056 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -864,6 +864,7 @@ struct file_buffer { sign_entry_T *b_signlist; // list of placed signs int b_signcols; // last calculated number of sign columns bool b_signcols_valid; // calculated sign columns is valid + int b_signcols_max; // Maximum value b_signcols is valid for. Terminal *terminal; // Terminal instance associated with the buffer @@ -1353,6 +1354,7 @@ struct window_S { // recomputed int w_nrwidth; // width of 'number' and 'relativenumber' // column being used + int w_scwidth; // width of 'signcolumn' /* * === end of cached values === diff --git a/src/nvim/change.c b/src/nvim/change.c index 6ac759d5e0..736867b6d3 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -223,19 +223,20 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra // values for the cursor. // Update the folds for this window. Can't postpone this, because // a following operator might work on the whole fold: ">>dd". - foldUpdate(wp, lnum, lnume + xtra - 1); + linenr_T last = lnume + xtra - 1; // last line after the change + foldUpdate(wp, lnum, last); // The change may cause lines above or below the change to become // included in a fold. Set lnum/lnume to the first/last line that // might be displayed differently. // Set w_cline_folded here as an efficient way to update it when - // inserting lines just above a closed fold. */ + // inserting lines just above a closed fold. bool folded = hasFoldingWin(wp, lnum, &lnum, NULL, false, NULL); if (wp->w_cursor.lnum == lnum) { wp->w_cline_folded = folded; } - folded = hasFoldingWin(wp, lnume, NULL, &lnume, false, NULL); - if (wp->w_cursor.lnum == lnume) { + folded = hasFoldingWin(wp, last, NULL, &last, false, NULL); + if (wp->w_cursor.lnum == last) { wp->w_cline_folded = folded; } diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 583a040ed1..f4882e57e1 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1439,7 +1439,7 @@ bool vim_isblankline(char_u *lbuf) /// @param unptr Returns the unsigned result. /// @param maxlen Max length of string to check. /// @param strict If true, fail if the number has unexpected trailing -/// alpha-numeric chars: *len is set to 0 and nothing else is +/// alphanumeric chars: *len is set to 0 and nothing else is /// returned. void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen, @@ -1585,7 +1585,7 @@ vim_str2nr_hex: #undef PARSE_NUMBER vim_str2nr_proceed: - // Check for an alpha-numeric character immediately following, that is + // Check for an alphanumeric character immediately following, that is // most likely a typo. if (strict && ptr - (const char *)start != maxlen && ASCII_ISALNUM(*ptr)) { return; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index c6baa105b0..3763390c22 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -3225,7 +3225,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) set_vim_var_nr(VV_MOUSE_COL, 0); rettv->vval.v_number = n; - if (IS_SPECIAL(n) || mod_mask != 0) { + if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) { char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 int i = 0; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 4dba0b97ed..48749afcb3 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4141,7 +4141,11 @@ static linenr_T get_address(exarg_T *eap, char_u **ptr, cmd_addr_T addr_type, in if (!ascii_isdigit(*cmd)) { // '+' is '+1', but '+0' is not '+1' n = 1; } else { - n = getdigits(&cmd, true, 0); + n = getdigits(&cmd, false, MAXLNUM); + if (n == MAXLNUM) { + emsg(_(e_line_number_out_of_range)); + goto error; + } } if (addr_type == ADDR_TABS_RELATIVE) { @@ -4160,6 +4164,10 @@ static linenr_T get_address(exarg_T *eap, char_u **ptr, cmd_addr_T addr_type, in if (i == '-') { lnum -= n; } else { + if (n >= LONG_MAX - lnum) { + emsg(_(e_line_number_out_of_range)); + goto error; + } lnum += n; } } @@ -5164,6 +5172,24 @@ char_u *get_command_name(expand_T *xp, int idx) return cmdnames[idx].cmd_name; } +/// Check for a valid user command name +/// +/// If the given {name} is valid, then a pointer to the end of the valid name is returned. +/// Otherwise, returns NULL. +char *uc_validate_name(char *name) +{ + if (ASCII_ISALPHA(*name)) { + while (ASCII_ISALNUM(*name)) { + name++; + } + } + if (!ends_excmd(*name) && !ascii_iswhite(*name)) { + return NULL; + } + + return name; +} + int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t argt, long def, int flags, int compl, char_u *compl_arg, LuaRef compl_luaref, cmd_addr_T addr_type, LuaRef luaref, bool force) @@ -5679,23 +5705,18 @@ static void ex_command(exarg_T *eap) // Get the name (if any) and skip to the following argument. name = p; - if (ASCII_ISALPHA(*p)) { - while (ASCII_ISALNUM(*p)) { - p++; - } - } - if (!ends_excmd(*p) && !ascii_iswhite(*p)) { + end = (char_u *)uc_validate_name((char *)name); + if (!end) { emsg(_("E182: Invalid command name")); return; } - end = p; - name_len = (int)(end - name); + name_len = (size_t)(end - name); // If there is nothing after the name, and no attributes were specified, // we are listing commands p = skipwhite(end); if (!has_attr && ends_excmd(*p)) { - uc_list(name, end - name); + uc_list(name, name_len); } else if (!ASCII_ISUPPER(*name)) { emsg(_("E183: User defined commands must start with an uppercase letter")); } else if (name_len <= 4 && STRNCMP(name, "Next", name_len) == 0) { @@ -5703,7 +5724,7 @@ static void ex_command(exarg_T *eap) } else if (compl > 0 && (argt & EX_EXTRA) == 0) { emsg(_(e_complete_used_without_nargs)); } else { - uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg, LUA_NOREF, + uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF, addr_type_arg, LUA_NOREF, eap->forceit); } } @@ -7485,7 +7506,7 @@ static void ex_edit(exarg_T *eap) do_exedit(eap, NULL); } -/// ":edit <file>" command and alikes. +/// ":edit <file>" command and alike. /// /// @param old_curwin curwin before doing a split or NULL void do_exedit(exarg_T *eap, win_T *old_curwin) @@ -7784,7 +7805,7 @@ static char_u *get_prevdir(CdScope scope) /// Deal with the side effects of changing the current directory. /// /// @param scope Scope of the function call (global, tab or window). -void post_chdir(CdScope scope, bool trigger_dirchanged) +static void post_chdir(CdScope scope, bool trigger_dirchanged) { // Always overwrite the window-local CWD. XFREE_CLEAR(curwin->w_localdir); @@ -7825,7 +7846,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) shorten_fnames(true); if (trigger_dirchanged) { - do_autocmd_dirchanged(cwd, scope, kCdCauseManual); + do_autocmd_dirchanged(cwd, scope, kCdCauseManual, false); } } @@ -7869,10 +7890,13 @@ bool changedir_func(char_u *new_dir, CdScope scope) } bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 0; - if (dir_differs && vim_chdir(new_dir) != 0) { - emsg(_(e_failed)); - xfree(pdir); - return false; + if (dir_differs) { + do_autocmd_dirchanged((char *)new_dir, scope, kCdCauseManual, true); + if (vim_chdir(new_dir) != 0) { + emsg(_(e_failed)); + xfree(pdir); + return false; + } } char_u **pp; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 25dbf680de..d0f7a91d6c 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1600,11 +1600,13 @@ theend: return file_name; } -void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) +void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre) { static bool recursive = false; - if (recursive || !has_event(EVENT_DIRCHANGED)) { + event_T event = pre ? EVENT_DIRCHANGEDPRE : EVENT_DIRCHANGED; + + if (recursive || !has_event(event)) { // No autocommand was defined or we changed // the directory from this autocommand. return; @@ -1638,8 +1640,12 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) new_dir = new_dir_buf; #endif + if (pre) { + tv_dict_add_str(dict, S_LEN("directory"), new_dir); + } else { + tv_dict_add_str(dict, S_LEN("cwd"), new_dir); + } tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614 - tv_dict_add_str(dict, S_LEN("cwd"), new_dir); tv_dict_add_bool(dict, S_LEN("changed_window"), cause == kCdCauseWindow); tv_dict_set_keys_readonly(dict); @@ -1655,8 +1661,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) abort(); } - apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false, - curbuf); + apply_autocmds(event, (char_u *)buf, (char_u *)new_dir, false, curbuf); restore_v_event(dict, &save_v_event); @@ -1682,12 +1687,16 @@ int vim_chdirfile(char_u *fname, CdCause cause) return OK; } + if (cause != kCdCauseOther) { + do_autocmd_dirchanged(dir, kCdScopeWindow, cause, true); + } + if (os_chdir(dir) != 0) { return FAIL; } if (cause != kCdCauseOther) { - do_autocmd_dirchanged(dir, kCdScopeWindow, cause); + do_autocmd_dirchanged(dir, kCdScopeWindow, cause, false); } return OK; diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 110981159f..f5824d83be 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2013,10 +2013,8 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp) return lnum; } -/* - * Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary to be - * equal to the buffer "buf". Used for calling readfile(). - */ +/// Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary' to be +/// equal to the buffer "buf". Used for calling readfile(). void prep_exarg(exarg_T *eap, const buf_T *buf) FUNC_ATTR_NONNULL_ALL { @@ -4901,13 +4899,11 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf) return retval; } -/* - * Check if buffer "buf" has been changed. - * Also check if the file for a new buffer unexpectedly appeared. - * return 1 if a changed buffer was found. - * return 2 if a message has been displayed. - * return 0 otherwise. - */ +/// Check if buffer "buf" has been changed. +/// Also check if the file for a new buffer unexpectedly appeared. +/// return 1 if a changed buffer was found. +/// return 2 if a message has been displayed. +/// return 0 otherwise. int buf_check_timestamp(buf_T *buf) FUNC_ATTR_NONNULL_ALL { @@ -4916,7 +4912,11 @@ int buf_check_timestamp(buf_T *buf) char *mesg = NULL; char *mesg2 = ""; bool helpmesg = false; - bool reload = false; + enum { + RELOAD_NONE, + RELOAD_NORMAL, + RELOAD_DETECT + } reload = RELOAD_NONE; bool can_reload = false; uint64_t orig_size = buf->b_orig_size; int orig_mode = buf->b_orig_mode; @@ -4969,7 +4969,7 @@ int buf_check_timestamp(buf_T *buf) // If 'autoread' is set, the buffer has no changes and the file still // exists, reload the buffer. Use the buffer-local option value if it // was set, the global option value otherwise. - reload = true; + reload = RELOAD_NORMAL; } else { if (!file_info_ok) { reason = "deleted"; @@ -5000,7 +5000,9 @@ int buf_check_timestamp(buf_T *buf) } s = get_vim_var_str(VV_FCS_CHOICE); if (STRCMP(s, "reload") == 0 && *reason != 'd') { - reload = true; + reload = RELOAD_NORMAL; + } else if (STRCMP(s, "edit") == 0) { + reload = RELOAD_DETECT; } else if (STRCMP(s, "ask") == 0) { n = false; } else { @@ -5064,9 +5066,15 @@ int buf_check_timestamp(buf_T *buf) xstrlcat(tbuf, "\n", tbuf_len - 1); xstrlcat(tbuf, mesg2, tbuf_len - 1); } - if (do_dialog(VIM_WARNING, (char_u *)_("Warning"), (char_u *)tbuf, - (char_u *)_("&OK\n&Load File"), 1, NULL, true) == 2) { - reload = true; + switch (do_dialog(VIM_WARNING, (char_u *)_("Warning"), (char_u *)tbuf, + (char_u *)_("&OK\n&Load File\nLoad File &and Options"), + 1, NULL, true)) { + case 2: + reload = RELOAD_NORMAL; + break; + case 3: + reload = RELOAD_DETECT; + break; } } else if (State > NORMAL_BUSY || (State & CMDLINE) || already_warned) { if (*mesg2 != NUL) { @@ -5100,9 +5108,9 @@ int buf_check_timestamp(buf_T *buf) xfree(tbuf); } - if (reload) { + if (reload != RELOAD_NONE) { // Reload the buffer. - buf_reload(buf, orig_mode); + buf_reload(buf, orig_mode, reload == RELOAD_DETECT); if (buf->b_p_udf && buf->b_ffname != NULL) { char_u hash[UNDO_HASH_SIZE]; @@ -5120,13 +5128,11 @@ int buf_check_timestamp(buf_T *buf) return retval; } -/* - * Reload a buffer that is already loaded. - * Used when the file was changed outside of Vim. - * "orig_mode" is buf->b_orig_mode before the need for reloading was detected. - * buf->b_orig_mode may have been reset already. - */ -void buf_reload(buf_T *buf, int orig_mode) +/// Reload a buffer that is already loaded. +/// Used when the file was changed outside of Vim. +/// "orig_mode" is buf->b_orig_mode before the need for reloading was detected. +/// buf->b_orig_mode may have been reset already. +void buf_reload(buf_T *buf, int orig_mode, bool reload_options) { exarg_T ea; pos_T old_cursor; @@ -5141,11 +5147,15 @@ void buf_reload(buf_T *buf, int orig_mode) // set curwin/curbuf for "buf" and save some things aucmd_prepbuf(&aco, buf); - // We only want to read the text from the file, not reset the syntax - // highlighting, clear marks, diff status, etc. Force the fileformat and - // encoding to be the same. + // Unless reload_options is set, we only want to read the text from the + // file, not reset the syntax highlighting, clear marks, diff status, etc. + // Force the fileformat and encoding to be the same. + if (reload_options) { + memset(&ea, 0, sizeof(ea)); + } else { + prep_exarg(&ea, buf); + } - prep_exarg(&ea, buf); old_cursor = curwin->w_cursor; old_topline = curwin->w_topline; diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 5d8a8ddbfe..34cde9a7c4 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1004,20 +1004,9 @@ int ins_char_typebuf(int c, int modifier) buf[len + 2] = (char_u)K_THIRD(c); buf[len + 3] = NUL; } else { - char_u *p = buf + len; - int char_len = utf_char2bytes(c, p); - len += char_len; - // If the character contains K_SPECIAL bytes they need escaping. - for (int i = char_len; --i >= 0; p++) { - if ((uint8_t)(*p) == K_SPECIAL) { - memmove(p + 3, p + 1, (size_t)i); - *p++ = K_SPECIAL; - *p++ = KS_SPECIAL; - *p = KE_FILLER; - len += 2; - } - } - *p = NUL; + char_u *end = add_char2buf(c, buf + len); + *end = NUL; + len = (int)(end - buf); } (void)ins_typebuf(buf, KeyNoremap, 0, !KeyTyped, cmd_silent); return len; @@ -2466,7 +2455,7 @@ static int vgetorpeek(bool advance) /// 1. a scriptfile /// 2. the keyboard /// -/// As much characters as we can get (up to 'maxlen') are put in "buf" and +/// As many characters as we can get (up to 'maxlen') are put in "buf" and /// NUL terminated (buffer length must be 'maxlen' + 1). /// Minimum for "maxlen" is 3!!!! /// @@ -2679,8 +2668,7 @@ void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, mapargs->orig_rhs_len = 0; // stores <lua>ref_no<cr> in map_str mapargs->rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "%c%c%c%d\r", K_SPECIAL, - (char_u)KEY2TERMCAP0(K_LUA), KEY2TERMCAP1(K_LUA), - rhs_lua); + (char_u)KS_EXTRA, KE_LUA, rhs_lua); mapargs->rhs = vim_strsave((char_u *)tmp_buf); } @@ -3434,8 +3422,8 @@ static void showmap(mapblock_T *mp, bool local) { size_t len = 1; - if (message_filtered(mp->m_keys) - && mp->m_str != NULL && message_filtered(mp->m_str)) { + if (message_filtered(mp->m_keys) && message_filtered(mp->m_str) + && (mp->m_desc == NULL || message_filtered((char_u *)mp->m_desc))) { return; } @@ -3484,7 +3472,7 @@ static void showmap(mapblock_T *mp, bool local) char msg[100]; snprintf(msg, sizeof(msg), "<Lua function %d>", mp->m_luaref); msg_puts_attr(msg, HL_ATTR(HLF_8)); - } else if (mp->m_str == NULL) { + } else if (mp->m_str[0] == NUL) { msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); } else { // Remove escaping of K_SPECIAL, because "m_str" is in a format to be used @@ -3584,8 +3572,7 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr) mp = maphash[hash]; } for (; mp; mp = mp->m_next) { - if ((mp->m_mode & mode) - && mp->m_str != NULL && strstr((char *)mp->m_str, rhs) != NULL) { + if ((mp->m_mode & mode) && strstr((char *)mp->m_str, rhs) != NULL) { return true; } } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 98a38c5fe2..5aa564623f 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1002,6 +1002,8 @@ EXTERN char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cann EXTERN char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long")); +EXTERN char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range")); + EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long")); EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index fcd91cdf16..e43a56086f 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -800,6 +800,7 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e { HlAttrs hlattrs = HLATTRS_INIT; int32_t fg = -1, bg = -1, ctermfg = -1, ctermbg = -1, sp = -1; + int blend = -1; int16_t mask = 0; int16_t cterm_mask = 0; bool cterm_mask_provided = false; @@ -821,27 +822,41 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e CHECK_FLAG(dict, mask, global, , HL_GLOBAL); if (HAS_KEY(dict->fg)) { - fg = object_to_color(dict->fg, "fg", err); + fg = object_to_color(dict->fg, "fg", true, err); } else if (HAS_KEY(dict->foreground)) { - fg = object_to_color(dict->foreground, "foreground", err); + fg = object_to_color(dict->foreground, "foreground", true, err); } if (ERROR_SET(err)) { return hlattrs; } if (HAS_KEY(dict->bg)) { - bg = object_to_color(dict->bg, "bg", err); + bg = object_to_color(dict->bg, "bg", true, err); } else if (HAS_KEY(dict->background)) { - bg = object_to_color(dict->background, "background", err); + bg = object_to_color(dict->background, "background", true, err); } if (ERROR_SET(err)) { return hlattrs; } if (HAS_KEY(dict->sp)) { - sp = object_to_color(dict->sp, "sp", err); + sp = object_to_color(dict->sp, "sp", true, err); } else if (HAS_KEY(dict->special)) { - sp = object_to_color(dict->special, "special", err); + sp = object_to_color(dict->special, "special", true, err); + } + if (ERROR_SET(err)) { + return hlattrs; + } + + if (dict->blend.type == kObjectTypeInteger) { + Integer blend0 = dict->blend.data.integer; + if (blend0 < 0 || blend0 > 100) { + api_set_error(err, kErrorTypeValidation, "'blend' is not between 0 to 100"); + } else { + blend = (int)blend0; + } + } else if (HAS_KEY(dict->blend)) { + api_set_error(err, kErrorTypeValidation, "'blend' must be an integer"); } if (ERROR_SET(err)) { return hlattrs; @@ -876,20 +891,24 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e CHECK_FLAG(cterm, cterm_mask, strikethrough, , HL_STRIKETHROUGH); CHECK_FLAG(cterm, cterm_mask, nocombine, , HL_NOCOMBINE); + } else if (dict->cterm.type == kObjectTypeArray && dict->cterm.data.array.size == 0) { + // empty list from Lua API should clear all cterm attributes + // TODO(clason): handle via gen_api_dispatch + cterm_mask_provided = true; } else if (HAS_KEY(dict->cterm)) { api_set_error(err, kErrorTypeValidation, "'cterm' must be a Dictionary."); } #undef CHECK_FLAG if (HAS_KEY(dict->ctermfg)) { - ctermfg = object_to_color(dict->ctermfg, "ctermfg", err); + ctermfg = object_to_color(dict->ctermfg, "ctermfg", false, err); if (ERROR_SET(err)) { return hlattrs; } } if (HAS_KEY(dict->ctermbg)) { - ctermbg = object_to_color(dict->ctermbg, "ctermbg", err); + ctermbg = object_to_color(dict->ctermbg, "ctermbg", false, err); if (ERROR_SET(err)) { return hlattrs; } @@ -904,28 +923,39 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e hlattrs.rgb_bg_color = bg; hlattrs.rgb_fg_color = fg; hlattrs.rgb_sp_color = sp; - hlattrs.cterm_bg_color = - ctermbg == -1 ? cterm_normal_bg_color : ctermbg + 1; - hlattrs.cterm_fg_color = - ctermfg == -1 ? cterm_normal_fg_color : ctermfg + 1; + hlattrs.hl_blend = blend; + hlattrs.cterm_bg_color = ctermbg == -1 ? 0 : ctermbg + 1; + hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : ctermfg + 1; hlattrs.cterm_ae_attr = cterm_mask; } else { hlattrs.cterm_ae_attr = cterm_mask; - hlattrs.cterm_bg_color = bg == -1 ? cterm_normal_bg_color : bg + 1; - hlattrs.cterm_fg_color = fg == -1 ? cterm_normal_fg_color : fg + 1; + hlattrs.cterm_bg_color = bg == -1 ? 0 : bg + 1; + hlattrs.cterm_fg_color = fg == -1 ? 0 : fg + 1; } return hlattrs; } -int object_to_color(Object val, char *key, Error *err) +int object_to_color(Object val, char *key, bool rgb, Error *err) { if (val.type == kObjectTypeInteger) { return (int)val.data.integer; } else if (val.type == kObjectTypeString) { String str = val.data.string; // TODO(bfredl): be more fancy with "bg", "fg" etc - return str.size ? name_to_color(str.data) : 0; + if (!str.size || STRICMP(str.data, "NONE") == 0) { + return -1; + } + int color; + if (rgb) { + color = name_to_color(str.data); + } else { + color = name_to_ctermcolor(str.data); + } + if (color < 0) { + api_set_error(err, kErrorTypeValidation, "'%s' is not a valid color", str.data); + } + return color; } else { api_set_error(err, kErrorTypeValidation, "'%s' must be string or integer", key); return 0; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index cbfb8364f6..029e7eb660 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1241,6 +1241,9 @@ void ex_luafile(exarg_T *const eap) /// execute lua code from a file. /// +/// Note: we call the lua global loadfile as opposed to calling luaL_loadfile +/// in case loadfile has been overridden in the users environment. +/// /// @param path path of the file /// /// @return true if everything ok, false if there was an error (echoed) @@ -1249,11 +1252,30 @@ bool nlua_exec_file(const char *path) { lua_State *const lstate = global_lstate; - if (luaL_loadfile(lstate, path)) { + lua_getglobal(lstate, "loadfile"); + lua_pushstring(lstate, path); + + if (nlua_pcall(lstate, 1, 2)) { + nlua_error(lstate, _("E5111: Error calling lua: %.*s")); + return false; + } + + // loadstring() returns either: + // 1. nil, error + // 2. chunk, nil + + if (lua_isnil(lstate, -2)) { + // 1 nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s")); + assert(lua_isnil(lstate, -1)); + lua_pop(lstate, 1); return false; } + // 2 + assert(lua_isnil(lstate, -1)); + lua_pop(lstate, 1); + if (nlua_pcall(lstate, 0, 0)) { nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s")); return false; diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index e5fa80a242..f634c7dda8 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -1317,6 +1317,12 @@ bool mb_isupper(int a) return mb_tolower(a) != a; } +bool mb_isalpha(int a) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + return mb_islower(a) || mb_isupper(a); +} + static int utf_strnicmp(const char_u *s1, const char_u *s2, size_t n1, size_t n2) { int c1, c2, cdiff; diff --git a/src/nvim/message.c b/src/nvim/message.c index 93742ccbdb..b39450cdc6 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -3464,6 +3464,7 @@ void msg_advance(int col) /// /// @param textfiel IObuff for inputdialog(), NULL otherwise /// @param ex_cmd when TRUE pressing : accepts default and starts Ex command +/// @returns 0 if cancelled, otherwise the nth button (1-indexed). int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfltbutton, char_u *textfield, int ex_cmd) { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 21c465434a..7fe6469527 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -824,15 +824,12 @@ static bool normal_get_command_count(NormalState *s) if (s->c == K_DEL || s->c == K_KDEL) { s->ca.count0 /= 10; del_from_showcmd(4); // delete the digit and ~@% + } else if (s->ca.count0 > 99999999L) { + s->ca.count0 = 999999999L; } else { s->ca.count0 = s->ca.count0 * 10 + (s->c - '0'); } - if (s->ca.count0 < 0) { - // overflow - s->ca.count0 = 999999999L; - } - // Set v:count here, when called from main() and not a stuffed // command, so that v:count can be used in an expression mapping // right after the count. Do set it for redo. @@ -1046,14 +1043,14 @@ static int normal_execute(VimState *state, int key) // If you give a count before AND after the operator, they are // multiplied. if (s->ca.count0) { - s->ca.count0 = (long)((uint64_t)s->ca.count0 * (uint64_t)s->ca.opcount); + if (s->ca.opcount >= 999999999L / s->ca.count0) { + s->ca.count0 = 999999999L; + } else { + s->ca.count0 *= s->ca.opcount; + } } else { s->ca.count0 = s->ca.opcount; } - if (s->ca.count0 < 0) { - // overflow - s->ca.count0 = 999999999L; - } } // Always remember the count. It will be set to zero (on the next call, diff --git a/src/nvim/ops.c b/src/nvim/ops.c index f999b68236..b5c7020dee 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -568,21 +568,18 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def } if (spaces > 0) { - int off; - - // Avoid starting halfway through a multi-byte character. - if (b_insert) { - off = utf_head_off(oldp, oldp + offset + spaces); - } else { - off = mb_off_next(oldp, oldp + offset); - offset += off; - } - spaces -= off; - count -= off; + // avoid copying part of a multi-byte character + offset -= utf_head_off(oldp, oldp + offset); + } + if (spaces < 0) { // can happen when the cursor was moved + spaces = 0; } assert(count >= 0); - newp = (char_u *)xmalloc(STRLEN(oldp) + s_len + (size_t)count + 1); + // Make sure the allocated size matches what is actually copied below. + newp = xmalloc(STRLEN(oldp) + (size_t)spaces + s_len + + (spaces > 0 && !bdp->is_short ? (size_t)p_ts - (size_t)spaces : 0) + + (size_t)count + 1); // copy up to shifted part memmove(newp, oldp, (size_t)offset); @@ -597,14 +594,19 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def offset += (int)s_len; int skipped = 0; - if (spaces && !bdp->is_short) { - // insert post-padding - memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces)); - // We're splitting a TAB, don't copy it. - oldp++; - // We allowed for that TAB, remember this now - count++; - skipped = 1; + if (spaces > 0 && !bdp->is_short) { + if (*oldp == TAB) { + // insert post-padding + memset(newp + offset + spaces, ' ', (size_t)(p_ts - spaces)); + // We're splitting a TAB, don't copy it. + oldp++; + // We allowed for that TAB, remember this now + count++; + skipped = 1; + } else { + // Not a TAB, no extra spaces + count = spaces; + } } if (spaces > 0) { @@ -1959,11 +1961,14 @@ static int op_replace(oparg_T *oap, int c) while (ltoreq(curwin->w_cursor, oap->end)) { n = gchar_cursor(); if (n != NUL) { - if (utf_char2len(c) > 1 || utf_char2len(n) > 1) { + int new_byte_len = utf_char2len(c); + int old_byte_len = utfc_ptr2len(get_cursor_pos_ptr()); + + if (new_byte_len > 1 || old_byte_len > 1) { // This is slow, but it handles replacing a single-byte // with a multi-byte and the other way around. if (curwin->w_cursor.lnum == oap->end.lnum) { - oap->end.col += utf_char2len(c) - utf_char2len(n); + oap->end.col += new_byte_len - old_byte_len; } replace_character(c); } else { @@ -2278,6 +2283,7 @@ void op_insert(oparg_T *oap, long count1) } t1 = oap->start; + const pos_T start_insert = curwin->w_cursor; (void)edit(NUL, false, (linenr_T)count1); // When a tab was inserted, and the characters in front of the tab @@ -2312,23 +2318,18 @@ void op_insert(oparg_T *oap, long count1) // The user may have moved the cursor before inserting something, try // to adjust the block for that. But only do it, if the difference // does not come from indent kicking in. - if (oap->start.lnum == curbuf->b_op_start_orig.lnum - && !bd.is_MAX - && !did_indent) { + if (oap->start.lnum == curbuf->b_op_start_orig.lnum && !bd.is_MAX && !did_indent) { + const int t = getviscol2(curbuf->b_op_start_orig.col, curbuf->b_op_start_orig.coladd); + if (oap->op_type == OP_INSERT && oap->start.col + oap->start.coladd != curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) { - int t = getviscol2(curbuf->b_op_start_orig.col, - curbuf->b_op_start_orig.coladd); oap->start.col = curbuf->b_op_start_orig.col; pre_textlen -= t - oap->start_vcol; oap->start_vcol = t; } else if (oap->op_type == OP_APPEND - && oap->end.col + oap->end.coladd - >= curbuf->b_op_start_orig.col - + curbuf->b_op_start_orig.coladd) { - int t = getviscol2(curbuf->b_op_start_orig.col, - curbuf->b_op_start_orig.coladd); + && oap->start.col + oap->start.coladd + >= curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) { oap->start.col = curbuf->b_op_start_orig.col; // reset pre_textlen to the value of OP_INSERT pre_textlen += bd.textlen; @@ -2376,15 +2377,27 @@ void op_insert(oparg_T *oap, long count1) firstline = ml_get(oap->start.lnum); const size_t len = STRLEN(firstline); colnr_T add = bd.textcol; + colnr_T offset = 0; // offset when cursor was moved in insert mode if (oap->op_type == OP_APPEND) { add += bd.textlen; + // account for pressing cursor in insert mode when '$' was used + if (bd.is_MAX && start_insert.lnum == Insstart.lnum && start_insert.col > Insstart.col) { + offset = start_insert.col - Insstart.col; + add -= offset; + if (oap->end_vcol > offset) { + oap->end_vcol -= offset + 1; + } else { + // moved outside of the visual block, what to do? + return; + } + } } if ((size_t)add > len) { firstline += len; // short line, point to the NUL } else { firstline += add; } - ins_len = (long)STRLEN(firstline) - pre_textlen; + ins_len = (long)STRLEN(firstline) - pre_textlen - offset; if (pre_textlen >= 0 && ins_len > 0) { ins_text = vim_strnsave(firstline, (size_t)ins_len); // block handled here @@ -3301,18 +3314,28 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } } - // insert the new text + // Insert the new text. + // First check for multiplication overflow. + if (yanklen + spaces != 0 + && count > ((INT_MAX - (bd.startspaces + bd.endspaces)) / (yanklen + spaces))) { + emsg(_(e_resulting_text_too_long)); + break; + } + totlen = (size_t)(count * (yanklen + spaces) + bd.startspaces + bd.endspaces); int addcount = (int)totlen + lines_appended; newp = (char_u *)xmalloc(totlen + oldlen + 1); + // copy part up to cursor to new line ptr = newp; memmove(ptr, oldp, (size_t)bd.textcol); ptr += bd.textcol; + // may insert some spaces before the new text memset(ptr, ' ', (size_t)bd.startspaces); ptr += bd.startspaces; + // insert the new text for (long j = 0; j < count; j++) { memmove(ptr, y_array[i], (size_t)yanklen); @@ -3326,9 +3349,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) addcount -= spaces; } } + // may insert some spaces after the new text memset(ptr, ' ', (size_t)bd.endspaces); ptr += bd.endspaces; + // move the text after the cursor to the end of the line. int columns = (int)oldlen - bd.textcol - delcount + 1; assert(columns >= 0); @@ -3417,10 +3442,18 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } } - do { + if (count == 0 || yanklen == 0) { + if (VIsual_active) { + lnum = end_lnum; + } + } else if (count > INT_MAX / yanklen) { + // multiplication overflow + emsg(_(e_resulting_text_too_long)); + } else { totlen = (size_t)(count * yanklen); - if (totlen > 0) { + do { oldp = ml_get(lnum); + oldlen = STRLEN(oldp); if (lnum > start_lnum) { pos_T pos = { .lnum = lnum, @@ -3431,11 +3464,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) col = MAXCOL; } } - if (VIsual_active && col > (int)STRLEN(oldp)) { + if (VIsual_active && col > (colnr_T)oldlen) { lnum++; continue; } - newp = (char_u *)xmalloc((size_t)(STRLEN(oldp) + totlen + 1)); + newp = (char_u *)xmalloc(totlen + oldlen + 1); memmove(newp, oldp, (size_t)col); ptr = newp + col; for (i = 0; i < (size_t)count; i++) { @@ -3457,14 +3490,14 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) changed_bytes(lnum, col); extmark_splice_cols(curbuf, (int)lnum-1, col, 0, (int)totlen, kExtmarkUndo); - } - if (VIsual_active) { - lnum++; - } - } while (VIsual_active && lnum <= end_lnum); + if (VIsual_active) { + lnum++; + } + } while (VIsual_active && lnum <= end_lnum); - if (VIsual_active) { // reset lnum to the last visual line - lnum--; + if (VIsual_active) { // reset lnum to the last visual line + lnum--; + } } // put '] at the first byte of the last character @@ -5678,7 +5711,9 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char_u *str // When appending, copy the previous line and free it after. size_t extra = append ? STRLEN(pp[--lnum]) : 0; char_u *s = xmallocz(line_len + extra); - memcpy(s, pp[lnum], extra); + if (extra > 0) { + memcpy(s, pp[lnum], extra); + } memcpy(s + extra, start, line_len); size_t s_len = extra + line_len; diff --git a/src/nvim/option.c b/src/nvim/option.c index c8e50d4494..4ec6ee3148 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4277,7 +4277,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } // Save the global value before changing anything. This is needed as for - // a global-only option setting the "local value" infact sets the global + // a global-only option setting the "local value" in fact sets the global // value (since there is only one value). if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { old_global_value = *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); @@ -8165,7 +8165,7 @@ int win_signcol_configured(win_T *wp, int *is_fixed) } int needed_signcols = buf_signcols(wp->w_buffer, maximum); - int ret = MAX(minimum, needed_signcols); + int ret = MAX(minimum, MIN(maximum, needed_signcols)); assert(ret <= SIGN_SHOW_MAX); return ret; } diff --git a/src/nvim/path.c b/src/nvim/path.c index 7f7f941e26..d3aa5e5bf2 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -642,8 +642,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, } else if (path_end >= path + wildoff && (vim_strchr((char_u *)"*?[{~$", *path_end) != NULL #ifndef WIN32 - || (!p_fic && (flags & EW_ICASE) - && isalpha(utf_ptr2char(path_end))) + || (!p_fic && (flags & EW_ICASE) && mb_isalpha(utf_ptr2char(path_end))) #endif )) { e = p; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 15fb6901cc..7fafe3dd6e 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -326,6 +326,18 @@ void redraw_buf_status_later(buf_T *buf) } } +void redraw_win_signcol(win_T *wp) +{ + // If we can compute a change in the automatic sizing of the sign column + // under 'signcolumn=auto:X' and signs currently placed in the buffer, better + // figuring it out here so we can redraw the entire screen for it. + int scwidth = wp->w_scwidth; + wp->w_scwidth = win_signcol_count(wp); + if (wp->w_scwidth != scwidth) { + changed_line_abv_curs_win(wp); + } +} + /// Redraw the parts of the screen that is marked for redraw. /// /// Most code shouldn't call this directly, rather use redraw_later() and @@ -790,12 +802,6 @@ static void win_update(win_T *wp, Providers *providers) linenr_T mod_bot = 0; int save_got_int; - - // If we can compute a change in the automatic sizing of the sign column - // under 'signcolumn=auto:X' and signs currently placed in the buffer, better - // figuring it out here so we can redraw the entire screen for it. - win_signcol_count(wp); - type = wp->w_redr_type; if (type >= NOT_VALID) { @@ -817,6 +823,8 @@ static void win_update(win_T *wp, Providers *providers) return; } + redraw_win_signcol(wp); + init_search_hl(wp); /* Force redraw when width of 'number' or 'relativenumber' column @@ -1846,7 +1854,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, i win_hl_attr(wp, HLF_FC)); } // draw the sign column - int count = win_signcol_count(wp); + int count = wp->w_scwidth; if (count > 0) { n = win_fill_end(wp, ' ', ' ', n, win_signcol_width(wp) * count, row, endrow, win_hl_attr(wp, HLF_SC)); @@ -2792,10 +2800,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc draw_state = WL_SIGN; /* Show the sign column when there are any signs in this * buffer or when using Netbeans. */ - int count = win_signcol_count(wp); - if (count > 0) { + if (wp->w_scwidth > 0) { get_sign_display_info(false, wp, lnum, sattrs, row, - startrow, filler_lines, filler_todo, count, + startrow, filler_lines, filler_todo, &c_extra, &c_final, extra, sizeof(extra), &p_extra, &n_extra, &char_attr, &draw_state, &sign_idx); @@ -2814,9 +2821,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // number. if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' && num_signs > 0 && sign_get_attr(SIGN_TEXT, sattrs, 0, 1)) { - int count = win_signcol_count(wp); get_sign_display_info(true, wp, lnum, sattrs, row, - startrow, filler_lines, filler_todo, count, + startrow, filler_lines, filler_todo, &c_extra, &c_final, extra, sizeof(extra), &p_extra, &n_extra, &char_attr, &draw_state, &sign_idx); @@ -4674,10 +4680,11 @@ static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) // @param[in, out] sign_idxp Index of the displayed sign static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, sign_attrs_T sattrs[], int row, int startrow, int filler_lines, int filler_todo, - int count, int *c_extrap, int *c_finalp, char_u *extra, + int *c_extrap, int *c_finalp, char_u *extra, size_t extra_size, char_u **pp_extra, int *n_extrap, int *char_attrp, int *draw_statep, int *sign_idxp) { + int count = wp->w_scwidth; // Draw cells with the sign value or blank. *c_extrap = ' '; *c_finalp = NUL; diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index f6b95f37b1..d7b220b3f6 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -5654,7 +5654,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo // If the .add file is edited somewhere, reload it. if (buf != NULL) { - buf_reload(buf, buf->b_orig_mode); + buf_reload(buf, buf->b_orig_mode, false); } redraw_all_later(SOME_VALID); diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 119f6e811f..2b74c45478 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6758,16 +6758,26 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) { NULL, -1, NIL }, }; + char hex_name[8]; + char *name; + for (int j = 0; cattrs[j].dest; j++) { - if (cattrs[j].val != -1) { + if (cattrs[j].val < 0) { + XFREE_CLEAR(*cattrs[j].dest); + continue; + } + + if (cattrs[j].name.type == kObjectTypeString && cattrs[j].name.data.string.size) { + name = cattrs[j].name.data.string.data; + } else { + snprintf(hex_name, sizeof(hex_name), "#%06x", cattrs[j].val); + name = hex_name; + } + + if (!*cattrs[j].dest + || STRCMP(*cattrs[j].dest, name) != 0) { xfree(*cattrs[j].dest); - if (cattrs[j].name.type == kObjectTypeString && cattrs[j].name.data.string.size) { - *cattrs[j].dest = xstrdup(cattrs[j].name.data.string.data); - } else { - char hex_name[8]; - snprintf(hex_name, sizeof(hex_name), "#%06x", cattrs[j].val); - *cattrs[j].dest = xstrdup(hex_name); - } + *cattrs[j].dest = xstrdup(name); } } @@ -8849,6 +8859,22 @@ RgbValue name_to_color(const char *name) return -1; } +int name_to_ctermcolor(const char *name) +{ + int i; + int off = TOUPPER_ASC(*name); + for (i = ARRAY_SIZE(color_names); --i >= 0;) { + if (off == color_names[i][0] + && STRICMP(name+1, color_names[i]+1) == 0) { + break; + } + } + if (i < 0) { + return -1; + } + TriState bold = kNone; + return lookup_color(i, false, &bold); +} /************************************** * End of Highlighting stuff * diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index fdae0697c3..15e3b31498 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -10,6 +10,7 @@ let s:did_load = 1 set backspace= set directory^=. set fillchars=vert:\|,fold:- +set fsync set laststatus=1 set listchars=eol:$ set joinspaces diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index c0ac4393c4..5a3d1d56bb 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -39,7 +39,8 @@ source test_put.vim source test_rename.vim source test_scroll_opt.vim source test_shift.vim -source test_sort.vim +" Test fails on windows CI when using the MSVC compiler. +" source test_sort.vim source test_sha256.vim source test_suspend.vim source test_syn_attr.vim diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim index 9ad727241e..53ed4617f7 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -64,4 +64,14 @@ func Test_verbose_pwd() call delete('Xautodir', 'rf') endfunc +func Test_multibyte() + " using an invalid character should not cause a crash + set wic + " Except on Windows, E472 is also thrown last, but v8.1.1183 isn't ported yet + " call assert_fails('tc *', has('win32') ? 'E480:' : 'E344:') + call assert_fails('tc *', has('win32') ? 'E480:' : 'E472:') + set nowic +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index a8d51ef598..c39546b9ea 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -502,7 +502,7 @@ func Test_autocmd_bufwipe_in_SessLoadPost() [CODE] call writefile(content, 'Xvimrc') - call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq') + call system(GetVimCommand('Xvimrc') .. ' --headless --noplugins -S Session.vim -c cq') let errors = join(readfile('Xerrors')) call assert_match('E814', errors) @@ -562,7 +562,7 @@ func Test_autocmd_bufwipe_in_SessLoadPost2() [CODE] call writefile(content, 'Xvimrc') - call system(v:progpath. ' --headless -i NONE -u Xvimrc --noplugins -S Session.vim -c cq') + call system(GetVimCommand('Xvimrc') .. ' --headless --noplugins -S Session.vim -c cq') let errors = join(readfile('Xerrors')) " This probably only ever matches on unix. call assert_notmatch('Caught deadly signal SEGV', errors) @@ -1506,7 +1506,7 @@ func Test_bufunload_all() call writefile(content, 'Xtest') call delete('Xout') - call system(v:progpath. ' -u NORC -i NONE -N -S Xtest') + call system(GetVimCommandClean() .. ' -N --headless -S Xtest') call assert_true(filereadable('Xout')) call delete('Xxx1') @@ -1827,6 +1827,14 @@ func Test_autocommand_all_events() call assert_fails('au * x bwipe', 'E1155:') endfunc +func Test_autocmd_user() + au User MyEvent let s:res = [expand("<afile>"), expand("<amatch>")] + doautocmd User MyEvent + call assert_equal(['MyEvent', 'MyEvent'], s:res) + au! User + unlet s:res +endfunc + function s:Before_test_dirchanged() augroup test_dirchanged autocmd! @@ -1850,14 +1858,16 @@ endfunc function Test_dirchanged_global() call s:Before_test_dirchanged() + autocmd test_dirchanged DirChangedPre global call add(s:li, expand("<amatch>") .. " pre cd " .. v:event.directory) autocmd test_dirchanged DirChanged global call add(s:li, "cd:") autocmd test_dirchanged DirChanged global call add(s:li, expand("<afile>")) call chdir(s:dir_foo) - call assert_equal(["cd:", s:dir_foo], s:li) + let expected = ["global pre cd " .. s:dir_foo, "cd:", s:dir_foo] + call assert_equal(expected, s:li) call chdir(s:dir_foo) - call assert_equal(["cd:", s:dir_foo], s:li) + call assert_equal(expected, s:li) exe 'lcd ' .. fnameescape(s:dir_bar) - call assert_equal(["cd:", s:dir_foo], s:li) + call assert_equal(expected, s:li) call s:After_test_dirchanged() endfunc @@ -1879,6 +1889,7 @@ function Test_dirchanged_auto() CheckOption autochdir call s:Before_test_dirchanged() call test_autochdir() + autocmd test_dirchanged DirChangedPre auto call add(s:li, "pre cd " .. v:event.directory) autocmd test_dirchanged DirChanged auto call add(s:li, "auto:") autocmd test_dirchanged DirChanged auto call add(s:li, expand("<afile>")) set acd @@ -1886,7 +1897,8 @@ function Test_dirchanged_auto() call assert_equal([], s:li) exe 'edit ' . s:dir_foo . '/Xfile' call assert_equal(s:dir_foo, getcwd()) - call assert_equal(["auto:", s:dir_foo], s:li) + let expected = ["pre cd " .. s:dir_foo, "auto:", s:dir_foo] + call assert_equal(expected, s:li) set noacd bwipe! call s:After_test_dirchanged() diff --git a/src/nvim/testdir/test_blockedit.vim b/src/nvim/testdir/test_blockedit.vim index 38978ef689..7b56b1554f 100644 --- a/src/nvim/testdir/test_blockedit.vim +++ b/src/nvim/testdir/test_blockedit.vim @@ -81,4 +81,52 @@ func Test_blockinsert_delete() bwipe! endfunc +func Test_blockappend_eol_cursor() + new + " Test 1 Move 1 char left + call setline(1, ['aaa', 'bbb', 'ccc']) + exe "norm! gg$\<c-v>2jA\<left>x\<esc>" + call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$')) + " Test 2 Move 2 chars left + sil %d + call setline(1, ['aaa', 'bbb', 'ccc']) + exe "norm! gg$\<c-v>2jA\<left>\<left>x\<esc>" + call assert_equal(['axaa', 'bxbb', 'cxcc'], getline(1, '$')) + " Test 3 Move 3 chars left (outside of the visual selection) + sil %d + call setline(1, ['aaa', 'bbb', 'ccc']) + exe "norm! ggl$\<c-v>2jA\<left>\<left>\<left>x\<esc>" + call assert_equal(['xaaa', 'bbb', 'ccc'], getline(1, '$')) + bw! +endfunc + +func Test_blockappend_eol_cursor2() + new + " Test 1 Move 1 char left + call setline(1, ['aaaaa', 'bbb', 'ccccc']) + exe "norm! gg\<c-v>$2jA\<left>x\<esc>" + call assert_equal(['aaaaxa', 'bbbx', 'ccccxc'], getline(1, '$')) + " Test 2 Move 2 chars left + sil %d + call setline(1, ['aaaaa', 'bbb', 'ccccc']) + exe "norm! gg\<c-v>$2jA\<left>\<left>x\<esc>" + call assert_equal(['aaaxaa', 'bbbx', 'cccxcc'], getline(1, '$')) + " Test 3 Move 3 chars left (to the beginning of the visual selection) + sil %d + call setline(1, ['aaaaa', 'bbb', 'ccccc']) + exe "norm! gg\<c-v>$2jA\<left>\<left>\<left>x\<esc>" + call assert_equal(['aaxaaa', 'bbxb', 'ccxccc'], getline(1, '$')) + " Test 4 Move 3 chars left (outside of the visual selection) + sil %d + call setline(1, ['aaaaa', 'bbb', 'ccccc']) + exe "norm! ggl\<c-v>$2jA\<left>\<left>\<left>x\<esc>" + call assert_equal(['aaxaaa', 'bbxb', 'ccxccc'], getline(1, '$')) + " Test 5 Move 4 chars left (outside of the visual selection) + sil %d + call setline(1, ['aaaaa', 'bbb', 'ccccc']) + exe "norm! ggl\<c-v>$2jA\<left>\<left>\<left>\<left>x\<esc>" + call assert_equal(['axaaaa', 'bxbb', 'cxcccc'], getline(1, '$')) + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index b619f2adb6..438edb0257 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -1,7 +1,7 @@ " Test for breakindent " " Note: if you get strange failures when adding new tests, it might be that -" while the test is run, the breakindent cacheing gets in its way. +" while the test is run, the breakindent caching gets in its way. " It helps to change the tabstop setting and force a redraw (e.g. see " Test_breakindent08()) if !exists('+breakindent') diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index 5dc54111e7..4b702bf2b8 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -815,7 +815,7 @@ func Test_cindent_1() } } - public: // <-- this was incoreectly indented before!! + public: // <-- this was incorrectly indented before!! void testfall(); protected: void testfall(); @@ -1792,7 +1792,7 @@ func Test_cindent_1() } } - public: // <-- this was incoreectly indented before!! + public: // <-- this was incorrectly indented before!! void testfall(); protected: void testfall(); @@ -5302,9 +5302,12 @@ endfunc " this was going beyond the end of the line. func Test_cindent_case() new - call setline(1, "case x: // x") + call setline(1, 'case x: // x') set cindent norm! f:a: + call assert_equal('case x:: // x', getline(1)) + + set cindent& bwipe! endfunc diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index d23748a3e3..5965ee48ef 100644 --- a/src/nvim/testdir/test_digraph.vim +++ b/src/nvim/testdir/test_digraph.vim @@ -81,7 +81,7 @@ func Test_digraphs() call Put_Dig(".e") call Put_Dig("a.") " not defined call assert_equal(['ḃ', 'ė', '.'], getline(line('.')-2,line('.'))) - " Diaresis + " Diaeresis call Put_Dig("a:") call Put_Dig(":u") call Put_Dig("b:") " not defined @@ -288,7 +288,7 @@ func Test_digraphs_option() call Put_Dig_BS(".","e") call Put_Dig_BS("a",".") " not defined call assert_equal(['ḃ', 'ė', '.'], getline(line('.')-2,line('.'))) - " Diaresis + " Diaeresis call Put_Dig_BS("a",":") call Put_Dig_BS(":","u") call Put_Dig_BS("b",":") " not defined diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index a1f6a84a99..360b3aaaa0 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -590,7 +590,7 @@ func Test_edit_CTRL_K() call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<down>\<cr>\<esc>", 'tnix') call assert_equal(['AA'], getline(1, '$')) - " press an unexecpted key after dictionary completion + " press an unexpected key after dictionary completion %d call setline(1, 'A') call cursor(1, 1) diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index bbf8b4dfc8..8055a51a11 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -409,4 +409,18 @@ func Test_not_break_expression_register() call assert_equal('1+1', getreg('=', 1)) endfunc +func Test_address_line_overflow() + throw 'Skipped: v:sizeoflong is N/A' " use legacy/excmd_spec.lua instead + + if v:sizeoflong < 8 + throw 'Skipped: only works with 64 bit long ints' + endif + new + call setline(1, 'text') + call assert_fails('|.44444444444444444444444', 'E1247:') + call assert_fails('|.9223372036854775806', 'E1247:') + bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim index b4c32fcc16..06ccd6e85f 100644 --- a/src/nvim/testdir/test_filechanged.vim +++ b/src/nvim/testdir/test_filechanged.vim @@ -1,9 +1,10 @@ " Tests for when a file was changed outside of Vim. +source check.vim + func Test_FileChangedShell_reload() - if !has('unix') - return - endif + CheckUnix + augroup testreload au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload' augroup END @@ -90,11 +91,107 @@ func Test_FileChangedShell_reload() call delete('Xchanged_r') endfunc +func Test_FileChangedShell_edit() + CheckUnix + + new Xchanged_r + call setline(1, 'reload this') + set fileformat=unix + write + + " File format changed, reload (content only, no 'ff' etc) + augroup testreload + au! + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload' + augroup END + call assert_equal(&fileformat, 'unix') + sleep 10m " make the test less flaky in Nvim + call writefile(["line1\r", "line2\r"], 'Xchanged_r') + let g:reason = '' + checktime + call assert_equal('changed', g:reason) + call assert_equal(&fileformat, 'unix') + call assert_equal("line1\r", getline(1)) + call assert_equal("line2\r", getline(2)) + %s/\r + write + + " File format changed, reload with 'ff', etc + augroup testreload + au! + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'edit' + augroup END + call assert_equal(&fileformat, 'unix') + sleep 10m " make the test less flaky in Nvim + call writefile(["line1\r", "line2\r"], 'Xchanged_r') + let g:reason = '' + checktime + call assert_equal('changed', g:reason) + call assert_equal(&fileformat, 'dos') + call assert_equal('line1', getline(1)) + call assert_equal('line2', getline(2)) + set fileformat=unix + write + + au! testreload + bwipe! + call delete(undofile('Xchanged_r')) + call delete('Xchanged_r') +endfunc + +func Test_FileChangedShell_edit_dialog() + throw 'Skipped: requires a UI to be active' + CheckNotGui + + new Xchanged_r + call setline(1, 'reload this') + set fileformat=unix + write + + " File format changed, reload (content only) via prompt + augroup testreload + au! + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask' + augroup END + call assert_equal(&fileformat, 'unix') + call writefile(["line1\r", "line2\r"], 'Xchanged_r') + let g:reason = '' + call feedkeys('L', 'L') " load file content only + checktime + call assert_equal('changed', g:reason) + call assert_equal(&fileformat, 'unix') + call assert_equal("line1\r", getline(1)) + call assert_equal("line2\r", getline(2)) + %s/\r + write + + " File format changed, reload (file and options) via prompt + augroup testreload + au! + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask' + augroup END + call assert_equal(&fileformat, 'unix') + call writefile(["line1\r", "line2\r"], 'Xchanged_r') + let g:reason = '' + call feedkeys('a', 'L') " load file content and options + checktime + call assert_equal('changed', g:reason) + call assert_equal(&fileformat, 'dos') + call assert_equal("line1", getline(1)) + call assert_equal("line2", getline(2)) + set fileformat=unix + write + + au! testreload + bwipe! + call delete(undofile('Xchanged_r')) + call delete('Xchanged_r') +endfunc + func Test_file_changed_dialog() - throw 'Skipped: requires a UI to a active' - if !has('unix') || has('gui_running') - return - endif + throw 'Skipped: requires a UI to be active' + CheckUnix + CheckNotGui au! FileChangedShell new Xchanged_d diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index b663032c24..5f4a7dac6e 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -81,6 +81,7 @@ let s:filename_checks = { \ 'bc': ['file.bc'], \ 'bdf': ['file.bdf'], \ 'bib': ['file.bib'], + \ 'bicep': ['file.bicep'], \ 'beancount': ['file.beancount'], \ 'bindzone': ['named.root', '/bind/db.file', '/named/db.file', 'any/bind/db.file', 'any/named/db.file'], \ 'blank': ['file.bl'], @@ -131,6 +132,7 @@ let s:filename_checks = { \ 'cvs': ['cvs123'], \ 'cvsrc': ['.cvsrc'], \ 'cynpp': ['file.cyn'], + \ 'd': ['file.d'], \ 'dart': ['file.dart', 'file.drt'], \ 'datascript': ['file.ds'], \ 'dcd': ['file.dcd'], @@ -153,6 +155,7 @@ let s:filename_checks = { \ 'dot': ['file.dot', 'file.gv'], \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'], \ 'dtd': ['file.dtd'], + \ 'dtrace': ['/usr/lib/dtrace/io.d'], \ 'dts': ['file.dts', 'file.dtsi'], \ 'dune': ['jbuild', 'dune', 'dune-project', 'dune-workspace'], \ 'dylan': ['file.dylan'], @@ -271,6 +274,7 @@ let s:filename_checks = { \ 'java': ['file.java', 'file.jav'], \ 'javacc': ['file.jj', 'file.jjt'], \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'], + \ 'javascript.glimmer': ['file.gjs'], \ 'javascriptreact': ['file.jsx'], \ 'jess': ['file.clp'], \ 'jgraph': ['file.jgr'], @@ -477,6 +481,7 @@ let s:filename_checks = { \ 'skill': ['file.il', 'file.ils', 'file.cdf'], \ 'slang': ['file.sl'], \ 'slice': ['file.ice'], + \ 'solidity': ['file.sol'], \ 'solution': ['file.sln'], \ 'slpconf': ['/etc/slp.conf', 'any/etc/slp.conf'], \ 'slpreg': ['/etc/slp.reg', 'any/etc/slp.reg'], @@ -544,6 +549,7 @@ let s:filename_checks = { \ 'tssgm': ['file.tssgm'], \ 'tssop': ['file.tssop'], \ 'twig': ['file.twig'], + \ 'typescript.glimmer': ['file.gts'], \ 'typescriptreact': ['file.tsx'], \ 'uc': ['file.uc'], \ 'udevconf': ['/etc/udev/udev.conf', 'any/etc/udev/udev.conf'], @@ -798,6 +804,42 @@ func Test_bas_file() filetype off endfunc +func Test_d_file() + filetype on + + call writefile(['looks like D'], 'Xfile.d') + split Xfile.d + call assert_equal('d', &filetype) + bwipe! + + call writefile(['#!/some/bin/dtrace'], 'Xfile.d') + split Xfile.d + call assert_equal('dtrace', &filetype) + bwipe! + + call writefile(['#pragma D option'], 'Xfile.d') + split Xfile.d + call assert_equal('dtrace', &filetype) + bwipe! + + call writefile([':some:thing:'], 'Xfile.d') + split Xfile.d + call assert_equal('dtrace', &filetype) + bwipe! + + call writefile(['module this', '#pragma D option'], 'Xfile.d') + split Xfile.d + call assert_equal('d', &filetype) + bwipe! + + call writefile(['import that', '#pragma D option'], 'Xfile.d') + split Xfile.d + call assert_equal('d', &filetype) + bwipe! + + filetype off +endfunc + func Test_dep3patch_file() filetype on diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 438bed51c6..994d74601a 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -332,37 +332,6 @@ func Test_simplify() call assert_fails('call simplify(1.2)', 'E806:') endfunc -func Test_setbufvar_options() - " This tests that aucmd_prepbuf() and aucmd_restbuf() properly restore the - " window layout. - call assert_equal(1, winnr('$')) - split dummy_preview - resize 2 - set winfixheight winfixwidth - let prev_id = win_getid() - - wincmd j - let wh = winheight(0) - let dummy_buf = bufnr('dummy_buf1', v:true) - call setbufvar(dummy_buf, '&buftype', 'nofile') - execute 'belowright vertical split #' . dummy_buf - call assert_equal(wh, winheight(0)) - let dum1_id = win_getid() - - wincmd h - let wh = winheight(0) - let dummy_buf = bufnr('dummy_buf2', v:true) - eval 'nofile'->setbufvar(dummy_buf, '&buftype') - execute 'belowright vertical split #' . dummy_buf - call assert_equal(wh, winheight(0)) - - bwipe! - call win_gotoid(prev_id) - bwipe! - call win_gotoid(dum1_id) - bwipe! -endfunc - func Test_pathshorten() call assert_equal('', pathshorten('')) call assert_equal('foo', pathshorten('foo')) @@ -1293,6 +1262,37 @@ func Test_shellescape() let &shell = save_shell endfunc +func Test_setbufvar_options() + " This tests that aucmd_prepbuf() and aucmd_restbuf() properly restore the + " window layout. + call assert_equal(1, winnr('$')) + split dummy_preview + resize 2 + set winfixheight winfixwidth + let prev_id = win_getid() + + wincmd j + let wh = winheight(0) + let dummy_buf = bufnr('dummy_buf1', v:true) + call setbufvar(dummy_buf, '&buftype', 'nofile') + execute 'belowright vertical split #' . dummy_buf + call assert_equal(wh, winheight(0)) + let dum1_id = win_getid() + + wincmd h + let wh = winheight(0) + let dummy_buf = bufnr('dummy_buf2', v:true) + eval 'nofile'->setbufvar(dummy_buf, '&buftype') + execute 'belowright vertical split #' . dummy_buf + call assert_equal(wh, winheight(0)) + + bwipe! + call win_gotoid(prev_id) + bwipe! + call win_gotoid(dum1_id) + bwipe! +endfunc + func Test_redo_in_nested_functions() nnoremap g. :set opfunc=Operator<CR>g@ function Operator( type, ... ) @@ -1350,98 +1350,6 @@ func Test_trim() call assert_equal("x", trim(chars . "x" . chars)) endfunc -func EditAnotherFile() - let word = expand('<cword>') - edit Xfuncrange2 -endfunc - -func Test_func_range_with_edit() - " Define a function that edits another buffer, then call it with a range that - " is invalid in that buffer. - call writefile(['just one line'], 'Xfuncrange2') - new - eval 10->range()->setline(1) - write Xfuncrange1 - call assert_fails('5,8call EditAnotherFile()', 'E16:') - - call delete('Xfuncrange1') - call delete('Xfuncrange2') - bwipe! -endfunc - -func Test_func_exists_on_reload() - call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists') - call assert_equal(0, exists('*ExistingFunction')) - source Xfuncexists - call assert_equal(1, '*ExistingFunction'->exists()) - " Redefining a function when reloading a script is OK. - source Xfuncexists - call assert_equal(1, exists('*ExistingFunction')) - - " But redefining in another script is not OK. - call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists2') - call assert_fails('source Xfuncexists2', 'E122:') - - delfunc ExistingFunction - call assert_equal(0, exists('*ExistingFunction')) - call writefile([ - \ 'func ExistingFunction()', 'echo "yes"', 'endfunc', - \ 'func ExistingFunction()', 'echo "no"', 'endfunc', - \ ], 'Xfuncexists') - call assert_fails('source Xfuncexists', 'E122:') - call assert_equal(1, exists('*ExistingFunction')) - - call delete('Xfuncexists2') - call delete('Xfuncexists') - delfunc ExistingFunction -endfunc - -func Test_platform_name() - " The system matches at most only one name. - let names = ['amiga', 'beos', 'bsd', 'hpux', 'linux', 'mac', 'qnx', 'sun', 'vms', 'win32', 'win32unix'] - call assert_inrange(0, 1, len(filter(copy(names), 'has(v:val)'))) - - " Is Unix? - call assert_equal(has('beos'), has('beos') && has('unix')) - call assert_equal(has('bsd'), has('bsd') && has('unix')) - call assert_equal(has('hpux'), has('hpux') && has('unix')) - call assert_equal(has('linux'), has('linux') && has('unix')) - call assert_equal(has('mac'), has('mac') && has('unix')) - call assert_equal(has('qnx'), has('qnx') && has('unix')) - call assert_equal(has('sun'), has('sun') && has('unix')) - call assert_equal(has('win32'), has('win32') && !has('unix')) - call assert_equal(has('win32unix'), has('win32unix') && has('unix')) - - if has('unix') && executable('uname') - let uname = system('uname') - call assert_equal(uname =~? 'BeOS', has('beos')) - " GNU userland on BSD kernels (e.g., GNU/kFreeBSD) don't have BSD defined - call assert_equal(uname =~? '\%(GNU/k\w\+\)\@<!BSD\|DragonFly', has('bsd')) - call assert_equal(uname =~? 'HP-UX', has('hpux')) - call assert_equal(uname =~? 'Linux', has('linux')) - call assert_equal(uname =~? 'Darwin', has('mac')) - call assert_equal(uname =~? 'QNX', has('qnx')) - call assert_equal(uname =~? 'SunOS', has('sun')) - call assert_equal(uname =~? 'CYGWIN\|MSYS', has('win32unix')) - endif -endfunc - -sandbox function Fsandbox() - normal ix -endfunc - -func Test_func_sandbox() - sandbox let F = {-> 'hello'} - call assert_equal('hello', F()) - - sandbox let F = {-> "normal ix\<Esc>"->execute()} - call assert_fails('call F()', 'E48:') - unlet F - - call assert_fails('call Fsandbox()', 'E48:') - delfunc Fsandbox -endfunc - " Test for reg_recording() and reg_executing() func Test_reg_executing_and_recording() let s:reg_stat = '' @@ -1543,6 +1451,10 @@ func Test_getchar() call assert_equal('', getcharstr(0)) call assert_equal('', getcharstr(1)) + call feedkeys("\<M-F2>", '') + call assert_equal("\<M-F2>", getchar(0)) + call assert_equal(0, getchar(0)) + call setline(1, 'xxxx') " call test_setmouse(1, 3) " let v:mouse_win = 9 @@ -1615,47 +1527,96 @@ func Test_libcall_libcallnr() call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", 'E364:') endfunc -func Test_bufadd_bufload() - call assert_equal(0, bufexists('someName')) - let buf = bufadd('someName') - call assert_notequal(0, buf) - call assert_equal(1, bufexists('someName')) - call assert_equal(0, getbufvar(buf, '&buflisted')) - call assert_equal(0, bufloaded(buf)) - call bufload(buf) - call assert_equal(1, bufloaded(buf)) - call assert_equal([''], getbufline(buf, 1, '$')) +sandbox function Fsandbox() + normal ix +endfunc - let curbuf = bufnr('') - eval ['some', 'text']->writefile('XotherName') - let buf = 'XotherName'->bufadd() - call assert_notequal(0, buf) - eval 'XotherName'->bufexists()->assert_equal(1) - call assert_equal(0, getbufvar(buf, '&buflisted')) - call assert_equal(0, bufloaded(buf)) - eval buf->bufload() - call assert_equal(1, bufloaded(buf)) - call assert_equal(['some', 'text'], getbufline(buf, 1, '$')) - call assert_equal(curbuf, bufnr('')) +func Test_func_sandbox() + sandbox let F = {-> 'hello'} + call assert_equal('hello', F()) - let buf1 = bufadd('') - let buf2 = bufadd('') - call assert_notequal(0, buf1) - call assert_notequal(0, buf2) - call assert_notequal(buf1, buf2) - call assert_equal(1, bufexists(buf1)) - call assert_equal(1, bufexists(buf2)) - call assert_equal(0, bufloaded(buf1)) - exe 'bwipe ' .. buf1 - call assert_equal(0, bufexists(buf1)) - call assert_equal(1, bufexists(buf2)) - exe 'bwipe ' .. buf2 - call assert_equal(0, bufexists(buf2)) + sandbox let F = {-> "normal ix\<Esc>"->execute()} + call assert_fails('call F()', 'E48:') + unlet F - bwipe someName - bwipe XotherName - call assert_equal(0, bufexists('someName')) - call delete('XotherName') + call assert_fails('call Fsandbox()', 'E48:') + delfunc Fsandbox +endfunc + +func EditAnotherFile() + let word = expand('<cword>') + edit Xfuncrange2 +endfunc + +func Test_func_range_with_edit() + " Define a function that edits another buffer, then call it with a range that + " is invalid in that buffer. + call writefile(['just one line'], 'Xfuncrange2') + new + eval 10->range()->setline(1) + write Xfuncrange1 + call assert_fails('5,8call EditAnotherFile()', 'E16:') + + call delete('Xfuncrange1') + call delete('Xfuncrange2') + bwipe! +endfunc + +func Test_func_exists_on_reload() + call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists') + call assert_equal(0, exists('*ExistingFunction')) + source Xfuncexists + call assert_equal(1, '*ExistingFunction'->exists()) + " Redefining a function when reloading a script is OK. + source Xfuncexists + call assert_equal(1, exists('*ExistingFunction')) + + " But redefining in another script is not OK. + call writefile(['func ExistingFunction()', 'echo "yes"', 'endfunc'], 'Xfuncexists2') + call assert_fails('source Xfuncexists2', 'E122:') + + delfunc ExistingFunction + call assert_equal(0, exists('*ExistingFunction')) + call writefile([ + \ 'func ExistingFunction()', 'echo "yes"', 'endfunc', + \ 'func ExistingFunction()', 'echo "no"', 'endfunc', + \ ], 'Xfuncexists') + call assert_fails('source Xfuncexists', 'E122:') + call assert_equal(1, exists('*ExistingFunction')) + + call delete('Xfuncexists2') + call delete('Xfuncexists') + delfunc ExistingFunction +endfunc + +func Test_platform_name() + " The system matches at most only one name. + let names = ['amiga', 'beos', 'bsd', 'hpux', 'linux', 'mac', 'qnx', 'sun', 'vms', 'win32', 'win32unix'] + call assert_inrange(0, 1, len(filter(copy(names), 'has(v:val)'))) + + " Is Unix? + call assert_equal(has('beos'), has('beos') && has('unix')) + call assert_equal(has('bsd'), has('bsd') && has('unix')) + call assert_equal(has('hpux'), has('hpux') && has('unix')) + call assert_equal(has('linux'), has('linux') && has('unix')) + call assert_equal(has('mac'), has('mac') && has('unix')) + call assert_equal(has('qnx'), has('qnx') && has('unix')) + call assert_equal(has('sun'), has('sun') && has('unix')) + call assert_equal(has('win32'), has('win32') && !has('unix')) + call assert_equal(has('win32unix'), has('win32unix') && has('unix')) + + if has('unix') && executable('uname') + let uname = system('uname') + call assert_equal(uname =~? 'BeOS', has('beos')) + " GNU userland on BSD kernels (e.g., GNU/kFreeBSD) don't have BSD defined + call assert_equal(uname =~? '\%(GNU/k\w\+\)\@<!BSD\|DragonFly', has('bsd')) + call assert_equal(uname =~? 'HP-UX', has('hpux')) + call assert_equal(uname =~? 'Linux', has('linux')) + call assert_equal(uname =~? 'Darwin', has('mac')) + call assert_equal(uname =~? 'QNX', has('qnx')) + call assert_equal(uname =~? 'SunOS', has('sun')) + call assert_equal(uname =~? 'CYGWIN\|MSYS', has('win32unix')) + endif endfunc func Test_readdir() @@ -1714,6 +1675,49 @@ func Test_eventhandler() call assert_equal(0, eventhandler()) endfunc +func Test_bufadd_bufload() + call assert_equal(0, bufexists('someName')) + let buf = bufadd('someName') + call assert_notequal(0, buf) + call assert_equal(1, bufexists('someName')) + call assert_equal(0, getbufvar(buf, '&buflisted')) + call assert_equal(0, bufloaded(buf)) + call bufload(buf) + call assert_equal(1, bufloaded(buf)) + call assert_equal([''], getbufline(buf, 1, '$')) + + let curbuf = bufnr('') + eval ['some', 'text']->writefile('XotherName') + let buf = 'XotherName'->bufadd() + call assert_notequal(0, buf) + eval 'XotherName'->bufexists()->assert_equal(1) + call assert_equal(0, getbufvar(buf, '&buflisted')) + call assert_equal(0, bufloaded(buf)) + eval buf->bufload() + call assert_equal(1, bufloaded(buf)) + call assert_equal(['some', 'text'], getbufline(buf, 1, '$')) + call assert_equal(curbuf, bufnr('')) + + let buf1 = bufadd('') + let buf2 = bufadd('') + call assert_notequal(0, buf1) + call assert_notequal(0, buf2) + call assert_notequal(buf1, buf2) + call assert_equal(1, bufexists(buf1)) + call assert_equal(1, bufexists(buf2)) + call assert_equal(0, bufloaded(buf1)) + exe 'bwipe ' .. buf1 + call assert_equal(0, bufexists(buf1)) + call assert_equal(1, bufexists(buf2)) + exe 'bwipe ' .. buf2 + call assert_equal(0, bufexists(buf2)) + + bwipe someName + bwipe XotherName + call assert_equal(0, bufexists('someName')) + call delete('XotherName') +endfunc + " Test for the eval() function func Test_eval() call assert_fails("call eval('5 a')", 'E488:') diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 5b7cf6fee5..f45cd96733 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1118,7 +1118,7 @@ func Test_normal20_exmode() endif call writefile(['1a', 'foo', 'bar', '.', 'w! Xfile2', 'q!'], 'Xscript') call writefile(['1', '2'], 'Xfile') - call system(v:progpath .' -e -s < Xscript Xfile') + call system(GetVimCommand() .. ' -e -s < Xscript Xfile') let a=readfile('Xfile2') call assert_equal(['1', 'foo', 'bar', '2'], a) @@ -1171,13 +1171,13 @@ func Test_normal22_zet() endfor call writefile(['1', '2'], 'Xfile_Test_normal22_zet') - let args = ' --headless -u NONE -N -U NONE -i NONE --noplugins' - call system(v:progpath . args . ' -c "%d" -c ":norm! ZZ" Xfile_Test_normal22_zet') + let args = ' -N -i NONE --noplugins -X --headless' + call system(GetVimCommand() .. args .. ' -c "%d" -c ":norm! ZZ" Xfile_Test_normal22_zet') let a = readfile('Xfile_Test_normal22_zet') call assert_equal([], a) " Test for ZQ call writefile(['1', '2'], 'Xfile_Test_normal22_zet') - call system(v:progpath . args . ' -c "%d" -c ":norm! ZQ" Xfile_Test_normal22_zet') + call system(GetVimCommand() . args . ' -c "%d" -c ":norm! ZQ" Xfile_Test_normal22_zet') let a = readfile('Xfile_Test_normal22_zet') call assert_equal(['1', '2'], a) @@ -2779,4 +2779,25 @@ func Test_normal_gj_on_extra_wide_char() bw! endfunc +func Test_normal_count_out_of_range() + new + call setline(1, 'text') + normal 44444444444| + call assert_equal(999999999, v:count) + normal 444444444444| + call assert_equal(999999999, v:count) + normal 4444444444444| + call assert_equal(999999999, v:count) + normal 4444444444444444444| + call assert_equal(999999999, v:count) + + normal 9y99999999| + call assert_equal(899999991, v:count) + normal 10y99999999| + call assert_equal(999999999, v:count) + normal 44444444444y44444444444| + call assert_equal(999999999, v:count) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim index 4b0097617e..fdb6f13e2b 100644 --- a/src/nvim/testdir/test_profile.vim +++ b/src/nvim/testdir/test_profile.vim @@ -1,8 +1,9 @@ " Test Vim profiler -if !has('profile') - finish -endif +source check.vim +CheckFeature profile + +source shared.vim source screendump.vim func Test_profile_func() @@ -37,7 +38,7 @@ func Test_profile_func() [CODE] call writefile(lines, 'Xprofile_func.vim') - call system(v:progpath + call system(GetVimCommand() \ . ' -es --clean' \ . ' -c "so Xprofile_func.vim"' \ . ' -c "qall!"') @@ -124,8 +125,8 @@ func Test_profile_func_with_ifelse() [CODE] call writefile(lines, 'Xprofile_func.vim') - call system(v:progpath - \ . ' -es -u NONE -U NONE -i NONE --noplugin' + call system(GetVimCommand() + \ . ' -es -i NONE --noplugin' \ . ' -c "profile start Xprofile_func.log"' \ . ' -c "profile func Foo*"' \ . ' -c "so Xprofile_func.vim"' @@ -237,8 +238,8 @@ func Test_profile_func_with_trycatch() [CODE] call writefile(lines, 'Xprofile_func.vim') - call system(v:progpath - \ . ' -es -u NONE -U NONE -i NONE --noplugin' + call system(GetVimCommand() + \ . ' -es -i NONE --noplugin' \ . ' -c "profile start Xprofile_func.log"' \ . ' -c "profile func Foo*"' \ . ' -c "so Xprofile_func.vim"' @@ -324,8 +325,8 @@ func Test_profile_file() [CODE] call writefile(lines, 'Xprofile_file.vim') - call system(v:progpath - \ . ' -es --clean' + call system(GetVimCommandClean() + \ . ' -es' \ . ' -c "profile start Xprofile_file.log"' \ . ' -c "profile file Xprofile_file.vim"' \ . ' -c "so Xprofile_file.vim"' @@ -369,8 +370,8 @@ func Test_profile_file_with_cont() \ ] call writefile(lines, 'Xprofile_file.vim') - call system(v:progpath - \ . ' -es -u NONE -U NONE -i NONE --noplugin' + call system(GetVimCommandClean() + \ . ' -es' \ . ' -c "profile start Xprofile_file.log"' \ . ' -c "profile file Xprofile_file.vim"' \ . ' -c "so Xprofile_file.vim"' @@ -427,7 +428,7 @@ func Test_profile_truncate_mbyte() \ ] call writefile(lines, 'Xprofile_file.vim') - call system(v:progpath + call system(GetVimCommandClean() \ . ' -es --cmd "set enc=utf-8"' \ . ' -c "profile start Xprofile_file.log"' \ . ' -c "profile file Xprofile_file.vim"' @@ -474,7 +475,7 @@ func Test_profdel_func() call Foo3() [CODE] call writefile(lines, 'Xprofile_file.vim') - call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q') + call system(GetVimCommandClean() . ' -es -c "so Xprofile_file.vim" -c q') call assert_equal(0, v:shell_error) let lines = readfile('Xprofile_file.log') @@ -509,7 +510,7 @@ func Test_profdel_star() call Foo() [CODE] call writefile(lines, 'Xprofile_file.vim') - call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q') + call system(GetVimCommandClean() . ' -es -c "so Xprofile_file.vim" -c q') call assert_equal(0, v:shell_error) let lines = readfile('Xprofile_file.log') diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim index ed76709a56..65232175c6 100644 --- a/src/nvim/testdir/test_put.vim +++ b/src/nvim/testdir/test_put.vim @@ -138,6 +138,50 @@ func Test_p_with_count_leaves_mark_at_end() bwipe! endfunc +func Test_very_large_count() + new + " total put-length (21474837 * 100) brings 32 bit int overflow + let @" = repeat('x', 100) + call assert_fails('norm 21474837p', 'E1240:') + bwipe! +endfunc + +func Test_very_large_count_64bit() + throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead + + if v:sizeoflong < 8 + throw 'Skipped: only works with 64 bit long ints' + endif + + new + let @" = repeat('x', 100) + call assert_fails('norm 999999999p', 'E1240:') + bwipe! +endfunc + +func Test_very_large_count_block() + new + " total put-length (21474837 * 100) brings 32 bit int overflow + call setline(1, repeat('x', 100)) + exe "norm \<C-V>99ly" + call assert_fails('norm 21474837p', 'E1240:') + bwipe! +endfunc + +func Test_very_large_count_block_64bit() + throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead + + if v:sizeoflong < 8 + throw 'Skipped: only works with 64 bit long ints' + endif + + new + call setline(1, repeat('x', 100)) + exe "norm \<C-V>$y" + call assert_fails('norm 999999999p', 'E1240:') + bwipe! +endfunc + func Test_put_above_first_line() new let @" = 'text' diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index c4d70fb1de..6852f53ea8 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -2739,7 +2739,7 @@ func Test_cwindow_jump() call assert_true(winnr('$') == 2) call assert_true(winnr() == 1) - " Jumping to a file from the location list window should find a usuable + " Jumping to a file from the location list window should find a usable " window by wrapping around the window list. enew | only call setloclist(0, [], 'f') diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 2e92d9aa2f..23e39eba35 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -219,7 +219,7 @@ func Test_set_register() call setreg('=', 'b', 'a') call assert_equal('regwrite', getreg('=')) - " Test for settting a list of lines to special registers + " Test for setting a list of lines to special registers call setreg('/', []) call assert_equal('', @/) call setreg('=', []) diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim index b44f3e9b94..d3059664e9 100644 --- a/src/nvim/testdir/test_stat.vim +++ b/src/nvim/testdir/test_stat.vim @@ -7,7 +7,7 @@ func CheckFileTime(doSleep) let times = [] let result = 0 - " Use three files istead of localtim(), with a network filesystem the file + " Use three files instead of localtim(), with a network filesystem the file " times may differ at bit let fl = ['Hello World!'] for fname in fnames diff --git a/src/nvim/testdir/test_suspend.vim b/src/nvim/testdir/test_suspend.vim index 4b3bd5eadf..bf88bd4453 100644 --- a/src/nvim/testdir/test_suspend.vim +++ b/src/nvim/testdir/test_suspend.vim @@ -26,8 +26,8 @@ func Test_suspend() " Wait for shell prompt. call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))}) - call term_sendkeys(buf, v:progpath - \ . " --clean -X" + call term_sendkeys(buf, GetVimCommandClean() + \ . " -X" \ . " -c 'set nu'" \ . " -c 'call setline(1, \"foo\")'" \ . " Xfoo\<CR>") diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index 5b8079d7b6..18692f42c9 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -50,11 +50,11 @@ endfunc func Test_system_exmode() if has('unix') " echo $? only works on Unix - let cmd = ' -es --headless -u NONE -c "source Xscript" +q; echo "result=$?"' + let cmd = ' -es -c "source Xscript" +q; echo "result=$?"' " Need to put this in a script, "catch" isn't found after an unknown " function. call writefile(['try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript') - let a = system(v:progpath . cmd) + let a = system(GetVimCommand() . cmd) call assert_match('result=0', a) call assert_equal(0, v:shell_error) endif @@ -62,33 +62,33 @@ func Test_system_exmode() " Error before try does set error flag. call writefile(['call nosuchfunction()', 'try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript') if has('unix') " echo $? only works on Unix - let a = system(v:progpath . cmd) + let a = system(GetVimCommand() . cmd) call assert_notequal('0', a[0]) endif - let cmd = ' -es --headless -u NONE -c "source Xscript" +q' - let a = system(v:progpath . cmd) + let cmd = ' -es -c "source Xscript" +q' + let a = system(GetVimCommand() . cmd) call assert_notequal(0, v:shell_error) call delete('Xscript') if has('unix') " echo $? only works on Unix - let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q; echo $?' - let a = system(v:progpath. cmd) + let cmd = ' -es -c "call doesnotexist()" +q; echo $?' + let a = system(GetVimCommand() . cmd) call assert_notequal(0, a[0]) endif - let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q' - let a = system(v:progpath. cmd) + let cmd = ' -es -c "call doesnotexist()" +q' + let a = system(GetVimCommand(). cmd) call assert_notequal(0, v:shell_error) if has('unix') " echo $? only works on Unix - let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q; echo $?' - let a = system(v:progpath. cmd) + let cmd = ' -es -c "call doesnotexist()|let a=1" +q; echo $?' + let a = system(GetVimCommand() . cmd) call assert_notequal(0, a[0]) endif - let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q' - let a = system(v:progpath. cmd) + let cmd = ' -es -c "call doesnotexist()|let a=1" +q' + let a = system(GetVimCommand() . cmd) call assert_notequal(0, v:shell_error) endfunc diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim index 117d962d08..3a18206078 100644 --- a/src/nvim/testdir/test_tabline.vim +++ b/src/nvim/testdir/test_tabline.vim @@ -86,6 +86,17 @@ func Test_tabline_empty_group() set tabline= endfunc +" When there are exactly 20 tabline format items (the exact size of the +" initial tabline items array), test that we don't write beyond the size +" of the array. +func Test_tabline_20_format_items_no_overrun() + set showtabline=2 + + let tabline = repeat('%#StatColorHi2#', 20) + let &tabline = tabline + redrawtabline + set showtabline& tabline& +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim index 36776d5a64..9b010a5dbc 100644 --- a/src/nvim/testdir/test_utf8.vim +++ b/src/nvim/testdir/test_utf8.vim @@ -8,7 +8,7 @@ func Test_visual_block_insert() new call setline(1, ["aaa", "あああ", "bbb"]) exe ":norm! gg0l\<C-V>jjIx\<Esc>" - call assert_equal(['axaa', 'xあああ', 'bxbb'], getline(1, '$')) + call assert_equal(['axaa', ' xあああ', 'bxbb'], getline(1, '$')) bwipeout! endfunc diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 75a965f16d..f93eb6e274 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1,6 +1,9 @@ " Test various aspects of the Vim script language. " Most of this was formerly in test49. +source check.vim +source shared.vim + "------------------------------------------------------------------------------- " Test environment {{{1 "------------------------------------------------------------------------------- @@ -1744,7 +1747,7 @@ func Test_function_defined_line() [CODE] call writefile(lines, 'Xtest.vim') - let res = system(v:progpath .. ' --clean -es -X -S Xtest.vim') + let res = system(GetVimCommandClean() .. ' -es -X -S Xtest.vim') call assert_equal(0, v:shell_error) let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*') diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 099a90643f..ae8f9ba70b 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -22,6 +22,14 @@ func Test_block_shift_overflow() q! endfunc +func Test_dotregister_paste() + new + exe "norm! ihello world\<esc>" + norm! 0ve".p + call assert_equal('hello world world', getline(1)) + q! +endfunc + func Test_Visual_ctrl_o() new call setline(1, ['one', 'two', 'three']) @@ -42,14 +50,6 @@ func Test_Visual_vapo() bwipe! endfunc -func Test_dotregister_paste() - new - exe "norm! ihello world\<esc>" - norm! 0ve".p - call assert_equal('hello world world', getline(1)) - q! -endfunc - func Test_Visual_inner_quote() new normal oxX @@ -57,9 +57,37 @@ func Test_Visual_inner_quote() bwipe! endfunc +" Test for Visual mode not being reset causing E315 error. +func TriggerTheProblem() + " At this point there is no visual selection because :call reset it. + " Let's restore the selection: + normal gv + '<,'>del _ + try + exe "normal \<Esc>" + catch /^Vim\%((\a\+)\)\=:E315/ + echom 'Snap! E315 error!' + let g:msg = 'Snap! E315 error!' + endtry +endfunc + +func Test_visual_mode_reset() + enew + let g:msg = "Everything's fine." + enew + setl buftype=nofile + call append(line('$'), 'Delete this line.') + + " NOTE: this has to be done by a call to a function because executing :del + " the ex-way will require the colon operator which resets the visual mode + " thus preventing the problem: + exe "normal! GV:call TriggerTheProblem()\<CR>" + call assert_equal("Everything's fine.", g:msg) +endfunc + " Test for visual block shift and tab characters. func Test_block_shift_tab() - enew! + new call append(0, repeat(['one two three'], 5)) call cursor(1,1) exe "normal i\<C-G>u" @@ -68,7 +96,7 @@ func Test_block_shift_tab() call assert_equal('on1 two three', getline(2)) call assert_equal('on1 two three', getline(5)) - enew! + %d _ call append(0, repeat(['abcdefghijklmnopqrstuvwxyz'], 5)) call cursor(1,1) exe "normal \<C-V>4jI \<Esc>j<<11|D" @@ -93,12 +121,26 @@ func Test_block_shift_tab() call assert_equal(" abc\<Tab>\<Tab>defghijklmnopqrstuvwxyz", getline(4)) call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(5)) - enew! + " Test for block shift with space characters at the beginning and with + " 'noexpandtab' and 'expandtab' + %d _ + call setline(1, [" 1", " 2", " 3"]) + setlocal shiftwidth=2 noexpandtab + exe "normal gg\<C-V>3j>" + call assert_equal(["\t1", "\t2", "\t3"], getline(1, '$')) + %d _ + call setline(1, [" 1", " 2", " 3"]) + setlocal shiftwidth=2 expandtab + exe "normal gg\<C-V>3j>" + call assert_equal([" 1", " 2", " 3"], getline(1, '$')) + setlocal shiftwidth& + + bw! endfunc " Tests Blockwise Visual when there are TABs before the text. func Test_blockwise_visual() - enew! + new call append(0, ['123456', \ '234567', \ '345678', @@ -120,26 +162,31 @@ func Test_blockwise_visual() \ "\t\tsomext", \ "\t\ttesext"], getline(1, 7)) - enew! + bw! endfunc " Test swapping corners in blockwise visual mode with o and O func Test_blockwise_visual_o_O() - enew! + new exe "norm! 10i.\<Esc>Y4P3lj\<C-V>4l2jr " exe "norm! gvO\<Esc>ra" exe "norm! gvO\<Esc>rb" exe "norm! gvo\<C-c>rc" exe "norm! gvO\<C-c>rd" + set selection=exclusive + exe "norm! gvOo\<C-c>re" + call assert_equal('...a be.', getline(4)) + exe "norm! gvOO\<C-c>rf" + set selection& call assert_equal(['..........', \ '...c d..', \ '... ..', - \ '...a b..', + \ '...a bf.', \ '..........'], getline(1, '$')) - enew! + bw! endfun " Test Virtual replace mode. @@ -242,35 +289,6 @@ func Test_virtual_replace2() set bs&vim endfunc -" Test for Visual mode not being reset causing E315 error. -func TriggerTheProblem() - " At this point there is no visual selection because :call reset it. - " Let's restore the selection: - normal gv - '<,'>del _ - try - exe "normal \<Esc>" - catch /^Vim\%((\a\+)\)\=:E315/ - echom 'Snap! E315 error!' - let g:msg = 'Snap! E315 error!' - endtry -endfunc - -func Test_visual_mode_reset() - enew - let g:msg = "Everything's fine." - enew - setl buftype=nofile - call append(line('$'), 'Delete this line.') - - " NOTE: this has to be done by a call to a function because executing :del - " the ex-way will require the colon operator which resets the visual mode - " thus preventing the problem: - exe "normal! GV:call TriggerTheProblem()\<CR>" - call assert_equal("Everything's fine.", g:msg) - -endfunc - func Test_Visual_word_textobject() new call setline(1, ['First sentence. Second sentence.']) @@ -349,17 +367,6 @@ func Test_Visual_sentence_textobject() bwipe! endfunc -func Test_curswant_not_changed() - new - call setline(1, ['one', 'two']) - au InsertLeave * call getcurpos() - call feedkeys("gg0\<C-V>jI123 \<Esc>j", 'xt') - call assert_equal([0, 2, 1, 0, 1], getcurpos()) - - bwipe! - au! InsertLeave -endfunc - func Test_Visual_paragraph_textobject() new call setline(1, ['First line.', @@ -409,6 +416,17 @@ func Test_Visual_paragraph_textobject() bwipe! endfunc +func Test_curswant_not_changed() + new + call setline(1, ['one', 'two']) + au InsertLeave * call getcurpos() + call feedkeys("gg0\<C-V>jI123 \<Esc>j", 'xt') + call assert_equal([0, 2, 1, 0, 1], getcurpos()) + + bwipe! + au! InsertLeave +endfunc + " Tests for "vaBiB", end could be wrong. func Test_Visual_Block() new @@ -435,24 +453,13 @@ endfunc " Test for 'p'ut in visual block mode func Test_visual_block_put() - enew - + new call append(0, ['One', 'Two', 'Three']) normal gg yank call feedkeys("jl\<C-V>ljp", 'xt') call assert_equal(['One', 'T', 'Tee', 'One', ''], getline(1, '$')) - - enew! -endfunc - -func Test_visual_put_in_block() - new - call setline(1, ['xxxx', 'y∞yy', 'zzzz']) - normal 1G2yl - exe "normal 1G2l\<C-V>jjlp" - call assert_equal(['xxxx', 'y∞xx', 'zzxx'], getline(1, 3)) - bwipe! + bw! endfunc " Visual modes (v V CTRL-V) followed by an operator; count; repeating @@ -623,6 +630,17 @@ func Test_characterwise_visual_mode() normal Gkvj$d call assert_equal(['', 'a', ''], getline(1, '$')) + " characterwise visual mode: replace a single character line and the eol + %d _ + call setline(1, "a") + normal v$rx + call assert_equal(['x'], getline(1, '$')) + + " replace a character with composing characters + call setline(1, "xã̳x") + normal gg0lvrb + call assert_equal("xbx", getline(1)) + bwipe! endfunc @@ -658,6 +676,16 @@ func Test_characterwise_select_mode() exe "normal Gkgh\<Down>\<End>\<Del>" call assert_equal(['', 'a', ''], getline(1, '$')) + " CTRL-H in select mode behaves like 'x' + call setline(1, 'abcdef') + exe "normal! gggh\<Right>\<Right>\<Right>\<C-H>" + call assert_equal('ef', getline(1)) + + " CTRL-O in select mode switches to visual mode for one command + call setline(1, 'abcdef') + exe "normal! gggh\<C-O>3lm" + call assert_equal('mef', getline(1)) + sunmap <lt>End> sunmap <lt>Down> sunmap <lt>Del> @@ -757,8 +785,7 @@ endfunc func Test_visual_block_mode() new call append(0, '') - call setline(1, ['abcdefghijklm', 'abcdefghijklm', 'abcdefghijklm', - \ 'abcdefghijklm', 'abcdefghijklm']) + call setline(1, repeat(['abcdefghijklm'], 5)) call cursor(1, 1) " Test shift-right of a block @@ -777,6 +804,76 @@ func Test_visual_block_mode() \ 'axyzqqqqefgmnoklm', \ 'abcdqqqqijklm'], getline(1, 5)) + " Test 'C' to change till the end of the line + call cursor(3, 4) + exe "normal! \<C-V>j3lCooo" + call assert_equal(['axyooo', 'axyooo'], getline(3, 4)) + + " Test 'D' to delete till the end of the line + call cursor(3, 3) + exe "normal! \<C-V>j2lD" + call assert_equal(['ax', 'ax'], getline(3, 4)) + + " Test block insert with a short line that ends before the block + %d _ + call setline(1, [" one", "a", " two"]) + exe "normal gg\<C-V>2jIx" + call assert_equal([" xone", "a", " xtwo"], getline(1, '$')) + + " Test block append at EOL with '$' and without '$' + %d _ + call setline(1, ["one", "a", "two"]) + exe "normal gg$\<C-V>2jAx" + call assert_equal(["onex", "ax", "twox"], getline(1, '$')) + %d _ + call setline(1, ["one", "a", "two"]) + exe "normal gg3l\<C-V>2jAx" + call assert_equal(["onex", "a x", "twox"], getline(1, '$')) + + " Test block replace with an empty line in the middle and use $ to jump to + " the end of the line. + %d _ + call setline(1, ['one', '', 'two']) + exe "normal gg$\<C-V>2jrx" + call assert_equal(["onx", "", "twx"], getline(1, '$')) + + " Test block replace with an empty line in the middle and move cursor to the + " end of the line + %d _ + call setline(1, ['one', '', 'two']) + exe "normal gg2l\<C-V>2jrx" + call assert_equal(["onx", "", "twx"], getline(1, '$')) + + " Replace odd number of characters with a multibyte character + %d _ + call setline(1, ['abcd', 'efgh']) + exe "normal ggl\<C-V>2ljr\u1100" + call assert_equal(["a\u1100 ", "e\u1100 "], getline(1, '$')) + + " During visual block append, if the cursor moved outside of the selected + " range, then the edit should not be applied to the block. + %d _ + call setline(1, ['aaa', 'bbb', 'ccc']) + exe "normal 2G\<C-V>jAx\<Up>" + call assert_equal(['aaa', 'bxbb', 'ccc'], getline(1, '$')) + + " During visual block append, if the cursor is moved before the start of the + " block, then the new text should be appended there. + %d _ + call setline(1, ['aaa', 'bbb', 'ccc']) + exe "normal $\<C-V>2jA\<Left>x" + call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$')) + " Repeat the previous test but use 'l' to move the cursor instead of '$' + call setline(1, ['aaa', 'bbb', 'ccc']) + exe "normal! gg2l\<C-V>2jA\<Left>x" + call assert_equal(['aaxa', 'bbxb', 'ccxc'], getline(1, '$')) + + " Change a characterwise motion to a blockwise motion using CTRL-V + %d _ + call setline(1, ['123', '456', '789']) + exe "normal ld\<C-V>j" + call assert_equal(['13', '46', '789'], getline(1, '$')) + " Test from ':help v_b_I_example' %d _ setlocal tabstop=8 shiftwidth=4 @@ -1008,6 +1105,15 @@ func Test_block_insert_replace_tabs() bwipe! endfunc +func Test_visual_put_in_block() + new + call setline(1, ['xxxx', 'y∞yy', 'zzzz']) + normal 1G2yl + exe "normal 1G2l\<C-V>jjlp" + call assert_equal(['xxxx', 'y∞xx', 'zzxx'], getline(1, 3)) + bwipe! +endfunc + func Test_visual_put_in_block_using_zp() new " paste using zP @@ -1141,6 +1247,15 @@ func Test_visual_block_ctrl_w_f() au! BufNew endfunc +func Test_visual_block_append_invalid_char() + " this was going over the end of the line + new + call setline(1, [' let xxx', 'xxxxx', 'xxxxxxxxxxx']) + exe "normal 0\<C-V>jjA-\<Esc>" + call assert_equal([' - let xxx', 'xxxxx -', 'xxxxxxxx-xxx'], getline(1, 3)) + bwipe! +endfunc + func Test_visual_reselect_with_count() " this was causing an illegal memory access let lines =<< trim END @@ -1161,6 +1276,25 @@ func Test_visual_reselect_with_count() call delete('XvisualReselect') endfunc +func Test_visual_block_insert_round_off() + new + " The number of characters are tuned to fill a 4096 byte allocated block, + " so that valgrind reports going over the end. + call setline(1, ['xxxxx', repeat('0', 1350), "\t", repeat('x', 60)]) + exe "normal gg0\<C-V>GI" .. repeat('0', 1320) .. "\<Esc>" + bwipe! +endfunc + +" this was causing an ml_get error +func Test_visual_exchange_windows() + enew! + new + call setline(1, ['foo', 'bar']) + exe "normal G\<C-V>gg\<C-W>\<C-X>OO\<Esc>" + bwipe! + bwipe! +endfunc + " this was leaving the end of the Visual area beyond the end of a line func Test_visual_ex_copy_line() new diff --git a/src/nvim/window.c b/src/nvim/window.c index e09af7a7bb..43667377c5 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1727,6 +1727,12 @@ static void win_exchange(long Prenum) (void)win_comp_pos(); // recompute window positions + if (wp->w_buffer != curbuf) { + reset_VIsual_and_resel(); + } else if (VIsual_active) { + wp->w_cursor = curwin->w_cursor; + } + win_enter(wp, true); redraw_later(curwin, NOT_VALID); redraw_later(wp, NOT_VALID); @@ -4682,20 +4688,29 @@ void fix_current_dir(void) globaldir = (char_u *)xstrdup(cwd); } } + bool dir_differs = pathcmp(new_dir, cwd, -1) != 0; + if (!p_acd && dir_differs) { + do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage, + kCdCauseWindow, true); + } if (os_chdir(new_dir) == 0) { - if (!p_acd && pathcmp(new_dir, cwd, -1) != 0) { - do_autocmd_dirchanged(new_dir, curwin->w_localdir - ? kCdScopeWindow : kCdScopeTabpage, kCdCauseWindow); + if (!p_acd && dir_differs) { + do_autocmd_dirchanged(new_dir, curwin->w_localdir ? kCdScopeWindow : kCdScopeTabpage, + kCdCauseWindow, false); } - last_chdir_reason = NULL; - shorten_fnames(true); } + last_chdir_reason = NULL; + shorten_fnames(true); } else if (globaldir != NULL) { // Window doesn't have a local directory and we are not in the global // directory: Change to the global directory. + bool dir_differs = pathcmp((char *)globaldir, cwd, -1) != 0; + if (!p_acd && dir_differs) { + do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow, true); + } if (os_chdir((char *)globaldir) == 0) { - if (!p_acd && pathcmp((char *)globaldir, cwd, -1) != 0) { - do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow); + if (!p_acd && dir_differs) { + do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, kCdCauseWindow, false); } } XFREE_CLEAR(globaldir); |