diff options
Diffstat (limited to 'src')
64 files changed, 3119 insertions, 1181 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 66c4454f7b..c55dc39605 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -222,11 +222,7 @@ Boolean nvim_buf_attach(uint64_t channel_id, return buf_updates_register(buf, channel_id, cb, send_buffer); error: - // TODO(bfredl): ASAN build should check that the ref table is empty? - api_free_luaref(cb.on_lines); - api_free_luaref(cb.on_bytes); - api_free_luaref(cb.on_changedtick); - api_free_luaref(cb.on_detach); + buffer_update_callbacks_free(cb); return false; } @@ -1453,6 +1449,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// the extmark end position (if it exists) will be shifted /// in when new text is inserted (true for right, false /// for left). Defaults to false. +/// - priority: a priority value for the highlight group. For +/// example treesitter highlighting uses a value of 100. /// @param[out] err Error details, if any /// @return Id of the created/updated extmark Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index d2b787a6f5..24ba6110c4 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1708,33 +1708,6 @@ const char *describe_ns(NS ns_id) return "(UNKNOWN PLUGIN)"; } -DecorProvider *get_provider(NS ns_id, bool force) -{ - ssize_t i; - for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) { - DecorProvider *item = &kv_A(decor_providers, i); - if (item->ns_id == ns_id) { - return item; - } else if (item->ns_id > ns_id) { - break; - } - } - - if (!force) { - return NULL; - } - - for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) { - // allocates if needed: - (void)kv_a(decor_providers, (size_t)j+1); - kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j); - } - DecorProvider *item = &kv_a(decor_providers, (size_t)i); - *item = DECORATION_PROVIDER_INIT(ns_id); - - return item; -} - static bool parse_float_anchor(String anchor, FloatAnchor *out) { if (anchor.size == 0) { @@ -1787,10 +1760,13 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) struct { const char *name; schar_T chars[8]; + bool shadow_color; } defaults[] = { - { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" } }, - { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" } }, - { NULL, { { NUL } } }, + { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false }, + { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" }, false }, + { "shadow", { "", "", " ", " ", " ", " ", " ", "" }, true }, + { "solid", { " ", " ", " ", " ", " ", " ", " ", " " }, false }, + { NULL, { { NUL } } , false }, }; schar_T *chars = fconfig->border_chars; @@ -1834,13 +1810,16 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) api_set_error(err, kErrorTypeValidation, "invalid border char"); return; } - if (!string.size - || mb_string2cells_len((char_u *)string.data, string.size) != 1) { + if (string.size + && mb_string2cells_len((char_u *)string.data, string.size) > 1) { api_set_error(err, kErrorTypeValidation, "border chars must be one cell"); + return; } size_t len = MIN(string.size, sizeof(*chars)-1); - memcpy(chars[i], string.data, len); + if (len) { + memcpy(chars[i], string.data, len); + } chars[i][len] = NUL; hl_ids[i] = hl_id; } @@ -1849,6 +1828,13 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) memcpy(hl_ids+size, hl_ids, sizeof(*hl_ids) * size); size <<= 1; } + if ((chars[7][0] && chars[1][0] && !chars[0][0]) + || (chars[1][0] && chars[3][0] && !chars[2][0]) + || (chars[3][0] && chars[5][0] && !chars[4][0]) + || (chars[5][0] && chars[7][0] && !chars[6][0])) { + api_set_error(err, kErrorTypeValidation, + "corner between used edges must be specified"); + } } else if (style.type == kObjectTypeString) { String str = style.data.string; if (str.size == 0 || strequal(str.data, "none")) { @@ -1859,6 +1845,15 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) if (strequal(str.data, defaults[i].name)) { memcpy(chars, defaults[i].chars, sizeof(defaults[i].chars)); memset(hl_ids, 0, 8 * sizeof(*hl_ids)); + if (defaults[i].shadow_color) { + int hl_blend = SYN_GROUP_STATIC("FloatShadow"); + int hl_through = SYN_GROUP_STATIC("FloatShadowThrough"); + hl_ids[2] = hl_through; + hl_ids[3] = hl_blend; + hl_ids[4] = hl_blend; + hl_ids[5] = hl_blend; + hl_ids[6] = hl_through; + } return; } } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 787b6addc9..b5e53beabe 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1333,7 +1333,8 @@ void nvim_chan_send(Integer chan, String data, Error *err) return; } - channel_send((uint64_t)chan, data.data, data.size, &error); + channel_send((uint64_t)chan, data.data, data.size, + false, &error); if (error) { api_set_error(err, kErrorTypeValidation, "%s", error); } @@ -1421,6 +1422,7 @@ void nvim_chan_send(Integer chan, String data, Error *err) /// - "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 /// eight. The array will specifify the eight chars building up the border /// in a clockwise fashion starting with the top-left corner. As, an @@ -1431,6 +1433,9 @@ void nvim_chan_send(Integer chan, String data, Error *err) /// [ "/", "-", "\\", "|" ] /// 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` /// when not defined. It could also be specified by character: /// [ {"+", "MyCorner"}, {"x", "MyBorder"} ] @@ -2708,6 +2713,7 @@ Dictionary nvim__stats(void) Dictionary rv = ARRAY_DICT_INIT; PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync)); PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); + PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount)); return rv; } @@ -2880,19 +2886,6 @@ void nvim__screenshot(String path) ui_call_screenshot(path); } -static void clear_provider(DecorProvider *p) -{ - if (p == NULL) { - return; - } - NLUA_CLEAR_REF(p->redraw_start); - NLUA_CLEAR_REF(p->redraw_buf); - NLUA_CLEAR_REF(p->redraw_win); - NLUA_CLEAR_REF(p->redraw_line); - NLUA_CLEAR_REF(p->redraw_end); - p->active = false; -} - /// Set or change decoration provider for a namespace /// /// This is a very general purpose interface for having lua callbacks @@ -2937,8 +2930,8 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Error *err) FUNC_API_SINCE(7) FUNC_API_LUA_ONLY { - DecorProvider *p = get_provider((NS)ns_id, true); - clear_provider(p); + DecorProvider *p = get_decor_provider((NS)ns_id, true); + decor_provider_clear(p); // regardless of what happens, it seems good idea to redraw redraw_all_later(NOT_VALID); // TODO(bfredl): too soon? @@ -2981,5 +2974,5 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, p->active = true; return; error: - clear_provider(p); + decor_provider_clear(p); } diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index f41068ea70..7e4dee3d34 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -31,7 +31,9 @@ #define CSI 0x9b // Control Sequence Introducer #define CSI_STR "\233" #define DCS 0x90 // Device Control String +#define DCS_STR "\033P" #define STERM 0x9c // String Terminator +#define STERM_STR "\033\\" #define POUND 0xA3 diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index f71075ae74..145f6f5601 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1621,13 +1621,21 @@ static bool apply_autocmds_group(event_T event, ap->last = false; } ap->last = true; - check_lnums(true); // make sure cursor and topline are valid + + if (nesting == 1) { + // make sure cursor and topline are valid + check_lnums(true); + } // Execute the autocmd. The `getnextac` callback handles iteration. do_cmdline(NULL, getnextac, (void *)&patcmd, DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT); - reset_lnums(); // restore cursor and topline, unless they were changed + if (nesting == 1) { + // restore cursor and topline, unless they were changed + reset_lnums(); + } + if (eap != NULL) { (void)set_cmdarg(NULL, save_cmdarg); diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index c7ec3a456c..c98f2786c2 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -5489,20 +5489,20 @@ bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp) int buf_signcols(buf_T *buf) { if (buf->b_signcols_max == -1) { - signlist_T *sign; // a sign in the signlist + sign_entry_T *sign; // a sign in the sign list buf->b_signcols_max = 0; int linesum = 0; linenr_T curline = 0; FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->lnum > curline) { + if (sign->se_lnum > curline) { if (linesum > buf->b_signcols_max) { buf->b_signcols_max = linesum; } - curline = sign->lnum; + curline = sign->se_lnum; linesum = 0; } - if (sign->has_text_or_icon) { + if (sign->se_has_text_or_icon) { linesum++; } } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index b36b7beab8..dd24db910e 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -110,7 +110,7 @@ typedef uint16_t disptick_T; // display tick type #include "nvim/regexp_defs.h" // for synstate_T (needs reg_extmatch_T, win_T, buf_T) #include "nvim/syntax_defs.h" -// for signlist_T +// for sign_entry_T #include "nvim/sign_defs.h" #include "nvim/os/fs_defs.h" // for FileID @@ -848,7 +848,7 @@ struct file_buffer { // normally points to this, but some windows // may use a different synblock_T. - signlist_T *b_signlist; // list of signs to draw + sign_entry_T *b_signlist; // list of placed signs int b_signcols_max; // cached maximum number of sign columns int b_signcols; // last calculated number of sign columns @@ -1085,6 +1085,7 @@ typedef struct { bool focusable; WinStyle style; bool border; + bool shadow; schar_T border_chars[8]; int border_hl_ids[8]; int border_attr[8]; @@ -1266,7 +1267,7 @@ struct window_S { int w_height_request; int w_width_request; - int w_border_adj; + int w_border_adj[4]; // top, right, bottom, left // outer size of window grid, including border int w_height_outer; int w_width_outer; diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 97562eace6..5c573530d1 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -176,7 +176,7 @@ void buf_updates_unload(buf_T *buf, bool can_reload) if (keep) { kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i); } else { - free_update_callbacks(cb); + buffer_update_callbacks_free(cb); } } kv_size(buf->update_callbacks) = j; @@ -290,7 +290,7 @@ void buf_updates_send_changes(buf_T *buf, textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { - free_update_callbacks(cb); + buffer_update_callbacks_free(cb); keep = false; } api_free_object(res); @@ -342,7 +342,7 @@ void buf_updates_send_splice( textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { - free_update_callbacks(cb); + buffer_update_callbacks_free(cb); keep = false; } } @@ -378,7 +378,7 @@ void buf_updates_changedtick(buf_T *buf) textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { - free_update_callbacks(cb); + buffer_update_callbacks_free(cb); keep = false; } api_free_object(res); @@ -406,8 +406,11 @@ void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id) rpc_send_event(channel_id, "nvim_buf_changedtick_event", args); } -static void free_update_callbacks(BufUpdateCallbacks cb) +void buffer_update_callbacks_free(BufUpdateCallbacks cb) { api_free_luaref(cb.on_lines); + api_free_luaref(cb.on_bytes); api_free_luaref(cb.on_changedtick); + api_free_luaref(cb.on_reload); + api_free_luaref(cb.on_detach); } diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 7a08ba58d0..22eb31513d 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -499,48 +499,54 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, } /// @param data will be consumed -size_t channel_send(uint64_t id, char *data, size_t len, const char **error) +size_t channel_send(uint64_t id, char *data, size_t len, + bool data_owned, const char **error) { Channel *chan = find_channel(id); + size_t written = 0; if (!chan) { *error = _(e_invchan); - goto err; + goto retfree; } if (chan->streamtype == kChannelStreamStderr) { if (chan->stream.err.closed) { *error = _("Can't send data to closed stream"); - goto err; + goto retfree; } // unbuffered write - size_t written = fwrite(data, len, 1, stderr); - xfree(data); - return len * written; + written = len * fwrite(data, len, 1, stderr); + goto retfree; } if (chan->streamtype == kChannelStreamInternal && chan->term) { terminal_receive(chan->term, data, len); - return len; + written = len; + goto retfree; } Stream *in = channel_instream(chan); if (in->closed) { *error = _("Can't send data to closed stream"); - goto err; + goto retfree; } if (chan->is_rpc) { *error = _("Can't send raw data to rpc channel"); - goto err; + goto retfree; } - WBuffer *buf = wstream_new_buffer(data, len, 1, xfree); + // write can be delayed indefinitely, so always use an allocated buffer + WBuffer *buf = wstream_new_buffer(data_owned ? data : xmemdup(data, len), + len, 1, xfree); return wstream_write(in, buf) ? len : 0; -err: - xfree(data); - return 0; +retfree: + if (data_owned) { + xfree(data); + } + return written; } /// Convert binary byte array to a readfile()-style list diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index e16598e7d2..52a48ae6fb 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -2,6 +2,7 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include "nvim/vim.h" +#include "nvim/lua/executor.h" #include "nvim/extmark.h" #include "nvim/decoration.h" #include "nvim/screen.h" @@ -365,3 +366,52 @@ void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, priority); } + + +DecorProvider *get_decor_provider(NS ns_id, bool force) +{ + ssize_t i; + for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) { + DecorProvider *item = &kv_A(decor_providers, i); + if (item->ns_id == ns_id) { + return item; + } else if (item->ns_id > ns_id) { + break; + } + } + + if (!force) { + return NULL; + } + + for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) { + // allocates if needed: + (void)kv_a(decor_providers, (size_t)j+1); + kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j); + } + DecorProvider *item = &kv_a(decor_providers, (size_t)i); + *item = DECORATION_PROVIDER_INIT(ns_id); + + return item; +} + +void decor_provider_clear(DecorProvider *p) +{ + if (p == NULL) { + return; + } + NLUA_CLEAR_REF(p->redraw_start); + NLUA_CLEAR_REF(p->redraw_buf); + NLUA_CLEAR_REF(p->redraw_win); + NLUA_CLEAR_REF(p->redraw_line); + NLUA_CLEAR_REF(p->redraw_end); + p->active = false; +} + +void decor_free_all_mem(void) +{ + for (size_t i = 0; i < kv_size(decor_providers); i++) { + decor_provider_clear(&kv_A(decor_providers, i)); + } + kv_destroy(decor_providers); +} diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 49bd170bcd..ea13052f25 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -2319,7 +2319,11 @@ static int ins_compl_add(char_u *const str, int len, const Direction dir = (cdir == kDirectionNotSet ? compl_direction : cdir); int flags = flags_arg; - os_breakcheck(); + if (flags & CP_FAST) { + fast_breakcheck(); + } else { + os_breakcheck(); + } #define FREE_CPTEXT(cptext, cptext_allocated) \ do { \ if (cptext != NULL && cptext_allocated) { \ @@ -2523,7 +2527,8 @@ static void ins_compl_add_matches(int num_matches, char_u **matches, int icase) for (int i = 0; i < num_matches && add_r != FAIL; i++) { if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir, - icase ? CP_ICASE : 0, false)) == OK) { + CP_FAST | (icase ? CP_ICASE : 0), + false)) == OK) { // If dir was BACKWARD then honor it just once. dir = FORWARD; } @@ -2598,7 +2603,7 @@ void set_completion(colnr_T startcol, list_T *list) flags |= CP_ICASE; } if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, - flags, false) != OK) { + flags | CP_FAST, false) != OK) { return; } @@ -3318,8 +3323,8 @@ static int ins_compl_bs(void) // allow the word to be deleted, we won't match everything. // Respect the 'backspace' option. if ((int)(p - line) - (int)compl_col < 0 - || ((int)(p - line) - (int)compl_col == 0 - && ctrl_x_mode != CTRL_X_OMNI) || ctrl_x_mode == CTRL_X_EVAL + || ((int)(p - line) - (int)compl_col == 0 && ctrl_x_mode != CTRL_X_OMNI) + || ctrl_x_mode == CTRL_X_EVAL || (!can_bs(BS_START) && (int)(p - line) - (int)compl_col - compl_length < 0)) { return K_BS; @@ -3934,7 +3939,7 @@ static void ins_compl_add_list(list_T *const list) // Go through the List with matches and add each of them. TV_LIST_ITER(list, li, { - if (ins_compl_add_tv(TV_LIST_ITEM_TV(li), dir) == OK) { + if (ins_compl_add_tv(TV_LIST_ITEM_TV(li), dir, true) == OK) { // If dir was BACKWARD then honor it just once. dir = FORWARD; } else if (did_emsg) { @@ -3973,17 +3978,18 @@ static void ins_compl_add_dict(dict_T *dict) /// /// @param[in] tv Object to get matches from. /// @param[in] dir Completion direction. +/// @param[in] fast use fast_breakcheck() instead of os_breakcheck(). /// /// @return NOTDONE if the given string is already in the list of completions, /// otherwise it is added to the list and OK is returned. FAIL will be /// returned in case of error. -int ins_compl_add_tv(typval_T *const tv, const Direction dir) +int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast) FUNC_ATTR_NONNULL_ALL { const char *word; bool dup = false; bool empty = false; - int flags = 0; + int flags = fast ? CP_FAST : 0; char *(cptext[CPT_COUNT]); typval_T user_data; @@ -8787,10 +8793,6 @@ static bool ins_tab(void) getvcol(curwin, &fpos, &vcol, NULL, NULL); getvcol(curwin, cursor, &want_vcol, NULL, NULL); - // save start of changed region for extmark_splice - int start_row = fpos.lnum; - colnr_T start_col = fpos.col; - // Use as many TABs as possible. Beware of 'breakindent', 'showbreak' // and 'linebreak' adding extra virtual columns. while (ascii_iswhite(*ptr)) { @@ -8841,8 +8843,8 @@ static bool ins_tab(void) } } if (!(State & VREPLACE_FLAG)) { - extmark_splice_cols(curbuf, start_row - 1, start_col, - cursor->col - start_col, fpos.col - start_col, + extmark_splice_cols(curbuf, fpos.lnum - 1, change_col, + cursor->col - change_col, fpos.col - change_col, kExtmarkUndo); } } diff --git a/src/nvim/edit.h b/src/nvim/edit.h index 09f401ee82..ef5dce738a 100644 --- a/src/nvim/edit.h +++ b/src/nvim/edit.h @@ -19,6 +19,7 @@ typedef enum { CP_CONT_S_IPOS = 4, // use CONT_S_IPOS for compl_cont_status CP_EQUAL = 8, // ins_compl_equal() always returns true CP_ICASE = 16, // ins_compl_equal ignores case + CP_FAST = 32, // use fast_breakcheck instead of os_breakcheck } cp_flags_T; typedef int (*IndentGetter)(void); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9c3941b0fd..05d429c7d5 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -229,6 +229,7 @@ static struct vimvar { // Neovim VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), + VV(VV__NULL_STRING, "_null_string", VAR_STRING, VV_RO), VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), @@ -916,6 +917,17 @@ varnumber_T eval_to_number(char_u *expr) return retval; } +// Top level evaluation function. +// Returns an allocated typval_T with the result. +// Returns NULL when there is an error. +typval_T *eval_expr(char_u *arg) +{ + typval_T *tv = xmalloc(sizeof(*tv)); + if (eval0(arg, tv, NULL, true) == FAIL) { + XFREE_CLEAR(tv); + } + return tv; +} /* * Prepare v: variable "idx" to be used. @@ -3129,21 +3141,6 @@ static int pattern_match(char_u *pat, char_u *text, bool ic) return matches; } -/* - * types for expressions. - */ -typedef enum { - TYPE_UNKNOWN = 0, - TYPE_EQUAL, // == - TYPE_NEQUAL, // != - TYPE_GREATER, // > - TYPE_GEQUAL, // >= - TYPE_SMALLER, // < - TYPE_SEQUAL, // <= - TYPE_MATCH, // =~ - TYPE_NOMATCH, // !~ -} exptype_T; - // TODO(ZyX-I): move to eval/expressions /* @@ -3420,11 +3417,8 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) { typval_T var2; char_u *p; - int i; - exptype_T type = TYPE_UNKNOWN; - bool type_is = false; // true for "is" and "isnot" + exprtype_T type = EXPR_UNKNOWN; int len = 2; - varnumber_T n1, n2; bool ic; /* @@ -3435,35 +3429,42 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) p = *arg; switch (p[0]) { - case '=': if (p[1] == '=') - type = TYPE_EQUAL; - else if (p[1] == '~') - type = TYPE_MATCH; + case '=': + if (p[1] == '=') { + type = EXPR_EQUAL; + } else if (p[1] == '~') { + type = EXPR_MATCH; + } break; - case '!': if (p[1] == '=') - type = TYPE_NEQUAL; - else if (p[1] == '~') - type = TYPE_NOMATCH; + case '!': + if (p[1] == '=') { + type = EXPR_NEQUAL; + } else if (p[1] == '~') { + type = EXPR_NOMATCH; + } break; - case '>': if (p[1] != '=') { - type = TYPE_GREATER; + case '>': + if (p[1] != '=') { + type = EXPR_GREATER; len = 1; - } else - type = TYPE_GEQUAL; + } else { + type = EXPR_GEQUAL; + } break; - case '<': if (p[1] != '=') { - type = TYPE_SMALLER; + case '<': + if (p[1] != '=') { + type = EXPR_SMALLER; len = 1; - } else - type = TYPE_SEQUAL; + } else { + type = EXPR_SEQUAL; + } break; case 'i': if (p[1] == 's') { if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') { len = 5; } if (!isalnum(p[len]) && p[len] != '_') { - type = len == 2 ? TYPE_EQUAL : TYPE_NEQUAL; - type_is = true; + type = len == 2 ? EXPR_IS : EXPR_ISNOT; } } break; @@ -3472,7 +3473,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) /* * If there is a comparative operator, use it. */ - if (type != TYPE_UNKNOWN) { + if (type != EXPR_UNKNOWN) { // extra question mark appended: ignore case if (p[len] == '?') { ic = true; @@ -3490,173 +3491,11 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) tv_clear(rettv); return FAIL; } - if (evaluate) { - if (type_is && rettv->v_type != var2.v_type) { - /* For "is" a different type always means FALSE, for "notis" - * it means TRUE. */ - n1 = (type == TYPE_NEQUAL); - } else if (rettv->v_type == VAR_LIST || var2.v_type == VAR_LIST) { - if (type_is) { - n1 = (rettv->v_type == var2.v_type - && rettv->vval.v_list == var2.vval.v_list); - if (type == TYPE_NEQUAL) - n1 = !n1; - } else if (rettv->v_type != var2.v_type - || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) { - if (rettv->v_type != var2.v_type) { - EMSG(_("E691: Can only compare List with List")); - } else { - EMSG(_("E692: Invalid operation for List")); - } - tv_clear(rettv); - tv_clear(&var2); - return FAIL; - } else { - // Compare two Lists for being equal or unequal. - n1 = tv_list_equal(rettv->vval.v_list, var2.vval.v_list, ic, false); - if (type == TYPE_NEQUAL) { - n1 = !n1; - } - } - } else if (rettv->v_type == VAR_DICT || var2.v_type == VAR_DICT) { - if (type_is) { - n1 = (rettv->v_type == var2.v_type - && rettv->vval.v_dict == var2.vval.v_dict); - if (type == TYPE_NEQUAL) - n1 = !n1; - } else if (rettv->v_type != var2.v_type - || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) { - if (rettv->v_type != var2.v_type) - EMSG(_("E735: Can only compare Dictionary with Dictionary")); - else - EMSG(_("E736: Invalid operation for Dictionary")); - tv_clear(rettv); - tv_clear(&var2); - return FAIL; - } else { - // Compare two Dictionaries for being equal or unequal. - n1 = tv_dict_equal(rettv->vval.v_dict, var2.vval.v_dict, - ic, false); - if (type == TYPE_NEQUAL) { - n1 = !n1; - } - } - } else if (tv_is_func(*rettv) || tv_is_func(var2)) { - if (type != TYPE_EQUAL && type != TYPE_NEQUAL) { - EMSG(_("E694: Invalid operation for Funcrefs")); - tv_clear(rettv); - tv_clear(&var2); - return FAIL; - } - if ((rettv->v_type == VAR_PARTIAL - && rettv->vval.v_partial == NULL) - || (var2.v_type == VAR_PARTIAL - && var2.vval.v_partial == NULL)) { - // when a partial is NULL assume not equal - n1 = false; - } else if (type_is) { - if (rettv->v_type == VAR_FUNC && var2.v_type == VAR_FUNC) { - // strings are considered the same if their value is - // the same - n1 = tv_equal(rettv, &var2, ic, false); - } else if (rettv->v_type == VAR_PARTIAL - && var2.v_type == VAR_PARTIAL) { - n1 = (rettv->vval.v_partial == var2.vval.v_partial); - } else { - n1 = false; - } - } else { - n1 = tv_equal(rettv, &var2, ic, false); - } - if (type == TYPE_NEQUAL) { - n1 = !n1; - } - } - /* - * If one of the two variables is a float, compare as a float. - * When using "=~" or "!~", always compare as string. - */ - else if ((rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT) - && type != TYPE_MATCH && type != TYPE_NOMATCH) { - float_T f1, f2; + const int ret = typval_compare(rettv, &var2, type, ic); - if (rettv->v_type == VAR_FLOAT) { - f1 = rettv->vval.v_float; - } else { - f1 = tv_get_number(rettv); - } - if (var2.v_type == VAR_FLOAT) { - f2 = var2.vval.v_float; - } else { - f2 = tv_get_number(&var2); - } - n1 = false; - switch (type) { - case TYPE_EQUAL: n1 = (f1 == f2); break; - case TYPE_NEQUAL: n1 = (f1 != f2); break; - case TYPE_GREATER: n1 = (f1 > f2); break; - case TYPE_GEQUAL: n1 = (f1 >= f2); break; - case TYPE_SMALLER: n1 = (f1 < f2); break; - case TYPE_SEQUAL: n1 = (f1 <= f2); break; - case TYPE_UNKNOWN: - case TYPE_MATCH: - case TYPE_NOMATCH: break; - } - } - /* - * If one of the two variables is a number, compare as a number. - * When using "=~" or "!~", always compare as string. - */ - else if ((rettv->v_type == VAR_NUMBER || var2.v_type == VAR_NUMBER) - && type != TYPE_MATCH && type != TYPE_NOMATCH) { - n1 = tv_get_number(rettv); - n2 = tv_get_number(&var2); - switch (type) { - case TYPE_EQUAL: n1 = (n1 == n2); break; - case TYPE_NEQUAL: n1 = (n1 != n2); break; - case TYPE_GREATER: n1 = (n1 > n2); break; - case TYPE_GEQUAL: n1 = (n1 >= n2); break; - case TYPE_SMALLER: n1 = (n1 < n2); break; - case TYPE_SEQUAL: n1 = (n1 <= n2); break; - case TYPE_UNKNOWN: - case TYPE_MATCH: - case TYPE_NOMATCH: break; - } - } else { - char buf1[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *const s1 = tv_get_string_buf(rettv, buf1); - const char *const s2 = tv_get_string_buf(&var2, buf2); - if (type != TYPE_MATCH && type != TYPE_NOMATCH) { - i = mb_strcmp_ic(ic, s1, s2); - } else { - i = 0; - } - n1 = false; - switch (type) { - case TYPE_EQUAL: n1 = (i == 0); break; - case TYPE_NEQUAL: n1 = (i != 0); break; - case TYPE_GREATER: n1 = (i > 0); break; - case TYPE_GEQUAL: n1 = (i >= 0); break; - case TYPE_SMALLER: n1 = (i < 0); break; - case TYPE_SEQUAL: n1 = (i <= 0); break; - - case TYPE_MATCH: - case TYPE_NOMATCH: { - n1 = pattern_match((char_u *)s2, (char_u *)s1, ic); - if (type == TYPE_NOMATCH) { - n1 = !n1; - } - break; - } - case TYPE_UNKNOWN: break; // Avoid gcc warning. - } - } - tv_clear(rettv); tv_clear(&var2); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = n1; + return ret; } } @@ -5981,6 +5820,35 @@ static void assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, } } +int assert_beeps(typval_T *argvars, bool no_beep) + FUNC_ATTR_NONNULL_ALL +{ + const char *const cmd = tv_get_string_chk(&argvars[0]); + int ret = 0; + + called_vim_beep = false; + suppress_errthrow = true; + emsg_silent = false; + do_cmdline_cmd(cmd); + if (no_beep ? called_vim_beep : !called_vim_beep) { + garray_T ga; + prepare_assert_error(&ga); + if (no_beep) { + ga_concat(&ga, (const char_u *)"command did beep: "); + } else { + ga_concat(&ga, (const char_u *)"command did not beep: "); + } + ga_concat(&ga, (const char_u *)cmd); + assert_error(&ga); + ga_clear(&ga); + ret = 1; + } + + suppress_errthrow = false; + emsg_on_display = false; + return ret; +} + int assert_fails(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { @@ -6234,6 +6102,7 @@ void common_function(typval_T *argvars, typval_T *rettv, // function(dict.MyFunc, [arg]) arg_pt = argvars[0].vval.v_partial; s = partial_name(arg_pt); + // TODO(bfredl): do the entire nlua_is_table_from_lua dance } else { // function('MyFunc', [arg], dict) s = (char_u *)tv_get_string(&argvars[0]); @@ -7333,7 +7202,6 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) char_u *name = nlua_register_table_as_callable(arg); if (name != NULL) { - func_ref(name); callback->data.funcref = vim_strsave(name); callback->type = kCallbackFuncref; } else { @@ -7971,8 +7839,8 @@ int get_id_len(const char **const arg) */ int get_name_len(const char **const arg, char **alias, - int evaluate, - int verbose) + bool evaluate, + bool verbose) { int len; @@ -8457,10 +8325,8 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg) return oldval; } -/* - * Get the value of internal variable "name". - * Return OK or FAIL. - */ +// Get the value of internal variable "name". +// Return OK or FAIL. If OK is returned "rettv" must be cleared. int get_var_tv( const char *name, int len, // length of "name" @@ -10717,3 +10583,209 @@ bool invoke_prompt_interrupt(void) tv_clear(&rettv); return true; } + +// Compare "typ1" and "typ2". Put the result in "typ1". +int typval_compare( + typval_T *typ1, // first operand + typval_T *typ2, // second operand + exprtype_T type, // operator + bool ic // ignore case +) + FUNC_ATTR_NONNULL_ALL +{ + varnumber_T n1, n2; + const bool type_is = type == EXPR_IS || type == EXPR_ISNOT; + + if (type_is && typ1->v_type != typ2->v_type) { + // For "is" a different type always means false, for "notis" + // it means true. + n1 = type == EXPR_ISNOT; + } else if (typ1->v_type == VAR_LIST || typ2->v_type == VAR_LIST) { + if (type_is) { + n1 = typ1->v_type == typ2->v_type + && typ1->vval.v_list == typ2->vval.v_list; + if (type == EXPR_ISNOT) { + n1 = !n1; + } + } else if (typ1->v_type != typ2->v_type + || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) { + if (typ1->v_type != typ2->v_type) { + EMSG(_("E691: Can only compare List with List")); + } else { + EMSG(_("E692: Invalid operation for List")); + } + tv_clear(typ1); + return FAIL; + } else { + // Compare two Lists for being equal or unequal. + n1 = tv_list_equal(typ1->vval.v_list, typ2->vval.v_list, ic, false); + if (type == EXPR_NEQUAL) { + n1 = !n1; + } + } + } else if (typ1->v_type == VAR_DICT || typ2->v_type == VAR_DICT) { + if (type_is) { + n1 = typ1->v_type == typ2->v_type + && typ1->vval.v_dict == typ2->vval.v_dict; + if (type == EXPR_ISNOT) { + n1 = !n1; + } + } else if (typ1->v_type != typ2->v_type + || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) { + if (typ1->v_type != typ2->v_type) { + EMSG(_("E735: Can only compare Dictionary with Dictionary")); + } else { + EMSG(_("E736: Invalid operation for Dictionary")); + } + tv_clear(typ1); + return FAIL; + } else { + // Compare two Dictionaries for being equal or unequal. + n1 = tv_dict_equal(typ1->vval.v_dict, typ2->vval.v_dict, ic, false); + if (type == EXPR_NEQUAL) { + n1 = !n1; + } + } + } else if (tv_is_func(*typ1) || tv_is_func(*typ2)) { + if (type != EXPR_EQUAL && type != EXPR_NEQUAL + && type != EXPR_IS && type != EXPR_ISNOT) { + EMSG(_("E694: Invalid operation for Funcrefs")); + tv_clear(typ1); + return FAIL; + } + if ((typ1->v_type == VAR_PARTIAL && typ1->vval.v_partial == NULL) + || (typ2->v_type == VAR_PARTIAL && typ2->vval.v_partial == NULL)) { + // when a partial is NULL assume not equal + n1 = false; + } else if (type_is) { + if (typ1->v_type == VAR_FUNC && typ2->v_type == VAR_FUNC) { + // strings are considered the same if their value is + // the same + n1 = tv_equal(typ1, typ2, ic, false); + } else if (typ1->v_type == VAR_PARTIAL && typ2->v_type == VAR_PARTIAL) { + n1 = typ1->vval.v_partial == typ2->vval.v_partial; + } else { + n1 = false; + } + } else { + n1 = tv_equal(typ1, typ2, ic, false); + } + if (type == EXPR_NEQUAL || type == EXPR_ISNOT) { + n1 = !n1; + } + } else if ((typ1->v_type == VAR_FLOAT || typ2->v_type == VAR_FLOAT) + && type != EXPR_MATCH && type != EXPR_NOMATCH) { + // If one of the two variables is a float, compare as a float. + // When using "=~" or "!~", always compare as string. + const float_T f1 = tv_get_float(typ1); + const float_T f2 = tv_get_float(typ2); + n1 = false; + switch (type) { + case EXPR_IS: + case EXPR_EQUAL: n1 = f1 == f2; break; + case EXPR_ISNOT: + case EXPR_NEQUAL: n1 = f1 != f2; break; + case EXPR_GREATER: n1 = f1 > f2; break; + case EXPR_GEQUAL: n1 = f1 >= f2; break; + case EXPR_SMALLER: n1 = f1 < f2; break; + case EXPR_SEQUAL: n1 = f1 <= f2; break; + case EXPR_UNKNOWN: + case EXPR_MATCH: + case EXPR_NOMATCH: break; // avoid gcc warning + } + } else if ((typ1->v_type == VAR_NUMBER || typ2->v_type == VAR_NUMBER) + && type != EXPR_MATCH && type != EXPR_NOMATCH) { + // If one of the two variables is a number, compare as a number. + // When using "=~" or "!~", always compare as string. + n1 = tv_get_number(typ1); + n2 = tv_get_number(typ2); + switch (type) { + case EXPR_IS: + case EXPR_EQUAL: n1 = n1 == n2; break; + case EXPR_ISNOT: + case EXPR_NEQUAL: n1 = n1 != n2; break; + case EXPR_GREATER: n1 = n1 > n2; break; + case EXPR_GEQUAL: n1 = n1 >= n2; break; + case EXPR_SMALLER: n1 = n1 < n2; break; + case EXPR_SEQUAL: n1 = n1 <= n2; break; + case EXPR_UNKNOWN: + case EXPR_MATCH: + case EXPR_NOMATCH: break; // avoid gcc warning + } + } else { + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char *const s1 = tv_get_string_buf(typ1, buf1); + const char *const s2 = tv_get_string_buf(typ2, buf2); + int i; + if (type != EXPR_MATCH && type != EXPR_NOMATCH) { + i = mb_strcmp_ic(ic, s1, s2); + } else { + i = 0; + } + n1 = false; + switch (type) { + case EXPR_IS: + case EXPR_EQUAL: n1 = i == 0; break; + case EXPR_ISNOT: + case EXPR_NEQUAL: n1 = i != 0; break; + case EXPR_GREATER: n1 = i > 0; break; + case EXPR_GEQUAL: n1 = i >= 0; break; + case EXPR_SMALLER: n1 = i < 0; break; + case EXPR_SEQUAL: n1 = i <= 0; break; + + case EXPR_MATCH: + case EXPR_NOMATCH: + n1 = pattern_match((char_u *)s2, (char_u *)s1, ic); + if (type == EXPR_NOMATCH) { + n1 = !n1; + } + break; + case EXPR_UNKNOWN: break; // avoid gcc warning + } + } + tv_clear(typ1); + typ1->v_type = VAR_NUMBER; + typ1->vval.v_number = n1; + return OK; +} + +char *typval_tostring(typval_T *arg) +{ + if (arg == NULL) { + return xstrdup("(does not exist)"); + } + return encode_tv2string(arg, NULL); +} + +bool var_exists(const char *var) + FUNC_ATTR_NONNULL_ALL +{ + char *tofree; + bool n = false; + + // get_name_len() takes care of expanding curly braces + const char *name = var; + const int len = get_name_len((const char **)&var, &tofree, true, false); + if (len > 0) { + typval_T tv; + + if (tofree != NULL) { + name = tofree; + } + n = get_var_tv(name, len, &tv, NULL, false, true) == OK; + if (n) { + // Handle d.key, l[idx], f(expr). + n = handle_subscript(&var, &tv, true, false) == OK; + if (n) { + tv_clear(&tv); + } + } + } + if (*var != NUL) { + n = false; + } + + xfree(tofree); + return n; +} diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 4f03d5d259..3da4bb8655 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -158,6 +158,7 @@ typedef enum { // Neovim VV_STDERR, VV_MSGPACK_TYPES, + VV__NULL_STRING, // String with NULL value. For test purposes only. VV__NULL_LIST, // List with NULL value. For test purposes only. VV__NULL_DICT, // Dictionary with NULL value. For test purposes only. VV_LUA, @@ -227,6 +228,21 @@ typedef enum ASSERT_OTHER, } assert_type_T; +/// types for expressions. +typedef enum { + EXPR_UNKNOWN = 0, + EXPR_EQUAL, ///< == + EXPR_NEQUAL, ///< != + EXPR_GREATER, ///< > + EXPR_GEQUAL, ///< >= + EXPR_SMALLER, ///< < + EXPR_SEQUAL, ///< <= + EXPR_MATCH, ///< =~ + EXPR_NOMATCH, ///< !~ + EXPR_IS, ///< is + EXPR_ISNOT, ///< isnot +} exprtype_T; + /// Type for dict_list function typedef enum { kDictListKeys, ///< List dictionary keys. diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 72168060cc..b10e99fc08 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -26,7 +26,7 @@ return { arglistid={args={0, 2}}, argv={args={0, 2}}, asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc - assert_beeps={args={1, 2}}, + assert_beeps={args={1}}, assert_equal={args={2, 3}}, assert_equalfile={args={2, 3}}, assert_exception={args={1, 2}}, @@ -34,6 +34,7 @@ return { assert_false={args={1, 2}}, assert_inrange={args={3, 4}}, assert_match={args={2, 3}}, + assert_nobeep={args={1}}, assert_notequal={args={2, 3}}, assert_notmatch={args={2, 3}}, assert_report={args=1}, @@ -315,8 +316,10 @@ return { sign_getplaced={args={0, 2}}, sign_jump={args={3, 3}}, sign_place={args={4, 5}}, + sign_placelist={args={1}}, sign_undefine={args={0, 1}}, sign_unplace={args={1, 2}}, + sign_unplacelist={args={1}}, simplify={args=1}, sin={args=1, func="float_op_wrapper", data="&sin"}, sinh={args=1, func="float_op_wrapper", data="&sinh"}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index deeda28571..0d288e2cc2 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -391,28 +391,16 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +// "assert_beeps(cmd [, error])" function static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - const char *const cmd = tv_get_string_chk(&argvars[0]); - garray_T ga; - int ret = 0; - - called_vim_beep = false; - suppress_errthrow = true; - emsg_silent = false; - do_cmdline_cmd(cmd); - if (!called_vim_beep) { - prepare_assert_error(&ga); - ga_concat(&ga, (const char_u *)"command did not beep: "); - ga_concat(&ga, (const char_u *)cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } + rettv->vval.v_number = assert_beeps(argvars, false); +} - suppress_errthrow = false; - emsg_on_display = false; - rettv->vval.v_number = ret; +// "assert_nobeep(cmd [, error])" function +static void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_beeps(argvars, true); } // "assert_equal(expected, actual[, msg])" function @@ -822,6 +810,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + bool owned = false; char_u *func; partial_T *partial = NULL; dict_T *selfdict = NULL; @@ -832,6 +821,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) func = partial_name(partial); } else if (nlua_is_table_from_lua(&argvars[0])) { func = nlua_register_table_as_callable(&argvars[0]); + owned = true; } else { func = (char_u *)tv_get_string(&argvars[0]); } @@ -849,6 +839,9 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) } func_call(func, &argvars[1], partial, selfdict, rettv); + if (owned) { + func_unref(func); + } } /* @@ -923,7 +916,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } uint64_t id = argvars[0].vval.v_number; const char *error = NULL; - rettv->vval.v_number = channel_send(id, input, input_len, &error); + rettv->vval.v_number = channel_send(id, input, input_len, true, &error); if (error) { EMSG(error); } @@ -1105,7 +1098,7 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0); + rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0, false); } /* @@ -2058,7 +2051,6 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int n = false; - int len = 0; const char *p = tv_get_string(&argvars[0]); if (*p == '$') { // Environment variable. @@ -2089,29 +2081,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) n = au_exists(p + 1); } } else { // Internal variable. - typval_T tv; - - // get_name_len() takes care of expanding curly braces - const char *name = p; - char *tofree; - len = get_name_len((const char **)&p, &tofree, true, false); - if (len > 0) { - if (tofree != NULL) { - name = tofree; - } - n = (get_var_tv(name, len, &tv, NULL, false, true) == OK); - if (n) { - // Handle d.key, l[idx], f(expr). - n = (handle_subscript(&p, &tv, true, false) == OK); - if (n) { - tv_clear(&tv); - } - } - } - if (*p != NUL) - n = FALSE; - - xfree(tofree); + n = var_exists(p); } rettv->vval.v_number = n; @@ -8868,56 +8838,30 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *name; - dict_T *dict; - char *icon = NULL; - char *linehl = NULL; - char *text = NULL; - char *texthl = NULL; - char *numhl = NULL; - rettv->vval.v_number = -1; + if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { + // Define multiple signs + tv_list_alloc_ret(rettv, kListLenMayKnow); - name = tv_get_string_chk(&argvars[0]); - if (name == NULL) { + sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list); return; } - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } + // Define a single sign + rettv->vval.v_number = -1; - // sign attributes - dict = argvars[1].vval.v_dict; - if (tv_dict_find(dict, "icon", -1) != NULL) { - icon = tv_dict_get_string(dict, "icon", true); - } - if (tv_dict_find(dict, "linehl", -1) != NULL) { - linehl = tv_dict_get_string(dict, "linehl", true); - } - if (tv_dict_find(dict, "text", -1) != NULL) { - text = tv_dict_get_string(dict, "text", true); - } - if (tv_dict_find(dict, "texthl", -1) != NULL) { - texthl = tv_dict_get_string(dict, "texthl", true); - } - if (tv_dict_find(dict, "numhl", -1) != NULL) { - numhl = tv_dict_get_string(dict, "numhl", true); - } + name = tv_get_string_chk(&argvars[0]); + if (name == NULL) { + return; } - if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, - (char_u *)text, (char_u *)texthl, (char_u *)numhl) - == OK) { - rettv->vval.v_number = 0; + if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; } - xfree(icon); - xfree(linehl); - xfree(text); - xfree(texthl); - xfree(numhl); + rettv->vval.v_number = sign_define_from_dict( + name, argvars[1].v_type == VAR_DICT ? argvars[1].vval.v_dict : NULL); } /// "sign_getdefined()" function @@ -9038,83 +8982,44 @@ cleanup: /// "sign_place()" function static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int sign_id; - char_u *group = NULL; - const char *sign_name; - buf_T *buf; - dict_T *dict; - dictitem_T *di; - linenr_T lnum = 0; - int prio = SIGN_DEF_PRIO; - bool notanum = false; + dict_T *dict = NULL; rettv->vval.v_number = -1; - // Sign identifier - sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); - if (notanum) { - return; - } - if (sign_id < 0) { - EMSG(_(e_invarg)); + if (argvars[4].v_type != VAR_UNKNOWN + && (argvars[4].v_type != VAR_DICT + || ((dict = argvars[4].vval.v_dict) == NULL))) { + EMSG(_(e_dictreq)); return; } - // Sign group - const char *group_chk = tv_get_string_chk(&argvars[1]); - if (group_chk == NULL) { - return; - } - if (group_chk[0] == '\0') { - group = NULL; // global sign group - } else { - group = vim_strsave((const char_u *)group_chk); - } + rettv->vval.v_number = sign_place_from_dict( + &argvars[0], &argvars[1], &argvars[2], &argvars[3], dict); +} - // Sign name - sign_name = tv_get_string_chk(&argvars[2]); - if (sign_name == NULL) { - goto cleanup; - } +/// "sign_placelist()" function. Place multiple signs. +static void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int sign_id; - // Buffer to place the sign - buf = get_buf_arg(&argvars[3]); - if (buf == NULL) { - goto cleanup; + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; } - if (argvars[4].v_type != VAR_UNKNOWN) { - if (argvars[4].v_type != VAR_DICT - || ((dict = argvars[4].vval.v_dict) == NULL)) { + // Process the List of sign attributes + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + sign_id = -1; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { + sign_id = sign_place_from_dict( + NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); + } else { EMSG(_(e_dictreq)); - goto cleanup; - } - - // Line number where the sign is to be placed - if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { - lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - goto cleanup; - } - (void)lnum; - lnum = tv_get_lnum(&di->di_tv); } - if ((di = tv_dict_find(dict, "priority", -1)) != NULL) { - // Sign priority - prio = (int)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - goto cleanup; - } - } - } - - if (sign_place(&sign_id, group, (const char_u *)sign_name, buf, lnum, prio) - == OK) { - rettv->vval.v_number = sign_id; - } - -cleanup: - xfree(group); + tv_list_append_number(rettv->vval.v_list, sign_id); + }); } /// "sign_undefine()" function @@ -9122,6 +9027,14 @@ static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *name; + if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { + // Undefine multiple signs + tv_list_alloc_ret(rettv, kListLenMayKnow); + + sign_undefine_multiple(argvars[0].vval.v_list, rettv->vval.v_list); + return; + } + rettv->vval.v_number = -1; if (argvars[0].v_type == VAR_UNKNOWN) { @@ -9144,11 +9057,7 @@ static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "sign_unplace()" function static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - dict_T *dict; - dictitem_T *di; - int sign_id = 0; - buf_T *buf = NULL; - char_u *group = NULL; + dict_T *dict = NULL; rettv->vval.v_number = -1; @@ -9157,46 +9066,38 @@ static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - const char *group_chk = tv_get_string(&argvars[0]); - if (group_chk[0] == '\0') { - group = NULL; // global sign group - } else { - group = vim_strsave((const char_u *)group_chk); - } - if (argvars[1].v_type != VAR_UNKNOWN) { if (argvars[1].v_type != VAR_DICT) { EMSG(_(e_dictreq)); - goto cleanup; + return; } dict = argvars[1].vval.v_dict; - - if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) { - buf = get_buf_arg(&di->di_tv); - if (buf == NULL) { - goto cleanup; - } - } - if (tv_dict_find(dict, "id", -1) != NULL) { - sign_id = tv_dict_get_number(dict, "id"); - } } - if (buf == NULL) { - // Delete the sign in all the buffers - FOR_ALL_BUFFERS(cbuf) { - if (sign_unplace(sign_id, group, cbuf, 0) == OK) { - rettv->vval.v_number = 0; - } - } - } else { - if (sign_unplace(sign_id, group, buf, 0) == OK) { - rettv->vval.v_number = 0; - } + rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict); +} + +/// "sign_unplacelist()" function +static void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int retval; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; } -cleanup: - xfree(group); + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + retval = -1; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { + retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); + } else { + EMSG(_(e_dictreq)); + } + tv_list_append_number(rettv->vval.v_list, retval); + }); } /* diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index fe3d147040..71e4edc667 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -219,6 +219,7 @@ list_T *tv_list_alloc(const ptrdiff_t len) list->lv_used_next = gc_first_list; gc_first_list = list; list_log(list, NULL, (void *)(uintptr_t)len, "alloc"); + list->lua_table_ref = LUA_NOREF; return list; } @@ -302,7 +303,7 @@ void tv_list_free_list(list_T *const l) } list_log(l, NULL, NULL, "freelist"); - nlua_free_typval_list(l); + NLUA_CLEAR_REF(l->lua_table_ref); xfree(l); } @@ -1404,6 +1405,8 @@ dict_T *tv_dict_alloc(void) d->dv_copyID = 0; QUEUE_INIT(&d->watchers); + d->lua_table_ref = LUA_NOREF; + return d; } @@ -1454,7 +1457,7 @@ void tv_dict_free_dict(dict_T *const d) d->dv_used_next->dv_used_prev = d->dv_used_prev; } - nlua_free_typval_dict(d); + NLUA_CLEAR_REF(d->lua_table_ref); xfree(d); } diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 0b1ecb12e2..c02f730431 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -82,7 +82,7 @@ int libuv_process_spawn(LibuvProcess *uvproc) int status; if ((status = uv_spawn(&proc->loop->uv, &uvproc->uv, &uvproc->uvopts))) { - ELOG("uv_spawn failed: %s", uv_strerror(status)); + ELOG("uv_spawn(%s) failed: %s", uvproc->uvopts.file, uv_strerror(status)); if (uvproc->uvopts.env) { os_free_fullenv(uvproc->uvopts.env); } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index d34282419a..b191e8cf67 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -790,7 +790,8 @@ void ex_retab(exarg_T *eap) /* len is actual number of white characters used */ len = num_spaces + num_tabs; old_len = (long)STRLEN(ptr); - new_line = xmalloc(old_len - col + start_col + len + 1); + long new_len = old_len - col + start_col + len + 1; + new_line = xmalloc(new_len); if (start_col > 0) memmove(new_line, ptr, (size_t)start_col); @@ -803,6 +804,8 @@ void ex_retab(exarg_T *eap) if (ml_replace(lnum, new_line, false) == OK) { // "new_line" may have been copied new_line = curbuf->b_ml.ml_line_ptr; + extmark_splice_cols(curbuf, lnum - 1, 0, (colnr_T)old_len, + (colnr_T)new_len - 1, kExtmarkUndo); } if (first_line == 0) { first_line = lnum; diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index cc0ec71627..950a1a436f 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -120,6 +120,9 @@ struct source_cookie { /// batch mode debugging: don't save and restore typeahead. static bool debug_greedy = false; +static char *debug_oldval = NULL; // old and newval for debug expressions +static char *debug_newval = NULL; + /// Debug mode. Repeatedly get Ex commands, until told to continue normal /// execution. void do_debug(char_u *cmd) @@ -166,6 +169,16 @@ void do_debug(char_u *cmd) if (!debug_did_msg) { MSG(_("Entering Debug mode. Type \"cont\" to continue.")); } + if (debug_oldval != NULL) { + smsg(_("Oldval = \"%s\""), debug_oldval); + xfree(debug_oldval); + debug_oldval = NULL; + } + if (debug_newval != NULL) { + smsg(_("Newval = \"%s\""), debug_newval); + xfree(debug_newval); + debug_newval = NULL; + } if (sourcing_name != NULL) { msg(sourcing_name); } @@ -174,7 +187,6 @@ void do_debug(char_u *cmd) } else { smsg(_("cmd: %s"), cmd); } - // Repeat getting a command and executing it. for (;; ) { msg_scroll = true; @@ -514,11 +526,13 @@ bool dbg_check_skipped(exarg_T *eap) /// This is a grow-array of structs. struct debuggy { int dbg_nr; ///< breakpoint number - int dbg_type; ///< DBG_FUNC or DBG_FILE - char_u *dbg_name; ///< function or file name + int dbg_type; ///< DBG_FUNC or DBG_FILE or DBG_EXPR + char_u *dbg_name; ///< function, expression or file name regprog_T *dbg_prog; ///< regexp program linenr_T dbg_lnum; ///< line number in function or file int dbg_forceit; ///< ! used + typval_T *dbg_val; ///< last result of watchexpression + int dbg_level; ///< stored nested level for expr }; static garray_T dbg_breakp = { 0, 0, sizeof(struct debuggy), 4, NULL }; @@ -530,6 +544,7 @@ static int last_breakp = 0; // nr of last defined breakpoint static garray_T prof_ga = { 0, 0, sizeof(struct debuggy), 4, NULL }; #define DBG_FUNC 1 #define DBG_FILE 2 +#define DBG_EXPR 3 /// Parse the arguments of ":profile", ":breakadd" or ":breakdel" and put them @@ -562,6 +577,8 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) } bp->dbg_type = DBG_FILE; here = true; + } else if (gap != &prof_ga && STRNCMP(p, "expr", 4) == 0) { + bp->dbg_type = DBG_EXPR; } else { EMSG2(_(e_invarg2), p); return FAIL; @@ -590,6 +607,9 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) bp->dbg_name = vim_strsave(p); } else if (here) { bp->dbg_name = vim_strsave(curbuf->b_ffname); + } else if (bp->dbg_type == DBG_EXPR) { + bp->dbg_name = vim_strsave(p); + bp->dbg_val = eval_expr(bp->dbg_name); } else { // Expand the file name in the same way as do_source(). This means // doing it twice, so that $DIR/file gets expanded when $DIR is @@ -621,7 +641,6 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) void ex_breakadd(exarg_T *eap) { struct debuggy *bp; - char_u *pat; garray_T *gap; gap = &dbg_breakp; @@ -633,22 +652,28 @@ void ex_breakadd(exarg_T *eap) bp = &DEBUGGY(gap, gap->ga_len); bp->dbg_forceit = eap->forceit; - pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, false); - if (pat != NULL) { - bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING); - xfree(pat); - } - if (pat == NULL || bp->dbg_prog == NULL) { - xfree(bp->dbg_name); - } else { - if (bp->dbg_lnum == 0) { // default line number is 1 - bp->dbg_lnum = 1; + if (bp->dbg_type != DBG_EXPR) { + char_u *pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, false); + if (pat != NULL) { + bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + xfree(pat); } - if (eap->cmdidx != CMD_profile) { - DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp; - debug_tick++; + if (pat == NULL || bp->dbg_prog == NULL) { + xfree(bp->dbg_name); + } else { + if (bp->dbg_lnum == 0) { // default line number is 1 + bp->dbg_lnum = 1; + } + if (eap->cmdidx != CMD_profile) { + DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp; + debug_tick++; + } + gap->ga_len++; } - gap->ga_len++; + } else { + // DBG_EXPR + DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp; + debug_tick++; } } } @@ -691,7 +716,7 @@ void ex_breakdel(exarg_T *eap) todel = 0; del_all = true; } else { - // ":breakdel {func|file} [lnum] {name}" + // ":breakdel {func|file|expr} [lnum] {name}" if (dbg_parsearg(eap->arg, gap) == FAIL) { return; } @@ -716,6 +741,10 @@ void ex_breakdel(exarg_T *eap) } else { while (!GA_EMPTY(gap)) { xfree(DEBUGGY(gap, todel).dbg_name); + if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR + && DEBUGGY(gap, todel).dbg_val != NULL) { + tv_free(DEBUGGY(gap, todel).dbg_val); + } vim_regfree(DEBUGGY(gap, todel).dbg_prog); gap->ga_len--; if (todel < gap->ga_len) { @@ -750,11 +779,15 @@ void ex_breaklist(exarg_T *eap) if (bp->dbg_type == DBG_FILE) { home_replace(NULL, bp->dbg_name, NameBuff, MAXPATHL, true); } - smsg(_("%3d %s %s line %" PRId64), - bp->dbg_nr, - bp->dbg_type == DBG_FUNC ? "func" : "file", - bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, - (int64_t)bp->dbg_lnum); + if (bp->dbg_type != DBG_EXPR) { + smsg(_("%3d %s %s line %" PRId64), + bp->dbg_nr, + bp->dbg_type == DBG_FUNC ? "func" : "file", + bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, + (int64_t)bp->dbg_lnum); + } else { + smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name); + } } } } @@ -814,6 +847,7 @@ debuggy_find( // an already found breakpoint. bp = &DEBUGGY(gap, i); if ((bp->dbg_type == DBG_FILE) == file + && bp->dbg_type != DBG_EXPR && (gap == &prof_ga || (bp->dbg_lnum > after && (lnum == 0 || bp->dbg_lnum < lnum)))) { // Save the value of got_int and reset it. We don't want a @@ -828,6 +862,46 @@ debuggy_find( } } got_int |= prev_got_int; + } else if (bp->dbg_type == DBG_EXPR) { + bool line = false; + + prev_got_int = got_int; + got_int = false; + + typval_T *tv = eval_expr(bp->dbg_name); + if (tv != NULL) { + if (bp->dbg_val == NULL) { + debug_oldval = typval_tostring(NULL); + bp->dbg_val = tv; + debug_newval = typval_tostring(bp->dbg_val); + line = true; + } else { + if (typval_compare(tv, bp->dbg_val, EXPR_IS, false) == OK + && tv->vval.v_number == false) { + line = true; + debug_oldval = typval_tostring(bp->dbg_val); + // Need to evaluate again, typval_compare() overwrites "tv". + typval_T *v = eval_expr(bp->dbg_name); + debug_newval = typval_tostring(v); + tv_free(bp->dbg_val); + bp->dbg_val = v; + } + tv_free(tv); + } + } else if (bp->dbg_val != NULL) { + debug_oldval = typval_tostring(bp->dbg_val); + debug_newval = typval_tostring(NULL); + tv_free(bp->dbg_val); + bp->dbg_val = NULL; + line = true; + } + + if (line) { + lnum = after > 0 ? after : 1; + break; + } + + got_int |= prev_got_int; } } if (name != fname) { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 3aaf171b2c..d1eddfc74f 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -3928,7 +3928,7 @@ static linenr_T get_address(exarg_T *eap, } searchcmdlen = 0; flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG; - if (!do_search(NULL, c, cmd, 1L, flags, NULL)) { + if (!do_search(NULL, c, c, cmd, 1L, flags, NULL)) { curwin->w_cursor = pos; cmd = NULL; goto error; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 38385d19b2..7159b27665 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -275,8 +275,9 @@ static void init_incsearch_state(incsearch_state_T *s) // Return true when 'incsearch' highlighting is to be done. // Sets search_first_line and search_last_line to the address range. -static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s, - int *skiplen, int *patlen) +static bool do_incsearch_highlighting(int firstc, int *search_delim, + incsearch_state_T *s, int *skiplen, + int *patlen) FUNC_ATTR_NONNULL_ALL { char_u *cmd; @@ -303,6 +304,7 @@ static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s, search_last_line = MAXLNUM; if (firstc == '/' || firstc == '?') { + *search_delim = firstc; return true; } if (firstc != ':') { @@ -371,6 +373,7 @@ static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s, p = skipwhite(p); delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++; + *search_delim = delim; end = skip_regexp(p, delim, p_magic, NULL); use_last_pat = end == p && *end == delim; @@ -431,12 +434,14 @@ static void may_do_incsearch_highlighting(int firstc, long count, int skiplen, patlen; char_u next_char; char_u use_last_pat; + int search_delim; // Parsing range may already set the last search pattern. // NOTE: must call restore_last_search_pattern() before returning! save_last_search_pattern(); - if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) { + if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen, + &patlen)) { restore_last_search_pattern(); finish_incsearch_highlighting(false, s, true); return; @@ -490,7 +495,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, ccline.cmdbuff[skiplen + patlen] = NUL; memset(&sia, 0, sizeof(sia)); sia.sa_tm = &tm; - found = do_search(NULL, firstc == ':' ? '/' : firstc, + found = do_search(NULL, firstc == ':' ? '/' : firstc, search_delim, ccline.cmdbuff + skiplen, count, search_flags, &sia); ccline.cmdbuff[skiplen + patlen] = next_char; @@ -581,13 +586,15 @@ static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s) FUNC_ATTR_NONNULL_ALL { int skiplen, patlen; + int search_delim; // Parsing range may already set the last search pattern. // NOTE: must call restore_last_search_pattern() before returning! save_last_search_pattern(); // Add a character from under the cursor for 'incsearch' - if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) { + if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen, + &patlen)) { restore_last_search_pattern(); return FAIL; } @@ -604,7 +611,7 @@ static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s) && !pat_has_uppercase(ccline.cmdbuff + skiplen)) { *c = mb_tolower(*c); } - if (*c == firstc + if (*c == search_delim || vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), *c) != NULL) { // put a backslash before special characters @@ -777,9 +784,18 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) // Redraw the statusline in case it uses the current mode using the mode() // function. - if (!cmd_silent && msg_scrolled == 0 && *p_stl != NUL) { - curwin->w_redr_status = true; - redraw_statuslines(); + if (!cmd_silent && msg_scrolled == 0) { + bool found_one = false; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (*p_stl != NUL || *wp->w_p_stl != NUL) { + wp->w_redr_status = true; + found_one = true; + } + } + if (found_one) { + redraw_statuslines(); + } } did_emsg = false; @@ -1465,13 +1481,14 @@ static int may_do_command_line_next_incsearch(int firstc, long count, bool next_match) FUNC_ATTR_NONNULL_ALL { - int skiplen, patlen; + int skiplen, patlen, search_delim; // Parsing range may already set the last search pattern. // NOTE: must call restore_last_search_pattern() before returning! save_last_search_pattern(); - if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) { + if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen, + &patlen)) { restore_last_search_pattern(); return OK; } @@ -1489,7 +1506,7 @@ static int may_do_command_line_next_incsearch(int firstc, long count, char_u save; - if (firstc == ccline.cmdbuff[skiplen]) { + if (search_delim == ccline.cmdbuff[skiplen]) { pat = last_search_pattern(); skiplen = 0; patlen = (int)STRLEN(pat); diff --git a/src/nvim/globals.h b/src/nvim/globals.h index ffe0357bd8..624b7c93f3 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -469,7 +469,7 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer // Iterate through all the signs placed in a buffer #define FOR_ALL_SIGNS_IN_BUF(buf, sign) \ - for (sign = buf->b_signlist; sign != NULL; sign = sign->next) // NOLINT + for (sign = buf->b_signlist; sign != NULL; sign = sign->se_next) // NOLINT // List of files being edited (global argument list). curwin->w_alist points diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index f03382bea7..79801262cb 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -8,6 +8,7 @@ #include "nvim/highlight_defs.h" #include "nvim/map.h" #include "nvim/message.h" +#include "nvim/option.h" #include "nvim/popupmnu.h" #include "nvim/screen.h" #include "nvim/syntax.h" @@ -151,7 +152,7 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id) { - DecorProvider *p = get_provider(ns_id, true); + DecorProvider *p = get_decor_provider(ns_id, true); if ((attrs.rgb_ae_attr & HL_DEFAULT) && map_has(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id))) { return; @@ -175,7 +176,7 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault) ns_id = ns_hl_active; } - DecorProvider *p = get_provider(ns_id, true); + DecorProvider *p = get_decor_provider(ns_id, true); ColorItem it = map_get(ColorKey, ColorItem)(ns_hl, ColorKey(ns_id, hl_id)); // TODO(bfredl): map_ref true even this? bool valid_cache = it.version >= p->hl_valid; @@ -342,16 +343,24 @@ void update_window_hl(win_T *wp, bool invalid) wp->w_hl_attrs[hlf] = attr; } + wp->w_float_config.shadow = false; if (wp->w_floating && wp->w_float_config.border) { for (int i = 0; i < 8; i++) { int attr = wp->w_hl_attrs[HLF_BORDER]; if (wp->w_float_config.border_hl_ids[i]) { attr = hl_get_ui_attr(HLF_BORDER, wp->w_float_config.border_hl_ids[i], false); + HlAttrs a = syn_attr2entry(attr); + if (a.hl_blend) { + wp->w_float_config.shadow = true; + } } wp->w_float_config.border_attr[i] = attr; } } + + // shadow might cause blending + check_blending(wp); } /// Gets HL_UNDERLINE highlight. diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 83b3729ad3..ce8c9b0d06 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -400,7 +400,6 @@ nlua_pop_typval_table_processing_end: case LUA_TFUNCTION: { LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); state->lua_callable.func_ref = nlua_ref(lstate, -1); - state->lua_callable.table_ref = LUA_NOREF; char_u *name = register_cfunc( &nlua_CFunction_func_call, @@ -412,6 +411,7 @@ nlua_pop_typval_table_processing_end: break; } case LUA_TUSERDATA: { + // TODO(bfredl): check mt.__call and convert to function? nlua_pushref(lstate, nlua_nil_ref); bool is_nil = lua_rawequal(lstate, -2, -1); lua_pop(lstate, 1); diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h index 8601a32418..43a7e06019 100644 --- a/src/nvim/lua/converter.h +++ b/src/nvim/lua/converter.h @@ -11,7 +11,6 @@ typedef struct { LuaRef func_ref; - LuaRef table_ref; } LuaCallable; typedef struct { diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 03d178467b..f99a2dd0fe 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -5,6 +5,7 @@ #include <lualib.h> #include <lauxlib.h> +#include "nvim/assert.h" #include "nvim/version.h" #include "nvim/misc1.h" #include "nvim/getchar.h" @@ -16,8 +17,10 @@ #include "nvim/api/vim.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/vim.h" +#include "nvim/extmark.h" #include "nvim/ex_getln.h" #include "nvim/ex_cmds2.h" +#include "nvim/map.h" #include "nvim/message.h" #include "nvim/memline.h" #include "nvim/buffer_defs.h" @@ -32,9 +35,7 @@ #include "nvim/event/time.h" #include "nvim/event/loop.h" -#ifdef WIN32 #include "nvim/os/os.h" -#endif #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" @@ -63,6 +64,11 @@ typedef struct { } \ } +#if __has_feature(address_sanitizer) + PMap(handle_T) *nlua_ref_markers = NULL; +# define NLUA_TRACK_REFS +#endif + /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -547,6 +553,13 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL static lua_State *nlua_init(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { +#ifdef NLUA_TRACK_REFS + const char *env = os_getenv("NVIM_LUA_NOTRACK"); + if (!env || !*env) { + nlua_ref_markers = pmap_new(handle_T)(); + } +#endif + lua_State *lstate = luaL_newstate(); if (lstate == NULL) { EMSG(_("E970: Failed to initialize lua interpreter")); @@ -554,9 +567,13 @@ static lua_State *nlua_init(void) } luaL_openlibs(lstate); nlua_state_init(lstate); + return lstate; } +// only to be used by nlua_enter and nlua_free_all_mem! +static lua_State *global_lstate = NULL; + /// Enter lua interpreter /// /// Calls nlua_init() if needed. Is responsible for pre-lua call initalization @@ -567,26 +584,39 @@ static lua_State *nlua_init(void) static lua_State *nlua_enter(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT { - static lua_State *global_lstate = NULL; if (global_lstate == NULL) { global_lstate = nlua_init(); } lua_State *const lstate = global_lstate; - // Last used p_rtp value. Must not be dereferenced because value pointed to - // may already be freed. Used to check whether &runtimepath option value - // changed. - static const void *last_p_rtp = NULL; - if (last_p_rtp != (const void *)p_rtp) { - // stack: (empty) - lua_getglobal(lstate, "vim"); - // stack: vim - lua_pop(lstate, 1); - // stack: (empty) - last_p_rtp = (const void *)p_rtp; - } return lstate; } +void nlua_free_all_mem(void) +{ + if (!global_lstate) { + return; + } + lua_State *lstate = global_lstate; + + nlua_unref(lstate, nlua_nil_ref); + nlua_unref(lstate, nlua_empty_dict_ref); + +#ifdef NLUA_TRACK_REFS + if (nlua_refcount) { + fprintf(stderr, "%d lua references were leaked!", nlua_refcount); + } + + if (nlua_ref_markers) { + // in case there are leaked luarefs, leak the associated memory + // to get LeakSanitizer stacktraces on exit + pmap_free(handle_T)(nlua_ref_markers); + } +#endif + + nlua_refcount = 0; + lua_close(lstate); +} + static void nlua_print_event(void **argv) { char *str = argv[0]; @@ -866,17 +896,35 @@ static int nlua_getenv(lua_State *lstate) } #endif + /// add the value to the registry LuaRef nlua_ref(lua_State *lstate, int index) { lua_pushvalue(lstate, index); - return luaL_ref(lstate, LUA_REGISTRYINDEX); + LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX); + if (ref > 0) { + nlua_refcount++; +#ifdef NLUA_TRACK_REFS + if (nlua_ref_markers) { + // dummy allocation to make LeakSanitizer track our luarefs + pmap_put(handle_T)(nlua_ref_markers, ref, xmalloc(3)); + } +#endif + } + return ref; } /// remove the value from the registry void nlua_unref(lua_State *lstate, LuaRef ref) { if (ref > 0) { + nlua_refcount--; +#ifdef NLUA_TRACK_REFS + // NB: don't remove entry from map to track double-unref + if (nlua_ref_markers) { + xfree(pmap_get(handle_T)(nlua_ref_markers, ref)); + } +#endif luaL_unref(lstate, LUA_REGISTRYINDEX, ref); } } @@ -893,19 +941,11 @@ void nlua_pushref(lua_State *lstate, LuaRef ref) lua_rawgeti(lstate, LUA_REGISTRYINDEX, ref); } + /// Gets a new reference to an object stored at original_ref /// /// NOTE: It does not copy the value, it creates a new ref to the lua object. /// Leaves the stack unchanged. -LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref) -{ - nlua_pushref(lstate, original_ref); - LuaRef new_ref = nlua_ref(lstate, -1); - lua_pop(lstate, 1); - - return new_ref; -} - LuaRef api_new_luaref(LuaRef original_ref) { if (original_ref == LUA_NOREF) { @@ -913,7 +953,10 @@ LuaRef api_new_luaref(LuaRef original_ref) } lua_State *const lstate = nlua_enter(); - return nlua_newref(lstate, original_ref); + nlua_pushref(lstate, original_ref); + LuaRef new_ref = nlua_ref(lstate, -1); + lua_pop(lstate, 1); + return new_ref; } @@ -1023,25 +1066,13 @@ int typval_exec_lua_callable( typval_T *rettv ) { - int offset = 0; LuaRef cb = lua_cb.func_ref; - if (cb == LUA_NOREF) { - // This shouldn't happen. - luaL_error(lstate, "Invalid function passed to VimL"); - return ERROR_OTHER; - } - nlua_pushref(lstate, cb); - if (lua_cb.table_ref != LUA_NOREF) { - offset += 1; - nlua_pushref(lstate, lua_cb.table_ref); - } - PUSH_ALL_TYPVALS(lstate, argvars, argcount, false); - if (lua_pcall(lstate, argcount + offset, 1, 0)) { + if (lua_pcall(lstate, argcount, 1, 0)) { nlua_print(lstate); return ERROR_OTHER; } @@ -1213,13 +1244,16 @@ void ex_luado(exarg_T *const eap) break; } lua_pushvalue(lstate, -1); - lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); + const char *old_line = (const char *)ml_get_buf(curbuf, l, false); + lua_pushstring(lstate, old_line); lua_pushnumber(lstate, (lua_Number)l); if (lua_pcall(lstate, 2, 1, 0)) { nlua_error(lstate, _("E5111: Error calling lua: %.*s")); break; } if (lua_isstring(lstate, -1)) { + size_t old_line_len = STRLEN(old_line); + size_t new_line_len; const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); char *const new_line_transformed = xmemdupz(new_line, new_line_len); @@ -1229,7 +1263,7 @@ void ex_luado(exarg_T *const eap) } } ml_replace(l, (char_u *)new_line_transformed, false); - changed_bytes(l, 0); + inserted_bytes(l, 0, (int)old_line_len, (int)new_line_len); } lua_pop(lstate, 1); } @@ -1508,6 +1542,8 @@ static int regex_match_line(lua_State *lstate) return nret; } +// Required functions for lua c functions as VimL callbacks + int nlua_CFunction_func_call( int argcount, typval_T *argvars, @@ -1517,53 +1553,40 @@ int nlua_CFunction_func_call( lua_State *const lstate = nlua_enter(); LuaCFunctionState *funcstate = (LuaCFunctionState *)state; - return typval_exec_lua_callable( - lstate, - funcstate->lua_callable, - argcount, - argvars, - rettv); + return typval_exec_lua_callable(lstate, funcstate->lua_callable, + argcount, argvars, rettv); } -/// Required functions for lua c functions as VimL callbacks + void nlua_CFunction_func_free(void *state) { lua_State *const lstate = nlua_enter(); LuaCFunctionState *funcstate = (LuaCFunctionState *)state; nlua_unref(lstate, funcstate->lua_callable.func_ref); - nlua_unref(lstate, funcstate->lua_callable.table_ref); xfree(funcstate); } bool nlua_is_table_from_lua(typval_T *const arg) { - if (arg->v_type != VAR_DICT && arg->v_type != VAR_LIST) { - return false; - } - if (arg->v_type == VAR_DICT) { - return arg->vval.v_dict->lua_table_ref > 0 - && arg->vval.v_dict->lua_table_ref != LUA_NOREF; + return arg->vval.v_dict->lua_table_ref != LUA_NOREF; } else if (arg->v_type == VAR_LIST) { - return arg->vval.v_list->lua_table_ref > 0 - && arg->vval.v_list->lua_table_ref != LUA_NOREF; + return arg->vval.v_list->lua_table_ref != LUA_NOREF; + } else { + return false; } - - return false; } char_u *nlua_register_table_as_callable(typval_T *const arg) { - if (!nlua_is_table_from_lua(arg)) { - return NULL; - } - - LuaRef table_ref; + LuaRef table_ref = LUA_NOREF; if (arg->v_type == VAR_DICT) { table_ref = arg->vval.v_dict->lua_table_ref; } else if (arg->v_type == VAR_LIST) { table_ref = arg->vval.v_list->lua_table_ref; - } else { + } + + if (table_ref == LUA_NOREF) { return NULL; } @@ -1573,55 +1596,34 @@ char_u *nlua_register_table_as_callable(typval_T *const arg) int top = lua_gettop(lstate); #endif - nlua_pushref(lstate, table_ref); + nlua_pushref(lstate, table_ref); // [table] if (!lua_getmetatable(lstate, -1)) { + lua_pop(lstate, 1); + assert(top == lua_gettop(lstate)); return NULL; - } + } // [table, mt] - lua_getfield(lstate, -1, "__call"); + lua_getfield(lstate, -1, "__call"); // [table, mt, mt.__call] if (!lua_isfunction(lstate, -1)) { + lua_pop(lstate, 3); + assert(top == lua_gettop(lstate)); return NULL; } - - LuaRef new_table_ref = nlua_newref(lstate, table_ref); + lua_pop(lstate, 2); // [table] LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); state->lua_callable.func_ref = nlua_ref(lstate, -1); - state->lua_callable.table_ref = new_table_ref; - char_u *name = register_cfunc( - &nlua_CFunction_func_call, - &nlua_CFunction_func_free, - state); + char_u *name = register_cfunc(&nlua_CFunction_func_call, + &nlua_CFunction_func_free, state); - lua_pop(lstate, 3); + lua_pop(lstate, 1); // [] assert(top == lua_gettop(lstate)); return name; } -/// Helper function to free a list_T -void nlua_free_typval_list(list_T *const l) -{ - if (l->lua_table_ref != LUA_NOREF && l->lua_table_ref > 0) { - lua_State *const lstate = nlua_enter(); - nlua_unref(lstate, l->lua_table_ref); - l->lua_table_ref = LUA_NOREF; - } -} - - -/// Helper function to free a dict_T -void nlua_free_typval_dict(dict_T *const d) -{ - if (d->lua_table_ref != LUA_NOREF && d->lua_table_ref > 0) { - lua_State *const lstate = nlua_enter(); - nlua_unref(lstate, d->lua_table_ref); - d->lua_table_ref = LUA_NOREF; - } -} - void nlua_execute_log_keystroke(int c) { char_u buf[NUMBUFLEN]; diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 1d7a15d9aa..ea774ac2e3 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -16,6 +16,8 @@ void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF); EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF); +EXTERN int nlua_refcount INIT(= 0); + #define set_api_error(s, err) \ do { \ Error *err_ = (err); \ diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 38848b0266..c186928ae2 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -230,6 +230,11 @@ int tslua_inspect_lang(lua_State *L) } lua_setfield(L, -2, "fields"); // [retval] + + uint32_t lang_version = ts_language_version(lang); + lua_pushinteger(L, lang_version); // [retval, version] + lua_setfield(L, -2, "_abi_version"); + return 1; } diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 9bc6b23ce3..7a8fc4da75 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -19,6 +19,8 @@ #include "nvim/ui.h" #include "nvim/sign.h" #include "nvim/api/vim.h" +#include "nvim/lua/executor.h" +#include "nvim/decoration.h" #ifdef UNIT_TESTING # define malloc(size) mem_malloc(size) @@ -695,6 +697,10 @@ void free_all_mem(void) list_free_log(); check_quickfix_busy(); + + decor_free_all_mem(); + + nlua_free_all_mem(); } #endif diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index a0b439ac45..a2d8859c68 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -219,7 +219,7 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, char buf[256]; snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client", channel->id); - call_set_error(channel, buf, WARN_LOG_LEVEL); + call_set_error(channel, buf, INFO_LOG_LEVEL); goto end; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 3587b12277..f016ef6813 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3971,7 +3971,8 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) while (dist--) { if (dir == BACKWARD) { - if (curwin->w_curswant >= width1) { + if (curwin->w_curswant >= width1 + && !hasFolding(curwin->w_cursor.lnum, NULL, NULL)) { // Move back within the line. This can give a negative value // for w_curswant if width1 < width2 (with cpoptions+=n), // which will get clipped to column 0. @@ -4003,14 +4004,16 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) n = ((linelen - width1 - 1) / width2 + 1) * width2 + width1; else n = width1; - if (curwin->w_curswant + width2 < (colnr_T)n) - /* move forward within line */ + if (curwin->w_curswant + width2 < (colnr_T)n + && !hasFolding(curwin->w_cursor.lnum, NULL, NULL)) { + // move forward within line curwin->w_curswant += width2; - else { - /* to next line */ - /* Move to the end of a closed fold. */ + } else { + // to next line + + // Move to the end of a closed fold. (void)hasFolding(curwin->w_cursor.lnum, NULL, - &curwin->w_cursor.lnum); + &curwin->w_cursor.lnum); if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { retval = false; break; @@ -5459,7 +5462,7 @@ static int normal_search( curwin->w_set_curswant = true; memset(&sia, 0, sizeof(sia)); - i = do_search(cap->oap, dir, pat, cap->count1, + i = do_search(cap->oap, dir, dir, pat, cap->count1, opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); if (wrapped != NULL) { *wrapped = sia.sa_wrapped; diff --git a/src/nvim/option.c b/src/nvim/option.c index 612ecca96a..914b92618c 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3437,6 +3437,12 @@ skip: return NULL; // no error } +void check_blending(win_T *wp) +{ + wp->w_grid_alloc.blending = + wp->w_p_winbl > 0 || (wp->w_floating && wp->w_float_config.shadow); +} + /// Handle setting 'listchars' or 'fillchars'. /// Assume monocell characters @@ -4380,7 +4386,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // 'floatblend' curwin->w_p_winbl = MAX(MIN(curwin->w_p_winbl, 100), 0); curwin->w_hl_needs_update = true; - curwin->w_grid_alloc.blending = curwin->w_p_winbl > 0; + check_blending(curwin); } @@ -5895,6 +5901,7 @@ void didset_window_options(win_T *wp) set_chars_option(wp, &wp->w_p_fcs, true); set_chars_option(wp, &wp->w_p_lcs, true); parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl + check_blending(wp); wp->w_grid_alloc.blending = wp->w_p_winbl > 0; } @@ -7607,7 +7614,9 @@ int win_signcol_count(win_T *wp) } } - return MAX(minimum, MIN(maximum, needed_signcols)); + int ret = MAX(minimum, MIN(maximum, needed_signcols)); + assert(ret <= SIGN_SHOW_MAX); + return ret; } /// Get window or buffer local options diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index d794969ab5..36d6dbe2db 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -175,7 +175,7 @@ static void init_child(PtyProcess *ptyproc) Process *proc = (Process *)ptyproc; if (proc->cwd && os_chdir(proc->cwd) != 0) { - ELOG("chdir failed: %s", strerror(errno)); + ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno)); return; } @@ -184,7 +184,7 @@ static void init_child(PtyProcess *ptyproc) assert(proc->env); environ = tv_dict_to_env(proc->env); execvp(prog, proc->argv); - ELOG("execvp failed: %s: %s", strerror(errno), prog); + ELOG("execvp(%s) failed: %s", prog, strerror(errno)); _exit(122); // 122 is EXEC_FAILED in the Vim source. } diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 94444e4d23..2bf73d08e6 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -203,11 +203,13 @@ int pty_process_spawn(PtyProcess *ptyproc) cleanup: if (status) { // In the case of an error of MultiByteToWideChar or CreateProcessW. - ELOG("pty_process_spawn: %s: error code: %d", emsg, status); + ELOG("pty_process_spawn(%s): %s: error code: %d", + proc->argv[0], emsg, status); status = os_translate_sys_error(status); } else if (err != NULL) { status = (int)winpty_error_code(err); - ELOG("pty_process_spawn: %s: error code: %d", emsg, status); + ELOG("pty_process_spawn(%s): %s: error code: %d", + proc->argv[0], emsg, status); status = translate_winpty_error(status); } winpty_error_free(err); diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 0785fa703d..464d72eccb 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -2798,7 +2798,7 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol, // Move the cursor to the first line in the buffer pos_T save_cursor = curwin->w_cursor; curwin->w_cursor.lnum = 0; - if (!do_search(NULL, '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) { + if (!do_search(NULL, '/', '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) { curwin->w_cursor = save_cursor; } } diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index d7693c7a6f..184f5da97d 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -6665,6 +6665,10 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int len = 0; /* init for GCC */ static char_u *eval_result = NULL; + // We need to keep track of how many backslashes we escape, so that the byte + // counts for `extmark_splice` are correct. + int num_escaped = 0; + // Be paranoid... if ((source == NULL && expr == NULL) || dest == NULL) { EMSG(_(e_null)); @@ -6840,6 +6844,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, // later. Used to insert a literal CR. default: if (backslash) { + num_escaped += 1; if (copy) { *dst = '\\'; } @@ -6979,7 +6984,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, *dst = NUL; exit: - return (int)((dst - dest) + 1); + return (int)((dst - dest) + 1 - num_escaped); } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 5bf5a471c1..9fb2eb2772 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2084,6 +2084,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, colnr_T trailcol = MAXCOL; // start of trailing spaces colnr_T leadcol = 0; // start of leading spaces bool need_showbreak = false; // overlong line, skip first x chars + sign_attrs_T sattrs[SIGN_SHOW_MAX]; // attributes for signs + int num_signs; // number of signs for line int line_attr = 0; // attribute for the whole line int line_attr_lowprio = 0; // low-priority attribute for the line matchitem_T *cur; // points to the match list @@ -2375,11 +2377,14 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, wp->w_last_cursorline = wp->w_cursor.lnum; } + memset(sattrs, 0, sizeof(sattrs)); + num_signs = buf_get_signattrs(wp->w_buffer, lnum, sattrs); + // If this line has a sign with line highlighting set line_attr. // TODO(bfredl, vigoux): this should not take priority over decoration! - v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1); - if (v != 0) { - line_attr = sign_get_attr((int)v, SIGN_LINEHL); + sign_attrs_T * sattr = sign_get_attr(SIGN_LINEHL, sattrs, 0, 1); + if (sattr != NULL) { + line_attr = sattr->sat_linehl; } // Highlight the current line in the quickfix window. @@ -2696,7 +2701,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int count = win_signcol_count(wp); if (count > 0) { get_sign_display_info( - false, wp, lnum, row, + false, wp, sattrs, row, startrow, filler_lines, filler_todo, count, &c_extra, &c_final, extra, sizeof(extra), &p_extra, &n_extra, @@ -2715,10 +2720,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, // in 'lnum', then display the sign instead of the line // number. if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' - && buf_findsign_id(wp->w_buffer, lnum, (char_u *)"*") != 0) { + && num_signs > 0) { int count = win_signcol_count(wp); get_sign_display_info( - true, wp, lnum, row, + true, wp, sattrs, row, startrow, filler_lines, filler_todo, count, &c_extra, &c_final, extra, sizeof(extra), &p_extra, &n_extra, @@ -2768,11 +2773,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, n_extra = number_width(wp) + 1; char_attr = win_hl_attr(wp, HLF_N); - int num_sign = buf_getsigntype( - wp->w_buffer, lnum, SIGN_NUMHL, 0, 1); - if (num_sign != 0) { + sign_attrs_T *num_sattr = sign_get_attr(SIGN_NUMHL, sattrs, 0, 1); + if (num_sattr != NULL) { // :sign defined with "numhl" highlight. - char_attr = sign_get_attr(num_sign, SIGN_NUMHL); + char_attr = num_sattr->sat_numhl; } else if ((wp->w_p_cul || wp->w_p_rnu) && lnum == wp->w_cursor.lnum && filler_todo == 0) { @@ -4451,7 +4455,7 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) static void get_sign_display_info( bool nrcol, win_T *wp, - linenr_T lnum, + sign_attrs_T sattrs[], int row, int startrow, int filler_lines, @@ -4468,8 +4472,6 @@ static void get_sign_display_info( int *sign_idxp ) { - int text_sign; - // Draw cells with the sign value or blank. *c_extrap = ' '; *c_finalp = NUL; @@ -4481,10 +4483,9 @@ static void get_sign_display_info( } if (row == startrow + filler_lines && filler_todo <= 0) { - text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT, - *sign_idxp, count); - if (text_sign != 0) { - *pp_extra = sign_get_text(text_sign); + sign_attrs_T *sattr = sign_get_attr(SIGN_TEXT, sattrs, *sign_idxp, count); + if (sattr != NULL) { + *pp_extra = sattr->sat_text; if (*pp_extra != NULL) { *c_extrap = NUL; *c_finalp = NUL; @@ -4517,7 +4518,7 @@ static void get_sign_display_info( (*pp_extra)[*n_extrap] = NUL; } } - *char_attrp = sign_get_attr(text_sign, SIGN_TEXT); + *char_attrp = sattr->sat_texthl; } } @@ -5453,32 +5454,50 @@ static void win_redr_border(win_T *wp) schar_T *chars = wp->w_float_config.border_chars; int *attrs = wp->w_float_config.border_attr; - int endrow = grid->Rows-1, endcol = grid->Columns-1; - grid_puts_line_start(grid, 0); - grid_put_schar(grid, 0, 0, chars[0], attrs[0]); - for (int i = 1; i < endcol; i++) { - grid_put_schar(grid, 0, i, chars[1], attrs[1]); - } - grid_put_schar(grid, 0, endcol, chars[2], attrs[2]); - grid_puts_line_flush(false); + int *adj = wp->w_border_adj; + int irow = wp->w_height_inner, icol = wp->w_width_inner; - for (int i = 1; i < endrow; i++) { - grid_puts_line_start(grid, i); - grid_put_schar(grid, i, 0, chars[7], attrs[7]); - grid_puts_line_flush(false); - grid_puts_line_start(grid, i); - grid_put_schar(grid, i, endcol, chars[3], attrs[3]); + if (adj[0]) { + grid_puts_line_start(grid, 0); + if (adj[3]) { + grid_put_schar(grid, 0, 0, chars[0], attrs[0]); + } + for (int i = 0; i < icol; i++) { + grid_put_schar(grid, 0, i+adj[3], chars[1], attrs[1]); + } + if (adj[1]) { + grid_put_schar(grid, 0, icol+adj[3], chars[2], attrs[2]); + } grid_puts_line_flush(false); } - grid_puts_line_start(grid, endrow); - grid_put_schar(grid, endrow, 0, chars[6], attrs[6]); - for (int i = 1; i < endcol; i++) { - grid_put_schar(grid, endrow, i, chars[5], attrs[5]); + for (int i = 0; i < irow; i++) { + if (adj[3]) { + grid_puts_line_start(grid, i+adj[0]); + grid_put_schar(grid, i+adj[0], 0, chars[7], attrs[7]); + grid_puts_line_flush(false); + } + if (adj[1]) { + int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3; + grid_puts_line_start(grid, i+adj[0]); + grid_put_schar(grid, i+adj[0], icol+adj[3], chars[ic], attrs[ic]); + grid_puts_line_flush(false); + } + } + + if (adj[2]) { + grid_puts_line_start(grid, irow+adj[0]); + if (adj[3]) { + grid_put_schar(grid, irow+adj[0], 0, chars[6], attrs[6]); + } + for (int i = 0; i < icol; i++) { + int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5; + grid_put_schar(grid, irow+adj[0], i+adj[3], chars[ic], attrs[ic]); + } + grid_put_schar(grid, irow+adj[0], icol+adj[3], chars[4], attrs[4]); + grid_puts_line_flush(false); } - grid_put_schar(grid, endrow, endcol, chars[4], attrs[4]); - grid_puts_line_flush(false); } // Low-level functions to manipulate invidual character cells on the @@ -6246,7 +6265,7 @@ void win_grid_alloc(win_T *wp) grid_alloc(grid_allocated, total_rows, total_cols, wp->w_grid_alloc.valid, false); grid_allocated->valid = true; - if (wp->w_border_adj) { + if (wp->w_floating && wp->w_float_config.border) { wp->w_redr_border = true; } was_resized = true; @@ -6266,8 +6285,8 @@ void win_grid_alloc(win_T *wp) if (want_allocation) { grid->target = grid_allocated; - grid->row_offset = wp->w_border_adj; - grid->col_offset = wp->w_border_adj; + grid->row_offset = wp->w_border_adj[0]; + grid->col_offset = wp->w_border_adj[3]; } else { grid->target = &default_grid; grid->row_offset = wp->w_winrow; @@ -7508,6 +7527,10 @@ void screen_resize(int width, int height) Rows = height; Columns = width; check_shellsize(); + int max_p_ch = Rows - min_rows() + 1; + if (!ui_has(kUIMessages) && p_ch > max_p_ch) { + p_ch = max_p_ch ? max_p_ch : 1; + } height = Rows; width = Columns; p_lines = Rows; diff --git a/src/nvim/search.c b/src/nvim/search.c index 84b71d56a0..c4479a077e 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1021,8 +1021,9 @@ static int first_submatch(regmmatch_T *rp) * Return 0 for failure, 1 for found, 2 for found and line offset added. */ int do_search( - oparg_T *oap, /* can be NULL */ - int dirc, /* '/' or '?' */ + oparg_T *oap, // can be NULL + int dirc, // '/' or '?' + int search_delim, // delimiter for search, e.g. '%' in s%regex%replacement char_u *pat, long count, int options, @@ -1101,8 +1102,8 @@ int do_search( searchstr = pat; dircp = NULL; - /* use previous pattern */ - if (pat == NULL || *pat == NUL || *pat == dirc) { + // use previous pattern + if (pat == NULL || *pat == NUL || *pat == search_delim) { if (spats[RE_SEARCH].pat == NULL) { // no previous pattern searchstr = spats[RE_SUBST].pat; if (searchstr == NULL) { @@ -1122,15 +1123,15 @@ int do_search( * If there is a matching '/' or '?', toss it. */ ps = strcopy; - p = skip_regexp(pat, dirc, p_magic, &strcopy); + p = skip_regexp(pat, search_delim, p_magic, &strcopy); if (strcopy != ps) { /* made a copy of "pat" to change "\?" to "?" */ searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy)); pat = strcopy; searchstr = strcopy; } - if (*p == dirc) { - dircp = p; /* remember where we put the NUL */ + if (*p == search_delim) { + dircp = p; // remember where we put the NUL *p++ = NUL; } spats[0].off.line = FALSE; @@ -1320,7 +1321,7 @@ int do_search( RE_LAST, sia); if (dircp != NULL) { - *dircp = dirc; // restore second '/' or '?' for normal_cmd() + *dircp = search_delim; // restore second '/' or '?' for normal_cmd() } if (!shortmess(SHM_SEARCH) @@ -1400,6 +1401,7 @@ int do_search( } dirc = *++pat; + search_delim = dirc; if (dirc != '?' && dirc != '/') { retval = 0; EMSG(_("E386: Expected '?' or '/' after ';'")); @@ -2326,6 +2328,9 @@ showmatch( return; } } + if (*p == NUL) { + return; + } if ((lpos = findmatch(NULL, NUL)) == NULL) { // no match, so beep vim_beep(BO_MATCH); diff --git a/src/nvim/sign.c b/src/nvim/sign.c index fc9f53c192..c7dc1a5b22 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -83,13 +83,13 @@ static signgroup_T * sign_group_ref(const char_u *groupname) group = xmalloc((unsigned)(sizeof(signgroup_T) + STRLEN(groupname))); STRCPY(group->sg_name, groupname); - group->refcount = 1; - group->next_sign_id = 1; + group->sg_refcount = 1; + group->sg_next_sign_id = 1; hash_add_item(&sg_table, hi, group->sg_name, hash); } else { // existing group group = HI2SG(hi); - group->refcount++; + group->sg_refcount++; } return group; @@ -105,8 +105,8 @@ static void sign_group_unref(char_u *groupname) hi = hash_find(&sg_table, groupname); if (!HASHITEM_EMPTY(hi)) { group = HI2SG(hi); - group->refcount--; - if (group->refcount == 0) { + group->sg_refcount--; + if (group->sg_refcount == 0) { // All the signs in this group are removed hash_remove(&sg_table, hi); xfree(group); @@ -117,12 +117,12 @@ static void sign_group_unref(char_u *groupname) /// Returns TRUE if 'sign' is in 'group'. /// A sign can either be in the global group (sign->group == NULL) /// or in a named group. If 'group' is '*', then the sign is part of the group. -int sign_in_group(signlist_T *sign, const char_u *group) +int sign_in_group(sign_entry_T *sign, const char_u *group) { return ((group != NULL && STRCMP(group, "*") == 0) - || (group == NULL && sign->group == NULL) - || (group != NULL && sign->group != NULL - && STRCMP(group, sign->group->sg_name) == 0)); + || (group == NULL && sign->se_group == NULL) + || (group != NULL && sign->se_group != NULL + && STRCMP(group, sign->se_group->sg_name) == 0)); } /// Get the next free sign identifier in the specified group @@ -130,7 +130,7 @@ int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) { int id = 1; signgroup_T *group = NULL; - signlist_T *sign; + sign_entry_T *sign; hashitem_T *hi; int found = false; @@ -147,13 +147,13 @@ int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) if (group == NULL) { id = next_sign_id++; // global group } else { - id = group->next_sign_id++; + id = group->sg_next_sign_id++; } // Check whether this sign is already placed in the buffer found = true; FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (id == sign->id && sign_in_group(sign, groupname)) { + if (id == sign->se_id && sign_in_group(sign, groupname)) { found = false; // sign identifier is in use break; } @@ -167,8 +167,8 @@ int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) /// 'next' signs. static void insert_sign( buf_T *buf, // buffer to store sign in - signlist_T *prev, // previous sign entry - signlist_T *next, // next sign entry + sign_entry_T *prev, // previous sign entry + sign_entry_T *next, // next sign entry int id, // sign ID const char_u *group, // sign group; NULL for global group int prio, // sign priority @@ -177,21 +177,21 @@ static void insert_sign( bool has_text_or_icon // sign has text or icon ) { - signlist_T *newsign = xmalloc(sizeof(signlist_T)); - newsign->id = id; - newsign->lnum = lnum; - newsign->typenr = typenr; - newsign->has_text_or_icon = has_text_or_icon; + sign_entry_T *newsign = xmalloc(sizeof(sign_entry_T)); + newsign->se_id = id; + newsign->se_lnum = lnum; + newsign->se_typenr = typenr; + newsign->se_has_text_or_icon = has_text_or_icon; if (group != NULL) { - newsign->group = sign_group_ref(group); + newsign->se_group = sign_group_ref(group); } else { - newsign->group = NULL; + newsign->se_group = NULL; } - newsign->priority = prio; - newsign->next = next; - newsign->prev = prev; + newsign->se_priority = prio; + newsign->se_next = next; + newsign->se_prev = prev; if (next != NULL) { - next->prev = newsign; + next->se_prev = newsign; } buf->b_signcols_max = -1; @@ -206,14 +206,14 @@ static void insert_sign( // first sign in signlist buf->b_signlist = newsign; } else { - prev->next = newsign; + prev->se_next = newsign; } } /// Insert a new sign sorted by line number and sign priority. static void insert_sign_by_lnum_prio( buf_T *buf, // buffer to store sign in - signlist_T *prev, // previous sign entry + sign_entry_T *prev, // previous sign entry int id, // sign ID const char_u *group, // sign group; NULL for global group int prio, // sign priority @@ -222,19 +222,19 @@ static void insert_sign_by_lnum_prio( bool has_text_or_icon // sign has text or icon ) { - signlist_T *sign; + sign_entry_T *sign; // keep signs sorted by lnum, priority and id: insert new sign at // the proper position in the list for this lnum. - while (prev != NULL && prev->lnum == lnum - && (prev->priority < prio - || (prev->priority == prio && prev->id <= id))) { - prev = prev->prev; + while (prev != NULL && prev->se_lnum == lnum + && (prev->se_priority < prio + || (prev->se_priority == prio && prev->se_id <= id))) { + prev = prev->se_prev; } if (prev == NULL) { sign = buf->b_signlist; } else { - sign = prev->next; + sign = prev->se_next; } insert_sign(buf, prev, sign, id, group, prio, lnum, typenr, has_text_or_icon); @@ -254,16 +254,16 @@ char_u * sign_typenr2name(int typenr) } /// Return information about a sign in a Dict -dict_T * sign_get_info(signlist_T *sign) +dict_T * sign_get_info(sign_entry_T *sign) { dict_T *d = tv_dict_alloc(); - tv_dict_add_nr(d, S_LEN("id"), sign->id); - tv_dict_add_str(d, S_LEN("group"), ((sign->group == NULL) + tv_dict_add_nr(d, S_LEN("id"), sign->se_id); + tv_dict_add_str(d, S_LEN("group"), ((sign->se_group == NULL) ? (char *)"" - : (char *)sign->group->sg_name)); - tv_dict_add_nr(d, S_LEN("lnum"), sign->lnum); - tv_dict_add_str(d, S_LEN("name"), (char *)sign_typenr2name(sign->typenr)); - tv_dict_add_nr(d, S_LEN("priority"), sign->priority); + : (char *)sign->se_group->sg_name)); + tv_dict_add_nr(d, S_LEN("lnum"), sign->se_lnum); + tv_dict_add_str(d, S_LEN("name"), (char *)sign_typenr2name(sign->se_typenr)); + tv_dict_add_nr(d, S_LEN("priority"), sign->se_priority); return d; } @@ -271,17 +271,17 @@ dict_T * sign_get_info(signlist_T *sign) // Sort the signs placed on the same line as "sign" by priority. Invoked after // changing the priority of an already placed sign. Assumes the signs in the // buffer are sorted by line number and priority. -static void sign_sort_by_prio_on_line(buf_T *buf, signlist_T *sign) +static void sign_sort_by_prio_on_line(buf_T *buf, sign_entry_T *sign) FUNC_ATTR_NONNULL_ALL { // If there is only one sign in the buffer or only one sign on the line or // the sign is already sorted by priority, then return. - if ((sign->prev == NULL - || sign->prev->lnum != sign->lnum - || sign->prev->priority > sign->priority) - && (sign->next == NULL - || sign->next->lnum != sign->lnum - || sign->next->priority < sign->priority)) { + if ((sign->se_prev == NULL + || sign->se_prev->se_lnum != sign->se_lnum + || sign->se_prev->se_priority > sign->se_priority) + && (sign->se_next == NULL + || sign->se_next->se_lnum != sign->se_lnum + || sign->se_next->se_priority < sign->se_priority)) { return; } @@ -289,55 +289,55 @@ static void sign_sort_by_prio_on_line(buf_T *buf, signlist_T *sign) // Find a sign after which 'sign' should be inserted // First search backward for a sign with higher priority on the same line - signlist_T *p = sign; - while (p->prev != NULL - && p->prev->lnum == sign->lnum - && p->prev->priority <= sign->priority) { - p = p->prev; + sign_entry_T *p = sign; + while (p->se_prev != NULL + && p->se_prev->se_lnum == sign->se_lnum + && p->se_prev->se_priority <= sign->se_priority) { + p = p->se_prev; } if (p == sign) { // Sign not found. Search forward for a sign with priority just before // 'sign'. - p = sign->next; - while (p->next != NULL - && p->next->lnum == sign->lnum - && p->next->priority > sign->priority) { - p = p->next; + p = sign->se_next; + while (p->se_next != NULL + && p->se_next->se_lnum == sign->se_lnum + && p->se_next->se_priority > sign->se_priority) { + p = p->se_next; } } // Remove 'sign' from the list if (buf->b_signlist == sign) { - buf->b_signlist = sign->next; + buf->b_signlist = sign->se_next; } - if (sign->prev != NULL) { - sign->prev->next = sign->next; + if (sign->se_prev != NULL) { + sign->se_prev->se_next = sign->se_next; } - if (sign->next != NULL) { - sign->next->prev = sign->prev; + if (sign->se_next != NULL) { + sign->se_next->se_prev = sign->se_prev; } - sign->prev = NULL; - sign->next = NULL; + sign->se_prev = NULL; + sign->se_next = NULL; // Re-insert 'sign' at the right place - if (p->priority <= sign->priority) { + if (p->se_priority <= sign->se_priority) { // 'sign' has a higher priority and should be inserted before 'p' - sign->prev = p->prev; - sign->next = p; - p->prev = sign; - if (sign->prev != NULL) { - sign->prev->next = sign; + sign->se_prev = p->se_prev; + sign->se_next = p; + p->se_prev = sign; + if (sign->se_prev != NULL) { + sign->se_prev->se_next = sign; } if (buf->b_signlist == p) { buf->b_signlist = sign; } } else { // 'sign' has a lower priority and should be inserted after 'p' - sign->prev = p; - sign->next = p->next; - p->next = sign; - if (sign->next != NULL) { - sign->next->prev = sign; + sign->se_prev = p; + sign->se_next = p->se_next; + p->se_next = sign; + if (sign->se_next != NULL) { + sign->se_next->se_prev = sign; } } } @@ -354,19 +354,19 @@ void buf_addsign( bool has_text_or_icon // sign has text or icon ) { - signlist_T *sign; // a sign in the signlist - signlist_T *prev; // the previous sign + sign_entry_T *sign; // a sign in the signlist + sign_entry_T *prev; // the previous sign prev = NULL; FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (lnum == sign->lnum && id == sign->id + if (lnum == sign->se_lnum && id == sign->se_id && sign_in_group(sign, groupname)) { // Update an existing sign - sign->typenr = typenr; - sign->priority = prio; + sign->se_typenr = typenr; + sign->se_priority = prio; sign_sort_by_prio_on_line(buf, sign); return; - } else if (lnum < sign->lnum) { + } else if (lnum < sign->se_lnum) { insert_sign_by_lnum_prio( buf, prev, @@ -398,69 +398,119 @@ linenr_T buf_change_sign_type( buf_T *buf, // buffer to store sign in int markId, // sign ID const char_u *group, // sign group - int typenr // typenr of sign we are adding + int typenr, // typenr of sign we are adding + int prio // sign priority ) { - signlist_T *sign; // a sign in the signlist + sign_entry_T *sign; // a sign in the signlist FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->id == markId && sign_in_group(sign, group)) { - sign->typenr = typenr; - return sign->lnum; + if (sign->se_id == markId && sign_in_group(sign, group)) { + sign->se_typenr = typenr; + sign->se_priority = prio; + sign_sort_by_prio_on_line(buf, sign); + return sign->se_lnum; } } return (linenr_T)0; } -/// Gets a sign from a given line. -/// -/// Return the type number of the sign at line number 'lnum' in buffer 'buf' -/// which has the attribute specified by 'type'. Returns 0 if a sign is not -/// found at the line number or it doesn't have the specified attribute. -/// @param buf Buffer in which to search -/// @param lnum Line in which to search +/// Return the sign attrs which has the attribute specified by 'type'. Returns +/// NULL if a sign is not found with the specified attribute. /// @param type Type of sign to look for +/// @param sattrs Sign attrs to search through /// @param idx if there multiple signs, this index will pick the n-th -// out of the most `max_signs` sorted ascending by Id. +/// out of the most `max_signs` sorted ascending by Id. /// @param max_signs the number of signs, with priority for the ones -// with the highest Ids. -/// @return Identifier of the matching sign, or 0 -int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type, - int idx, int max_signs) +/// with the highest Ids. +/// @return Attrs of the matching sign, or NULL +sign_attrs_T * sign_get_attr(SignType type, sign_attrs_T sattrs[], + int idx, int max_signs) { - signlist_T *sign; // a sign in a b_signlist - signlist_T *matches[9]; + sign_attrs_T *matches[SIGN_SHOW_MAX]; int nr_matches = 0; - FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->lnum == lnum - && (type == SIGN_ANY - || (type == SIGN_TEXT - && sign_get_text(sign->typenr) != NULL) - || (type == SIGN_LINEHL - && sign_get_attr(sign->typenr, SIGN_LINEHL) != 0) - || (type == SIGN_NUMHL - && sign_get_attr(sign->typenr, SIGN_NUMHL) != 0))) { - matches[nr_matches] = sign; + for (int i = 0; i < SIGN_SHOW_MAX; i++) { + if ( (type == SIGN_TEXT && sattrs[i].sat_text != NULL) + || (type == SIGN_LINEHL && sattrs[i].sat_linehl != 0) + || (type == SIGN_NUMHL && sattrs[i].sat_numhl != 0)) { + matches[nr_matches] = &sattrs[i]; nr_matches++; - // signlist is sorted with most important (priority, id), thus we + // attr list is sorted with most important (priority, id), thus we // may stop as soon as we have max_signs matches - if (nr_matches == ARRAY_SIZE(matches) || nr_matches >= max_signs) { + if (nr_matches >= max_signs) { break; } } } - if (nr_matches > 0) { - if (idx >= nr_matches) { - return 0; - } + if (nr_matches > idx) { + return matches[nr_matches - idx - 1]; + } + + return NULL; +} + +/// Lookup a sign by typenr. Returns NULL if sign is not found. +static sign_T * find_sign_by_typenr(int typenr) +{ + sign_T *sp; - return matches[nr_matches - idx -1]->typenr; + for (sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (sp->sn_typenr == typenr) { + return sp; + } } + return NULL; +} - return 0; +/// Return the attributes of all the signs placed on line 'lnum' in buffer +/// 'buf'. Used when refreshing the screen. Returns the number of signs. +/// @param buf Buffer in which to search +/// @param lnum Line in which to search +/// @param sattrs Output array for attrs +/// @return Number of signs of which attrs were found +int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[]) +{ + sign_entry_T *sign; + sign_T *sp; + + int nr_matches = 0; + + FOR_ALL_SIGNS_IN_BUF(buf, sign) { + if (sign->se_lnum > lnum) { + // Signs are sorted by line number in the buffer. No need to check + // for signs after the specified line number 'lnum'. + break; + } + + if (sign->se_lnum == lnum) { + sign_attrs_T sattr; + memset(&sattr, 0, sizeof(sattr)); + sattr.sat_typenr = sign->se_typenr; + sp = find_sign_by_typenr(sign->se_typenr); + if (sp != NULL) { + sattr.sat_text = sp->sn_text; + if (sattr.sat_text != NULL && sp->sn_text_hl != 0) { + sattr.sat_texthl = syn_id2attr(sp->sn_text_hl); + } + if (sp->sn_line_hl != 0) { + sattr.sat_linehl = syn_id2attr(sp->sn_line_hl); + } + if (sp->sn_num_hl != 0) { + sattr.sat_numhl = syn_id2attr(sp->sn_num_hl); + } + } + + sattrs[nr_matches] = sattr; + nr_matches++; + if (nr_matches == SIGN_SHOW_MAX) { + break; + } + } + } + return nr_matches; } /// Delete sign 'id' in group 'group' from buffer 'buf'. @@ -478,26 +528,26 @@ linenr_T buf_delsign( char_u *group // sign group ) { - signlist_T **lastp; // pointer to pointer to current sign - signlist_T *sign; // a sign in a b_signlist - signlist_T *next; // the next sign in a b_signlist + sign_entry_T **lastp; // pointer to pointer to current sign + sign_entry_T *sign; // a sign in a b_signlist + sign_entry_T *next; // the next sign in a b_signlist linenr_T lnum; // line number whose sign was deleted buf->b_signcols_max = -1; lastp = &buf->b_signlist; lnum = 0; for (sign = buf->b_signlist; sign != NULL; sign = next) { - next = sign->next; - if ((id == 0 || sign->id == id) - && (atlnum == 0 || sign->lnum == atlnum) + next = sign->se_next; + if ((id == 0 || sign->se_id == id) + && (atlnum == 0 || sign->se_lnum == atlnum) && sign_in_group(sign, group)) { *lastp = next; if (next != NULL) { - next->prev = sign->prev; + next->se_prev = sign->se_prev; } - lnum = sign->lnum; - if (sign->group != NULL) { - sign_group_unref(sign->group->sg_name); + lnum = sign->se_lnum; + if (sign->se_group != NULL) { + sign_group_unref(sign->se_group->sg_name); } xfree(sign); redraw_buf_line_later(buf, lnum); @@ -511,7 +561,7 @@ linenr_T buf_delsign( break; } } else { - lastp = &sign->next; + lastp = &sign->se_next; } } @@ -535,11 +585,11 @@ int buf_findsign( char_u *group // sign group ) { - signlist_T *sign; // a sign in the signlist + sign_entry_T *sign; // a sign in the signlist FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->id == id && sign_in_group(sign, group)) { - return (int)sign->lnum; + if (sign->se_id == id && sign_in_group(sign, group)) { + return (int)sign->se_lnum; } } @@ -548,16 +598,22 @@ int buf_findsign( /// Return the sign at line 'lnum' in buffer 'buf'. Returns NULL if a sign is /// not found at the line. If 'groupname' is NULL, searches in the global group. -static signlist_T * buf_getsign_at_line( +static sign_entry_T * buf_getsign_at_line( buf_T *buf, // buffer whose sign we are searching for linenr_T lnum, // line number of sign char_u *groupname // sign group name ) { - signlist_T *sign; // a sign in the signlist + sign_entry_T *sign; // a sign in the signlist FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->lnum == lnum && sign_in_group(sign, groupname)) { + if (sign->se_lnum > lnum) { + // Signs are sorted by line number in the buffer. No need to check + // for signs after the specified line number 'lnum'. + break; + } + + if (sign->se_lnum == lnum && sign_in_group(sign, groupname)) { return sign; } } @@ -572,11 +628,11 @@ int buf_findsign_id( char_u *groupname // sign group name ) { - signlist_T *sign; // a sign in the signlist + sign_entry_T *sign; // a sign in the signlist sign = buf_getsign_at_line(buf, lnum, groupname); if (sign != NULL) { - return sign->id; + return sign->se_id; } return 0; @@ -585,9 +641,9 @@ int buf_findsign_id( /// Delete signs in buffer "buf". void buf_delete_signs(buf_T *buf, char_u *group) { - signlist_T *sign; - signlist_T **lastp; // pointer to pointer to current sign - signlist_T *next; + sign_entry_T *sign; + sign_entry_T **lastp; // pointer to pointer to current sign + sign_entry_T *next; // When deleting the last sign need to redraw the windows to remove the // sign column. Not when curwin is NULL (this means we're exiting). @@ -597,18 +653,18 @@ void buf_delete_signs(buf_T *buf, char_u *group) lastp = &buf->b_signlist; for (sign = buf->b_signlist; sign != NULL; sign = next) { - next = sign->next; + next = sign->se_next; if (sign_in_group(sign, group)) { *lastp = next; if (next != NULL) { - next->prev = sign->prev; + next->se_prev = sign->se_prev; } - if (sign->group != NULL) { - sign_group_unref(sign->group->sg_name); + if (sign->se_group != NULL) { + sign_group_unref(sign->se_group->sg_name); } xfree(sign); } else { - lastp = &sign->next; + lastp = &sign->se_next; } } buf->b_signcols_max = -1; @@ -618,7 +674,7 @@ void buf_delete_signs(buf_T *buf, char_u *group) void sign_list_placed(buf_T *rbuf, char_u *sign_group) { buf_T *buf; - signlist_T *sign; + sign_entry_T *sign; char lbuf[MSG_BUF_LEN]; char group[MSG_BUF_LEN]; @@ -642,16 +698,16 @@ void sign_list_placed(buf_T *rbuf, char_u *sign_group) if (!sign_in_group(sign, sign_group)) { continue; } - if (sign->group != NULL) { + if (sign->se_group != NULL) { vim_snprintf(group, MSG_BUF_LEN, _(" group=%s"), - sign->group->sg_name); + sign->se_group->sg_name); } else { group[0] = '\0'; } vim_snprintf(lbuf, MSG_BUF_LEN, _(" line=%ld id=%d%s name=%s priority=%d"), - (long)sign->lnum, sign->id, group, - sign_typenr2name(sign->typenr), sign->priority); + (long)sign->se_lnum, sign->se_id, group, + sign_typenr2name(sign->se_typenr), sign->se_priority); MSG_PUTS(lbuf); msg_putchar('\n'); } @@ -670,25 +726,25 @@ void sign_mark_adjust( long amount_after ) { - signlist_T *sign; // a sign in a b_signlist + sign_entry_T *sign; // a sign in a b_signlist linenr_T new_lnum; // new line number to assign to sign curbuf->b_signcols_max = -1; FOR_ALL_SIGNS_IN_BUF(curbuf, sign) { - new_lnum = sign->lnum; - if (sign->lnum >= line1 && sign->lnum <= line2) { + new_lnum = sign->se_lnum; + if (sign->se_lnum >= line1 && sign->se_lnum <= line2) { if (amount != MAXLNUM) { new_lnum += amount; } - } else if (sign->lnum > line2) { + } else if (sign->se_lnum > line2) { new_lnum += amount_after; } // If the new sign line number is past the last line in the buffer, // then don't adjust the line number. Otherwise, it will always be past // the last line and will not be visible. - if (sign->lnum >= line1 && new_lnum <= curbuf->b_ml.ml_line_count) { - sign->lnum = new_lnum; + if (sign->se_lnum >= line1 && new_lnum <= curbuf->b_ml.ml_line_count) { + sign->se_lnum = new_lnum; } } } @@ -973,8 +1029,8 @@ int sign_place( sp->sn_typenr, has_text_or_icon); } else { - // ":sign place {id} file={fname}": change sign type - lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr); + // ":sign place {id} file={fname}": change sign type and/or priority + lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr, prio); } if (lnum > 0) { redraw_buf_line_later(buf, lnum); @@ -1488,7 +1544,7 @@ void sign_getlist(const char_u *name, list_T *retlist) list_T *get_buffer_signs(buf_T *buf) FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - signlist_T *sign; + sign_entry_T *sign; dict_T *d; list_T *const l = tv_list_alloc(kListLenMayKnow); @@ -1509,7 +1565,7 @@ static void sign_get_placed_in_buf( { dict_T *d; list_T *l; - signlist_T *sign; + sign_entry_T *sign; d = tv_dict_alloc(); tv_list_append_dict(retlist, d); @@ -1524,9 +1580,9 @@ static void sign_get_placed_in_buf( continue; } if ((lnum == 0 && sign_id == 0) - || (sign_id == 0 && lnum == sign->lnum) - || (lnum == 0 && sign_id == sign->id) - || (lnum == sign->lnum && sign_id == sign->id)) { + || (sign_id == 0 && lnum == sign->se_lnum) + || (lnum == 0 && sign_id == sign->se_id) + || (lnum == sign->se_lnum && sign_id == sign->se_id)) { tv_list_append_dict(l, sign_get_info(sign)); } } @@ -1613,50 +1669,6 @@ static void sign_undefine(sign_T *sp, sign_T *sp_prev) xfree(sp); } -/// Gets highlighting attribute for sign "typenr" corresponding to "type". -int sign_get_attr(int typenr, SignType type) -{ - sign_T *sp; - int sign_hl = 0; - - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (sp->sn_typenr == typenr) { - switch (type) { - case SIGN_TEXT: - sign_hl = sp->sn_text_hl; - break; - case SIGN_LINEHL: - sign_hl = sp->sn_line_hl; - break; - case SIGN_NUMHL: - sign_hl = sp->sn_num_hl; - break; - default: - abort(); - } - if (sign_hl > 0) { - return syn_id2attr(sign_hl); - } - break; - } - } - return 0; -} - -/// Get text mark for sign "typenr". -/// Returns NULL if there isn't one. -char_u * sign_get_text(int typenr) -{ - sign_T *sp; - - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (sp->sn_typenr == typenr) { - return sp->sn_text; - } - } - return NULL; -} - /// Undefine/free all signs. void free_signs(void) { @@ -1860,3 +1872,267 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) } } +/// Define a sign using the attributes in 'dict'. Returns 0 on success and -1 on +/// failure. +int sign_define_from_dict(const char *name_arg, dict_T *dict) +{ + char *name = NULL; + char *icon = NULL; + char *linehl = NULL; + char *text = NULL; + char *texthl = NULL; + char *numhl = NULL; + int retval = -1; + + if (name_arg == NULL) { + if (dict == NULL) { + return -1; + } + name = tv_dict_get_string(dict, "name", true); + } else { + name = xstrdup(name_arg); + } + if (name == NULL || name[0] == NUL) { + goto cleanup; + } + if (dict != NULL) { + icon = tv_dict_get_string(dict, "icon" , true); + linehl = tv_dict_get_string(dict, "linehl", true); + text = tv_dict_get_string(dict, "text" , true); + texthl = tv_dict_get_string(dict, "texthl", true); + numhl = tv_dict_get_string(dict, "numhl" , true); + } + + if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, + (char_u *)text, (char_u *)texthl, (char_u *)numhl) + == OK) { + retval = 0; + } + +cleanup: + xfree(name); + xfree(icon); + xfree(linehl); + xfree(text); + xfree(texthl); + xfree(numhl); + + return retval; +} + +/// Define multiple signs using attributes from list 'l' and store the return +/// values in 'retlist'. +void sign_define_multiple(list_T *l, list_T *retlist) +{ + int retval; + + TV_LIST_ITER_CONST(l, li, { + retval = -1; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { + retval = sign_define_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); + } else { + EMSG(_(e_dictreq)); + } + tv_list_append_number(retlist, retval); + }); +} + +/// Place a new sign using the values specified in dict 'dict'. Returns the sign +/// identifier if successfully placed, otherwise returns 0. +int sign_place_from_dict( + typval_T *id_tv, + typval_T *group_tv, + typval_T *name_tv, + typval_T *buf_tv, + dict_T *dict) +{ + int sign_id = 0; + char_u *group = NULL; + char_u *sign_name = NULL; + buf_T *buf = NULL; + dictitem_T *di; + linenr_T lnum = 0; + int prio = SIGN_DEF_PRIO; + bool notanum = false; + int ret_sign_id = -1; + + // sign identifier + if (id_tv == NULL) { + di = tv_dict_find(dict, "id", -1); + if (di != NULL) { + id_tv = &di->di_tv; + } + } + if (id_tv == NULL) { + sign_id = 0; + } else { + sign_id = (int)tv_get_number_chk(id_tv, ¬anum); + if (notanum) { + return -1; + } + if (sign_id < 0) { + EMSG(_(e_invarg)); + return -1; + } + } + + // sign group + if (group_tv == NULL) { + di = tv_dict_find(dict, "group", -1); + if (di != NULL) { + group_tv = &di->di_tv; + } + } + if (group_tv == NULL) { + group = NULL; // global group + } else { + group = (char_u *)tv_get_string_chk(group_tv); + if (group == NULL) { + goto cleanup; + } + if (group[0] == '\0') { // global sign group + group = NULL; + } else { + group = vim_strsave(group); + if (group == NULL) { + return -1; + } + } + } + + // sign name + if (name_tv == NULL) { + di = tv_dict_find(dict, "name", -1); + if (di != NULL) { + name_tv = &di->di_tv; + } + } + if (name_tv == NULL) { + goto cleanup; + } + sign_name = (char_u *)tv_get_string_chk(name_tv); + if (sign_name == NULL) { + goto cleanup; + } + + // buffer to place the sign + if (buf_tv == NULL) { + di = tv_dict_find(dict, "buffer", -1); + if (di != NULL) { + buf_tv = &di->di_tv; + } + } + if (buf_tv == NULL) { + goto cleanup; + } + buf = get_buf_arg(buf_tv); + if (buf == NULL) { + goto cleanup; + } + + // line number of the sign + di = tv_dict_find(dict, "lnum", -1); + if (di != NULL) { + lnum = tv_get_lnum(&di->di_tv); + if (lnum <= 0) { + EMSG(_(e_invarg)); + goto cleanup; + } + } + + // sign priority + di = tv_dict_find(dict, "priority", -1); + if (di != NULL) { + prio = (int)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + goto cleanup; + } + } + + if (sign_place(&sign_id, group, sign_name, buf, lnum, prio) == OK) { + ret_sign_id = sign_id; + } + +cleanup: + xfree(group); + + return ret_sign_id; +} + +/// Undefine multiple signs +void sign_undefine_multiple(list_T *l, list_T *retlist) +{ + char_u *name; + int retval; + + TV_LIST_ITER_CONST(l, li, { + retval = -1; + name = (char_u *)tv_get_string_chk(TV_LIST_ITEM_TV(li)); + if (name != NULL && (sign_undefine_by_name(name) == OK)) { + retval = 0; + } + tv_list_append_number(retlist, retval); + }); +} + +/// Unplace the sign with attributes specified in 'dict'. Returns 0 on success +/// and -1 on failure. +int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict) +{ + dictitem_T *di; + int sign_id = 0; + buf_T *buf = NULL; + char_u *group = NULL; + int retval = -1; + + // sign group + if (group_tv != NULL) { + group = (char_u *)tv_get_string(group_tv); + } else { + group = (char_u *)tv_dict_get_string(dict, "group", false); + } + if (group != NULL) { + if (group[0] == '\0') { // global sign group + group = NULL; + } else { + group = vim_strsave(group); + if (group == NULL) { + return -1; + } + } + } + + if (dict != NULL) { + if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) { + buf = get_buf_arg(&di->di_tv); + if (buf == NULL) { + goto cleanup; + } + } + if (tv_dict_find(dict, "id", -1) != NULL) { + sign_id = (int)tv_dict_get_number(dict, "id"); + if (sign_id <= 0) { + EMSG(_(e_invarg)); + goto cleanup; + } + } + } + + if (buf == NULL) { + // Delete the sign in all the buffers + retval = 0; + FOR_ALL_BUFFERS(buf2) { + if (sign_unplace(sign_id, group, buf2, 0) != OK) { + retval = -1; + } + } + } else if (sign_unplace(sign_id, group, buf, 0) == OK) { + retval = 0; + } + +cleanup: + xfree(group); + + return retval; +} + diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index 19c0263cf1..721b2db25b 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -10,39 +10,47 @@ // Sign group typedef struct signgroup_S { - uint16_t refcount; // number of signs in this group - int next_sign_id; // next sign id for this group - char_u sg_name[1]; // sign group name + uint16_t sg_refcount; // number of signs in this group + int sg_next_sign_id; // next sign id for this group + char_u sg_name[1]; // sign group name } signgroup_T; // Macros to get the sign group structure from the group name #define SGN_KEY_OFF offsetof(signgroup_T, sg_name) #define HI2SG(hi) ((signgroup_T *)((hi)->hi_key - SGN_KEY_OFF)) -typedef struct signlist signlist_T; - -struct signlist -{ - int id; // unique identifier for each placed sign - int typenr; // typenr of sign - int priority; // priority for highlighting - bool has_text_or_icon; // has text or icon - linenr_T lnum; // line number which has this sign - signgroup_T *group; // sign group - signlist_T *next; // next signlist entry - signlist_T *prev; // previous entry -- for easy reordering +typedef struct sign_entry sign_entry_T; + +struct sign_entry { + int se_id; // unique identifier for each placed sign + int se_typenr; // typenr of sign + int se_priority; // priority for highlighting + bool se_has_text_or_icon; // has text or icon + linenr_T se_lnum; // line number which has this sign + signgroup_T *se_group; // sign group + sign_entry_T *se_next; // next entry in a list of signs + sign_entry_T *se_prev; // previous entry -- for easy reordering }; +/// Sign attributes. Used by the screen refresh routines. +typedef struct sign_attrs_S { + int sat_typenr; + char_u *sat_text; + int sat_texthl; + int sat_linehl; + int sat_numhl; +} sign_attrs_T; + +#define SIGN_SHOW_MAX 9 + // Default sign priority for highlighting #define SIGN_DEF_PRIO 10 -// type argument for buf_getsigntype() and sign_get_attr() +// type argument for sign_get_attr() typedef enum { - SIGN_ANY, SIGN_LINEHL, - SIGN_ICON, - SIGN_TEXT, SIGN_NUMHL, + SIGN_TEXT, } SignType; diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 55f9594de2..f6dc3a04a7 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -3035,7 +3035,7 @@ void ex_spellrepall(exarg_T *eap) sub_nlines = 0; curwin->w_cursor.lnum = 0; while (!got_int) { - if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL) == 0 + if (do_search(NULL, '/', '/', frompat, 1L, SEARCH_KEEP, NULL) == 0 || u_save_cursor() == FAIL) { break; } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index f1eb7879b0..825aef1465 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6047,6 +6047,8 @@ static const char *highlight_init_both[] = { "default link MsgSeparator StatusLine", "default link NormalFloat Pmenu", "default link FloatBorder VertSplit", + "default FloatShadow blend=80 guibg=Black", + "default FloatShadowThrough blend=100 guibg=Black", "RedrawDebugNormal cterm=reverse gui=reverse", "RedrawDebugClear ctermbg=Yellow guibg=Yellow", "RedrawDebugComposed ctermbg=Green guibg=Green", diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h index 9fbad74f64..38f848f178 100644 --- a/src/nvim/syntax.h +++ b/src/nvim/syntax.h @@ -27,6 +27,8 @@ #define HL_CONCEAL 0x20000 /* can be concealed */ #define HL_CONCEALENDS 0x40000 /* can be concealed */ +#define SYN_GROUP_STATIC(s) syn_check_group((char_u *)S_LEN(s)) + typedef struct { char *name; RgbValue color; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 6b8f393572..588821f260 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -2811,7 +2811,7 @@ static int jumpto_tag( // start search before first line curwin->w_cursor.lnum = 0; } - if (do_search(NULL, pbuf[0], pbuf + 1, (long)1, + if (do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1, search_options, NULL)) { retval = OK; } else { @@ -2821,8 +2821,8 @@ static int jumpto_tag( /* * try again, ignore case now */ - p_ic = TRUE; - if (!do_search(NULL, pbuf[0], pbuf + 1, (long)1, + p_ic = true; + if (!do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1, search_options, NULL)) { // Failed to find pattern, take a guess: "^func (" found = 2; @@ -2830,11 +2830,12 @@ static int jumpto_tag( cc = *tagp.tagname_end; *tagp.tagname_end = NUL; snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname); - if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) { + if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) { // Guess again: "^char * \<func (" snprintf((char *)pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", tagp.tagname); - if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) { + if (!do_search(NULL, '/', '/', pbuf, (long)1, + search_options, NULL)) { found = 0; } } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 913ef3baed..afad20f557 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -580,6 +580,9 @@ static bool is_filter_char(int c) void terminal_paste(long count, char_u **y_array, size_t y_size) { + if (y_size == 0) { + return; + } vterm_keyboard_start_paste(curbuf->terminal->vt); terminal_flush_output(curbuf->terminal); size_t buff_len = STRLEN(y_array[0]); diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 2d94b637e0..49993c03aa 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -13,6 +13,9 @@ " For csh: " setenv TEST_FILTER Test_channel " +" While working on a test you can make $TEST_NO_RETRY non-empty to not retry: +" export TEST_NO_RETRY=yes +" " To ignore failure for tests that are known to fail in a certain environment, " set $TEST_MAY_FAIL to a comma separated list of function names. E.g. for " sh/bash: @@ -413,9 +416,11 @@ for s:test in sort(s:tests) call RunTheTest(s:test) " Repeat a flaky test. Give up when: + " - $TEST_NO_RETRY is not empty " - it fails again with the same message " - it fails five times (with a different message) if len(v:errors) > 0 + \ && $TEST_NO_RETRY == '' \ && (index(s:flaky_tests, s:test) >= 0 \ || g:test_is_flaky) while 1 diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 71af3eead7..e50602ccad 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -35,6 +35,7 @@ source test_popup.vim source test_put.vim source test_rename.vim source test_scroll_opt.vim +source test_shift.vim source test_sort.vim source test_sha256.vim source test_suspend.vim diff --git a/src/nvim/testdir/test_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim index be0bd01413..70f14320a6 100644 --- a/src/nvim/testdir/test_alot_utf8.vim +++ b/src/nvim/testdir/test_alot_utf8.vim @@ -6,7 +6,6 @@ source test_charsearch_utf8.vim source test_expr_utf8.vim -source test_listlbr_utf8.vim source test_matchadd_conceal_utf8.vim source test_mksession_utf8.vim source test_regexp_utf8.vim diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index d361205baa..c3de7d0050 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -60,10 +60,10 @@ func Test_compiler_completion() call assert_match('^"compiler ' .. clist .. '$', @:) call feedkeys(":compiler p\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"compiler pbx perl php pylint pyunit', @:) + call assert_match('"compiler pbx perl\( p[a-z]\+\)\+ pylint pyunit', @:) call feedkeys(":compiler! p\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"compiler! pbx perl php pylint pyunit', @:) + call assert_match('"compiler! pbx perl\( p[a-z]\+\)\+ pylint pyunit', @:) endfunc func Test_compiler_error() diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim index 59d51b855b..d1464e9d3b 100644 --- a/src/nvim/testdir/test_debugger.vim +++ b/src/nvim/testdir/test_debugger.vim @@ -2,6 +2,31 @@ source shared.vim source screendump.vim +source check.vim + +func CheckCWD() + " Check that the longer lines don't wrap due to the length of the script name + " in cwd + let script_len = len( getcwd() .. '/Xtest1.vim' ) + let longest_line = len( 'Breakpoint in "" line 1' ) + if script_len > ( 75 - longest_line ) + throw 'Skipped: Your CWD has too many characters' + endif +endfunc +command! -nargs=0 -bar CheckCWD call CheckCWD() + +func CheckDbgOutput(buf, lines, options = {}) + " Verify the expected output + let lnum = 20 - len(a:lines) + for l in a:lines + if get(a:options, 'match', 'equal') ==# 'pattern' + call WaitForAssert({-> assert_match(l, term_getline(a:buf, lnum))}, 200) + else + call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}, 200) + endif + let lnum += 1 + endfor +endfunc " Run a Vim debugger command " If the expected output argument is supplied, then check for it. @@ -10,20 +35,17 @@ func RunDbgCmd(buf, cmd, ...) call term_wait(a:buf) if a:0 != 0 - " Verify the expected output - let lnum = 20 - len(a:1) - for l in a:1 - call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}) - let lnum += 1 - endfor + let options = #{match: 'equal'} + if a:0 > 1 + call extend(options, a:2) + endif + call CheckDbgOutput(a:buf, a:1, options) endif endfunc " Debugger tests func Test_Debugger() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run Vim in a terminal window' - endif + CheckRunVimInTerminal " Create a Vim script with some functions let lines =<< trim END @@ -317,6 +339,785 @@ func Test_Debugger() call delete('Xtest.vim') endfunc +func Test_Backtrace_Through_Source() + CheckRunVimInTerminal + CheckCWD + let file1 =<< trim END + func SourceAnotherFile() + source Xtest2.vim + endfunc + + func CallAFunction() + call SourceAnotherFile() + call File2Function() + endfunc + + func GlobalFunction() + call CallAFunction() + endfunc + END + call writefile(file1, 'Xtest1.vim') + + let file2 =<< trim END + func DoAThing() + echo "DoAThing" + endfunc + + func File2Function() + call DoAThing() + endfunc + + call File2Function() + END + call writefile(file2, 'Xtest2.vim') + + let buf = RunVimInTerminal('-S Xtest1.vim', {}) + + call RunDbgCmd(buf, + \ ':debug call GlobalFunction()', + \ ['cmd: call GlobalFunction()']) + call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()']) + + call RunDbgCmd(buf, 'backtrace', ['>backtrace', + \ '->0 function GlobalFunction', + \ 'line 1: call CallAFunction()']) + + call RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()']) + call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim']) + + call RunDbgCmd(buf, 'backtrace', ['>backtrace', + \ ' 2 function GlobalFunction[1]', + \ ' 1 CallAFunction[1]', + \ '->0 SourceAnotherFile', + \ 'line 1: source Xtest2.vim']) + + " Step into the 'source' command. Note that we print the full trace all the + " way though the source command. + call RunDbgCmd(buf, 'step', ['line 1: func DoAThing()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ '->0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()']) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ '->1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ '->2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ '->3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up', [ 'frame at highest level: 3' ] ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ '->3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ '->2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ '->1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ '->0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down', [ 'frame is zero' ] ) + + " step until we have another meaninfgul trace + call RunDbgCmd(buf, 'step', ['line 5: func File2Function()']) + call RunDbgCmd(buf, 'step', ['line 9: call File2Function()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ '->0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 9: call File2Function()']) + + call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()']) + call RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 5 function GlobalFunction[1]', + \ ' 4 CallAFunction[1]', + \ ' 3 SourceAnotherFile[1]', + \ ' 2 script ' .. getcwd() .. '/Xtest2.vim[9]', + \ ' 1 function File2Function[1]', + \ '->0 DoAThing', + \ 'line 1: echo "DoAThing"']) + + " Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim + call RunDbgCmd(buf, 'step', ['line 1: End of function']) + call RunDbgCmd(buf, 'step', ['line 1: End of function']) + call RunDbgCmd(buf, 'step', ['line 10: End of sourced file']) + call RunDbgCmd(buf, 'step', ['line 1: End of function']) + call RunDbgCmd(buf, 'step', ['line 2: call File2Function()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 1 function GlobalFunction[1]', + \ '->0 CallAFunction', + \ 'line 2: call File2Function()']) + + call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 2 function GlobalFunction[1]', + \ ' 1 CallAFunction[2]', + \ '->0 File2Function', + \ 'line 1: call DoAThing()']) + + call StopVimInTerminal(buf) + call delete('Xtest1.vim') + call delete('Xtest2.vim') +endfunc + +func Test_Backtrace_Autocmd() + CheckRunVimInTerminal + CheckCWD + let file1 =<< trim END + func SourceAnotherFile() + source Xtest2.vim + endfunc + + func CallAFunction() + call SourceAnotherFile() + call File2Function() + endfunc + + func GlobalFunction() + call CallAFunction() + endfunc + + au User TestGlobalFunction :call GlobalFunction() | echo "Done" + END + call writefile(file1, 'Xtest1.vim') + + let file2 =<< trim END + func DoAThing() + echo "DoAThing" + endfunc + + func File2Function() + call DoAThing() + endfunc + + call File2Function() + END + call writefile(file2, 'Xtest2.vim') + + let buf = RunVimInTerminal('-S Xtest1.vim', {}) + + call RunDbgCmd(buf, + \ ':debug doautocmd User TestGlobalFunction', + \ ['cmd: doautocmd User TestGlobalFunction']) + call RunDbgCmd(buf, 'step', ['cmd: call GlobalFunction() | echo "Done"']) + + " At this point the ontly thing in the stack is the autocommand + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ '->0 User Autocommands for "TestGlobalFunction"', + \ 'cmd: call GlobalFunction() | echo "Done"']) + + " And now we're back into the call stack + call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 1 User Autocommands for "TestGlobalFunction"', + \ '->0 function GlobalFunction', + \ 'line 1: call CallAFunction()']) + + call RunDbgCmd(buf, 'step', ['line 1: call SourceAnotherFile()']) + call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim']) + + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 User Autocommands for "TestGlobalFunction"', + \ ' 2 function GlobalFunction[1]', + \ ' 1 CallAFunction[1]', + \ '->0 SourceAnotherFile', + \ 'line 1: source Xtest2.vim']) + + " Step into the 'source' command. Note that we print the full trace all the + " way though the source command. + call RunDbgCmd(buf, 'step', ['line 1: func DoAThing()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ '->0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()']) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ '->1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ '->2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ '->3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ '->4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'up', [ 'frame at highest level: 4' ] ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ '->4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ '->3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ '->2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ '->1 SourceAnotherFile[1]', + \ ' 0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down' ) + call RunDbgCmd( buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ '->0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 1: func DoAThing()' ] ) + + call RunDbgCmd( buf, 'down', [ 'frame is zero' ] ) + + " step until we have another meaninfgul trace + call RunDbgCmd(buf, 'step', ['line 5: func File2Function()']) + call RunDbgCmd(buf, 'step', ['line 9: call File2Function()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 4 User Autocommands for "TestGlobalFunction"', + \ ' 3 function GlobalFunction[1]', + \ ' 2 CallAFunction[1]', + \ ' 1 SourceAnotherFile[1]', + \ '->0 script ' .. getcwd() .. '/Xtest2.vim', + \ 'line 9: call File2Function()']) + + call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()']) + call RunDbgCmd(buf, 'step', ['line 1: echo "DoAThing"']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 6 User Autocommands for "TestGlobalFunction"', + \ ' 5 function GlobalFunction[1]', + \ ' 4 CallAFunction[1]', + \ ' 3 SourceAnotherFile[1]', + \ ' 2 script ' .. getcwd() .. '/Xtest2.vim[9]', + \ ' 1 function File2Function[1]', + \ '->0 DoAThing', + \ 'line 1: echo "DoAThing"']) + + " Now, step (back to Xfile1.vim), and call the function _in_ Xfile2.vim + call RunDbgCmd(buf, 'step', ['line 1: End of function']) + call RunDbgCmd(buf, 'step', ['line 1: End of function']) + call RunDbgCmd(buf, 'step', ['line 10: End of sourced file']) + call RunDbgCmd(buf, 'step', ['line 1: End of function']) + call RunDbgCmd(buf, 'step', ['line 2: call File2Function()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 2 User Autocommands for "TestGlobalFunction"', + \ ' 1 function GlobalFunction[1]', + \ '->0 CallAFunction', + \ 'line 2: call File2Function()']) + + call RunDbgCmd(buf, 'step', ['line 1: call DoAThing()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 User Autocommands for "TestGlobalFunction"', + \ ' 2 function GlobalFunction[1]', + \ ' 1 CallAFunction[2]', + \ '->0 File2Function', + \ 'line 1: call DoAThing()']) + + + " Now unwind so that we get back to the original autocommand (and the second + " cmd echo "Done") + call RunDbgCmd(buf, 'finish', ['line 1: End of function']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 3 User Autocommands for "TestGlobalFunction"', + \ ' 2 function GlobalFunction[1]', + \ ' 1 CallAFunction[2]', + \ '->0 File2Function', + \ 'line 1: End of function']) + + call RunDbgCmd(buf, 'finish', ['line 2: End of function']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 2 User Autocommands for "TestGlobalFunction"', + \ ' 1 function GlobalFunction[1]', + \ '->0 CallAFunction', + \ 'line 2: End of function']) + + call RunDbgCmd(buf, 'finish', ['line 1: End of function']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 1 User Autocommands for "TestGlobalFunction"', + \ '->0 function GlobalFunction', + \ 'line 1: End of function']) + + call RunDbgCmd(buf, 'step', ['cmd: echo "Done"']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ '->0 User Autocommands for "TestGlobalFunction"', + \ 'cmd: echo "Done"']) + + call StopVimInTerminal(buf) + call delete('Xtest1.vim') + call delete('Xtest2.vim') +endfunc + +func Test_Backtrace_CmdLine() + CheckRunVimInTerminal + CheckCWD + let file1 =<< trim END + func SourceAnotherFile() + source Xtest2.vim + endfunc + + func CallAFunction() + call SourceAnotherFile() + call File2Function() + endfunc + + func GlobalFunction() + call CallAFunction() + endfunc + + au User TestGlobalFunction :call GlobalFunction() | echo "Done" + END + call writefile(file1, 'Xtest1.vim') + + let file2 =<< trim END + func DoAThing() + echo "DoAThing" + endfunc + + func File2Function() + call DoAThing() + endfunc + + call File2Function() + END + call writefile(file2, 'Xtest2.vim') + + let buf = RunVimInTerminal( + \ '-S Xtest1.vim -c "debug call GlobalFunction()"', + \ {'wait_for_ruler': 0}) + + " Need to wait for the vim-in-terminal to be ready + call CheckDbgOutput(buf, ['command line', + \ 'cmd: call GlobalFunction()']) + + " At this point the ontly thing in the stack is the cmdline + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ '->0 command line', + \ 'cmd: call GlobalFunction()']) + + " And now we're back into the call stack + call RunDbgCmd(buf, 'step', ['line 1: call CallAFunction()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '>backtrace', + \ ' 1 command line', + \ '->0 function GlobalFunction', + \ 'line 1: call CallAFunction()']) + + call StopVimInTerminal(buf) + call delete('Xtest1.vim') + call delete('Xtest2.vim') +endfunc + +func Test_Backtrace_DefFunction() + CheckRunVimInTerminal + CheckCWD + let file1 =<< trim END + vim9script + import File2Function from './Xtest2.vim' + + def SourceAnotherFile() + source Xtest2.vim + enddef + + def CallAFunction() + SourceAnotherFile() + File2Function() + enddef + + def g:GlobalFunction() + CallAFunction() + enddef + + defcompile + END + call writefile(file1, 'Xtest1.vim') + + let file2 =<< trim END + vim9script + + def DoAThing(): number + var a = 100 * 2 + a += 3 + return a + enddef + + export def File2Function() + DoAThing() + enddef + + defcompile + File2Function() + END + call writefile(file2, 'Xtest2.vim') + + let buf = RunVimInTerminal('-S Xtest1.vim', {}) + + call RunDbgCmd(buf, + \ ':debug call GlobalFunction()', + \ ['cmd: call GlobalFunction()']) + + " FIXME: Vim9 lines are not debugged! + call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim']) + + " But they do appear in the backtrace + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 2 function GlobalFunction[1]', + \ '\V 1 <SNR>\.\*_CallAFunction[1]', + \ '\V->0 <SNR>\.\*_SourceAnotherFile', + \ '\Vline 1: source Xtest2.vim'], + \ #{match: 'pattern'}) + + + call RunDbgCmd(buf, 'step', ['line 1: vim9script']) + call RunDbgCmd(buf, 'step', ['line 3: def DoAThing(): number']) + call RunDbgCmd(buf, 'step', ['line 9: export def File2Function()']) + call RunDbgCmd(buf, 'step', ['line 9: def File2Function()']) + call RunDbgCmd(buf, 'step', ['line 13: defcompile']) + call RunDbgCmd(buf, 'step', ['line 14: File2Function()']) + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 3 function GlobalFunction[1]', + \ '\V 2 <SNR>\.\*_CallAFunction[1]', + \ '\V 1 <SNR>\.\*_SourceAnotherFile[1]', + \ '\V->0 script ' .. getcwd() .. '/Xtest2.vim', + \ '\Vline 14: File2Function()'], + \ #{match: 'pattern'}) + + " Don't step into compiled functions... + call RunDbgCmd(buf, 'step', ['line 15: End of sourced file']) + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 3 function GlobalFunction[1]', + \ '\V 2 <SNR>\.\*_CallAFunction[1]', + \ '\V 1 <SNR>\.\*_SourceAnotherFile[1]', + \ '\V->0 script ' .. getcwd() .. '/Xtest2.vim', + \ '\Vline 15: End of sourced file'], + \ #{match: 'pattern'}) + + + call StopVimInTerminal(buf) + call delete('Xtest1.vim') + call delete('Xtest2.vim') +endfunc + +func Test_debug_backtrace_level() + CheckRunVimInTerminal + CheckCWD + let lines =<< trim END + let s:file1_var = 'file1' + let g:global_var = 'global' + + func s:File1Func( arg ) + let s:file1_var .= a:arg + let local_var = s:file1_var .. ' test1' + let g:global_var .= local_var + source Xtest2.vim + endfunc + + call s:File1Func( 'arg1' ) + END + call writefile(lines, 'Xtest1.vim') + + let lines =<< trim END + let s:file2_var = 'file2' + + func s:File2Func( arg ) + let s:file2_var .= a:arg + let local_var = s:file2_var .. ' test2' + let g:global_var .= local_var + endfunc + + call s:File2Func( 'arg2' ) + END + call writefile(lines, 'Xtest2.vim') + + let file1 = getcwd() .. '/Xtest1.vim' + let file2 = getcwd() .. '/Xtest2.vim' + + " set a breakpoint and source file1.vim + let buf = RunVimInTerminal( + \ '-c "breakadd file 1 Xtest1.vim" -S Xtest1.vim', + \ #{ wait_for_ruler: 0 } ) + + call CheckDbgOutput(buf, [ + \ 'Breakpoint in "' .. file1 .. '" line 1', + \ 'Entering Debug mode. Type "cont" to continue.', + \ 'command line..script ' .. file1, + \ 'line 1: let s:file1_var = ''file1''' + \ ]) + + " step throught the initial declarations + call RunDbgCmd(buf, 'step', [ 'line 2: let g:global_var = ''global''' ] ) + call RunDbgCmd(buf, 'step', [ 'line 4: func s:File1Func( arg )' ] ) + call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] ) + call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] ) + call RunDbgCmd(buf, 'echo global_var', [ 'global' ] ) + + " step in to the first function + call RunDbgCmd(buf, 'step', [ 'line 11: call s:File1Func( ''arg1'' )' ] ) + call RunDbgCmd(buf, 'step', [ 'line 1: let s:file1_var .= a:arg' ] ) + call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] ) + call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] ) + call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] ) + call RunDbgCmd(buf, + \'echo global_var', + \[ 'E121: Undefined variable: global_var' ] ) + call RunDbgCmd(buf, + \'echo local_var', + \[ 'E121: Undefined variable: local_var' ] ) + call RunDbgCmd(buf, + \'echo l:local_var', + \[ 'E121: Undefined variable: l:local_var' ] ) + + " backtrace up + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 2 command line', + \ '\V 1 script ' .. file1 .. '[11]', + \ '\V->0 function <SNR>\.\*_File1Func', + \ '\Vline 1: let s:file1_var .= a:arg', + \ ], + \ #{ match: 'pattern' } ) + call RunDbgCmd(buf, 'up', [ '>up' ] ) + + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 2 command line', + \ '\V->1 script ' .. file1 .. '[11]', + \ '\V 0 function <SNR>\.\*_File1Func', + \ '\Vline 1: let s:file1_var .= a:arg', + \ ], + \ #{ match: 'pattern' } ) + + " Expression evaluation in the script frame (not the function frame) + " FIXME: Unexpected in this scope (a: should not be visibnle) + call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] ) + call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] ) + call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] ) + " FIXME: Unexpected in this scope (global should be found) + call RunDbgCmd(buf, + \'echo global_var', + \[ 'E121: Undefined variable: global_var' ] ) + call RunDbgCmd(buf, + \'echo local_var', + \[ 'E121: Undefined variable: local_var' ] ) + call RunDbgCmd(buf, + \'echo l:local_var', + \[ 'E121: Undefined variable: l:local_var' ] ) + + + " step while backtraced jumps to the latest frame + call RunDbgCmd(buf, 'step', [ + \ 'line 2: let local_var = s:file1_var .. '' test1''' ] ) + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 2 command line', + \ '\V 1 script ' .. file1 .. '[11]', + \ '\V->0 function <SNR>\.\*_File1Func', + \ '\Vline 2: let local_var = s:file1_var .. '' test1''', + \ ], + \ #{ match: 'pattern' } ) + + call RunDbgCmd(buf, 'step', [ 'line 3: let g:global_var .= local_var' ] ) + call RunDbgCmd(buf, 'echo local_var', [ 'file1arg1 test1' ] ) + call RunDbgCmd(buf, 'echo l:local_var', [ 'file1arg1 test1' ] ) + + call RunDbgCmd(buf, 'step', [ 'line 4: source Xtest2.vim' ] ) + call RunDbgCmd(buf, 'step', [ 'line 1: let s:file2_var = ''file2''' ] ) + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 3 command line', + \ '\V 2 script ' .. file1 .. '[11]', + \ '\V 1 function <SNR>\.\*_File1Func[4]', + \ '\V->0 script ' .. file2, + \ '\Vline 1: let s:file2_var = ''file2''', + \ ], + \ #{ match: 'pattern' } ) + + " Expression evaluation in the script frame file2 (not the function frame) + call RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] ) + call RunDbgCmd(buf, + \ 'echo s:file1_var', + \ [ 'E121: Undefined variable: s:file1_var' ] ) + call RunDbgCmd(buf, 'echo g:global_var', [ 'globalfile1arg1 test1' ] ) + call RunDbgCmd(buf, 'echo global_var', [ 'globalfile1arg1 test1' ] ) + call RunDbgCmd(buf, + \'echo local_var', + \[ 'E121: Undefined variable: local_var' ] ) + call RunDbgCmd(buf, + \'echo l:local_var', + \[ 'E121: Undefined variable: l:local_var' ] ) + call RunDbgCmd(buf, + \ 'echo s:file2_var', + \ [ 'E121: Undefined variable: s:file2_var' ] ) + + call RunDbgCmd(buf, 'step', [ 'line 3: func s:File2Func( arg )' ] ) + call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] ) + + " Up the stack to the other script context + call RunDbgCmd(buf, 'up') + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 3 command line', + \ '\V 2 script ' .. file1 .. '[11]', + \ '\V->1 function <SNR>\.\*_File1Func[4]', + \ '\V 0 script ' .. file2, + \ '\Vline 3: func s:File2Func( arg )', + \ ], + \ #{ match: 'pattern' } ) + " FIXME: Unexpected. Should see the a: and l: dicts from File1Func + call RunDbgCmd(buf, 'echo a:arg', [ 'E121: Undefined variable: a:arg' ] ) + call RunDbgCmd(buf, + \ 'echo l:local_var', + \ [ 'E121: Undefined variable: l:local_var' ] ) + + call RunDbgCmd(buf, 'up') + call RunDbgCmd(buf, 'backtrace', [ + \ '\V>backtrace', + \ '\V 3 command line', + \ '\V->2 script ' .. file1 .. '[11]', + \ '\V 1 function <SNR>\.\*_File1Func[4]', + \ '\V 0 script ' .. file2, + \ '\Vline 3: func s:File2Func( arg )', + \ ], + \ #{ match: 'pattern' } ) + + " FIXME: Unexpected (wrong script vars are used) + call RunDbgCmd(buf, + \ 'echo s:file1_var', + \ [ 'E121: Undefined variable: s:file1_var' ] ) + call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] ) + + call StopVimInTerminal(buf) + call delete('Xtest1.vim') + call delete('Xtest2.vim') +endfunc + " Test for setting a breakpoint on a :endif where the :if condition is false " and then quit the script. This should generate an interrupt. func Test_breakpt_endif_intr() diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 44b8479621..1a98dc6451 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -371,6 +371,8 @@ let s:filename_checks = { \ 'promela': ['file.pml'], \ 'proto': ['file.proto'], \ 'protocols': ['/etc/protocols'], + \ 'ps1': ['file.ps1', 'file.psd1', 'file.psm1', 'file.pssc'], + \ 'ps1xml': ['file.ps1xml'], \ 'psf': ['file.psf'], \ 'puppet': ['file.pp'], \ 'pyrex': ['file.pyx', 'file.pxd'], @@ -415,7 +417,7 @@ let s:filename_checks = { \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf'], \ 'services': ['/etc/services'], \ 'setserial': ['/etc/serial.conf'], - \ 'sh': ['/etc/udev/cdsymlinks.conf'], + \ 'sh': ['.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'], \ 'sieve': ['file.siv', 'file.sieve'], \ 'simula': ['file.sim'], \ 'sinda': ['file.sin', 'file.s85'], @@ -469,7 +471,7 @@ let s:filename_checks = { \ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'], \ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'], \ 'texmf': ['texmf.cnf'], - \ 'text': ['file.text', 'README'], + \ 'text': ['file.text', 'README', '/usr/share/doc/bash-completion/AUTHORS'], \ 'tf': ['file.tf', '.tfrc', 'tfrc'], \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'], \ 'tilde': ['file.t.html'], @@ -521,7 +523,7 @@ let s:filename_checks = { \ 'xhtml': ['file.xhtml', 'file.xht'], \ 'xinetd': ['/etc/xinetd.conf'], \ 'xmath': ['file.msc', 'file.msf'], - \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss'], + \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1'], \ 'xmodmap': ['anyXmodmap'], \ 'xf86conf': ['xorg.conf', 'xorg.conf-4'], \ 'xpm2': ['file.xpm2'], diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 2d058e8e32..fcdf888b96 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -823,31 +823,36 @@ func Test_fold_create_delete() endfunc func Test_fold_relative_move() - enew! + new set fdm=indent sw=2 wrap tw=80 - let content = [ ' foo', ' bar', ' baz', - \ repeat('x', &columns + 1), - \ ' foo', ' bar', ' baz' + let longtext = repeat('x', &columns + 1) + let content = [ ' foo', ' ' .. longtext, ' baz', + \ longtext, + \ ' foo', ' ' .. longtext, ' baz' \ ] call append(0, content) normal zM - call cursor(3, 1) - call assert_true(foldclosed(line('.'))) - normal gj - call assert_equal(2, winline()) + for lnum in range(1, 3) + call cursor(lnum, 1) + call assert_true(foldclosed(line('.'))) + normal gj + call assert_equal(2, winline()) + endfor call cursor(2, 1) call assert_true(foldclosed(line('.'))) normal 2gj call assert_equal(3, winline()) - call cursor(5, 1) - call assert_true(foldclosed(line('.'))) - normal gk - call assert_equal(3, winline()) + for lnum in range(5, 7) + call cursor(lnum, 1) + call assert_true(foldclosed(line('.'))) + normal gk + call assert_equal(3, winline()) + endfor call cursor(6, 1) call assert_true(foldclosed(line('.'))) diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index 8e2a987e74..affb141a26 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -506,6 +506,15 @@ func Test_dict_lock_extend() call assert_equal({'a': 99, 'b': 100}, d) endfunc +" Cannot use += with a locked dict +func Test_dict_lock_operator() + unlet! d + let d = {} + lockvar d + call assert_fails("let d += {'k' : 10}", 'E741:') + unlockvar d +endfunc + " No remove() of write-protected scope-level variable func! Tfunc(this_is_a_long_parameter_name) call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E742') @@ -709,6 +718,23 @@ func Test_listdict_extend() call assert_fails("call extend([1, 2], 1)", 'E712:') call assert_fails("call extend([1, 2], {})", 'E712:') + + " Extend g: dictionary with an invalid variable name + call assert_fails("call extend(g:, {'-!' : 10})", 'E461:') + + " Extend a list with itself. + let l = [1, 5, 7] + call extend(l, l, 0) + call assert_equal([1, 5, 7, 1, 5, 7], l) + let l = [1, 5, 7] + call extend(l, l, 1) + call assert_equal([1, 1, 5, 7, 5, 7], l) + let l = [1, 5, 7] + call extend(l, l, 2) + call assert_equal([1, 5, 1, 5, 7, 7], l) + let l = [1, 5, 7] + call extend(l, l, 3) + call assert_equal([1, 5, 7, 1, 5, 7], l) endfunc func s:check_scope_dict(x, fixed) @@ -782,3 +808,40 @@ func Test_scope_dict() " Test for v: call s:check_scope_dict('v', v:true) endfunc + +" Test for a null list +func Test_null_list() + let l = v:_null_list + call assert_equal('', join(l)) + call assert_equal(0, len(l)) + call assert_equal(1, empty(l)) + call assert_fails('let s = join([1, 2], [])', 'E730:') + call assert_equal([], split(v:_null_string)) + call assert_equal([], l[:2]) + call assert_true([] == l) + call assert_equal('[]', string(l)) + " call assert_equal(0, sort(l)) + " call assert_equal(0, sort(l)) + " call assert_equal(0, uniq(l)) + let k = [] + l + call assert_equal([], k) + let k = l + [] + call assert_equal([], k) + call assert_equal(0, len(copy(l))) + call assert_equal(0, count(l, 5)) + call assert_equal([], deepcopy(l)) + call assert_equal(5, get(l, 2, 5)) + call assert_equal(-1, index(l, 2, 5)) + " call assert_equal(0, insert(l, 2, -1)) + call assert_equal(0, min(l)) + call assert_equal(0, max(l)) + " call assert_equal(0, remove(l, 0, 2)) + call assert_equal([], repeat(l, 2)) + " call assert_equal(0, reverse(l)) + " call assert_equal(0, sort(l)) + call assert_equal('[]', string(l)) + " call assert_equal(0, extend(l, l, 0)) + lockvar l + call assert_equal(1, islocked('l')) + unlockvar l +endfunc diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim index 055d944b15..de6d4aa359 100644 --- a/src/nvim/testdir/test_menu.vim +++ b/src/nvim/testdir/test_menu.vim @@ -11,7 +11,13 @@ func Test_load_menu() call assert_report('error while loading menus: ' . v:exception) endtry call assert_match('browse confirm w', execute(':menu File.Save')) + + let v:errmsg = '' + doautocmd LoadBufferMenu VimEnter + call assert_equal('', v:errmsg) + source $VIMRUNTIME/delmenu.vim + call assert_equal('', v:errmsg) endfunc func Test_translate_menu() diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index d4d529e4b9..75d42b986b 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -7,9 +7,8 @@ source check.vim " See test/functional/legacy/search_spec.lua func Test_search_cmdline() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) @@ -206,9 +205,8 @@ endfunc " See test/functional/legacy/search_spec.lua func Test_search_cmdline2() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) @@ -369,9 +367,8 @@ func Incsearch_cleanup() endfunc func Test_search_cmdline3() - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 " first match @@ -382,9 +379,8 @@ func Test_search_cmdline3() endfunc func Test_search_cmdline3s() - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 call feedkeys(":%s/the\<c-l>/xxx\<cr>", 'tx') @@ -408,9 +404,8 @@ func Test_search_cmdline3s() endfunc func Test_search_cmdline3g() - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 call feedkeys(":g/the\<c-l>/d\<cr>", 'tx') @@ -431,9 +426,8 @@ func Test_search_cmdline3g() endfunc func Test_search_cmdline3v() - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 call feedkeys(":v/the\<c-l>/d\<cr>", 'tx') @@ -450,9 +444,8 @@ endfunc " See test/functional/legacy/search_spec.lua func Test_search_cmdline4() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) @@ -484,9 +477,8 @@ func Test_search_cmdline4() endfunc func Test_search_cmdline5() - if !exists('+incsearch') - return - endif + CheckOption incsearch + " Do not call test_override("char_avail", 1) so that <C-g> and <C-t> work " regardless char_avail. new @@ -503,6 +495,46 @@ func Test_search_cmdline5() bw! endfunc +func Test_search_cmdline6() + " Test that consecutive matches + " are caught by <c-g>/<c-t> + CheckFunction test_override + CheckOption incsearch + + " need to disable char_avail, + " so that expansion of commandline works + call test_override("char_avail", 1) + new + call setline(1, [' bbvimb', '']) + set incsearch + " first match + norm! gg0 + call feedkeys("/b\<cr>", 'tx') + call assert_equal([0,1,2,0], getpos('.')) + " second match + norm! gg0 + call feedkeys("/b\<c-g>\<cr>", 'tx') + call assert_equal([0,1,3,0], getpos('.')) + " third match + norm! gg0 + call feedkeys("/b\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal([0,1,7,0], getpos('.')) + " first match again + norm! gg0 + call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal([0,1,2,0], getpos('.')) + set nowrapscan + " last match + norm! gg0 + call feedkeys("/b\<c-g>\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal([0,1,7,0], getpos('.')) + " clean up + set wrapscan&vim + set noincsearch + call test_override("char_avail", 0) + bw! +endfunc + func Test_search_cmdline7() CheckFunction test_override " Test that pressing <c-g> in an empty command line @@ -598,26 +630,226 @@ func Test_search_regexp() enew! endfunc -" Test for search('multi-byte char', 'bce') -func Test_search_multibyte() - let save_enc = &encoding - set encoding=utf8 - enew! - call append('$', 'A') - call cursor(2, 1) - call assert_equal(2, search('A', 'bce', line('.'))) - enew! - let &encoding = save_enc +func Test_search_cmdline_incsearch_highlight() + CheckFunction test_override + CheckOption incsearch + + set incsearch hlsearch + " need to disable char_avail, + " so that expansion of commandline works + call test_override("char_avail", 1) + new + call setline(1, ['aaa 1 the first', ' 2 the second', ' 3 the third']) + + 1 + call feedkeys("/second\<cr>", 'tx') + call assert_equal('second', @/) + call assert_equal(' 2 the second', getline('.')) + + " Canceling search won't change @/ + 1 + let @/ = 'last pattern' + call feedkeys("/third\<C-c>", 'tx') + call assert_equal('last pattern', @/) + call feedkeys("/third\<Esc>", 'tx') + call assert_equal('last pattern', @/) + call feedkeys("/3\<bs>\<bs>", 'tx') + call assert_equal('last pattern', @/) + call feedkeys("/third\<c-g>\<c-t>\<Esc>", 'tx') + call assert_equal('last pattern', @/) + + " clean up + set noincsearch nohlsearch + bw! endfunc -" Similar to Test_incsearch_substitute() but with a screendump halfway. -func Test_incsearch_substitute_dump() - if !exists('+incsearch') +func Test_search_cmdline_incsearch_highlight_attr() + CheckOption incsearch + CheckFeature terminal + CheckNotGui + + let h = winheight(0) + if h < 3 return endif + + " Prepare buffer text + let lines = ['abb vim vim vi', 'vimvivim'] + call writefile(lines, 'Xsearch.txt') + let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3}) + + call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])}) + " wait for vim to complete initialization + call term_wait(buf) + + " Get attr of normal(a0), incsearch(a1), hlsearch(a2) highlight + call term_sendkeys(buf, ":set incsearch hlsearch\<cr>") + call term_sendkeys(buf, '/b') + call term_wait(buf, 200) + let screen_line1 = term_scrape(buf, 1) + call assert_true(len(screen_line1) > 2) + " a0: attr_normal + let a0 = screen_line1[0].attr + " a1: attr_incsearch + let a1 = screen_line1[1].attr + " a2: attr_hlsearch + let a2 = screen_line1[2].attr + call assert_notequal(a0, a1) + call assert_notequal(a0, a2) + call assert_notequal(a1, a2) + call term_sendkeys(buf, "\<cr>gg0") + + " Test incremental highlight search + call term_sendkeys(buf, "/vim") + call term_wait(buf, 200) + " Buffer: + " abb vim vim vi + " vimvivim + " Search: /vim + let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a2,a2,a2,a0,a0,a0] + let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + + " Test <C-g> + call term_sendkeys(buf, "\<C-g>\<C-g>") + call term_wait(buf, 200) + let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0] + let attr_line2 = [a1,a1,a1,a0,a0,a2,a2,a2] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + + " Test <C-t> + call term_sendkeys(buf, "\<C-t>") + call term_wait(buf, 200) + let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a1,a1,a1,a0,a0,a0] + let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + + " Type Enter and a1(incsearch highlight) should become a2(hlsearch highlight) + call term_sendkeys(buf, "\<cr>") + call term_wait(buf, 200) + let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0] + let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + + " Test nohlsearch. a2(hlsearch highlight) should become a0(normal highlight) + call term_sendkeys(buf, ":1\<cr>") + call term_sendkeys(buf, ":set nohlsearch\<cr>") + call term_sendkeys(buf, "/vim") + call term_wait(buf, 200) + let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a0,a0,a0,a0,a0,a0] + let attr_line2 = [a0,a0,a0,a0,a0,a0,a0,a0] + call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) + call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) + call delete('Xsearch.txt') + + call delete('Xsearch.txt') + bwipe! +endfunc + +func Test_incsearch_cmdline_modifier() + CheckFunction test_override + CheckOption incsearch + + call test_override("char_avail", 1) + new + call setline(1, ['foo']) + set incsearch + " Test that error E14 does not occur in parsing command modifier. + call feedkeys("V:tab", 'tx') + + call Incsearch_cleanup() +endfunc + +func Test_incsearch_scrolling() if !CanRunVimInTerminal() throw 'Skipped: cannot make screendumps' endif + call assert_equal(0, &scrolloff) + call writefile([ + \ 'let dots = repeat(".", 120)', + \ 'set incsearch cmdheight=2 scrolloff=0', + \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])', + \ 'normal gg', + \ 'redraw', + \ ], 'Xscript') + let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70}) + " Need to send one key at a time to force a redraw + call term_sendkeys(buf, '/') + sleep 100m + call term_sendkeys(buf, 't') + sleep 100m + call term_sendkeys(buf, 'a') + sleep 100m + call term_sendkeys(buf, 'r') + sleep 100m + call term_sendkeys(buf, 'g') + call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {}) + + call term_sendkeys(buf, "\<Esc>") + call StopVimInTerminal(buf) + call delete('Xscript') +endfunc + +func Test_incsearch_search_dump() + CheckOption incsearch + CheckScreendump + + call writefile([ + \ 'set incsearch hlsearch scrolloff=0', + \ 'for n in range(1, 8)', + \ ' call setline(n, "foo " . n)', + \ 'endfor', + \ '3', + \ ], 'Xis_search_script') + let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70}) + " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by + " the 'ambiwidth' check. + sleep 100m + + " Need to send one key at a time to force a redraw. + call term_sendkeys(buf, '/fo') + call VerifyScreenDump(buf, 'Test_incsearch_search_01', {}) + call term_sendkeys(buf, "\<Esc>") + sleep 100m + + call term_sendkeys(buf, '/\v') + call VerifyScreenDump(buf, 'Test_incsearch_search_02', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_search_script') +endfunc + +func Test_incsearch_substitute() + CheckFunction test_override + CheckOption incsearch + + call test_override("char_avail", 1) + new + set incsearch + for n in range(1, 10) + call setline(n, 'foo ' . n) + endfor + 4 + call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx') + call assert_equal('foo 3', getline(3)) + call assert_equal('xxx 4', getline(4)) + call assert_equal('xxx 5', getline(5)) + call assert_equal('xxx 6', getline(6)) + call assert_equal('foo 7', getline(7)) + + call Incsearch_cleanup() +endfunc + +" Similar to Test_incsearch_substitute() but with a screendump halfway. +func Test_incsearch_substitute_dump() + CheckOption incsearch + CheckScreendump + call writefile([ \ 'set incsearch hlsearch scrolloff=0', \ 'for n in range(1, 10)', @@ -723,14 +955,58 @@ func Test_incsearch_substitute_dump() call delete('Xis_subst_script') endfunc +func Test_incsearch_highlighting() + CheckOption incsearch + CheckScreendump + + call writefile([ + \ 'set incsearch hlsearch', + \ 'call setline(1, "hello/there")', + \ ], 'Xis_subst_hl_script') + let buf = RunVimInTerminal('-S Xis_subst_hl_script', {'rows': 4, 'cols': 20}) + " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by + " the 'ambiwidth' check. + sleep 300m + + " Using a different search delimiter should still highlight matches + " that contain a '/'. + call term_sendkeys(buf, ":%s;ello/the") + call VerifyScreenDump(buf, 'Test_incsearch_substitute_15', {}) + call term_sendkeys(buf, "<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_subst_hl_script') +endfunc + +func Test_incsearch_with_change() + CheckFeature timers + CheckOption incsearch + CheckScreendump + + call writefile([ + \ 'set incsearch hlsearch scrolloff=0', + \ 'call setline(1, ["one", "two ------ X", "three"])', + \ 'call timer_start(200, { _ -> setline(2, "x")})', + \ ], 'Xis_change_script') + let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70}) + " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by + " the 'ambiwidth' check. + sleep 300m + + " Highlight X, it will be deleted by the timer callback. + call term_sendkeys(buf, ':%s/X') + call VerifyScreenDump(buf, 'Test_incsearch_change_01', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_change_script') +endfunc + " Similar to Test_incsearch_substitute_dump() for :sort func Test_incsearch_sort_dump() - if !exists('+incsearch') - return - endif - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckOption incsearch + CheckScreendump + call writefile([ \ 'set incsearch hlsearch scrolloff=0', \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])', @@ -754,12 +1030,9 @@ endfunc " Similar to Test_incsearch_substitute_dump() for :vimgrep famiry func Test_incsearch_vimgrep_dump() - if !exists('+incsearch') - return - endif - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckOption incsearch + CheckScreendump + call writefile([ \ 'set incsearch hlsearch scrolloff=0', \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])', @@ -796,9 +1069,8 @@ endfunc func Test_keep_last_search_pattern() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + new call setline(1, ['foo', 'foo', 'foo']) set incsearch @@ -818,9 +1090,8 @@ endfunc func Test_word_under_cursor_after_match() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + new call setline(1, 'foo bar') set incsearch @@ -838,9 +1109,8 @@ endfunc func Test_subst_word_under_cursor() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + new call setline(1, ['int SomeLongName;', 'for (xxx = 1; xxx < len; ++xxx)']) set incsearch @@ -854,130 +1124,6 @@ func Test_subst_word_under_cursor() set noincsearch endfunc -func Test_incsearch_with_change() - if !has('timers') || !exists('+incsearch') || !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps and/or timers feature and/or incsearch option missing' - endif - - call writefile([ - \ 'set incsearch hlsearch scrolloff=0', - \ 'call setline(1, ["one", "two ------ X", "three"])', - \ 'call timer_start(200, { _ -> setline(2, "x")})', - \ ], 'Xis_change_script') - let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70}) - " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by - " the 'ambiwidth' check. - sleep 300m - - " Highlight X, it will be deleted by the timer callback. - call term_sendkeys(buf, ':%s/X') - call VerifyScreenDump(buf, 'Test_incsearch_change_01', {}) - call term_sendkeys(buf, "\<Esc>") - - call StopVimInTerminal(buf) - call delete('Xis_change_script') -endfunc - -func Test_incsearch_cmdline_modifier() - CheckFunction test_override - if !exists('+incsearch') - return - endif - call test_override("char_avail", 1) - new - call setline(1, ['foo']) - set incsearch - " Test that error E14 does not occur in parsing command modifier. - call feedkeys("V:tab", 'tx') - - call Incsearch_cleanup() -endfunc - -func Test_incsearch_scrolling() - if !CanRunVimInTerminal() - return - endif - call assert_equal(0, &scrolloff) - call writefile([ - \ 'let dots = repeat(".", 120)', - \ 'set incsearch cmdheight=2 scrolloff=0', - \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])', - \ 'normal gg', - \ 'redraw', - \ ], 'Xscript') - let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70}) - " Need to send one key at a time to force a redraw - call term_sendkeys(buf, '/') - sleep 100m - call term_sendkeys(buf, 't') - sleep 100m - call term_sendkeys(buf, 'a') - sleep 100m - call term_sendkeys(buf, 'r') - sleep 100m - call term_sendkeys(buf, 'g') - call VerifyScreenDump(buf, 'Test_incsearch_scrolling_01', {}) - - call term_sendkeys(buf, "\<Esc>") - call StopVimInTerminal(buf) - call delete('Xscript') -endfunc - -func Test_incsearch_search_dump() - if !exists('+incsearch') - return - endif - if !CanRunVimInTerminal() - return - endif - call writefile([ - \ 'set incsearch hlsearch scrolloff=0', - \ 'for n in range(1, 8)', - \ ' call setline(n, "foo " . n)', - \ 'endfor', - \ '3', - \ ], 'Xis_search_script') - let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70}) - " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by - " the 'ambiwidth' check. - sleep 100m - - " Need to send one key at a time to force a redraw. - call term_sendkeys(buf, '/fo') - call VerifyScreenDump(buf, 'Test_incsearch_search_01', {}) - call term_sendkeys(buf, "\<Esc>") - sleep 100m - - call term_sendkeys(buf, '/\v') - call VerifyScreenDump(buf, 'Test_incsearch_search_02', {}) - call term_sendkeys(buf, "\<Esc>") - - call StopVimInTerminal(buf) - call delete('Xis_search_script') -endfunc - -func Test_incsearch_substitute() - CheckFunction test_override - if !exists('+incsearch') - return - endif - call test_override("char_avail", 1) - new - set incsearch - for n in range(1, 10) - call setline(n, 'foo ' . n) - endfor - 4 - call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx') - call assert_equal('foo 3', getline(3)) - call assert_equal('xxx 4', getline(4)) - call assert_equal('xxx 5', getline(5)) - call assert_equal('xxx 6', getline(6)) - call assert_equal('foo 7', getline(7)) - - call Incsearch_cleanup() -endfunc - func Test_incsearch_substitute_long_line() CheckFunction test_override new @@ -994,9 +1140,8 @@ func Test_incsearch_substitute_long_line() endfunc func Test_search_undefined_behaviour() - if !has("terminal") - return - endif + CheckFeature terminal + let h = winheight(0) if h < 3 return @@ -1012,6 +1157,18 @@ func Test_search_undefined_behaviour2() call search("\%UC0000000") endfunc +" Test for search('multi-byte char', 'bce') +func Test_search_multibyte() + let save_enc = &encoding + set encoding=utf8 + enew! + call append('$', 'A') + call cursor(2, 1) + call assert_equal(2, search('A', 'bce', line('.'))) + enew! + let &encoding = save_enc +endfunc + " This was causing E874. Also causes an invalid read? func Test_look_behind() new @@ -1050,9 +1207,8 @@ func Test_search_Ctrl_L_combining() " ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE ̇ /\%u307\Z "\u0307" " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW ̣ /\%u323 "\u0323" " Those should also appear on the commandline - if !exists('+incsearch') - return - endif + CheckOption incsearch + call Cmdline3_prep() 1 let bufcontent = ['', 'Miạ̀́̇m'] @@ -1102,9 +1258,8 @@ endfunc func Test_incsearch_add_char_under_cursor() CheckFunction test_override - if !exists('+incsearch') - return - endif + CheckOption incsearch + set incsearch new call setline(1, ['find match', 'anything']) @@ -1189,7 +1344,7 @@ func Test_search_smartcase_utf8() close! endfunc -func Test_zzzz_incsearch_highlighting_newline() +func Test_incsearch_highlighting_newline() CheckRunVimInTerminal CheckOption incsearch CheckScreendump @@ -1202,20 +1357,16 @@ func Test_zzzz_incsearch_highlighting_newline() [CODE] call writefile(commands, 'Xincsearch_nl') let buf = RunVimInTerminal('-S Xincsearch_nl', {'rows': 5, 'cols': 10}) - " Need to send one key at a time to force a redraw call term_sendkeys(buf, '/test') - sleep 100m call VerifyScreenDump(buf, 'Test_incsearch_newline1', {}) + " Need to send one key at a time to force a redraw call term_sendkeys(buf, '\n') - sleep 100m call VerifyScreenDump(buf, 'Test_incsearch_newline2', {}) call term_sendkeys(buf, 'x') - sleep 100m call VerifyScreenDump(buf, 'Test_incsearch_newline3', {}) call term_sendkeys(buf, 'x') call VerifyScreenDump(buf, 'Test_incsearch_newline4', {}) call term_sendkeys(buf, "\<CR>") - sleep 100m call VerifyScreenDump(buf, 'Test_incsearch_newline5', {}) call StopVimInTerminal(buf) diff --git a/src/nvim/testdir/test_shift.vim b/src/nvim/testdir/test_shift.vim new file mode 100644 index 0000000000..ec357dac88 --- /dev/null +++ b/src/nvim/testdir/test_shift.vim @@ -0,0 +1,117 @@ +" Test shifting lines with :> and :< + +source check.vim + +func Test_ex_shift_right() + set shiftwidth=2 + + " shift right current line. + call setline(1, range(1, 5)) + 2 + > + 3 + >> + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ '4', + \ '5'], getline(1, '$')) + + " shift right with range. + call setline(1, range(1, 4)) + 2,3>> + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ '4', + \ '5'], getline(1, '$')) + + " shift right with range and count. + call setline(1, range(1, 4)) + 2>3 + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ ' 4', + \ '5'], getline(1, '$')) + + bw! + set shiftwidth& +endfunc + +func Test_ex_shift_left() + set shiftwidth=2 + + call setline(1, range(1, 5)) + %>>> + + " left shift current line. + 2< + 3<< + 4<<<<< + call assert_equal([' 1', + \ ' 2', + \ ' 3', + \ '4', + \ ' 5'], getline(1, '$')) + + " shift right with range. + call setline(1, range(1, 5)) + %>>> + 2,3<< + call assert_equal([' 1', + \ ' 2', + \ ' 3', + \ ' 4', + \ ' 5'], getline(1, '$')) + + " shift right with range and count. + call setline(1, range(1, 5)) + %>>> + 2<<3 + call assert_equal([' 1', + \ ' 2', + \ ' 3', + \ ' 4', + \ ' 5'], getline(1, '$')) + + bw! + set shiftwidth& +endfunc + +func Test_ex_shift_rightleft() + CheckFeature rightleft + + set shiftwidth=2 rightleft + + call setline(1, range(1, 4)) + 2,3<< + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ '4'], getline(1, '$')) + + 3,4> + call assert_equal(['1', + \ ' 2', + \ ' 3', + \ '4'], getline(1, '$')) + + bw! + set rightleft& shiftwidth& +endfunc + +func Test_ex_shift_errors() + call assert_fails('><', 'E488:') + call assert_fails('<>', 'E488:') + + call assert_fails('>!', 'E477:') + call assert_fails('<!', 'E477:') + + " call assert_fails('2,1>', 'E493:') + call assert_fails('execute "2,1>"', 'E493:') + " call assert_fails('2,1<', 'E493:') + call assert_fails('execute "2,1<"', 'E493:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index 9c3a5636ce..f6b96c1e5d 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -134,7 +134,7 @@ func Test_sign() sign define Sign5 text=X\ linehl=Comment sign undefine Sign5 - sign define Sign5 linehl=Comment text=X\ + sign define Sign5 linehl=Comment text=X\ sign undefine Sign5 " define sign with backslash @@ -415,7 +415,7 @@ func Test_sign_funcs() " Tests for invalid arguments to sign_define() call assert_fails('call sign_define("sign4", {"text" : "===>"})', 'E239:') " call assert_fails('call sign_define("sign5", {"text" : ""})', 'E239:') - call assert_fails('call sign_define([])', 'E730:') + call assert_fails('call sign_define({})', 'E731:') call assert_fails('call sign_define("sign6", [])', 'E715:') " Tests for sign_getdefined() @@ -444,7 +444,7 @@ func Test_sign_funcs() call assert_fails('call sign_place([], "", "mySign", 1)', 'E745:') call assert_fails('call sign_place(5, "", "mySign", -1)', 'E158:') call assert_fails('call sign_place(-1, "", "sign1", "Xsign", [])', - \ 'E474:') + \ 'E715:') call assert_fails('call sign_place(-1, "", "sign1", "Xsign", \ {"lnum" : 30})', 'E474:') call assert_fails('call sign_place(10, "", "xsign1x", "Xsign", @@ -460,11 +460,11 @@ func Test_sign_funcs() call assert_fails('call sign_place(5, "", "sign1", [], {"lnum" : 10})', \ 'E158:') call assert_fails('call sign_place(21, "", "sign1", "Xsign", - \ {"lnum" : -1})', 'E885:') + \ {"lnum" : -1})', 'E474:') call assert_fails('call sign_place(22, "", "sign1", "Xsign", - \ {"lnum" : 0})', 'E885:') + \ {"lnum" : 0})', 'E474:') call assert_fails('call sign_place(22, "", "sign1", "Xsign", - \ {"lnum" : []})', 'E745:') + \ {"lnum" : []})', 'E474:') call assert_equal(-1, sign_place(1, "*", "sign1", "Xsign", {"lnum" : 10})) " Tests for sign_getplaced() @@ -504,11 +504,21 @@ func Test_sign_funcs() \ {'id' : 20, 'buffer' : 200})", 'E158:') call assert_fails("call sign_unplace('g1', 'mySign')", 'E715:') + call sign_unplace('*') + + " Test for modifying a placed sign + call assert_equal(15, sign_place(15, '', 'sign1', 'Xsign', {'lnum' : 20})) + call assert_equal(15, sign_place(15, '', 'sign2', 'Xsign')) + call assert_equal([{'bufnr' : bufnr(''), 'signs' : + \ [{'id' : 15, 'group' : '', 'lnum' : 20, 'name' : 'sign2', + \ 'priority' : 10}]}], + \ sign_getplaced()) + " Tests for sign_undefine() call assert_equal(0, sign_undefine("sign1")) call assert_equal([], sign_getdefined("sign1")) call assert_fails('call sign_undefine("none")', 'E155:') - call assert_fails('call sign_undefine([])', 'E730:') + call assert_fails('call sign_undefine({})', 'E731:') " Test for using '.' as the line number for sign_place() call Sign_define_ignore_error("sign1", attr) @@ -644,7 +654,7 @@ func Test_sign_group() call assert_equal([], sign_getplaced(bnum, {'group' : '*'})[0].signs) " Error case - call assert_fails("call sign_unplace([])", 'E474:') + call assert_fails("call sign_unplace({})", 'E474:') " Place a sign in the global group and try to delete it using a group call assert_equal(5, sign_place(5, '', 'sign1', bnum, {'lnum' : 10})) @@ -1131,7 +1141,7 @@ func Test_sign_unplace() endfunc " Tests for auto-generating the sign identifier -func Test_sign_id_autogen() +func Test_aaa_sign_id_autogen() enew | only call sign_unplace('*') call sign_undefine() @@ -1868,3 +1878,121 @@ func Test_sign_numcol() set number& enew! | close endfunc + +" Test for managing multiple signs using the sign functions +func Test_sign_funcs_multi() + call writefile(repeat(["Sun is shining"], 30), "Xsign") + edit Xsign + let bnum = bufnr('') + + " Define multiple signs at once + call assert_equal([0, 0, 0, 0], sign_define([ + \ {'name' : 'sign1', 'text' : '=>', 'linehl' : 'Search', + \ 'texthl' : 'Search'}, + \ {'name' : 'sign2', 'text' : '=>', 'linehl' : 'Search', + \ 'texthl' : 'Search'}, + \ {'name' : 'sign3', 'text' : '=>', 'linehl' : 'Search', + \ 'texthl' : 'Search'}, + \ {'name' : 'sign4', 'text' : '=>', 'linehl' : 'Search', + \ 'texthl' : 'Search'}])) + + " Negative cases for sign_define() + call assert_equal([], sign_define([])) + call assert_equal([-1], sign_define([{}])) + call assert_fails('call sign_define([6])', 'E715:') + call assert_fails('call sign_define(["abc"])', 'E715:') + call assert_fails('call sign_define([[]])', 'E715:') + + " Place multiple signs at once with specific sign identifier + let l = sign_placelist([{'id' : 1, 'group' : 'g1', 'name' : 'sign1', + \ 'buffer' : 'Xsign', 'lnum' : 11, 'priority' : 50}, + \ {'id' : 2, 'group' : 'g2', 'name' : 'sign2', + \ 'buffer' : 'Xsign', 'lnum' : 11, 'priority' : 100}, + \ {'id' : 3, 'group' : '', 'name' : 'sign3', + \ 'buffer' : 'Xsign', 'lnum' : 11}]) + call assert_equal([1, 2, 3], l) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 2, 'name' : 'sign2', 'lnum' : 11, + \ 'group' : 'g2', 'priority' : 100}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, + \ 'group' : 'g1', 'priority' : 50}, + \ {'id' : 3, 'name' : 'sign3', 'lnum' : 11, + \ 'group' : '', 'priority' : 10}], s[0].signs) + + call sign_unplace('*') + + " Place multiple signs at once with auto-generated sign identifier + call assert_equal([1, 1, 5], sign_placelist([ + \ {'group' : 'g1', 'name' : 'sign1', + \ 'buffer' : 'Xsign', 'lnum' : 11}, + \ {'group' : 'g2', 'name' : 'sign2', + \ 'buffer' : 'Xsign', 'lnum' : 11}, + \ {'group' : '', 'name' : 'sign3', + \ 'buffer' : 'Xsign', 'lnum' : 11}])) + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 5, 'name' : 'sign3', 'lnum' : 11, + \ 'group' : '', 'priority' : 10}, + \ {'id' : 1, 'name' : 'sign2', 'lnum' : 11, + \ 'group' : 'g2', 'priority' : 10}, + \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, + \ 'group' : 'g1', 'priority' : 10}], s[0].signs) + + " Change an existing sign without specifying the group + call assert_equal([5], sign_placelist([ + \ {'id' : 5, 'name' : 'sign1', 'buffer' : 'Xsign'}])) + let s = sign_getplaced('Xsign', {'id' : 5, 'group' : ''}) + call assert_equal([{'id' : 5, 'name' : 'sign1', 'lnum' : 11, + \ 'group' : '', 'priority' : 10}], s[0].signs) + + " Place a sign using '.' as the line number + call cursor(23, 1) + call assert_equal([7], sign_placelist([ + \ {'id' : 7, 'name' : 'sign1', 'buffer' : '%', 'lnum' : '.'}])) + let s = sign_getplaced('%', {'lnum' : '.'}) + call assert_equal([{'id' : 7, 'name' : 'sign1', 'lnum' : 23, + \ 'group' : '', 'priority' : 10}], s[0].signs) + + " Place sign without a sign name + call assert_equal([-1], sign_placelist([{'id' : 10, 'buffer' : 'Xsign', + \ 'lnum' : 12, 'group' : ''}])) + + " Place sign without a buffer + call assert_equal([-1], sign_placelist([{'id' : 10, 'name' : 'sign1', + \ 'lnum' : 12, 'group' : ''}])) + + " Invalid arguments + call assert_equal([], sign_placelist([])) + call assert_fails('call sign_placelist({})', "E714:") + call assert_fails('call sign_placelist([[]])', "E715:") + call assert_fails('call sign_placelist(["abc"])', "E715:") + call assert_fails('call sign_placelist([100])', "E715:") + + " Unplace multiple signs + call assert_equal([0, 0, 0], sign_unplacelist([{'id' : 5}, + \ {'id' : 1, 'group' : 'g1'}, {'id' : 1, 'group' : 'g2'}])) + + " Invalid arguments + call assert_equal([], sign_unplacelist([])) + call assert_fails('call sign_unplacelist({})', "E714:") + call assert_fails('call sign_unplacelist([[]])', "E715:") + call assert_fails('call sign_unplacelist(["abc"])', "E715:") + call assert_fails('call sign_unplacelist([100])', "E715:") + call assert_fails("call sign_unplacelist([{'id' : -1}])", 'E474') + + call assert_equal([0, 0, 0, 0], + \ sign_undefine(['sign1', 'sign2', 'sign3', 'sign4'])) + call assert_equal([], sign_getdefined()) + + " Invalid arguments + call assert_equal([], sign_undefine([])) + call assert_fails('call sign_undefine([[]])', 'E730:') + call assert_fails('call sign_undefine([{}])', 'E731:') + call assert_fails('call sign_undefine(["1abc2"])', 'E155:') + + call sign_unplace('*') + call sign_undefine() + enew! + call delete("Xsign") +endfunc diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index 48b7b4f2f1..f5b6446108 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -444,19 +444,20 @@ func Test_statusline_using_mode() CheckScreendump let lines =<< trim END - set laststatus=2 - let &statusline = '-%{mode()}-' + setlocal statusline=-%{mode()}- + split + setlocal statusline=+%{mode()}+ END call writefile(lines, 'XTest_statusline') - let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 5, 'cols': 50}) + let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 7, 'cols': 50}) call VerifyScreenDump(buf, 'Test_statusline_mode_1', {}) call term_sendkeys(buf, ":") call VerifyScreenDump(buf, 'Test_statusline_mode_2', {}) " clean up - call term_sendkeys(buf, "\<CR>") + call term_sendkeys(buf, "close\<CR>") call StopVimInTerminal(buf) call delete('XTest_statusline') endfunc diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 4af52b536c..29f0433954 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -891,6 +891,14 @@ func Test_mps() bwipe! endfunc +func Test_empty_matchpairs() + split + set matchpairs= showmatch + call assert_nobeep('call feedkeys("ax\tx\t\<Esc>", "xt")') + set matchpairs& noshowmatch + bwipe! +endfunc + " Test for ra on multi-byte characters func Test_ra_multibyte() new diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim index e8161f8fcb..c51fb3a759 100644 --- a/src/nvim/testdir/test_utf8.vim +++ b/src/nvim/testdir/test_utf8.vim @@ -84,7 +84,7 @@ func Test_list2str_str2list_utf8() " Null list is the same as an empty list call assert_equal('', list2str([])) - " call assert_equal('', list2str(test_null_list())) + call assert_equal('', list2str(v:_null_list)) endfunc func Test_list2str_str2list_latin1() diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index 56031662a3..c62c01d5f3 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -1,5 +1,8 @@ " Tests for the writefile() function and some :write commands. +source check.vim +source term_util.vim + func Test_writefile() let f = tempname() call writefile(["over","written"], f, "b") @@ -179,3 +182,120 @@ func Test_writefile_sync_arg() call writefile(['two'], 'Xtest', 'S') call delete('Xtest') endfunc + +" Tests for reading and writing files with conversion for Win32. +func Test_write_file_encoding() + CheckMSWindows + throw 'skipped: Nvim does not support :w ++enc=cp1251' + let save_encoding = &encoding + let save_fileencodings = &fileencodings + set encoding& fileencodings& + let text =<< trim END + 1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + END + call writefile(text, 'Xfile') + edit Xfile + + " write tests: + " combine three values for 'encoding' with three values for 'fileencoding' + " also write files for read tests + call cursor(1, 1) + set encoding=utf-8 + .w! ++enc=utf-8 Xtest + .w ++enc=cp1251 >> Xtest + .w ++enc=cp866 >> Xtest + .w! ++enc=utf-8 Xutf8 + let expected =<< trim END + 1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 1 utf-8 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + END + call assert_equal(expected, readfile('Xtest')) + + call cursor(2, 1) + set encoding=cp1251 + .w! ++enc=utf-8 Xtest + .w ++enc=cp1251 >> Xtest + .w ++enc=cp866 >> Xtest + .w! ++enc=cp1251 Xcp1251 + let expected =<< trim END + 2 cp1251 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 2 cp1251 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + END + call assert_equal(expected, readfile('Xtest')) + + call cursor(3, 1) + set encoding=cp866 + .w! ++enc=utf-8 Xtest + .w ++enc=cp1251 >> Xtest + .w ++enc=cp866 >> Xtest + .w! ++enc=cp866 Xcp866 + let expected =<< trim END + 3 cp866 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + END + call assert_equal(expected, readfile('Xtest')) + + " read three 'fileencoding's with utf-8 'encoding' + set encoding=utf-8 fencs=utf-8,cp1251 + e Xutf8 + .w! ++enc=utf-8 Xtest + e Xcp1251 + .w ++enc=utf-8 >> Xtest + set fencs=utf-8,cp866 + e Xcp866 + .w ++enc=utf-8 >> Xtest + let expected =<< trim END + 1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 2 cp1251 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 3 cp866 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + END + call assert_equal(expected, readfile('Xtest')) + + " read three 'fileencoding's with cp1251 'encoding' + set encoding=utf-8 fencs=utf-8,cp1251 + e Xutf8 + .w! ++enc=cp1251 Xtest + e Xcp1251 + .w ++enc=cp1251 >> Xtest + set fencs=utf-8,cp866 + e Xcp866 + .w ++enc=cp1251 >> Xtest + let expected =<< trim END + 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + END + call assert_equal(expected, readfile('Xtest')) + + " read three 'fileencoding's with cp866 'encoding' + set encoding=cp866 fencs=utf-8,cp1251 + e Xutf8 + .w! ++enc=cp866 Xtest + e Xcp1251 + .w ++enc=cp866 >> Xtest + set fencs=utf-8,cp866 + e Xcp866 + .w ++enc=cp866 >> Xtest + let expected =<< trim END + 1 utf-8 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 2 cp1251 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + END + call assert_equal(expected, readfile('Xtest')) + + call delete('Xfile') + call delete('Xtest') + call delete('Xutf8') + call delete('Xcp1251') + call delete('Xcp866') + let &encoding = save_encoding + let &fileencodings = save_fileencodings + %bw! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 62d7dc8b18..ed40a64c66 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -55,7 +55,11 @@ #define STARTS_WITH(str, prefix) (strlen(str) >= (sizeof(prefix) - 1) \ && 0 == memcmp((str), (prefix), sizeof(prefix) - 1)) #define TMUX_WRAP(is_tmux, seq) ((is_tmux) \ - ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq) + ? DCS_STR "tmux;\x1b" seq STERM_STR : seq) +#define SCREEN_TMUX_WRAP(is_screen, is_tmux, seq) \ + ((is_screen) \ + ? DCS_STR seq STERM_STR : (is_tmux) \ + ? DCS_STR "tmux;\x1b" seq STERM_STR : seq) #define LINUXSET0C "\x1b[?0c" #define LINUXSET1C "\x1b[?1c" @@ -297,6 +301,12 @@ static void terminfo_start(UI *ui) data->invis, sizeof data->invis); // Set 't_Co' from the result of unibilium & fix_terminfo. t_colors = unibi_get_num(data->ut, unibi_max_colors); + // Ask the terminal to send us the background color. + // If get_bg is sent at the same time after enter_ca_mode, tmux will not send + // get_bg to the host terminal. To avoid this, send get_bg before + // enter_ca_mode. + data->input.waiting_for_bg_response = 5; + unibi_out_ext(ui, data->unibi_ext.get_bg); // Enter alternate screen, save title, and clear. // NOTE: Do this *before* changing terminal settings. #6433 unibi_out(ui, unibi_enter_ca_mode); @@ -304,9 +314,6 @@ static void terminfo_start(UI *ui) unibi_out_ext(ui, data->unibi_ext.save_title); unibi_out(ui, unibi_keypad_xmit); unibi_out(ui, unibi_clear_screen); - // Ask the terminal to send us the background color. - data->input.waiting_for_bg_response = 5; - unibi_out_ext(ui, data->unibi_ext.get_bg); // Enable bracketed paste unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); @@ -328,6 +335,7 @@ static void terminfo_start(UI *ui) uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); uv_pipe_open(&data->output_handle.pipe, data->out_fd); } + flush_buf(ui); } @@ -1772,8 +1780,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, #define XTERM_SETAB_16 \ "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m" - data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", - "\x1b]11;?\x07"); + data->unibi_ext.get_bg = + (int)unibi_add_ext_str(ut, "ext.get_bg", + SCREEN_TMUX_WRAP((screen && !tmux), tmux, + "\x1b]11;?\x07")); // Terminals with 256-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 256) { diff --git a/src/nvim/window.c b/src/nvim/window.c index 859f4353b3..c482d265ff 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -668,7 +668,11 @@ void win_config_float(win_T *wp, FloatConfig fconfig) } bool change_external = fconfig.external != wp->w_float_config.external; - bool change_border = fconfig.border != wp->w_float_config.border; + bool change_border = (fconfig.border != wp->w_float_config.border + || memcmp(fconfig.border_hl_ids, + wp->w_float_config.border_hl_ids, + sizeof fconfig.border_hl_ids)); + wp->w_float_config = fconfig; @@ -5731,9 +5735,16 @@ void win_set_inner_size(win_T *wp) terminal_check_size(wp->w_buffer->terminal); } - wp->w_border_adj = wp->w_floating && wp->w_float_config.border ? 1 : 0; - wp->w_height_outer = wp->w_height_inner + 2 * wp->w_border_adj; - wp->w_width_outer = wp->w_width_inner + 2 * wp->w_border_adj; + bool has_border = wp->w_floating && wp->w_float_config.border; + for (int i = 0; i < 4; i++) { + wp->w_border_adj[i] = + has_border && wp->w_float_config.border_chars[2 * i+1][0]; + } + + wp->w_height_outer = (wp->w_height_inner + + wp->w_border_adj[0] + wp->w_border_adj[2]); + wp->w_width_outer = (wp->w_width_inner + + wp->w_border_adj[1] + wp->w_border_adj[3]); } /// Set the width of a window. |