diff options
Diffstat (limited to 'src')
52 files changed, 891 insertions, 163 deletions
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index e370c0d4d4..3d4ff202fe 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -233,6 +233,7 @@ Object vim_to_object(typval_T *obj) { if (obj->v_type == VAR_FUNC) { ufunc_T *fp = find_func(obj->vval.v_string); + assert(fp != NULL); if (fp->uf_cb == nlua_CFunction_func_call) { LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); return LUAREF_OBJ(ref); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index cc622a00dc..7c194935ce 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2283,6 +2283,11 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * fillchar = ' '; } else { wp = find_window_by_handle(window, err); + + if (wp == NULL) { + api_set_error(err, kErrorTypeException, "unknown winid %d", window); + return result; + } ewp = wp; if (fillchar == 0) { diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index abd22fba26..eee5a0b46c 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1454,7 +1454,10 @@ void set_curbuf(buf_T *buf, int action) } if (bufref_valid(&prevbufref) && !aborting()) { win_T *previouswin = curwin; - if (prevbuf == curbuf) { + // Do not sync when in Insert mode and the buffer is open in + // another window, might be a timer doing something in another + // window. + if (prevbuf == curbuf && ((State & INSERT) == 0 || curbuf->b_nwindows <= 1)) { u_sync(false); } close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL, @@ -4001,14 +4004,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use case STL_VIRTCOL: case STL_VIRTCOL_ALT: { - // In list mode virtcol needs to be recomputed - colnr_T virtcol = wp->w_virtcol; - if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { - wp->w_p_list = false; - getvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); - wp->w_p_list = true; - } - virtcol++; + colnr_T virtcol = wp->w_virtcol + 1; // Don't display %V if it's the same as %c. if (opt == STL_VIRTCOL_ALT && (virtcol == (colnr_T)(!(State & INSERT) && empty_line @@ -4351,7 +4347,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use // Only free the string buffer if we allocated it. // Note: This is not needed if `str` is pointing at `tmp` if (opt == STL_VIM_EXPR) { - xfree(str); + XFREE_CLEAR(str); } if (num >= 0 || (!itemisflag && str && *str)) { diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index bba53b415a..7b17c5b506 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -204,6 +204,10 @@ typedef struct { #define w_p_nu w_onebuf_opt.wo_nu // 'number' int wo_rnu; #define w_p_rnu w_onebuf_opt.wo_rnu // 'relativenumber' + char_u *wo_ve; +#define w_p_ve w_onebuf_opt.wo_ve // 'virtualedit' + unsigned wo_ve_flags; +#define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit' long wo_nuw; #define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth' int wo_wfh; diff --git a/src/nvim/change.c b/src/nvim/change.c index 1dbbfff024..0c16b204e3 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -789,7 +789,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) // fixpos is true, we don't want to end up positioned at the NUL, // unless "restart_edit" is set or 'virtualedit' contains "onemore". if (col > 0 && fixpos && restart_edit == 0 - && (ve_flags & VE_ONEMORE) == 0) { + && (get_ve_flags() & VE_ONEMORE) == 0) { curwin->w_cursor.col--; curwin->w_cursor.coladd = 0; curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col); diff --git a/src/nvim/channel.h b/src/nvim/channel.h index 81b75e2d31..50d6b3600a 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -96,6 +96,8 @@ struct Channel { EXTERN PMap(uint64_t) channels INIT(= MAP_INIT); +EXTERN Callback on_print INIT(= CALLBACK_INIT); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "channel.h.generated.h" #endif diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 6e2c6232d7..55f55a46b2 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -15,6 +15,7 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/move.h" +#include "nvim/option.h" #include "nvim/plines.h" #include "nvim/screen.h" #include "nvim/state.h" @@ -110,7 +111,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a || (State & TERM_FOCUS) || restart_edit != NUL || (VIsual_active && *p_sel != 'o') - || ((ve_flags & VE_ONEMORE) && wcol < MAXCOL); + || ((get_ve_flags() & VE_ONEMORE) && wcol < MAXCOL); line = ml_get_buf(curbuf, pos->lnum, false); if (wcol >= MAXCOL) { @@ -366,6 +367,7 @@ void check_cursor_col_win(win_T *win) colnr_T len; colnr_T oldcol = win->w_cursor.col; colnr_T oldcoladd = win->w_cursor.col + win->w_cursor.coladd; + unsigned int cur_ve_flags = get_ve_flags(); len = (colnr_T)STRLEN(ml_get_buf(win->w_buffer, win->w_cursor.lnum, false)); if (len == 0) { @@ -377,7 +379,7 @@ void check_cursor_col_win(win_T *win) * - 'virtualedit' is set */ if ((State & INSERT) || restart_edit || (VIsual_active && *p_sel != 'o') - || (ve_flags & VE_ONEMORE) + || (cur_ve_flags & VE_ONEMORE) || virtual_active()) { win->w_cursor.col = len; } else { @@ -394,7 +396,7 @@ void check_cursor_col_win(win_T *win) // line. if (oldcol == MAXCOL) { win->w_cursor.coladd = 0; - } else if (ve_flags == VE_ALL) { + } else if (cur_ve_flags == VE_ALL) { if (oldcoladd > win->w_cursor.col) { win->w_cursor.coladd = oldcoladd - win->w_cursor.col; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index a9c606ec13..b7656230dd 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -663,8 +663,12 @@ static int insert_execute(VimState *state, int key) InsertState *const s = (InsertState *)state; if (stop_insert_mode) { // Insert mode ended, possibly from a callback. + if (key != K_IGNORE && key != K_NOP) { + vungetc(key); + } s->count = 0; s->nomove = true; + ins_compl_prep(ESC); return 0; } @@ -909,7 +913,7 @@ static int insert_handle_key(InsertState *s) ins_ctrl_o(); // don't move the cursor left when 'virtualedit' has "onemore". - if (ve_flags & VE_ONEMORE) { + if (get_ve_flags() & VE_ONEMORE) { ins_at_eol = false; s->nomove = true; } @@ -5631,8 +5635,12 @@ int get_literal(void) i = 0; for (;;) { nc = plain_vgetc(); - if (!(State & CMDLINE) - && MB_BYTE2LEN_CHECK(nc) == 1) { + if ((mod_mask & ~MOD_MASK_SHIFT) != 0) { + // A character with non-Shift modifiers should not be a valid + // character for i_CTRL-V_digit. + break; + } + if (!(State & CMDLINE) && MB_BYTE2LEN_CHECK(nc) == 1) { add_to_showcmd(nc); } if (nc == 'x' || nc == 'X') { @@ -5698,6 +5706,8 @@ int get_literal(void) --no_mapping; if (nc) { vungetc(nc); + // A character typed with i_CTRL-V_digit cannot have modifiers. + mod_mask = 0; } got_int = false; // CTRL-C typed after CTRL-V is not an interrupt return cc; @@ -6905,8 +6915,7 @@ int oneright(void) // move "l" bytes right, but don't end up on the NUL, unless 'virtualedit' // contains "onemore". - if (ptr[l] == NUL - && (ve_flags & VE_ONEMORE) == 0) { + if (ptr[l] == NUL && (get_ve_flags() & VE_ONEMORE) == 0) { return FAIL; } curwin->w_cursor.col += l; @@ -8028,7 +8037,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) && !VIsual_active )) && !revins_on) { - if (curwin->w_cursor.coladd > 0 || ve_flags == VE_ALL) { + if (curwin->w_cursor.coladd > 0 || get_ve_flags() == VE_ALL) { oneleft(); if (restart_edit != NUL) { curwin->w_cursor.coladd++; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 6dbdc09c3b..d25903c12a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6468,6 +6468,10 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) if (argvars[0].v_type == VAR_DICT) { vimvars[VV_KEY].vv_type = VAR_STRING; + const VarLockStatus prev_lock = d->dv_lock; + if (map && d->dv_lock == VAR_UNLOCKED) { + d->dv_lock = VAR_LOCKED; + } ht = &d->dv_hashtab; hash_lock(ht); todo = (int)ht->ht_used; @@ -6498,6 +6502,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) } } hash_unlock(ht); + d->dv_lock = prev_lock; } else if (argvars[0].v_type == VAR_BLOB) { vimvars[VV_KEY].vv_type = VAR_NUMBER; @@ -6530,6 +6535,10 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) assert(argvars[0].v_type == VAR_LIST); vimvars[VV_KEY].vv_type = VAR_NUMBER; + const VarLockStatus prev_lock = tv_list_locked(l); + if (map && tv_list_locked(l) == VAR_UNLOCKED) { + tv_list_set_lock(l, VAR_LOCKED); + } for (listitem_T *li = tv_list_first(l); li != NULL;) { if (map && var_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, @@ -6548,6 +6557,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) } idx++; } + tv_list_set_lock(l, prev_lock); } restore_vimvar(VV_KEY, &save_key); diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 7701688b49..4a07f6a850 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -10215,6 +10215,10 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) { return; } + if (!tv_dict_get_callback(opts, S_LEN("on_print"), &on_print)) { + return; + } + on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered"); if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) { on_stdin.self = opts; diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index d1275d6512..ad01c01499 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -81,7 +81,8 @@ typedef struct { } data; CallbackType type; } Callback; -#define CALLBACK_NONE ((Callback){ .type = kCallbackNone }) +#define CALLBACK_INIT { .type = kCallbackNone } +#define CALLBACK_NONE ((Callback)CALLBACK_INIT) /// Structure holding dictionary watcher typedef struct dict_watcher { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 95390b1a70..ca5e14ee63 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1099,6 +1099,9 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n) } appended_lines_mark(n, count); + if (VIsual_active) { + check_pos(curbuf, &VIsual); + } msgmore((long)count); } diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index c388373ac1..c391cf96aa 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -2413,7 +2413,7 @@ module.cmds = { }, { command='set', - flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK), + flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK), addr_type='ADDR_NONE', func='ex_set', }, @@ -2425,13 +2425,13 @@ module.cmds = { }, { command='setglobal', - flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK), + flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK), addr_type='ADDR_NONE', func='ex_set', }, { command='setlocal', - flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK), + flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK), addr_type='ADDR_NONE', func='ex_set', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 267f5616f5..846789233f 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2323,9 +2323,11 @@ void ex_scriptnames(exarg_T *eap) for (int i = 1; i <= script_items.ga_len && !got_int; i++) { if (SCRIPT_ITEM(i).sn_name != NULL) { - home_replace(NULL, SCRIPT_ITEM(i).sn_name, - NameBuff, MAXPATHL, true); - smsg("%3d: %s", i, NameBuff); + home_replace(NULL, SCRIPT_ITEM(i).sn_name, NameBuff, MAXPATHL, true); + vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff); + msg_putchar('\n'); + msg_outtrans(IObuff); + line_breakcheck(); } } } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index a4b43e598d..bfcb8c1663 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -9604,18 +9604,6 @@ static void ex_digraphs(exarg_T *eap) } } -static void ex_set(exarg_T *eap) -{ - int flags = 0; - - if (eap->cmdidx == CMD_setlocal) { - flags = OPT_LOCAL; - } else if (eap->cmdidx == CMD_setglobal) { - flags = OPT_GLOBAL; - } - (void)do_set(eap->arg, flags); -} - void set_no_hlsearch(bool flag) { no_hlsearch = flag; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 78b8e43e65..fd75cfc7f8 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -772,7 +772,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) ccline.cmdindent = (s->firstc > 0 ? s->indent : 0); // alloc initial ccline.cmdbuff - alloc_cmdbuff(exmode_active ? 250 : s->indent + 1); + alloc_cmdbuff(indent + 50); ccline.cmdlen = ccline.cmdpos = 0; ccline.cmdbuff[0] = NUL; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index b2cd5c510b..b4becb3066 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1433,7 +1433,11 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first rel_fname = NULL; } - if (first == TRUE) { + if (first == true) { + if (len == 0) { + return NULL; + } + // copy file name into NameBuff, expanding environment variables save_char = ptr[len]; ptr[len] = NUL; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 31a09b3969..041b60d838 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -524,6 +524,8 @@ EXTERN pos_T VIsual; EXTERN int VIsual_active INIT(= false); /// Whether Select mode is active. EXTERN int VIsual_select INIT(= false); +/// Register name for Select mode +EXTERN int VIsual_select_reg INIT(= 0); /// Restart Select mode when next cmd finished EXTERN int restart_VIsual_select INIT(= 0); /// Whether to restart the selection after a Select-mode mapping or menu. diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index f9a2533d4e..0fbd56ed53 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -619,6 +619,7 @@ bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special) } if (tv->v_type == VAR_FUNC) { ufunc_T *fp = find_func(tv->vval.v_string); + assert(fp != NULL); if (fp->uf_cb == nlua_CFunction_func_call) { nlua_pushref(lstate, ((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); return true; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index cfdbe7b344..5c4d7e3c91 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -4,6 +4,7 @@ #include <lauxlib.h> #include <lua.h> #include <lualib.h> +#include <tree_sitter/api.h> #include "luv/luv.h" #include "nvim/api/private/defs.h" @@ -1267,6 +1268,12 @@ int tslua_get_language_version(lua_State *L) return 1; } +int tslua_get_minimum_language_version(lua_State *L) +{ + lua_pushnumber(L, TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION); + return 1; +} + static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { tslua_init(lstate); @@ -1288,6 +1295,9 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, tslua_get_language_version); lua_setfield(lstate, -2, "_ts_get_language_version"); + + lua_pushcfunction(lstate, tslua_get_minimum_language_version); + lua_setfield(lstate, -2, "_ts_get_minimum_language_version"); } int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char_u ***results) diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h index 95d63dd14a..30f5aacebc 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -2,6 +2,7 @@ #define NVIM_MARKTREE_H #include <stdint.h> +#include <assert.h> #include "nvim/assert.h" #include "nvim/garray.h" diff --git a/src/nvim/message.c b/src/nvim/message.c index 17ccef37f1..e1e253cd2e 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -2647,6 +2647,17 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) char buf[7]; char *p; + if (on_print.type != kCallbackNone) { + typval_T argv[1]; + argv[0].v_type = VAR_STRING; + argv[0].v_lock = VAR_UNLOCKED; + argv[0].vval.v_string = (char_u *)str; + typval_T rettv = TV_INITIAL_VALUE; + callback_call(&on_print, 1, argv, &rettv); + tv_clear(&rettv); + return; + } + while ((maxlen < 0 || s - str < maxlen) && *s != NUL) { int len = utf_ptr2len((const char_u *)s); if (!(silent_mode && p_verbose == 0)) { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index f802dce20d..ddebfb9756 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -164,7 +164,7 @@ static const struct nv_cmd { { Ctrl_O, nv_ctrlo, 0, 0 }, { Ctrl_P, nv_up, NV_STS, false }, { Ctrl_Q, nv_visual, 0, false }, - { Ctrl_R, nv_redo, 0, 0 }, + { Ctrl_R, nv_redo_or_register, 0, 0 }, { Ctrl_S, nv_ignore, 0, 0 }, { Ctrl_T, nv_tagpop, NV_NCW, 0 }, { Ctrl_U, nv_halfpage, 0, 0 }, @@ -961,6 +961,7 @@ normal_end: && s->oa.regname == 0) { if (restart_VIsual_select == 1) { VIsual_select = true; + VIsual_select_reg = 0; trigger_modechanged(); showmode(); restart_VIsual_select = 0; @@ -3286,7 +3287,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) int col_off1; // margin offset for first screen line int col_off2; // margin offset for wrapped screen line int width1; // text width for first screen line - int width2; // test width for wrapped screen line + int width2; // text width for wrapped screen line oap->motion_type = kMTCharWise; oap->inclusive = (curwin->w_curswant == MAXCOL); @@ -3410,6 +3411,13 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) virtcol -= vim_strsize(get_showbreak_value(curwin)); } + int c = utf_ptr2char(get_cursor_pos_ptr()); + if (dir == FORWARD && virtcol < curwin->w_curswant + && (curwin->w_curswant <= (colnr_T)width1) + && !vim_isprintc(c) && c > 255) { + oneright(); + } + if (virtcol > curwin->w_curswant && (curwin->w_curswant < (colnr_T)width1 ? (curwin->w_curswant > (colnr_T)width1 / 2) @@ -4477,8 +4485,13 @@ bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp) *pp = ml_get_pos(&VIsual); *lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1; } - // Correct the length to include the whole last character. - *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1); + if (**pp == NUL) { + *lenp = 0; + } + if (*lenp > 0) { + // Correct the length to include all bytes of the last character. + *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1); + } } reset_VIsual_and_resel(); return true; @@ -5968,11 +5981,8 @@ static void nv_visual(cmdarg_T *cap) * was only one -- webb */ if (resel_VIsual_mode != 'v' || resel_VIsual_line_count > 1) { - curwin->w_cursor.lnum += - resel_VIsual_line_count * cap->count0 - 1; - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - } + curwin->w_cursor.lnum += resel_VIsual_line_count * cap->count0 - 1; + check_cursor(); } VIsual_mode = resel_VIsual_mode; if (VIsual_mode == 'v') { @@ -6051,7 +6061,7 @@ static void n_start_visual_mode(int c) // Corner case: the 0 position in a tab may change when going into // virtualedit. Recalculate curwin->w_cursor to avoid bad highlighting. // - if (c == Ctrl_V && (ve_flags & VE_BLOCK) && gchar_cursor() == TAB) { + if (c == Ctrl_V && (get_ve_flags() & VE_BLOCK) && gchar_cursor() == TAB) { validate_virtcol(); coladvance(curwin->w_virtcol); } @@ -6191,6 +6201,7 @@ static void nv_g_cmd(cmdarg_T *cap) // start Select mode. if (cap->arg) { VIsual_select = true; + VIsual_select_reg = 0; } else { may_start_select('c'); } @@ -6703,11 +6714,26 @@ static void nv_dot(cmdarg_T *cap) } } -/* - * CTRL-R: undo undo - */ -static void nv_redo(cmdarg_T *cap) +// CTRL-R: undo undo or specify register in select mode +static void nv_redo_or_register(cmdarg_T *cap) { + if (VIsual_select && VIsual_active) { + int reg; + // Get register name + no_mapping++; + reg = plain_vgetc(); + LANGMAP_ADJUST(reg, true); + no_mapping--; + + if (reg == '"') { + // the unnamed register is 0 + reg = 0; + } + + VIsual_select_reg = valid_yank_reg(reg, true) ? reg : 0; + return; + } + if (!checkclearopq(cap->oap)) { u_redo((int)cap->count1); curwin->w_set_curswant = true; @@ -6956,7 +6982,7 @@ static void adjust_cursor(oparg_T *oap) if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL && (!VIsual_active || *p_sel == 'o') && !virtual_active() - && (ve_flags & VE_ONEMORE) == 0) { + && (get_ve_flags() & VE_ONEMORE) == 0) { curwin->w_cursor.col--; // prevent cursor from moving on the trail byte mb_adjust_cursor(); @@ -7028,6 +7054,7 @@ static void nv_select(cmdarg_T *cap) { if (VIsual_active) { VIsual_select = true; + VIsual_select_reg = 0; } else if (VIsual_reselect) { cap->nchar = 'v'; // fake "gv" command cap->arg = true; @@ -7162,7 +7189,7 @@ static void nv_esc(cmdarg_T *cap) void set_cursor_for_append_to_line(void) { curwin->w_set_curswant = true; - if (ve_flags == VE_ALL) { + if (get_ve_flags() == VE_ALL) { const int save_State = State; // Pretend Insert mode here to allow the cursor on the diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 2bc92ce295..e66935db7e 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1435,6 +1435,11 @@ int op_delete(oparg_T *oap) return FAIL; } + if (VIsual_select && oap->is_VIsual) { + // Use the register given with CTRL_R, defaults to zero + oap->regname = VIsual_select_reg; + } + mb_adjust_opend(oap); /* @@ -2196,19 +2201,22 @@ void op_insert(oparg_T *oap, long count1) // doing block_prep(). When only "block" is used, virtual edit is // already disabled, but still need it when calling // coladvance_force(). + // coladvance_force() uses get_ve_flags() to get the 'virtualedit' + // state for the current window. To override that state, we need to + // set the window-local value of ve_flags rather than the global value. if (curwin->w_cursor.coladd > 0) { - unsigned old_ve_flags = ve_flags; + unsigned old_ve_flags = curwin->w_ve_flags; - ve_flags = VE_ALL; if (u_save_cursor() == FAIL) { return; } + curwin->w_ve_flags = VE_ALL; coladvance_force(oap->op_type == OP_APPEND ? oap->end_vcol + 1 : getviscol()); if (oap->op_type == OP_APPEND) { --curwin->w_cursor.col; } - ve_flags = old_ve_flags; + curwin->w_ve_flags = old_ve_flags; } // Get the info about the block before entering the text block_prep(oap, &bd, oap->start.lnum, true); @@ -2801,7 +2809,7 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx, if (exclude_trailing_space) { int s = bd->textlen + bd->endspaces; - while (ascii_iswhite(*(bd->textstart + s - 1)) && s > 0) { + while (s > 0 && ascii_iswhite(*(bd->textstart + s - 1))) { s = s - utf_head_off(bd->textstart, bd->textstart + s - 1) - 1; pnew--; } @@ -2906,6 +2914,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) char_u *insert_string = NULL; bool allocated = false; long cnt; + unsigned int cur_ve_flags = get_ve_flags(); if (flags & PUT_FIXINDENT) { orig_indent = get_indent(); @@ -2976,7 +2985,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) eol = (*(cursor_pos + utfc_ptr2len(cursor_pos)) == NUL); } - bool ve_allows = (ve_flags == VE_ALL || ve_flags == VE_ONEMORE); + bool ve_allows = (cur_ve_flags == VE_ALL || cur_ve_flags == VE_ONEMORE); bool eof = curbuf->b_ml.ml_line_count == curwin->w_cursor.lnum && one_past_line; if (ve_allows || !(eol || eof)) { @@ -3152,7 +3161,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) yanklen = (int)STRLEN(y_array[0]); - if (ve_flags == VE_ALL && y_type == kMTCharWise) { + if (cur_ve_flags == VE_ALL && y_type == kMTCharWise) { if (gchar_cursor() == TAB) { int viscol = getviscol(); long ts = curbuf->b_p_ts; @@ -3181,7 +3190,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) colnr_T endcol2 = 0; if (dir == FORWARD && c != NUL) { - if (ve_flags == VE_ALL) { + if (cur_ve_flags == VE_ALL) { getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2); } else { getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col); @@ -3195,9 +3204,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } col += curwin->w_cursor.coladd; - if (ve_flags == VE_ALL - && (curwin->w_cursor.coladd > 0 - || endcol2 == curwin->w_cursor.col)) { + if (cur_ve_flags == VE_ALL + && (curwin->w_cursor.coladd > 0 || endcol2 == curwin->w_cursor.col)) { if (dir == FORWARD && c == NUL) { col++; } @@ -3629,14 +3637,16 @@ end: */ void adjust_cursor_eol(void) { + unsigned int cur_ve_flags = get_ve_flags(); + if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL - && (ve_flags & VE_ONEMORE) == 0 + && (cur_ve_flags & VE_ONEMORE) == 0 && !(restart_edit || (State & INSERT))) { // Put the cursor on the last character in the line. dec_cursor(); - if (ve_flags == VE_ALL) { + if (cur_ve_flags == VE_ALL) { colnr_T scol, ecol; // Coladd is set to the width of the last character. diff --git a/src/nvim/option.c b/src/nvim/option.c index 659965b64c..fd6483c555 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -281,7 +281,7 @@ typedef struct vimoption { # include "options.generated.h" #endif -#define PARAM_COUNT ARRAY_SIZE(options) +#define OPTION_COUNT ARRAY_SIZE(options) static char *(p_ambw_values[]) = { "single", "double", NULL }; static char *(p_bg_values[]) = { "light", "dark", NULL }; @@ -931,6 +931,21 @@ void set_title_defaults(void) } } +void ex_set(exarg_T *eap) +{ + int flags = 0; + + if (eap->cmdidx == CMD_setlocal) { + flags = OPT_LOCAL; + } else if (eap->cmdidx == CMD_setglobal) { + flags = OPT_GLOBAL; + } + if (eap->forceit) { + flags |= OPT_ONECOLUMN; + } + (void)do_set(eap->arg, flags); +} + /// Parse 'arg' for option settings. /// /// 'arg' may be IObuff, but only when no errors can be present and option @@ -3084,14 +3099,27 @@ ambw_end: if (foldmethodIsIndent(curwin)) { foldUpdateAll(curwin); } - } else if (varp == &p_ve) { // 'virtualedit' - if (opt_strings_flags(p_ve, p_ve_values, &ve_flags, true) != OK) { - errmsg = e_invarg; - } else if (STRCMP(p_ve, oldval) != 0) { - // Recompute cursor position in case the new 've' setting - // changes something. - validate_virtcol(); - coladvance(curwin->w_virtcol); + } else if (gvarp == &p_ve) { // 'virtualedit' + char_u *ve = p_ve; + unsigned int *flags = &ve_flags; + + if (opt_flags & OPT_LOCAL) { + ve = curwin->w_p_ve; + flags = &curwin->w_ve_flags; + } + + if ((opt_flags & OPT_LOCAL) && *ve == NUL) { + // make the local value empty: use the global value + *flags = 0; + } else { + if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) { + errmsg = e_invarg; + } else if (STRCMP(p_ve, oldval) != 0) { + // Recompute cursor position in case the new 've' setting + // changes something. + validate_virtcol(); + coladvance(curwin->w_virtcol); + } } } else if (varp == &p_csqf) { if (p_csqf != NULL) { @@ -5184,7 +5212,7 @@ static void showoptions(int all, int opt_flags) #define INC 20 #define GAP 3 - vimoption_T **items = xmalloc(sizeof(vimoption_T *) * PARAM_COUNT); + vimoption_T **items = xmalloc(sizeof(vimoption_T *) * OPTION_COUNT); // Highlight title if (opt_flags & OPT_GLOBAL) { @@ -5198,6 +5226,7 @@ static void showoptions(int all, int opt_flags) // Do the loop two times: // 1. display the short items // 2. display the long items (only strings and numbers) + // When "opt_flags" has OPT_ONECOLUMN do everything in run 2. for (run = 1; run <= 2 && !got_int; run++) { // collect the items in items[] item_count = 0; @@ -5208,7 +5237,7 @@ static void showoptions(int all, int opt_flags) } varp = NULL; - if (opt_flags != 0) { + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) != 0) { if (p->indir != PV_NONE) { varp = get_varp_scope(p, opt_flags); } @@ -5217,8 +5246,10 @@ static void showoptions(int all, int opt_flags) } if (varp != NULL && (all == 1 || (all == 0 && !optval_default(p, varp)))) { - if (p->flags & P_BOOL) { - len = 1; // a toggle option fits always + if (opt_flags & OPT_ONECOLUMN) { + len = Columns; + } else if (p->flags & P_BOOL) { + len = 1; // a toggle option fits always } else { option_value2string(p, opt_flags); len = (int)STRLEN(p->fullname) + vim_strsize(NameBuff) + 1; @@ -5748,6 +5779,10 @@ void unset_global_local_option(char *name, void *from) set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true); redraw_later((win_T *)from, NOT_VALID); break; + case PV_VE: + clear_string_option(&((win_T *)from)->w_p_ve); + ((win_T *)from)->w_ve_flags = 0; + break; } } @@ -5814,6 +5849,8 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) return (char_u *)&(curwin->w_p_fcs); case PV_LCS: return (char_u *)&(curwin->w_p_lcs); + case PV_VE: + return (char_u *)&(curwin->w_p_ve); } return NULL; // "cannot happen" } @@ -5908,6 +5945,9 @@ static char_u *get_varp(vimoption_T *p) case PV_LCS: return *curwin->w_p_lcs != NUL ? (char_u *)&(curwin->w_p_lcs) : p->var; + case PV_VE: + return *curwin->w_p_ve != NUL + ? (char_u *)&curwin->w_p_ve : p->var; case PV_ARAB: return (char_u *)&(curwin->w_p_arab); @@ -6148,6 +6188,8 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_list = from->wo_list; to->wo_nu = from->wo_nu; to->wo_rnu = from->wo_rnu; + to->wo_ve = vim_strsave(from->wo_ve); + to->wo_ve_flags = from->wo_ve_flags; to->wo_nuw = from->wo_nuw; to->wo_rl = from->wo_rl; to->wo_rlc = vim_strsave(from->wo_rlc); @@ -6224,6 +6266,7 @@ static void check_winopt(winopt_T *wop) check_string_option(&wop->wo_winhl); check_string_option(&wop->wo_fcs); check_string_option(&wop->wo_lcs); + check_string_option(&wop->wo_ve); } /// Free the allocated memory inside a winopt_T. @@ -6248,6 +6291,7 @@ void clear_winopt(winopt_T *wop) clear_string_option(&wop->wo_winhl); clear_string_option(&wop->wo_fcs); clear_string_option(&wop->wo_lcs); + clear_string_option(&wop->wo_ve); } void didset_window_options(win_T *wp) @@ -7815,6 +7859,12 @@ unsigned int get_bkc_value(buf_T *buf) return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags; } +/// Get the local or global value of the 'virtualedit' flags. +unsigned int get_ve_flags(void) +{ + return (curwin->w_ve_flags ? curwin->w_ve_flags : ve_flags) & ~(VE_NONE | VE_NONEU); +} + /// Get the local or global value of 'showbreak'. /// /// @param win If not NULL, the window to get the local option from; global diff --git a/src/nvim/option.h b/src/nvim/option.h index f7dbaafeec..9321dd5454 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -13,16 +13,16 @@ /// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global /// values, get local value. typedef enum { - OPT_FREE = 1, ///< Free old value if it was allocated. - OPT_GLOBAL = 2, ///< Use global value. - OPT_LOCAL = 4, ///< Use local value. - OPT_MODELINE = 8, ///< Option in modeline. - OPT_WINONLY = 16, ///< Only set window-local options. - OPT_NOWIN = 32, ///< Donβt set window-local options. - OPT_ONECOLUMN = 64, ///< list options one per line - OPT_NO_REDRAW = 128, ///< ignore redraw flags on option - OPT_SKIPRTP = 256, ///< "skiprtp" in 'sessionoptions' - OPT_CLEAR = 512, ///< Clear local value of an option. + OPT_FREE = 0x01, ///< Free old value if it was allocated. + OPT_GLOBAL = 0x02, ///< Use global value. + OPT_LOCAL = 0x04, ///< Use local value. + OPT_MODELINE = 0x08, ///< Option in modeline. + OPT_WINONLY = 0x10, ///< Only set window-local options. + OPT_NOWIN = 0x20, ///< Donβt set window-local options. + OPT_ONECOLUMN = 0x40, ///< list options one per line + OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option + OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions' + OPT_CLEAR = 0x200, ///< Clear local value of an option. } OptionFlags; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 09c3bf3800..5d6aca9574 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -705,12 +705,14 @@ EXTERN int p_vb; ///< 'visualbell' EXTERN char_u *p_ve; ///< 'virtualedit' EXTERN unsigned ve_flags; #ifdef IN_OPTION_C -static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", NULL }; +static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL }; #endif -#define VE_BLOCK 5 // includes "all" -#define VE_INSERT 6 // includes "all" -#define VE_ALL 4 -#define VE_ONEMORE 8 +#define VE_BLOCK 5U // includes "all" +#define VE_INSERT 6U // includes "all" +#define VE_ALL 4U +#define VE_ONEMORE 8U +#define VE_NONE 16U // "none" +#define VE_NONEU 32U // "NONE" EXTERN long p_verbose; // 'verbose' #ifdef IN_OPTION_C char_u *p_vfile = (char_u *)""; // used before options are initialized @@ -869,6 +871,7 @@ enum { WV_LBR, WV_NU, WV_RNU, + WV_VE, WV_NUW, WV_PVW, WV_RL, diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 5133fe7ac8..aea2179a61 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -2736,7 +2736,7 @@ return { { full_name='virtualedit', abbreviation='ve', short_desc=N_("when to use virtual editing"), - type='string', list='onecomma', scope={'global'}, + type='string', list='onecomma', scope={'global', 'window'}, deny_duplicates=true, redraw={'curswant'}, varname='p_ve', diff --git a/src/nvim/path.c b/src/nvim/path.c index 674d67e21a..39e276e0a5 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -1682,6 +1682,10 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count, char_u *file_name; char_u *tofree = NULL; + if (len == 0) { + return NULL; + } + if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { tofree = (char_u *)eval_includeexpr((char *)ptr, len); if (tofree != NULL) { @@ -1743,14 +1747,32 @@ int path_is_url(const char *p) return 0; } -/// Check if "fname" starts with "name://". Return URL_SLASH if it does. +/// Check if "fname" starts with "name://" or "name:\\". /// /// @param fname is the filename to test -/// @return URL_BACKSLASH for "name:\\", zero otherwise. +/// @return URL_SLASH for "name://", URL_BACKSLASH for "name:\\", zero otherwise. int path_with_url(const char *fname) { const char *p; - for (p = fname; isalpha(*p); p++) {} + + // We accept alphabetic characters and a dash in scheme part. + // RFC 3986 allows for more, but it increases the risk of matching + // non-URL text. + + // first character must be alpha + if (!isalpha(*fname)) { + return 0; + } + + // check body: alpha or dash + for (p = fname; (isalpha(*p) || (*p == '-')); p++) {} + + // check last char is not a dash + if (p[-1] == '-') { + return 0; + } + + // "://" or ":\\" must follow return path_is_url(p); } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 538604cf79..af023d6785 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -1215,19 +1215,40 @@ static void win_update(win_T *wp, Providers *providers) */ if (VIsual_mode == Ctrl_V) { colnr_T fromc, toc; - int save_ve_flags = ve_flags; + unsigned int save_ve_flags = curwin->w_ve_flags; if (curwin->w_p_lbr) { - ve_flags = VE_ALL; + curwin->w_ve_flags = VE_ALL; } getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc); - ve_flags = save_ve_flags; toc++; + curwin->w_ve_flags = save_ve_flags; // Highlight to the end of the line, unless 'virtualedit' has // "block". - if (curwin->w_curswant == MAXCOL && !(ve_flags & VE_BLOCK)) { - toc = MAXCOL; + if (curwin->w_curswant == MAXCOL) { + if (get_ve_flags() & VE_BLOCK) { + pos_T pos; + int cursor_above = curwin->w_cursor.lnum < VIsual.lnum; + + // Need to find the longest line. + toc = 0; + pos.coladd = 0; + for (pos.lnum = curwin->w_cursor.lnum; + cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum; + pos.lnum += cursor_above ? 1 : -1) { + colnr_T t; + + pos.col = STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false)); + getvvcol(wp, &pos, NULL, NULL, &t); + if (toc < t) { + toc = t; + } + } + toc++; + } else { + toc = MAXCOL; + } } if (fromc != wp->w_old_cursor_fcol @@ -3720,41 +3741,46 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc tab_len += n_extra - tab_len; } - // if n_extra > 0, it gives the number of chars + // If n_extra > 0, it gives the number of chars // to use for a tab, else we need to calculate the width - // for a tab + // for a tab. int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2)); + if (wp->w_p_lcs_chars.tab3) { + len += utf_char2len(wp->w_p_lcs_chars.tab3); + } if (n_extra > 0) { len += n_extra - tab_len; } c = wp->w_p_lcs_chars.tab1; p = xmalloc(len + 1); - memset(p, ' ', len); - p[len] = NUL; - xfree(p_extra_free); - p_extra_free = p; - for (i = 0; i < tab_len; i++) { - if (*p == NUL) { - tab_len = i; - break; - } - int lcs = wp->w_p_lcs_chars.tab2; + if (p == NULL) { + n_extra = 0; + } else { + memset(p, ' ', len); + p[len] = NUL; + xfree(p_extra_free); + p_extra_free = p; + for (i = 0; i < tab_len; i++) { + if (*p == NUL) { + tab_len = i; + break; + } + int lcs = wp->w_p_lcs_chars.tab2; - // if tab3 is given, need to change the char - // for tab - if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) { - lcs = wp->w_p_lcs_chars.tab3; + // if tab3 is given, use it for the last char + if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) { + lcs = wp->w_p_lcs_chars.tab3; + } + p += utf_char2bytes(lcs, p); + n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0); } - utf_char2bytes(lcs, p); - p += utf_char2len(lcs); - n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0); - } - p_extra = p_extra_free; + p_extra = p_extra_free; - // n_extra will be increased by FIX_FOX_BOGUSCOLS - // macro below, so need to adjust for that here - if (vcol_off > 0) { - n_extra -= vcol_off; + // n_extra will be increased by FIX_FOX_BOGUSCOLS + // macro below, so need to adjust for that here + if (vcol_off > 0) { + n_extra -= vcol_off; + } } } @@ -4232,6 +4258,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // Show "extends" character from 'listchars' if beyond the line end and // 'list' is set. if (wp->w_p_lcs_chars.ext != NUL + && draw_state == WL_LINE && wp->w_p_list && !wp->w_p_wrap && filler_todo <= 0 @@ -4427,7 +4454,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc */ if ((wp->w_p_rl ? (col < 0) : (col >= grid->Columns)) && foldinfo.fi_lines == 0 - && (*ptr != NUL + && (draw_state != WL_LINE + || *ptr != NUL || filler_todo > 0 || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL && p_extra != at_end_str) diff --git a/src/nvim/state.c b/src/nvim/state.c index 1fe8bb671d..9e4c9b2bad 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -12,6 +12,7 @@ #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/main.h" +#include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/os/input.h" #include "nvim/state.h" @@ -107,15 +108,17 @@ void state_handle_k_event(void) /// Return true if in the current mode we need to use virtual. bool virtual_active(void) { + unsigned int cur_ve_flags = get_ve_flags(); + // While an operator is being executed we return "virtual_op", because // VIsual_active has already been reset, thus we can't check for "block" // being used. if (virtual_op != kNone) { return virtual_op; } - return ve_flags == VE_ALL - || ((ve_flags & VE_BLOCK) && VIsual_active && VIsual_mode == Ctrl_V) - || ((ve_flags & VE_INSERT) && (State & INSERT)); + return cur_ve_flags == VE_ALL + || ((cur_ve_flags & VE_BLOCK) && VIsual_active && VIsual_mode == Ctrl_V) + || ((cur_ve_flags & VE_INSERT) && (State & INSERT)); } /// VISUAL, SELECTMODE and OP_PENDING State are never set, they are equal to diff --git a/src/nvim/strings.c b/src/nvim/strings.c index e2a8108c45..291138ef23 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -1001,22 +1001,20 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t - str_arg); } if (fmt_spec == 'S') { - if (min_field_width != 0) { - min_field_width += (strlen(str_arg) - - mb_string2cells((char_u *)str_arg)); - } - if (precision) { - char_u *p1; - size_t i = 0; - - for (p1 = (char_u *)str_arg; *p1; - p1 += utfc_ptr2len(p1)) { - i += (size_t)utf_ptr2cells(p1); - if (i > precision) { - break; - } + char_u *p1; + size_t i; + + for (i = 0, p1 = (char_u *)str_arg; *p1; p1 += utfc_ptr2len(p1)) { + size_t cell = (size_t)utf_ptr2cells(p1); + if (precision_specified && i + cell > precision) { + break; } - str_arg_l = (size_t)(p1 - (char_u *)str_arg); + i += cell; + } + + str_arg_l = (size_t)(p1 - (char_u *)str_arg); + if (min_field_width != 0) { + min_field_width += str_arg_l - i; } } break; diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim index 14bab33a2f..ab26dddbe0 100644 --- a/src/nvim/testdir/check.vim +++ b/src/nvim/testdir/check.vim @@ -12,9 +12,9 @@ endfunc " Command to check for the absence of a feature. command -nargs=1 CheckNotFeature call CheckNotFeature(<f-args>) func CheckNotFeature(name) - if !has(a:name, 1) - throw 'Checking for non-existent feature ' .. a:name - endif + " if !has(a:name, 1) + " throw 'Checking for non-existent feature ' .. a:name + " endif if has(a:name) throw 'Skipped: ' .. a:name .. ' feature present' endif diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim index 40111fdf06..a31cdbb49a 100644 --- a/src/nvim/testdir/test_buffer.vim +++ b/src/nvim/testdir/test_buffer.vim @@ -1,5 +1,7 @@ " Tests for Vim buffer +source check.vim + func Test_buffer_error() new foo1 new foo2 @@ -30,4 +32,33 @@ func Test_balt() call assert_equal('OtherBuffer', bufname()) endfunc +" Test for buffer match URL(scheme) check +" scheme is alpha and inner hyphen only. +func Test_buffer_scheme() + CheckMSWindows + + set noshellslash + %bwipe! + let bufnames = [ + \ #{id: 'b0', name: 'test://xyz/foo/b0' , match: 1}, + \ #{id: 'b1', name: 'test+abc://xyz/foo/b1', match: 0}, + \ #{id: 'b2', name: 'test_abc://xyz/foo/b2', match: 0}, + \ #{id: 'b3', name: 'test-abc://xyz/foo/b3', match: 1}, + \ #{id: 'b4', name: '-test://xyz/foo/b4' , match: 0}, + \ #{id: 'b5', name: 'test-://xyz/foo/b5' , match: 0}, + \] + for buf in bufnames + new `=buf.name` + if buf.match + call assert_equal(buf.name, getbufinfo(buf.id)[0].name) + else + " slashes will have become backslashes + call assert_notequal(buf.name, getbufinfo(buf.id)[0].name) + endif + bwipe + endfor + + set shellslash& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_conceal.vim b/src/nvim/testdir/test_conceal.vim index 1306dbe5cf..bffc2f49d3 100644 --- a/src/nvim/testdir/test_conceal.vim +++ b/src/nvim/testdir/test_conceal.vim @@ -4,10 +4,10 @@ source check.vim CheckFeature conceal source screendump.vim -" CheckScreendump func Test_conceal_two_windows() CheckScreendump + let code =<< trim [CODE] let lines = ["one one one one one", "two |hidden| here", "three |hidden| three"] call setline(1, lines) @@ -111,6 +111,7 @@ endfunc func Test_conceal_with_cursorline() CheckScreendump + " Opens a help window, where 'conceal' is set, switches to the other window " where 'cursorline' needs to be updated when the cursor moves. let code =<< trim [CODE] @@ -139,6 +140,7 @@ endfunc func Test_conceal_resize_term() CheckScreendump + let code =<< trim [CODE] call setline(1, '`one` `two` `three` `four` `five`, the backticks should be concealed') setl cocu=n cole=3 diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index fc4e80f0d6..c1f74e7675 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -1006,16 +1006,16 @@ func Test_edit_DROP() endfunc func Test_edit_CTRL_V() - if has("ebcdic") - return - endif + CheckNotFeature ebcdic + new call setline(1, ['abc']) call cursor(2, 1) + " force some redraws set showmode showcmd - "call test_override_char_avail(1) - " call test_override('ALL', 1) + " call test_override('char_avail', 1) + call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix') call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$')) @@ -1028,8 +1028,19 @@ func Test_edit_CTRL_V() set norl endif - " call test_override('ALL', 0) set noshowmode showcmd + " call test_override('char_avail', 0) + + " No modifiers should be applied to the char typed using i_CTRL-V_digit. + call feedkeys(":append\<CR>\<C-V>76c\<C-V>76\<C-F2>\<C-V>u3c0j\<C-V>u3c0\<M-F3>\<CR>.\<CR>", 'tnix') + call assert_equal('LcL<C-F2>ΟjΟ<M-F3>', getline(2)) + + if has('osx') + " A char with a modifier should not be a valid char for i_CTRL-V_digit. + call feedkeys("o\<C-V>\<D-j>\<C-V>\<D-1>\<C-V>\<D-o>\<C-V>\<D-x>\<C-V>\<D-u>", 'tnix') + call assert_equal('<D-j><D-1><D-o><D-x><D-u>', getline(3)) + endif + bw! endfunc diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index 92e0559618..78663f7deb 100644 --- a/src/nvim/testdir/test_ex_mode.vim +++ b/src/nvim/testdir/test_ex_mode.vim @@ -98,4 +98,14 @@ func Test_ex_mode_count_overflow() call delete('Xexmodescript') endfunc +func Test_ex_mode_large_indent() + new + set ts=500 ai + call setline(1, "\t") + exe "normal gQi\<CR>." + set ts=8 noai + bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 1d7fd3e385..5b10e691e5 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -283,6 +283,71 @@ function Test_printf_misc() call assert_equal('π', printf('%.2S', 'ππ')) call assert_equal('', printf('%.1S', 'ππ')) + call assert_equal('[ γγγ]', printf('[%10.6S]', 'γγγγγ')) + call assert_equal('[ γγγγ]', printf('[%10.8S]', 'γγγγγ')) + call assert_equal('[γγγγγ]', printf('[%10.10S]', 'γγγγγ')) + call assert_equal('[γγγγγ]', printf('[%10.12S]', 'γγγγγ')) + + call assert_equal('γγγ', printf('%S', 'γγγ')) + call assert_equal('γγγ', printf('%#S', 'γγγ')) + + call assert_equal('γb', printf('%2S', 'γb')) + call assert_equal('γb', printf('%.4S', 'γb')) + call assert_equal('γ', printf('%.2S', 'γb')) + call assert_equal(' γb', printf('%4S', 'γb')) + call assert_equal('0γb', printf('%04S', 'γb')) + call assert_equal('γb ', printf('%-4S', 'γb')) + call assert_equal('γ ', printf('%-4.2S', 'γb')) + + call assert_equal('aγ', printf('%2S', 'aγ')) + call assert_equal('aγ', printf('%.4S', 'aγ')) + call assert_equal('a', printf('%.2S', 'aγ')) + call assert_equal(' aγ', printf('%4S', 'aγ')) + call assert_equal('0aγ', printf('%04S', 'aγ')) + call assert_equal('aγ ', printf('%-4S', 'aγ')) + call assert_equal('a ', printf('%-4.2S', 'aγ')) + + call assert_equal('[γγγ]', printf('[%05S]', 'γγγ')) + call assert_equal('[γγγ]', printf('[%06S]', 'γγγ')) + call assert_equal('[0γγγ]', printf('[%07S]', 'γγγ')) + + call assert_equal('[γiγ]', printf('[%05S]', 'γiγ')) + call assert_equal('[0γiγ]', printf('[%06S]', 'γiγ')) + call assert_equal('[00γiγ]', printf('[%07S]', 'γiγ')) + + call assert_equal('[0γγ]', printf('[%05.4S]', 'γγγ')) + call assert_equal('[00γγ]', printf('[%06.4S]', 'γγγ')) + call assert_equal('[000γγ]', printf('[%07.4S]', 'γγγ')) + + call assert_equal('[00γi]', printf('[%05.4S]', 'γiγ')) + call assert_equal('[000γi]', printf('[%06.4S]', 'γiγ')) + call assert_equal('[0000γi]', printf('[%07.4S]', 'γiγ')) + + call assert_equal('[0γγ]', printf('[%05.5S]', 'γγγ')) + call assert_equal('[00γγ]', printf('[%06.5S]', 'γγγ')) + call assert_equal('[000γγ]', printf('[%07.5S]', 'γγγ')) + + call assert_equal('[γiγ]', printf('[%05.5S]', 'γiγ')) + call assert_equal('[0γiγ]', printf('[%06.5S]', 'γiγ')) + call assert_equal('[00γiγ]', printf('[%07.5S]', 'γiγ')) + + call assert_equal('[0000000000]', printf('[%010.0S]', 'γγγ')) + call assert_equal('[0000000000]', printf('[%010.1S]', 'γγγ')) + call assert_equal('[00000000γ]', printf('[%010.2S]', 'γγγ')) + call assert_equal('[00000000γ]', printf('[%010.3S]', 'γγγ')) + call assert_equal('[000000γγ]', printf('[%010.4S]', 'γγγ')) + call assert_equal('[000000γγ]', printf('[%010.5S]', 'γγγ')) + call assert_equal('[0000γγγ]', printf('[%010.6S]', 'γγγ')) + call assert_equal('[0000γγγ]', printf('[%010.7S]', 'γγγ')) + + call assert_equal('[0000000000]', printf('[%010.1S]', 'γiγ')) + call assert_equal('[00000000γ]', printf('[%010.2S]', 'γiγ')) + call assert_equal('[0000000γi]', printf('[%010.3S]', 'γiγ')) + call assert_equal('[0000000γi]', printf('[%010.4S]', 'γiγ')) + call assert_equal('[00000γiγ]', printf('[%010.5S]', 'γiγ')) + call assert_equal('[00000γiγ]', printf('[%010.6S]', 'γiγ')) + call assert_equal('[00000γiγ]', printf('[%010.7S]', 'γiγ')) + call assert_equal('1%', printf('%d%%', 1)) endfunc diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 8930d62fd3..eb4824aa32 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -190,8 +190,11 @@ let s:filename_checks = { \ 'freebasic': ['file.fb'], \ 'fsharp': ['file.fs', 'file.fsi', 'file.fsx'], \ 'fstab': ['fstab', 'mtab'], + \ 'fusion': ['file.fusion'], \ 'fvwm': ['/.fvwm/file', 'any/.fvwm/file'], \ 'gdb': ['.gdbinit', 'gdbinit'], + \ 'gdresource': ['file.tscn', 'file.tres'], + \ 'gdscript': ['file.gd'], \ 'gdmo': ['file.mo', 'file.gdmo'], \ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'], \ 'gemtext': ['file.gmi', 'file.gemini'], @@ -202,28 +205,36 @@ let s:filename_checks = { \ 'gitrebase': ['git-rebase-todo'], \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], \ 'gkrellmrc': ['gkrellmrc', 'gkrellmrc_x'], + \ 'glsl': ['file.glsl'], \ 'gnash': ['gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'], \ 'gnuplot': ['file.gpi', '.gnuplot'], \ 'go': ['file.go'], \ 'gomod': ['go.mod'], + \ 'gowork': ['go.work'], \ 'gp': ['file.gp', '.gprc'], \ 'gpg': ['/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel', 'any/.gnupg/gpg.conf', 'any/.gnupg/options', 'any/usr/any/gnupg/options.skel'], \ 'grads': ['file.gs'], + \ 'graphql': ['file.graphql', 'file.graphqls', 'file.gql'], \ 'gretl': ['file.gretl'], \ 'groovy': ['file.gradle', 'file.groovy'], \ 'group': ['any/etc/group', 'any/etc/group-', 'any/etc/group.edit', 'any/etc/gshadow', 'any/etc/gshadow-', 'any/etc/gshadow.edit', 'any/var/backups/group.bak', 'any/var/backups/gshadow.bak', '/etc/group', '/etc/group-', '/etc/group.edit', '/etc/gshadow', '/etc/gshadow-', '/etc/gshadow.edit', '/var/backups/group.bak', '/var/backups/gshadow.bak'], \ 'grub': ['/boot/grub/menu.lst', '/boot/grub/grub.conf', '/etc/grub.conf', 'any/boot/grub/grub.conf', 'any/boot/grub/menu.lst', 'any/etc/grub.conf'], \ 'gsp': ['file.gsp'], \ 'gtkrc': ['.gtkrc', 'gtkrc', '.gtkrc-file', 'gtkrc-file'], + \ 'hack': ['file.hack', 'file.hackpartial'], \ 'haml': ['file.haml'], \ 'hamster': ['file.hsm'], + \ 'handlebars': ['file.hbs'], \ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot', 'file.hsig'], \ 'haste': ['file.ht'], \ 'hastepreproc': ['file.htpp'], \ 'hb': ['file.hb'], + \ 'hcl': ['file.hcl'], \ 'hercules': ['file.vc', 'file.ev', 'file.sum', 'file.errsum'], + \ 'heex': ['file.heex'], \ 'hex': ['file.hex', 'file.h32'], \ 'hgcommit': ['hg-editor-file.txt'], + \ 'hjson': ['file.hjson'], \ 'hog': ['file.hog', 'snort.conf', 'vision.conf'], \ 'hollywood': ['file.hws'], \ 'hostconf': ['/etc/host.conf', 'any/etc/host.conf'], @@ -279,6 +290,7 @@ let s:filename_checks = { \ 'latte': ['file.latte', 'file.lte'], \ 'ld': ['file.ld'], \ 'ldif': ['file.ldif'], + \ 'ledger': ['file.ldg', 'file.ledger', 'file.journal'], \ 'less': ['file.less'], \ 'lex': ['file.lex', 'file.l', 'file.lxx', 'file.l++'], \ 'lftp': ['lftp.conf', '.lftprc', 'anylftp/rc', 'lftp/rc', 'some-lftp/rc'], @@ -352,6 +364,7 @@ let s:filename_checks = { \ 'netrc': ['.netrc'], \ 'nginx': ['file.nginx', 'nginxfile.conf', 'filenginx.conf', 'any/etc/nginx/file', 'any/usr/local/nginx/conf/file', 'any/nginx/file.conf'], \ 'ninja': ['file.ninja'], + \ 'nix': ['file.nix'], \ 'nqc': ['file.nqc'], \ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'], \ 'nsis': ['file.nsi', 'file.nsh'], @@ -394,6 +407,7 @@ let s:filename_checks = { \ 'ppd': ['file.ppd'], \ 'ppwiz': ['file.it', 'file.ih'], \ 'privoxy': ['file.action'], + \ 'prisma': ['file.prisma'], \ 'proc': ['file.pc'], \ 'procmail': ['.procmail', '.procmailrc'], \ 'prolog': ['file.pdb'], @@ -404,10 +418,12 @@ let s:filename_checks = { \ 'ps1xml': ['file.ps1xml'], \ 'psf': ['file.psf'], \ 'psl': ['file.psl'], + \ 'pug': ['file.pug'], \ 'puppet': ['file.pp'], \ 'pyret': ['file.arr'], \ 'pyrex': ['file.pyx', 'file.pxd'], \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'], + \ 'ql': ['file.ql', 'file.qll'], \ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'], \ 'radiance': ['file.rad', 'file.mat'], \ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'], @@ -489,6 +505,7 @@ let s:filename_checks = { \ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'], \ 'stp': ['file.stp'], \ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp', '/etc/sudoers', 'any/etc/sudoers.d/file'], + \ 'surface': ['file.sface'], \ 'svg': ['file.svg'], \ 'svn': ['svn-commitfile.tmp', 'svn-commit-file.tmp', 'svn-commit.tmp'], \ 'swift': ['file.swift'], @@ -502,8 +519,10 @@ let s:filename_checks = { \ 'taskdata': ['pending.data', 'completed.data', 'undo.data'], \ 'taskedit': ['file.task'], \ 'tcl': ['file.tcl', 'file.tm', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl', '.tclshrc', 'tclsh.rc', '.wishrc'], + \ 'teal': ['file.tl'], \ 'teraterm': ['file.ttl'], \ 'terminfo': ['file.ti'], + \ 'terraform': ['file.tfvars'], \ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'], \ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'], \ 'texmf': ['texmf.cnf'], @@ -511,6 +530,7 @@ let s:filename_checks = { \ 'tf': ['file.tf', '.tfrc', 'tfrc'], \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'], \ 'tilde': ['file.t.html'], + \ 'tla': ['file.tla'], \ 'tli': ['file.tli'], \ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf', '.tmux-file.conf', '.tmux.conf', 'tmux-file.conf', 'tmux.conf', 'tmux.conf.local'], \ 'toml': ['file.toml', 'Gopkg.lock', 'Pipfile', '/home/user/.cargo/config'], @@ -571,6 +591,7 @@ let s:filename_checks = { \ 'xslt': ['file.xsl', 'file.xslt'], \ 'yacc': ['file.yy', 'file.yxx', 'file.y++'], \ 'yaml': ['file.yaml', 'file.yml'], + \ 'yang': ['file.yang'], \ 'raml': ['file.raml'], \ 'z8a': ['file.z8a'], \ 'zig': ['file.zig'], diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim index a52a66ac2f..1cd3a2287b 100644 --- a/src/nvim/testdir/test_filter_map.vim +++ b/src/nvim/testdir/test_filter_map.vim @@ -88,4 +88,14 @@ func Test_map_filter_fails() call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:') endfunc +func Test_map_and_modify() + let l = ["abc"] + " cannot change the list halfway a map() + call assert_fails('call map(l, "remove(l, 0)[0]")', 'E741:') + + let d = #{a: 1, b: 2, c: 3} + call assert_fails('call map(d, "remove(d, v:key)[0]")', 'E741:') + call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index ce75799551..6803271c03 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -445,6 +445,28 @@ func Test_issue_7021() set completeslash= endfunc +func Test_pum_stopped_by_timer() + CheckScreendump + + let lines =<< trim END + call setline(1, ['hello', 'hullo', 'heeee', '']) + func StartCompl() + call timer_start(100, { -> execute('stopinsert') }) + call feedkeys("Gah\<C-N>") + endfunc + END + + call writefile(lines, 'Xpumscript') + let buf = RunVimInTerminal('-S Xpumscript', #{rows: 12}) + call term_sendkeys(buf, ":call StartCompl()\<CR>") + call TermWait(buf, 200) + call term_sendkeys(buf, "k") + call VerifyScreenDump(buf, 'Test_pum_stopped_by_timer', {}) + + call StopVimInTerminal(buf) + call delete('Xpumscript') +endfunc + func Test_pum_with_folds_two_tabs() CheckScreendump diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim index 0bcbd9c4a5..c6e2ebd406 100644 --- a/src/nvim/testdir/test_listchars.vim +++ b/src/nvim/testdir/test_listchars.vim @@ -1,6 +1,8 @@ " Tests for 'listchars' display with 'list' and :list +source check.vim source view_util.vim +source screendump.vim func Test_listchars() enew! @@ -517,4 +519,34 @@ func Test_listchars_window_local() set list& listchars& endfunc +func Test_listchars_foldcolumn() + CheckScreendump + + let lines =<< trim END + call setline(1, ['aaa', '', 'a', 'aaaaaa']) + vsplit + vsplit + windo set signcolumn=yes foldcolumn=1 winminwidth=0 nowrap list listchars=extends:>,precedes:< + END + call writefile(lines, 'XTest_listchars') + + let buf = RunVimInTerminal('-S XTest_listchars', {'rows': 10, 'cols': 60}) + + call term_sendkeys(buf, "13\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_01', {}) + call term_sendkeys(buf, "\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_02', {}) + call term_sendkeys(buf, "\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_03', {}) + call term_sendkeys(buf, "\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_04', {}) + call term_sendkeys(buf, "\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_05', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_listchars') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim index c38e0c5f3c..1f100d6244 100644 --- a/src/nvim/testdir/test_listlbr_utf8.vim +++ b/src/nvim/testdir/test_listlbr_utf8.vim @@ -69,6 +69,16 @@ func Test_nolinebreak_with_list() call s:close_windows() endfunc +" this was causing a crash +func Test_linebreak_with_list_and_tabs() + set linebreak list listchars=tab:β€\ β₯ tabstop=100 + new + call setline(1, "\t\t\ttext") + redraw + bwipe! + set nolinebreak nolist listchars&vim tabstop=8 +endfunc + func Test_linebreak_with_nolist() call s:test_windows('setl nolist') call setline(1, "\t*mask = nil;") diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index aff22f5d01..cb1d66cb98 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -2759,4 +2759,16 @@ func Test_normal_count_after_operator() bw! endfunc +func Test_normal_gj_on_extra_wide_char() + new | 25vsp + let text='1 foooooooo ar e insβzwe1 foooooooo insβzwei' . + \ ' i drei vier fΓΌnf sechs sieben acht un zehn elf zwΓΆfl' . + \ ' dreizehn v ierzehn fΓΌnfzehn' + put =text + call cursor(2,1) + norm! gj + call assert_equal([0,2,25,0], getpos('.')) + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 5946732937..2312df5450 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -51,7 +51,7 @@ func Test_wildoptions() call assert_equal('tagfile', &wildoptions) endfunc -function! Test_options() +func Test_options_command() let caught = 'ok' try options @@ -88,7 +88,7 @@ function! Test_options() " close option-window close -endfunction +endfunc function! Test_path_keep_commas() " Test that changing 'path' keeps two commas. @@ -368,6 +368,13 @@ func Test_set_all() set tw& iskeyword& splitbelow& endfunc +func Test_set_one_column() + let out_mult = execute('set all')->split("\n") + let out_one = execute('set! all')->split("\n") + " one column should be two to four times as many lines + call assert_inrange(len(out_mult) * 2, len(out_mult) * 4, len(out_one)) +endfunc + func Test_set_values() " The file is only generated when running "make test" in the src directory. if filereadable('opt_test.vim') diff --git a/src/nvim/testdir/test_scriptnames.vim b/src/nvim/testdir/test_scriptnames.vim index fc6c910bfa..44ec146666 100644 --- a/src/nvim/testdir/test_scriptnames.vim +++ b/src/nvim/testdir/test_scriptnames.vim @@ -23,4 +23,10 @@ func Test_scriptnames() bwipe call delete('Xscripting') + + let msgs = execute('messages') + scriptnames + call assert_equal(msgs, execute('messages')) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim new file mode 100644 index 0000000000..b483841060 --- /dev/null +++ b/src/nvim/testdir/test_selectmode.vim @@ -0,0 +1,57 @@ +" Test for Select-mode + +source shared.vim + +" Test for selecting a register with CTRL-R +func Test_selectmode_register() + new + + " Default behavior: use unnamed register + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>a" + call assert_equal(getline('.'), 'aoo') + call assert_equal('f', getreg('"')) + call assert_equal('baz', getreg('a')) + + " Use the black hole register + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>\<c-r>_a" + call assert_equal(getline('.'), 'aoo') + call assert_equal('bar', getreg('"')) + call assert_equal('baz', getreg('a')) + + " Invalid register: use unnamed register + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>\<c-r>?a" + call assert_equal(getline('.'), 'aoo') + call assert_equal('f', getreg('"')) + call assert_equal('baz', getreg('a')) + + " Use unnamed register + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>\<c-r>\"a" + call assert_equal(getline('.'), 'aoo') + call assert_equal('f', getreg('"')) + call assert_equal('baz', getreg('a')) + + " use specicifed register, unnamed register is also written + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>\<c-r>aa" + call assert_equal(getline('.'), 'aoo') + call assert_equal('f', getreg('"')) + call assert_equal('f', getreg('a')) + + bw! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index a3e4dcdd25..f40c9ae097 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -186,7 +186,16 @@ func Test_statusline() set virtualedit=all norm 10| call assert_match('^10,-10\s*$', s:get_statusline()) + set list + call assert_match('^10,-10\s*$', s:get_statusline()) set virtualedit& + exe "norm A\<Tab>\<Tab>a\<Esc>" + " In list mode a <Tab> is shown as "^I", which is 2-wide. + call assert_match('^9,-9\s*$', s:get_statusline()) + set list& + " Now the second <Tab> ends at the 16th screen column. + call assert_match('^17,-17\s*$', s:get_statusline()) + undo " %w: Preview window flag, text is "[Preview]". " %W: Preview window flag, text is ",PRV". diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index aae315b2c5..09eed4e10d 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -379,4 +379,27 @@ func Test_timer_invalid_callback() call assert_fails('call timer_start(0, "0")', 'E921') endfunc +func Test_timer_using_win_execute_undo_sync() + let bufnr1 = bufnr() + new + let g:bufnr2 = bufnr() + let g:winid = win_getid() + exe "buffer " .. bufnr1 + wincmd w + call setline(1, ['test']) + autocmd InsertEnter * call timer_start(100, { -> win_execute(g:winid, 'buffer ' .. g:bufnr2) }) + call timer_start(200, { -> feedkeys("\<CR>bbbb\<Esc>") }) + call feedkeys("Oaaaa", 'x!t') + " will hang here until the second timer fires + call assert_equal(['aaaa', 'bbbb', 'test'], getline(1, '$')) + undo + call assert_equal(['test'], getline(1, '$')) + + bwipe! + bwipe! + unlet g:winid + unlet g:bufnr2 + au! InsertEnter +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index 0a218898ed..d2a5258bd3 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -258,4 +258,139 @@ func Test_yank_paste_small_del_reg() set virtualedit= endfunc +" After calling s:TryVirtualeditReplace(), line 1 will contain one of these +" two strings, depending on whether virtual editing is on or off. +let s:result_ve_on = 'a x' +let s:result_ve_off = 'x' + +" Utility function for Test_global_local_virtualedit() +func s:TryVirtualeditReplace() + call setline(1, 'a') + normal gg7l + normal rx +endfunc + +" Test for :set and :setlocal +func Test_global_local_virtualedit() + new + + " Verify that 'virtualedit' is initialized to empty, can be set globally to + " all and to empty, and can be set locally to all and to empty. + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + set ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + set ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + " Verify that :set affects multiple windows. + split + set ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + wincmd p + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + set ve= + wincmd p + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + bwipe! + + " Verify that :setlocal affects only the current window. + new + split + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + wincmd p + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + bwipe! + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + " Verify that the buffer 'virtualedit' state follows the global value only + " when empty and that "none" works as expected. + " + " 'virtualedit' State + " +--------+--------------------------+ + " | Local | Global | + " | | | + " +--------+--------+--------+--------+ + " | | "" | "all" | "none" | + " +--------+--------+--------+--------+ + " | "" | off | on | off | + " | "all" | on | on | on | + " | "none" | off | off | off | + " +--------+--------+--------+--------+ + new + + setglobal ve= + setlocal ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve=none + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + setglobal ve=all + setlocal ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve=none + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + setlocal ve=NONE + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + setglobal ve=none + setlocal ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve=none + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + bwipe! + + " Verify that the 'virtualedit' state is copied to new windows. + new + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + split + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + split + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve= + split + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + bwipe! + + setlocal virtualedit& + set virtualedit& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index d58ca92a2f..76274fb038 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -1103,6 +1103,13 @@ func Test_visual_put_blockedit_zy_and_zp() bw! endfunc +func Test_visual_block_yank_zy() + new + " this was reading before the start of the line + exe "norm o\<C-T>\<Esc>\<C-V>zy" + bwipe! +endfunc + func Test_visual_block_with_virtualedit() CheckScreendump @@ -1117,10 +1124,67 @@ func Test_visual_block_with_virtualedit() call term_sendkeys(buf, "\<C-V>gg$") call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit', {}) + call term_sendkeys(buf, "\<Esc>gg\<C-V>G$") + call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit2', {}) + " clean up call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('XTest_beval') + call delete('XTest_block') +endfunc + +func Test_visual_block_ctrl_w_f() + " Emtpy block selected in new buffer should not result in an error. + au! BufNew foo sil norm f + edit foo + + au! BufNew +endfunc + +func Test_visual_reselect_with_count() + " this was causing an illegal memory access + let lines =<< trim END + + + + : + r<sfile> + exe "%norm e3\<c-v>kr\t" + : + + : + END + call writefile(lines, 'XvisualReselect') + source XvisualReselect + + bwipe! + call delete('XvisualReselect') +endfunc + +" this was leaving the end of the Visual area beyond the end of a line +func Test_visual_ex_copy_line() + new + call setline(1, ["aaa", "bbbbbbbbbxbb"]) + /x + exe "normal ggvjfxO" + t0 + normal gNU + bwipe! +endfunc + +" This was leaving the end of the Visual area beyond the end of a line. +" Set 'undolevels' to start a new undo block. +func Test_visual_undo_deletes_last_line() + new + call setline(1, ["aaa", "ccc", "dyd"]) + set undolevels=100 + exe "normal obbbbbbbbbxbb\<Esc>" + set undolevels=100 + /y + exe "normal ggvjfxO" + undo + normal gNU + bwipe! endfunc diff --git a/src/nvim/undo.c b/src/nvim/undo.c index d18f35a43a..2d8df4cad8 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2633,6 +2633,10 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) } } + if (VIsual_active) { + check_pos(curbuf, &VIsual); + } + smsg_attr_keep(0, _("%" PRId64 " %s; %s #%" PRId64 " %s"), u_oldcount < 0 ? (int64_t)-u_oldcount : (int64_t)u_oldcount, |