diff options
Diffstat (limited to 'src')
54 files changed, 2275 insertions, 451 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index f84e8c99a4..b371c08d2a 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1489,21 +1489,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, return 0; } - size_t len = 0; - if (line < 0 || line > buf->b_ml.ml_line_count) { - api_set_error(err, kErrorTypeValidation, "line value outside range"); - return 0; - } else if (line < buf->b_ml.ml_line_count) { - len = STRLEN(ml_get_buf(buf, (linenr_T)line+1, false)); - } - - if (col == -1) { - col = (Integer)len; - } else if (col < -1 || col > (Integer)len) { - api_set_error(err, kErrorTypeValidation, "col value outside range"); - return 0; - } - bool ephemeral = false; uint64_t id = 0; @@ -1674,6 +1659,22 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, } } + size_t len = 0; + if (line < 0 || line > buf->b_ml.ml_line_count) { + api_set_error(err, kErrorTypeValidation, "line value outside range"); + return 0; + } else if (line < buf->b_ml.ml_line_count) { + len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line+1, false)); + } + + if (col == -1) { + col = (Integer)len; + } else if (col < -1 || col > (Integer)len) { + api_set_error(err, kErrorTypeValidation, "col value outside range"); + return 0; + } + + // Only error out if they try to set end_right_gravity without // setting end_col or end_line if (line2 == -1 && col2 == -1 && end_gravity_set) { @@ -1684,7 +1685,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, if (col2 >= 0) { if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) { - len = STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false)); + len = ephemeral ? MAXCOL : STRLEN( + ml_get_buf(buf, (linenr_T)line2 + 1, false)); } else if (line2 == buf->b_ml.ml_line_count) { // We are trying to add an extmark past final newline len = 0; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 4b1c2d4baa..c7d261ba18 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1770,6 +1770,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false }, { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false }, { "shadow", { "", "", " ", " ", " ", " ", " ", "" }, true }, + { "rounded", { "╭", "─", "╮", "│", "╯", "─", "╰", "│" }, false }, { "solid", { " ", " ", " ", " ", " ", " ", " ", " " }, false }, { NULL, { { NUL } } , false }, }; diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 11e21a88ea..35d39a34d7 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -130,7 +130,8 @@ void popupmenu_hide(void) void popupmenu_select(Integer selected) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -void tabline_update(Tabpage current, Array tabs) +void tabline_update(Tabpage current, Array tabs, + Buffer current_buffer, Array buffers) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_show(Array content, Integer pos, String firstc, String prompt, diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 60535b13b3..349cc0e7da 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1437,28 +1437,30 @@ void nvim_chan_send(Integer chan, String data, Error *err) /// end-of-buffer region is hidden by setting `eob` flag of /// 'fillchars' to a space char, and clearing the /// |EndOfBuffer| region in 'winhighlight'. -/// - `border`: style of (optional) window border. This can either be a string -/// or an array. the string values are: -/// - "none" No border. This is the default -/// - "single" a single line box -/// - "double" a double line box -/// - "shadow" a drop shadow effect by blending with the background. -/// If it is an array it should be an array of eight items or any divisor of +/// - `border`: Style of (optional) window border. This can either be a string +/// or an array. The string values are +/// - "none": No border (default). +/// - "single": A single line box. +/// - "double": A double line box. +/// - "rounded": Like "single", but with rounded corners ("╭" etc.). +/// - "solid": Adds padding by a single whitespace cell. +/// - "shadow": A drop shadow effect by blending with the background. +/// - If it is an array, it should have a length of eight or any divisor of /// eight. The array will specifify the eight chars building up the border -/// in a clockwise fashion starting with the top-left corner. As, an -/// example, the double box style could be specified as: -/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ] -/// if the number of chars are less than eight, they will be repeated. Thus -/// an ASCII border could be specified as: -/// [ "/", "-", "\\", "|" ] -/// or all chars the same as: -/// [ "x" ] -/// An empty string can be used to turn off a specific border, for instance: +/// in a clockwise fashion starting with the top-left corner. As an +/// example, the double box style could be specified as +/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. +/// If the number of chars are less than eight, they will be repeated. Thus +/// an ASCII border could be specified as +/// [ "/", "-", "\\", "|" ], +/// or all chars the same as +/// [ "x" ]. +/// An empty string can be used to turn off a specific border, for instance, /// [ "", "", "", ">", "", "", "", "<" ] /// will only make vertical borders but not horizontal ones. -/// By default `FloatBorder` highlight is used which links to `VertSplit` +/// By default, `FloatBorder` highlight is used, which links to `VertSplit` /// when not defined. It could also be specified by character: -/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ] +/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]. /// - `noautocmd`: If true then no buffer-related autocommand events such as /// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from /// calling this function. diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 6a50264e0f..0b72dd1885 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -5665,7 +5665,7 @@ bool buf_contents_changed(buf_T *buf) void wipe_buffer( buf_T *buf, - int aucmd // When true trigger autocommands. + bool aucmd // When true trigger autocommands. ) { if (!aucmd) { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a3fa9c986f..ff019d1e07 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2340,10 +2340,8 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), &tv, &di, true, false) == OK) { if ((di == NULL - || (!var_check_ro(di->di_flags, (const char *)lp->ll_name, - TV_CSTRING) - && !tv_check_lock(di->di_tv.v_lock, (const char *)lp->ll_name, - TV_CSTRING))) + || (!var_check_ro(di->di_flags, lp->ll_name, TV_CSTRING) + && !tv_check_lock(&di->di_tv, lp->ll_name, TV_CSTRING))) && eexe_mod_op(&tv, rettv, op) == OK) { set_var(lp->ll_name, lp->ll_name_len, &tv, false); } @@ -2353,10 +2351,10 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, set_var_const(lp->ll_name, lp->ll_name_len, rettv, copy, is_const); } *endp = cc; - } else if (tv_check_lock(lp->ll_newkey == NULL - ? lp->ll_tv->v_lock - : lp->ll_tv->vval.v_dict->dv_lock, - (const char *)lp->ll_name, TV_CSTRING)) { + } else if (var_check_lock(lp->ll_newkey == NULL + ? lp->ll_tv->v_lock + : lp->ll_tv->vval.v_dict->dv_lock, + lp->ll_name, TV_CSTRING)) { } else if (lp->ll_range) { listitem_T *ll_li = lp->ll_li; int ll_n1 = lp->ll_n1; @@ -2369,9 +2367,8 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, // Check whether any of the list items is locked for (ri = tv_list_first(rettv->vval.v_list); ri != NULL && ll_li != NULL; ) { - if (tv_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, - (const char *)lp->ll_name, - TV_CSTRING)) { + if (var_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, lp->ll_name, + TV_CSTRING)) { return; } ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri); @@ -2795,13 +2792,13 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, exarg_T *eap, } else if ((lp->ll_list != NULL // ll_list is not NULL when lvalue is not in a list, NULL lists // yield E689. - && tv_check_lock(tv_list_locked(lp->ll_list), - (const char *)lp->ll_name, - lp->ll_name_len)) + && var_check_lock(tv_list_locked(lp->ll_list), + lp->ll_name, + lp->ll_name_len)) || (lp->ll_dict != NULL - && tv_check_lock(lp->ll_dict->dv_lock, - (const char *)lp->ll_name, - lp->ll_name_len))) { + && var_check_lock(lp->ll_dict->dv_lock, + lp->ll_name, + lp->ll_name_len))) { return FAIL; } else if (lp->ll_range) { assert(lp->ll_list != NULL); @@ -2810,9 +2807,9 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, exarg_T *eap, listitem_T *last_li = first_li; for (;;) { listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); - if (tv_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, - (const char *)lp->ll_name, - lp->ll_name_len)) { + if (var_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, + lp->ll_name, + lp->ll_name_len)) { return false; } lp->ll_li = li; @@ -2897,11 +2894,11 @@ int do_unlet(const char *const name, const size_t name_len, const bool forceit) dictitem_T *const di = TV_DICT_HI2DI(hi); if (var_check_fixed(di->di_flags, (const char *)name, TV_CSTRING) || var_check_ro(di->di_flags, (const char *)name, TV_CSTRING) - || tv_check_lock(d->dv_lock, (const char *)name, TV_CSTRING)) { + || var_check_lock(d->dv_lock, name, TV_CSTRING)) { return FAIL; } - if (tv_check_lock(d->dv_lock, (const char *)name, TV_CSTRING)) { + if (var_check_lock(d->dv_lock, name, TV_CSTRING)) { return FAIL; } @@ -5962,14 +5959,14 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) if (argvars[0].v_type == VAR_LIST) { tv_copy(&argvars[0], rettv); if ((l = argvars[0].vval.v_list) == NULL - || (!map && tv_check_lock(tv_list_locked(l), arg_errmsg, - TV_TRANSLATE))) { + || (!map + && var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) { return; } } else if (argvars[0].v_type == VAR_DICT) { tv_copy(&argvars[0], rettv); if ((d = argvars[0].vval.v_dict) == NULL - || (!map && tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { + || (!map && var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { return; } } else { @@ -6002,7 +5999,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) di = TV_DICT_HI2DI(hi); if (map - && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) + && (var_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) { break; } @@ -6029,8 +6026,8 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) for (listitem_T *li = tv_list_first(l); li != NULL;) { if (map - && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, - TV_TRANSLATE)) { + && var_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, + TV_TRANSLATE)) { break; } vimvars[VV_KEY].vv_nr = idx; @@ -7200,12 +7197,15 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) r = FAIL; } else if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) { char_u *name = arg->vval.v_string; - if (name != NULL) { + if (name == NULL) { + r = FAIL; + } else if (*name == NUL) { + callback->type = kCallbackNone; + callback->data.funcref = NULL; + } else { func_ref(name); callback->data.funcref = vim_strsave(name); callback->type = kCallbackFuncref; - } else { - r = FAIL; } } else if (nlua_is_table_from_lua(arg)) { char_u *name = nlua_register_table_as_callable(arg); @@ -7216,8 +7216,10 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) } else { r = FAIL; } - } else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) { + } else if (arg->v_type == VAR_SPECIAL + || (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0)) { callback->type = kCallbackNone; + callback->data.funcref = NULL; } else { r = FAIL; } @@ -7324,14 +7326,7 @@ void add_timer_info(typval_T *rettv, timer_T *timer) return; } - if (timer->callback.type == kCallbackPartial) { - di->di_tv.v_type = VAR_PARTIAL; - di->di_tv.vval.v_partial = timer->callback.data.partial; - timer->callback.data.partial->pt_refcount++; - } else if (timer->callback.type == kCallbackFuncref) { - di->di_tv.v_type = VAR_FUNC; - di->di_tv.vval.v_string = vim_strsave(timer->callback.data.funcref); - } + callback_put(&timer->callback, &di->di_tv); } void add_timer_info_all(typval_T *rettv) @@ -8949,7 +8944,7 @@ static void set_var_const(const char *name, const size_t name_len, // existing variable, need to clear the value if (var_check_ro(v->di_flags, name, name_len) - || tv_check_lock(v->di_tv.v_lock, name, name_len)) { + || var_check_lock(v->di_tv.v_lock, name, name_len)) { return; } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index ce09d268ea..1ba31bfe68 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -268,7 +268,8 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 1; // Default: failed. if (argvars[0].v_type == VAR_LIST) { list_T *const l = argvars[0].vval.v_list; - if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) { + if (!var_check_lock(tv_list_locked(l), N_("add() argument"), + TV_TRANSLATE)) { tv_list_append_tv(l, &argvars[1]); tv_copy(&argvars[0], rettv); } @@ -2277,9 +2278,9 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) list = argvars[0].vval.v_list; if (list != NULL - && !tv_check_lock(tv_list_locked(list), - N_("flatten() argument"), - TV_TRANSLATE) + && !var_check_lock(tv_list_locked(list), + N_("flatten() argument"), + TV_TRANSLATE) && tv_list_flatten(list, maxdepth) == OK) { tv_copy(&argvars[0], rettv); } @@ -2299,7 +2300,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *const l1 = argvars[0].vval.v_list; list_T *const l2 = argvars[1].vval.v_list; - if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { + if (!var_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { listitem_T *item; if (argvars[2].v_type != VAR_UNKNOWN) { before = (long)tv_get_number_chk(&argvars[2], &error); @@ -2328,13 +2329,13 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) dict_T *const d1 = argvars[0].vval.v_dict; dict_T *const d2 = argvars[1].vval.v_dict; if (d1 == NULL) { - const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); + const bool locked = var_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); (void)locked; assert(locked == true); } else if (d2 == NULL) { // Do nothing tv_copy(&argvars[0], rettv); - } else if (!tv_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { + } else if (!var_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { const char *action = "force"; // Check the third argument. if (argvars[2].v_type != VAR_UNKNOWN) { @@ -4845,8 +4846,8 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "insert()"); - } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("insert() argument"), TV_TRANSLATE)) { + } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("insert() argument"), TV_TRANSLATE)) { long before = 0; if (argvars[2].v_type != VAR_UNKNOWN) { before = tv_get_number_chk(&argvars[2], &error); @@ -7079,7 +7080,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[2].v_type != VAR_UNKNOWN) { EMSG2(_(e_toomanyarg), "remove()"); } else if ((d = argvars[0].vval.v_dict) != NULL - && !tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { + && !var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { const char *key = tv_get_string_chk(&argvars[1]); if (key != NULL) { di = tv_dict_find(d, key, -1); @@ -7098,8 +7099,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } else if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listdictarg), "remove()"); - } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - arg_errmsg, TV_TRANSLATE)) { + } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + arg_errmsg, TV_TRANSLATE)) { bool error = false; idx = tv_get_number_chk(&argvars[1], &error); @@ -7374,8 +7375,8 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *l; if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "reverse()"); - } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("reverse() argument"), TV_TRANSLATE)) { + } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("reverse() argument"), TV_TRANSLATE)) { tv_list_reverse(l); tv_list_set_ret(rettv, l); } @@ -9462,7 +9463,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); } else { list_T *const l = argvars[0].vval.v_list; - if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { + if (var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { goto theend; } tv_list_set_ret(rettv, l); @@ -9661,6 +9662,18 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *word = ""; hlf_T attr = HLF_COUNT; size_t len = 0; + const int wo_spell_save = curwin->w_p_spell; + + if (!curwin->w_p_spell) { + did_set_spelllang(curwin); + curwin->w_p_spell = true; + } + + if (*curwin->w_s->b_p_spl == NUL) { + EMSG(_(e_no_spell)); + curwin->w_p_spell = wo_spell_save; + return; + } if (argvars[0].v_type == VAR_UNKNOWN) { // Find the start and length of the badly spelled word. @@ -9669,7 +9682,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) word = (char *)get_cursor_pos_ptr(); curwin->w_set_curswant = true; } - } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) { + } else if (*curbuf->b_s.b_p_spl != NUL) { const char *str = tv_get_string_chk(&argvars[0]); int capcol = -1; @@ -9687,6 +9700,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } + curwin->w_p_spell = wo_spell_save; assert(len <= INT_MAX); tv_list_alloc_ret(rettv, 2); @@ -9708,8 +9722,20 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) int maxcount; garray_T ga = GA_EMPTY_INIT_VALUE; bool need_capital = false; + const int wo_spell_save = curwin->w_p_spell; + + if (!curwin->w_p_spell) { + did_set_spelllang(curwin); + curwin->w_p_spell = true; + } + + if (*curwin->w_s->b_p_spl == NUL) { + EMSG(_(e_no_spell)); + curwin->w_p_spell = wo_spell_save; + return; + } - if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { + if (*curwin->w_s->b_p_spl != NUL) { const char *const str = tv_get_string(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) { maxcount = tv_get_number_chk(&argvars[1], &typeerr); @@ -9736,6 +9762,7 @@ f_spellsuggest_return: tv_list_append_allocated_string(rettv->vval.v_list, p); } ga_clear(&ga); + curwin->w_p_spell = wo_spell_save; } static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 61de83fc21..7221dc8bc9 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1162,6 +1162,49 @@ void callback_free(Callback *callback) } } callback->type = kCallbackNone; + callback->data.funcref = NULL; +} + +/// Copy a callback into a typval_T. +void callback_put(Callback *cb, typval_T *tv) + FUNC_ATTR_NONNULL_ALL +{ + switch (cb->type) { + case kCallbackPartial: + tv->v_type = VAR_PARTIAL; + tv->vval.v_partial = cb->data.partial; + cb->data.partial->pt_refcount++; + break; + case kCallbackFuncref: + tv->v_type = VAR_FUNC; + tv->vval.v_string = vim_strsave(cb->data.funcref); + func_ref(cb->data.funcref); + break; + default: + tv->v_type = VAR_SPECIAL; + tv->vval.v_special = kSpecialVarNull; + break; + } +} + +// Copy callback from "src" to "dest", incrementing the refcounts. +void callback_copy(Callback *dest, Callback *src) + FUNC_ATTR_NONNULL_ALL +{ + dest->type = src->type; + switch (src->type) { + case kCallbackPartial: + dest->data.partial = src->data.partial; + dest->data.partial->pt_refcount++; + break; + case kCallbackFuncref: + dest->data.funcref = vim_strsave(src->data.funcref); + func_ref(src->data.funcref); + break; + default: + dest->data.funcref = NULL; + break; + } } /// Remove watcher from a dictionary @@ -1951,7 +1994,7 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, } else if (*action == 'f' && di2 != di1) { typval_T oldtv; - if (tv_check_lock(di1->di_tv.v_lock, arg_errmsg, arg_errmsg_len) + if (var_check_lock(di1->di_tv.v_lock, arg_errmsg, arg_errmsg_len) || var_check_ro(di1->di_flags, arg_errmsg, arg_errmsg_len)) { break; } @@ -2582,7 +2625,7 @@ bool tv_islocked(const typval_T *const tv) /// /// Also gives an error message when typval is locked. /// -/// @param[in] lock Lock status. +/// @param[in] tv Typval. /// @param[in] name Variable name, used in the error message. /// @param[in] name_len Variable name length. Use #TV_TRANSLATE to translate /// variable name and compute the length. Use #TV_CSTRING @@ -2596,10 +2639,37 @@ bool tv_islocked(const typval_T *const tv) /// gettext. /// /// @return true if variable is locked, false otherwise. -bool tv_check_lock(const VarLockStatus lock, const char *name, +bool tv_check_lock(const typval_T *tv, const char *name, size_t name_len) FUNC_ATTR_WARN_UNUSED_RESULT { + VarLockStatus lock = VAR_UNLOCKED; + + switch (tv->v_type) { + // case VAR_BLOB: + // if (tv->vval.v_blob != NULL) + // lock = tv->vval.v_blob->bv_lock; + // break; + case VAR_LIST: + if (tv->vval.v_list != NULL) { + lock = tv->vval.v_list->lv_lock; + } + break; + case VAR_DICT: + if (tv->vval.v_dict != NULL) { + lock = tv->vval.v_dict->dv_lock; + } + break; + default: + break; + } + return var_check_lock(tv->v_lock, name, name_len) + || (lock != VAR_UNLOCKED && var_check_lock(lock, name, name_len)); +} + +/// @return true if variable "name" is locked (immutable) +bool var_check_lock(VarLockStatus lock, const char *name, size_t name_len) +{ const char *error_message = NULL; switch (lock) { case VAR_UNLOCKED: { diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index 2b4612016b..050b84efec 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -120,7 +120,7 @@ typedef enum { VAR_DICT, ///< Dictionary, .v_dict is used. VAR_FLOAT, ///< Floating-point value, .v_float is used. VAR_BOOL, ///< true, false - VAR_SPECIAL, ///< Special value (true, false, null), .v_special + VAR_SPECIAL, ///< Special value (null), .v_special ///< is used. VAR_PARTIAL, ///< Partial, .v_partial is used. } VarType; diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index f5d1b1e870..5ffc06ec44 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2455,13 +2455,13 @@ void ex_function(exarg_T *eap) goto erret; } if (fudi.fd_di == NULL) { - if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, - TV_CSTRING)) { + if (var_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, + TV_CSTRING)) { // Can't add a function to a locked dictionary goto erret; } - } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, - TV_CSTRING)) { + } else if (var_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, + TV_CSTRING)) { // Can't change an existing function if it is locked goto erret; } diff --git a/src/nvim/event/multiqueue.c b/src/nvim/event/multiqueue.c index 1e6d62135c..f534fc483f 100644 --- a/src/nvim/event/multiqueue.c +++ b/src/nvim/event/multiqueue.c @@ -73,7 +73,7 @@ struct multiqueue_item { struct multiqueue { MultiQueue *parent; QUEUE headtail; // circularly-linked - put_callback put_cb; + PutCallback put_cb; void *data; size_t size; }; @@ -91,7 +91,7 @@ typedef struct { static Event NILEVENT = { .handler = NULL, .argv = {NULL} }; -MultiQueue *multiqueue_new_parent(put_callback put_cb, void *data) +MultiQueue *multiqueue_new_parent(PutCallback put_cb, void *data) { return multiqueue_new(NULL, put_cb, data); } @@ -104,7 +104,7 @@ MultiQueue *multiqueue_new_child(MultiQueue *parent) return multiqueue_new(parent, NULL, NULL); } -static MultiQueue *multiqueue_new(MultiQueue *parent, put_callback put_cb, +static MultiQueue *multiqueue_new(MultiQueue *parent, PutCallback put_cb, void *data) { MultiQueue *rv = xmalloc(sizeof(MultiQueue)); diff --git a/src/nvim/event/multiqueue.h b/src/nvim/event/multiqueue.h index a688107665..dc60fbb4c7 100644 --- a/src/nvim/event/multiqueue.h +++ b/src/nvim/event/multiqueue.h @@ -7,7 +7,7 @@ #include "nvim/lib/queue.h" typedef struct multiqueue MultiQueue; -typedef void (*put_callback)(MultiQueue *multiq, void *data); +typedef void (*PutCallback)(MultiQueue *multiq, void *data); #define multiqueue_put(q, h, ...) \ multiqueue_put_event(q, event_create(h, __VA_ARGS__)); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 8e0761f80c..4af7794317 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -3344,6 +3344,15 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags, return cmd; } +static int check_regexp_delim(int c) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (isalpha(c)) { + EMSG(_("E146: Regular expressions can't be delimited by letters")); + return FAIL; + } + return OK; +} /// Perform a substitution from line eap->line1 to line eap->line2 using the /// command pointed to by eap->arg which should be of the form: @@ -3408,16 +3417,14 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, /* new pattern and substitution */ if (eap->cmd[0] == 's' && *cmd != NUL && !ascii_iswhite(*cmd) && vim_strchr((char_u *)"0123456789cegriIp|\"", *cmd) == NULL) { - /* don't accept alphanumeric for separator */ - if (isalpha(*cmd)) { - EMSG(_("E146: Regular expressions can't be delimited by letters")); + // don't accept alphanumeric for separator + if (check_regexp_delim(*cmd) == FAIL) { return NULL; } - /* - * undocumented vi feature: - * "\/sub/" and "\?sub?" use last used search pattern (almost like - * //sub/r). "\&sub&" use last substitute pattern (like //sub/). - */ + + // undocumented vi feature: + // "\/sub/" and "\?sub?" use last used search pattern (almost like + // //sub/r). "\&sub&" use last substitute pattern (like //sub/). if (*cmd == '\\') { ++cmd; if (vim_strchr((char_u *)"/?&", *cmd) == NULL) { @@ -4457,6 +4464,8 @@ void ex_global(exarg_T *eap) } else if (*cmd == NUL) { EMSG(_("E148: Regular expression missing from global")); return; + } else if (check_regexp_delim(*cmd) == FAIL) { + return; } else { delim = *cmd; /* get the delimiter */ if (delim) @@ -4775,8 +4784,9 @@ void ex_help(exarg_T *eap) * window. */ if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) { buf = buflist_findnr(empty_fnum); - if (buf != NULL && buf->b_nwindows == 0) - wipe_buffer(buf, TRUE); + if (buf != NULL && buf->b_nwindows == 0) { + wipe_buffer(buf, true); + } } /* keep the previous alternate file */ diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index d99383303b..7b971f464f 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -2568,6 +2568,12 @@ module.cmds = { func='ex_spellrepall', }, { + command='spellrare', + flags=bit.bor(BANG, RANGE, NEEDARG, EXTRA, TRLBAR), + addr_type='ADDR_OTHER', + func='ex_spell', + }, + { command='spellundo', flags=bit.bor(BANG, RANGE, NEEDARG, EXTRA, TRLBAR), addr_type='ADDR_OTHER', diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 56a14887df..9d500a8ddb 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -27,6 +27,7 @@ #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -53,6 +54,7 @@ #include "nvim/os/fs_defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/private/defs.h" +#include "nvim/lua/executor.h" /// Growarray to store info about already sourced scripts. @@ -2421,6 +2423,7 @@ void ex_compiler(exarg_T *eap) if (*eap->arg == NUL) { // List all compiler scripts. do_cmdline_cmd("echo globpath(&rtp, 'compiler/*.vim')"); // NOLINT + do_cmdline_cmd("echo globpath(&rtp, 'compiler/*.lua')"); // NOLINT } else { size_t bufsize = STRLEN(eap->arg) + 14; buf = xmalloc(bufsize); @@ -2445,7 +2448,11 @@ void ex_compiler(exarg_T *eap) snprintf((char *)buf, bufsize, "compiler/%s.vim", eap->arg); if (source_in_path(p_rtp, buf, DIP_ALL) == FAIL) { - EMSG2(_("E666: compiler not supported: %s"), eap->arg); + // Try lua compiler + snprintf((char *)buf, bufsize, "compiler/%s.lua", eap->arg); + if (source_in_path(p_rtp, buf, DIP_ALL) == FAIL) { + EMSG2(_("E666: compiler not supported: %s"), eap->arg); + } } xfree(buf); @@ -2656,8 +2663,14 @@ static void cmd_source_buffer(const exarg_T *eap) .curr_lnum = eap->line1, .final_lnum = eap->line2, }; - source_using_linegetter((void *)&cookie, get_buffer_line, - ":source (no file)"); + if (curbuf != NULL && curbuf->b_fname + && path_with_extension((const char *)curbuf->b_fname, "lua")) { + nlua_source_using_linegetter(get_buffer_line, (void *)&cookie, + ":source (no file)"); + } else { + source_using_linegetter((void *)&cookie, get_buffer_line, + ":source (no file)"); + } } /// ":source" and associated commands. @@ -2769,7 +2782,8 @@ int do_source_str(const char *cmd, const char *traceback_name) return source_using_linegetter((void *)&cookie, get_str_line, traceback_name); } -/// Reads the file `fname` and executes its lines as Ex commands. +/// When fname is a 'lua' file nlua_exec_file() is invoked to source it. +/// Otherwise reads the file `fname` and executes its lines as Ex commands. /// /// This function may be called recursively! /// @@ -2988,9 +3002,23 @@ int do_source(char_u *fname, int check_other, int is_vimrc) firstline = p; } - // Call do_cmdline, which will call getsourceline() to get the lines. - do_cmdline(firstline, getsourceline, (void *)&cookie, - DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT); + if (path_with_extension((const char *)fname, "lua")) { + // TODO(shadmansaleh): Properly handle :verbose for lua + // For now change currennt_sctx before sourcing lua files + // So verbose doesn't say everything was done in line 1 since we don't know + const sctx_T current_sctx_backup = current_sctx; + const linenr_T sourcing_lnum_backup = sourcing_lnum; + current_sctx.sc_lnum = 0; + sourcing_lnum = 0; + // Source the file as lua + nlua_exec_file((const char *)fname); + current_sctx = current_sctx_backup; + sourcing_lnum = sourcing_lnum_backup; + } else { + // Call do_cmdline, which will call getsourceline() to get the lines. + do_cmdline(firstline, getsourceline, (void *)&cookie, + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT); + } retval = OK; if (l_do_profiling == PROF_YES) { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 29347def4c..57d1339bb0 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4184,10 +4184,6 @@ static char_u *invalid_range(exarg_T *eap) } break; case ADDR_UNSIGNED: - if (eap->line2 < 0) { - return (char_u *)_(e_invrange); - } - break; case ADDR_NONE: // Will give an error elsewhere. break; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 75ed5dc0e5..f63987136f 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -5115,11 +5115,12 @@ ExpandFromContext ( } if (xp->xp_context == EXPAND_COLORS) { char *directories[] = { "colors", NULL }; - return ExpandRTDir(pat, DIP_START + DIP_OPT, num_file, file, directories); + return ExpandRTDir(pat, DIP_START + DIP_OPT + DIP_LUA, num_file, file, + directories); } if (xp->xp_context == EXPAND_COMPILER) { char *directories[] = { "compiler", NULL }; - return ExpandRTDir(pat, 0, num_file, file, directories); + return ExpandRTDir(pat, DIP_LUA, num_file, file, directories); } if (xp->xp_context == EXPAND_OWNSYNTAX) { char *directories[] = { "syntax", NULL }; @@ -5127,7 +5128,7 @@ ExpandFromContext ( } if (xp->xp_context == EXPAND_FILETYPE) { char *directories[] = { "syntax", "indent", "ftplugin", NULL }; - return ExpandRTDir(pat, 0, num_file, file, directories); + return ExpandRTDir(pat, DIP_LUA, num_file, file, directories); } if (xp->xp_context == EXPAND_CHECKHEALTH) { char *directories[] = { "autoload/health", NULL }; @@ -5567,6 +5568,7 @@ static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file) /// 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim /// When "flags" has DIP_OPT: search also from 'opt' of 'packpath': /// 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim +/// When "flags" has DIP_LUA: search also performed for .lua files /// "dirnames" is an array with one or more directory names. static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, char *dirnames[]) @@ -5584,6 +5586,10 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, char_u *s = xmalloc(size); snprintf((char *)s, size, "%s/%s*.vim", dirnames[i], pat); globpath(p_rtp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "%s/%s*.lua", dirnames[i], pat); + globpath(p_rtp, s, &ga, 0); + } xfree(s); } @@ -5593,6 +5599,10 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, char_u *s = xmalloc(size); snprintf((char *)s, size, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT globpath(p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "pack/*/start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT + globpath(p_pp, s, &ga, 0); + } xfree(s); } @@ -5601,6 +5611,10 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, char_u *s = xmalloc(size); snprintf((char *)s, size, "start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT globpath(p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT + globpath(p_pp, s, &ga, 0); + } xfree(s); } } @@ -5611,6 +5625,10 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, char_u *s = xmalloc(size); snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT globpath(p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT + globpath(p_pp, s, &ga, 0); + } xfree(s); } @@ -5619,6 +5637,10 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, char_u *s = xmalloc(size); snprintf((char *)s, size, "opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT globpath(p_pp, s, &ga, 0); + if (flags & DIP_LUA) { + snprintf((char *)s, size, "opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT + globpath(p_pp, s, &ga, 0); + } xfree(s); } } @@ -5627,7 +5649,9 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, char_u *match = ((char_u **)ga.ga_data)[i]; char_u *s = match; char_u *e = s + STRLEN(s); - if (e - s > 4 && STRNICMP(e - 4, ".vim", 4) == 0) { + if (e - s > 4 && (STRNICMP(e - 4, ".vim", 4) == 0 + || ((flags & DIP_LUA) + && STRNICMP(e - 4, ".lua", 4) == 0))) { e -= 4; for (s = e; s > match; MB_PTR_BACK(match, s)) { if (vim_ispathsep(*s)) { diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 5032646d7e..ad8418034a 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -2227,8 +2227,9 @@ static linenr_T foldUpdateIEMSRecurse( if (getlevel == foldlevelMarker && flp->start <= flp->lvl - level && flp->lvl > 0) { (void)foldFind(gap, startlnum - 1, &fp); - if (fp >= ((fold_T *)gap->ga_data) + gap->ga_len - || fp->fd_top >= startlnum) { + if (fp != NULL + && (fp >= ((fold_T *)gap->ga_data) + gap->ga_len + || fp->fd_top >= startlnum)) { fp = NULL; } } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 0ce2b586e3..7c7ce5e65f 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -878,6 +878,7 @@ EXTERN char_u e_invexpr2[] INIT(= N_("E15: Invalid expression: %s")); EXTERN char_u e_invrange[] INIT(= N_("E16: Invalid range")); EXTERN char_u e_invcmd[] INIT(= N_("E476: Invalid command")); EXTERN char_u e_isadir2[] INIT(= N_("E17: \"%s\" is a directory")); +EXTERN char_u e_no_spell[] INIT(= N_("E756: Spell checking is not possible")); EXTERN char_u e_invchan[] INIT(= N_("E900: Invalid channel id")); EXTERN char_u e_invchanjob[] INIT(= N_("E900: Invalid channel id: not a job")); EXTERN char_u e_jobtblfull[] INIT(= N_("E901: Job table is full")); diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 0a52cc16cb..4d4286354b 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1161,6 +1161,34 @@ static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, } } +int nlua_source_using_linegetter(LineGetter fgetline, + void *cookie, char *name) +{ + const linenr_T save_sourcing_lnum = sourcing_lnum; + const sctx_T save_current_sctx = current_sctx; + current_sctx.sc_sid = SID_STR; + current_sctx.sc_seq = 0; + current_sctx.sc_lnum = 0; + sourcing_lnum = 0; + + garray_T ga; + char_u *line = NULL; + + ga_init(&ga, (int)sizeof(char_u *), 10); + while ((line = fgetline(0, cookie, 0, false)) != NULL) { + GA_APPEND(char_u *, &ga, line); + } + char *code = (char *)ga_concat_strings_sep(&ga, "\n"); + size_t len = strlen(code); + nlua_typval_exec(code, len, name, NULL, 0, false, NULL); + + sourcing_lnum = save_sourcing_lnum; + current_sctx = save_current_sctx; + ga_clear_strings(&ga); + xfree(code); + return OK; +} + /// Call a LuaCallable given some typvals /// /// Used to call any lua callable passed from Lua into VimL diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index c186928ae2..11d30aae09 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -1073,6 +1073,7 @@ static int node_rawquery(lua_State *L) // TODO(bfredl): these are expensive allegedly, // use a reuse list later on? TSQueryCursor *cursor = ts_query_cursor_new(); + ts_query_cursor_set_match_limit(cursor, 32); ts_query_cursor_exec(cursor, query, node); bool captures = lua_toboolean(L, 3); diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 5c9c5103a7..8cecaa51dd 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -349,6 +349,11 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive) vim.fn.bufload(bufnr) end + -- check that region falls within current buffer + local buf_line_count = vim.api.nvim_buf_line_count(bufnr) + pos1[1] = math.min(pos1[1], buf_line_count - 1) + pos2[1] = math.min(pos2[1], buf_line_count - 1) + -- in case of block selection, columns need to be adjusted for non-ASCII characters -- TODO: handle double-width characters local bufline diff --git a/src/nvim/macros.h b/src/nvim/macros.h index eb9357d027..e718254fb9 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -169,7 +169,7 @@ #if NVIM_HAS_ATTRIBUTE(fallthrough) \ && (!defined(__apple_build_version__) || __apple_build_version__ >= 7000000) -# define FALLTHROUGH __attribute__((fallthrough)) +# define FALLTHROUGH {} __attribute__((fallthrough)) #else # define FALLTHROUGH #endif diff --git a/src/nvim/main.c b/src/nvim/main.c index 56cd97f133..7d7eba2105 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -1101,11 +1101,7 @@ static void command_line_scan(mparm_T *parmp) size_t s_size = STRLEN(a) + 9; char *s = xmalloc(s_size); - if (path_with_extension(a, "lua")) { - snprintf(s, s_size, "luafile %s", a); - } else { - snprintf(s, s_size, "so %s", a); - } + snprintf(s, s_size, "so %s", a); parmp->cmds_tofree[parmp->n_commands] = true; parmp->commands[parmp->n_commands++] = s; } else { @@ -1367,7 +1363,8 @@ static void load_plugins(void) { if (p_lpl) { char_u *rtp_copy = NULL; - char_u *const plugin_pattern = (char_u *)"plugin/**/*.vim"; // NOLINT + char_u *const plugin_pattern_vim = (char_u *)"plugin/**/*.vim"; // NOLINT + char_u *const plugin_pattern_lua = (char_u *)"plugin/**/*.lua"; // NOLINT // First add all package directories to 'runtimepath', so that their // autoload directories can be found. Only if not done already with a @@ -1380,7 +1377,10 @@ static void load_plugins(void) } source_in_path(rtp_copy == NULL ? p_rtp : rtp_copy, - plugin_pattern, + plugin_pattern_vim, + DIP_ALL | DIP_NOAFTER); + source_in_path(rtp_copy == NULL ? p_rtp : rtp_copy, + plugin_pattern_lua, DIP_ALL | DIP_NOAFTER); TIME_MSG("loading plugins"); xfree(rtp_copy); @@ -1392,7 +1392,8 @@ static void load_plugins(void) } TIME_MSG("loading packages"); - source_runtime(plugin_pattern, DIP_ALL | DIP_AFTER); + source_runtime(plugin_pattern_vim, DIP_ALL | DIP_AFTER); + source_runtime(plugin_pattern_lua, DIP_ALL | DIP_AFTER); TIME_MSG("loading after plugins"); } } @@ -1810,7 +1811,7 @@ static bool do_user_initialization(void) char_u *init_lua_path = (char_u *)stdpaths_user_conf_subpath("init.lua"); if (os_path_exists(init_lua_path) - && nlua_exec_file((const char *)init_lua_path)) { + && do_source(init_lua_path, true, DOSO_VIMRC)) { os_setenv("MYVIMRC", (const char *)init_lua_path, 1); char_u *vimrc_path = (char_u *)stdpaths_user_conf_subpath("init.vim"); @@ -1883,12 +1884,8 @@ static void source_startup_scripts(const mparm_T *const parmp) || strequal(parmp->use_vimrc, "NORC")) { // Do nothing. } else { - if (path_with_extension(parmp->use_vimrc, "lua")) { - nlua_exec_file(parmp->use_vimrc); - } else { - if (do_source((char_u *)parmp->use_vimrc, false, DOSO_NONE) != OK) { - EMSG2(_("E282: Cannot read from \"%s\""), parmp->use_vimrc); - } + if (do_source((char_u *)parmp->use_vimrc, false, DOSO_NONE) != OK) { + EMSG2(_("E282: Cannot read from \"%s\""), parmp->use_vimrc); } } } else if (!silent_mode) { diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 34acf64d83..feb54eae4a 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -356,6 +356,7 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) y = y->level ? y->ptr[0] : NULL; } } + itr->i--; } b->n_keys--; diff --git a/src/nvim/memline.c b/src/nvim/memline.c index e42b138253..cb2437b2b3 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1207,6 +1207,7 @@ void ml_recover(bool checkext) && !(curbuf->b_ml.ml_flags & ML_EMPTY)) ml_delete(curbuf->b_ml.ml_line_count, false); curbuf->b_flags |= BF_RECOVERED; + check_cursor(); recoverymode = FALSE; if (got_int) diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 69afe1644e..44cdc09c0b 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -818,7 +818,7 @@ static bool normal_get_command_count(NormalState *s) } if (s->ca.count0 < 0) { - // got too large! + // overflow s->ca.count0 = 999999999L; } @@ -1025,10 +1025,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 *= s->ca.opcount; + s->ca.count0 = (long)((uint64_t)s->ca.count0 * (uint64_t)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, @@ -1866,6 +1870,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } } else { curwin->w_p_lbr = lbr_saved; + oap->excl_tr_ws = cap->cmdchar == 'z'; (void)op_yank(oap, !gui_yank, false); } check_cursor_col(); @@ -4389,6 +4394,9 @@ dozet: case 'p': nv_put(cap); break; + // "zy" Yank without trailing spaces + case 'y': nv_operator(cap); + break; /* "zF": create fold command */ /* "zf": create fold operator */ @@ -4596,7 +4604,9 @@ dozet: if (ptr == NULL && (len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) return; assert(len <= INT_MAX); - spell_add_word(ptr, (int)len, nchar == 'w' || nchar == 'W', + spell_add_word(ptr, (int)len, + nchar == 'w' || nchar == 'W' + ? SPELL_ADD_BAD : SPELL_ADD_GOOD, (nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1, undo); } @@ -5817,6 +5827,9 @@ static void nv_percent(cmdarg_T *cap) curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count * cap->count0 + 99L) / 100L; } + if (curwin->w_cursor.lnum < 1) { + curwin->w_cursor.lnum = 1; + } if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; } diff --git a/src/nvim/normal.h b/src/nvim/normal.h index 51170105ed..8e15e909d4 100644 --- a/src/nvim/normal.h +++ b/src/nvim/normal.h @@ -48,6 +48,8 @@ typedef struct oparg_S { colnr_T end_vcol; // end col for block mode operator long prev_opcount; // ca.opcount saved for K_EVENT long prev_count0; // ca.count0 saved for K_EVENT + bool excl_tr_ws; // exclude trailing whitespace for yank of a + // block } oparg_T; /* diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 35496d372a..04a8fc8499 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -156,6 +156,9 @@ int get_op_type(int char1, int char2) // subtract return OP_NR_SUB; } + if (char1 == 'z' && char2 == 'y') { // OP_YANK + return OP_YANK; + } for (i = 0;; i++) { if (opchars[i][0] == char1 && opchars[i][1] == char2) { break; @@ -2563,7 +2566,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) switch (reg->y_type) { case kMTBlockWise: block_prep(oap, &bd, lnum, false); - yank_copy_line(reg, &bd, y_idx); + yank_copy_line(reg, &bd, y_idx, oap->excl_tr_ws); break; case kMTLineWise: @@ -2627,7 +2630,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) bd.textlen = endcol - startcol + oap->inclusive; } bd.textstart = p + startcol; - yank_copy_line(reg, &bd, y_idx); + yank_copy_line(reg, &bd, y_idx, false); break; } // NOTREACHED @@ -2714,7 +2717,11 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) return; } -static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx) +// Copy a block range into a register. +// If "exclude_trailing_space" is set, do not copy trailing whitespaces. +static void yank_copy_line(yankreg_T *reg, const struct block_def *bd, + size_t y_idx, bool exclude_trailing_space) + FUNC_ATTR_NONNULL_ALL { int size = bd->startspaces + bd->endspaces + bd->textlen; assert(size >= 0); @@ -2726,6 +2733,14 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx) pnew += bd->textlen; memset(pnew, ' ', (size_t)bd->endspaces); pnew += bd->endspaces; + if (exclude_trailing_space) { + int s = bd->textlen + bd->endspaces; + + while (ascii_iswhite(*(bd->textstart + s - 1)) && s > 0) { + s = s - utf_head_off(bd->textstart, bd->textstart + s - 1) - 1; + pnew--; + } + } *pnew = NUL; } diff --git a/src/nvim/option.c b/src/nvim/option.c index 67fb78bcc8..f57abe89cc 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -85,6 +85,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/os/input.h" #include "nvim/os/lang.h" +#include "nvim/quickfix.h" /* * The options that are local to a window or buffer have "indir" set to one of @@ -3182,6 +3183,10 @@ ambw_end: } } } + } else if (varp == &p_qftf) { + if (!qf_process_qftf_option()) { + errmsg = e_invarg; + } } else { // Options that are a list of flags. p = NULL; @@ -5370,7 +5375,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, } p = buf; while (*p != NUL) { - // for each comma seperated option part, append value to + // for each comma separated option part, append value to // the option, :set rtp+=value if (fprintf(fd, "%s %s+=", cmd, name) < 0) { goto fail; diff --git a/src/nvim/path.c b/src/nvim/path.c index fe50be5ea1..6ac24182cc 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -180,6 +180,34 @@ const char *path_next_component(const char *fname) return fname; } +/// Returns the length of the path head on the current platform. +/// @return +/// - 3 on windows +/// - 1 otherwise +int path_head_length(void) +{ +#ifdef WIN32 + return 3; +#else + return 1; +#endif +} + +/// Returns true if path begins with characters denoting the head of a path +/// (e.g. '/' on linux and 'D:' on windows). +/// @param path The path to be checked. +/// @return +/// - True if path begins with a path head +/// - False otherwise +bool is_path_head(const char_u *path) +{ +#ifdef WIN32 + return isalpha(path[0]) && path[1] == ':'; +#else + return vim_ispathsep(*path); +#endif +} + /// Get a pointer to one character past the head of a path name. /// Unix: after "/"; Win: after "c:\" /// If there is no head, path is returned. @@ -189,7 +217,7 @@ char_u *get_past_head(const char_u *path) #ifdef WIN32 // May skip "c:" - if (isalpha(path[0]) && path[1] == ':') { + if (is_path_head(path)) { retval = path + 2; } #endif @@ -1991,10 +2019,24 @@ char_u *path_shorten_fname(char_u *full_path, char_u *dir_name) assert(dir_name != NULL); size_t len = strlen((char *)dir_name); + + // If dir_name is a path head, full_path can always be made relative. + if (len == (size_t)path_head_length() && is_path_head(dir_name)) { + return full_path + len; + } + + // If full_path and dir_name do not match, it's impossible to make one + // relative to the other. + if (fnamencmp(dir_name, full_path, len) != 0) { + return NULL; + } + char_u *p = full_path + len; - if (fnamencmp(dir_name, full_path, len) != 0 - || !vim_ispathsep(*p)) { + // If *p is not pointing to a path separator, this means that full_path's + // last directory name is longer than *dir_name's last directory, so they + // don't actually match. + if (!vim_ispathsep(*p)) { return NULL; } diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 7d452d6797..f620517aff 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -440,7 +440,7 @@ void pum_redraw(void) } if (ui_has(kUIMultigrid)) { const char *anchor = pum_above ? "SW" : "NW"; - int row_off = pum_above ? pum_height : 0; + int row_off = pum_above ? -pum_height : 0; ui_call_win_float_pos(pum_grid.handle, -1, cstr_to_string(anchor), pum_anchor_grid, pum_row-row_off, pum_col-col_off, false, pum_grid.zindex); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 1a9bbe26f0..71624baaf4 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -100,7 +100,7 @@ typedef struct qf_list_S { char_u *qf_title; ///< title derived from the command that created ///< the error list or set by setqflist typval_T *qf_ctx; ///< context set by setqflist/setloclist - char_u *qf_qftf; ///< 'quickfixtextfunc' setting for this list + Callback qftf_cb; ///< 'quickfixtextfunc' callback function struct dir_stack_T *qf_dir_stack; char_u *qf_directory; @@ -541,6 +541,9 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls +// callback function for 'quickfixtextfunc' +static Callback qftf_cb; + static void free_efm_list(efm_T **efm_first) { for (efm_T *efm_ptr = *efm_first; efm_ptr != NULL; efm_ptr = *efm_first) { @@ -1978,7 +1981,7 @@ static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl) } /// Copy the specified location list 'from_qfl' to 'to_qfl'. -static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl) +static int copy_loclist(qf_list_T *from_qfl, qf_list_T *to_qfl) FUNC_ATTR_NONNULL_ALL { // Some of the fields are populated by qf_add_entry() @@ -2000,11 +2003,7 @@ static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl) } else { to_qfl->qf_ctx = NULL; } - if (from_qfl->qf_qftf != NULL) { - to_qfl->qf_qftf = vim_strsave(from_qfl->qf_qftf); - } else { - to_qfl->qf_qftf = NULL; - } + callback_copy(&to_qfl->qftf_cb, &from_qfl->qftf_cb); if (from_qfl->qf_count) { if (copy_loclist_entries(from_qfl, to_qfl) == FAIL) { @@ -3385,7 +3384,7 @@ static void qf_free(qf_list_T *qfl) XFREE_CLEAR(qfl->qf_title); tv_free(qfl->qf_ctx); qfl->qf_ctx = NULL; - XFREE_CLEAR(qfl->qf_qftf); + callback_free(&qfl->qftf_cb); qfl->qf_id = 0; qfl->qf_changedtick = 0L; } @@ -3860,6 +3859,41 @@ static buf_T *qf_find_buf(qf_info_T *qi) return NULL; } +// Process the 'quickfixtextfunc' option value. +bool qf_process_qftf_option(void) +{ + typval_T *tv; + Callback cb; + + if (p_qftf == NULL || *p_qftf == NUL) { + callback_free(&qftf_cb); + return true; + } + + if (*p_qftf == '{') { + // Lambda expression + tv = eval_expr(p_qftf); + if (tv == NULL) { + return false; + } + } else { + // treat everything else as a function name string + tv = xcalloc(1, sizeof(*tv)); + tv->v_type = VAR_STRING; + tv->vval.v_string = vim_strsave(p_qftf); + } + + if (!callback_from_typval(&cb, tv)) { + tv_free(tv); + return false; + } + + callback_free(&qftf_cb); + qftf_cb = cb; + tv_free(tv); + return true; +} + /// Update the w:quickfix_title variable in the quickfix/location list window in /// all the tab pages. static void qf_update_win_titlevar(qf_info_T *qi) @@ -3891,7 +3925,15 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) int qf_winid = 0; if (IS_LL_STACK(qi)) { - qf_winid = curwin->handle; + if (curwin->w_llist == qi) { + win = curwin; + } else { + win = qf_find_win_with_loclist(qi); + if (win == NULL) { + return; + } + } + qf_winid = (int)win->handle; } if (old_last == NULL) { @@ -3928,7 +3970,9 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, int len; buf_T *errbuf; - if (qftf_str != NULL) { + // If the 'quickfixtextfunc' function returned an non-empty custom string + // for this entry, then use it. + if (qftf_str != NULL && *qftf_str != NUL) { STRLCPY(IObuff, qftf_str, IOSIZE); } else { if (qfp->qf_module != NULL) { @@ -3997,22 +4041,25 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, return OK; } +// Call the 'quickfixtextfunc' function to get the list of lines to display in +// the quickfix window for the entries 'start_idx' to 'end_idx'. static list_T *call_qftf_func(qf_list_T *qfl, int qf_winid, long start_idx, long end_idx) { - char_u *qftf = p_qftf; + Callback *cb = &qftf_cb; list_T *qftf_list = NULL; // If 'quickfixtextfunc' is set, then use the user-supplied function to get // the text to display. Use the local value of 'quickfixtextfunc' if it is // set. - if (qfl->qf_qftf != NULL) { - qftf = qfl->qf_qftf; + if (qfl->qftf_cb.type != kCallbackNone) { + cb = &qfl->qftf_cb; } - if (qftf != NULL && *qftf != NUL) { + if (cb != NULL && cb->type != kCallbackNone) { typval_T args[1]; + typval_T rettv; // create the dict argument dict_T *const dict = tv_dict_alloc_lock(VAR_FIXED); @@ -4026,8 +4073,16 @@ static list_T *call_qftf_func(qf_list_T *qfl, args[0].v_type = VAR_DICT; args[0].vval.v_dict = dict; - qftf_list = call_func_retlist(qftf, 1, args); - dict->dv_refcount--; + qftf_list = NULL; + + if (callback_call(cb, 1, args, &rettv)) { + if (rettv.v_type == VAR_LIST) { + qftf_list = rettv.vval.v_list; + tv_list_ref(qftf_list); + } + tv_clear(&rettv); + } + tv_dict_unref(dict); } return qftf_list; @@ -4064,6 +4119,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, if (qfl != NULL) { char_u dirname[MAXPATHL]; int prev_bufnr = -1; + bool invalid_val = false; *dirname = NUL; @@ -4086,10 +4142,15 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, while (lnum < qfl->qf_count) { char_u *qftf_str = NULL; - if (qftf_li != NULL) { - // Use the text supplied by the user defined function - qftf_str = (char_u *)tv_get_string_chk(TV_LIST_ITEM_TV(qftf_li)); + // Use the text supplied by the user defined function (if any). + // If the returned value is not string, then ignore the rest + // of the returned values and use the default. + if (qftf_li != NULL && !invalid_val) { + qftf_str = (char_u *)tv_get_string_chk(TV_LIST_ITEM_TV(qftf_li)); + if (qftf_str == NULL) { + invalid_val = true; } + } if (qf_buf_add_line(qfl, buf, lnum, qfp, dirname, qftf_str, prev_bufnr != qfp->qf_fnum) == FAIL) { @@ -5660,7 +5721,7 @@ static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start) // work when got_int is set. enter_cleanup(&cs); - wipe_buffer(buf, FALSE); + wipe_buffer(buf, true); // Restore the error/interrupt/exception state if not discarded by a // new aborting error, interrupt, or uncaught exception. @@ -5796,7 +5857,9 @@ enum { QF_GETLIST_SIZE = 0x80, QF_GETLIST_TICK = 0x100, QF_GETLIST_FILEWINID = 0x200, - QF_GETLIST_ALL = 0x3FF, + QF_GETLIST_QFBUFNR = 0x400, + QF_GETLIST_QFTF = 0x800, + QF_GETLIST_ALL = 0xFFF, }; /// Parse text from 'di' and return the quickfix list items. @@ -5894,6 +5957,9 @@ static int qf_getprop_keys2flags(const dict_T *what, bool loclist) if (loclist && tv_dict_find(what, S_LEN("filewinid")) != NULL) { flags |= QF_GETLIST_FILEWINID; } + if (tv_dict_find(what, S_LEN("quickfixtextfunc")) != NULL) { + flags |= QF_GETLIST_QFTF; + } return flags; } @@ -5985,6 +6051,9 @@ static int qf_getprop_defaults(qf_info_T *qi, if ((status == OK) && locstack && (flags & QF_GETLIST_FILEWINID)) { status = tv_dict_add_nr(retdict, S_LEN("filewinid"), 0); } + if ((status == OK) && (flags & QF_GETLIST_QFTF)) { + status = tv_dict_add_str(retdict, S_LEN("quickfixtextfunc"), ""); + } return status; } @@ -6060,6 +6129,26 @@ static int qf_getprop_idx(qf_list_T *qfl, int eidx, dict_T *retdict) return tv_dict_add_nr(retdict, S_LEN("idx"), eidx); } +/// Return the 'quickfixtextfunc' function of a quickfix/location list +/// @return OK or FAIL +static int qf_getprop_qftf(qf_list_T *qfl, dict_T *retdict) + FUNC_ATTR_NONNULL_ALL +{ + int status; + + if (qfl->qftf_cb.type != kCallbackNone) { + typval_T tv; + + callback_put(&qfl->qftf_cb, &tv); + status = tv_dict_add_tv(retdict, S_LEN("quickfixtextfunc"), &tv); + tv_clear(&tv); + } else { + status = tv_dict_add_str(retdict, S_LEN("quickfixtextfunc"), ""); + } + + return status; +} + /// Return quickfix/location list details (title) as a dictionary. /// 'what' contains the details to return. If 'list_idx' is -1, /// then current list is used. Otherwise the specified list is used. @@ -6133,19 +6222,25 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) if ((status == OK) && (wp != NULL) && (flags & QF_GETLIST_FILEWINID)) { status = qf_getprop_filewinid(wp, qi, retdict); } + if ((status == OK) && (flags & QF_GETLIST_QFTF)) { + status = qf_getprop_qftf(qfl, retdict); + } return status; } /// Set the current index in the specified quickfix list -static int qf_setprop_qftf(qf_info_T *qi, qf_list_T *qfl, - dictitem_T *di) +/// @return OK +static int qf_setprop_qftf(qf_list_T *qfl, dictitem_T *di) + FUNC_ATTR_NONNULL_ALL { - XFREE_CLEAR(qfl->qf_qftf); - if (di->di_tv.v_type == VAR_STRING && di->di_tv.vval.v_string != NULL) { - qfl->qf_qftf = vim_strsave(di->di_tv.vval.v_string); - } - return OK; + Callback cb; + + callback_free(&qfl->qftf_cb); + if (callback_from_typval(&cb, &di->di_tv)) { + qfl->qftf_cb = cb; + } + return OK; } /// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the @@ -6514,7 +6609,7 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, retval = qf_setprop_curidx(qi, qfl, di); } if ((di = tv_dict_find(what, S_LEN("quickfixtextfunc"))) != NULL) { - retval = qf_setprop_qftf(qi, qfl, di); + retval = qf_setprop_qftf(qfl, di); } if (newlist || retval == OK) { diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 1fb7e3b434..c3cd210538 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -245,7 +245,8 @@ int source_in_path(char_u *path, char_u *name, int flags) return do_in_path_and_pp(path, name, flags, source_callback, NULL); } -// Expand wildcards in "pat" and invoke do_source() for each match. +// Expand wildcards in "pat" and invoke do_source()/nlua_exec_file() +// for each match. static void source_all_matches(char_u *pat) { int num_files; @@ -405,17 +406,15 @@ theend: /// Load scripts in "plugin" and "ftdetect" directories of the package. static int load_pack_plugin(char_u *fname) { - static const char *plugpat = "%s/plugin/**/*.vim"; // NOLINT static const char *ftpat = "%s/ftdetect/*.vim"; // NOLINT - int retval = FAIL; char *const ffname = fix_fname((char *)fname); size_t len = strlen(ffname) + STRLEN(ftpat); - char_u *pat = try_malloc(len + 1); - if (pat == NULL) { - goto theend; - } - vim_snprintf((char *)pat, len, plugpat, ffname); + char_u *pat = xmallocz(len); + + vim_snprintf((char *)pat, len, "%s/plugin/**/*.vim", ffname); // NOLINT + source_all_matches(pat); + vim_snprintf((char *)pat, len, "%s/plugin/**/*.lua", ffname); // NOLINT source_all_matches(pat); char_u *cmd = vim_strsave((char_u *)"g:did_load_filetypes"); @@ -426,16 +425,15 @@ static int load_pack_plugin(char_u *fname) do_cmdline_cmd("augroup filetypedetect"); vim_snprintf((char *)pat, len, ftpat, ffname); source_all_matches(pat); + vim_snprintf((char *)pat, len, "%s/ftdetect/*.lua", ffname); // NOLINT + source_all_matches(pat); do_cmdline_cmd("augroup END"); } xfree(cmd); xfree(pat); - retval = OK; - -theend: xfree(ffname); - return retval; + return OK; } // used for "cookie" of add_pack_plugin() diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 844104e7d0..3446a944cd 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -1709,7 +1709,7 @@ static void win_update(win_T *wp, Providers *providers) && (wp->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW)) { // A win_line() call applied a fix to screen cursor column to - // accomodate concealment of cursor line, but in this call to + // accommodate concealment of cursor line, but in this call to // update_topline() the cursor's row or column got invalidated. // If they are left invalid, setcursor() will recompute them // but there won't be any further win_line() call to re-fix the @@ -2757,8 +2757,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } if (wp->w_p_rl) { // reverse line numbers // like rl_mirror(), but keep the space at the end - char_u *p2 = skiptowhite(extra) - 1; - for (char_u *p1 = extra; p1 < p2; p1++, p2--) { + char_u *p2 = skipwhite(extra); + p2 = skiptowhite(p2) - 1; + for (char_u *p1 = skipwhite(extra); p1 < p2; p1++, p2--) { const int t = *p1; *p1 = *p2; *p2 = t; @@ -7210,7 +7211,24 @@ void ui_ext_tabline_update(void) ADD(tabs, DICTIONARY_OBJ(tab_info)); } - ui_call_tabline_update(curtab->handle, tabs); + + Array buffers = ARRAY_DICT_INIT; + FOR_ALL_BUFFERS(buf) { + // Do not include unlisted buffers + if (!buf->b_p_bl) { + continue; + } + + Dictionary buffer_info = ARRAY_DICT_INIT; + PUT(buffer_info, "buffer", BUFFER_OBJ(buf->handle)); + + get_trans_bufname(buf); + PUT(buffer_info, "name", STRING_OBJ(cstr_to_string((char *)NameBuff))); + + ADD(buffers, DICTIONARY_OBJ(buffer_info)); + } + + ui_call_tabline_update(curtab->handle, tabs, curbuf->handle, buffers); } /* diff --git a/src/nvim/spell.c b/src/nvim/spell.c index d1428b0117..771c2106db 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -441,7 +441,8 @@ size_t spell_check( MB_PTR_ADV(mi.mi_fend); } - (void)spell_casefold(ptr, (int)(mi.mi_fend - ptr), mi.mi_fword, MAXWLEN + 1); + (void)spell_casefold(wp, ptr, (int)(mi.mi_fend - ptr), mi.mi_fword, + MAXWLEN + 1); mi.mi_fwordlen = (int)STRLEN(mi.mi_fword); if (camel_case) { @@ -869,10 +870,11 @@ static void find_word(matchinf_T *mip, int mode) if (slang->sl_compsylmax < MAXWLEN) { // "fword" is only needed for checking syllables. - if (ptr == mip->mi_word) - (void)spell_casefold(ptr, wlen, fword, MAXWLEN); - else + if (ptr == mip->mi_word) { + (void)spell_casefold(mip->mi_win, ptr, wlen, fword, MAXWLEN); + } else { STRLCPY(fword, ptr, endlen[endidxcnt] + 1); + } } if (!can_compound(slang, fword, mip->mi_compflags)) continue; @@ -1315,9 +1317,9 @@ static int fold_more(matchinf_T *mip) MB_PTR_ADV(mip->mi_fend); } - (void)spell_casefold(p, (int)(mip->mi_fend - p), - mip->mi_fword + mip->mi_fwordlen, - MAXWLEN - mip->mi_fwordlen); + (void)spell_casefold(mip->mi_win, p, (int)(mip->mi_fend - p), + mip->mi_fword + mip->mi_fwordlen, + MAXWLEN - mip->mi_fwordlen); flen = (int)STRLEN(mip->mi_fword + mip->mi_fwordlen); mip->mi_fwordlen += flen; return flen; @@ -1341,7 +1343,7 @@ static bool no_spell_checking(win_T *wp) { if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL || GA_EMPTY(&wp->w_s->b_langp)) { - EMSG(_("E756: Spell checking is not enabled")); + EMSG(_(e_no_spell)); return true; } return false; @@ -2655,7 +2657,9 @@ static bool spell_iswordp_w(const int *p, const win_T *wp) // Uses the character definitions from the .spl file. // When using a multi-byte 'encoding' the length may change! // Returns FAIL when something wrong. -int spell_casefold(char_u *str, int len, char_u *buf, int buflen) +int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf, + int buflen) + FUNC_ATTR_NONNULL_ALL { if (len >= buflen) { buf[0] = NUL; @@ -2670,8 +2674,22 @@ int spell_casefold(char_u *str, int len, char_u *buf, int buflen) buf[outi] = NUL; return FAIL; } - const int c = mb_cptr2char_adv((const char_u **)&p); - outi += utf_char2bytes(SPELL_TOFOLD(c), buf + outi); + int c = mb_cptr2char_adv((const char_u **)&p); + + // Exception: greek capital sigma 0x03A3 folds to 0x03C3, except + // when it is the last character in a word, then it folds to + // 0x03C2. + if (c == 0x03a3 || c == 0x03c2) { + if (p == str + len || !spell_iswordp(p, wp)) { + c = 0x03c2; + } else { + c = 0x03c3; + } + } else { + c = SPELL_TOFOLD(c); + } + + outi += utf_char2bytes(c, buf + outi); } buf[outi] = NUL; @@ -2753,9 +2771,17 @@ void spell_suggest(int count) int selected = count; int badlen = 0; int msg_scroll_save = msg_scroll; + const int wo_spell_save = curwin->w_p_spell; + + if (!curwin->w_p_spell) { + did_set_spelllang(curwin); + curwin->w_p_spell = true; + } - if (no_spell_checking(curwin)) + if (*curwin->w_s->b_p_spl == NUL) { + EMSG(_(e_no_spell)); return; + } if (VIsual_active) { // Use the Visually selected text as the bad word. But reject @@ -2948,6 +2974,7 @@ void spell_suggest(int count) spell_find_cleanup(&sug); xfree(line); + curwin->w_p_spell = wo_spell_save; } // Check if the word at line "lnum" column "col" is required to start with a @@ -3155,7 +3182,8 @@ spell_find_suggest ( if (su->su_badlen >= MAXWLEN) su->su_badlen = MAXWLEN - 1; // just in case STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); - (void)spell_casefold(su->su_badptr, su->su_badlen, su->su_fbadword, MAXWLEN); + (void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword, + MAXWLEN); // TODO(vim): make this work if the case-folded text is longer than the // original text. Currently an illegal byte causes wrong pointer @@ -3535,7 +3563,7 @@ static void suggest_try_change(suginfo_T *su) STRCPY(fword, su->su_fbadword); n = (int)STRLEN(fword); p = su->su_badptr + su->su_badlen; - (void)spell_casefold(p, (int)STRLEN(p), fword + n, MAXWLEN - n); + (void)spell_casefold(curwin, p, (int)STRLEN(p), fword + n, MAXWLEN - n); for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); @@ -5087,7 +5115,7 @@ stp_sal_score ( pbad = badsound; else { // soundfold the bad word with more characters following - (void)spell_casefold(su->su_badptr, stp->st_orglen, fword, MAXWLEN); + (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, fword, MAXWLEN); // When joining two words the sound often changes a lot. E.g., "t he" // sounds like "t h" while "the" sounds like "@". Avoid that by @@ -5742,7 +5770,9 @@ cleanup_suggestions ( xfree(stp[i].st_word); } gap->ga_len = keep; - return stp[keep - 1].st_score; + if (keep >= 1) { + return stp[keep - 1].st_score; + } } } return maxscore; @@ -5800,10 +5830,10 @@ void spell_soundfold(slang_T *slang, char_u *inword, bool folded, char_u *res) spell_soundfold_sofo(slang, inword, res); else { // SAL items used. Requires the word to be case-folded. - if (folded) + if (folded) { word = inword; - else { - (void)spell_casefold(inword, (int)STRLEN(inword), fword, MAXWLEN); + } else { + (void)spell_casefold(curwin, inword, (int)STRLEN(inword), fword, MAXWLEN); word = fword; } diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index e2c9ab7ae8..f07f5673f9 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -284,4 +284,11 @@ extern int did_set_spelltab; extern char *e_format; +// Values for "what" argument of spell_add_word() +typedef enum { + SPELL_ADD_GOOD = 0, + SPELL_ADD_BAD = 1, + SPELL_ADD_RARE = 2, +} SpellAddType; + #endif // NVIM_SPELL_DEFS_H diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 3c125959a9..0597f392e7 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -2942,9 +2942,9 @@ static void add_fromto(spellinfo_T *spin, garray_T *gap, char_u *from, char_u *t char_u word[MAXWLEN]; fromto_T *ftp = GA_APPEND_VIA_PTR(fromto_T, gap); - (void)spell_casefold(from, (int)STRLEN(from), word, MAXWLEN); + (void)spell_casefold(curwin, from, (int)STRLEN(from), word, MAXWLEN); ftp->ft_from = getroom_save(spin, word); - (void)spell_casefold(to, (int)STRLEN(to), word, MAXWLEN); + (void)spell_casefold(curwin, to, (int)STRLEN(to), word, MAXWLEN); ftp->ft_to = getroom_save(spin, word); } @@ -3764,7 +3764,7 @@ store_word ( char_u *word, int flags, // extra flags, WF_BANNED int region, // supported region(s) - char_u *pfxlist, // list of prefix IDs or NULL + const char_u *pfxlist, // list of prefix IDs or NULL bool need_affix // only store word with affix ID ) { @@ -3772,25 +3772,28 @@ store_word ( int ct = captype(word, word + len); char_u foldword[MAXWLEN]; int res = OK; - char_u *p; - (void)spell_casefold(word, len, foldword, MAXWLEN); - for (p = pfxlist; res == OK; ++p) { - if (!need_affix || (p != NULL && *p != NUL)) + (void)spell_casefold(curwin, word, len, foldword, MAXWLEN); + for (const char_u *p = pfxlist; res == OK; p++) { + if (!need_affix || (p != NULL && *p != NUL)) { res = tree_add_word(spin, foldword, spin->si_foldroot, ct | flags, - region, p == NULL ? 0 : *p); - if (p == NULL || *p == NUL) + region, p == NULL ? 0 : *p); + } + if (p == NULL || *p == NUL) { break; + } } ++spin->si_foldwcount; if (res == OK && (ct == WF_KEEPCAP || (flags & WF_KEEPCAP))) { - for (p = pfxlist; res == OK; ++p) { - if (!need_affix || (p != NULL && *p != NUL)) + for (const char_u *p = pfxlist; res == OK; p++) { + if (!need_affix || (p != NULL && *p != NUL)) { res = tree_add_word(spin, word, spin->si_keeproot, flags, - region, p == NULL ? 0 : *p); - if (p == NULL || *p == NUL) + region, p == NULL ? 0 : *p); + } + if (p == NULL || *p == NUL) { break; + } } ++spin->si_keepwcount; } @@ -5287,13 +5290,16 @@ static void spell_message(const spellinfo_T *spin, char_u *str) } // ":[count]spellgood {word}" -// ":[count]spellwrong {word}" +// ":[count]spellwrong {word}" // ":[count]spellundo {word}" +// ":[count]spellrare {word}" void ex_spell(exarg_T *eap) { - spell_add_word(eap->arg, (int)STRLEN(eap->arg), eap->cmdidx == CMD_spellwrong, - eap->forceit ? 0 : (int)eap->line2, - eap->cmdidx == CMD_spellundo); + spell_add_word(eap->arg, (int)STRLEN(eap->arg), + eap->cmdidx == CMD_spellwrong ? SPELL_ADD_BAD : + eap->cmdidx == CMD_spellrare ? SPELL_ADD_RARE : SPELL_ADD_GOOD, + eap->forceit ? 0 : (int)eap->line2, + eap->cmdidx == CMD_spellundo); } // Add "word[len]" to 'spellfile' as a good or bad word. @@ -5301,10 +5307,10 @@ void spell_add_word ( char_u *word, int len, - int bad, - int idx, // "zG" and "zW": zero, otherwise index in - // 'spellfile' - bool undo // true for "zug", "zuG", "zuw" and "zuW" + SpellAddType what, // SPELL_ADD_ values + int idx, // "zG" and "zW": zero, otherwise index in + // 'spellfile' + bool undo // true for "zug", "zuG", "zuw" and "zuW" ) { FILE *fd = NULL; @@ -5361,7 +5367,7 @@ spell_add_word ( fname = fnamebuf; } - if (bad || undo) { + if (what == SPELL_ADD_BAD || undo) { // When the word appears as good word we need to remove that one, // since its flags sort before the one with WF_BANNED. fd = os_fopen((char *)fname, "r"); @@ -5419,13 +5425,16 @@ spell_add_word ( } } - if (fd == NULL) + if (fd == NULL) { EMSG2(_(e_notopen), fname); - else { - if (bad) + } else { + if (what == SPELL_ADD_BAD) { fprintf(fd, "%.*s/!\n", len, word); - else + } else if (what == SPELL_ADD_RARE) { + fprintf(fd, "%.*s/?\n", len, word); + } else { fprintf(fd, "%.*s\n", len, word); + } fclose(fd); home_replace(NULL, fname, NameBuff, MAXPATHL, TRUE); diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index ed886ab7f9..ce81f26d38 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6438,6 +6438,10 @@ int load_colors(char_u *name) apply_autocmds(EVENT_COLORSCHEMEPRE, name, curbuf->b_fname, false, curbuf); snprintf((char *)buf, buflen, "colors/%s.vim", name); retval = source_runtime(buf, DIP_START + DIP_OPT); + if (retval == FAIL) { + snprintf((char *)buf, buflen, "colors/%s.lua", name); + retval = source_runtime(buf, DIP_START + DIP_OPT); + } xfree(buf); apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, FALSE, curbuf); diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index bb84fa498e..ad28118f16 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -108,19 +108,19 @@ func Test_bufunload() autocmd BufWipeout * call add(s:li, "bufwipeout") augroup END - let s:li=[] + let s:li = [] new setlocal bufhidden= bunload call assert_equal(["bufunload", "bufdelete"], s:li) - let s:li=[] + let s:li = [] new setlocal bufhidden=delete bunload call assert_equal(["bufunload", "bufdelete"], s:li) - let s:li=[] + let s:li = [] new setlocal bufhidden=unload bwipeout @@ -196,6 +196,29 @@ func Test_autocmd_bufunload_avoiding_SEGV_02() bwipe! a.txt endfunc +func Test_autocmd_dummy_wipeout() + " prepare files + call writefile([''], 'Xdummywipetest1.txt') + call writefile([''], 'Xdummywipetest2.txt') + augroup test_bufunload_group + autocmd! + autocmd BufUnload * call add(s:li, "bufunload") + autocmd BufDelete * call add(s:li, "bufdelete") + autocmd BufWipeout * call add(s:li, "bufwipeout") + augroup END + + let s:li = [] + split Xdummywipetest1.txt + silent! vimgrep /notmatched/ Xdummywipetest* + call assert_equal(["bufunload", "bufwipeout"], s:li) + + bwipeout + call delete('Xdummywipetest1.txt') + call delete('Xdummywipetest2.txt') + au! test_bufunload_group + augroup! test_bufunload_group +endfunc + func Test_win_tab_autocmd() let g:record = [] @@ -428,7 +451,7 @@ func Test_autocmd_bufwipe_in_SessLoadPost() let content =<< trim [CODE] set nocp noswapfile - let v:swapchoice="e" + let v:swapchoice = "e" augroup test_autocmd_sessionload autocmd! autocmd SessionLoadPost * exe bufnr("Xsomething") . "bw!" @@ -537,92 +560,92 @@ func Test_OptionSet() au OptionSet * :call s:AutoCommandOptionSet(expand("<amatch>")) " 1: Setting number option" - let g:options=[['number', 0, 1, 'global']] + let g:options = [['number', 0, 1, 'global']] set nu call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 2: Setting local number option" - let g:options=[['number', 1, 0, 'local']] + let g:options = [['number', 1, 0, 'local']] setlocal nonu call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 3: Setting global number option" - let g:options=[['number', 1, 0, 'global']] + let g:options = [['number', 1, 0, 'global']] setglobal nonu call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 4: Setting local autoindent option" - let g:options=[['autoindent', 0, 1, 'local']] + let g:options = [['autoindent', 0, 1, 'local']] setlocal ai call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 5: Setting global autoindent option" - let g:options=[['autoindent', 0, 1, 'global']] + let g:options = [['autoindent', 0, 1, 'global']] setglobal ai call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 6: Setting global autoindent option" - let g:options=[['autoindent', 1, 0, 'global']] + let g:options = [['autoindent', 1, 0, 'global']] set ai! call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " Should not print anything, use :noa " 7: don't trigger OptionSet" - let g:options=[['invalid', 1, 1, 'invalid']] + let g:options = [['invalid', 1, 1, 'invalid']] noa set nonu call assert_equal([['invalid', 1, 1, 'invalid']], g:options) call assert_equal(g:opt[0], g:opt[1]) " 8: Setting several global list and number option" - let g:options=[['list', 0, 1, 'global'], ['number', 0, 1, 'global']] + let g:options = [['list', 0, 1, 'global'], ['number', 0, 1, 'global']] set list nu call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 9: don't trigger OptionSet" - let g:options=[['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']] + let g:options = [['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']] noa set nolist nonu call assert_equal([['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']], g:options) call assert_equal(g:opt[0], g:opt[1]) " 10: Setting global acd" - let g:options=[['autochdir', 0, 1, 'local']] + let g:options = [['autochdir', 0, 1, 'local']] setlocal acd call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 11: Setting global autoread (also sets local value)" - let g:options=[['autoread', 0, 1, 'global']] + let g:options = [['autoread', 0, 1, 'global']] set ar call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 12: Setting local autoread" - let g:options=[['autoread', 1, 1, 'local']] + let g:options = [['autoread', 1, 1, 'local']] setlocal ar call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 13: Setting global autoread" - let g:options=[['autoread', 1, 0, 'global']] + let g:options = [['autoread', 1, 0, 'global']] setglobal invar call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 14: Setting option backspace through :let" - let g:options=[['backspace', '', 'eol,indent,start', 'global']] - let &bs="eol,indent,start" + let g:options = [['backspace', '', 'eol,indent,start', 'global']] + let &bs = "eol,indent,start" call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 15: Setting option backspace through setbufvar()" - let g:options=[['backup', 0, 1, 'local']] + let g:options = [['backup', 0, 1, 'local']] " try twice, first time, shouldn't trigger because option name is invalid, " second time, it should trigger call assert_fails("call setbufvar(1, '&l:bk', 1)", "E355") @@ -632,13 +655,13 @@ func Test_OptionSet() call assert_equal(g:opt[0], g:opt[1]) " 16: Setting number option using setwinvar" - let g:options=[['number', 0, 1, 'local']] + let g:options = [['number', 0, 1, 'local']] call setwinvar(0, '&number', 1) call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 17: Setting key option, shouldn't trigger" - let g:options=[['key', 'invalid', 'invalid1', 'invalid']] + let g:options = [['key', 'invalid', 'invalid1', 'invalid']] setlocal key=blah setlocal key= call assert_equal([['key', 'invalid', 'invalid1', 'invalid']], g:options) @@ -646,13 +669,13 @@ func Test_OptionSet() " 18: Setting string option" let oldval = &tags - let g:options=[['tags', oldval, 'tagpath', 'global']] + let g:options = [['tags', oldval, 'tagpath', 'global']] set tags=tagpath call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 1l: Resetting string option" - let g:options=[['tags', 'tagpath', oldval, 'global']] + let g:options = [['tags', 'tagpath', oldval, 'global']] set tags& call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) @@ -672,7 +695,7 @@ func Test_OptionSet_diffmode() call test_override('starting', 1) " 18: Changing an option when entering diff mode new - au OptionSet diff :let &l:cul=v:option_new + au OptionSet diff :let &l:cul = v:option_new call setline(1, ['buffer 1', 'line2', 'line3', 'line4']) call assert_equal(0, &l:cul) @@ -1754,7 +1777,7 @@ func Test_autocmd_CmdWinEnter() autocmd CmdWinEnter * quit let winnr = winnr('$') END - let filename='XCmdWinEnter' + let filename = 'XCmdWinEnter' call writefile(lines, filename) let buf = RunVimInTerminal('-S '.filename, #{rows: 6}) diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index f70cb261e0..1c645ad0f8 100644 --- a/src/nvim/testdir/test_ex_mode.vim +++ b/src/nvim/testdir/test_ex_mode.vim @@ -1,5 +1,8 @@ " Test editing line in Ex mode (see :help Q and :help gQ). +source check.vim +source shared.vim + " Helper function to test editing line in Q Ex mode func Ex_Q(cmd) " Is there a simpler way to test editing Ex line? @@ -79,4 +82,20 @@ func Test_ex_mode_errors() quit endfunc +func Test_ex_mode_count_overflow() + " this used to cause a crash + let lines =<< trim END + call feedkeys("\<Esc>Q\<CR>") + v9|9silent! vi|333333233333y32333333%O + call writefile(['done'], 'Xdidexmode') + qall! + END + call writefile(lines, 'Xexmodescript') + call assert_equal(1, RunVim([], [], '-e -s -S Xexmodescript -c qa')) + call assert_equal(['done'], readfile('Xdidexmode')) + + call delete('Xdidexmode') + call delete('Xexmodescript') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 09fdbf4e20..eb6151fbe1 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -161,6 +161,8 @@ let s:filename_checks = { \ 'ecd': ['file.ecd'], \ 'edif': ['file.edf', 'file.edif', 'file.edo'], \ 'elinks': ['elinks.conf'], + \ 'elixir': ['file.ex', 'file.exs', 'mix.lock'], + \ 'eelixir': ['file.eex', 'file.leex'], \ 'elm': ['file.elm'], \ 'elmfilt': ['filter-rules'], \ 'epuppet': ['file.epp'], @@ -257,7 +259,7 @@ let s:filename_checks = { \ 'jgraph': ['file.jgr'], \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'], \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file'], - \ 'json': ['file.json', 'file.jsonp', 'file.webmanifest', 'Pipfile.lock'], + \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb'], \ 'jsp': ['file.jsp'], \ 'kconfig': ['Kconfig', 'Kconfig.debug', 'Kconfig.file'], \ 'kivy': ['file.kv'], @@ -422,7 +424,7 @@ let s:filename_checks = { \ 'sass': ['file.sass'], \ 'sather': ['file.sa'], \ 'sbt': ['file.sbt'], - \ 'scala': ['file.scala'], + \ 'scala': ['file.scala', 'file.sc'], \ 'scheme': ['file.scm', 'file.ss', 'file.rkt'], \ 'scilab': ['file.sci', 'file.sce'], \ 'screen': ['.screenrc', 'screenrc'], @@ -765,5 +767,41 @@ func Test_pp_file() filetype off endfunc +func Test_ex_file() + filetype on + + call writefile(['arbitrary content'], 'Xfile.ex') + split Xfile.ex + call assert_equal('elixir', &filetype) + bwipe! + let g:filetype_euphoria = 'euphoria4' + split Xfile.ex + call assert_equal('euphoria4', &filetype) + bwipe! + unlet g:filetype_euphoria + + call writefile(['-- filetype euphoria comment'], 'Xfile.ex') + split Xfile.ex + call assert_equal('euphoria3', &filetype) + bwipe! + + call writefile(['--filetype euphoria comment'], 'Xfile.ex') + split Xfile.ex + call assert_equal('euphoria3', &filetype) + bwipe! + + call writefile(['ifdef '], 'Xfile.ex') + split Xfile.ex + call assert_equal('euphoria3', &filetype) + bwipe! + + call writefile(['include '], 'Xfile.ex') + split Xfile.ex + call assert_equal('euphoria3', &filetype) + bwipe! + + call delete('Xfile.ex') + filetype off +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index fcdf888b96..2cc5b47cb0 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -796,6 +796,26 @@ func Test_fold_delete_first_line() set foldmethod& endfunc +func Test_undo_fold_deletion() + new + set fdm=marker + let lines =<< trim END + " {{{ + " }}}1 + " {{{ + END + call setline(1, lines) + 3d + g/"/d + undo + redo + " eval getline(1, '$')->assert_equal(['']) + eval assert_equal(getline(1, '$'), ['']) + + set fdm&vim + bwipe! +endfunc + " this was crashing func Test_move_no_folds() new diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim index 2de2c412de..8edc9c2608 100644 --- a/src/nvim/testdir/test_global.vim +++ b/src/nvim/testdir/test_global.vim @@ -36,4 +36,8 @@ func Test_global_error() call assert_fails('g/\(/y', 'E476:') endfunc +func Test_wrong_delimiter() + call assert_fails('g x^bxd', 'E146:') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index a5cbd8f6a6..6cb736a38a 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -126,11 +126,16 @@ endfunction func s:set_varg7(...) abort let b = a:000 - call add(b, 1) + let b += [1] endfunction func s:set_varg8(...) abort let b = a:000 + call add(b, 1) +endfunction + +func s:set_varg9(...) abort + let b = a:000 let b[0][0] = 1 endfunction @@ -142,7 +147,8 @@ func Test_let_varg_fail() call s:set_varg5([0]) call assert_fails('call s:set_varg6(1)', 'E742:') call assert_fails('call s:set_varg7(1)', 'E742:') - call s:set_varg8([0]) + call assert_fails('call s:set_varg8(1)', 'E742:') + call s:set_varg9([0]) endfunction func Test_let_utf8_environment() diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 4a00999c45..5c413d1e16 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1111,161 +1111,6 @@ func Test_normal18_z_fold() bw! endfunc -func Test_normal19_z_spell() - if !has("spell") || !has('syntax') - return - endif - new - call append(0, ['1 good', '2 goood', '3 goood']) - set spell spellfile=./Xspellfile.add spelllang=en - let oldlang=v:lang - lang C - - " Test for zg - 1 - norm! ]s - call assert_equal('2 goood', getline('.')) - norm! zg - 1 - let a=execute('unsilent :norm! ]s') - call assert_equal('1 good', getline('.')) - call assert_equal('search hit BOTTOM, continuing at TOP', a[1:]) - let cnt=readfile('./Xspellfile.add') - call assert_equal('goood', cnt[0]) - - " Test for zw - 2 - norm! $zw - 1 - norm! ]s - call assert_equal('2 goood', getline('.')) - let cnt=readfile('./Xspellfile.add') - call assert_equal('#oood', cnt[0]) - call assert_equal('goood/!', cnt[1]) - - " Test for zg in visual mode - let a=execute('unsilent :norm! V$zg') - call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:]) - 1 - norm! ]s - call assert_equal('3 goood', getline('.')) - let cnt=readfile('./Xspellfile.add') - call assert_equal('2 goood', cnt[2]) - " Remove "2 good" from spellfile - 2 - let a=execute('unsilent norm! V$zw') - call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:]) - let cnt=readfile('./Xspellfile.add') - call assert_equal('2 goood/!', cnt[3]) - - " Test for zG - let a=execute('unsilent norm! V$zG') - call assert_match("Word '2 goood' added to .*", a) - let fname=matchstr(a, 'to\s\+\zs\f\+$') - let fname=Fix_truncated_tmpfile(fname) - let cnt=readfile(fname) - call assert_equal('2 goood', cnt[0]) - - " Test for zW - let a=execute('unsilent norm! V$zW') - call assert_match("Word '2 goood' added to .*", a) - let cnt=readfile(fname) - call assert_equal('# goood', cnt[0]) - call assert_equal('2 goood/!', cnt[1]) - - " Test for zuW - let a=execute('unsilent norm! V$zuW') - call assert_match("Word '2 goood' removed from .*", a) - let cnt=readfile(fname) - call assert_equal('# goood', cnt[0]) - call assert_equal('# goood/!', cnt[1]) - - " Test for zuG - let a=execute('unsilent norm! $zG') - call assert_match("Word 'goood' added to .*", a) - let cnt=readfile(fname) - call assert_equal('# goood', cnt[0]) - call assert_equal('# goood/!', cnt[1]) - call assert_equal('goood', cnt[2]) - let a=execute('unsilent norm! $zuG') - let cnt=readfile(fname) - call assert_match("Word 'goood' removed from .*", a) - call assert_equal('# goood', cnt[0]) - call assert_equal('# goood/!', cnt[1]) - call assert_equal('#oood', cnt[2]) - " word not found in wordlist - let a=execute('unsilent norm! V$zuG') - let cnt=readfile(fname) - call assert_match("", a) - call assert_equal('# goood', cnt[0]) - call assert_equal('# goood/!', cnt[1]) - call assert_equal('#oood', cnt[2]) - - " Test for zug - call delete('./Xspellfile.add') - 2 - let a=execute('unsilent norm! $zg') - let cnt=readfile('./Xspellfile.add') - call assert_equal('goood', cnt[0]) - let a=execute('unsilent norm! $zug') - call assert_match("Word 'goood' removed from \./Xspellfile.add", a) - let cnt=readfile('./Xspellfile.add') - call assert_equal('#oood', cnt[0]) - " word not in wordlist - let a=execute('unsilent norm! V$zug') - call assert_match('', a) - let cnt=readfile('./Xspellfile.add') - call assert_equal('#oood', cnt[0]) - - " Test for zuw - call delete('./Xspellfile.add') - 2 - let a=execute('unsilent norm! Vzw') - let cnt=readfile('./Xspellfile.add') - call assert_equal('2 goood/!', cnt[0]) - let a=execute('unsilent norm! Vzuw') - call assert_match("Word '2 goood' removed from \./Xspellfile.add", a) - let cnt=readfile('./Xspellfile.add') - call assert_equal('# goood/!', cnt[0]) - " word not in wordlist - let a=execute('unsilent norm! $zug') - call assert_match('', a) - let cnt=readfile('./Xspellfile.add') - call assert_equal('# goood/!', cnt[0]) - - " add second entry to spellfile setting - set spellfile=./Xspellfile.add,./Xspellfile2.add - call delete('./Xspellfile.add') - 2 - let a=execute('unsilent norm! $2zg') - let cnt=readfile('./Xspellfile2.add') - call assert_match("Word 'goood' added to ./Xspellfile2.add", a) - call assert_equal('goood', cnt[0]) - - " Test for :spellgood! - let temp = execute(':spe!0/0') - call assert_match('Invalid region', temp) - let spellfile = matchstr(temp, 'Invalid region nr in \zs.*\ze line \d: 0') - call assert_equal(['# goood', '# goood/!', '#oood', '0/0'], readfile(spellfile)) - call delete(spellfile) - - " clean up - exe "lang" oldlang - call delete("./Xspellfile.add") - call delete("./Xspellfile2.add") - call delete("./Xspellfile.add.spl") - call delete("./Xspellfile2.add.spl") - - " zux -> no-op - 2 - norm! $zux - call assert_equal([], glob('Xspellfile.add',0,1)) - call assert_equal([], glob('Xspellfile2.add',0,1)) - - set spellfile= - bw! -endfunc - func Test_normal20_exmode() if !has("unix") " Reading from redirected file doesn't work on MS-Windows diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim index 81326bce14..eaabe3f67e 100644 --- a/src/nvim/testdir/test_number.vim +++ b/src/nvim/testdir/test_number.vim @@ -1,5 +1,6 @@ " Test for 'number' and 'relativenumber' +source check.vim source view_util.vim func s:screen_lines(start, end) abort @@ -263,3 +264,27 @@ func Test_relativenumber_uninitialised() redraw bwipe! endfunc + +" Test for displaying line numbers with 'rightleft' +func Test_number_rightleft() + CheckFeature rightleft + new + setlocal number + setlocal rightleft + call setline(1, range(1, 1000)) + normal! 9Gzt + redraw! + call assert_match('^\s\+9 9$', Screenline(1)) + normal! 10Gzt + redraw! + call assert_match('^\s\+01 10$', Screenline(1)) + normal! 100Gzt + redraw! + call assert_match('^\s\+001 100$', Screenline(1)) + normal! 1000Gzt + redraw! + call assert_match('^\s\+0001 1000$', Screenline(1)) + bw! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index bf15f7f52b..6bd64caa6c 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -891,7 +891,7 @@ func Test_efm1() Xtestfile:9: parse error before `asd' make: *** [vim] Error 1 in file "Xtestfile" linenr 10: there is an error - + 2 returned "Xtestfile", line 11 col 1; this is an error "Xtestfile", line 12 col 2; this is another error @@ -914,7 +914,7 @@ func Test_efm1() x should be a dot xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20 ^ - + Does anyone know what is the problem and how to correction it? "Xtestfile", line 21 col 9: What is the title of the quickfix window? "Xtestfile", line 22 col 9: What is the title of the quickfix window? @@ -2692,6 +2692,28 @@ func Test_cwindow_jump() set efm&vim endfunc +func Test_cwindow_highlight() + CheckScreendump + + let lines =<< trim END + call setline(1, ['some', 'text', 'with', 'matches']) + write XCwindow + vimgrep e XCwindow + redraw + cwindow 4 + END + call writefile(lines, 'XtestCwindow') + let buf = RunVimInTerminal('-S XtestCwindow', #{rows: 12}) + call VerifyScreenDump(buf, 'Test_quickfix_cwindow_1', {}) + call term_sendkeys(buf, ":cnext\<CR>") + call VerifyScreenDump(buf, 'Test_quickfix_cwindow_2', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XtestCwindow') + call delete('XCwindow') +endfunc + func XvimgrepTests(cchar) call s:setup_commands(a:cchar) @@ -3461,12 +3483,13 @@ func Xgetlist_empty_tests(cchar) if a:cchar == 'c' call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, \ 'items' : [], 'nr' : 0, 'size' : 0, - \ 'title' : '', 'winid' : 0, 'changedtick': 0}, - \ g:Xgetlist({'all' : 0})) + \ 'title' : '', 'winid' : 0, 'changedtick': 0, + \ 'quickfixtextfunc' : ''}, g:Xgetlist({'all' : 0})) else call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, \ 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', - \ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0}, + \ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0, + \ 'quickfixtextfunc' : ''}, \ g:Xgetlist({'all' : 0})) endif @@ -3504,11 +3527,13 @@ func Xgetlist_empty_tests(cchar) if a:cchar == 'c' call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, + \ 'quickfixtextfunc' : '', \ 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0})) else call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'changedtick' : 0, 'filewinid' : 0}, + \ 'changedtick' : 0, 'filewinid' : 0, + \ 'quickfixtextfunc' : ''}, \ g:Xgetlist({'id' : qfid, 'all' : 0})) endif @@ -3525,12 +3550,13 @@ func Xgetlist_empty_tests(cchar) if a:cchar == 'c' call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'changedtick' : 0}, g:Xgetlist({'nr' : 5, 'all' : 0})) + \ 'changedtick' : 0, + \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0})) else call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'changedtick' : 0, 'filewinid' : 0}, - \ g:Xgetlist({'nr' : 5, 'all' : 0})) + \ 'changedtick' : 0, 'filewinid' : 0, + \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0})) endif endfunc @@ -4972,6 +4998,9 @@ func Xtest_qftextfunc(cchar) set efm=%f:%l:%c:%m set quickfixtextfunc=Tqfexpr + call assert_equal('Tqfexpr', &quickfixtextfunc) + call assert_equal('', + \ g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc) Xexpr ['F1:10:2:green', 'F1:20:4:blue'] Xwindow call assert_equal('F1-L10C2-green', getline(1)) @@ -5008,12 +5037,15 @@ func Xtest_qftextfunc(cchar) call assert_equal('Line 10, Col 2', getline(1)) call assert_equal('Line 20, Col 4', getline(2)) Xclose + call assert_equal(function('PerQfText'), + \ g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc) " Add entries to the list when the quickfix buffer is hidden Xaddexpr ['F1:30:6:red'] Xwindow call assert_equal('Line 30, Col 6', getline(3)) Xclose call g:Xsetlist([], 'r', {'quickfixtextfunc' : ''}) + call assert_equal('', g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc) set quickfixtextfunc& delfunc PerQfText @@ -5052,12 +5084,53 @@ func Xtest_qftextfunc(cchar) " \ 'E730:') Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red'] call assert_fails('Xwindow', 'E730:') - call assert_equal(['one', 'F1|20 col 4| blue', 'two'], getline(1, '$')) + call assert_equal(['one', 'F1|20 col 4| blue', 'F1|30 col 6| red'], + \ getline(1, '$')) Xclose set quickfixtextfunc& delfunc Xqftext delfunc Xqftext2 + + " set the global option to a lambda function + set quickfixtextfunc={d\ ->\ map(g:Xgetlist({'id'\ :\ d.id,\ 'items'\ :\ 1}).items[d.start_idx-1:d.end_idx-1],\ 'v:val.text')} + Xexpr ['F1:10:2:green', 'F1:20:4:blue'] + Xwindow + call assert_equal(['green', 'blue'], getline(1, '$')) + Xclose + call assert_equal("{d -> map(g:Xgetlist({'id' : d.id, 'items' : 1}).items[d.start_idx-1:d.end_idx-1], 'v:val.text')}", &quickfixtextfunc) + set quickfixtextfunc& + + " use a lambda function that returns an empty list + set quickfixtextfunc={d\ ->\ []} + Xexpr ['F1:10:2:green', 'F1:20:4:blue'] + Xwindow + call assert_equal(['F1|10 col 2| green', 'F1|20 col 4| blue'], + \ getline(1, '$')) + Xclose + set quickfixtextfunc& + + " use a lambda function that returns a list with empty strings + set quickfixtextfunc={d\ ->\ ['',\ '']} + Xexpr ['F1:10:2:green', 'F1:20:4:blue'] + Xwindow + call assert_equal(['F1|10 col 2| green', 'F1|20 col 4| blue'], + \ getline(1, '$')) + Xclose + set quickfixtextfunc& + + " set the per-quickfix list text function to a lambda function + call g:Xsetlist([], ' ', + \ {'quickfixtextfunc' : + \ {d -> map(g:Xgetlist({'id' : d.id, 'items' : 1}).items[d.start_idx-1:d.end_idx-1], + \ "'Line ' .. v:val.lnum .. ', Col ' .. v:val.col")}}) + Xaddexpr ['F1:10:2:green', 'F1:20:4:blue'] + Xwindow + call assert_equal('Line 10, Col 2', getline(1)) + call assert_equal('Line 20, Col 4', getline(2)) + Xclose + call assert_match("function('<lambda>\\d\\+')", string(g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc)) + call g:Xsetlist([], 'f') endfunc func Test_qftextfunc() @@ -5065,4 +5138,62 @@ func Test_qftextfunc() call Xtest_qftextfunc('l') endfunc +" Test for updating a location list for some other window and check that +" 'qftextfunc' uses the correct location list. +func Test_qftextfunc_other_loclist() + %bw! + call setloclist(0, [], 'f') + + " create a window and a location list for it and open the location list + " window + lexpr ['F1:10:12:one', 'F1:20:14:two'] + let w1_id = win_getid() + call setloclist(0, [], ' ', + \ {'lines': ['F1:10:12:one', 'F1:20:14:two'], + \ 'quickfixtextfunc': + \ {d -> map(getloclist(d.winid, {'id' : d.id, + \ 'items' : 1}).items[d.start_idx-1:d.end_idx-1], + \ "'Line ' .. v:val.lnum .. ', Col ' .. v:val.col")}}) + lwindow + let w2_id = win_getid() + + " create another window and a location list for it and open the location + " list window + topleft new + let w3_id = win_getid() + call setloclist(0, [], ' ', + \ {'lines': ['F2:30:32:eleven', 'F2:40:34:twelve'], + \ 'quickfixtextfunc': + \ {d -> map(getloclist(d.winid, {'id' : d.id, + \ 'items' : 1}).items[d.start_idx-1:d.end_idx-1], + \ "'Ligne ' .. v:val.lnum .. ', Colonne ' .. v:val.col")}}) + lwindow + let w4_id = win_getid() + + topleft new + lexpr ['F3:50:52:green', 'F3:60:54:blue'] + let w5_id = win_getid() + + " change the location list for some other window + call setloclist(0, [], 'r', {'lines': ['F3:55:56:aaa', 'F3:57:58:bbb']}) + call setloclist(w1_id, [], 'r', {'lines': ['F1:62:63:bbb', 'F1:64:65:ccc']}) + call setloclist(w3_id, [], 'r', {'lines': ['F2:76:77:ddd', 'F2:78:79:eee']}) + call assert_equal(['Line 62, Col 63', 'Line 64, Col 65'], + \ getbufline(winbufnr(w2_id), 1, '$')) + call assert_equal(['Ligne 76, Colonne 77', 'Ligne 78, Colonne 79'], + \ getbufline(winbufnr(w4_id), 1, '$')) + call setloclist(w2_id, [], 'r', {'lines': ['F1:32:33:fff', 'F1:34:35:ggg']}) + call setloclist(w4_id, [], 'r', {'lines': ['F2:46:47:hhh', 'F2:48:49:jjj']}) + call assert_equal(['Line 32, Col 33', 'Line 34, Col 35'], + \ getbufline(winbufnr(w2_id), 1, '$')) + call assert_equal(['Ligne 46, Colonne 47', 'Ligne 48, Colonne 49'], + \ getbufline(winbufnr(w4_id), 1, '$')) + + call win_gotoid(w5_id) + lwindow + call assert_equal(['F3|55 col 56| aaa', 'F3|57 col 58| bbb'], + \ getline(1, '$')) + %bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index ab8a998bb8..e525d06ea2 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -106,11 +106,14 @@ foobar/? set spelllang=Xwords.spl call assert_equal(['foobar', 'rare'], spellbadword('foo foobar')) - " Typo should not be detected without the 'spell' option. + " Typo should be detected even without the 'spell' option. set spelllang=en_gb nospell call assert_equal(['', ''], spellbadword('centre')) - call assert_equal(['', ''], spellbadword('My bycycle.')) - call assert_equal(['', ''], spellbadword('A sentence. another sentence')) + call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.')) + call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence')) + + set spelllang= + call assert_fails("call spellbadword('maxch')", 'E756:') call delete('Xwords.spl') call delete('Xwords') @@ -172,6 +175,183 @@ func Test_spellreall() bwipe! endfunc +" Test spellsuggest({word} [, {max} [, {capital}]]) +func Test_spellsuggest() + " Verify suggestions are given even when spell checking is not enabled. + set nospell + call assert_equal(['march', 'March'], spellsuggest('marrch', 2)) + + set spell + + " With 1 argument. + call assert_equal(['march', 'March'], spellsuggest('marrch')[0:1]) + + " With 2 arguments. + call assert_equal(['march', 'March'], spellsuggest('marrch', 2)) + + " With 3 arguments. + call assert_equal(['march'], spellsuggest('marrch', 1, 0)) + call assert_equal(['March'], spellsuggest('marrch', 1, 1)) + + " Test with digits and hyphen. + call assert_equal('Carbon-14', spellsuggest('Carbon-15')[0]) + + " Comment taken from spellsuggest.c explains the following test cases: + " + " If there are more UPPER than lower case letters suggest an + " ALLCAP word. Otherwise, if the first letter is UPPER then + " suggest ONECAP. Exception: "ALl" most likely should be "All", + " require three upper case letters. + call assert_equal(['THIRD', 'third'], spellsuggest('thIRD', 2)) + call assert_equal(['third', 'THIRD'], spellsuggest('tHIrd', 2)) + call assert_equal(['Third'], spellsuggest('THird', 1)) + call assert_equal(['All'], spellsuggest('ALl', 1)) + + call assert_fails("call spellsuggest('maxch', [])", 'E745:') + call assert_fails("call spellsuggest('maxch', 2, [])", 'E745:') + + set spelllang= + call assert_fails("call spellsuggest('maxch')", 'E756:') + set spelllang& + + set spell& +endfunc + +" Test 'spellsuggest' option with methods fast, best and double. +func Test_spellsuggest_option_methods() + set spell + + for e in ['utf-8'] + exe 'set encoding=' .. e + + set spellsuggest=fast + call assert_equal(['Stick', 'Stitch'], spellsuggest('Stich', 2), e) + + " With best or double option, "Stitch" should become the top suggestion + " because of better phonetic matching. + set spellsuggest=best + call assert_equal(['Stitch', 'Stick'], spellsuggest('Stich', 2), e) + + set spellsuggest=double + call assert_equal(['Stitch', 'Stick'], spellsuggest('Stich', 2), e) + endfor + + set spell& spellsuggest& encoding& +endfunc + +" Test 'spellsuggest' option with value file:{filename} +func Test_spellsuggest_option_file() + set spell spellsuggest=file:Xspellsuggest + call writefile(['emacs/vim', + \ 'theribal/terrible', + \ 'teribal/terrrible', + \ 'terribal'], + \ 'Xspellsuggest') + + call assert_equal(['vim'], spellsuggest('emacs', 2)) + call assert_equal(['terrible'], spellsuggest('theribal',2)) + + " If the suggestion is misspelled (*terrrible* with 3 r), + " it should not be proposed. + " The entry for "terribal" should be ignored because of missing slash. + call assert_equal([], spellsuggest('teribal', 2)) + call assert_equal([], spellsuggest('terribal', 2)) + + set spell spellsuggest=best,file:Xspellsuggest + call assert_equal(['vim', 'Emacs'], spellsuggest('emacs', 2)) + call assert_equal(['terrible', 'tribal'], spellsuggest('theribal', 2)) + call assert_equal(['tribal'], spellsuggest('teribal', 1)) + call assert_equal(['tribal'], spellsuggest('terribal', 1)) + + call delete('Xspellsuggest') + call assert_fails("call spellsuggest('vim')", "E484: Can't open file Xspellsuggest") + + set spellsuggest& spell& +endfunc + +" Test 'spellsuggest' option with value {number} +" to limit the number of suggestions +func Test_spellsuggest_option_number() + set spell spellsuggest=2,best + new + + " We limited the number of suggestions to 2, so selecting + " the 1st and 2nd suggestion should correct the word, but + " selecting a 3rd suggestion should do nothing. + call setline(1, 'A baord') + norm $1z= + call assert_equal('A board', getline(1)) + + call setline(1, 'A baord') + norm $2z= + call assert_equal('A bard', getline(1)) + + call setline(1, 'A baord') + norm $3z= + call assert_equal('A baord', getline(1)) + + let a = execute('norm $z=') + call assert_equal( + \ "\n" + \ .. "Change \"baord\" to:\n" + \ .. " 1 \"board\"\n" + \ .. " 2 \"bard\"\n" + \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a) + + set spell spellsuggest=0 + call assert_equal("\nSorry, no suggestions", execute('norm $z=')) + + " Unlike z=, function spellsuggest(...) should not be affected by the + " max number of suggestions (2) set by the 'spellsuggest' option. + call assert_equal(['board', 'bard', 'broad'], spellsuggest('baord', 3)) + + set spellsuggest& spell& + bwipe! +endfunc + +" Test 'spellsuggest' option with value expr:{expr} +func Test_spellsuggest_option_expr() + " A silly 'spellsuggest' function which makes suggestions all uppercase + " and makes the score of each suggestion the length of the suggested word. + " So shorter suggestions are preferred. + func MySuggest() + let spellsuggest_save = &spellsuggest + set spellsuggest=3,best + let result = map(spellsuggest(v:val, 3), "[toupper(v:val), len(v:val)]") + let &spellsuggest = spellsuggest_save + return result + endfunc + + set spell spellsuggest=expr:MySuggest() + call assert_equal(['BARD', 'BOARD', 'BROAD'], spellsuggest('baord', 3)) + + new + call setline(1, 'baord') + let a = execute('norm z=') + call assert_equal( + \ "\n" + \ .. "Change \"baord\" to:\n" + \ .. " 1 \"BARD\"\n" + \ .. " 2 \"BOARD\"\n" + \ .. " 3 \"BROAD\"\n" + \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a) + + " With verbose, z= should show the score i.e. word length with + " our SpellSuggest() function. + set verbose=1 + let a = execute('norm z=') + call assert_equal( + \ "\n" + \ .. "Change \"baord\" to:\n" + \ .. " 1 \"BARD\" (4 - 0)\n" + \ .. " 2 \"BOARD\" (5 - 0)\n" + \ .. " 3 \"BROAD\" (5 - 0)\n" + \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a) + + set spell& spellsuggest& verbose& + bwipe! +endfunc + func Test_spellinfo() throw 'skipped: Nvim does not support enc=latin1' new @@ -227,7 +407,7 @@ func Test_zz_basic() \ ) call assert_equal("gebletegek", soundfold('goobledygoook')) - call assert_equal("kepereneven", soundfold('koprnven')) + call assert_equal("kepereneven", soundfold('kóopërÿnôven')) call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale')) endfunc @@ -408,7 +588,7 @@ func Test_zz_sal_and_addition() mkspell! Xtest Xtest set spl=Xtest.latin1.spl spell call assert_equal('kbltykk', soundfold('goobledygoook')) - call assert_equal('kprnfn', soundfold('koprnven')) + call assert_equal('kprnfn', soundfold('kóopërÿnôven')) call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale')) "also use an addition file @@ -461,6 +641,34 @@ func Test_zeq_crash() bwipe! endfunc +" Check that z= works even when 'nospell' is set. This test uses one of the +" tests in Test_spellsuggest_option_number() just to verify that z= basically +" works and that "E756: Spell checking is not enabled" is not generated. +func Test_zeq_nospell() + new + set nospell spellsuggest=1,best + call setline(1, 'A baord') + try + norm $1z= + call assert_equal('A board', getline(1)) + catch + call assert_report("Caught exception: " . v:exception) + endtry + set spell& spellsuggest& + bwipe! +endfunc + +" Check that "E756: Spell checking is not possible" is reported when z= is +" executed and 'spelllang' is empty. +func Test_zeq_no_spelllang() + new + set spelllang= spellsuggest=1,best + call setline(1, 'A baord') + call assert_fails('normal $1z=', 'E756:') + set spelllang& spellsuggest& + bwipe! +endfunc + " Check handling a word longer than MAXWLEN. func Test_spell_long_word() set enc=utf-8 diff --git a/src/nvim/testdir/test_spell_utf8.vim b/src/nvim/testdir/test_spell_utf8.vim new file mode 100644 index 0000000000..cafdb97f28 --- /dev/null +++ b/src/nvim/testdir/test_spell_utf8.vim @@ -0,0 +1,773 @@ +" Test for spell checking with 'encoding' set to utf-8 + +source check.vim +CheckFeature spell + +scriptencoding utf-8 + +func TearDown() + set nospell + call delete('Xtest.aff') + call delete('Xtest.dic') + call delete('Xtest.utf-8.add') + call delete('Xtest.utf-8.add.spl') + call delete('Xtest.utf-8.spl') + call delete('Xtest.utf-8.sug') +endfunc + +let g:test_data_aff1 = [ + \"SET ISO8859-1", + \"TRY esianrtolcdugmphbyfvkwjkqxz-ëéèêïîäàâöüû'ESIANRTOLCDUGMPHBYFVKWJKQXZ", + \"", + \"FOL àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ", + \"LOW àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ", + \"UPP ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßÿ", + \"", + \"SOFOFROM abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xBF", + \"SOFOTO ebctefghejklnnepkrstevvkesebctefghejklnnepkrstevvkeseeeeeeeceeeeeeeedneeeeeeeeeeepseeeeeeeeceeeeeeeedneeeeeeeeeeep?", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out .", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF" + \ ] +let g:test_data_dic1 = [ + \"123456", + \"test/NO", + \"# comment", + \"wrong", + \"Comment", + \"OK", + \"uk", + \"put/ISO", + \"the end", + \"deol", + \"d\xE9\xF4r", + \ ] +let g:test_data_aff2 = [ + \"SET ISO8859-1", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"PFXPOSTPONE", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out [a-z]", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \ ] +let g:test_data_aff3 = [ + \"SET ISO8859-1", + \"", + \"COMPOUNDMIN 3", + \"COMPOUNDRULE m*", + \"NEEDCOMPOUND x", + \ ] +let g:test_data_dic3 = [ + \"1234", + \"foo/m", + \"bar/mx", + \"m\xEF/m", + \"la/mx", + \ ] +let g:test_data_aff4 = [ + \"SET ISO8859-1", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"COMPOUNDRULE m+", + \"COMPOUNDRULE sm*e", + \"COMPOUNDRULE sm+", + \"COMPOUNDMIN 3", + \"COMPOUNDWORDMAX 3", + \"COMPOUNDFORBIDFLAG t", + \"", + \"COMPOUNDSYLMAX 5", + \"SYLLABLE a\xE1e\xE9i\xEDo\xF3\xF6\xF5u\xFA\xFC\xFBy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \"", + \"NEEDAFFIX x", + \"", + \"PFXPOSTPONE", + \"", + \"MIDWORD '-", + \"", + \"SFX q N 1", + \"SFX q 0 -ok .", + \"", + \"SFX a Y 2", + \"SFX a 0 s .", + \"SFX a 0 ize/t .", + \"", + \"PFX p N 1", + \"PFX p 0 pre .", + \"", + \"PFX P N 1", + \"PFX P 0 nou .", + \ ] +let g:test_data_dic4 = [ + \"1234", + \"word/mP", + \"util/am", + \"pro/xq", + \"tomato/m", + \"bork/mp", + \"start/s", + \"end/e", + \ ] +let g:test_data_aff5 = [ + \"SET ISO8859-1", + \"", + \"FLAG long", + \"", + \"NEEDAFFIX !!", + \"", + \"COMPOUNDRULE ssmm*ee", + \"", + \"NEEDCOMPOUND xx", + \"COMPOUNDPERMITFLAG pp", + \"", + \"SFX 13 Y 1", + \"SFX 13 0 bork .", + \"", + \"SFX a1 Y 1", + \"SFX a1 0 a1 .", + \"", + \"SFX a\xE9 Y 1", + \"SFX a\xE9 0 a\xE9 .", + \"", + \"PFX zz Y 1", + \"PFX zz 0 pre/pp .", + \"", + \"PFX yy Y 1", + \"PFX yy 0 nou .", + \ ] +let g:test_data_dic5 = [ + \"1234", + \"foo/a1a\xE9!!", + \"bar/zz13ee", + \"start/ss", + \"end/eeyy", + \"middle/mmxx", + \ ] +let g:test_data_aff6 = [ + \"SET ISO8859-1", + \"", + \"FLAG caplong", + \"", + \"NEEDAFFIX A!", + \"", + \"COMPOUNDRULE sMm*Ee", + \"", + \"NEEDCOMPOUND Xx", + \"", + \"COMPOUNDPERMITFLAG p", + \"", + \"SFX N3 Y 1", + \"SFX N3 0 bork .", + \"", + \"SFX A1 Y 1", + \"SFX A1 0 a1 .", + \"", + \"SFX A\xE9 Y 1", + \"SFX A\xE9 0 a\xE9 .", + \"", + \"PFX Zz Y 1", + \"PFX Zz 0 pre/p .", + \ ] +let g:test_data_dic6 = [ + \"1234", + \"mee/A1A\xE9A!", + \"bar/ZzN3Ee", + \"lead/s", + \"end/Ee", + \"middle/MmXx", + \ ] +let g:test_data_aff7 = [ + \"SET ISO8859-1", + \"", + \"FLAG num", + \"", + \"NEEDAFFIX 9999", + \"", + \"COMPOUNDRULE 2,77*123", + \"", + \"NEEDCOMPOUND 1", + \"COMPOUNDPERMITFLAG 432", + \"", + \"SFX 61003 Y 1", + \"SFX 61003 0 meat .", + \"", + \"SFX 0 Y 1", + \"SFX 0 0 zero .", + \"", + \"SFX 391 Y 1", + \"SFX 391 0 a1 .", + \"", + \"SFX 111 Y 1", + \"SFX 111 0 a\xE9 .", + \"", + \"PFX 17 Y 1", + \"PFX 17 0 pre/432 .", + \ ] +let g:test_data_dic7 = [ + \"1234", + \"mee/0,391,111,9999", + \"bar/17,61003,123", + \"lead/2", + \"tail/123", + \"middle/77,1", + \ ] +let g:test_data_aff8 = [ + \"SET ISO8859-1", + \"", + \"NOSPLITSUGS", + \ ] +let g:test_data_dic8 = [ + \"1234", + \"foo", + \"bar", + \"faabar", + \ ] +let g:test_data_aff9 = [ + \ ] +let g:test_data_dic9 = [ + \"1234", + \"foo", + \"bar", + \ ] +let g:test_data_aff10 = [ + \"COMPOUNDRULE se", + \"COMPOUNDPERMITFLAG p", + \"", + \"SFX A Y 1", + \"SFX A 0 able/Mp .", + \"", + \"SFX M Y 1", + \"SFX M 0 s .", + \ ] +let g:test_data_dic10 = [ + \"1234", + \"drink/As", + \"table/e", + \ ] +let g:test_data_aff_sal = [ + \"SET ISO8859-1", + \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out .", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \"", + \"SAL AH(AEIOUY)-^ *H", + \"SAL AR(AEIOUY)-^ *R", + \"SAL A(HR)^ *", + \"SAL A^ *", + \"SAL AH(AEIOUY)- H", + \"SAL AR(AEIOUY)- R", + \"SAL A(HR) _", + \"SAL \xC0^ *", + \"SAL \xC5^ *", + \"SAL BB- _", + \"SAL B B", + \"SAL CQ- _", + \"SAL CIA X", + \"SAL CH X", + \"SAL C(EIY)- S", + \"SAL CK K", + \"SAL COUGH^ KF", + \"SAL CC< C", + \"SAL C K", + \"SAL DG(EIY) K", + \"SAL DD- _", + \"SAL D T", + \"SAL \xC9< E", + \"SAL EH(AEIOUY)-^ *H", + \"SAL ER(AEIOUY)-^ *R", + \"SAL E(HR)^ *", + \"SAL ENOUGH^$ *NF", + \"SAL E^ *", + \"SAL EH(AEIOUY)- H", + \"SAL ER(AEIOUY)- R", + \"SAL E(HR) _", + \"SAL FF- _", + \"SAL F F", + \"SAL GN^ N", + \"SAL GN$ N", + \"SAL GNS$ NS", + \"SAL GNED$ N", + \"SAL GH(AEIOUY)- K", + \"SAL GH _", + \"SAL GG9 K", + \"SAL G K", + \"SAL H H", + \"SAL IH(AEIOUY)-^ *H", + \"SAL IR(AEIOUY)-^ *R", + \"SAL I(HR)^ *", + \"SAL I^ *", + \"SAL ING6 N", + \"SAL IH(AEIOUY)- H", + \"SAL IR(AEIOUY)- R", + \"SAL I(HR) _", + \"SAL J K", + \"SAL KN^ N", + \"SAL KK- _", + \"SAL K K", + \"SAL LAUGH^ LF", + \"SAL LL- _", + \"SAL L L", + \"SAL MB$ M", + \"SAL MM M", + \"SAL M M", + \"SAL NN- _", + \"SAL N N", + \"SAL OH(AEIOUY)-^ *H", + \"SAL OR(AEIOUY)-^ *R", + \"SAL O(HR)^ *", + \"SAL O^ *", + \"SAL OH(AEIOUY)- H", + \"SAL OR(AEIOUY)- R", + \"SAL O(HR) _", + \"SAL PH F", + \"SAL PN^ N", + \"SAL PP- _", + \"SAL P P", + \"SAL Q K", + \"SAL RH^ R", + \"SAL ROUGH^ RF", + \"SAL RR- _", + \"SAL R R", + \"SAL SCH(EOU)- SK", + \"SAL SC(IEY)- S", + \"SAL SH X", + \"SAL SI(AO)- X", + \"SAL SS- _", + \"SAL S S", + \"SAL TI(AO)- X", + \"SAL TH @", + \"SAL TCH-- _", + \"SAL TOUGH^ TF", + \"SAL TT- _", + \"SAL T T", + \"SAL UH(AEIOUY)-^ *H", + \"SAL UR(AEIOUY)-^ *R", + \"SAL U(HR)^ *", + \"SAL U^ *", + \"SAL UH(AEIOUY)- H", + \"SAL UR(AEIOUY)- R", + \"SAL U(HR) _", + \"SAL V^ W", + \"SAL V F", + \"SAL WR^ R", + \"SAL WH^ W", + \"SAL W(AEIOU)- W", + \"SAL X^ S", + \"SAL X KS", + \"SAL Y(AEIOU)- Y", + \"SAL ZZ- _", + \"SAL Z S", + \ ] + +func LoadAffAndDic(aff_contents, dic_contents) + set enc=utf-8 + set spellfile= + call writefile(a:aff_contents, "Xtest.aff") + call writefile(a:dic_contents, "Xtest.dic") + " Generate a .spl file from a .dic and .aff file. + mkspell! Xtest Xtest + " use that spell file + set spl=Xtest.utf-8.spl spell +endfunc + +func ListWords() + spelldump + %yank + quit + return split(@", "\n") +endfunc + +func TestGoodBadBase() + exe '1;/^good:' + normal 0f:]s + let prevbad = '' + let result = [] + while 1 + let [bad, a] = spellbadword() + if bad == '' || bad == prevbad || bad == 'badend' + break + endif + let prevbad = bad + " let lst = bad->spellsuggest(3) + let lst = spellsuggest(bad, 3) + normal mm + + call add(result, [bad, lst]) + normal `m]s + endwhile + return result +endfunc + +func RunGoodBad(good, bad, expected_words, expected_bad_words) + %bwipe! + call setline(1, ['', "good: ", a:good, a:bad, " badend "]) + let words = ListWords() + call assert_equal(a:expected_words, words[1:-1]) + let bad_words = TestGoodBadBase() + call assert_equal(a:expected_bad_words, bad_words) + %bwipe! +endfunc + +func Test_spell_basic() + call LoadAffAndDic(g:test_data_aff1, g:test_data_dic1) + call RunGoodBad("wrong OK puts. Test the end", + \ "bad: inputs comment ok Ok. test d\u00E9\u00F4l end the", + \["Comment", "deol", "d\u00E9\u00F4r", "input", "OK", "output", "outputs", "outtest", "put", "puts", + \ "test", "testen", "testn", "the end", "uk", "wrong"], + \[ + \ ["bad", ["put", "uk", "OK"]], + \ ["inputs", ["input", "puts", "outputs"]], + \ ["comment", ["Comment", "outtest", "the end"]], + \ ["ok", ["OK", "uk", "put"]], + \ ["Ok", ["OK", "Uk", "Put"]], + \ ["test", ["Test", "testn", "testen"]], + \ ["d\u00E9\u00F4l", ["deol", "d\u00E9\u00F4r", "test"]], + \ ["end", ["put", "uk", "test"]], + \ ["the", ["put", "uk", "test"]], + \ ] + \ ) + + call assert_equal("gebletegek", soundfold('goobledygoook')) + " call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold()) + call assert_equal("kepereneven", soundfold('kóopërÿnôven')) + call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale')) +endfunc + +" Postponed prefixes +func Test_spell_prefixes() + call LoadAffAndDic(g:test_data_aff2, g:test_data_dic1) + call RunGoodBad("puts", + \ "bad: inputs comment ok Ok end the. test d\u00E9\u00F4l", + \ ["Comment", "deol", "d\u00E9\u00F4r", "OK", "put", "input", "output", "puts", "outputs", "test", "outtest", "testen", "testn", "the end", "uk", "wrong"], + \ [ + \ ["bad", ["put", "uk", "OK"]], + \ ["inputs", ["input", "puts", "outputs"]], + \ ["comment", ["Comment"]], + \ ["ok", ["OK", "uk", "put"]], + \ ["Ok", ["OK", "Uk", "Put"]], + \ ["end", ["put", "uk", "deol"]], + \ ["the", ["put", "uk", "test"]], + \ ["test", ["Test", "testn", "testen"]], + \ ["d\u00E9\u00F4l", ["deol", "d\u00E9\u00F4r", "test"]], + \ ]) +endfunc + +"Compound words +func Test_spell_compound() + throw 'skipped: TODO: ' + call LoadAffAndDic(g:test_data_aff3, g:test_data_dic3) + call RunGoodBad("foo m\u00EF foobar foofoobar barfoo barbarfoo", + \ "bad: bar la foom\u00EF barm\u00EF m\u00EFfoo m\u00EFbar m\u00EFm\u00EF lala m\u00EFla lam\u00EF foola labar", + \ ["foo", "m\u00EF"], + \ [ + \ ["bad", ["foo", "m\u00EF"]], + \ ["bar", ["barfoo", "foobar", "foo"]], + \ ["la", ["m\u00EF", "foo"]], + \ ["foom\u00EF", ["foo m\u00EF", "foo", "foofoo"]], + \ ["barm\u00EF", ["barfoo", "m\u00EF", "barbar"]], + \ ["m\u00EFfoo", ["m\u00EF foo", "foo", "foofoo"]], + \ ["m\u00EFbar", ["foobar", "barbar", "m\u00EF"]], + \ ["m\u00EFm\u00EF", ["m\u00EF m\u00EF", "m\u00EF"]], + \ ["lala", []], + \ ["m\u00EFla", ["m\u00EF", "m\u00EF m\u00EF"]], + \ ["lam\u00EF", ["m\u00EF", "m\u00EF m\u00EF"]], + \ ["foola", ["foo", "foobar", "foofoo"]], + \ ["labar", ["barbar", "foobar"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff4, g:test_data_dic4) + call RunGoodBad("word util bork prebork start end wordutil wordutils pro-ok bork borkbork borkborkbork borkborkborkbork borkborkborkborkbork tomato tomatotomato startend startword startwordword startwordend startwordwordend startwordwordwordend prebork preborkbork preborkborkbork nouword", + \ "bad: wordutilize pro borkborkborkborkborkbork tomatotomatotomato endstart endend startstart wordend wordstart preborkprebork preborkpreborkbork startwordwordwordwordend borkpreborkpreborkbork utilsbork startnouword", + \ ["bork", "prebork", "end", "pro-ok", "start", "tomato", "util", "utilize", "utils", "word", "nouword"], + \ [ + \ ["bad", ["end", "bork", "word"]], + \ ["wordutilize", ["word utilize", "wordutils", "wordutil"]], + \ ["pro", ["bork", "word", "end"]], + \ ["borkborkborkborkborkbork", ["bork borkborkborkborkbork", "borkbork borkborkborkbork", "borkborkbork borkborkbork"]], + \ ["tomatotomatotomato", ["tomato tomatotomato", "tomatotomato tomato", "tomato tomato tomato"]], + \ ["endstart", ["end start", "start"]], + \ ["endend", ["end end", "end"]], + \ ["startstart", ["start start"]], + \ ["wordend", ["word end", "word", "wordword"]], + \ ["wordstart", ["word start", "bork start"]], + \ ["preborkprebork", ["prebork prebork", "preborkbork", "preborkborkbork"]], + \ ["preborkpreborkbork", ["prebork preborkbork", "preborkborkbork", "preborkborkborkbork"]], + \ ["startwordwordwordwordend", ["startwordwordwordword end", "startwordwordwordword", "start wordwordwordword end"]], + \ ["borkpreborkpreborkbork", ["bork preborkpreborkbork", "bork prebork preborkbork", "bork preborkprebork bork"]], + \ ["utilsbork", ["utilbork", "utils bork", "util bork"]], + \ ["startnouword", ["start nouword", "startword", "startborkword"]], + \ ]) + +endfunc + +" Test affix flags with two characters +func Test_spell_affix() + throw 'skipped: TODO: ' + call LoadAffAndDic(g:test_data_aff5, g:test_data_dic5) + call RunGoodBad("fooa1 fooa\u00E9 bar prebar barbork prebarbork startprebar start end startend startmiddleend nouend", + \ "bad: foo fooa2 prabar probarbirk middle startmiddle middleend endstart startprobar startnouend", + \ ["bar", "barbork", "end", "fooa1", "fooa\u00E9", "nouend", "prebar", "prebarbork", "start"], + \ [ + \ ["bad", ["bar", "end", "fooa1"]], + \ ["foo", ["fooa1", "fooa\u00E9", "bar"]], + \ ["fooa2", ["fooa1", "fooa\u00E9", "bar"]], + \ ["prabar", ["prebar", "bar", "bar bar"]], + \ ["probarbirk", ["prebarbork"]], + \ ["middle", []], + \ ["startmiddle", ["startmiddleend", "startmiddlebar"]], + \ ["middleend", []], + \ ["endstart", ["end start", "start"]], + \ ["startprobar", ["startprebar", "start prebar", "startbar"]], + \ ["startnouend", ["start nouend", "startend"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff6, g:test_data_dic6) + call RunGoodBad("meea1 meea\u00E9 bar prebar barbork prebarbork leadprebar lead end leadend leadmiddleend", + \ "bad: mee meea2 prabar probarbirk middle leadmiddle middleend endlead leadprobar", + \ ["bar", "barbork", "end", "lead", "meea1", "meea\u00E9", "prebar", "prebarbork"], + \ [ + \ ["bad", ["bar", "end", "lead"]], + \ ["mee", ["meea1", "meea\u00E9", "bar"]], + \ ["meea2", ["meea1", "meea\u00E9", "lead"]], + \ ["prabar", ["prebar", "bar", "leadbar"]], + \ ["probarbirk", ["prebarbork"]], + \ ["middle", []], + \ ["leadmiddle", ["leadmiddleend", "leadmiddlebar"]], + \ ["middleend", []], + \ ["endlead", ["end lead", "lead", "end end"]], + \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7) + call RunGoodBad("meea1 meezero meea\u00E9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail", + \ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar", + \ ["bar", "barmeat", "lead", "meea1", "meea\u00E9", "meezero", "prebar", "prebarmeat", "tail"], + \ [ + \ ["bad", ["bar", "lead", "tail"]], + \ ["mee", ["meea1", "meea\u00E9", "bar"]], + \ ["meea2", ["meea1", "meea\u00E9", "lead"]], + \ ["prabar", ["prebar", "bar", "leadbar"]], + \ ["probarmaat", ["prebarmeat"]], + \ ["middle", []], + \ ["leadmiddle", ["leadmiddlebar"]], + \ ["middletail", []], + \ ["taillead", ["tail lead", "tail"]], + \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]], + \ ]) +endfunc + +func Test_spell_NOSLITSUGS() + call LoadAffAndDic(g:test_data_aff8, g:test_data_dic8) + call RunGoodBad("foo bar faabar", "bad: foobar barfoo", + \ ["bar", "faabar", "foo"], + \ [ + \ ["bad", ["bar", "foo"]], + \ ["foobar", ["faabar", "foo bar", "bar"]], + \ ["barfoo", ["bar foo", "bar", "foo"]], + \ ]) +endfunc + +" Numbers +func Test_spell_Numbers() + call LoadAffAndDic(g:test_data_aff9, g:test_data_dic9) + call RunGoodBad("0b1011 0777 1234 0x01ff", "", + \ ["bar", "foo"], + \ [ + \ ]) +endfunc + +" Affix flags +func Test_spell_affix_flags() + throw 'skipped: TODO: ' + call LoadAffAndDic(g:test_data_aff10, g:test_data_dic10) + call RunGoodBad("drink drinkable drinkables drinktable drinkabletable", + \ "bad: drinks drinkstable drinkablestable", + \ ["drink", "drinkable", "drinkables", "table"], + \ [['bad', []], + \ ['drinks', ['drink']], + \ ['drinkstable', ['drinktable', 'drinkable', 'drink table']], + \ ['drinkablestable', ['drinkabletable', 'drinkables table', 'drinkable table']], + \ ]) +endfunc + +function FirstSpellWord() + call feedkeys("/^start:\n", 'tx') + normal ]smm + let [str, a] = spellbadword() + return str +endfunc + +function SecondSpellWord() + normal `m]s + let [str, a] = spellbadword() + return str +endfunc + +" Test with SAL instead of SOFO items; test automatic reloading +func Test_spell_sal_and_addition() + set spellfile= + call writefile(g:test_data_dic1, "Xtest.dic") + call writefile(g:test_data_aff_sal, "Xtest.aff") + mkspell! Xtest Xtest + set spl=Xtest.utf-8.spl spell + call assert_equal('kbltykk', soundfold('goobledygoook')) + call assert_equal('kprnfn', soundfold('kóopërÿnôven')) + call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale')) + + "also use an addition file + call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.utf-8.add") + mkspell! Xtest.utf-8.add.spl Xtest.utf-8.add + + bwipe! + call setline(1, ["start: elequint test elekwint test elekwent asdf"]) + + set spellfile=Xtest.utf-8.add + call assert_equal("elekwent", FirstSpellWord()) + + set spl=Xtest_us.utf-8.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwint", SecondSpellWord()) + + set spl=Xtest_gb.utf-8.spl + call assert_equal("elekwint", FirstSpellWord()) + call assert_equal("elekwent", SecondSpellWord()) + + set spl=Xtest_nz.utf-8.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwent", SecondSpellWord()) + + set spl=Xtest_ca.utf-8.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwint", SecondSpellWord()) +endfunc + +func Test_spellfile_value() + set spellfile=Xdir/Xtest.utf-8.add + set spellfile=Xdir/Xtest.utf-8.add,Xtest_other.add +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spellfile.vim b/src/nvim/testdir/test_spellfile.vim new file mode 100644 index 0000000000..729467b556 --- /dev/null +++ b/src/nvim/testdir/test_spellfile.vim @@ -0,0 +1,240 @@ +" Test for commands that operate on the spellfile. + +source shared.vim +source check.vim + +CheckFeature spell +CheckFeature syntax + +func Test_spell_normal() + new + call append(0, ['1 good', '2 goood', '3 goood']) + set spell spellfile=./Xspellfile.add spelllang=en + let oldlang=v:lang + lang C + + " Test for zg + 1 + norm! ]s + call assert_equal('2 goood', getline('.')) + norm! zg + 1 + let a=execute('unsilent :norm! ]s') + call assert_equal('1 good', getline('.')) + call assert_equal('search hit BOTTOM, continuing at TOP', a[1:]) + let cnt=readfile('./Xspellfile.add') + call assert_equal('goood', cnt[0]) + + " Test for zw + 2 + norm! $zw + 1 + norm! ]s + call assert_equal('2 goood', getline('.')) + let cnt=readfile('./Xspellfile.add') + call assert_equal('#oood', cnt[0]) + call assert_equal('goood/!', cnt[1]) + + " Test for :spellrare + spellrare rare + let cnt=readfile('./Xspellfile.add') + call assert_equal(['#oood', 'goood/!', 'rare/?'], cnt) + + " Make sure :spellundo works for rare words. + spellundo rare + let cnt=readfile('./Xspellfile.add') + call assert_equal(['#oood', 'goood/!', '#are/?'], cnt) + + " Test for zg in visual mode + let a=execute('unsilent :norm! V$zg') + call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:]) + 1 + norm! ]s + call assert_equal('3 goood', getline('.')) + let cnt=readfile('./Xspellfile.add') + call assert_equal('2 goood', cnt[3]) + " Remove "2 good" from spellfile + 2 + let a=execute('unsilent norm! V$zw') + call assert_equal("Word '2 goood' added to ./Xspellfile.add", a[1:]) + let cnt=readfile('./Xspellfile.add') + call assert_equal('2 goood/!', cnt[4]) + + " Test for zG + let a=execute('unsilent norm! V$zG') + call assert_match("Word '2 goood' added to .*", a) + let fname=matchstr(a, 'to\s\+\zs\f\+$') + let cnt=readfile(fname) + call assert_equal('2 goood', cnt[0]) + + " Test for zW + let a=execute('unsilent norm! V$zW') + call assert_match("Word '2 goood' added to .*", a) + let cnt=readfile(fname) + call assert_equal('# goood', cnt[0]) + call assert_equal('2 goood/!', cnt[1]) + + " Test for zuW + let a=execute('unsilent norm! V$zuW') + call assert_match("Word '2 goood' removed from .*", a) + let cnt=readfile(fname) + call assert_equal('# goood', cnt[0]) + call assert_equal('# goood/!', cnt[1]) + + " Test for zuG + let a=execute('unsilent norm! $zG') + call assert_match("Word 'goood' added to .*", a) + let cnt=readfile(fname) + call assert_equal('# goood', cnt[0]) + call assert_equal('# goood/!', cnt[1]) + call assert_equal('goood', cnt[2]) + let a=execute('unsilent norm! $zuG') + let cnt=readfile(fname) + call assert_match("Word 'goood' removed from .*", a) + call assert_equal('# goood', cnt[0]) + call assert_equal('# goood/!', cnt[1]) + call assert_equal('#oood', cnt[2]) + " word not found in wordlist + let a=execute('unsilent norm! V$zuG') + let cnt=readfile(fname) + call assert_match("", a) + call assert_equal('# goood', cnt[0]) + call assert_equal('# goood/!', cnt[1]) + call assert_equal('#oood', cnt[2]) + + " Test for zug + call delete('./Xspellfile.add') + 2 + let a=execute('unsilent norm! $zg') + let cnt=readfile('./Xspellfile.add') + call assert_equal('goood', cnt[0]) + let a=execute('unsilent norm! $zug') + call assert_match("Word 'goood' removed from \./Xspellfile.add", a) + let cnt=readfile('./Xspellfile.add') + call assert_equal('#oood', cnt[0]) + " word not in wordlist + let a=execute('unsilent norm! V$zug') + call assert_match('', a) + let cnt=readfile('./Xspellfile.add') + call assert_equal('#oood', cnt[0]) + + " Test for zuw + call delete('./Xspellfile.add') + 2 + let a=execute('unsilent norm! Vzw') + let cnt=readfile('./Xspellfile.add') + call assert_equal('2 goood/!', cnt[0]) + let a=execute('unsilent norm! Vzuw') + call assert_match("Word '2 goood' removed from \./Xspellfile.add", a) + let cnt=readfile('./Xspellfile.add') + call assert_equal('# goood/!', cnt[0]) + " word not in wordlist + let a=execute('unsilent norm! $zug') + call assert_match('', a) + let cnt=readfile('./Xspellfile.add') + call assert_equal('# goood/!', cnt[0]) + + " add second entry to spellfile setting + set spellfile=./Xspellfile.add,./Xspellfile2.add + call delete('./Xspellfile.add') + 2 + let a=execute('unsilent norm! $2zg') + let cnt=readfile('./Xspellfile2.add') + call assert_match("Word 'goood' added to ./Xspellfile2.add", a) + call assert_equal('goood', cnt[0]) + + " Test for :spellgood! + let temp = execute(':spe!0/0') + call assert_match('Invalid region', temp) + let spellfile = matchstr(temp, 'Invalid region nr in \zs.*\ze line \d: 0') + call assert_equal(['# goood', '# goood/!', '#oood', '0/0'], readfile(spellfile)) + + " Test for :spellrare! + :spellrare! raare + call assert_equal(['# goood', '# goood/!', '#oood', '0/0', 'raare/?'], readfile(spellfile)) + call delete(spellfile) + + " clean up + exe "lang" oldlang + call delete("./Xspellfile.add") + call delete("./Xspellfile2.add") + call delete("./Xspellfile.add.spl") + call delete("./Xspellfile2.add.spl") + + " zux -> no-op + 2 + norm! $zux + call assert_equal([], glob('Xspellfile.add',0,1)) + call assert_equal([], glob('Xspellfile2.add',0,1)) + + set spellfile= + bw! +endfunc + +" Test CHECKCOMPOUNDPATTERN (see :help spell-CHECKCOMPOUNDPATTERN) +func Test_spellfile_CHECKCOMPOUNDPATTERN() + call writefile(['4', + \ 'one/c', + \ 'two/c', + \ 'three/c', + \ 'four'], 'XtestCHECKCOMPOUNDPATTERN.dic') + " Forbid compound words where first word ends with 'wo' and second starts with 'on'. + call writefile(['CHECKCOMPOUNDPATTERN 1', + \ 'CHECKCOMPOUNDPATTERN wo on', + \ 'COMPOUNDFLAG c'], 'XtestCHECKCOMPOUNDPATTERN.aff') + + let output = execute('mkspell! XtestCHECKCOMPOUNDPATTERN-utf8.spl XtestCHECKCOMPOUNDPATTERN') + set spell spelllang=XtestCHECKCOMPOUNDPATTERN-utf8.spl + + " Check valid words with and without valid compounds. + for goodword in ['one', 'two', 'three', 'four', + \ 'oneone', 'onetwo', 'onethree', + \ 'twotwo', 'twothree', + \ 'threeone', 'threetwo', 'threethree', + \ 'onetwothree', 'onethreetwo', 'twothreeone', 'oneoneone'] + call assert_equal(['', ''], spellbadword(goodword), goodword) + endfor + + " Compounds 'twoone' or 'threetwoone' should be forbidden by CHECKCOMPOUNPATTERN. + " 'four' does not have the 'c' flag in *.aff file so no compound. + " 'five' is not in the *.dic file. + for badword in ['five', 'onetwox', + \ 'twoone', 'threetwoone', + \ 'fourone', 'onefour'] + call assert_equal([badword, 'bad'], spellbadword(badword)) + endfor + + set spell& spelllang& + call delete('XtestCHECKCOMPOUNDPATTERN.dic') + call delete('XtestCHECKCOMPOUNDPATTERN.aff') + call delete('XtestCHECKCOMPOUNDPATTERN-utf8.spl') +endfunc + +" Test COMMON (better suggestions with common words, see :help spell-COMMON) +func Test_spellfile_COMMON() + call writefile(['7', + \ 'and', + \ 'ant', + \ 'end', + \ 'any', + \ 'tee', + \ 'the', + \ 'ted'], 'XtestCOMMON.dic') + call writefile(['COMMON the and'], 'XtestCOMMON.aff') + + let output = execute('mkspell! XtestCOMMON-utf8.spl XtestCOMMON') + set spell spelllang=XtestCOMMON-utf8.spl + + " COMMON words 'and' and 'the' should be the top suggestions. + call assert_equal(['and', 'ant'], spellsuggest('anr', 2)) + call assert_equal(['and', 'end'], spellsuggest('ond', 2)) + call assert_equal(['the', 'ted'], spellsuggest('tha', 2)) + call assert_equal(['the', 'tee'], spellsuggest('dhe', 2)) + + set spell& spelllang& + call delete('XtestCOMMON.dic') + call delete('XtestCOMMON.aff') + call delete('XtestCOMMON-utf8.spl') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_startup_utf8.vim b/src/nvim/testdir/test_startup_utf8.vim index 1b3d2184a0..bb4304396e 100644 --- a/src/nvim/testdir/test_startup_utf8.vim +++ b/src/nvim/testdir/test_startup_utf8.vim @@ -1,5 +1,6 @@ " Tests for startup using utf-8. +source check.vim source shared.vim source screendump.vim @@ -73,7 +74,7 @@ func Test_detect_ambiwidth() \ 'call test_option_not_set("ambiwidth")', \ 'redraw', \ ], 'Xscript') - let buf = RunVimInTerminal('-S Xscript', {}) + let buf = RunVimInTerminal('-S Xscript', #{keep_t_u7: 1}) call term_wait(buf) call term_sendkeys(buf, "S\<C-R>=&ambiwidth\<CR>\<Esc>") call WaitForAssert({-> assert_match('single', term_getline(buf, 1))}) diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 21fd57b791..a40b0236e0 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -1006,4 +1006,59 @@ func Test_visual_put_in_block_using_zp() bwipe! endfunc +func Test_visual_put_in_block_using_zy_and_zp() + new + + " Test 1) Paste using zp - after the cursor without trailing spaces + call setline(1, ['/path;text', '/path;text', '/path;text', '', + \ 'texttext /subdir columntext', + \ 'texttext /longsubdir columntext', + \ 'texttext /longlongsubdir columntext']) + exe "normal! 5G0f/\<c-v>2jezy" + norm! 1G0f;hzp + call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3)) + + " Test 2) Paste using zP - in front of the cursor without trailing spaces + %d + call setline(1, ['/path;text', '/path;text', '/path;text', '', + \ 'texttext /subdir columntext', + \ 'texttext /longsubdir columntext', + \ 'texttext /longlongsubdir columntext']) + exe "normal! 5G0f/\<c-v>2jezy" + norm! 1G0f;zP + call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3)) + + " Test 3) Paste using p - with trailing spaces + %d + call setline(1, ['/path;text', '/path;text', '/path;text', '', + \ 'texttext /subdir columntext', + \ 'texttext /longsubdir columntext', + \ 'texttext /longlongsubdir columntext']) + exe "normal! 5G0f/\<c-v>2jezy" + norm! 1G0f;hp + call assert_equal(['/path/subdir ;text', '/path/longsubdir ;text', '/path/longlongsubdir;text'], getline(1, 3)) + + " Test 4) Paste using P - with trailing spaces + %d + call setline(1, ['/path;text', '/path;text', '/path;text', '', + \ 'texttext /subdir columntext', + \ 'texttext /longsubdir columntext', + \ 'texttext /longlongsubdir columntext']) + exe "normal! 5G0f/\<c-v>2jezy" + norm! 1G0f;P + call assert_equal(['/path/subdir ;text', '/path/longsubdir ;text', '/path/longlongsubdir;text'], getline(1, 3)) + + " Test 5) Yank with spaces inside the block + %d + call setline(1, ['/path;text', '/path;text', '/path;text', '', + \ 'texttext /sub dir/ columntext', + \ 'texttext /lon gsubdir/ columntext', + \ 'texttext /lon glongsubdir/ columntext']) + exe "normal! 5G0f/\<c-v>2jf/zy" + norm! 1G0f;zP + call assert_equal(['/path/sub dir/;text', '/path/lon gsubdir/;text', '/path/lon glongsubdir/;text'], getline(1, 3)) + bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 0245c472ef..df4ab04eb6 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -313,6 +313,7 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() #define DIP_NORTP 0x20 // do not use 'runtimepath' #define DIP_NOAFTER 0x40 // skip "after" directories #define DIP_AFTER 0x80 // only use "after" directories +#define DIP_LUA 0x100 // also use ".lua" files // Lowest number used for window ID. Cannot have this many windows per tab. #define LOWEST_WIN_ID 1000 |