diff options
Diffstat (limited to 'src')
114 files changed, 5249 insertions, 1679 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 66c4454f7b..6142db049d 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; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 7cee569989..382244d6b3 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1645,6 +1645,20 @@ bool api_object_to_bool(Object obj, const char *what, } } +int object_to_hl_id(Object obj, const char *what, Error *err) +{ + if (obj.type == kObjectTypeString) { + String str = obj.data.string; + return str.size ? syn_check_group((char_u *)str.data, (int)str.size) : 0; + } else if (obj.type == kObjectTypeInteger) { + return (int)obj.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "%s is not a valid highlight", what); + return 0; + } +} + HlMessage parse_hl_msg(Array chunks, Error *err) { HlMessage hl_msg = KV_INITIAL_VALUE; @@ -1694,29 +1708,340 @@ const char *describe_ns(NS ns_id) return "(UNKNOWN PLUGIN)"; } -DecorProvider *get_provider(NS ns_id, bool force) +static bool parse_float_anchor(String anchor, FloatAnchor *out) { - 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 (anchor.size == 0) { + *out = (FloatAnchor)0; + } + char *str = anchor.data; + if (striequal(str, "NW")) { + *out = 0; // NW is the default + } else if (striequal(str, "NE")) { + *out = kFloatAnchorEast; + } else if (striequal(str, "SW")) { + *out = kFloatAnchorSouth; + } else if (striequal(str, "SE")) { + *out = kFloatAnchorSouth | kFloatAnchorEast; + } else { + return false; + } + return true; +} + +static bool parse_float_relative(String relative, FloatRelative *out) +{ + char *str = relative.data; + if (striequal(str, "editor")) { + *out = kFloatRelativeEditor; + } else if (striequal(str, "win")) { + *out = kFloatRelativeWindow; + } else if (striequal(str, "cursor")) { + *out = kFloatRelativeCursor; + } else { + return false; + } + return true; +} + +static bool parse_float_bufpos(Array bufpos, lpos_T *out) +{ + if (bufpos.size != 2 + || bufpos.items[0].type != kObjectTypeInteger + || bufpos.items[1].type != kObjectTypeInteger) { + return false; + } + out->lnum = bufpos.items[0].data.integer; + out->col = (colnr_T)bufpos.items[1].data.integer; + return true; +} + +static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) +{ + struct { + const char *name; + schar_T chars[8]; + } defaults[] = { + { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" } }, + { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" } }, + { NULL, { { NUL } } }, + }; + + schar_T *chars = fconfig->border_chars; + int *hl_ids = fconfig->border_hl_ids; + + fconfig->border = true; + + if (style.type == kObjectTypeArray) { + Array arr = style.data.array; + size_t size = arr.size; + if (!size || size > 8 || (size & (size-1))) { + api_set_error(err, kErrorTypeValidation, + "invalid number of border chars"); + return; + } + for (size_t i = 0; i < size; i++) { + Object iytem = arr.items[i]; + String string = NULL_STRING; + int hl_id = 0; + if (iytem.type == kObjectTypeArray) { + Array iarr = iytem.data.array; + if (!iarr.size || iarr.size > 2) { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + if (iarr.items[0].type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + string = iarr.items[0].data.string; + if (iarr.size == 2) { + hl_id = object_to_hl_id(iarr.items[1], "border char highlight", err); + if (ERROR_SET(err)) { + return; + } + } + + } else if (iytem.type == kObjectTypeString) { + string = iytem.data.string; + } else { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + if (!string.size + || mb_string2cells_len((char_u *)string.data, string.size) != 1) { + api_set_error(err, kErrorTypeValidation, + "border chars must be one cell"); + } + size_t len = MIN(string.size, sizeof(*chars)-1); + memcpy(chars[i], string.data, len); + chars[i][len] = NUL; + hl_ids[i] = hl_id; + } + while (size < 8) { + memcpy(chars+size, chars, sizeof(*chars) * size); + memcpy(hl_ids+size, hl_ids, sizeof(*hl_ids) * size); + size <<= 1; + } + } else if (style.type == kObjectTypeString) { + String str = style.data.string; + if (str.size == 0 || strequal(str.data, "none")) { + fconfig->border = false; + return; + } + for (size_t i = 0; defaults[i].name; i++) { + if (strequal(str.data, defaults[i].name)) { + memcpy(chars, defaults[i].chars, sizeof(defaults[i].chars)); + memset(hl_ids, 0, 8 * sizeof(*hl_ids)); + return; + } + } + api_set_error(err, kErrorTypeValidation, + "invalid border style \"%s\"", str.data); + } +} + +bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, + Error *err) +{ + // TODO(bfredl): use a get/has_key interface instead and get rid of extra + // flags + bool has_row = false, has_col = false, has_relative = false; + bool has_external = false, has_window = false; + bool has_width = false, has_height = false; + bool has_bufpos = false; + + for (size_t i = 0; i < config.size; i++) { + char *key = config.items[i].key.data; + Object val = config.items[i].value; + if (!strcmp(key, "row")) { + has_row = true; + if (val.type == kObjectTypeInteger) { + fconfig->row = (double)val.data.integer; + } else if (val.type == kObjectTypeFloat) { + fconfig->row = val.data.floating; + } else { + api_set_error(err, kErrorTypeValidation, + "'row' key must be Integer or Float"); + return false; + } + } else if (!strcmp(key, "col")) { + has_col = true; + if (val.type == kObjectTypeInteger) { + fconfig->col = (double)val.data.integer; + } else if (val.type == kObjectTypeFloat) { + fconfig->col = val.data.floating; + } else { + api_set_error(err, kErrorTypeValidation, + "'col' key must be Integer or Float"); + return false; + } + } else if (strequal(key, "width")) { + has_width = true; + if (val.type == kObjectTypeInteger && val.data.integer > 0) { + fconfig->width = (int)val.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "'width' key must be a positive Integer"); + return false; + } + } else if (strequal(key, "height")) { + has_height = true; + if (val.type == kObjectTypeInteger && val.data.integer > 0) { + fconfig->height= (int)val.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "'height' key must be a positive Integer"); + return false; + } + } else if (!strcmp(key, "anchor")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "'anchor' key must be String"); + return false; + } + if (!parse_float_anchor(val.data.string, &fconfig->anchor)) { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'anchor' key"); + return false; + } + } else if (!strcmp(key, "relative")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "'relative' key must be String"); + return false; + } + // ignore empty string, to match nvim_win_get_config + if (val.data.string.size > 0) { + has_relative = true; + if (!parse_float_relative(val.data.string, &fconfig->relative)) { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'relative' key"); + return false; + } + } + } else if (!strcmp(key, "win")) { + has_window = true; + if (val.type != kObjectTypeInteger + && val.type != kObjectTypeWindow) { + api_set_error(err, kErrorTypeValidation, + "'win' key must be Integer or Window"); + return false; + } + fconfig->window = (Window)val.data.integer; + } else if (!strcmp(key, "bufpos")) { + if (val.type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, + "'bufpos' key must be Array"); + return false; + } + if (!parse_float_bufpos(val.data.array, &fconfig->bufpos)) { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'bufpos' key"); + return false; + } + has_bufpos = true; + } else if (!strcmp(key, "external")) { + if (val.type == kObjectTypeInteger) { + fconfig->external = val.data.integer; + } else if (val.type == kObjectTypeBoolean) { + fconfig->external = val.data.boolean; + } else { + api_set_error(err, kErrorTypeValidation, + "'external' key must be Boolean"); + return false; + } + has_external = fconfig->external; + } else if (!strcmp(key, "focusable")) { + if (val.type == kObjectTypeInteger) { + fconfig->focusable = val.data.integer; + } else if (val.type == kObjectTypeBoolean) { + fconfig->focusable = val.data.boolean; + } else { + api_set_error(err, kErrorTypeValidation, + "'focusable' key must be Boolean"); + return false; + } + } else if (!strcmp(key, "border")) { + parse_border_style(val, fconfig, err); + if (ERROR_SET(err)) { + return false; + } + } else if (!strcmp(key, "style")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "'style' key must be String"); + return false; + } + if (val.data.string.data[0] == NUL) { + fconfig->style = kWinStyleUnused; + } else if (striequal(val.data.string.data, "minimal")) { + fconfig->style = kWinStyleMinimal; + } else { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'style' key"); + } + } else { + api_set_error(err, kErrorTypeValidation, + "Invalid key '%s'", key); + return false; + } + } + + if (has_window && !(has_relative + && fconfig->relative == kFloatRelativeWindow)) { + api_set_error(err, kErrorTypeValidation, + "'win' key is only valid with relative='win'"); + return false; + } + + if ((has_relative && fconfig->relative == kFloatRelativeWindow) + && (!has_window || fconfig->window == 0)) { + fconfig->window = curwin->handle; + } + + if (has_window && !has_bufpos) { + fconfig->bufpos.lnum = -1; + } + + if (has_bufpos) { + if (!has_row) { + fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1; + has_row = true; + } + if (!has_col) { + fconfig->col = 0; + has_col = true; } } - if (!force) { - return NULL; + if (has_relative && has_external) { + api_set_error(err, kErrorTypeValidation, + "Only one of 'relative' and 'external' must be used"); + return false; + } else if (!reconf && !has_relative && !has_external) { + api_set_error(err, kErrorTypeValidation, + "One of 'relative' and 'external' must be used"); + return false; + } else if (has_relative) { + fconfig->external = false; + } + + if (!reconf && !(has_height && has_width)) { + api_set_error(err, kErrorTypeValidation, + "Must specify 'width' and 'height'"); + return false; } - 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); + if (fconfig->external && !ui_has(kUIMultigrid)) { + api_set_error(err, kErrorTypeValidation, + "UI doesn't support external windows"); + return false; } - DecorProvider *item = &kv_a(decor_providers, (size_t)i); - *item = DECORATION_PROVIDER_INIT(ns_id); - return item; + if (has_relative != has_row || has_row != has_col) { + api_set_error(err, kErrorTypeValidation, + "'relative' requires 'row'/'col' or 'bufpos'"); + return false; + } + return true; } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 586123aac1..3ae944de4d 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -39,6 +39,7 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/fileio.h" +#include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/state.h" @@ -241,8 +242,7 @@ void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err) /// /// @param ns_id the namespace to activate /// @param[out] err Error details, if any -void nvim_set_hl_ns(Integer ns_id, Error *err) - FUNC_API_SINCE(7) +void nvim__set_hl_ns(Integer ns_id, Error *err) FUNC_API_FAST { if (ns_id >= 0) { @@ -1246,6 +1246,99 @@ fail: return 0; } +/// Open a terminal instance in a buffer +/// +/// By default (and currently the only option) the terminal will not be +/// connected to an external process. Instead, input send on the channel +/// will be echoed directly by the terminal. This is useful to disply +/// ANSI terminal sequences returned as part of a rpc message, or similar. +/// +/// Note: to directly initiate the terminal using the right size, display the +/// buffer in a configured window before calling this. For instance, for a +/// floating display, first create an empty buffer using |nvim_create_buf()|, +/// then display it using |nvim_open_win()|, and then call this function. +/// Then |nvim_chan_send()| cal be called immediately to process sequences +/// in a virtual terminal having the intended size. +/// +/// @param buffer the buffer to use (expected to be empty) +/// @param opts Optional parameters. Reserved for future use. +/// @param[out] err Error details, if any +Integer nvim_open_term(Buffer buffer, Dictionary opts, Error *err) + FUNC_API_SINCE(7) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + return 0; + } + + TerminalOptions topts; + Channel *chan = channel_alloc(kChannelStreamInternal); + topts.data = chan; + // NB: overriden in terminal_check_size if a window is already + // displaying the buffer + topts.width = (uint16_t)MAX(curwin->w_width_inner - win_col_off(curwin), 0); + topts.height = (uint16_t)curwin->w_height_inner; + topts.write_cb = term_write; + topts.resize_cb = term_resize; + topts.close_cb = term_close; + Terminal *term = terminal_open(buf, topts); + terminal_check_size(term); + chan->term = term; + channel_incref(chan); + return (Integer)chan->id; +} + +static void term_write(char *buf, size_t size, void *data) +{ + // TODO(bfredl): lua callback +} + +static void term_resize(uint16_t width, uint16_t height, void *data) +{ + // TODO(bfredl): lua callback +} + +static void term_close(void *data) +{ + Channel *chan = data; + terminal_destroy(chan->term); + chan->term = NULL; + channel_decref(chan); +} + + +/// Send data to channel `id`. For a job, it writes it to the +/// stdin of the process. For the stdio channel |channel-stdio|, +/// it writes to Nvim's stdout. For an internal terminal instance +/// (|nvim_open_term()|) it writes directly to terimal output. +/// See |channel-bytes| for more information. +/// +/// This function writes raw data, not RPC messages. If the channel +/// was created with `rpc=true` then the channel expects RPC +/// messages, use |vim.rpcnotify()| and |vim.rpcrequest()| instead. +/// +/// @param chan id of the channel +/// @param data data to write. 8-bit clean: can contain NUL bytes. +/// @param[out] err Error details, if any +void nvim_chan_send(Integer chan, String data, Error *err) + FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY FUNC_API_LUA_ONLY +{ + const char *error = NULL; + if (!data.size) { + return; + } + + channel_send((uint64_t)chan, data.data, data.size, &error); + if (error) { + api_set_error(err, kErrorTypeValidation, "%s", error); + } +} + /// Open a new window. /// /// Currently this is used to open floating and external windows. @@ -1323,6 +1416,25 @@ fail: /// end-of-buffer region is hidden by setting `eob` flag of /// 'fillchars' to a space char, and clearing the /// |EndOfBuffer| region in 'winhighlight'. +/// - `border`: style of (optional) window border. This can either be a string +/// or an array. the string values are: +/// - "none" No border. This is the default +/// - "single" a single line box +/// - "double" a double line box +/// 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 +/// example, the double box style could be specified as: +/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ] +/// if the number of chars are less than eight, they will be repeated. Thus +/// an ASCII border could be specified as: +/// [ "/", "-", "\\", "|" ] +/// or all chars the same as: +/// [ "x" ] +/// By default `FloatBorder` highlight is used which links to `VertSplit` +/// when not defined. It could also be specified by character: +/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ] +/// /// @param[out] err Error details, if any /// /// @return Window handle, or 0 on error @@ -2596,6 +2708,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; } @@ -2738,8 +2851,8 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) g = &pum_grid; } else if (grid > 1) { win_T *wp = get_win_by_grid_handle((handle_T)grid); - if (wp != NULL && wp->w_grid.chars != NULL) { - g = &wp->w_grid; + if (wp != NULL && wp->w_grid_alloc.chars != NULL) { + g = &wp->w_grid_alloc; } else { api_set_error(err, kErrorTypeValidation, "No grid with the given handle"); @@ -2768,19 +2881,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 @@ -2825,8 +2925,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? @@ -2848,7 +2948,7 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, String k = opts.items[i].key; Object *v = &opts.items[i].value; size_t j; - for (j = 0; cbs[j].name; j++) { + for (j = 0; cbs[j].name && cbs[j].dest; j++) { if (strequal(cbs[j].name, k.data)) { if (v->type != kObjectTypeLuaRef) { api_set_error(err, kErrorTypeValidation, @@ -2869,5 +2969,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/api/window.c b/src/nvim/api/window.c index f4af1632ec..89fa2f86fb 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -492,6 +492,35 @@ Dictionary nvim_win_get_config(Window window, Error *err) return rv; } +/// Closes the window and hide the buffer it contains (like |:hide| with a +/// |window-ID|). +/// +/// Like |:hide| the buffer becomes hidden unless another window is editing it, +/// or 'bufhidden' is `unload`, `delete` or `wipe` as opposed to |:close| or +/// |nvim_win_close|, which will close the buffer. +/// +/// @param window Window handle, or 0 for current window +/// @param[out] err Error details, if any +void nvim_win_hide(Window window, Error *err) + FUNC_API_SINCE(7) + FUNC_API_CHECK_TEXTLOCK +{ + win_T *win = find_window_by_handle(window, err); + if (!win) { + return; + } + + tabpage_T *tabpage = win_find_tabpage(win); + TryState tstate; + try_enter(&tstate); + if (tabpage == curtab) { + win_close(win, false); + } else { + win_close_othertab(win, false, tabpage); + } + vim_ignored = try_leave(&tstate, err); +} + /// Closes the window (like |:close| with a |window-ID|). /// /// @param window Window handle, or 0 for current window diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 3de2e0f342..145f6f5601 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -968,7 +968,7 @@ static int do_autocmd_event(event_T event, // Implementation of ":doautocmd [group] event [fname]". // Return OK for success, FAIL for failure; int do_doautocmd(char_u *arg, - int do_msg, // give message for no matching autocmds? + bool do_msg, // give message for no matching autocmds? bool *did_something) { char_u *fname; @@ -1017,11 +1017,12 @@ int do_doautocmd(char_u *arg, // ":doautoall": execute autocommands for each loaded buffer. void ex_doautoall(exarg_T *eap) { - int retval; + int retval = OK; aco_save_T aco; char_u *arg = eap->arg; int call_do_modelines = check_nomodeline(&arg); bufref_T bufref; + bool did_aucmd; // This is a bit tricky: For some commands curwin->w_buffer needs to be // equal to curbuf, but for some buffers there may not be a window. @@ -1029,14 +1030,14 @@ void ex_doautoall(exarg_T *eap) // gives problems when the autocommands make changes to the list of // buffers or windows... FOR_ALL_BUFFERS(buf) { - if (buf->b_ml.ml_mfp == NULL) { + // Only do loaded buffers and skip the current buffer, it's done last. + if (buf->b_ml.ml_mfp == NULL || buf == curbuf) { continue; } // Find a window for this buffer and save some values. aucmd_prepbuf(&aco, buf); set_bufref(&bufref, buf); - bool did_aucmd; // execute the autocommands for this buffer retval = do_doautocmd(arg, false, &did_aucmd); @@ -1052,10 +1053,19 @@ void ex_doautoall(exarg_T *eap) // Stop if there is some error or buffer was deleted. if (retval == FAIL || !bufref_valid(&bufref)) { + retval = FAIL; break; } } + // Execute autocommands for the current buffer last. + if (retval == OK) { + (void)do_doautocmd(arg, false, &did_aucmd); + if (call_do_modelines && did_aucmd) { + do_modelines(0); + } + } + check_cursor(); // just in case lines got deleted } @@ -1182,10 +1192,10 @@ void aucmd_restbuf(aco_save_T *aco) win_remove(curwin, NULL); handle_unregister_window(curwin); - if (curwin->w_grid.chars != NULL) { - ui_comp_remove_grid(&curwin->w_grid); - ui_call_win_hide(curwin->w_grid.handle); - grid_free(&curwin->w_grid); + if (curwin->w_grid_alloc.chars != NULL) { + ui_comp_remove_grid(&curwin->w_grid_alloc); + ui_call_win_hide(curwin->w_grid_alloc.handle); + grid_free(&curwin->w_grid_alloc); } aucmd_win_used = false; @@ -1611,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); @@ -1661,11 +1679,13 @@ static bool apply_autocmds_group(event_T event, did_filetype = false; while (au_pending_free_buf != NULL) { buf_T *b = au_pending_free_buf->b_next; + xfree(au_pending_free_buf); au_pending_free_buf = b; } while (au_pending_free_win != NULL) { win_T *w = au_pending_free_win->w_next; + xfree(au_pending_free_win); au_pending_free_win = w; } diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index c42a0e2dad..c7ec3a456c 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -282,7 +282,7 @@ int open_buffer( // Set/reset the Changed flag first, autocmds may change the buffer. // Apply the automatic commands, before processing the modelines. - // So the modelines have priority over auto commands. + // So the modelines have priority over autocommands. // When reading stdin, the buffer contents always needs writing, so set // the changed flag. Unless in readonly mode: "ls | nvim -R -". @@ -1946,6 +1946,13 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_fo); clear_string_option(&buf->b_p_flp); clear_string_option(&buf->b_p_isk); + clear_string_option(&buf->b_p_vsts); + xfree(buf->b_p_vsts_nopaste); + buf->b_p_vsts_nopaste = NULL; + xfree(buf->b_p_vsts_array); + buf->b_p_vsts_array = NULL; + clear_string_option(&buf->b_p_vts); + XFREE_CLEAR(buf->b_p_vts_array); clear_string_option(&buf->b_p_keymap); keymap_ga_clear(&buf->b_kmap_ga); ga_clear(&buf->b_kmap_ga); @@ -5375,8 +5382,8 @@ bool bt_terminal(const buf_T *const buf) return buf != NULL && buf->b_p_bt[0] == 't'; } -// Return true if "buf" is a "nofile", "acwrite" or "terminal" buffer. -// This means the buffer name is not a file name. +// Return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" +// buffer. This means the buffer name is not a file name. bool bt_nofile(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5386,7 +5393,8 @@ bool bt_nofile(const buf_T *const buf) || buf->b_p_bt[0] == 'p'); } -// Return true if "buf" is a "nowrite", "nofile" or "terminal" buffer. +// Return true if "buf" is a "nowrite", "nofile", "terminal" or "prompt" +// buffer. bool bt_dontwrite(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index a05bd6fcc7..b36b7beab8 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -743,6 +743,11 @@ struct file_buffer { long b_p_wm; ///< 'wrapmargin' long b_p_wm_nobin; ///< b_p_wm saved for binary mode long b_p_wm_nopaste; ///< b_p_wm saved for paste mode + char_u *b_p_vsts; ///< 'varsofttabstop' + long *b_p_vsts_array; ///< 'varsofttabstop' in internal format + char_u *b_p_vsts_nopaste; ///< b_p_vsts saved for paste mode + char_u *b_p_vts; ///< 'vartabstop' + long *b_p_vts_array; ///< 'vartabstop' in internal format char_u *b_p_keymap; ///< 'keymap' // local values for options which are normally global @@ -1079,6 +1084,10 @@ typedef struct { bool external; bool focusable; WinStyle style; + bool border; + schar_T border_chars[8]; + int border_hl_ids[8]; + int border_attr[8]; } FloatConfig; #define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \ @@ -1192,6 +1201,7 @@ struct window_S { int tab1; ///< first tab character int tab2; ///< second tab character int tab3; ///< third tab character + int lead; int trail; int conceal; } w_p_lcs_chars; @@ -1256,6 +1266,11 @@ struct window_S { int w_height_request; int w_width_request; + int w_border_adj; + // outer size of window grid, including border + int w_height_outer; + int w_width_outer; + /* * === start of cached values ==== */ @@ -1331,7 +1346,8 @@ struct window_S { // w_redr_type is REDRAW_TOP linenr_T w_redraw_top; // when != 0: first line needing redraw linenr_T w_redraw_bot; // when != 0: last line needing redraw - int w_redr_status; // if TRUE status line must be redrawn + bool w_redr_status; // if true status line must be redrawn + bool w_redr_border; // if true border must be redrawn // remember what is shown in the ruler for this window (if 'ruler' set) pos_T w_ru_cursor; // cursor position shown in ruler @@ -1409,6 +1425,7 @@ struct window_S { int w_tagstacklen; // number of tags on stack ScreenGrid w_grid; // the grid specific to the window + ScreenGrid w_grid_alloc; // the grid specific to the window bool w_pos_changed; // true if window position changed bool w_floating; ///< whether the window is floating FloatConfig w_float_config; 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/change.c b/src/nvim/change.c index 0f5081c94c..38bd591eca 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -828,6 +828,7 @@ int copy_indent(int size, char_u *src) int tab_pad; int ind_done; int round; + int ind_col; // Round 1: compute the number of characters needed for the indent // Round 2: copy the characters. @@ -835,13 +836,15 @@ int copy_indent(int size, char_u *src) todo = size; ind_len = 0; ind_done = 0; + ind_col = 0; s = src; // Count/copy the usable portion of the source line. while (todo > 0 && ascii_iswhite(*s)) { if (*s == TAB) { - tab_pad = (int)curbuf->b_p_ts - - (ind_done % (int)curbuf->b_p_ts); + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); // Stop if this tab will overshoot the target. if (todo < tab_pad) { @@ -849,9 +852,11 @@ int copy_indent(int size, char_u *src) } todo -= tab_pad; ind_done += tab_pad; + ind_col += tab_pad; } else { todo--; ind_done++; + ind_col++; } ind_len++; @@ -862,11 +867,12 @@ int copy_indent(int size, char_u *src) } // Fill to next tabstop with a tab, if possible. - tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); + tab_pad = tabstop_padding(ind_done, curbuf->b_p_ts, curbuf->b_p_vts_array); if ((todo >= tab_pad) && !curbuf->b_p_et) { todo -= tab_pad; ind_len++; + ind_col += tab_pad; if (p != NULL) { *p++ = TAB; @@ -874,12 +880,20 @@ int copy_indent(int size, char_u *src) } // Add tabs required for indent. - while (todo >= (int)curbuf->b_p_ts && !curbuf->b_p_et) { - todo -= (int)curbuf->b_p_ts; - ind_len++; - - if (p != NULL) { - *p++ = TAB; + if (!curbuf->b_p_et) { + for (;;) { + tab_pad = tabstop_padding(ind_col, + curbuf->b_p_ts, + curbuf->b_p_vts_array); + if (todo < tab_pad) { + break; + } + todo -= tab_pad; + ind_len++; + ind_col += tab_pad; + if (p != NULL) { + *p++ = TAB; + } } } @@ -1029,7 +1043,9 @@ int open_line( || do_si ) { // count white space on current line - newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts, false); + newindent = get_indent_str_vtab(saved_line, + curbuf->b_p_ts, + curbuf->b_p_vts_array, false); if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) { newindent = second_line_indent; // for ^^D command in insert mode } @@ -1453,7 +1469,9 @@ int open_line( if (curbuf->b_p_ai || do_si ) { - newindent = get_indent_str(leader, (int)curbuf->b_p_ts, false); + newindent = get_indent_str_vtab(leader, + curbuf->b_p_ts, + curbuf->b_p_vts_array, false); } // Add the indent offset diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 09a34ca9fe..7a08ba58d0 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -161,7 +161,7 @@ void channel_init(void) /// /// Channel is allocated with refcount 1, which should be decreased /// when the underlying stream closes. -static Channel *channel_alloc(ChannelStreamType type) +Channel *channel_alloc(ChannelStreamType type) { Channel *chan = xcalloc(1, sizeof(*chan)); if (type == kChannelStreamStdio) { @@ -503,7 +503,7 @@ size_t channel_send(uint64_t id, char *data, size_t len, const char **error) { Channel *chan = find_channel(id); if (!chan) { - EMSG(_(e_invchan)); + *error = _(e_invchan); goto err; } @@ -518,6 +518,11 @@ size_t channel_send(uint64_t id, char *data, size_t len, const char **error) return len * written; } + if (chan->streamtype == kChannelStreamInternal && chan->term) { + terminal_receive(chan->term, data, len); + return len; + } + Stream *in = channel_instream(chan); if (in->closed) { @@ -724,8 +729,8 @@ static void channel_callback_call(Channel *chan, CallbackReader *reader) /// Open terminal for channel /// /// Channel `chan` is assumed to be an open pty channel, -/// and curbuf is assumed to be a new, unmodified buffer. -void channel_terminal_open(Channel *chan) +/// and `buf` is assumed to be a new, unmodified buffer. +void channel_terminal_open(buf_T *buf, Channel *chan) { TerminalOptions topts; topts.data = chan; @@ -734,8 +739,8 @@ void channel_terminal_open(Channel *chan) topts.write_cb = term_write; topts.resize_cb = term_resize; topts.close_cb = term_close; - curbuf->b_p_channel = (long)chan->id; // 'channel' option - Terminal *term = terminal_open(topts); + buf->b_p_channel = (long)chan->id; // 'channel' option + Terminal *term = terminal_open(buf, topts); chan->term = term; channel_incref(chan); } diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 5ad1fe0dfd..e2d844a351 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -744,8 +744,7 @@ int vim_strnsize(char_u *s, int len) /// @return Number of characters. #define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \ if (*(p) == TAB && (!(wp)->w_p_list || wp->w_p_lcs_chars.tab1)) { \ - const int ts = (int)(buf)->b_p_ts; \ - return (ts - (int)(col % ts)); \ + return tabstop_padding(col, (buf)->b_p_ts, (buf)->b_p_vts_array); \ } else { \ return ptr2cells(p); \ } @@ -1143,8 +1142,9 @@ static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int *headp) int n; if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { - n = (int)wp->w_buffer->b_p_ts; - return n - (col % n); + return tabstop_padding(col, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array); } n = ptr2cells(s); @@ -1211,6 +1211,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, char_u *line; // start of the line int incr; int head; + long *vts = wp->w_buffer->b_p_vts_array; int ts = (int)wp->w_buffer->b_p_ts; int c; @@ -1251,7 +1252,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, // A tab gets expanded, depending on the current column if (c == TAB) { - incr = ts - (vcol % ts); + incr = tabstop_padding(vcol, ts, vts); } else { // For utf-8, if the byte is >= 0x80, need to look at // further bytes to find the cell width. diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 9a20b06660..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" @@ -145,8 +146,7 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state) for (size_t i = 0; i < kv_size(state->active); i++) { HlRange item = kv_A(state->active, i); if (item.virt_text_owned) { - clear_virttext(item.virt_text); - xfree(item.virt_text); + clear_virttext(&item.virt_text); } } kv_size(state->active) = 0; @@ -229,7 +229,7 @@ static void decor_add(DecorState *state, int start_row, int start_col, HlRange range = { start_row, start_col, end_row, end_col, attr_id, MAX(priority, decor->priority), - kv_size(decor->virt_text) ? &decor->virt_text : NULL, + decor->virt_text, decor->virt_text_pos, decor->virt_text_hide, decor->hl_mode, kv_size(decor->virt_text) && owned, -1 }; @@ -304,7 +304,7 @@ next_mark: bool active = false, keep = true; if (item.end_row < state->row || (item.end_row == state->row && item.end_col <= col)) { - if (!(item.start_row >= state->row && item.virt_text)) { + if (!(item.start_row >= state->row && kv_size(item.virt_text))) { keep = false; } } else { @@ -324,14 +324,13 @@ next_mark: attr = hl_combine_attr(attr, item.attr_id); } if ((item.start_row == state->row && item.start_col <= col) - && item.virt_text && item.virt_col == -1) { + && kv_size(item.virt_text) && item.virt_col == -1) { item.virt_col = (item.virt_text_hide && hidden) ? -2 : virt_col; } if (keep) { kv_A(state->active, j++) = item; } else if (item.virt_text_owned) { - clear_virttext(item.virt_text); - xfree(item.virt_text); + clear_virttext(&item.virt_text); } } kv_size(state->active) = j; @@ -344,22 +343,75 @@ void decor_redraw_end(DecorState *state) state->buf = NULL; } -VirtText *decor_redraw_virt_text(buf_T *buf, DecorState *state) +VirtText decor_redraw_virt_text(buf_T *buf, DecorState *state) { decor_redraw_col(buf, MAXCOL, MAXCOL, false, state); for (size_t i = 0; i < kv_size(state->active); i++) { HlRange item = kv_A(state->active, i); - if (item.start_row == state->row && item.virt_text + if (item.start_row == state->row && kv_size(item.virt_text) && item.virt_text_pos == kVTEndOfLine) { return item.virt_text; } } - return NULL; + return VIRTTEXT_EMPTY; } void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, Decoration *decor, DecorPriority priority) { + if (end_row == -1) { + end_row = start_row; + end_col = start_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/decoration.h b/src/nvim/decoration.h index 264e8a4a82..c5424a1642 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -53,7 +53,7 @@ typedef struct { // TODO(bfredl): embed decoration instead, perhaps using an arena // for ephemerals? DecorPriority priority; - VirtText *virt_text; + VirtText virt_text; VirtTextPos virt_text_pos; bool virt_text_hide; HlMode hl_mode; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 68c7438ea3..49bd170bcd 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -597,7 +597,10 @@ static int insert_check(VimState *state) s->mincol = curwin->w_wcol; validate_cursor_col(); - if (curwin->w_wcol < s->mincol - curbuf->b_p_ts + if ( + curwin->w_wcol < s->mincol - tabstop_at(get_nolist_virtcol(), + curbuf->b_p_ts, + curbuf->b_p_vts_array) && curwin->w_wrow == curwin->w_winrow + curwin->w_height_inner - 1 - get_scrolloff_value(curwin) && (curwin->w_cursor.lnum != curwin->w_topline @@ -1565,7 +1568,7 @@ void edit_putchar(int c, bool highlight) { int attr; - if (curwin->w_grid.chars != NULL || default_grid.chars != NULL) { + if (curwin->w_grid_alloc.chars != NULL || default_grid.chars != NULL) { update_topline(curwin); // just in case w_topline isn't valid validate_cursor(); if (highlight) { @@ -7266,7 +7269,6 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) char_u *p; char_u *line; int icase; - int i; if (keytyped == NUL) { // Can happen with CTRL-Y and CTRL-E on a short line. @@ -7351,8 +7353,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) && p[curwin->w_cursor.col - 1] == ':' && p[curwin->w_cursor.col - 2] == ':') { p[curwin->w_cursor.col - 1] = ' '; - i = (cin_iscase(p, FALSE) || cin_isscopedecl(p) - || cin_islabel()); + const bool i = cin_iscase(p, false) + || cin_isscopedecl(p) + || cin_islabel(); p = get_cursor_line_ptr(); p[curwin->w_cursor.col - 1] = ':'; if (i) { @@ -8178,24 +8181,20 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) /* * Handle deleting one 'shiftwidth' or 'softtabstop'. */ - if ( mode == BACKSPACE_CHAR - && ((p_sta && in_indent) - || (get_sts_value() != 0 - && curwin->w_cursor.col > 0 - && (*(get_cursor_pos_ptr() - 1) == TAB - || (*(get_cursor_pos_ptr() - 1) == ' ' - && (!*inserted_space_p - || arrow_used)))))) { + if (mode == BACKSPACE_CHAR + && ((p_sta && in_indent) + || ((get_sts_value() != 0 + || tabstop_count(curbuf->b_p_vsts_array)) + && curwin->w_cursor.col > 0 + && (*(get_cursor_pos_ptr() - 1) == TAB + || (*(get_cursor_pos_ptr() - 1) == ' ' + && (!*inserted_space_p || arrow_used)))))) { int ts; colnr_T vcol; colnr_T want_vcol; colnr_T start_vcol; - *inserted_space_p = FALSE; - if (p_sta && in_indent) - ts = get_sw_value(curbuf); - else - ts = get_sts_value(); + *inserted_space_p = false; // Compute the virtual column where we want to be. Since // 'showbreak' may get in the way, need to get the last column of // the previous character. @@ -8204,7 +8203,14 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) dec_cursor(); getvcol(curwin, &curwin->w_cursor, NULL, NULL, &want_vcol); inc_cursor(); - want_vcol = (want_vcol / ts) * ts; + if (p_sta && in_indent) { + ts = (int)get_sw_value(curbuf); + want_vcol = (want_vcol / ts) * ts; + } else { + want_vcol = tabstop_start(want_vcol, + get_sts_value(), + curbuf->b_p_vsts_array); + } // delete characters until we are at or before want_vcol while (vcol > want_vcol @@ -8669,10 +8675,19 @@ static bool ins_tab(void) can_cindent = false; } - // When nothing special, insert TAB like a normal character + // When nothing special, insert TAB like a normal character. if (!curbuf->b_p_et - && !(p_sta && ind && curbuf->b_p_ts != get_sw_value(curbuf)) - && get_sts_value() == 0) { + && !( + p_sta + && ind + // These five lines mean 'tabstop' != 'shiftwidth' + && ((tabstop_count(curbuf->b_p_vts_array) > 1) + || (tabstop_count(curbuf->b_p_vts_array) == 1 + && tabstop_first(curbuf->b_p_vts_array) + != get_sw_value(curbuf)) + || (tabstop_count(curbuf->b_p_vts_array) == 0 + && curbuf->b_p_ts != get_sw_value(curbuf)))) + && tabstop_count(curbuf->b_p_vsts_array) == 0 && get_sts_value() == 0) { return true; } @@ -8686,16 +8701,22 @@ static bool ins_tab(void) can_si_back = false; AppendToRedobuff("\t"); - if (p_sta && ind) { // insert tab in indent, use "shiftwidth" - temp = get_sw_value(curbuf); - } else if (curbuf->b_p_sts != 0) { // use "softtabstop" when set - temp = get_sts_value(); - } else { // otherwise use "tabstop" - temp = (int)curbuf->b_p_ts; + if (p_sta && ind) { // insert tab in indent, use 'shiftwidth' + temp = (int)get_sw_value(curbuf); + temp -= get_nolist_virtcol() % temp; + } else if (tabstop_count(curbuf->b_p_vsts_array) > 0 + || curbuf->b_p_sts != 0) { + // use 'softtabstop' when set + temp = tabstop_padding(get_nolist_virtcol(), + get_sts_value(), + curbuf->b_p_vsts_array); + } else { + // otherwise use 'tabstop' + temp = tabstop_padding(get_nolist_virtcol(), + curbuf->b_p_ts, + curbuf->b_p_vts_array); } - temp -= get_nolist_virtcol() % temp; - /* * Insert the first space with ins_char(). It will delete one char in * replace mode. Insert the rest with ins_str(); it will not delete any @@ -8716,7 +8737,9 @@ static bool ins_tab(void) /* * When 'expandtab' not set: Replace spaces by TABs where possible. */ - if (!curbuf->b_p_et && (get_sts_value() || (p_sta && ind))) { + if (!curbuf->b_p_et && (tabstop_count(curbuf->b_p_vsts_array) > 0 + || get_sts_value() > 0 + || (p_sta && ind))) { char_u *ptr; char_u *saved_line = NULL; // init for GCC pos_T pos; @@ -8764,6 +8787,10 @@ 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)) { @@ -8813,6 +8840,11 @@ static bool ins_tab(void) replace_join(repl_off); } } + if (!(State & VREPLACE_FLAG)) { + extmark_splice_cols(curbuf, start_row - 1, start_col, + cursor->col - start_col, fpos.col - start_col, + kExtmarkUndo); + } } cursor->col -= i; @@ -9124,10 +9156,16 @@ static void ins_try_si(int c) * Get the value that w_virtcol would have when 'list' is off. * Unless 'cpo' contains the 'L' flag. */ -static colnr_T get_nolist_virtcol(void) +colnr_T get_nolist_virtcol(void) { - if (curwin->w_p_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) + // check validity of cursor in current buffer + if (curwin->w_buffer == NULL || curwin->w_buffer->b_ml.ml_mfp == NULL + || curwin->w_cursor.lnum > curwin->w_buffer->b_ml.ml_line_count) { + return 0; + } + if (curwin->w_p_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) { return getvcol_nolist(&curwin->w_cursor); + } validate_virtcol(); return curwin->w_virtcol; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 63d5216cc4..550fe8ab65 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3007,7 +3007,8 @@ static size_t varnamebuflen = 0; /* * Function to concatenate a prefix and a variable name. */ -static char_u *cat_prefix_varname(int prefix, char_u *name) +char_u *cat_prefix_varname(int prefix, const char_u *name) + FUNC_ATTR_NONNULL_ALL { size_t len = STRLEN(name) + 3; @@ -5288,10 +5289,10 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, QUEUE *w = NULL; DictWatcher *watcher = NULL; - QUEUE_FOREACH(w, &dd->watchers) { + QUEUE_FOREACH(w, &dd->watchers, { watcher = tv_dict_watcher_node_data(w); set_ref_in_callback(&watcher->callback, copyID, ht_stack, list_stack); - } + }) } break; } @@ -5866,26 +5867,53 @@ int assert_inrange(typval_T *argvars) FUNC_ATTR_NONNULL_ALL { bool error = false; - const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); - const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); - const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); - if (error) { - return 0; - } - if (actual < lower || actual > upper) { - garray_T ga; - prepare_assert_error(&ga); + if (argvars[0].v_type == VAR_FLOAT + || argvars[1].v_type == VAR_FLOAT + || argvars[2].v_type == VAR_FLOAT) { + const float_T flower = tv_get_float(&argvars[0]); + const float_T fupper = tv_get_float(&argvars[1]); + const float_T factual = tv_get_float(&argvars[2]); - char msg[55]; - vim_snprintf(msg, sizeof(msg), - "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", - lower, upper); - fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], - ASSERT_INRANGE); - assert_error(&ga); - ga_clear(&ga); - return 1; + if (factual < flower || factual > fupper) { + garray_T ga; + prepare_assert_error(&ga); + if (argvars[3].v_type != VAR_UNKNOWN) { + char_u *const tofree = (char_u *)encode_tv2string(&argvars[3], NULL); + ga_concat(&ga, tofree); + xfree(tofree); + } else { + char msg[80]; + vim_snprintf(msg, sizeof(msg), "Expected range %g - %g, but got %g", + flower, fupper, factual); + ga_concat(&ga, (char_u *)msg); + } + assert_error(&ga); + ga_clear(&ga); + return 1; + } + } else { + const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); + const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); + const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); + + if (error) { + return 0; + } + if (actual < lower || actual > upper) { + garray_T ga; + prepare_assert_error(&ga); + + char msg[55]; + vim_snprintf(msg, sizeof(msg), + "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", + lower, upper); + fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], + ASSERT_INRANGE); + assert_error(&ga); + ga_clear(&ga); + return 1; + } } return 0; } @@ -5953,6 +5981,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 { @@ -6206,6 +6263,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]); @@ -7305,7 +7363,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 { @@ -8500,7 +8557,7 @@ static bool tv_is_luafunc(typval_T *tv) int check_luafunc_name(const char *str, bool paren) { const char *p = str; - while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') { + while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.' || *p == '\'') { p++; } if (*p != (paren ? '(' : NUL)) { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index eac0feafcf..aed0824ad6 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}, @@ -309,7 +310,7 @@ return { setwinvar={args=3}, sha256={args=1}, shellescape={args={1, 2}}, - shiftwidth={}, + shiftwidth={args={0, 1}}, sign_define={args={1, 2}}, sign_getdefined={args={0, 1}}, sign_getplaced={args={0, 2}}, @@ -341,6 +342,7 @@ return { string={args=1}, strlen={args=1}, strpart={args={2, 4}}, + strptime={args=2}, strridx={args={2, 3}}, strtrans={args=1}, strwidth={args=1}, @@ -392,6 +394,7 @@ return { win_id2tabwin={args=1}, win_id2win={args=1}, win_screenpos={args=1}, + win_splitmove={args={2, 3}}, winbufnr={args=1}, wincol={}, windowsversion={}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 60229e1ebc..51e5a27348 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -117,8 +117,12 @@ char_u *get_function_name(expand_T *xp, int idx) intidx = -1; if (intidx < 0) { name = get_user_func_name(xp, idx); - if (name != NULL) + if (name != NULL) { + if (*name != '<' && STRNCMP("g:", xp->xp_pattern, 2) == 0) { + return cat_prefix_varname('g', name); + } return name; + } } while ((size_t)++intidx < ARRAY_SIZE(functions) && functions[intidx].name[0] == '\0') { @@ -387,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 @@ -818,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; @@ -828,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]); } @@ -845,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); + } } /* @@ -3976,6 +3973,87 @@ static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); } +// +// Move the window wp into a new split of targetwin in a given direction +// +static void win_move_into_split(win_T *wp, win_T *targetwin, + int size, int flags) +{ + int dir; + int height = wp->w_height; + win_T *oldwin = curwin; + + if (wp == targetwin) { + return; + } + + // Jump to the target window + if (curwin != targetwin) { + win_goto(targetwin); + } + + // Remove the old window and frame from the tree of frames + (void)winframe_remove(wp, &dir, NULL); + win_remove(wp, NULL); + last_status(false); // may need to remove last status line + (void)win_comp_pos(); // recompute window positions + + // Split a window on the desired side and put the old window there + (void)win_split_ins(size, flags, wp, dir); + + // If splitting horizontally, try to preserve height + if (size == 0 && !(flags & WSP_VERT)) { + win_setheight_win(height, wp); + if (p_ea) { + win_equal(wp, true, 'v'); + } + } + + if (oldwin != curwin) { + win_goto(oldwin); + } +} + +// "win_splitmove()" function +static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp; + win_T *targetwin; + int flags = 0, size = 0; + + wp = find_win_by_nr_or_id(&argvars[0]); + targetwin = find_win_by_nr_or_id(&argvars[1]); + + if (wp == NULL || targetwin == NULL || wp == targetwin + || !win_valid(wp) || !win_valid(targetwin) + || win_valid_floating(wp) || win_valid_floating(targetwin)) { + EMSG(_(e_invalwindow)); + rettv->vval.v_number = -1; + return; + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + dict_T *d; + dictitem_T *di; + + if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) { + EMSG(_(e_invarg)); + return; + } + + d = argvars[2].vval.v_dict; + if (tv_dict_get_number(d, "vertical")) { + flags |= WSP_VERT; + } + if ((di = tv_dict_find(d, "rightbelow", -1)) != NULL) { + flags |= tv_get_number(&di->di_tv) ? WSP_BELOW : WSP_ABOVE; + } + size = tv_dict_get_number(d, "size"); + } + + win_move_into_split(wp, targetwin, size, flags); +} + // "getwinpos({timeout})" function static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -4227,6 +4305,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "title", "user-commands", // was accidentally included in 5.4 "user_commands", + "vartabs", "vertsplit", "virtualedit", "visual", @@ -8724,8 +8803,6 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (set_tagstack(wp, d, action) == OK) { rettv->vval.v_number = 0; - } else { - EMSG(_(e_listreq)); } } @@ -8765,6 +8842,18 @@ static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) { + rettv->vval.v_number = 0; + + if (argvars[0].v_type != VAR_UNKNOWN) { + long col; + + col = (long)tv_get_number_chk(argvars, NULL); + if (col < 0) { + return; // type error; errmsg already given + } + rettv->vval.v_number = get_sw_value_col(curbuf, col); + return; + } rettv->vval.v_number = get_sw_value(curbuf); } @@ -8906,7 +8995,7 @@ static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = -1; - // Sign identifer + // Sign identifier sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); if (notanum) { return; @@ -8954,7 +9043,7 @@ static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = -1; - // Sign identifer + // Sign identifier sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); if (notanum) { return; @@ -10106,6 +10195,38 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len); } +// "strptime({format}, {timestring})" function +static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char fmt_buf[NUMBUFLEN]; + char str_buf[NUMBUFLEN]; + + struct tm tmval = { + .tm_isdst = -1, + }; + char *fmt = (char *)tv_get_string_buf(&argvars[0], fmt_buf); + char *str = (char *)tv_get_string_buf(&argvars[1], str_buf); + + vimconv_T conv = { + .vc_type = CONV_NONE, + }; + char_u *enc = enc_locale(); + convert_setup(&conv, p_enc, enc); + if (conv.vc_type != CONV_NONE) { + fmt = (char *)string_convert(&conv, (char_u *)fmt, NULL); + } + if (fmt == NULL + || os_strptime(str, fmt, &tmval) == NULL + || (rettv->vval.v_number = mktime(&tmval)) == -1) { + rettv->vval.v_number = 0; + } + if (conv.vc_type != CONV_NONE) { + xfree(fmt); + } + convert_setup(&conv, NULL, NULL); + xfree(enc); +} + /* * "strridx()" function */ @@ -10731,7 +10852,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) INTEGER_OBJ(pid), false, false, &err); api_clear_error(&err); - channel_terminal_open(chan); + channel_terminal_open(curbuf, chan); channel_create_event(chan, NULL); } @@ -11293,17 +11414,23 @@ static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int winnr = 1; garray_T ga; char_u buf[50]; ga_init(&ga, (int)sizeof(char), 70); - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height); - ga_concat(&ga, buf); - sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width); - ga_concat(&ga, buf); - ++winnr; + + // Do this twice to handle some window layouts properly. + for (int i = 0; i < 2; i++) { + int winnr = 1; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + snprintf((char *)buf, sizeof(buf), "%dresize %d|", winnr, + wp->w_height); + ga_concat(&ga, buf); + snprintf((char *)buf, sizeof(buf), "vert %dresize %d|", winnr, + wp->w_width); + ga_concat(&ga, buf); + winnr++; + } } ga_append(&ga, NUL); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 9be487f4fd..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); } @@ -1109,6 +1110,7 @@ void tv_dict_watcher_add(dict_T *const dict, const char *const key_pattern, watcher->key_pattern_len = key_pattern_len; watcher->callback = callback; watcher->busy = false; + watcher->needs_free = false; QUEUE_INSERT_TAIL(&dict->watchers, &watcher->node); } @@ -1182,22 +1184,30 @@ bool tv_dict_watcher_remove(dict_T *const dict, const char *const key_pattern, QUEUE *w = NULL; DictWatcher *watcher = NULL; bool matched = false; - QUEUE_FOREACH(w, &dict->watchers) { + bool queue_is_busy = false; + QUEUE_FOREACH(w, &dict->watchers, { watcher = tv_dict_watcher_node_data(w); + if (watcher->busy) { + queue_is_busy = true; + } if (tv_callback_equal(&watcher->callback, &callback) && watcher->key_pattern_len == key_pattern_len && memcmp(watcher->key_pattern, key_pattern, key_pattern_len) == 0) { matched = true; break; } - } + }) if (!matched) { return false; } - QUEUE_REMOVE(w); - tv_dict_watcher_free(watcher); + if (queue_is_busy) { + watcher->needs_free = true; + } else { + QUEUE_REMOVE(w); + tv_dict_watcher_free(watcher); + } return true; } @@ -1258,9 +1268,10 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, typval_T rettv; + bool any_needs_free = false; dict->dv_refcount++; QUEUE *w; - QUEUE_FOREACH(w, &dict->watchers) { + QUEUE_FOREACH(w, &dict->watchers, { DictWatcher *watcher = tv_dict_watcher_node_data(w); if (!watcher->busy && tv_dict_watcher_matches(watcher, key)) { rettv = TV_INITIAL_VALUE; @@ -1268,7 +1279,19 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, callback_call(&watcher->callback, 3, argv, &rettv); watcher->busy = false; tv_clear(&rettv); + if (watcher->needs_free) { + any_needs_free = true; + } } + }) + if (any_needs_free) { + QUEUE_FOREACH(w, &dict->watchers, { + DictWatcher *watcher = tv_dict_watcher_node_data(w); + if (watcher->needs_free) { + QUEUE_REMOVE(w); + tv_dict_watcher_free(watcher); + } + }) } tv_dict_unref(dict); @@ -1382,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; } @@ -1432,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/eval/typval.h b/src/nvim/eval/typval.h index 531b17cb59..2b4612016b 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -89,6 +89,7 @@ typedef struct dict_watcher { size_t key_pattern_len; QUEUE node; bool busy; // prevent recursion if the dict is changed in the callback + bool needs_free; } DictWatcher; /// Bool variable values diff --git a/src/nvim/event/multiqueue.c b/src/nvim/event/multiqueue.c index c9aa3acc4d..1e6d62135c 100644 --- a/src/nvim/event/multiqueue.c +++ b/src/nvim/event/multiqueue.c @@ -119,8 +119,8 @@ static MultiQueue *multiqueue_new(MultiQueue *parent, put_callback put_cb, void multiqueue_free(MultiQueue *this) { assert(this); - while (!QUEUE_EMPTY(&this->headtail)) { - QUEUE *q = QUEUE_HEAD(&this->headtail); + QUEUE *q; + QUEUE_FOREACH(q, &this->headtail, { MultiQueueItem *item = multiqueue_node_data(q); if (this->parent) { QUEUE_REMOVE(&item->data.item.parent_item->node); @@ -128,7 +128,7 @@ void multiqueue_free(MultiQueue *this) } QUEUE_REMOVE(q); xfree(item); - } + }) xfree(this); } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 854faf5377..d34282419a 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -712,14 +712,15 @@ void ex_retab(exarg_T *eap) long len; long col; long vcol; - long start_col = 0; /* For start of white-space string */ - long start_vcol = 0; /* For start of white-space string */ - int temp; + long start_col = 0; // For start of white-space string + long start_vcol = 0; // For start of white-space string long old_len; char_u *ptr; - char_u *new_line = (char_u *)1; /* init to non-NULL */ - int did_undo; /* called u_save for current line */ - int new_ts; + char_u *new_line = (char_u *)1; // init to non-NULL + int did_undo; // called u_save for current line + long *new_vts_array = NULL; + char_u *new_ts_str; // string value of tab argument + int save_list; linenr_T first_line = 0; /* first changed line */ linenr_T last_line = 0; /* last changed line */ @@ -727,14 +728,24 @@ void ex_retab(exarg_T *eap) save_list = curwin->w_p_list; curwin->w_p_list = 0; /* don't want list mode here */ - new_ts = getdigits_int(&(eap->arg), false, -1); - if (new_ts < 0) { - EMSG(_(e_positive)); + new_ts_str = eap->arg; + if (!tabstop_set(eap->arg, &new_vts_array)) { return; } - if (new_ts == 0) - new_ts = curbuf->b_p_ts; - for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum) { + while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') { + (eap->arg)++; + } + + // This ensures that either new_vts_array and new_ts_str are freshly + // allocated, or new_vts_array points to an existing array and new_ts_str + // is null. + if (new_vts_array == NULL) { + new_vts_array = curbuf->b_p_vts_array; + new_ts_str = NULL; + } else { + new_ts_str = vim_strnsave(new_ts_str, eap->arg - new_ts_str); + } + for (lnum = eap->line1; !got_int && lnum <= eap->line2; lnum++) { ptr = ml_get(lnum); col = 0; vcol = 0; @@ -758,13 +769,12 @@ void ex_retab(exarg_T *eap) len = num_spaces = vcol - start_vcol; num_tabs = 0; if (!curbuf->b_p_et) { - temp = new_ts - (start_vcol % new_ts); - if (num_spaces >= temp) { - num_spaces -= temp; - num_tabs++; - } - num_tabs += num_spaces / new_ts; - num_spaces -= (num_spaces / new_ts) * new_ts; + int t, s; + + tabstop_fromto(start_vcol, vcol, + curbuf->b_p_ts, new_vts_array, &t, &s); + num_tabs = t; + num_spaces = s; } if (curbuf->b_p_et || got_tab || (num_spaces + num_tabs < len)) { @@ -817,15 +827,42 @@ void ex_retab(exarg_T *eap) if (got_int) EMSG(_(e_interr)); - if (curbuf->b_p_ts != new_ts) + // If a single value was given then it can be considered equal to + // either the value of 'tabstop' or the value of 'vartabstop'. + if (tabstop_count(curbuf->b_p_vts_array) == 0 + && tabstop_count(new_vts_array) == 1 + && curbuf->b_p_ts == tabstop_first(new_vts_array)) { + // not changed + } else if (tabstop_count(curbuf->b_p_vts_array) > 0 + && tabstop_eq(curbuf->b_p_vts_array, new_vts_array)) { + // not changed + } else { redraw_curbuf_later(NOT_VALID); + } if (first_line != 0) { changed_lines(first_line, 0, last_line + 1, 0L, true); } curwin->w_p_list = save_list; /* restore 'list' */ - curbuf->b_p_ts = new_ts; + if (new_ts_str != NULL) { // set the new tabstop + // If 'vartabstop' is in use or if the value given to retab has more + // than one tabstop then update 'vartabstop'. + long *old_vts_ary = curbuf->b_p_vts_array; + + if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1) { + set_string_option_direct("vts", -1, new_ts_str, + OPT_FREE | OPT_LOCAL, 0); + curbuf->b_p_vts_array = new_vts_array; + xfree(old_vts_ary); + } else { + // 'vartabstop' wasn't in use and a single value was given to + // retab then update 'tabstop'. + curbuf->b_p_ts = tabstop_first(new_vts_array); + xfree(new_vts_array); + } + xfree(new_ts_str); + } coladvance(curwin->w_curswant); u_clearline(); diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index c4c18c4324..cc0ec71627 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2535,7 +2535,7 @@ void ex_source(exarg_T *eap) static void cmd_source(char_u *fname, exarg_T *eap) { - if (*fname == NUL) { + if (eap != NULL && *fname == NUL) { cmd_source_buffer(eap); } else if (eap != NULL && eap->forceit) { // ":source!": read Normal mode commands @@ -2575,7 +2575,8 @@ static char_u *get_buffer_line(int c, void *cookie, int indent, bool do_concat) return (char_u *)xstrdup((const char *)curr_line); } -static void cmd_source_buffer(exarg_T *eap) +static void cmd_source_buffer(const exarg_T *eap) + FUNC_ATTR_NONNULL_ALL { GetBufferLineCookie cookie = { .curr_lnum = eap->line1, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index bc3d29a03f..3aaf171b2c 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7494,7 +7494,7 @@ static void ex_syncbind(exarg_T *eap) ctrl_o[0] = Ctrl_O; ctrl_o[1] = 0; - ins_typebuf(ctrl_o, REMAP_NONE, 0, TRUE, FALSE); + ins_typebuf(ctrl_o, REMAP_NONE, 0, true, false); } } } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 5979f4d3a0..38385d19b2 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -775,9 +775,9 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) redrawcmd(); } - // redraw the statusline for statuslines that display the current mode - // using the mode() function. - if (!cmd_silent && msg_scrolled == 0) { + // 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(); } @@ -4093,9 +4093,10 @@ ExpandOne ( } if (mode == WILD_CANCEL) { - ss = vim_strsave(orig_save); + ss = vim_strsave(orig_save ? orig_save : (char_u *)""); } else if (mode == WILD_APPLY) { - ss = vim_strsave(findex == -1 ? orig_save : xp->xp_files[findex]); + ss = vim_strsave(findex == -1 ? (orig_save ? orig_save : (char_u *)"") : + xp->xp_files[findex]); } /* free old names */ @@ -5117,6 +5118,18 @@ ExpandFromContext ( if (xp->xp_context == EXPAND_PACKADD) { return ExpandPackAddDir(pat, num_file, file); } + + // When expanding a function name starting with s:, match the <SNR>nr_ + // prefix. + char_u *tofree = NULL; + if (xp->xp_context == EXPAND_USER_FUNC && STRNCMP(pat, "^s:", 3) == 0) { + const size_t len = STRLEN(pat) + 20; + + tofree = xmalloc(len); + snprintf((char *)tofree, len, "^<SNR>\\d\\+_%s", pat + 3); + pat = tofree; + } + if (xp->xp_context == EXPAND_LUA) { ILOG("PAT %s", pat); return nlua_expand_pat(xp, pat, num_file, file); @@ -5195,6 +5208,7 @@ ExpandFromContext ( } vim_regfree(regmatch.regprog); + xfree(tofree); return ret; } diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 63789b3981..09453e100d 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -387,9 +387,10 @@ static int put_view( if (wp->w_alt_fnum) { buf_T *const alt = buflist_findnr(wp->w_alt_fnum); - // Set the alternate file. + // Set the alternate file if the buffer is listed. if ((flagp == &ssop_flags) && alt != NULL && alt->b_fname != NULL && *alt->b_fname != NUL + && alt->b_p_bl && (fputs("balt ", fd) < 0 || ses_fname(fd, alt, flagp, true) == FAIL)) { return FAIL; diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 714bbb5780..65bd809436 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -4280,7 +4280,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) if (fname == NULL || *fname == NUL) { retval = xmalloc(MAXPATHL + extlen + 3); // +3 for PATHSEP, "_" (Win), NUL if (os_dirname((char_u *)retval, MAXPATHL) == FAIL - || (fnamelen = strlen(retval)) == 0) { + || strlen(retval) == 0) { xfree(retval); return NULL; } diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 7e78b9e9d6..d2a7c16186 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -104,8 +104,11 @@ for _,f in ipairs(shallowcopy(functions)) do elseif startswith(f.name, "nvim_tabpage_") then ismethod = true end + f.remote = f.remote_only or not f.lua_only + f.lua = f.lua_only or not f.remote_only + f.eval = (not f.lua_only) and (not f.remote_only) else - f.remote_only = true + f.remote = true f.since = 0 f.deprecated_since = 1 end @@ -127,7 +130,8 @@ for _,f in ipairs(shallowcopy(functions)) do newf.return_type = "Object" end newf.impl_name = f.name - newf.remote_only = true + newf.lua = false + newf.eval = false newf.since = 0 newf.deprecated_since = 1 functions[#functions+1] = newf @@ -192,7 +196,7 @@ end -- the real API. for i = 1, #functions do local fn = functions[i] - if fn.impl_name == nil and not fn.lua_only then + if fn.impl_name == nil and fn.remote then local args = {} output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)') @@ -323,7 +327,7 @@ void msgpack_rpc_init_method_table(void) for i = 1, #functions do local fn = functions[i] - if not fn.lua_only then + if fn.remote then output:write(' msgpack_rpc_add_method_handler('.. '(String) {.data = "'..fn.name..'", '.. '.size = sizeof("'..fn.name..'") - 1}, '.. @@ -492,7 +496,7 @@ local function process_function(fn) end for _, fn in ipairs(functions) do - if not fn.remote_only or fn.name:sub(1, 4) == '_vim' then + if fn.lua or fn.name:sub(1, 4) == '_vim' then process_function(fn) end end diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index d16453530f..679895421a 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -25,7 +25,7 @@ local gperfpipe = io.open(funcsfname .. '.gperf', 'wb') local funcs = require('eval').funcs local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all")) for _,fun in ipairs(metadata) do - if not (fun.remote_only or fun.lua_only) then + if fun.eval then funcs[fun.name] = { args=#fun.parameters, func='api_wrapper', diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 2e2993ed26..9afce6e9d5 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -78,7 +78,7 @@ FileDescriptor *scriptin[NSCRIPT] = { NULL }; * Un-escaping is done by vgetc(). */ -#define MINIMAL_SIZE 20 /* minimal size for b_str */ +#define MINIMAL_SIZE 20 // minimal size for b_str static buffheader_T redobuff = { { NULL, { NUL } }, NULL, 0, 0 }; static buffheader_T old_redobuff = { { NULL, { NUL } }, NULL, 0, 0 }; @@ -90,7 +90,7 @@ static buffheader_T readbuf1 = { { NULL, { NUL } }, NULL, 0, 0 }; // Second read ahead buffer. Used for redo. static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 }; -static int typeahead_char = 0; /* typeahead char that's not flushed */ +static int typeahead_char = 0; // typeahead char that's not flushed /* * when block_redo is TRUE redo buffer will not be changed @@ -116,9 +116,9 @@ static bool maphash_valid = false; /* * List used for abbreviations. */ -static mapblock_T *first_abbr = NULL; /* first entry in abbrlist */ +static mapblock_T *first_abbr = NULL; // first entry in abbrlist -static int KeyNoremap = 0; /* remapping flags */ +static int KeyNoremap = 0; // remapping flags /* * Variables used by vgetorpeek() and flush_buffers() @@ -139,18 +139,18 @@ static int KeyNoremap = 0; /* remapping flags */ * typebuf.tb_noremap[typebuf.tb_off] is the first valid flag. * (typebuf has been put in globals.h, because check_termcode() needs it). */ -#define RM_YES 0 /* tb_noremap: remap */ -#define RM_NONE 1 /* tb_noremap: don't remap */ -#define RM_SCRIPT 2 /* tb_noremap: remap local script mappings */ -#define RM_ABBR 4 /* tb_noremap: don't remap, do abbrev. */ +#define RM_YES 0 // tb_noremap: remap +#define RM_NONE 1 // tb_noremap: don't remap +#define RM_SCRIPT 2 // tb_noremap: remap local script mappings +#define RM_ABBR 4 // tb_noremap: don't remap, do abbrev. /* typebuf.tb_buf has three parts: room in front (for result of mappings), the * middle for typeahead and room for new characters (which needs to be 3 * * MAXMAPLEN) for the Amiga). */ #define TYPELEN_INIT (5 * (MAXMAPLEN + 3)) -static char_u typebuf_init[TYPELEN_INIT]; /* initial typebuf.tb_buf */ -static char_u noremapbuf_init[TYPELEN_INIT]; /* initial typebuf.tb_noremap */ +static char_u typebuf_init[TYPELEN_INIT]; // initial typebuf.tb_buf +static char_u noremapbuf_init[TYPELEN_INIT]; // initial typebuf.tb_noremap static size_t last_recorded_len = 0; // number of last recorded chars @@ -707,8 +707,9 @@ static int read_redo(bool init, bool old_redo) break; } c = *p; - if (c == NUL) /* cannot happen? */ + if (c == NUL) { // cannot happen? break; + } } return c; @@ -744,14 +745,15 @@ int start_redo(long count, bool old_redo) c = read_redo(false, old_redo); - /* copy the buffer name, if present */ + // copy the buffer name, if present if (c == '"') { add_buff(&readbuf2, "\"", 1L); c = read_redo(false, old_redo); - /* if a numbered buffer is used, increment the number */ - if (c >= '1' && c < '9') - ++c; + // if a numbered buffer is used, increment the number + if (c >= '1' && c < '9') { + c++; + } add_char_buff(&readbuf2, c); // the expression register should be re-evaluated @@ -763,7 +765,7 @@ int start_redo(long count, bool old_redo) c = read_redo(false, old_redo); } - if (c == 'v') { /* redo Visual */ + if (c == 'v') { // redo Visual VIsual = curwin->w_cursor; VIsual_active = true; VIsual_select = false; @@ -780,7 +782,7 @@ int start_redo(long count, bool old_redo) add_num_buff(&readbuf2, count); } - /* copy from the redo buffer into the stuff buffer */ + // copy from the redo buffer into the stuff buffer add_char_buff(&readbuf2, c); copy_redo(old_redo); return OK; @@ -838,26 +840,25 @@ static void init_typebuf(void) } } -/* - * insert a string in position 'offset' in the typeahead buffer (for "@r" - * and ":normal" command, vgetorpeek() and check_termcode()) - * - * If noremap is REMAP_YES, new string can be mapped again. - * If noremap is REMAP_NONE, new string cannot be mapped again. - * If noremap is REMAP_SKIP, fist char of new string cannot be mapped again, - * but abbreviations are allowed. - * If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for - * script-local mappings. - * If noremap is > 0, that many characters of the new string cannot be mapped. - * - * If nottyped is TRUE, the string does not return KeyTyped (don't use when - * offset is non-zero!). - * - * If silent is true, cmd_silent is set when the characters are obtained. - * - * return FAIL for failure, OK otherwise - */ -int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent) +// Insert a string in position 'offset' in the typeahead buffer (for "@r" +// and ":normal" command, vgetorpeek() and check_termcode()) +// +// If noremap is REMAP_YES, new string can be mapped again. +// If noremap is REMAP_NONE, new string cannot be mapped again. +// If noremap is REMAP_SKIP, fist char of new string cannot be mapped again, +// but abbreviations are allowed. +// If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for +// script-local mappings. +// If noremap is > 0, that many characters of the new string cannot be mapped. +// +// If nottyped is true, the string does not return KeyTyped (don't use when +// offset is non-zero!). +// +// If silent is true, cmd_silent is set when the characters are obtained. +// +// return FAIL for failure, OK otherwise +int ins_typebuf(char_u *str, int noremap, int offset, + bool nottyped, bool silent) { char_u *s1, *s2; int newlen; @@ -890,8 +891,8 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent) // often. newoff = MAXMAPLEN + 4; newlen = typebuf.tb_len + addlen + newoff + 4 * (MAXMAPLEN + 4); - if (newlen < 0) { /* string is getting too long */ - EMSG(_(e_toocompl)); /* also calls flush_buffers */ + if (newlen < 0) { // string is getting too long + EMSG(_(e_toocompl)); // also calls flush_buffers setcursor(); return FAIL; } @@ -927,13 +928,14 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent) } typebuf.tb_len += addlen; - /* If noremap == REMAP_SCRIPT: do remap script-local mappings. */ - if (noremap == REMAP_SCRIPT) + // If noremap == REMAP_SCRIPT: do remap script-local mappings. + if (noremap == REMAP_SCRIPT) { val = RM_SCRIPT; - else if (noremap == REMAP_SKIP) + } else if (noremap == REMAP_SKIP) { val = RM_ABBR; - else + } else { val = RM_NONE; + } /* * Adjust typebuf.tb_noremap[] for the new characters: @@ -962,8 +964,9 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent) typebuf.tb_silent += addlen; cmd_silent = true; } - if (typebuf.tb_no_abbr_cnt && offset == 0) /* and not used for abbrev.s */ + if (typebuf.tb_no_abbr_cnt && offset == 0) { // and not used for abbrev.s typebuf.tb_no_abbr_cnt += addlen; + } return OK; } @@ -997,9 +1000,8 @@ void ins_char_typebuf(int c) * Or "typebuf.tb_off" may have been changed and we would overwrite characters * that was just added. */ -int -typebuf_changed ( - int tb_change_cnt /* old value of typebuf.tb_change_cnt */ +bool typebuf_changed( + int tb_change_cnt // old value of typebuf.tb_change_cnt ) { return tb_change_cnt != 0 && (typebuf.tb_change_cnt != tb_change_cnt @@ -1031,8 +1033,9 @@ void del_typebuf(int len, int offset) { int i; - if (len == 0) - return; /* nothing to do */ + if (len == 0) { + return; // nothing to do + } typebuf.tb_len -= len; @@ -1068,23 +1071,26 @@ void del_typebuf(int len, int offset) (size_t)(typebuf.tb_len - offset)); } - if (typebuf.tb_maplen > offset) { /* adjust tb_maplen */ - if (typebuf.tb_maplen < offset + len) + if (typebuf.tb_maplen > offset) { // adjust tb_maplen + if (typebuf.tb_maplen < offset + len) { typebuf.tb_maplen = offset; - else + } else { typebuf.tb_maplen -= len; + } } - if (typebuf.tb_silent > offset) { /* adjust tb_silent */ - if (typebuf.tb_silent < offset + len) + if (typebuf.tb_silent > offset) { // adjust tb_silent + if (typebuf.tb_silent < offset + len) { typebuf.tb_silent = offset; - else + } else { typebuf.tb_silent -= len; + } } - if (typebuf.tb_no_abbr_cnt > offset) { /* adjust tb_no_abbr_cnt */ - if (typebuf.tb_no_abbr_cnt < offset + len) + if (typebuf.tb_no_abbr_cnt > offset) { // adjust tb_no_abbr_cnt + if (typebuf.tb_no_abbr_cnt < offset + len) { typebuf.tb_no_abbr_cnt = offset; - else + } else { typebuf.tb_no_abbr_cnt -= len; + } } /* Reset the flag that text received from a client or from feedkeys() @@ -1135,8 +1141,8 @@ static void gotchars(const char_u *chars, size_t len) may_sync_undo(); - /* output "debug mode" message next time in debug mode */ - debug_did_msg = FALSE; + // output "debug mode" message next time in debug mode + debug_did_msg = false; /* Since characters have been typed, consider the following to be in * another mapping. Search string will be kept in history. */ @@ -1253,10 +1259,9 @@ void restore_typeahead(tasave_T *tp) /* * Open a new script file for the ":source!" command. */ -void -openscript ( +void openscript( char_u *name, - int directly /* when TRUE execute directly */ + bool directly // when true execute directly ) { if (curscript + 1 == NSCRIPT) { @@ -1275,9 +1280,10 @@ openscript ( return; } - if (scriptin[curscript] != NULL) /* already reading script */ - ++curscript; - /* use NameBuff for expanded name */ + if (scriptin[curscript] != NULL) { // already reading script + curscript++; + } + // use NameBuff for expanded name expand_env(name, NameBuff, MAXPATHL); int error; if ((scriptin[curscript] = file_open_new(&error, (char *)NameBuff, @@ -1306,11 +1312,11 @@ openscript ( int save_msg_scroll = msg_scroll; State = NORMAL; - msg_scroll = FALSE; /* no msg scrolling in Normal mode */ - restart_edit = 0; /* don't go to Insert mode */ - p_im = FALSE; /* don't use 'insertmode' */ + msg_scroll = false; // no msg scrolling in Normal mode + restart_edit = 0; // don't go to Insert mode + p_im = false; // don't use 'insertmode' clear_oparg(&oa); - finish_op = FALSE; + finish_op = false; oldcurscript = curscript; do { @@ -1626,10 +1632,8 @@ int char_avail(void) return retval != NUL; } -void -vungetc ( /* unget one character (can only be done once!) */ - int c -) +// unget one character (can only be done once!) +void vungetc(int c) { old_char = c; old_mod_mask = mod_mask; @@ -1669,10 +1673,10 @@ static int vgetorpeek(bool advance) mapblock_T *mp2; mapblock_T *mp_match; int mp_match_len = 0; - int timedout = FALSE; /* waited for more than 1 second - for mapping to complete */ - int mapdepth = 0; /* check for recursive mapping */ - int mode_deleted = FALSE; /* set when mode has been deleted */ + bool timedout = false; // waited for more than 1 second + // for mapping to complete + int mapdepth = 0; // check for recursive mapping + bool mode_deleted = false; // set when mode has been deleted int local_State; int mlen; int max_mlen; @@ -1729,8 +1733,9 @@ static int vgetorpeek(bool advance) // needed for CTRL-W CTRL-] to open a fold, for example. KeyStuffed = true; } - if (typebuf.tb_no_abbr_cnt == 0) - typebuf.tb_no_abbr_cnt = 1; /* no abbreviations now */ + if (typebuf.tb_no_abbr_cnt == 0) { + typebuf.tb_no_abbr_cnt = 1; // no abbreviations now + } } else { /* * Loop until we either find a matching mapped key, or we @@ -1744,10 +1749,11 @@ static int vgetorpeek(bool advance) * inside a mapping. But call it each time for typed * characters. */ - if (typebuf.tb_maplen) + if (typebuf.tb_maplen) { line_breakcheck(); - else - os_breakcheck(); /* check for CTRL-C */ + } else { + os_breakcheck(); // check for CTRL-C + } keylen = 0; if (got_int) { // flush all input @@ -1814,11 +1820,11 @@ static int vgetorpeek(bool advance) && get_real_state() != SELECTMODE); nolmaplen = 0; } - /* First try buffer-local mappings. */ + // First try buffer-local mappings. mp = curbuf->b_maphash[MAP_HASH(local_State, c1)]; mp2 = maphash[MAP_HASH(local_State, c1)]; if (mp == NULL) { - /* There are no buffer-local mappings. */ + // There are no buffer-local mappings. mp = mp2; mp2 = NULL; } @@ -1845,8 +1851,8 @@ static int vgetorpeek(bool advance) || typebuf.tb_maplen == 0)) { int nomap = nolmaplen; int c2; - /* find the match length of this mapping */ - for (mlen = 1; mlen < typebuf.tb_len; ++mlen) { + // find the match length of this mapping + for (mlen = 1; mlen < typebuf.tb_len; mlen++) { c2 = typebuf.tb_buf[typebuf.tb_off + mlen]; if (nomap > 0) --nomap; @@ -1901,7 +1907,7 @@ static int vgetorpeek(bool advance) if (keylen > typebuf.tb_len) { if (!timedout && !(mp_match != NULL && mp_match->m_nowait)) { - /* break at a partly match */ + // break at a partly match keylen = KEYLEN_PART_MAP; break; } @@ -1953,13 +1959,14 @@ static int vgetorpeek(bool advance) setcursor(); continue; } - /* Need more chars for partly match. */ - if (mlen == typebuf.tb_len) + // Need more chars for partly match. + if (mlen == typebuf.tb_len) { keylen = KEYLEN_PART_KEY; - else if (max_mlen < mlen) - /* no match, may have to check for termcode at - * next character */ + } else if (max_mlen < mlen) { + // no match, may have to check for termcode at + // next character max_mlen = mlen + 1; + } } if ((mp == NULL || max_mlen >= mp_match_len) @@ -1991,13 +1998,11 @@ static int vgetorpeek(bool advance) } } - /* complete match */ + // complete match if (keylen >= 0 && keylen <= typebuf.tb_len) { int save_m_expr; int save_m_noremap; int save_m_silent; - char_u *save_m_keys; - char_u *save_m_str; // Write chars to script file(s) // Note: :lmap mappings are written *after* being applied. #5658 @@ -2007,7 +2012,7 @@ static int vgetorpeek(bool advance) } cmd_silent = (typebuf.tb_silent > 0); - del_typebuf(keylen, 0); /* remove the mapped keys */ + del_typebuf(keylen, 0); // remove the mapped keys /* * Put the replacement string in front of mapstr. @@ -2032,9 +2037,8 @@ static int vgetorpeek(bool advance) */ if (VIsual_active && VIsual_select && (mp->m_mode & VISUAL)) { - VIsual_select = FALSE; - (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, - 0, TRUE, FALSE); + VIsual_select = false; + (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, 0, true, false); } /* Copy the values from *mp that are used, because @@ -2044,8 +2048,8 @@ static int vgetorpeek(bool advance) save_m_expr = mp->m_expr; save_m_noremap = mp->m_noremap; save_m_silent = mp->m_silent; - save_m_keys = NULL; /* only saved when needed */ - save_m_str = NULL; /* only saved when needed */ + char_u *save_m_keys = NULL; // only saved when needed + char_u *save_m_str = NULL; // only saved when needed /* * Handle ":map <expr>": evaluate the {rhs} as an @@ -2141,14 +2145,14 @@ static int vgetorpeek(bool advance) char_u *ptr; if (mode_displayed) { - unshowmode(TRUE); - mode_deleted = TRUE; + unshowmode(true); + mode_deleted = true; } validate_cursor(); old_wcol = curwin->w_wcol; old_wrow = curwin->w_wrow; - /* move cursor left, if possible */ + // move cursor left, if possible if (curwin->w_cursor.col != 0) { if (curwin->w_wcol > 0) { if (did_ai) { @@ -2170,7 +2174,7 @@ static int vgetorpeek(bool advance) + curwin->w_wcol / curwin->w_width_inner; curwin->w_wcol %= curwin->w_width_inner; curwin->w_wcol += curwin_col_off(); - col = 0; /* no correction needed */ + col = 0; // no correction needed } else { --curwin->w_wcol; col = curwin->w_cursor.col - 1; @@ -2197,8 +2201,9 @@ static int vgetorpeek(bool advance) curwin->w_wcol = old_wcol; curwin->w_wrow = old_wrow; } - if (c < 0) - continue; /* end of input script reached */ + if (c < 0) { + continue; // end of input script reached + } // Allow mapping for just typed characters. When we get here c // is the number of extra bytes and typebuf.tb_len is 1. @@ -2207,20 +2212,20 @@ static int vgetorpeek(bool advance) } typebuf.tb_len += c; - /* buffer full, don't map */ + // buffer full, don't map if (typebuf.tb_len >= typebuf.tb_maplen + MAXMAPLEN) { - timedout = TRUE; + timedout = true; continue; } if (ex_normal_busy > 0) { static int tc = 0; - /* No typeahead left and inside ":normal". Must return - * something to avoid getting stuck. When an incomplete - * mapping is present, behave like it timed out. */ + // No typeahead left and inside ":normal". Must return + // something to avoid getting stuck. When an incomplete + // mapping is present, behave like it timed out. if (typebuf.tb_len > 0) { - timedout = TRUE; + timedout = true; continue; } /* When 'insertmode' is set, ESC just beeps in Insert @@ -2254,7 +2259,7 @@ static int vgetorpeek(bool advance) if (((State & INSERT) != 0 || p_lz) && (State & CMDLINE) == 0 && advance && must_redraw != 0 && !need_wait_return) { update_screen(0); - setcursor(); /* put cursor back where it belongs */ + setcursor(); // put cursor back where it belongs } /* @@ -2267,16 +2272,16 @@ static int vgetorpeek(bool advance) if (typebuf.tb_len > 0 && advance && !exmode_active) { if (((State & (NORMAL | INSERT)) || State == LANGMAP) && State != HITRETURN) { - /* this looks nice when typing a dead character map */ + // this looks nice when typing a dead character map if (State & INSERT && ptr2cells(typebuf.tb_buf + typebuf.tb_off - + typebuf.tb_len - 1) == 1) { - edit_putchar(typebuf.tb_buf[typebuf.tb_off - + typebuf.tb_len - 1], FALSE); - setcursor(); /* put cursor back where it belongs */ + + typebuf.tb_len - 1) == 1) { + edit_putchar(typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1], + false); + setcursor(); // put cursor back where it belongs c1 = 1; } - /* need to use the col and row from above here */ + // need to use the col and row from above here old_wcol = curwin->w_wcol; old_wrow = curwin->w_wrow; curwin->w_wcol = new_wcol; @@ -2332,31 +2337,34 @@ static int vgetorpeek(bool advance) if (i != 0) pop_showcmd(); if (c1 == 1) { - if (State & INSERT) + if (State & INSERT) { edit_unputchar(); - if (State & CMDLINE) + } + if (State & CMDLINE) { unputcmdline(); - else - setcursor(); /* put cursor back where it belongs */ + } else { + setcursor(); // put cursor back where it belongs + } } - if (c < 0) - continue; /* end of input script reached */ - if (c == NUL) { /* no character available */ - if (!advance) + if (c < 0) { + continue; // end of input script reached + } + if (c == NUL) { // no character available + if (!advance) { break; - if (wait_tb_len > 0) { /* timed out */ - timedout = TRUE; + } + if (wait_tb_len > 0) { // timed out + timedout = true; continue; } - } else { /* allow mapping for just typed characters */ - while (typebuf.tb_buf[typebuf.tb_off - + typebuf.tb_len] != NUL) - typebuf.tb_noremap[typebuf.tb_off - + typebuf.tb_len++] = RM_YES; + } else { // allow mapping for just typed characters + while (typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] != NUL) { + typebuf.tb_noremap[typebuf.tb_off + typebuf.tb_len++] = RM_YES; + } } - } /* for (;;) */ - } /* if (!character from stuffbuf) */ + } // for (;;) + } // if (!character from stuffbuf) // if advance is false don't loop on NULs } while (c < 0 || (advance && c == NUL)); @@ -2368,15 +2376,17 @@ static int vgetorpeek(bool advance) */ if (advance && p_smd && msg_silent == 0 && (State & INSERT)) { if (c == ESC && !mode_deleted && !no_mapping && mode_displayed) { - if (typebuf.tb_len && !KeyTyped) - redraw_cmdline = TRUE; /* delete mode later */ - else - unshowmode(FALSE); + if (typebuf.tb_len && !KeyTyped) { + redraw_cmdline = true; // delete mode later + } else { + unshowmode(false); + } } else if (c != ESC && mode_deleted) { - if (typebuf.tb_len && !KeyTyped) - redraw_cmdline = TRUE; /* show mode later */ - else + if (typebuf.tb_len && !KeyTyped) { + redraw_cmdline = true; // show mode later + } else { showmode(); + } } } @@ -2440,8 +2450,8 @@ int inchar( * on char wait, flush swapfile, write error....). */ if (State != HITRETURN) { - did_outofmem_msg = FALSE; /* display out of memory message (again) */ - did_swapwrite_msg = FALSE; /* display swap file write error again */ + did_outofmem_msg = false; // display out of memory message (again) + did_swapwrite_msg = false; // display swap file write error again } // Get a character from a script file if there is one. @@ -3175,7 +3185,7 @@ static void validate_maphash(void) /* * Get the mapping mode from the command name. */ -int get_map_mode(char_u **cmdp, int forceit) +int get_map_mode(char_u **cmdp, bool forceit) { char_u *p; int modec; @@ -3183,30 +3193,31 @@ int get_map_mode(char_u **cmdp, int forceit) p = *cmdp; modec = *p++; - if (modec == 'i') - mode = INSERT; /* :imap */ - else if (modec == 'l') - mode = LANGMAP; /* :lmap */ - else if (modec == 'c') - mode = CMDLINE; /* :cmap */ - else if (modec == 'n' && *p != 'o') /* avoid :noremap */ - mode = NORMAL; /* :nmap */ - else if (modec == 'v') - mode = VISUAL + SELECTMODE; /* :vmap */ - else if (modec == 'x') - mode = VISUAL; /* :xmap */ - else if (modec == 's') - mode = SELECTMODE; /* :smap */ - else if (modec == 'o') - mode = OP_PENDING; /* :omap */ - else if (modec == 't') - mode = TERM_FOCUS; // :tmap - else { - --p; - if (forceit) - mode = INSERT + CMDLINE; /* :map ! */ - else - mode = VISUAL + SELECTMODE + NORMAL + OP_PENDING; /* :map */ + if (modec == 'i') { + mode = INSERT; // :imap + } else if (modec == 'l') { + mode = LANGMAP; // :lmap + } else if (modec == 'c') { + mode = CMDLINE; // :cmap + } else if (modec == 'n' && *p != 'o') { // avoid :noremap + mode = NORMAL; // :nmap + } else if (modec == 'v') { + mode = VISUAL + SELECTMODE; // :vmap + } else if (modec == 'x') { + mode = VISUAL; // :xmap + } else if (modec == 's') { + mode = SELECTMODE; // :smap + } else if (modec == 'o') { + mode = OP_PENDING; // :omap + } else if (modec == 't') { + mode = TERM_FOCUS; // :tmap + } else { + p--; + if (forceit) { + mode = INSERT + CMDLINE; // :map ! + } else { + mode = VISUAL + SELECTMODE + NORMAL + OP_PENDING; // :map + } } *cmdp = p; @@ -3237,12 +3248,11 @@ void map_clear_mode(char_u *cmdp, char_u *arg, int forceit, int abbr) /* * Clear all mappings in "mode". */ -void -map_clear_int ( - buf_T *buf, /* buffer for local mappings */ - int mode, /* mode in which to delete */ - int local, /* TRUE for buffer-local mappings */ - int abbr /* TRUE for abbreviations */ +void map_clear_int( + buf_T *buf, // buffer for local mappings + int mode, // mode in which to delete + bool local, // true for buffer-local mappings + bool abbr // true for abbreviations ) { mapblock_T *mp, **mpp; @@ -3253,12 +3263,14 @@ map_clear_int ( for (hash = 0; hash < 256; ++hash) { if (abbr) { - if (hash > 0) /* there is only one abbrlist */ + if (hash > 0) { // there is only one abbrlist break; - if (local) + } + if (local) { mpp = &buf->b_first_abbr; - else + } else { mpp = &first_abbr; + } } else { if (local) mpp = &buf->b_maphash[hash]; @@ -3286,7 +3298,7 @@ map_clear_int ( mp->m_next = maphash[new_hash]; maphash[new_hash] = mp; } - continue; /* continue with *mpp */ + continue; // continue with *mpp } } mpp = &(mp->m_next); @@ -3341,10 +3353,9 @@ char *map_mode_to_chars(int mode) return (char *)mapmode.ga_data; } -static void -showmap ( +static void showmap( mapblock_T *mp, - int local /* TRUE for buffer-local map */ + bool local // true for buffer-local map ) { size_t len = 1; @@ -3355,8 +3366,9 @@ showmap ( if (msg_didout || msg_silent != 0) { msg_putchar('\n'); - if (got_int) /* 'q' typed at MORE prompt */ + if (got_int) { // 'q' typed at MORE prompt return; + } } { @@ -3372,8 +3384,8 @@ showmap ( // Display the LHS. Get length of what we write. len = (size_t)msg_outtrans_special(mp->m_keys, true, 0); do { - msg_putchar(' '); /* padd with blanks */ - ++len; + msg_putchar(' '); // padd with blanks + len++; } while (len < 12); if (mp->m_noremap == REMAP_NONE) { @@ -3506,21 +3518,20 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr) * Used below when expanding mapping/abbreviation names. */ static int expand_mapmodes = 0; -static int expand_isabbrev = 0; -static int expand_buffer = FALSE; +static bool expand_isabbrev = false; +static bool expand_buffer = false; /* * Work out what to complete when doing command line completion of mapping * or abbreviation names. */ -char_u * -set_context_in_map_cmd ( +char_u *set_context_in_map_cmd( expand_T *xp, char_u *cmd, char_u *arg, - int forceit, /* TRUE if '!' given */ - int isabbrev, /* TRUE if abbreviation */ - int isunmap, /* TRUE if unmap/unabbrev command */ + bool forceit, // true if '!' given + bool isabbrev, // true if abbreviation + bool isunmap, // true if unmap/unabbrev command cmdidx_T cmdidx ) { @@ -3536,10 +3547,10 @@ set_context_in_map_cmd ( } expand_isabbrev = isabbrev; xp->xp_context = EXPAND_MAPPINGS; - expand_buffer = FALSE; + expand_buffer = false; for (;; ) { if (STRNCMP(arg, "<buffer>", 8) == 0) { - expand_buffer = TRUE; + expand_buffer = true; arg = skipwhite(arg + 8); continue; } @@ -3589,7 +3600,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) validate_maphash(); - *num_file = 0; /* return values in case of FAIL */ + *num_file = 0; // return values in case of FAIL *file = NULL; /* @@ -3628,8 +3639,9 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) for (hash = 0; hash < 256; ++hash) { if (expand_isabbrev) { - if (hash > 0) /* only one abbrev list */ - break; /* for (hash) */ + if (hash > 0) { // only one abbrev list + break; // for (hash) + } mp = first_abbr; } else if (expand_buffer) mp = curbuf->b_maphash[hash]; @@ -3648,26 +3660,27 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) } xfree(p); } - } /* for (mp) */ - } /* for (hash) */ + } // for (mp) + } // for (hash) - if (count == 0) /* no match found */ - break; /* for (round) */ + if (count == 0) { // no match found + break; // for (round) + } if (round == 1) { *file = (char_u **)xmalloc((size_t)count * sizeof(char_u *)); } - } /* for (round) */ + } // for (round) if (count > 1) { char_u **ptr1; char_u **ptr2; char_u **ptr3; - /* Sort the matches */ + // Sort the matches sort_strings(*file, count); - /* Remove multiple entries */ + // Remove multiple entries ptr1 = *file; ptr2 = ptr1 + 1; ptr3 = ptr1 + count; @@ -3705,7 +3718,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) bool check_abbr(int c, char_u *ptr, int col, int mincol) { int len; - int scol; /* starting column of the abbr. */ + int scol; // starting column of the abbr. int j; char_u *s; char_u tb[MB_MAXBYTES + 4]; @@ -3756,7 +3769,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) if (scol < mincol) scol = mincol; - if (scol < col) { /* there is a word in front of the cursor */ + if (scol < col) { // there is a word in front of the cursor ptr += scol; len = col - scol; mp = curbuf->b_first_abbr; @@ -3778,7 +3791,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) vim_unescape_csi(q); qlen = (int)STRLEN(q); } - /* find entries with right mode and keys */ + // find entries with right mode and keys match = (mp->m_mode & State) && qlen == len && !STRNCMP(q, ptr, (size_t)len); @@ -3805,7 +3818,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) */ j = 0; if (c != Ctrl_RSB) { - /* special key code, split up */ + // special key code, split up if (IS_SPECIAL(c) || c == K_SPECIAL) { tb[j++] = K_SPECIAL; tb[j++] = (char_u)K_SECOND(c); @@ -3821,17 +3834,17 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) j += utf_char2bytes(c, tb + j); } tb[j] = NUL; - /* insert the last typed char */ - (void)ins_typebuf(tb, 1, 0, TRUE, mp->m_silent); + // insert the last typed char + (void)ins_typebuf(tb, 1, 0, true, mp->m_silent); } if (mp->m_expr) s = eval_map_expr(mp->m_str, c); else s = mp->m_str; if (s != NULL) { - /* insert the to string */ - (void)ins_typebuf(s, mp->m_noremap, 0, TRUE, mp->m_silent); - /* no abbrev. for these chars */ + // insert the to string + (void)ins_typebuf(s, mp->m_noremap, 0, true, mp->m_silent); + // no abbrev. for these chars typebuf.tb_no_abbr_cnt += (int)STRLEN(s) + j + 1; if (mp->m_expr) xfree(s); @@ -3856,7 +3869,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) static char_u * eval_map_expr ( char_u *str, - int c /* NUL or typed character for abbreviation */ + int c // NUL or typed character for abbreviation ) { char_u *res; @@ -3874,11 +3887,11 @@ eval_map_expr ( save_cmd = save_cmdline_alloc(); - /* Forbid changing text or using ":normal" to avoid most of the bad side - * effects. Also restore the cursor position. */ - ++textlock; - ++ex_normal_lock; - set_vim_var_char(c); /* set v:char to the typed character */ + // Forbid changing text or using ":normal" to avoid most of the bad side + // effects. Also restore the cursor position. + textlock++; + ex_normal_lock++; + set_vim_var_char(c); // set v:char to the typed character save_cursor = curwin->w_cursor; save_msg_col = msg_col; save_msg_row = msg_row; @@ -3894,7 +3907,7 @@ eval_map_expr ( if (p == NULL) return NULL; - /* Escape CSI in the result to be able to use the string as typeahead. */ + // Escape CSI in the result to be able to use the string as typeahead. res = vim_strsave_escape_csi(p); xfree(p); @@ -3914,7 +3927,7 @@ char_u *vim_strsave_escape_csi(char_u *p) char_u *d = res; for (char_u *s = p; *s != NUL; ) { if (s[0] == K_SPECIAL && s[1] != NUL && s[2] != NUL) { - /* Copy special key unmodified. */ + // Copy special key unmodified. *d++ = *s++; *d++ = *s++; *d++ = *s++; @@ -4213,9 +4226,10 @@ int put_escstr(FILE *fd, char_u *strstart, int what) c = TO_SPECIAL(str[1], str[2]); str += 2; } - if (IS_SPECIAL(c) || modifiers) { /* special key */ - if (fputs((char *)get_special_key_name(c, modifiers), fd) < 0) + if (IS_SPECIAL(c) || modifiers) { // special key + if (fputs((char *)get_special_key_name(c, modifiers), fd) < 0) { return FAIL; + } continue; } } @@ -4271,35 +4285,36 @@ char_u * check_map ( char_u *keys, int mode, - int exact, /* require exact match */ - int ign_mod, /* ignore preceding modifier */ - int abbr, /* do abbreviations */ - mapblock_T **mp_ptr, /* return: pointer to mapblock or NULL */ - int *local_ptr /* return: buffer-local mapping or NULL */ + int exact, // require exact match + int ign_mod, // ignore preceding modifier + int abbr, // do abbreviations + mapblock_T **mp_ptr, // return: pointer to mapblock or NULL + int *local_ptr // return: buffer-local mapping or NULL ) { - int hash; int len, minlen; mapblock_T *mp; - int local; validate_maphash(); len = (int)STRLEN(keys); - for (local = 1; local >= 0; --local) - /* loop over all hash lists */ - for (hash = 0; hash < 256; ++hash) { + for (int local = 1; local >= 0; local--) { + // loop over all hash lists + for (int hash = 0; hash < 256; hash++) { if (abbr) { - if (hash > 0) /* there is only one list. */ + if (hash > 0) { // there is only one list. break; - if (local) + } + if (local) { mp = curbuf->b_first_abbr; - else + } else { mp = first_abbr; - } else if (local) + } + } else if (local) { mp = curbuf->b_maphash[hash]; - else + } else { mp = maphash[hash]; + } for (; mp != NULL; mp = mp->m_next) { /* skip entries with wrong mode, wrong length and not matching * ones */ @@ -4322,6 +4337,7 @@ check_map ( } } } + } return NULL; } @@ -4336,7 +4352,7 @@ void add_map(char_u *map, int mode) char_u *s; char_u *cpo_save = p_cpo; - p_cpo = (char_u *)""; /* Allow <> notation */ + p_cpo = (char_u *)""; // Allow <> notation s = vim_strsave(map); (void)do_map(0, s, mode, FALSE); xfree(s); @@ -4384,7 +4400,7 @@ static char_u * translate_mapping ( } if (IS_SPECIAL(c) || modifiers) { // special key ga_concat(&ga, get_special_key_name(c, modifiers)); - continue; /* for (str) */ + continue; // for (str) } } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 7b8e809de7..ffe0357bd8 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -256,7 +256,7 @@ EXTERN linenr_T sourcing_lnum INIT(= 0); // line number of the source file EXTERN int ex_nesting_level INIT(= 0); // nesting level EXTERN int debug_break_level INIT(= -1); // break below this level -EXTERN int debug_did_msg INIT(= false); // did "debug mode" message +EXTERN bool debug_did_msg INIT(= false); // did "debug mode" message EXTERN int debug_tick INIT(= 0); // breakpoint change count EXTERN int debug_backtrace_level INIT(= 0); // breakpoint backtrace level diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index e14aae73d8..3b34af46e4 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -7,7 +7,7 @@ #include "nvim/types.h" -#define MAX_MCO 6 // maximum value for 'maxcombine' +#define MAX_MCO 6 // fixed value for 'maxcombine' // The characters and attributes drawn on grids. typedef char_u schar_T[(MAX_MCO+1) * 4 + 1]; @@ -35,7 +35,8 @@ typedef int sattr_T; /// line_wraps[] is an array of boolean flags indicating if the screen line /// wraps to the next line. It can only be true if a window occupies the entire /// screen width. -typedef struct { +typedef struct ScreenGrid ScreenGrid; +struct ScreenGrid { handle_T handle; schar_T *chars; @@ -58,10 +59,13 @@ typedef struct { // external UI. bool throttled; - // offsets for the grid relative to the global screen. Used by screen.c - // for windows that don't have w_grid->chars etc allocated + // TODO(bfredl): maybe physical grids and "views" (i e drawing + // specifications) should be two separate types? + // offsets for the grid relative to another grid. Used for grids + // that are views into another, actually allocated grid 'target' int row_offset; int col_offset; + ScreenGrid *target; // whether the compositor should blend the grid with the background grid bool blending; @@ -76,6 +80,12 @@ typedef struct { int comp_row; int comp_col; + // Requested width and height of the grid upon resize. Used by + // `ui_compositor` to correctly determine which regions need to + // be redrawn. + int comp_width; + int comp_height; + // z-index of the grid. Grids with higher index is draw on top. // default_grid.comp_index is always zero. size_t comp_index; @@ -83,9 +93,10 @@ typedef struct { // compositor should momentarily ignore the grid. Used internally when // moving around grids etc. bool comp_disabled; -} ScreenGrid; +}; #define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \ - false, 0, 0, false, true, 0, 0, 0, false } + false, 0, 0, NULL, false, true, \ + 0, 0, 0, 0, 0, false } #endif // NVIM_GRID_DEFS_H diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index 4ec949759c..abba5425e7 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -890,8 +890,11 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T * Appropriately expand any tabs to spaces. */ if (line[col] == TAB || tab_spaces != 0) { - if (tab_spaces == 0) - tab_spaces = (int)(curbuf->b_p_ts - (print_pos % curbuf->b_p_ts)); + if (tab_spaces == 0) { + tab_spaces = tabstop_padding(print_pos, + curbuf->b_p_ts, + curbuf->b_p_vts_array); + } while (tab_spaces > 0) { need_break = mch_print_text_out((char_u *)" ", 1); diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index b01cdde236..329c448cf0 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -151,7 +151,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 +175,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; @@ -341,6 +341,17 @@ void update_window_hl(win_T *wp, bool invalid) } wp->w_hl_attrs[hlf] = attr; } + + 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); + } + wp->w_float_config.border_attr[i] = attr; + } + } } /// Gets HL_UNDERLINE highlight. @@ -517,6 +528,10 @@ static HlAttrs get_colors_force(int attr) /// @return the resulting attributes. int hl_blend_attrs(int back_attr, int front_attr, bool *through) { + if (front_attr < 0 || back_attr < 0) { + return -1; + } + HlAttrs fattrs = get_colors_force(front_attr); int ratio = fattrs.hl_blend; if (ratio <= 0) { diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 2bda094d8e..ed4aefb577 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -101,6 +101,7 @@ typedef enum { , HLF_MSGSEP // message separator line , HLF_NFLOAT // Floating window , HLF_MSG // Message area + , HLF_BORDER // Floating window border , HLF_COUNT // MUST be the last one } hlf_T; @@ -155,6 +156,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_MSGSEP] = "MsgSeparator", [HLF_NFLOAT] = "NormalFloat", [HLF_MSG] = "MsgArea", + [HLF_BORDER] = "FloatBorder", }); diff --git a/src/nvim/indent.c b/src/nvim/indent.c index fae971b3b3..8fa61515ef 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -34,14 +34,20 @@ // Count the size (in window cells) of the indent in the current line. int get_indent(void) { - return get_indent_str(get_cursor_line_ptr(), (int)curbuf->b_p_ts, false); + return get_indent_str_vtab(get_cursor_line_ptr(), + curbuf->b_p_ts, + curbuf->b_p_vts_array, + false); } // Count the size (in window cells) of the indent in line "lnum". int get_indent_lnum(linenr_T lnum) { - return get_indent_str(ml_get(lnum), (int)curbuf->b_p_ts, false); + return get_indent_str_vtab(ml_get(lnum), + curbuf->b_p_ts, + curbuf->b_p_vts_array, + false); } @@ -49,7 +55,10 @@ int get_indent_lnum(linenr_T lnum) // "buf". int get_indent_buf(buf_T *buf, linenr_T lnum) { - return get_indent_str(ml_get_buf(buf, lnum, false), (int)buf->b_p_ts, false); + return get_indent_str_vtab(ml_get_buf(buf, lnum, false), + curbuf->b_p_ts, + buf->b_p_vts_array, + false); } @@ -82,6 +91,30 @@ int get_indent_str(const char_u *ptr, int ts, int list) return count; } +// Count the size (in window cells) of the indent in line "ptr", using +// variable tabstops. +// if "list" is true, count only screen size for tabs. +int get_indent_str_vtab(const char_u *ptr, long ts, long *vts, bool list) +{ + int count = 0; + + for (; *ptr; ptr++) { + if (*ptr == TAB) { // count a tab for what it is worth + if (!list || curwin->w_p_lcs_chars.tab1) { + count += tabstop_padding(count, ts, vts); + } else { + // In list mode, when tab is not set, count screen char width + // for Tab, displays: ^I + count += ptr2cells(ptr); + } + } else if (*ptr == ' ') { + count++; // count a space for one + } else { + break; + } + } + return count; +} // Set the indent of the current line. // Leaves the cursor on the first non-blank in the line. @@ -104,6 +137,7 @@ int set_indent(int size, int flags) int line_len; int doit = false; int ind_done = 0; // Measured in spaces. + int ind_col = 0; int tab_pad; int retval = false; @@ -130,7 +164,9 @@ int set_indent(int size, int flags) // Count as many characters as we can use. while (todo > 0 && ascii_iswhite(*p)) { if (*p == TAB) { - tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); // Stop if this tab will overshoot the target. if (todo < tab_pad) { @@ -147,35 +183,41 @@ int set_indent(int size, int flags) p++; } + // These diverge from this point. + ind_col = ind_done; // Set initial number of whitespace chars to copy if we are // preserving indent but expandtab is set. if (curbuf->b_p_et) { orig_char_len = ind_len; } - // Fill to next tabstop with a tab, if possible. - tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); - + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); if ((todo >= tab_pad) && (orig_char_len == -1)) { doit = true; todo -= tab_pad; ind_len++; // ind_done += tab_pad; + ind_col += tab_pad; } } // Count tabs required for indent. - while (todo >= (int)curbuf->b_p_ts) { + for (;;) { + tab_pad = tabstop_padding(ind_col, curbuf->b_p_ts, curbuf->b_p_vts_array); + if (todo < tab_pad) { + break; + } if (*p != TAB) { doit = true; } else { p++; } - todo -= (int)curbuf->b_p_ts; + todo -= tab_pad; ind_len++; - - // ind_done += (int)curbuf->b_p_ts; + ind_col += tab_pad; } } @@ -255,7 +297,9 @@ int set_indent(int size, int flags) while (todo > 0 && ascii_iswhite(*p)) { if (*p == TAB) { - tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); // Stop if this tab will overshoot the target. if (todo < tab_pad) { @@ -272,18 +316,28 @@ int set_indent(int size, int flags) } // Fill to next tabstop with a tab, if possible. - tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); if (todo >= tab_pad) { *s++ = TAB; todo -= tab_pad; + ind_done += tab_pad; } p = skipwhite(p); } - while (todo >= (int)curbuf->b_p_ts) { + for (;;) { + tab_pad = tabstop_padding(ind_done, + curbuf->b_p_ts, + curbuf->b_p_vts_array); + if (todo < tab_pad) { + break; + } *s++ = TAB; - todo -= (int)curbuf->b_p_ts; + todo -= tab_pad; + ind_done += tab_pad; } } @@ -375,11 +429,9 @@ int get_number_indent(linenr_T lnum) return (int)col; } -/* - * Return appropriate space number for breakindent, taking influencing - * parameters into account. Window must be specified, since it is not - * necessarily always the current one. - */ +// Return appropriate space number for breakindent, taking influencing +// parameters into account. Window must be specified, since it is not +// necessarily always the current one. int get_breakindent_win(win_T *wp, const char_u *line) FUNC_ATTR_NONNULL_ALL { @@ -387,6 +439,7 @@ int get_breakindent_win(win_T *wp, const char_u *line) static long prev_ts = 0; // Cached tabstop value. static const char_u *prev_line = NULL; // cached pointer to line. static varnumber_T prev_tick = 0; // Changedtick of cached value. + static long *prev_vts = NULL; // Cached vartabs values. int bri = 0; // window width minus window margin space, i.e. what rests for text const int eff_wwidth = wp->w_width_inner @@ -396,11 +449,16 @@ int get_breakindent_win(win_T *wp, const char_u *line) // used cached indent, unless pointer or 'tabstop' changed if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts - || prev_tick != buf_get_changedtick(wp->w_buffer)) { + || prev_tick != buf_get_changedtick(wp->w_buffer) + || prev_vts != wp->w_buffer->b_p_vts_array) { prev_line = line; prev_ts = wp->w_buffer->b_p_ts; prev_tick = buf_get_changedtick(wp->w_buffer); - prev_indent = get_indent_str(line, (int)wp->w_buffer->b_p_ts, wp->w_p_list); + prev_vts = wp->w_buffer->b_p_vts_array; + prev_indent = get_indent_str_vtab(line, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array, + wp->w_p_list); } bri = prev_indent + wp->w_briopt_shift; diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 9298e57411..771bf923b2 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -33,15 +33,12 @@ typedef struct { * Search starts at w_cursor.lnum and goes backwards. * Return NULL when not inside a comment. */ -static pos_T *ind_find_start_comment(void) -{ /* XXX */ +static pos_T *ind_find_start_comment(void) // XXX +{ return find_start_comment(curbuf->b_ind_maxcomment); } -pos_T * -find_start_comment ( /* XXX */ - int ind_maxcomment -) +pos_T *find_start_comment(int ind_maxcomment) // XXX { pos_T *pos; char_u *line; @@ -109,8 +106,8 @@ static pos_T *ind_find_start_CORS(linenr_T *is_raw) * Search starts at w_cursor.lnum and goes backwards. * Return NULL when not inside a raw string. */ -static pos_T *find_start_rawstring(int ind_maxcomment) -{ /* XXX */ +static pos_T *find_start_rawstring(int ind_maxcomment) // XXX +{ pos_T *pos; char_u *line; char_u *p; @@ -152,31 +149,35 @@ static char_u *skip_string(char_u *p) /* * We loop, because strings may be concatenated: "date""time". */ - for (;; ++p) { - if (p[0] == '\'') { /* 'c' or '\n' or '\000' */ - if (!p[1]) /* ' at end of line */ + for (;; p++) { + if (p[0] == '\'') { // 'c' or '\n' or '\000' + if (!p[1]) { // ' at end of line break; + } i = 2; - if (p[1] == '\\') { /* '\n' or '\000' */ - ++i; - while (ascii_isdigit(p[i - 1])) /* '\000' */ - ++i; + if (p[1] == '\\') { // '\n' or '\000' + i++; + while (ascii_isdigit(p[i - 1])) { // '\000' + i++; + } } - if (p[i] == '\'') { /* check for trailing ' */ + if (p[i] == '\'') { // check for trailing ' p += i; continue; } - } else if (p[0] == '"') { /* start of string */ - for (++p; p[0]; ++p) { - if (p[0] == '\\' && p[1] != NUL) - ++p; - else if (p[0] == '"') /* end of string */ + } else if (p[0] == '"') { // start of string + for (++p; p[0]; p++) { + if (p[0] == '\\' && p[1] != NUL) { + p++; + } else if (p[0] == '"') { // end of string break; + } + } + if (p[0] == '"') { + continue; // continue for another string } - if (p[0] == '"') - continue; /* continue for another string */ } else if (p[0] == 'R' && p[1] == '"') { - /* Raw string: R"[delim](...)[delim]" */ + // Raw string: R"[delim](...)[delim]" char_u *delim = p + 2; char_u *paren = vim_strchr(delim, '('); @@ -190,14 +191,16 @@ static char_u *skip_string(char_u *p) p += delim_len + 1; break; } - if (p[0] == '"') - continue; /* continue for another string */ + if (p[0] == '"') { + continue; // continue for another string + } } } - break; /* no string found */ + break; // no string found + } + if (!*p) { + p--; // backup from NUL } - if (!*p) - --p; /* backup from NUL */ return p; } @@ -255,20 +258,22 @@ static char_u *cin_skipcomment(char_u *s) s += STRLEN(s); break; } - if (*s != '/') + if (*s != '/') { break; - ++s; - if (*s == '/') { /* slash-slash comment continues till eol */ + } + s++; + if (*s == '/') { // slash-slash comment continues till eol s += STRLEN(s); break; } if (*s != '*') break; - for (++s; *s; ++s) /* skip slash-star comment */ + for (++s; *s; s++) { // skip slash-star comment if (s[0] == '*' && s[1] == '/') { s += 2; break; } + } } return s; } @@ -285,7 +290,7 @@ static int cin_nocode(char_u *s) /* * Check previous lines for a "//" line comment, skipping over blank lines. */ -static pos_T *find_line_comment(void) /* XXX */ +static pos_T *find_line_comment(void) // XXX { static pos_T pos; char_u *line; @@ -335,39 +340,38 @@ static bool cin_has_js_key(char_u *text) /// Checks if string matches "label:"; move to character after ':' if true. /// "*s" must point to the start of the label, if there is one. -static int cin_islabel_skip(char_u **s) +static bool cin_islabel_skip(char_u **s) + FUNC_ATTR_NONNULL_ALL { - if (!vim_isIDc(**s)) /* need at least one ID character */ - return FALSE; + if (!vim_isIDc(**s)) { // need at least one ID character + return false; + } while (vim_isIDc(**s)) (*s)++; *s = cin_skipcomment(*s); - /* "::" is not a label, it's C++ */ + // "::" is not a label, it's C++ return **s == ':' && *++*s != ':'; } -/* - * Recognize a label: "label:". - * Note: curwin->w_cursor must be where we are looking for the label. - */ -int cin_islabel(void) -{ /* XXX */ +// Recognize a label: "label:". +// Note: curwin->w_cursor must be where we are looking for the label. +bool cin_islabel(void) // XXX +{ char_u *s = cin_skipcomment(get_cursor_line_ptr()); - /* - * Exclude "default" from labels, since it should be indented - * like a switch label. Same for C++ scope declarations. - */ - if (cin_isdefault(s)) - return FALSE; - if (cin_isscopedecl(s)) - return FALSE; - + // Exclude "default" from labels, since it should be indented + // like a switch label. Same for C++ scope declarations. + if (cin_isdefault(s)) { + return false; + } + if (cin_isscopedecl(s)) { + return false; + } if (!cin_islabel_skip(&s)) { - return FALSE; + return false; } /* @@ -392,21 +396,24 @@ int cin_islabel(void) } line = get_cursor_line_ptr(); - if (cin_ispreproc(line)) /* ignore #defines, #if, etc. */ + if (cin_ispreproc(line)) { // ignore #defines, #if, etc. continue; - if (*(line = cin_skipcomment(line)) == NUL) + } + if (*(line = cin_skipcomment(line)) == NUL) { continue; + } curwin->w_cursor = cursor_save; if (cin_isterminated(line, TRUE, FALSE) || cin_isscopedecl(line) - || cin_iscase(line, TRUE) - || (cin_islabel_skip(&line) && cin_nocode(line))) - return TRUE; - return FALSE; + || cin_iscase(line, true) + || (cin_islabel_skip(&line) && cin_nocode(line))) { + return true; + } + return false; } curwin->w_cursor = cursor_save; - return TRUE; /* label at start of file??? */ + return true; // label at start of file??? } /* @@ -451,10 +458,9 @@ static int cin_isinit(void) /* * Recognize a switch label: "case .*:" or "default:". */ -int -cin_iscase ( +bool cin_iscase( char_u *s, - int strict /* Allow relaxed check of case statement for JS */ + bool strict // Allow relaxed check of case statement for JS ) { s = cin_skipcomment(s); @@ -465,29 +471,32 @@ cin_iscase ( break; } if (*s == ':') { - if (s[1] == ':') /* skip over "::" for C++ */ - ++s; - else - return TRUE; + if (s[1] == ':') { // skip over "::" for C++ + s++; + } else { + return true; + } } - if (*s == '\'' && s[1] && s[2] == '\'') - s += 2; /* skip over ':' */ - else if (*s == '/' && (s[1] == '*' || s[1] == '/')) - return FALSE; /* stop at comment */ - else if (*s == '"') { - /* JS etc. */ - if (strict) - return FALSE; /* stop at string */ - else - return TRUE; + if (*s == '\'' && s[1] && s[2] == '\'') { + s += 2; // skip over ':' + } else if (*s == '/' && (s[1] == '*' || s[1] == '/')) { + return false; // stop at comment + } else if (*s == '"') { + // JS etc. + if (strict) { + return false; // stop at string + } else { + return true; + } } } - return FALSE; + return false; } - if (cin_isdefault(s)) - return TRUE; - return FALSE; + if (cin_isdefault(s)) { + return true; + } + return false; } /* @@ -503,23 +512,24 @@ static int cin_isdefault(char_u *s) /* * Recognize a "public/private/protected" scope declaration label. */ -int cin_isscopedecl(char_u *s) +bool cin_isscopedecl(char_u *s) { int i; s = cin_skipcomment(s); - if (STRNCMP(s, "public", 6) == 0) + if (STRNCMP(s, "public", 6) == 0) { i = 6; - else if (STRNCMP(s, "protected", 9) == 0) + } else if (STRNCMP(s, "protected", 9) == 0) { i = 9; - else if (STRNCMP(s, "private", 7) == 0) + } else if (STRNCMP(s, "private", 7) == 0) { i = 7; - else - return FALSE; + } else { + return false; + } return *(s = cin_skipcomment(s + i)) == ':' && s[1] != ':'; } -/* Maximum number of lines to search back for a "namespace" line. */ +// Maximum number of lines to search back for a "namespace" line. #define FIND_NAMESPACE_LIM 20 // Recognize a "namespace" scope declaration. @@ -569,12 +579,14 @@ static char_u *after_label(char_u *l) { for (; *l; ++l) { if (*l == ':') { - if (l[1] == ':') /* skip over "::" for C++ */ - ++l; - else if (!cin_iscase(l + 1, FALSE)) + if (l[1] == ':') { // skip over "::" for C++ + l++; + } else if (!cin_iscase(l + 1, false)) { break; - } else if (*l == '\'' && l[1] && l[2] == '\'') - l += 2; /* skip over 'x' */ + } + } else if (*l == '\'' && l[1] && l[2] == '\'') { + l += 2; // skip over 'x' + } } if (*l == NUL) return NULL; @@ -588,10 +600,7 @@ static char_u *after_label(char_u *l) * Get indent of line "lnum", skipping a label. * Return 0 if there is nothing after the label. */ -static int -get_indent_nolabel ( /* XXX */ - linenr_T lnum -) +static int get_indent_nolabel(linenr_T lnum) // XXX { char_u *l; pos_T fp; @@ -624,12 +633,13 @@ static int skip_label(linenr_T lnum, char_u **pp) cursor_save = curwin->w_cursor; curwin->w_cursor.lnum = lnum; l = get_cursor_line_ptr(); - /* XXX */ - if (cin_iscase(l, FALSE) || cin_isscopedecl(l) || cin_islabel()) { + // XXX + if (cin_iscase(l, false) || cin_isscopedecl(l) || cin_islabel()) { amount = get_indent_nolabel(lnum); l = after_label(get_cursor_line_ptr()); - if (l == NULL) /* just in case */ + if (l == NULL) { // just in case l = get_cursor_line_ptr(); + } } else { amount = get_indent(); l = get_cursor_line_ptr(); @@ -710,10 +720,11 @@ static int cin_get_equal_amount(linenr_T lnum) line = s = ml_get(lnum); while (*s != NUL && vim_strchr((char_u *)"=;{}\"'", *s) == NULL) { - if (cin_iscomment(s)) /* ignore comments */ + if (cin_iscomment(s)) { // ignore comments s = cin_skipcomment(s); - else - ++s; + } else { + s++; + } } if (*s != '=') return 0; @@ -722,8 +733,9 @@ static int cin_get_equal_amount(linenr_T lnum) if (cin_nocode(s)) return 0; - if (*s == '"') /* nice alignment for continued strings */ - ++s; + if (*s == '"') { // nice alignment for continued strings + s++; + } fp.lnum = lnum; fp.col = (colnr_T)(s - line); @@ -806,8 +818,8 @@ static int cin_islinecomment(char_u *p) static char_u cin_isterminated ( char_u *s, - int incl_open, /* include '{' at the end as terminator */ - int incl_comma /* recognize a trailing comma */ + int incl_open, // include '{' at the end as terminator + int incl_comma // recognize a trailing comma ) { char_u found_start = 0; @@ -823,7 +835,7 @@ cin_isterminated ( is_else = cin_iselse(s); while (*s) { - /* skip over comments, "" strings and 'c'haracters */ + // skip over comments, "" strings and 'c'haracters s = skip_string(cin_skipcomment(s)); if (*s == '}' && n_open > 0) --n_open; @@ -942,12 +954,12 @@ static int cin_isfuncdecl(char_u **sp, linenr_T first_lnum, linenr_T min_lnum) s = skipwhite(s); if (!just_started && (!comma && *s != ',' && *s != ')')) break; - just_started = FALSE; - } else if (cin_iscomment(s)) /* ignore comments */ + just_started = false; + } else if (cin_iscomment(s)) { // ignore comments s = cin_skipcomment(s); - else { - ++s; - just_started = FALSE; + } else { + s++; + just_started = false; } } @@ -965,8 +977,9 @@ static int cin_isif(char_u *p) static int cin_iselse(char_u *p) { - if (*p == '}') /* accept "} else" */ + if (*p == '}') { // accept "} else" p = cin_skipcomment(p + 1); + } return STRNCMP(p, "else", 4) == 0 && !vim_isIDc(p[4]); } @@ -980,27 +993,24 @@ static int cin_isdo(char_u *p) * We only accept a "while (condition) ;", with only white space between the * ')' and ';'. The condition may be spread over several lines. */ -static int -cin_iswhileofdo ( /* XXX */ - char_u *p, - linenr_T lnum -) +static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX { pos_T cursor_save; pos_T *trypos; int retval = FALSE; p = cin_skipcomment(p); - if (*p == '}') /* accept "} while (cond);" */ + if (*p == '}') { // accept "} while (cond);" p = cin_skipcomment(p + 1); + } if (cin_starts_with(p, "while")) { cursor_save = curwin->w_cursor; curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = 0; p = get_cursor_line_ptr(); - while (*p && *p != 'w') { /* skip any '}', until the 'w' of the "while" */ - ++p; - ++curwin->w_cursor.col; + while (*p && *p != 'w') { // skip any '}', until the 'w' of the "while" + p++; + curwin->w_cursor.col++; } if ((trypos = findmatchlimit(NULL, 0, 0, curbuf->b_ind_maxparen)) != NULL @@ -1067,8 +1077,9 @@ static int cin_iswhileofdo_end(int terminated) pos_T *trypos; int i; - if (terminated != ';') /* there must be a ';' at the end */ - return FALSE; + if (terminated != ';') { // there must be a ';' at the end + return false; + } p = line = get_cursor_line_ptr(); while (*p != NUL) { @@ -1083,15 +1094,16 @@ static int cin_iswhileofdo_end(int terminated) trypos = find_match_paren(curbuf->b_ind_maxparen); if (trypos != NULL) { s = cin_skipcomment(ml_get(trypos->lnum)); - if (*s == '}') /* accept "} while (cond);" */ + if (*s == '}') { // accept "} while (cond);" s = cin_skipcomment(s + 1); + } if (cin_starts_with(s, "while")) { curwin->w_cursor.lnum = trypos->lnum; return TRUE; } } - /* Searching may have made "line" invalid, get it again. */ + // Searching may have made "line" invalid, get it again. line = get_cursor_line_ptr(); p = line + i; } @@ -1134,8 +1146,9 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) { pos->col = 0; s = skipwhite(line); - if (*s == '#') /* skip #define FOO x ? (x) : x */ - return FALSE; + if (*s == '#') { // skip #define FOO x ? (x) : x + return false; + } s = cin_skipcomment(s); if (*s == NUL) return FALSE; @@ -1230,23 +1243,23 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) { class_or_struct = FALSE; lookfor_ctor_init = TRUE; } else if (s[0] == '?') { - /* Avoid seeing '() :' after '?' as constructor init. */ - return FALSE; + // Avoid seeing '() :' after '?' as constructor init. + return false; } else if (!vim_isIDc(s[0])) { - /* if it is not an identifier, we are wrong */ + // if it is not an identifier, we are wrong class_or_struct = false; lookfor_ctor_init = false; } else if (pos->col == 0) { - /* it can't be a constructor-initialization any more */ - lookfor_ctor_init = FALSE; + // it can't be a constructor-initialization any more + lookfor_ctor_init = false; - /* the first statement starts here: lineup with this one... */ + // the first statement starts here: lineup with this one... if (cpp_base_class) { pos->col = (colnr_T)(s - line); } } - /* When the line ends in a comma don't align with it. */ + // When the line ends in a comma don't align with it. if (lnum == curwin->w_cursor.lnum && *s == ',' && cin_nocode(s + 1)) { pos->col = 0; } @@ -1271,10 +1284,12 @@ static int get_baseclass_amount(int col) if (col == 0) { amount = get_indent(); if (find_last_paren(get_cursor_line_ptr(), '(', ')') - && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) - amount = get_indent_lnum(trypos->lnum); /* XXX */ - if (!cin_ends_in(get_cursor_line_ptr(), (char_u *)",", NULL)) + && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) { + amount = get_indent_lnum(trypos->lnum); // XXX + } + if (!cin_ends_in(get_cursor_line_ptr(), (char_u *)",", NULL)) { amount += curbuf->b_ind_cpp_baseclass; + } } else { curwin->w_cursor.col = col; getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL); @@ -1389,12 +1404,12 @@ static int cin_skip2pos(pos_T *trypos) * Return NULL if no match found. * Ignore a '{' that is in a comment, makes indenting the next three lines * work. */ -/* foo() */ -/* { */ -/* } */ +// foo() +// { +// } -static pos_T *find_start_brace(void) -{ /* XXX */ +static pos_T *find_start_brace(void) // XXX +{ pos_T cursor_save; pos_T *trypos; pos_T *pos; @@ -1402,11 +1417,11 @@ static pos_T *find_start_brace(void) cursor_save = curwin->w_cursor; while ((trypos = findmatchlimit(NULL, '{', FM_BLOCKSTOP, 0)) != NULL) { - pos_copy = *trypos; /* copy pos_T, next findmatch will change it */ + pos_copy = *trypos; // copy pos_T, next findmatch will change it trypos = &pos_copy; curwin->w_cursor = *trypos; pos = NULL; - /* ignore the { if it's in a // or / * * / comment */ + // ignore the { if it's in a // or / * * / comment if ((colnr_T)cin_skip2pos(trypos) == trypos->col && (pos = ind_find_start_CORS(NULL)) == NULL) { // XXX break; @@ -1449,7 +1464,7 @@ retry: } else { pos_T *trypos_wk; - pos_copy = *trypos; /* copy trypos, findmatch will change it */ + pos_copy = *trypos; // copy trypos, findmatch will change it trypos = &pos_copy; curwin->w_cursor = *trypos; if ((trypos_wk = ind_find_start_CORS(NULL)) != NULL) { // XXX @@ -1515,17 +1530,17 @@ static int find_last_paren(char_u *l, int start, int end) int retval = FALSE; int open_count = 0; - curwin->w_cursor.col = 0; /* default is start of line */ + curwin->w_cursor.col = 0; // default is start of line for (i = 0; l[i] != NUL; i++) { - i = (int)(cin_skipcomment(l + i) - l); /* ignore parens in comments */ - i = (int)(skip_string(l + i) - l); /* ignore parens in quotes */ - if (l[i] == start) - ++open_count; - else if (l[i] == end) { - if (open_count > 0) - --open_count; - else { + i = (int)(cin_skipcomment(l + i) - l); // ignore parens in comments + i = (int)(skip_string(l + i) - l); // ignore parens in quotes + if (l[i] == start) { + open_count++; + } else if (l[i] == end) { + if (open_count > 0) { + open_count--; + } else { curwin->w_cursor.col = i; retval = TRUE; } @@ -1561,7 +1576,7 @@ void parse_cino(buf_T *buf) * an opening brace. */ buf->b_ind_no_brace = 0; - /* Column where the first { of a function should be located }. */ + // Column where the first { of a function should be located }. buf->b_ind_first_open = 0; /* Spaces from the prevailing indent a leftmost open brace should be @@ -1581,26 +1596,26 @@ void parse_cino(buf_T *buf) * otherwise the jump label will be put to column 1. */ buf->b_ind_jump_label = -1; - /* Spaces from the switch() indent a "case xx" label should be located. */ + // Spaces from the switch() indent a "case xx" label should be located. buf->b_ind_case = sw; - /* Spaces from the "case xx:" code after a switch() should be located. */ + // Spaces from the "case xx:" code after a switch() should be located. buf->b_ind_case_code = sw; - /* Lineup break at end of case in switch() with case label. */ + // Lineup break at end of case in switch() with case label. buf->b_ind_case_break = 0; /* Spaces from the class declaration indent a scope declaration label * should be located. */ buf->b_ind_scopedecl = sw; - /* Spaces from the scope declaration label code should be located. */ + // Spaces from the scope declaration label code should be located. buf->b_ind_scopedecl_code = sw; - /* Amount K&R-style parameters should be indented. */ + // Amount K&R-style parameters should be indented. buf->b_ind_param = sw; - /* Amount a function type spec should be indented. */ + // Amount a function type spec should be indented. buf->b_ind_func_type = sw; /* Amount a cpp base class declaration or constructor initialization @@ -1611,7 +1626,7 @@ void parse_cino(buf_T *buf) * should be located. */ buf->b_ind_continuation = sw; - /* Spaces from the indent of the line with an unclosed parentheses. */ + // Spaces from the indent of the line with an unclosed parentheses. buf->b_ind_unclosed = sw * 2; /* Spaces from the indent of the line with an unclosed parentheses, which @@ -1635,35 +1650,35 @@ void parse_cino(buf_T *buf) * opening parentheses. */ buf->b_ind_matching_paren = 0; - /* Indent a closing parentheses under the previous line. */ + // Indent a closing parentheses under the previous line. buf->b_ind_paren_prev = 0; - /* Extra indent for comments. */ + // Extra indent for comments. buf->b_ind_comment = 0; - /* Spaces from the comment opener when there is nothing after it. */ + // Spaces from the comment opener when there is nothing after it. buf->b_ind_in_comment = 3; /* Boolean: if non-zero, use b_ind_in_comment even if there is something * after the comment opener. */ buf->b_ind_in_comment2 = 0; - /* Max lines to search for an open paren. */ + // Max lines to search for an open paren. buf->b_ind_maxparen = 20; - /* Max lines to search for an open comment. */ + // Max lines to search for an open comment. buf->b_ind_maxcomment = 70; - /* Handle braces for java code. */ + // Handle braces for java code. buf->b_ind_java = 0; - /* Not to confuse JS object properties with labels. */ + // Not to confuse JS object properties with labels. buf->b_ind_js = 0; - /* Handle blocked cases correctly. */ + // Handle blocked cases correctly. buf->b_ind_keep_case_label = 0; - /* Handle C++ namespace. */ + // Handle C++ namespace. buf->b_ind_cpp_namespace = 0; /* Handle continuation lines containing conditions of if(), for() and @@ -1777,9 +1792,9 @@ int get_c_indent(void) pos_T our_paren_pos; char_u *start; int start_brace; -#define BRACE_IN_COL0 1 /* '{' is in column 0 */ -#define BRACE_AT_START 2 /* '{' is at start of line */ -#define BRACE_AT_END 3 /* '{' is at end of line */ +#define BRACE_IN_COL0 1 // '{' is in column 0 +#define BRACE_AT_START 2 // '{' is at start of line +#define BRACE_AT_END 3 // '{' is at end of line linenr_T ourscope; char_u *l; char_u *look; @@ -1802,24 +1817,24 @@ int get_c_indent(void) int whilelevel; linenr_T lnum; int n; - int iscase; int lookfor_break; - int lookfor_cpp_namespace = FALSE; - int cont_amount = 0; /* amount for continuation line */ + bool lookfor_cpp_namespace = false; + int cont_amount = 0; // amount for continuation line int original_line_islabel; int added_to_amount = 0; linenr_T raw_string_start = 0; cpp_baseclass_cache_T cache_cpp_baseclass = { false, { MAXLNUM, 0 } }; - /* make a copy, value is changed below */ + // make a copy, value is changed below int ind_continuation = curbuf->b_ind_continuation; - /* remember where the cursor was when we started */ + // remember where the cursor was when we started cur_curpos = curwin->w_cursor; - /* if we are at line 1 zero indent is fine, right? */ - if (cur_curpos.lnum == 1) + // if we are at line 1 zero indent is fine, right? + if (cur_curpos.lnum == 1) { return 0; + } /* Get a copy of the current contents of the line. * This is required, because only the most recent line obtained with @@ -1840,11 +1855,11 @@ int get_c_indent(void) theline = skipwhite(linecopy); - /* move the cursor to the start of the line */ + // move the cursor to the start of the line curwin->w_cursor.col = 0; - original_line_islabel = cin_islabel(); /* XXX */ + original_line_islabel = cin_islabel(); // XXX /* * If we are inside a raw string don't change the indent. @@ -1852,7 +1867,7 @@ int get_c_indent(void) */ comment_pos = ind_find_start_comment(); if (comment_pos != NULL) { - /* findmatchlimit() static pos is overwritten, make a copy */ + // findmatchlimit() static pos is overwritten, make a copy tryposCopy = *comment_pos; comment_pos = &tryposCopy; } @@ -1887,8 +1902,8 @@ int get_c_indent(void) * previous line, lineup with that one. */ if (cin_islinecomment(theline) - && (trypos = find_line_comment()) != NULL) { /* XXX */ - /* find how indented the line beginning the comment is */ + && (trypos = find_line_comment()) != NULL) { // XXX + // find how indented the line beginning the comment is getvcol(curwin, trypos, &col, NULL, NULL); amount = col; goto theend; @@ -1897,18 +1912,18 @@ int get_c_indent(void) * If we're inside a comment and not looking at the start of the * comment, try using the 'comments' option. */ - if (!cin_iscomment(theline) && comment_pos != NULL) { /* XXX */ + if (!cin_iscomment(theline) && comment_pos != NULL) { // XXX int lead_start_len = 2; int lead_middle_len = 1; - char_u lead_start[COM_MAX_LEN]; /* start-comment string */ - char_u lead_middle[COM_MAX_LEN]; /* middle-comment string */ - char_u lead_end[COM_MAX_LEN]; /* end-comment string */ + char_u lead_start[COM_MAX_LEN]; // start-comment string + char_u lead_middle[COM_MAX_LEN]; // middle-comment string + char_u lead_end[COM_MAX_LEN]; // end-comment string char_u *p; int start_align = 0; int start_off = 0; int done = FALSE; - /* find how indented the line beginning the comment is */ + // find how indented the line beginning the comment is getvcol(curwin, comment_pos, &col, NULL, NULL); amount = col; *lead_start = NUL; @@ -1981,13 +1996,13 @@ int get_c_indent(void) if (STRNCMP(theline, lead_middle, lead_middle_len) != 0 && STRNCMP(theline, lead_end, STRLEN(lead_end)) == 0) { amount = get_indent_lnum(curwin->w_cursor.lnum - 1); - /* XXX */ - if (off != 0) + // XXX + if (off != 0) { amount += off; - else if (align == COM_RIGHT) - amount += vim_strsize(lead_start) - - vim_strsize(lead_middle); - done = TRUE; + } else if (align == COM_RIGHT) { + amount += vim_strsize(lead_start) - vim_strsize(lead_middle); + } + done = true; break; } } @@ -2010,18 +2025,20 @@ int get_c_indent(void) * otherwise, add the amount specified by "c" in 'cino' */ amount = -1; - for (lnum = cur_curpos.lnum - 1; lnum > comment_pos->lnum; --lnum) { - if (linewhite(lnum)) /* skip blank lines */ + for (lnum = cur_curpos.lnum - 1; lnum > comment_pos->lnum; lnum--) { + if (linewhite(lnum)) { // skip blank lines continue; - amount = get_indent_lnum(lnum); /* XXX */ + } + amount = get_indent_lnum(lnum); // XXX break; } - if (amount == -1) { /* use the comment opener */ + if (amount == -1) { // use the comment opener if (!curbuf->b_ind_in_comment2) { - start = ml_get(comment_pos->lnum); - look = start + comment_pos->col + 2; /* skip / and * */ - if (*look != NUL) /* if something after it */ - comment_pos->col = (colnr_T)(skipwhite(look) - start); + start = ml_get(comment_pos->lnum); + look = start + comment_pos->col + 2; // skip / and * + if (*look != NUL) { // if something after it + comment_pos->col = (colnr_T)(skipwhite(look) - start); + } } getvcol(curwin, comment_pos, &col, NULL, NULL); amount = col; @@ -2038,9 +2055,8 @@ int get_c_indent(void) amount = get_indent_lnum(trypos->lnum); goto theend; } - /* - * Are we inside parentheses or braces? - */ /* XXX */ + // Are we inside parentheses or braces? + // XXX if (((trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL && curbuf->b_ind_java == 0) || (tryposBrace = find_start_brace()) != NULL @@ -2063,8 +2079,8 @@ int get_c_indent(void) * a previous non-empty line that matches the same paren. */ if (theline[0] == ')' && curbuf->b_ind_paren_prev) { - /* Line up with the start of the matching paren line. */ - amount = get_indent_lnum(curwin->w_cursor.lnum - 1); /* XXX */ + // Line up with the start of the matching paren line. + amount = get_indent_lnum(curwin->w_cursor.lnum - 1); // XXX } else { amount = -1; for (lnum = cur_curpos.lnum - 1; lnum > our_paren_pos.lnum; --lnum) { @@ -2083,12 +2099,12 @@ int get_c_indent(void) continue; } - /* XXX */ + // XXX if ((trypos = find_match_paren( corr_ind_maxparen(&cur_curpos))) != NULL && trypos->lnum == our_paren_pos.lnum && trypos->col == our_paren_pos.col) { - amount = get_indent_lnum(lnum); /* XXX */ + amount = get_indent_lnum(lnum); // XXX if (theline[0] == ')') { if (our_paren_pos.lnum != lnum @@ -2200,10 +2216,11 @@ int get_c_indent(void) col = our_paren_pos.col + 1; while (ascii_iswhite(l[col])) col++; - if (l[col] != NUL) /* In case of trailing space */ + if (l[col] != NUL) { // In case of trailing space our_paren_pos.col = col; - else + } else { our_paren_pos.col++; + } } } @@ -2219,7 +2236,7 @@ int get_c_indent(void) } if (theline[0] == ')' && curbuf->b_ind_matching_paren) { - /* Line up with the start of the matching paren line. */ + // Line up with the start of the matching paren line. } else if ((curbuf->b_ind_unclosed == 0 && is_if_for_while == 0) || (!curbuf->b_ind_unclosed_noignore && *look == '(' && ignore_paren_col == 0)) { @@ -2271,9 +2288,10 @@ int get_c_indent(void) } } - /* add extra indent for a comment */ - if (cin_iscomment(theline)) + // add extra indent for a comment + if (cin_iscomment(theline)) { amount += curbuf->b_ind_comment; + } } else { // We are inside braces, there is a { before this line at the position // stored in tryposBrace. @@ -2317,7 +2335,7 @@ int get_c_indent(void) // ldfd) { // } if ((curbuf->b_ind_js || curbuf->b_ind_keep_case_label) - && cin_iscase(skipwhite(get_cursor_line_ptr()), FALSE)) { + && cin_iscase(skipwhite(get_cursor_line_ptr()), false)) { amount = get_indent(); } else if (curbuf->b_ind_js) { amount = get_indent_lnum(lnum); @@ -2348,14 +2366,15 @@ int get_c_indent(void) * to match it with. */ lookfor = LOOKFOR_INITIAL; - if (cin_iselse(theline)) + if (cin_iselse(theline)) { lookfor = LOOKFOR_IF; - else if (cin_iswhileofdo(theline, cur_curpos.lnum)) /* XXX */ + } else if (cin_iswhileofdo(theline, cur_curpos.lnum)) { // XXX lookfor = LOOKFOR_DO; + } if (lookfor != LOOKFOR_INITIAL) { curwin->w_cursor.lnum = cur_curpos.lnum; if (find_match(lookfor, ourscope) == OK) { - amount = get_indent(); /* XXX */ + amount = get_indent(); // XXX goto theend; } } @@ -2390,7 +2409,7 @@ int get_c_indent(void) amount += curbuf->b_ind_cpp_extern_c; } } else { - /* Compensate for adding b_ind_open_extra later. */ + // Compensate for adding b_ind_open_extra later. amount -= curbuf->b_ind_open_extra; if (amount < 0) amount = 0; @@ -2399,19 +2418,20 @@ int get_c_indent(void) lookfor_break = FALSE; - if (cin_iscase(theline, FALSE)) { /* it's a switch() label */ - lookfor = LOOKFOR_CASE; /* find a previous switch() label */ + if (cin_iscase(theline, false)) { // it's a switch() label + lookfor = LOOKFOR_CASE; // find a previous switch() label amount += curbuf->b_ind_case; - } else if (cin_isscopedecl(theline)) { /* private:, ... */ - lookfor = LOOKFOR_SCOPEDECL; /* class decl is this block */ + } else if (cin_isscopedecl(theline)) { // private:, ... + lookfor = LOOKFOR_SCOPEDECL; // class decl is this block amount += curbuf->b_ind_scopedecl; } else { - if (curbuf->b_ind_case_break && cin_isbreak(theline)) - /* break; ... */ - lookfor_break = TRUE; + if (curbuf->b_ind_case_break && cin_isbreak(theline)) { + // break; ... + lookfor_break = true; + } lookfor = LOOKFOR_INITIAL; - /* b_ind_level from start of block */ + // b_ind_level from start of block amount += curbuf->b_ind_level; } scope_amount = amount; @@ -2503,16 +2523,17 @@ int get_c_indent(void) if (terminated != ';' && cin_isinit()) break; - /* nothing useful found */ - if (terminated == 0 || terminated == '{') + // nothing useful found + if (terminated == 0 || terminated == '{') { continue; + } } if (terminated != ';') { - /* Skip parens and braces. Position the cursor - * over the rightmost paren, so that matching it - * will take us back to the start of the line. - */ /* XXX */ + // Skip parens and braces. Position the cursor + // over the rightmost paren, so that matching it + // will take us back to the start of the line. + // XXX trypos = NULL; if (find_last_paren(l, '(', ')')) trypos = find_match_paren( @@ -2582,7 +2603,7 @@ int get_c_indent(void) continue; } - /* Finally the actual check for "namespace". */ + // Finally the actual check for "namespace". if (cin_is_cpp_namespace(l)) { amount += curbuf->b_ind_cpp_namespace - added_to_amount; @@ -2614,7 +2635,7 @@ int get_c_indent(void) * If this is a switch() label, may line up relative to that. * If this is a C++ scope declaration, do the same. */ - iscase = cin_iscase(l, FALSE); + bool iscase = cin_iscase(l, false); if (iscase || cin_isscopedecl(l)) { /* we are only looking for cpp base class * declaration/initialization any longer */ @@ -2640,27 +2661,24 @@ int get_c_indent(void) break; } - /* - * case xx: <- line up with this case - * x = 333; - * case yy: - */ - if ( (iscase && lookfor == LOOKFOR_CASE) - || (iscase && lookfor_break) - || (!iscase && lookfor == LOOKFOR_SCOPEDECL)) { - /* - * Check that this case label is not for another - * switch() - */ /* XXX */ + // case xx: <- line up with this case + // x = 333; + // case yy: + if ((iscase && lookfor == LOOKFOR_CASE) + || (iscase && lookfor_break) + || (!iscase && lookfor == LOOKFOR_SCOPEDECL)) { + // Check that this case label is not for another + // switch() + // XXX if ((trypos = find_start_brace()) == NULL || trypos->lnum == ourscope) { - amount = get_indent(); /* XXX */ + amount = get_indent(); // XXX break; } continue; } - n = get_indent_nolabel(curwin->w_cursor.lnum); /* XXX */ + n = get_indent_nolabel(curwin->w_cursor.lnum); // XXX /* * case xx: if (cond) <- line up with this if @@ -2708,7 +2726,7 @@ int get_c_indent(void) * case xx: * -> y = 1; */ - scope_amount = get_indent() + (iscase /* XXX */ + scope_amount = get_indent() + (iscase // XXX ? curbuf->b_ind_case_code : curbuf->b_ind_scopedecl_code); lookfor = curbuf->b_ind_case_break @@ -2750,11 +2768,10 @@ int get_c_indent(void) continue; } - /* - * Are we at the start of a cpp base class declaration or - * constructor initialization? - */ /* XXX */ - n = FALSE; + // Are we at the start of a cpp base class declaration or + // constructor initialization? + // XXX + n = 0; if (lookfor != LOOKFOR_TERM && curbuf->b_ind_cpp_baseclass > 0) { n = cin_is_cpp_baseclass(&cache_cpp_baseclass); l = get_cursor_line_ptr(); @@ -2766,13 +2783,14 @@ int get_c_indent(void) else amount += ind_continuation; } else if (theline[0] == '{') { - /* Need to find start of the declaration. */ + // Need to find start of the declaration. lookfor = LOOKFOR_UNTERM; ind_continuation = 0; continue; - } else - /* XXX */ + } else { + // XXX amount = get_baseclass_amount(cache_cpp_baseclass.lpos.col); + } break; } else if (lookfor == LOOKFOR_CPP_BASECLASS) { /* only look, whether there is a cpp base class @@ -2871,8 +2889,8 @@ int get_c_indent(void) */ curwin->w_cursor = *trypos; l = get_cursor_line_ptr(); - if (cin_iscase(l, FALSE) || cin_isscopedecl(l)) { - ++curwin->w_cursor.lnum; + if (cin_iscase(l, false) || cin_isscopedecl(l)) { + curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; continue; } @@ -3025,9 +3043,10 @@ int get_c_indent(void) * -> here; */ if (lookfor == LOOKFOR_UNTERM) { - /* When line ends in a comma add extra indent */ - if (terminated == ',') + // When line ends in a comma add extra indent + if (terminated == ',') { amount += ind_continuation; + } break; } @@ -3144,9 +3163,10 @@ int get_c_indent(void) if (whilelevel == 0) { lookfor = LOOKFOR_TERM; - amount = get_indent(); /* XXX */ - if (theline[0] == '{') + amount = get_indent(); // XXX + if (theline[0] == '{') { amount += curbuf->b_ind_open_extra; + } } ++whilelevel; } @@ -3174,8 +3194,8 @@ int get_c_indent(void) if (whilelevel > 0) { l = cin_skipcomment(get_cursor_line_ptr()); if (cin_isdo(l)) { - amount = get_indent(); /* XXX */ - --whilelevel; + amount = get_indent(); // XXX + whilelevel--; continue; } } @@ -3240,8 +3260,8 @@ term_again: */ curwin->w_cursor = *trypos; l = get_cursor_line_ptr(); - if (cin_iscase(l, FALSE) || cin_isscopedecl(l)) { - ++curwin->w_cursor.lnum; + if (cin_iscase(l, false) || cin_isscopedecl(l)) { + curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; continue; } @@ -3256,8 +3276,7 @@ term_again: * stat; * } */ - iscase = (curbuf->b_ind_keep_case_label - && cin_iscase(l, FALSE)); + iscase = curbuf->b_ind_keep_case_label && cin_iscase(l, false); /* * Get indent and pointer to text for current line, @@ -3267,7 +3286,7 @@ term_again: if (theline[0] == '{') amount += curbuf->b_ind_open_extra; - /* See remark above: "Only add b_ind_open_extra.." */ + // See remark above: "Only add b_ind_open_extra.." l = skipwhite(l); if (*l == '{') amount -= curbuf->b_ind_open_extra; @@ -3297,11 +3316,11 @@ term_again: * that block. */ l = get_cursor_line_ptr(); - if (find_last_paren(l, '{', '}') /* XXX */ + if (find_last_paren(l, '{', '}') // XXX && (trypos = find_start_brace()) != NULL) { curwin->w_cursor = *trypos; - /* if not "else {" check for terminated again */ - /* but skip block for "} else {" */ + // if not "else {" check for terminated again + // but skip block for "} else {" l = cin_skipcomment(get_cursor_line_ptr()); if (*l == '}' || !cin_iselse(l)) goto term_again; @@ -3314,13 +3333,14 @@ term_again: } } - /* add extra indent for a comment */ - if (cin_iscomment(theline)) + // add extra indent for a comment + if (cin_iscomment(theline)) { amount += curbuf->b_ind_comment; - - /* subtract extra left-shift for jump labels */ - if (curbuf->b_ind_jump_label > 0 && original_line_islabel) + } + // subtract extra left-shift for jump labels + if (curbuf->b_ind_jump_label > 0 && original_line_islabel) { amount -= curbuf->b_ind_jump_label; + } goto theend; } @@ -3360,7 +3380,7 @@ term_again: goto theend; } - /* search backwards until we find something we recognize */ + // search backwards until we find something we recognize amount = 0; curwin->w_cursor = cur_curpos; while (curwin->w_cursor.lnum > 1) { @@ -3386,7 +3406,7 @@ term_again: l = get_cursor_line_ptr(); } if (n) { - /* XXX */ + // XXX amount = get_baseclass_amount(cache_cpp_baseclass.lpos.col); break; } @@ -3415,11 +3435,11 @@ term_again: */ if (cin_ends_in(l, (char_u *)",", NULL) || (*l != NUL && (n = l[STRLEN(l) - 1]) == '\\')) { - /* take us back to opening paren */ + // take us back to opening paren if (find_last_paren(l, '(', ')') - && (trypos = find_match_paren( - curbuf->b_ind_maxparen)) != NULL) + && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) { curwin->w_cursor = *trypos; + } /* For a line ending in ',' that is a continuation line go * back to the first line with a backslash: @@ -3435,7 +3455,7 @@ term_again: curwin->w_cursor.col = 0; } - amount = get_indent(); /* XXX */ + amount = get_indent(); // XXX if (amount == 0) amount = cin_first_id_amount(); @@ -3448,8 +3468,9 @@ term_again: * If the line looks like a function declaration, and we're * not in a comment, put it the left margin. */ - if (cin_isfuncdecl(NULL, cur_curpos.lnum, 0)) /* XXX */ + if (cin_isfuncdecl(NULL, cur_curpos.lnum, 0)) { // XXX break; + } l = get_cursor_line_ptr(); /* @@ -3535,13 +3556,14 @@ term_again: if ((trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) curwin->w_cursor = *trypos; - amount = get_indent(); /* XXX */ + amount = get_indent(); // XXX break; } - /* add extra indent for a comment */ - if (cin_iscomment(theline)) + // add extra indent for a comment + if (cin_iscomment(theline)) { amount += curbuf->b_ind_comment; + } /* add extra indent if the previous line ended in a backslash: * "asdfasdf\ @@ -3565,7 +3587,7 @@ theend: amount = 0; laterend: - /* put the cursor back where it belongs */ + // put the cursor back where it belongs curwin->w_cursor = cur_curpos; xfree(linecopy); @@ -3598,7 +3620,7 @@ static int find_match(int lookfor, linenr_T ourscope) look = cin_skipcomment(get_cursor_line_ptr()); if (!cin_iselse(look) && !cin_isif(look) - && !cin_isdo(look) /* XXX */ + && !cin_isdo(look) // XXX && !cin_iswhileofdo(look, curwin->w_cursor.lnum)) { continue; } @@ -3607,9 +3629,10 @@ static int find_match(int lookfor, linenr_T ourscope) * if we've gone outside the braces entirely, * we must be out of scope... */ - theirscope = find_start_brace(); /* XXX */ - if (theirscope == NULL) + theirscope = find_start_brace(); // XXX + if (theirscope == NULL) { break; + } /* * and if the brace enclosing this is further @@ -3649,7 +3672,7 @@ static int find_match(int lookfor, linenr_T ourscope) continue; } - /* If it's an "if" decrement elselevel */ + // If it's an "if" decrement elselevel look = cin_skipcomment(get_cursor_line_ptr()); if (cin_isif(look)) { elselevel--; @@ -3661,9 +3684,10 @@ static int find_match(int lookfor, linenr_T ourscope) whilelevel = 0; } - /* If it's a "do" decrement whilelevel */ - if (cin_isdo(look)) + // If it's a "do" decrement whilelevel + if (cin_isdo(look)) { whilelevel--; + } /* * if we've used up all the elses, then diff --git a/src/nvim/lib/queue.h b/src/nvim/lib/queue.h index ab9270081e..452998a5a4 100644 --- a/src/nvim/lib/queue.h +++ b/src/nvim/lib/queue.h @@ -33,11 +33,17 @@ typedef struct _queue { #define QUEUE_DATA(ptr, type, field) \ ((type *)((char *)(ptr) - offsetof(type, field))) -// Important note: mutating the list while QUEUE_FOREACH is -// iterating over its elements results in undefined behavior. -#define QUEUE_FOREACH(q, h) \ - for ( /* NOLINT(readability/braces) */ \ - (q) = (h)->next; (q) != (h); (q) = (q)->next) +// Important note: the node currently being processed can be safely deleted. +// otherwise, mutating the list while QUEUE_FOREACH is iterating over its +// elements results in undefined behavior. +#define QUEUE_FOREACH(q, h, code) \ + (q) = (h)->next; \ + while((q) != (h)) { \ + QUEUE *next = q->next; \ + code \ + (q) = next; \ + } + // ffi.cdef is unable to swallow `bool` in place of `int` here. static inline int QUEUE_EMPTY(const QUEUE *const q) 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 310b194c8c..9b8e9ff8cc 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" @@ -18,6 +19,7 @@ #include "nvim/vim.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 +34,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 +63,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 +552,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 +566,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 +583,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 +895,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 +940,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 +952,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 +1065,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; } @@ -1272,6 +1302,12 @@ bool nlua_exec_file(const char *path) return true; } +int tslua_get_language_version(lua_State *L) +{ + lua_pushnumber(L, TREE_SITTER_LANGUAGE_VERSION); + return 1; +} + static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { tslua_init(lstate); @@ -1288,8 +1324,11 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, tslua_inspect_lang); lua_setfield(lstate, -2, "_ts_inspect_language"); - lua_pushcfunction(lstate, ts_lua_parse_query); + lua_pushcfunction(lstate, tslua_parse_query); lua_setfield(lstate, -2, "_ts_parse_query"); + + lua_pushcfunction(lstate, tslua_get_language_version); + lua_setfield(lstate, -2, "_ts_get_language_version"); } int nlua_expand_pat(expand_T *xp, @@ -1499,6 +1538,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, @@ -1508,53 +1549,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; } @@ -1564,55 +1592,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 a640b97d3b..38848b0266 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -171,7 +171,7 @@ int tslua_add_language(lua_State *L) TSLanguage *lang = lang_parser(); if (lang == NULL) { - return luaL_error(L, "Failed to load parser: internal error"); + return luaL_error(L, "Failed to load parser %s: internal error", path); } uint32_t lang_version = ts_language_version(lang); @@ -179,7 +179,8 @@ int tslua_add_language(lua_State *L) || lang_version > TREE_SITTER_LANGUAGE_VERSION) { return luaL_error( L, - "ABI version mismatch : supported between %d and %d, found %d", + "ABI version mismatch for %s: supported between %d and %d, found %d", + path, TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION, TREE_SITTER_LANGUAGE_VERSION, lang_version); } @@ -221,8 +222,9 @@ int tslua_inspect_lang(lua_State *L) lua_setfield(L, -2, "symbols"); // [retval] size_t nfields = (size_t)ts_language_field_count(lang); - lua_createtable(L, nfields-1, 1); // [retval, fields] - for (size_t i = 0; i < nfields; i++) { + lua_createtable(L, nfields, 1); // [retval, fields] + // Field IDs go from 1 to nfields inclusive (extra index 0 maps to NULL) + for (size_t i = 1; i <= nfields; i++) { lua_pushstring(L, ts_language_field_name_for_id(lang, i)); lua_rawseti(L, -2, i); // [retval, fields] } @@ -1108,7 +1110,7 @@ static int querycursor_gc(lua_State *L) // Query methods -int ts_lua_parse_query(lua_State *L) +int tslua_parse_query(lua_State *L) { if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) { return luaL_error(L, "string expected"); diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index ec4f4cbc21..73e3ba53a5 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -571,11 +571,12 @@ size_t mb_string2cells(const char_u *str) /// @param size maximum length of string. It will terminate on earlier NUL. /// @return The number of cells occupied by string `str` size_t mb_string2cells_len(const char_u *str, size_t size) + FUNC_ATTR_NONNULL_ARG(1) { size_t clen = 0; for (const char_u *p = str; *p != NUL && p < str+size; - p += utf_ptr2len_len(p, size+(p-str))) { + p += utfc_ptr2len_len(p, size+(p-str))) { clen += utf_ptr2cells(p); } 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/message.c b/src/nvim/message.c index ba7a667a60..7c98d3c6b5 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -178,6 +178,7 @@ void msg_grid_validate(void) msg_grid.throttled = false; // don't throttle in 'cmdheight' area msg_scrolled_at_flush = msg_scrolled; msg_grid.focusable = false; + msg_grid_adj.target = &msg_grid; if (!msg_scrolled) { msg_grid_set_pos(Rows - p_ch, false); } @@ -188,6 +189,7 @@ void msg_grid_validate(void) ui_call_grid_destroy(msg_grid.handle); msg_grid.throttled = false; msg_grid_adj.row_offset = 0; + msg_grid_adj.target = &default_grid; redraw_cmdline = true; } else if (msg_grid.chars && !msg_scrolled && msg_grid_pos != Rows - p_ch) { msg_grid_set_pos(Rows - p_ch, false); @@ -867,18 +869,18 @@ char_u *msg_trunc_attr(char_u *s, int force, int attr) */ char_u *msg_may_trunc(int force, char_u *s) { - int n; int room; room = (int)(Rows - cmdline_row - 1) * Columns + sc_col - 1; if ((force || (shortmess(SHM_TRUNC) && !exmode_active)) - && (n = (int)STRLEN(s) - room) > 0) { + && (int)STRLEN(s) - room > 0) { int size = vim_strsize(s); // There may be room anyway when there are multibyte chars. if (size <= room) { return s; } + int n; for (n = 0; size >= room; ) { size -= utf_ptr2cells(s + n); n += utfc_ptr2len(s + n); @@ -1703,6 +1705,7 @@ void msg_prt_line(char_u *s, int list) char_u *p_extra = NULL; // init to make SASC shut up int n; int attr = 0; + char_u *lead = NULL; char_u *trail = NULL; int l; @@ -1710,11 +1713,24 @@ void msg_prt_line(char_u *s, int list) list = true; } - // find start of trailing whitespace - if (list && curwin->w_p_lcs_chars.trail) { - trail = s + STRLEN(s); - while (trail > s && ascii_iswhite(trail[-1])) { - trail--; + if (list) { + // find start of trailing whitespace + if (curwin->w_p_lcs_chars.trail) { + trail = s + STRLEN(s); + while (trail > s && ascii_iswhite(trail[-1])) { + trail--; + } + } + // find end of leading whitespace + if (curwin->w_p_lcs_chars.lead) { + lead = s; + while (ascii_iswhite(lead[0])) { + lead++; + } + // in a line full of spaces all of them are treated as trailing + if (*lead == NUL) { + lead = NULL; + } } } @@ -1756,7 +1772,9 @@ void msg_prt_line(char_u *s, int list) c = *s++; if (c == TAB && (!list || curwin->w_p_lcs_chars.tab1)) { // tab amount depends on current column - n_extra = curbuf->b_p_ts - col % curbuf->b_p_ts - 1; + n_extra = tabstop_padding(col, + curbuf->b_p_ts, + curbuf->b_p_vts_array) - 1; if (!list) { c = ' '; c_extra = ' '; @@ -1789,6 +1807,9 @@ void msg_prt_line(char_u *s, int list) /* Use special coloring to be able to distinguish <hex> from * the same in plain text. */ attr = HL_ATTR(HLF_8); + } else if (c == ' ' && lead != NULL && s <= lead) { + c = curwin->w_p_lcs_chars.lead; + attr = HL_ATTR(HLF_8); } else if (c == ' ' && trail != NULL && s > trail) { c = curwin->w_p_lcs_chars.trail; attr = HL_ATTR(HLF_8); diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index f28658aa29..4c0339e5f4 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -470,21 +470,21 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) *gridp = DEFAULT_GRID_HANDLE; } else if (*gridp > 1) { win_T *wp = get_win_by_grid_handle(*gridp); - if (wp && wp->w_grid.chars + if (wp && wp->w_grid_alloc.chars && !(wp->w_floating && !wp->w_float_config.focusable)) { - *rowp = MIN(*rowp, wp->w_grid.Rows-1); - *colp = MIN(*colp, wp->w_grid.Columns-1); + *rowp = MIN(*rowp-wp->w_grid.row_offset, wp->w_grid.Rows-1); + *colp = MIN(*colp-wp->w_grid.col_offset, wp->w_grid.Columns-1); return wp; } } else if (*gridp == 0) { ScreenGrid *grid = ui_comp_mouse_focus(*rowp, *colp); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (&wp->w_grid != grid) { + if (&wp->w_grid_alloc != grid) { continue; } *gridp = grid->handle; - *rowp -= grid->comp_row; - *colp -= grid->comp_col; + *rowp -= grid->comp_row+wp->w_grid.row_offset; + *colp -= grid->comp_col+wp->w_grid.col_offset; return wp; } @@ -717,23 +717,22 @@ static int mouse_adjust_click(win_T *wp, int row, int col) // Check clicked cell is foldcolumn int mouse_check_fold(void) { - int grid = mouse_grid; - int row = mouse_row; - int col = mouse_col; + int click_grid = mouse_grid; + int click_row = mouse_row; + int click_col = mouse_col; int mouse_char = ' '; win_T *wp; - wp = mouse_find_win(&grid, &row, &col); + wp = mouse_find_win(&click_grid, &click_row, &click_col); if (wp && mouse_row >= 0 && mouse_row < Rows && mouse_col >= 0 && mouse_col <= Columns) { int multigrid = ui_has(kUIMultigrid); - ScreenGrid *gp = multigrid ? &wp->w_grid : &default_grid; + ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid; int fdc = win_fdccol_count(wp); - - row = multigrid && mouse_grid == 0 ? row : mouse_row; - col = multigrid && mouse_grid == 0 ? col : mouse_col; + int row = multigrid && mouse_grid == 0 ? click_row : mouse_row; + int col = multigrid && mouse_grid == 0 ? click_col : mouse_col; // Remember the character under the mouse, might be one of foldclose or // foldopen fillchars in the fold column. @@ -743,13 +742,13 @@ int mouse_check_fold(void) } // Check for position outside of the fold column. - if (wp->w_p_rl ? col < wp->w_width_inner - fdc : - col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { + if (wp->w_p_rl ? click_col < wp->w_width_inner - fdc : + click_col >= fdc + (cmdwin_type == 0 ? 0 : 1)) { mouse_char = ' '; } } - if (mouse_char == wp->w_p_fcs_chars.foldclosed) { + if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) { return MOUSE_FOLD_OPEN; } else if (mouse_char != ' ') { return MOUSE_FOLD_CLOSE; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 0b4e2e1f23..3587b12277 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -6777,9 +6777,10 @@ static void nv_g_cmd(cmdarg_T *cap) } coladvance((colnr_T)i); if (flag) { - do + do { i = gchar_cursor(); - while (ascii_iswhite(i) && oneright()); + } while (ascii_iswhite(i) && oneright()); + curwin->w_valid &= ~VALID_WCOL; } curwin->w_set_curswant = true; break; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 3038fad894..2cd71f2360 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -288,7 +288,7 @@ void shift_line( { int count; int i, j; - int p_sw = get_sw_value(curbuf); + int p_sw = (int)get_sw_value_indent(curbuf); count = get_indent(); // get current indent @@ -332,8 +332,9 @@ static void shift_block(oparg_T *oap, int amount) const int oldstate = State; char_u *newp; const int oldcol = curwin->w_cursor.col; - const int p_sw = get_sw_value(curbuf); - const int p_ts = (int)curbuf->b_p_ts; + int p_sw = (int)get_sw_value_indent(curbuf); + long *p_vts = curbuf->b_p_vts_array; + const long p_ts = curbuf->b_p_ts; struct block_def bd; int incr; int i = 0, j = 0; @@ -383,12 +384,11 @@ static void shift_block(oparg_T *oap, int amount) } /* OK, now total=all the VWS reqd, and textstart points at the 1st * non-ws char in the block. */ - if (!curbuf->b_p_et) - i = ((ws_vcol % p_ts) + total) / p_ts; /* number of tabs */ - if (i) - j = ((ws_vcol % p_ts) + total) % p_ts; /* number of spp */ - else + if (!curbuf->b_p_et) { + tabstop_fromto(ws_vcol, ws_vcol + total, p_ts, p_vts, &i, &j); + } else { j = total; + } // if we're splitting a TAB, allow for it int col_pre = bd.pre_whitesp_c - (bd.startspaces != 0); @@ -1079,13 +1079,15 @@ do_execreg( } } escaped = vim_strsave_escape_csi(reg->y_array[i]); - retval = ins_typebuf(escaped, remap, 0, TRUE, silent); + retval = ins_typebuf(escaped, remap, 0, true, silent); xfree(escaped); - if (retval == FAIL) + if (retval == FAIL) { return FAIL; - if (colon && ins_typebuf((char_u *)":", remap, 0, TRUE, silent) - == FAIL) + } + if (colon + && ins_typebuf((char_u *)":", remap, 0, true, silent) == FAIL) { return FAIL; + } } reg_executing = regname == 0 ? '"' : regname; // disable the 'q' command } @@ -1109,8 +1111,9 @@ static void put_reedit_in_typebuf(int silent) buf[0] = (char_u)(restart_edit == 'I' ? 'i' : restart_edit); buf[1] = NUL; } - if (ins_typebuf(buf, REMAP_NONE, 0, TRUE, silent) == OK) + if (ins_typebuf(buf, REMAP_NONE, 0, true, silent) == OK) { restart_edit = NUL; + } } } @@ -1130,25 +1133,29 @@ static int put_in_typebuf( int retval = OK; put_reedit_in_typebuf(silent); - if (colon) - retval = ins_typebuf((char_u *)"\n", REMAP_NONE, 0, TRUE, silent); + if (colon) { + retval = ins_typebuf((char_u *)"\n", REMAP_NONE, 0, true, silent); + } if (retval == OK) { char_u *p; - if (esc) + if (esc) { p = vim_strsave_escape_csi(s); - else + } else { p = s; - if (p == NULL) + } + if (p == NULL) { retval = FAIL; - else - retval = ins_typebuf(p, esc ? REMAP_NONE : REMAP_YES, - 0, TRUE, silent); - if (esc) + } else { + retval = ins_typebuf(p, esc ? REMAP_NONE : REMAP_YES, 0, true, silent); + } + if (esc) { xfree(p); + } + } + if (colon && retval == OK) { + retval = ins_typebuf((char_u *)":", REMAP_NONE, 0, true, silent); } - if (colon && retval == OK) - retval = ins_typebuf((char_u *)":", REMAP_NONE, 0, TRUE, silent); return retval; } @@ -2800,7 +2807,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) size_t y_size; size_t oldlen; int y_width = 0; - colnr_T vcol; + colnr_T vcol = 0; int delcount; int incr = 0; struct block_def bd; @@ -3061,14 +3068,17 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (gchar_cursor() == TAB) { /* Don't need to insert spaces when "p" on the last position of a * tab or "P" on the first position. */ + int viscol = getviscol(); if (dir == FORWARD - ? (int)curwin->w_cursor.coladd < curbuf->b_p_ts - 1 - : curwin->w_cursor.coladd > 0) - coladvance_force(getviscol()); - else + ? tabstop_padding(viscol, curbuf->b_p_ts, curbuf->b_p_vts_array) != 1 + : curwin->w_cursor.coladd > 0) { + coladvance_force(viscol); + } else { curwin->w_cursor.coladd = 0; - } else if (curwin->w_cursor.coladd > 0 || gchar_cursor() == NUL) + } + } else if (curwin->w_cursor.coladd > 0 || gchar_cursor() == NUL) { coladvance_force(getviscol() + (dir == FORWARD)); + } } lnum = curwin->w_cursor.lnum; @@ -3177,7 +3187,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // insert the new text totlen = (size_t)(count * (yanklen + spaces) + bd.startspaces + bd.endspaces); - newp = (char_u *) xmalloc(totlen + oldlen + 1); + int addcount = (int)totlen + lines_appended; + newp = (char_u *)xmalloc(totlen + oldlen + 1); // copy part up to cursor to new line ptr = newp; memmove(ptr, oldp, (size_t)bd.textcol); @@ -3194,6 +3205,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if ((j < count - 1 || !shortline) && spaces) { memset(ptr, ' ', (size_t)spaces); ptr += spaces; + } else { + addcount -= spaces; } } // may insert some spaces after the new text @@ -3205,7 +3218,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns); ml_replace(curwin->w_cursor.lnum, newp, false); extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol, - delcount, (int)totlen + lines_appended, kExtmarkUndo); + delcount, addcount, kExtmarkUndo); ++curwin->w_cursor.lnum; if (i == 0) diff --git a/src/nvim/option.c b/src/nvim/option.c index ac25c86b5f..612ecca96a 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -180,6 +180,8 @@ static long p_ts; static long p_tw; static int p_udf; static long p_wm; +static char_u *p_vsts; +static char_u *p_vts; static char_u *p_keymap; // Saved values for when 'bin' is set. @@ -194,6 +196,7 @@ static int p_et_nopaste; static long p_sts_nopaste; static long p_tw_nopaste; static long p_wm_nopaste; +static char_u *p_vsts_nopaste; typedef struct vimoption { char *fullname; // full option name @@ -379,8 +382,8 @@ void set_init_1(bool clean_arg) # else static char *(names[3]) = {"TMPDIR", "TEMP", "TMP"}; # endif - int len; garray_T ga; + opt_idx = findoption("backupskip"); ga_init(&ga, 1, 100); for (size_t n = 0; n < ARRAY_SIZE(names); n++) { @@ -401,15 +404,23 @@ void set_init_1(bool clean_arg) } if (p != NULL && *p != NUL) { // First time count the NUL, otherwise count the ','. - len = (int)strlen(p) + 3; - ga_grow(&ga, len); - if (!GA_EMPTY(&ga)) { - STRCAT(ga.ga_data, ","); + const size_t len = strlen(p) + 3; + char *item = xmalloc(len); + xstrlcpy(item, p, len); + add_pathsep(item); + xstrlcat(item, "*", len); + if (find_dup_item(ga.ga_data, (char_u *)item, options[opt_idx].flags) + == NULL) { + ga_grow(&ga, (int)len); + if (!GA_EMPTY(&ga)) { + STRCAT(ga.ga_data, ","); + } + STRCAT(ga.ga_data, p); + add_pathsep(ga.ga_data); + STRCAT(ga.ga_data, "*"); + ga.ga_len += (int)len; } - STRCAT(ga.ga_data, p); - add_pathsep(ga.ga_data); - STRCAT(ga.ga_data, "*"); - ga.ga_len += len; + xfree(item); } if(mustfree) { xfree(p); @@ -713,6 +724,38 @@ static void set_string_default(const char *name, char *val, bool allocated) } } +// For an option value that contains comma separated items, find "newval" in +// "origval". Return NULL if not found. +static char_u *find_dup_item(char_u *origval, const char_u *newval, + uint32_t flags) + FUNC_ATTR_NONNULL_ARG(2) +{ + int bs = 0; + + if (origval == NULL) { + return NULL; + } + + const size_t newlen = STRLEN(newval); + for (char_u *s = origval; *s != NUL; s++) { + if ((!(flags & P_COMMA) || s == origval || (s[-1] == ',' && !(bs & 1))) + && STRNCMP(s, newval, newlen) == 0 + && (!(flags & P_COMMA) || s[newlen] == ',' || s[newlen] == NUL)) { + return s; + } + // Count backslashes. Only a comma with an even number of backslashes + // or a single backslash preceded by a comma before it is recognized as + // a separator. + if ((s > origval + 1 && s[-1] == '\\' && s[-2] != ',') + || (s == origval + 1 && s[-1] == '\\')) { + bs++; + } else { + bs = 0; + } + } + return NULL; +} + /// Set the Vi-default value of a number option. /// Used for 'lines' and 'columns'. void set_number_default(char *name, long val) @@ -1285,9 +1328,7 @@ int do_set( char *saved_newval = NULL; unsigned newlen; int comma; - int bs; - int new_value_alloced; /* new string option - was allocated */ + bool new_value_alloced = false; // new string option was allocated /* When using ":set opt=val" for a global option * with a local value the local value will be @@ -1486,34 +1527,20 @@ int do_set( i = 0; // init for GCC if (removing || (flags & P_NODUP)) { i = (int)STRLEN(newval); - bs = 0; - for (s = origval; *s; s++) { - if ((!(flags & P_COMMA) - || s == origval - || (s[-1] == ',' && !(bs & 1))) - && STRNCMP(s, newval, i) == 0 - && (!(flags & P_COMMA) - || s[i] == ',' - || s[i] == NUL)) { - break; - } - // Count backslashes. Only a comma with an even number of - // backslashes or a single backslash preceded by a comma - // before it is recognized as a separator - if ((s > origval + 1 && s[-1] == '\\' && s[-2] != ',') - || (s == origval + 1 && s[-1] == '\\')) { - bs++; - } else { - bs = 0; - } - } + s = find_dup_item(origval, newval, flags); // do not add if already there - if ((adding || prepending) && *s) { + if ((adding || prepending) && s != NULL) { prepending = false; adding = false; STRCPY(newval, origval); } + + // if no duplicate, move pointer to end of + // original value + if (s == NULL) { + s = origval + (int)STRLEN(origval); + } } /* concatenate the two strings; add a ',' if @@ -1974,6 +2001,10 @@ static void didset_options2(void) // Parse default for 'wildmode'. check_opt_wim(); + xfree(curbuf->b_p_vsts_array); + tabstop_set(curbuf->b_p_vsts, &curbuf->b_p_vsts_array); + xfree(curbuf->b_p_vts_array); + tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array); } /// Check for string options that are NULL (normally only termcap options). @@ -2040,6 +2071,8 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_lw); check_string_option(&buf->b_p_bkc); check_string_option(&buf->b_p_menc); + check_string_option(&buf->b_p_vsts); + check_string_option(&buf->b_p_vts); } /// Free the string allocated for an option. @@ -2086,9 +2119,12 @@ int was_set_insecurely(win_T *const wp, char_u *opt, int opt_flags) /// Get a pointer to the flags used for the P_INSECURE flag of option /// "opt_idx". For some local options a local flags field is used. +/// NOTE: Caller must make sure that "wp" is set to the window from which +/// the option is used. static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) { - if (opt_flags & OPT_LOCAL) + if (opt_flags & OPT_LOCAL) { + assert(wp != NULL); switch ((int)options[opt_idx].indir) { case PV_STL: return &wp->w_p_stl_flags; case PV_FDE: return &wp->w_p_fde_flags; @@ -2097,6 +2133,7 @@ static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags) case PV_FEX: return &wp->w_buffer->b_p_fex_flags; case PV_INEX: return &wp->w_buffer->b_p_inex_flags; } + } // Nothing special, return global flags field. return &options[opt_idx].flags; @@ -2306,7 +2343,7 @@ static char_u * did_set_string_option( int opt_idx, // index in options[] table char_u **varp, // pointer to the option variable - int new_value_alloced, // new value was allocated + bool new_value_alloced, // new value was allocated char_u *oldval, // previous value of the option char_u *errbuf, // buffer for errors, or NULL size_t errbuflen, // length of errors buffer @@ -3082,6 +3119,65 @@ ambw_end: if (opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true) != OK) { errmsg = e_invarg; } + } else if (varp == &(curbuf->b_p_vsts)) { // 'varsofttabstop' + char_u *cp; + + if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { + if (curbuf->b_p_vsts_array) { + xfree(curbuf->b_p_vsts_array); + curbuf->b_p_vsts_array = 0; + } + } else { + for (cp = *varp; *cp; cp++) { + if (ascii_isdigit(*cp)) { + continue; + } + if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { + continue; + } + errmsg = e_invarg; + break; + } + if (errmsg == NULL) { + long *oldarray = curbuf->b_p_vsts_array; + if (tabstop_set(*varp, &(curbuf->b_p_vsts_array))) { + xfree(oldarray); + } else { + errmsg = e_invarg; + } + } + } + } else if (varp == &(curbuf->b_p_vts)) { // 'vartabstop' + char_u *cp; + + if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { + if (curbuf->b_p_vts_array) { + xfree(curbuf->b_p_vts_array); + curbuf->b_p_vts_array = NULL; + } + } else { + for (cp = *varp; *cp; cp++) { + if (ascii_isdigit(*cp)) { + continue; + } + if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { + continue; + } + errmsg = e_invarg; + break; + } + if (errmsg == NULL) { + long *oldarray = curbuf->b_p_vts_array; + if (tabstop_set(*varp, &(curbuf->b_p_vts_array))) { + xfree(oldarray); + if (foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + } else { + errmsg = e_invarg; + } + } + } } else { // Options that are a list of flags. p = NULL; @@ -3381,6 +3477,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set) { &wp->w_p_lcs_chars.prec, "precedes", NUL }, { &wp->w_p_lcs_chars.space, "space", NUL }, { &wp->w_p_lcs_chars.tab2, "tab", NUL }, + { &wp->w_p_lcs_chars.lead, "lead", NUL }, { &wp->w_p_lcs_chars.trail, "trail", NUL }, { &wp->w_p_lcs_chars.conceal, "conceal", NUL }, }; @@ -4283,7 +4380,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.blending = curwin->w_p_winbl > 0; + curwin->w_grid_alloc.blending = curwin->w_p_winbl > 0; } @@ -5656,6 +5753,8 @@ static char_u *get_varp(vimoption_T *p) case PV_TW: return (char_u *)&(curbuf->b_p_tw); case PV_UDF: return (char_u *)&(curbuf->b_p_udf); case PV_WM: return (char_u *)&(curbuf->b_p_wm); + case PV_VSTS: return (char_u *)&(curbuf->b_p_vsts); + case PV_VTS: return (char_u *)&(curbuf->b_p_vts); case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap); case PV_SCL: return (char_u *)&(curwin->w_p_scl); case PV_WINHL: return (char_u *)&(curwin->w_p_winhl); @@ -5796,7 +5895,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 - wp->w_grid.blending = wp->w_p_winbl > 0; + wp->w_grid_alloc.blending = wp->w_p_winbl > 0; } @@ -5907,6 +6006,15 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_tfu = vim_strsave(p_tfu); buf->b_p_sts = p_sts; buf->b_p_sts_nopaste = p_sts_nopaste; + buf->b_p_vsts = vim_strsave(p_vsts); + if (p_vsts && p_vsts != empty_option) { + tabstop_set(p_vsts, &buf->b_p_vsts_array); + } else { + buf->b_p_vsts_array = 0; + } + buf->b_p_vsts_nopaste = p_vsts_nopaste + ? vim_strsave(p_vsts_nopaste) + : NULL; buf->b_p_com = vim_strsave(p_com); buf->b_p_cms = vim_strsave(p_cms); buf->b_p_fo = vim_strsave(p_fo); @@ -5978,10 +6086,21 @@ void buf_copy_options(buf_T *buf, int flags) */ if (dont_do_help) { buf->b_p_isk = save_p_isk; + if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { + tabstop_set(p_vts, &buf->b_p_vts_array); + } else { + buf->b_p_vts_array = NULL; + } } else { buf->b_p_isk = vim_strsave(p_isk); did_isk = true; buf->b_p_ts = p_ts; + buf->b_p_vts = vim_strsave(p_vts); + if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { + tabstop_set(p_vts, &buf->b_p_vts_array); + } else { + buf->b_p_vts_array = NULL; + } buf->b_help = false; if (buf->b_p_bt[0] == 'h') { clear_string_option(&buf->b_p_bt); @@ -6178,6 +6297,8 @@ set_context_in_set_cmd( xp->xp_backslash = XP_BS_THREE; else xp->xp_backslash = XP_BS_ONE; + } else if (p == (char_u *)&p_ft) { + xp->xp_context = EXPAND_FILETYPE; } else { xp->xp_context = EXPAND_FILES; // for 'tags' need three backslashes for a space @@ -6594,6 +6715,12 @@ static void paste_option_changed(void) buf->b_p_sts_nopaste = buf->b_p_sts; buf->b_p_ai_nopaste = buf->b_p_ai; buf->b_p_et_nopaste = buf->b_p_et; + if (buf->b_p_vsts_nopaste) { + xfree(buf->b_p_vsts_nopaste); + } + buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_option + ? vim_strsave(buf->b_p_vsts) + : NULL; } // save global options @@ -6608,6 +6735,12 @@ static void paste_option_changed(void) p_sts_nopaste = p_sts; p_tw_nopaste = p_tw; p_wm_nopaste = p_wm; + if (p_vsts_nopaste) { + xfree(p_vsts_nopaste); + } + p_vsts_nopaste = p_vsts && p_vsts != empty_option + ? vim_strsave(p_vsts) + : NULL; } // Always set the option values, also when 'paste' is set when it is @@ -6619,6 +6752,14 @@ static void paste_option_changed(void) buf->b_p_sts = 0; // softtabstop is 0 buf->b_p_ai = 0; // no auto-indent buf->b_p_et = 0; // no expandtab + if (buf->b_p_vsts) { + free_string_option(buf->b_p_vsts); + } + buf->b_p_vsts = empty_option; + if (buf->b_p_vsts_array) { + xfree(buf->b_p_vsts_array); + } + buf->b_p_vsts_array = 0; } // set global options @@ -6635,6 +6776,10 @@ static void paste_option_changed(void) p_wm = 0; p_sts = 0; p_ai = 0; + if (p_vsts) { + free_string_option(p_vsts); + } + p_vsts = empty_option; } else if (old_p_paste) { // Paste switched from on to off: Restore saved values. @@ -6645,6 +6790,20 @@ static void paste_option_changed(void) buf->b_p_sts = buf->b_p_sts_nopaste; buf->b_p_ai = buf->b_p_ai_nopaste; buf->b_p_et = buf->b_p_et_nopaste; + if (buf->b_p_vsts) { + free_string_option(buf->b_p_vsts); + } + buf->b_p_vsts = buf->b_p_vsts_nopaste + ? vim_strsave(buf->b_p_vsts_nopaste) + : empty_option; + if (buf->b_p_vsts_array) { + xfree(buf->b_p_vsts_array); + } + if (buf->b_p_vsts && buf->b_p_vsts != empty_option) { + tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array); + } else { + buf->b_p_vsts_array = 0; + } } // restore global options @@ -6662,6 +6821,10 @@ static void paste_option_changed(void) p_sts = p_sts_nopaste; p_tw = p_tw_nopaste; p_wm = p_wm_nopaste; + if (p_vsts) { + free_string_option(p_vsts); + } + p_vsts = p_vsts_nopaste ? vim_strsave(p_vsts_nopaste) : empty_option; } old_p_paste = p_paste; @@ -6911,17 +7074,301 @@ int check_ff_value(char_u *p) return check_opt_strings(p, p_ff_values, false); } +// Set the integer values corresponding to the string setting of 'vartabstop'. +// "array" will be set, caller must free it if needed. +bool tabstop_set(char_u *var, long **array) +{ + long valcount = 1; + int t; + char_u *cp; + + if (var[0] == NUL || (var[0] == '0' && var[1] == NUL)) { + *array = NULL; + return true; + } + + for (cp = var; *cp != NUL; cp++) { + if (cp == var || cp[-1] == ',') { + char_u *end; + + if (strtol((char *)cp, (char **)&end, 10) <= 0) { + if (cp != end) { + EMSG(_(e_positive)); + } else { + EMSG(_(e_invarg)); + } + return false; + } + } + + if (ascii_isdigit(*cp)) { + continue; + } + if (cp[0] == ',' && cp > var && cp[-1] != ',' && cp[1] != NUL) { + valcount++; + continue; + } + EMSG(_(e_invarg)); + return false; + } + + *array = (long *)xmalloc((unsigned)(valcount + 1) * sizeof(long)); + (*array)[0] = valcount; + + t = 1; + for (cp = var; *cp != NUL;) { + (*array)[t++] = atoi((char *)cp); + while (*cp != NUL && *cp != ',') { + cp++; + } + if (*cp != NUL) { + cp++; + } + } + + return true; +} + +// Calculate the number of screen spaces a tab will occupy. +// If "vts" is set then the tab widths are taken from that array, +// otherwise the value of ts is used. +int tabstop_padding(colnr_T col, long ts_arg, long *vts) +{ + long ts = ts_arg == 0 ? 8 : ts_arg; + colnr_T tabcol = 0; + int t; + long padding = 0; + + if (vts == NULL || vts[0] == 0) { + return (int)(ts - (col % ts)); + } + + const long tabcount = vts[0]; + + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > col) { + padding = tabcol - col; + break; + } + } + if (t > tabcount) { + padding = vts[tabcount] - ((col - tabcol) % vts[tabcount]); + } + + return (int)padding; +} + +// Find the size of the tab that covers a particular column. +int tabstop_at(colnr_T col, long ts, long *vts) +{ + colnr_T tabcol = 0; + int t; + long tab_size = 0; + + if (vts == NULL || vts[0] == 0) { + return (int)ts; + } + + const long tabcount = vts[0]; + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > col) { + tab_size = vts[t]; + break; + } + } + if (t > tabcount) { + tab_size = vts[tabcount]; + } + + return (int)tab_size; +} + +// Find the column on which a tab starts. +colnr_T tabstop_start(colnr_T col, long ts, long *vts) +{ + colnr_T tabcol = 0; + int t; + + if (vts == NULL || vts[0] == 0) { + return (int)((col / ts) * ts); + } + + const long tabcount = vts[0]; + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > col) { + return (int)(tabcol - vts[t]); + } + } + + const int excess = (int)(tabcol % vts[tabcount]); + return (int)(excess + ((col - excess) / vts[tabcount]) * vts[tabcount]); +} + +// Find the number of tabs and spaces necessary to get from one column +// to another. +void tabstop_fromto(colnr_T start_col, + colnr_T end_col, + long ts_arg, + long *vts, + int *ntabs, + int *nspcs) +{ + int spaces = end_col - start_col; + colnr_T tabcol = 0; + long padding = 0; + int t; + long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg; + + if (vts == NULL || vts[0] == 0) { + int tabs = 0; + + const int initspc = (int)(ts - (start_col % ts)); + if (spaces >= initspc) { + spaces -= initspc; + tabs++; + } + tabs += (int)(spaces / ts); + spaces -= (int)((spaces / ts) * ts); + + *ntabs = tabs; + *nspcs = spaces; + return; + } + + // Find the padding needed to reach the next tabstop. + const long tabcount = vts[0]; + for (t = 1; t <= tabcount; t++) { + tabcol += (colnr_T)vts[t]; + if (tabcol > start_col) { + padding = tabcol - start_col; + break; + } + } + if (t > tabcount) { + padding = vts[tabcount] - ((start_col - tabcol) % vts[tabcount]); + } + + // If the space needed is less than the padding no tabs can be used. + if (spaces < padding) { + *ntabs = 0; + *nspcs = spaces; + return; + } + + *ntabs = 1; + spaces -= (int)padding; + + // At least one tab has been used. See if any more will fit. + while (spaces != 0 && ++t <= tabcount) { + padding = vts[t]; + if (spaces < padding) { + *nspcs = spaces; + return; + } + *ntabs += 1; + spaces -= (int)padding; + } + + *ntabs += spaces / (int)vts[tabcount]; + *nspcs = spaces % (int)vts[tabcount]; +} + +// See if two tabstop arrays contain the same values. +bool tabstop_eq(long *ts1, long *ts2) +{ + int t; + + if ((ts1 == 0 && ts2) || (ts1 && ts2 == 0)) { + return false; + } + if (ts1 == ts2) { + return true; + } + if (ts1[0] != ts2[0]) { + return false; + } + + for (t = 1; t <= ts1[0]; t++) { + if (ts1[t] != ts2[t]) { + return false; + } + } + + return true; +} + +// Copy a tabstop array, allocating space for the new array. +int *tabstop_copy(long *oldts) +{ + long *newts; + int t; + + if (oldts == 0) { + return 0; + } + + newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(long)); + for (t = 0; t <= oldts[0]; t++) { + newts[t] = oldts[t]; + } + + return (int *)newts; +} + +// Return a count of the number of tabstops. +int tabstop_count(long *ts) +{ + return ts != NULL ? (int)ts[0] : 0; +} + +// Return the first tabstop, or 8 if there are no tabstops defined. +int tabstop_first(long *ts) +{ + return ts != NULL ? (int)ts[1] : 8; +} + /// Return the effective shiftwidth value for current buffer, using the /// 'tabstop' value when 'shiftwidth' is zero. int get_sw_value(buf_T *buf) { - long result = buf->b_p_sw ? buf->b_p_sw : buf->b_p_ts; + long result = get_sw_value_col(buf, 0); assert(result >= 0 && result <= INT_MAX); return (int)result; } +// Idem, using the first non-black in the current line. +long get_sw_value_indent(buf_T *buf) +{ + pos_T pos = curwin->w_cursor; + + pos.col = (colnr_T)getwhitecols_curline(); + return get_sw_value_pos(buf, &pos); +} + +// Idem, using "pos". +long get_sw_value_pos(buf_T *buf, pos_T *pos) +{ + pos_T save_cursor = curwin->w_cursor; + long sw_value; + + curwin->w_cursor = *pos; + sw_value = get_sw_value_col(buf, get_nolist_virtcol()); + curwin->w_cursor = save_cursor; + return sw_value; +} + +// Idem, using virtual column "col". +long get_sw_value_col(buf_T *buf, colnr_T col) +{ + return buf->b_p_sw ? buf->b_p_sw + : tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array); +} + /// Return the effective softtabstop value for the current buffer, -/// using the effective shiftwidth value when 'softtabstop' is negative. +/// using the shiftwidth value when 'softtabstop' is negative. int get_sts_value(void) { long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 43b0107800..16749ba86b 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -165,8 +165,8 @@ enum { SHM_WRI = 'w', ///< "[w]" instead of "written". SHM_ABBREVIATIONS = 'a', ///< Use abbreviations from #SHM_ALL_ABBREVIATIONS. SHM_WRITE = 'W', ///< Don't use "written" at all. - SHM_TRUNC = 't', ///< Trunctate file messages. - SHM_TRUNCALL = 'T', ///< Trunctate all messages. + SHM_TRUNC = 't', ///< Truncate file messages. + SHM_TRUNCALL = 'T', ///< Truncate all messages. SHM_OVER = 'o', ///< Overwrite file messages. SHM_OVERALL = 'O', ///< Overwrite more messages. SHM_SEARCH = 's', ///< No search hit bottom messages. @@ -824,6 +824,8 @@ enum { , BV_UDF , BV_UL , BV_WM + , BV_VSTS + , BV_VTS , BV_COUNT // must be the last one }; diff --git a/src/nvim/options.lua b/src/nvim/options.lua index fe108ef1cc..d12b31bcaf 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -900,6 +900,7 @@ return { normal_fname_chars=true, vi_def=true, alloced=true, + expand=true, varname='p_ft', defaults={if_true={vi=""}} }, @@ -2998,6 +2999,23 @@ return { defaults={if_true={vi=4000}} }, { + full_name='varsofttabstop', abbreviation='vsts', + short_desc=N_("list of numbers of spaces that <Tab> uses while editing"), + type='string', list='comma', scope={'buffer'}, + vi_def=true, + varname='p_vsts', + defaults={if_true={vi=""}} + }, + { + full_name='vartabstop', abbreviation='vts', + short_desc=N_("list of numbers of spaces that <Tab> in file uses"), + type='string', list='comma', scope={'buffer'}, + vi_def=true, + varname='p_vts', + redraw={'current_buffer'}, + defaults={if_true={vi=""}} + }, + { full_name='verbose', abbreviation='vbs', short_desc=N_("give informative messages"), type='number', scope={'global'}, diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 52d2f84ace..94444e4d23 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -343,19 +343,17 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe) utf8_cmd_line_len += argc; char *utf8_cmd_line = xmalloc(utf8_cmd_line_len); *utf8_cmd_line = NUL; - while (1) { - QUEUE *head = QUEUE_HEAD(&args_q); - QUEUE_REMOVE(head); - ArgNode *arg_node = QUEUE_DATA(head, ArgNode, node); + QUEUE *q; + QUEUE_FOREACH(q, &args_q, { + ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node); xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len); xfree(arg_node->arg); xfree(arg_node); - if (QUEUE_EMPTY(&args_q)) { - break; - } else { + QUEUE_REMOVE(q); + if (!QUEUE_EMPTY(&args_q)) { xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len); } - } + }) int result = utf8_to_utf16(utf8_cmd_line, -1, cmd_line); xfree(utf8_cmd_line); @@ -507,11 +505,11 @@ static int build_env_block(dict_T *denv, wchar_t **env_block) *env_block = xmalloc(sizeof(**env_block) * env_block_len); wchar_t *pos = *env_block; - QUEUE_FOREACH(q, &env_q) { + QUEUE_FOREACH(q, &env_q, { EnvNode *env_node = QUEUE_DATA(q, EnvNode, node); memcpy(pos, env_node->str, env_node->len * sizeof(*pos)); pos += env_node->len; - } + }) *pos = L'\0'; diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index b5d890bf52..2974245857 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -123,7 +123,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, int shell_style = STYLE_ECHO; int check_spaces; static bool did_find_nul = false; - bool ampersent = false; + bool ampersand = false; // vimglob() function to define for Posix shell static char *sh_vimglob_func = "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >"; @@ -245,7 +245,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, p--; } if (*p == '&') { // remove trailing '&' - ampersent = true; + ampersand = true; *p = ' '; } STRCAT(command, ">"); @@ -309,7 +309,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, shellopts |= kShellOptHideMess; } - if (ampersent) { + if (ampersand) { STRCAT(command, "&"); // put the '&' after the redirection } @@ -331,7 +331,7 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file, // When running in the background, give it some time to create the temp // file, but don't wait for it to finish. - if (ampersent) { + if (ampersand) { os_delay(10L, true); } diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 5cf628935f..e7e0dc4013 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -196,6 +196,22 @@ char *os_ctime(char *result, size_t result_len) return os_ctime_r(&rawtime, result, result_len); } +/// Portable version of POSIX strptime() +/// +/// @param str[in] string to convert +/// @param format[in] format to parse "str" +/// @param tm[out] time representation of "str" +/// @return Pointer to first unprocessed character or NULL +char *os_strptime(const char *str, const char *format, struct tm *tm) + FUNC_ATTR_NONNULL_ALL +{ +#ifdef HAVE_STRPTIME + return strptime(str, format, tm); +#else + return NULL; +#endif +} + /// Obtains the current Unix timestamp. /// /// @return Seconds since epoch. diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index aef7ffa397..32c9750628 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -139,8 +139,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, cursor_col = curwin->w_wcol; } - pum_anchor_grid = (int)curwin->w_grid.handle; - if (!ui_has(kUIMultigrid)) { + pum_anchor_grid = (int)curwin->w_grid.target->handle; + pum_win_row += curwin->w_grid.row_offset; + cursor_col += curwin->w_grid.col_offset; + if (!ui_has(kUIMultigrid) && curwin->w_grid.target != &default_grid) { pum_anchor_grid = (int)default_grid.handle; pum_win_row += curwin->w_winrow; cursor_col += curwin->w_wincol; @@ -396,7 +398,7 @@ void pum_redraw(void) char_u *p = NULL; int totwidth, width, w; int thumb_pos = 0; - int thumb_heigth = 1; + int thumb_height = 1; int round; int n; @@ -447,11 +449,11 @@ void pum_redraw(void) } if (pum_scrollbar) { - thumb_heigth = pum_height * pum_height / pum_size; - if (thumb_heigth == 0) { - thumb_heigth = 1; + thumb_height = pum_height * pum_height / pum_size; + if (thumb_height == 0) { + thumb_height = 1; } - thumb_pos = (pum_first * (pum_height - thumb_heigth) + thumb_pos = (pum_first * (pum_height - thumb_height) + (pum_size - pum_height) / 2) / (pum_size - pum_height); } @@ -614,11 +616,11 @@ void pum_redraw(void) if (pum_scrollbar > 0) { if (pum_rl) { grid_putchar(&pum_grid, ' ', row, col_off - pum_width, - i >= thumb_pos && i < thumb_pos + thumb_heigth + i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll); } else { grid_putchar(&pum_grid, ' ', row, col_off + pum_width, - i >= thumb_pos && i < thumb_pos + thumb_heigth + i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll); } } diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index dfd38a6eca..0785fa703d 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3617,6 +3617,15 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height) if (win_split(height, flags) == FAIL) { return FAIL; // not enough room for window } + + // User autocommands may have invalidated the previous window after calling + // win_split, so add a check to ensure that the win is still here + if (IS_LL_STACK(qi) && !win_valid(win)) { + // close the window that was supposed to be for the loclist + win_close(curwin, false); + return FAIL; + } + RESET_BINDING(curwin); if (IS_LL_STACK(qi)) { diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index a2589ac431..d7693c7a6f 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -5895,7 +5895,7 @@ static void regdump(char_u *pattern, bt_regprog_T *r) fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s)); s += 4; } else if (op == RE_LNUM || op == RE_COL || op == RE_VCOL) { - /* one int plus comperator */ + // one int plus comparator fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s)); s += 5; } @@ -7139,6 +7139,7 @@ list_T *reg_submatch_list(int no) tv_list_append_string(list, s, (const char *)rsm.sm_match->endp[no] - s); } + tv_list_ref(list); return list; } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 8b5ee59d40..b6bcee3fda 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -559,7 +559,9 @@ static char_u *nfa_get_match_text(nfa_state_T *start) */ static void realloc_post_list(void) { - size_t new_max = (post_end - post_start) + 1000; + // For weird patterns the number of states can be very high. Increasing by + // 50% seems a reasonable compromise between memory use and speed. + const size_t new_max = (post_end - post_start) * 3 / 2; int *new_start = xrealloc(post_start, new_max * sizeof(int)); post_ptr = new_start + (post_ptr - post_start); post_end = new_start + new_max; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index aa3a7ae7ed..5bf5a471c1 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -232,7 +232,7 @@ void screen_invalidate_highlights(void) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { redraw_later(wp, NOT_VALID); - wp->w_grid.valid = false; + wp->w_grid_alloc.valid = false; } } @@ -582,11 +582,18 @@ int update_screen(int type) FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid.chars) { - grid_invalidate(&wp->w_grid); + if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid_alloc.chars) { + grid_invalidate(&wp->w_grid_alloc); wp->w_redr_type = NOT_VALID; } + // reallocate grid if needed. + win_grid_alloc(wp); + + if (wp->w_redr_border || wp->w_redr_type >= NOT_VALID) { + win_redr_border(wp); + } + if (wp->w_redr_type != 0) { if (!did_one) { did_one = TRUE; @@ -774,8 +781,6 @@ static void win_update(win_T *wp, Providers *providers) type = wp->w_redr_type; - win_grid_alloc(wp); - if (type >= NOT_VALID) { wp->w_redr_status = true; wp->w_lines_valid = 0; @@ -2077,6 +2082,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int change_start = MAXCOL; // first col of changed area int change_end = -1; // last col of changed area 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 int line_attr = 0; // attribute for the whole line int line_attr_lowprio = 0; // low-priority attribute for the line @@ -2317,7 +2323,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL); } // do at least one character; happens when past end of line - if (fromcol == tocol) { + if (fromcol == tocol && search_match_endcol) { tocol = fromcol + 1; } area_highlighting = true; @@ -2422,6 +2428,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, if (wp->w_p_list && !has_fold) { if (wp->w_p_lcs_chars.space || wp->w_p_lcs_chars.trail + || wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.nbsp) { extra_check = true; } @@ -2433,6 +2440,20 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } trailcol += (colnr_T) (ptr - line); } + // find end of leading whitespace + if (wp->w_p_lcs_chars.lead) { + leadcol = 0; + while (ascii_iswhite(ptr[leadcol])) { + leadcol++; + } + if (ptr[leadcol] == NUL) { + // in a line full of spaces all of them are treated as trailing + leadcol = (colnr_T)0; + } else { + // keep track of the first column not filled with spaces + leadcol += (colnr_T)(ptr - line) + 1; + } + } } /* @@ -2753,7 +2774,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, // :sign defined with "numhl" highlight. char_attr = sign_get_attr(num_sign, SIGN_NUMHL); } else if ((wp->w_p_cul || wp->w_p_rnu) - && lnum == wp->w_cursor.lnum) { + && lnum == wp->w_cursor.lnum + && filler_todo == 0) { // When 'cursorline' is set highlight the line number of // the current line differently. // TODO(vim): Can we use CursorLine instead of CursorLineNr @@ -3132,6 +3154,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, mb_utf8 = false; } } else { + assert(p_extra != NULL); c = *p_extra; mb_c = c; // If the UTF-8 character is more than one byte: @@ -3435,8 +3458,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, // TODO: is passing p for start of the line OK? n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, NULL) - 1; if (c == TAB && n_extra + col > grid->Columns) { - n_extra = (int)wp->w_buffer->b_p_ts - - vcol % (int)wp->w_buffer->b_p_ts - 1; + n_extra = tabstop_padding(vcol, wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array) - 1; } c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' '; c_final = NUL; @@ -3456,6 +3479,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, || (mb_utf8 && (mb_c == 160 || mb_c == 0x202f))) && curwin->w_p_lcs_chars.nbsp) || (c == ' ' && curwin->w_p_lcs_chars.space + && ptr - line >= leadcol && ptr - line <= trailcol))) { c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp; n_attr = 1; @@ -3471,8 +3495,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, } } - if (trailcol != MAXCOL && ptr > line + trailcol && c == ' ') { - c = wp->w_p_lcs_chars.trail; + if ((trailcol != MAXCOL && ptr > line + trailcol && c == ' ') + || (leadcol != 0 && ptr < line + leadcol && c == ' ')) { + c = (ptr > line + trailcol) ? wp->w_p_lcs_chars.trail + : wp->w_p_lcs_chars.lead; n_attr = 1; extra_attr = win_hl_attr(wp, HLF_0); saved_attr2 = char_attr; // save current attr @@ -3502,8 +3528,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, vcol_adjusted = vcol - MB_CHARLEN(p_sbr); } // tab amount depends on current column - tab_len = (int)wp->w_buffer->b_p_ts - - vcol_adjusted % (int)wp->w_buffer->b_p_ts - 1; + tab_len = tabstop_padding(vcol_adjusted, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array) - 1; if (!wp->w_p_lbr || !wp->w_p_list) { n_extra = tab_len; @@ -3536,6 +3563,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, xfree(p_extra_free); p_extra_free = p; for (i = 0; i < tab_len; i++) { + if (*p == NUL) { + tab_len = i; + break; + } int lcs = wp->w_p_lcs_chars.tab2; // if tab3 is given, need to change the char @@ -3916,9 +3947,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, .hl_id = hl_err })); do_virttext = true; } else if (has_decor) { - VirtText *vp = decor_redraw_virt_text(wp->w_buffer, &decor_state); - if (vp) { - virt_text = *vp; + virt_text = decor_redraw_virt_text(wp->w_buffer, &decor_state); + if (kv_size(virt_text)) { do_virttext = true; } } @@ -4348,10 +4378,10 @@ void draw_virt_text(buf_T *buf, int *end_col, int max_col) DecorState *state = &decor_state; for (size_t i = 0; i < kv_size(state->active); i++) { HlRange *item = &kv_A(state->active, i); - if (item->start_row == state->row && item->virt_text + if (item->start_row == state->row && kv_size(item->virt_text) && item->virt_text_pos == kVTOverlay && item->virt_col >= 0) { - VirtText vt = *item->virt_text; + VirtText vt = item->virt_text; LineState s = LINE_STATE(""); int virt_attr = 0; int col = item->virt_col; @@ -4404,14 +4434,10 @@ void draw_virt_text(buf_T *buf, int *end_col, int max_col) /// screen positions. void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) { - if (!(*grid)->chars && *grid != &default_grid) { - *row_off += (*grid)->row_offset; - *col_off += (*grid)->col_offset; - if (*grid == &msg_grid_adj && msg_grid.chars) { - *grid = &msg_grid; - } else { - *grid = &default_grid; - } + if ((*grid)->target) { + *row_off += (*grid)->row_offset; + *col_off += (*grid)->col_offset; + *grid = (*grid)->target; } } @@ -5415,6 +5441,46 @@ theend: entered = FALSE; } +static void win_redr_border(win_T *wp) +{ + wp->w_redr_border = false; + if (!(wp->w_floating && wp->w_float_config.border)) { + return; + } + + ScreenGrid *grid = &wp->w_grid_alloc; + + 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); + + 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]); + 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]); + } + 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 // screen grid. @@ -5552,6 +5618,20 @@ void grid_puts_line_start(ScreenGrid *grid, int row) put_dirty_grid = grid; } +void grid_put_schar(ScreenGrid *grid, int row, int col, char_u *schar, int attr) +{ + assert(put_dirty_row == row); + unsigned int off = grid->line_offset[row] + col; + if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar)) { + schar_copy(grid->chars[off], schar); + grid->attrs[off] = attr; + + put_dirty_first = MIN(put_dirty_first, col); + // TODO(bfredl): Y U NO DOUBLEWIDTH? + put_dirty_last = MAX(put_dirty_last, col+1); + } +} + /// like grid_puts(), but output "text[len]". When "len" is -1 output up to /// a NUL. void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, @@ -6143,12 +6223,15 @@ void check_for_delay(int check_msg_scroll) void win_grid_alloc(win_T *wp) { ScreenGrid *grid = &wp->w_grid; + ScreenGrid *grid_allocated = &wp->w_grid_alloc; int rows = wp->w_height_inner; int cols = wp->w_width_inner; + int total_rows = wp->w_height_outer; + int total_cols = wp->w_width_outer; bool want_allocation = ui_has(kUIMultigrid) || wp->w_floating; - bool has_allocation = (grid->chars != NULL); + bool has_allocation = (grid_allocated->chars != NULL); if (grid->Rows != rows) { wp->w_lines_valid = 0; @@ -6157,35 +6240,47 @@ void win_grid_alloc(win_T *wp) } int was_resized = false; - if ((has_allocation != want_allocation) - || grid->Rows != rows - || grid->Columns != cols) { - if (want_allocation) { - grid_alloc(grid, rows, cols, wp->w_grid.valid, false); - grid->valid = true; - } else { - // Single grid mode, all rendering will be redirected to default_grid. - // Only keep track of the size and offset of the window. - grid_free(grid); - grid->Rows = rows; - grid->Columns = cols; - grid->valid = false; + if (want_allocation && (!has_allocation + || grid_allocated->Rows != total_rows + || grid_allocated->Columns != total_cols)) { + grid_alloc(grid_allocated, total_rows, total_cols, + wp->w_grid_alloc.valid, false); + grid_allocated->valid = true; + if (wp->w_border_adj) { + wp->w_redr_border = true; } was_resized = true; - } else if (want_allocation && has_allocation && !wp->w_grid.valid) { - grid_invalidate(grid); - grid->valid = true; + } else if (!want_allocation && has_allocation) { + // Single grid mode, all rendering will be redirected to default_grid. + // Only keep track of the size and offset of the window. + grid_free(grid_allocated); + grid_allocated->valid = false; + was_resized = true; + } else if (want_allocation && has_allocation && !wp->w_grid_alloc.valid) { + grid_invalidate(grid_allocated); + grid_allocated->valid = true; } - grid->row_offset = wp->w_winrow; - grid->col_offset = wp->w_wincol; + grid->Rows = rows; + grid->Columns = cols; + + if (want_allocation) { + grid->target = grid_allocated; + grid->row_offset = wp->w_border_adj; + grid->col_offset = wp->w_border_adj; + } else { + grid->target = &default_grid; + grid->row_offset = wp->w_winrow; + grid->col_offset = wp->w_wincol; + } // send grid resize event if: // - a grid was just resized // - screen_resize was called and all grid sizes must be sent // - the UI wants multigrid event (necessary) if ((send_grid_resize || was_resized) && want_allocation) { - ui_call_grid_resize(grid->handle, grid->Columns, grid->Rows); + ui_call_grid_resize(grid_allocated->handle, + grid_allocated->Columns, grid_allocated->Rows); } } @@ -6274,6 +6369,9 @@ retry: tab_page_click_defs = new_tab_page_click_defs; tab_page_click_defs_size = Columns; + default_grid.comp_height = Rows; + default_grid.comp_width = Columns; + default_grid.row_offset = 0; default_grid.col_offset = 0; default_grid.handle = DEFAULT_GRID_HANDLE; @@ -7512,8 +7610,9 @@ void win_new_shellsize(void) static long old_Columns = 0; if (old_Rows != Rows) { - // if 'window' uses the whole screen, keep it using that */ - if (p_window == old_Rows - 1 || old_Rows == 0) { + // If 'window' uses the whole screen, keep it using that. + // Don't change it when set with "-w size" on the command line. + if (p_window == old_Rows - 1 || (old_Rows == 0 && p_window == 0)) { p_window = Rows - 1; } old_Rows = Rows; @@ -7528,7 +7627,7 @@ void win_new_shellsize(void) win_T *get_win_by_grid_handle(handle_T handle) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_grid.handle == handle) { + if (wp->w_grid_alloc.handle == handle) { return wp; } } diff --git a/src/nvim/search.c b/src/nvim/search.c index 84b71d56a0..9d3d3061e4 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -2326,6 +2326,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/syntax.c b/src/nvim/syntax.c index 547d953be9..f1eb7879b0 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -59,7 +59,9 @@ struct hl_group { bool sg_cleared; ///< "hi clear" was used int sg_attr; ///< Screen attr @see ATTR_ENTRY int sg_link; ///< link to this highlight group ID + int sg_deflink; ///< default link; restored in highlight_clear() int sg_set; ///< combination of flags in \ref SG_SET + sctx_T sg_deflink_sctx; ///< script where the default link was set sctx_T sg_script_ctx; ///< script in which the group was last set // for terminal UIs int sg_cterm; ///< "cterm=" highlighting attr @@ -6044,6 +6046,7 @@ static const char *highlight_init_both[] = { "default link Whitespace NonText", "default link MsgSeparator StatusLine", "default link NormalFloat Pmenu", + "default link FloatBorder VertSplit", "RedrawDebugNormal cterm=reverse gui=reverse", "RedrawDebugClear ctermbg=Yellow guibg=Yellow", "RedrawDebugComposed ctermbg=Green guibg=Green", @@ -6601,6 +6604,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) const char *to_end; int from_id; int to_id; + struct hl_group *hlgroup = NULL; from_end = (const char *)skiptowhite((const char_u *)from_start); to_start = (const char *)skipwhite((const char_u *)from_end); @@ -6627,7 +6631,16 @@ void do_highlight(const char *line, const bool forceit, const bool init) (int)(to_end - to_start)); } - if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0)) { + if (from_id > 0) { + hlgroup = &HL_TABLE()[from_id - 1]; + if (dodefault && (forceit || hlgroup->sg_deflink == 0)) { + hlgroup->sg_deflink = to_id; + hlgroup->sg_deflink_sctx = current_sctx; + hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum; + } + } + + if (from_id > 0 && (!init || hlgroup->sg_set == 0)) { // Don't allow a link when there already is some highlighting // for the group, unless '!' is used if (to_id > 0 && !forceit && !init @@ -6635,17 +6648,16 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (sourcing_name == NULL && !dodefault) { EMSG(_("E414: group has settings, highlight link ignored")); } - } else if (HL_TABLE()[from_id - 1].sg_link != to_id - || HL_TABLE()[from_id - 1].sg_script_ctx.sc_sid - != current_sctx.sc_sid - || HL_TABLE()[from_id - 1].sg_cleared) { + } else if (hlgroup->sg_link != to_id + || hlgroup->sg_script_ctx.sc_sid != current_sctx.sc_sid + || hlgroup->sg_cleared) { if (!init) { - HL_TABLE()[from_id - 1].sg_set |= SG_LINK; + hlgroup->sg_set |= SG_LINK; } - HL_TABLE()[from_id - 1].sg_link = to_id; - HL_TABLE()[from_id - 1].sg_script_ctx = current_sctx; - HL_TABLE()[from_id - 1].sg_script_ctx.sc_lnum += sourcing_lnum; - HL_TABLE()[from_id - 1].sg_cleared = false; + hlgroup->sg_link = to_id; + hlgroup->sg_script_ctx = current_sctx; + hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum; + hlgroup->sg_cleared = false; redraw_all_later(SOME_VALID); // Only call highlight changed() once after multiple changes @@ -7076,13 +7088,14 @@ void restore_cterm_colors(void) */ static int hl_has_settings(int idx, int check_link) { - return HL_TABLE()[idx].sg_attr != 0 - || HL_TABLE()[idx].sg_cterm_fg != 0 - || HL_TABLE()[idx].sg_cterm_bg != 0 - || HL_TABLE()[idx].sg_rgb_fg_name != NULL - || HL_TABLE()[idx].sg_rgb_bg_name != NULL - || HL_TABLE()[idx].sg_rgb_sp_name != NULL - || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK)); + return HL_TABLE()[idx].sg_cleared == 0 + && (HL_TABLE()[idx].sg_attr != 0 + || HL_TABLE()[idx].sg_cterm_fg != 0 + || HL_TABLE()[idx].sg_cterm_bg != 0 + || HL_TABLE()[idx].sg_rgb_fg_name != NULL + || HL_TABLE()[idx].sg_rgb_bg_name != NULL + || HL_TABLE()[idx].sg_rgb_sp_name != NULL + || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK))); } /* @@ -7105,12 +7118,11 @@ static void highlight_clear(int idx) XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_bg_name); XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_sp_name); HL_TABLE()[idx].sg_blend = -1; - // Clear the script ID only when there is no link, since that is not - // cleared. - if (HL_TABLE()[idx].sg_link == 0) { - HL_TABLE()[idx].sg_script_ctx.sc_sid = 0; - HL_TABLE()[idx].sg_script_ctx.sc_lnum = 0; - } + // Restore default link and context if they exist. Otherwise clears. + HL_TABLE()[idx].sg_link = HL_TABLE()[idx].sg_deflink; + // Since we set the default link, set the location to where the default + // link was set. + HL_TABLE()[idx].sg_script_ctx = HL_TABLE()[idx].sg_deflink_sctx; } diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 4ea298fba9..6b8f393572 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -908,7 +908,7 @@ add_llist_tags( if (len > 128) { len = 128; } - xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len); + xstrlcpy((char *)tag_name, (const char *)tagp.tagname, len + 1); tag_name[len] = NUL; // Save the tag file name @@ -975,7 +975,8 @@ add_llist_tags( if (cmd_len > (CMDBUFFSIZE - 5)) { cmd_len = CMDBUFFSIZE - 5; } - xstrlcat((char *)cmd, (char *)cmd_start, cmd_len); + snprintf((char *)cmd + len, CMDBUFFSIZE + 1 - len, + "%.*s", cmd_len, cmd_start); len += cmd_len; if (cmd[len - 1] == '$') { @@ -1141,7 +1142,7 @@ static int find_tagfunc_tags( int result = FAIL; typval_T args[4]; typval_T rettv; - char_u flagString[3]; + char_u flagString[4]; dict_T *d; taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx]; @@ -1170,9 +1171,10 @@ static int find_tagfunc_tags( args[3].v_type = VAR_UNKNOWN; vim_snprintf((char *)flagString, sizeof(flagString), - "%s%s", + "%s%s%s", g_tag_at_cursor ? "c": "", - flags & TAG_INS_COMP ? "i": ""); + flags & TAG_INS_COMP ? "i": "", + flags & TAG_REGEXP ? "r": ""); save_pos = curwin->w_cursor; result = call_vim_function(curbuf->b_p_tfu, 3, args, &rettv); @@ -3002,7 +3004,8 @@ static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname, */ static int find_extra(char_u **pp) { - char_u *str = *pp; + char_u *str = *pp; + char_u first_char = **pp; // Repeat for addresses separated with ';' for (;; ) { @@ -3010,7 +3013,7 @@ static int find_extra(char_u **pp) str = skipdigits(str); } else if (*str == '/' || *str == '?') { str = skip_regexp(str + 1, *str, false, NULL); - if (*str != **pp) { + if (*str != first_char) { str = NULL; } else { str++; @@ -3028,6 +3031,7 @@ static int find_extra(char_u **pp) break; } str++; // skip ';' + first_char = *str; } if (str != NULL && STRNCMP(str, ";\"", 2) == 0) { @@ -3404,6 +3408,7 @@ int set_tagstack(win_T *wp, const dict_T *d, int action) if ((di = tv_dict_find(d, "items", -1)) != NULL) { if (di->di_tv.v_type != VAR_LIST) { + EMSG(_(e_listreq)); return FAIL; } l = di->di_tv.vval.v_list; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index f6995cddb6..afad20f557 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -169,19 +169,20 @@ void terminal_teardown(void) multiqueue_free(refresh_timer.events); time_watcher_close(&refresh_timer, NULL); pmap_free(ptr_t)(invalidated_terminals); + invalidated_terminals = NULL; } // public API {{{ -Terminal *terminal_open(TerminalOptions opts) +Terminal *terminal_open(buf_T *buf, TerminalOptions opts) { // Create a new terminal instance and configure it Terminal *rv = xcalloc(1, sizeof(Terminal)); rv->opts = opts; rv->cursor.visible = true; // Associate the terminal instance with the new buffer - rv->buf_handle = curbuf->handle; - curbuf->terminal = rv; + rv->buf_handle = buf->handle; + buf->terminal = rv; // Create VTerm rv->vt = vterm_new(opts.height, opts.width); vterm_set_utf8(rv->vt, 1); @@ -198,28 +199,36 @@ Terminal *terminal_open(TerminalOptions opts) // have as many lines as screen rows when refresh_scrollback is called rv->invalid_start = 0; rv->invalid_end = opts.height; - refresh_screen(rv, curbuf); + + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + + refresh_screen(rv, buf); set_option_value("buftype", 0, "terminal", OPT_LOCAL); // -V666 // Default settings for terminal buffers - curbuf->b_p_ma = false; // 'nomodifiable' - curbuf->b_p_ul = -1; // 'undolevels' - curbuf->b_p_scbk = // 'scrollback' (initialize local from global) + buf->b_p_ma = false; // 'nomodifiable' + buf->b_p_ul = -1; // 'undolevels' + buf->b_p_scbk = // 'scrollback' (initialize local from global) (p_scbk < 0) ? 10000 : MAX(1, p_scbk); - curbuf->b_p_tw = 0; // 'textwidth' + buf->b_p_tw = 0; // 'textwidth' set_option_value("wrap", false, NULL, OPT_LOCAL); set_option_value("list", false, NULL, OPT_LOCAL); - buf_set_term_title(curbuf, (char *)curbuf->b_ffname); + if (buf->b_ffname != NULL) { + buf_set_term_title(buf, (char *)buf->b_ffname); + } RESET_BINDING(curwin); // Reset cursor in current window. curwin->w_cursor = (pos_T){ .lnum = 1, .col = 0, .coladd = 0 }; // Apply TermOpen autocmds _before_ configuring the scrollback buffer. - apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf); + apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, buf); // Local 'scrollback' _after_ autocmds. - curbuf->b_p_scbk = (curbuf->b_p_scbk < 1) ? SB_MAX : curbuf->b_p_scbk; + buf->b_p_scbk = (buf->b_p_scbk < 1) ? SB_MAX : buf->b_p_scbk; + + aucmd_restbuf(&aco); // Configure the scrollback buffer. - rv->sb_size = (size_t)curbuf->b_p_scbk; + rv->sb_size = (size_t)buf->b_p_scbk; rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size); // Configure the color palette. Try to get the color from: @@ -511,7 +520,9 @@ void terminal_destroy(Terminal *term) } if (!term->refcount) { - if (pmap_has(ptr_t)(invalidated_terminals, term)) { + // might be destroyed after terminal_teardown is invoked + if (invalidated_terminals + && pmap_has(ptr_t)(invalidated_terminals, term)) { // flush any pending changes to the buffer block_autocmds(); refresh_terminal(term); @@ -569,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]); @@ -1324,6 +1338,7 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) // focused) of a invalidated terminal static void refresh_screen(Terminal *term, buf_T *buf) { + assert(buf == curbuf); // TODO(bfredl): remove this condition int changed = 0; int added = 0; int height; diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index e52fd888bd..4641408069 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -31,20 +31,11 @@ endif SCRIPTS ?= $(SCRIPTS_DEFAULT) # Tests using runtest.vim. -NEW_TESTS_ALOT := test_alot_utf8 test_alot +NEW_TESTS_ALOT := test_alot_utf8 test_alot test_alot_latin NEW_TESTS_IN_ALOT := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' $(addsuffix .vim,$(NEW_TESTS_ALOT))) -NEW_TESTS_IN_ALOT_LATIN := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' test_alot_latin.vim) # Ignored tests. -# test_alot_latin: Nvim does not allow setting encoding. -# test_autochdir: ported to Lua, but kept for easier merging. -# test_eval_func: used as include in old-style test (test_eval.in). -# test_listlbr: Nvim does not allow setting encoding. # test_largefile: uses too much resources to run on CI. NEW_TESTS_IGNORE := \ - test_alot_latin $(NEW_TESTS_IN_ALOT_LATIN) \ - test_autochdir \ - test_eval_func \ - test_listlbr \ test_largefile \ NEW_TESTS := $(sort $(basename $(notdir $(wildcard test_*.vim)))) diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim index 24d3959f83..7b06e53dd5 100644 --- a/src/nvim/testdir/check.vim +++ b/src/nvim/testdir/check.vim @@ -108,3 +108,30 @@ func CheckNotMSWindows() throw 'Skipped: does not work on MS-Windows' endif endfunc + +" Command to check for satisfying any of the conditions. +" e.g. CheckAnyOf Feature:bsd Feature:sun Linux +command -nargs=+ CheckAnyOf call CheckAnyOf(<f-args>) +func CheckAnyOf(...) + let excp = [] + for arg in a:000 + try + exe 'Check' .. substitute(arg, ':', ' ', '') + return + catch /^Skipped:/ + let excp += [substitute(v:exception, '^Skipped:\s*', '', '')] + endtry + endfor + throw 'Skipped: ' .. join(excp, '; ') +endfunc + +" Command to check for satisfying all of the conditions. +" e.g. CheckAllOf Unix Gui Option:ballooneval +command -nargs=+ CheckAllOf call CheckAllOf(<f-args>) +func CheckAllOf(...) + for arg in a:000 + exe 'Check' .. substitute(arg, ':', ' ', '') + endfor +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 275edece1e..2d94b637e0 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -373,9 +373,6 @@ let s:flaky_tests = [ \ 'Test_with_partial_callback()', \ ] -" Pattern indicating a common flaky test failure. -let s:flaky_errors_re = 'StopVimInTerminal\|VerifyScreenDump' - " Locate Test_ functions and execute them. redir @q silent function /^Test_ @@ -410,6 +407,9 @@ for s:test in sort(s:tests) let total_errors = [] let run_nr = 1 + " A test can set g:test_is_flaky to retry running the test. + let g:test_is_flaky = 0 + call RunTheTest(s:test) " Repeat a flaky test. Give up when: @@ -417,7 +417,7 @@ for s:test in sort(s:tests) " - it fails five times (with a different message) if len(v:errors) > 0 \ && (index(s:flaky_tests, s:test) >= 0 - \ || v:errors[0] =~ s:flaky_errors_re) + \ || g:test_is_flaky) while 1 call add(s:messages, 'Found errors in ' . s:test . ':') call extend(s:messages, v:errors) diff --git a/src/nvim/testdir/script_util.vim b/src/nvim/testdir/script_util.vim new file mode 100644 index 0000000000..9913b1dfaf --- /dev/null +++ b/src/nvim/testdir/script_util.vim @@ -0,0 +1,69 @@ +" Functions shared by the tests for Vim Script + +" Commands to track the execution path of a script +com! XpathINIT let g:Xpath = '' +com! -nargs=1 -bar Xpath let g:Xpath ..= <args> +com! XloopINIT let g:Xloop = 1 +com! -nargs=1 -bar Xloop let g:Xpath ..= <args> .. g:Xloop +com! XloopNEXT let g:Xloop += 1 + +" MakeScript() - Make a script file from a function. {{{2 +" +" Create a script that consists of the body of the function a:funcname. +" Replace any ":return" by a ":finish", any argument variable by a global +" variable, and every ":call" by a ":source" for the next following argument +" in the variable argument list. This function is useful if similar tests are +" to be made for a ":return" from a function call or a ":finish" in a script +" file. +func MakeScript(funcname, ...) + let script = tempname() + execute "redir! >" . script + execute "function" a:funcname + redir END + execute "edit" script + " Delete the "function" and the "endfunction" lines. Do not include the + " word "function" in the pattern since it might be translated if LANG is + " set. When MakeScript() is being debugged, this deletes also the debugging + " output of its line 3 and 4. + exec '1,/.*' . a:funcname . '(.*)/d' + /^\d*\s*endfunction\>/,$d + %s/^\d*//e + %s/return/finish/e + %s/\<a:\(\h\w*\)/g:\1/ge + normal gg0 + let cnt = 0 + while search('\<call\s*\%(\u\|s:\)\w*\s*(.*)', 'W') > 0 + let cnt = cnt + 1 + s/\<call\s*\%(\u\|s:\)\w*\s*(.*)/\='source ' . a:{cnt}/ + endwhile + g/^\s*$/d + write + bwipeout + return script +endfunc + +" ExecAsScript - Source a temporary script made from a function. {{{2 +" +" Make a temporary script file from the function a:funcname, ":source" it, and +" delete it afterwards. However, if an exception is thrown the file may remain, +" the caller should call DeleteTheScript() afterwards. +let s:script_name = '' +function! ExecAsScript(funcname) + " Make a script from the function passed as argument. + let s:script_name = MakeScript(a:funcname) + + " Source and delete the script. + exec "source" s:script_name + call delete(s:script_name) + let s:script_name = '' +endfunction + +function! DeleteTheScript() + if s:script_name + call delete(s:script_name) + let s:script_name = '' + endif +endfunc + +com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) + diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index daf3c9c110..71af3eead7 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -33,7 +33,7 @@ source test_move.vim source test_partial.vim source test_popup.vim source test_put.vim -source test_recover.vim +source test_rename.vim source test_scroll_opt.vim source test_sort.vim source test_sha256.vim diff --git a/src/nvim/testdir/test_alot_latin.vim b/src/nvim/testdir/test_alot_latin.vim index ebb3bde4ce..23a404cac1 100644 --- a/src/nvim/testdir/test_alot_latin.vim +++ b/src/nvim/testdir/test_alot_latin.vim @@ -4,7 +4,4 @@ " These tests use latin1 'encoding'. Setting 'encoding' is in the individual " files, so that they can be run by themselves. -" Nvim does not allow setting 'encoding', so skip this test group. -finish - source test_regexp_latin.vim diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index b4f7478807..1d114221dc 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -52,6 +52,37 @@ func Test_assert_fails_in_try_block() endtry endfunc +func Test_assert_inrange() + call assert_equal(0, assert_inrange(7, 7, 7)) + call assert_equal(0, assert_inrange(5, 7, 5)) + call assert_equal(0, assert_inrange(5, 7, 6)) + call assert_equal(0, assert_inrange(5, 7, 7)) + call assert_equal(1, assert_inrange(5, 7, 4)) + call assert_match("Expected range 5 - 7, but got 4", v:errors[0]) + call remove(v:errors, 0) + call assert_equal(1, assert_inrange(5, 7, 8)) + call assert_match("Expected range 5 - 7, but got 8", v:errors[0]) + call remove(v:errors, 0) + + call assert_fails('call assert_inrange(1, 1)', 'E119:') + + if has('float') + call assert_equal(0, assert_inrange(7.0, 7, 7)) + call assert_equal(0, assert_inrange(7, 7.0, 7)) + call assert_equal(0, assert_inrange(7, 7, 7.0)) + call assert_equal(0, assert_inrange(5, 7, 5.0)) + call assert_equal(0, assert_inrange(5, 7, 6.0)) + call assert_equal(0, assert_inrange(5, 7, 7.0)) + + call assert_equal(1, assert_inrange(5, 7, 4.0)) + call assert_match("Expected range 5.0 - 7.0, but got 4.0", v:errors[0]) + call remove(v:errors, 0) + call assert_equal(1, assert_inrange(5, 7, 8.0)) + call assert_match("Expected range 5.0 - 7.0, but got 8.0", v:errors[0]) + call remove(v:errors, 0) + endif +endfunc + " Must be last. func Test_zz_quit_detected() " Verify that if a test function ends Vim the test script detects this. diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim index 67c537b407..d071f4b325 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -1,10 +1,10 @@ " Test 'autochdir' behavior -if !exists("+autochdir") - throw 'Skipped: autochdir feature missing' -endif +source check.vim +CheckOption autochdir func Test_set_filename() + CheckFunction test_autochdir let cwd = getcwd() call test_autochdir() set acd @@ -17,3 +17,5 @@ func Test_set_filename() exe 'cd ' . cwd call delete('samples/Xtest') endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 1f3a45a9ab..5e99edf233 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -76,7 +76,7 @@ if has('timers') endfunc func Test_OptionSet_modeline() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override call test_override('starting', 1) au! OptionSet augroup set_tabstop @@ -507,7 +507,7 @@ func s:AutoCommandOptionSet(match) endfunc func Test_OptionSet() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !has("eval") || !exists("+autochdir") return endif @@ -648,7 +648,7 @@ func Test_OptionSet() endfunc func Test_OptionSet_diffmode() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override call test_override('starting', 1) " 18: Changing an option when entering diff mode new @@ -682,7 +682,7 @@ func Test_OptionSet_diffmode() endfunc func Test_OptionSet_diffmode_close() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override call test_override('starting', 1) " 19: Try to close the current window when entering diff mode " should not segfault @@ -1285,9 +1285,9 @@ func Test_autocommand_all_events() endfunc " Test TextChangedI and TextChangedP +" See test/functional/viml/completion_spec.lua' func Test_ChangedP() - " Nvim does not support test_override(). - throw 'skipped: see test/functional/viml/completion_spec.lua' + CheckFunction test_override new call setline(1, ['foo', 'bar', 'foobar']) call test_override("char_avail", 1) @@ -1350,7 +1350,7 @@ func SetLineOne() endfunc func Test_TextChangedI_with_setline() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override new call test_override('char_avail', 1) autocmd TextChangedI <buffer> call SetLineOne() @@ -1366,9 +1366,11 @@ func Test_TextChangedI_with_setline() endfunc func Test_Changed_FirstTime() - if !has('terminal') || has('gui_running') - return - endif + CheckFeature terminal + CheckNotGui + " Starting a terminal to run Vim is always considered flaky. + let g:test_is_flaky = 1 + " Prepare file for TextChanged event. call writefile([''], 'Xchanged.txt') let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': 3}) @@ -1922,20 +1924,28 @@ func Test_autocmd_window() %bw! edit one.txt tabnew two.txt + vnew three.txt + tabnew four.txt + tabprevious let g:blist = [] - augroup aucmd_win_test + augroup aucmd_win_test1 au! au BufEnter * call add(g:blist, [expand('<afile>'), \ win_gettype(bufwinnr(expand('<afile>')))]) augroup END doautoall BufEnter - call assert_equal([['one.txt', 'autocmd'], ['two.txt', '']], g:blist) + call assert_equal([ + \ ['one.txt', 'autocmd'], + \ ['two.txt', ''], + \ ['four.txt', 'autocmd'], + \ ['three.txt', ''], + \ ], g:blist) - augroup aucmd_win_test + augroup aucmd_win_test1 au! augroup END - augroup! aucmd_win_test + augroup! aucmd_win_test1 %bw! endfunc diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index d53acb77d7..ff5029b889 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -12,56 +12,88 @@ source view_util.vim let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" -function s:screen_lines(lnum, width) abort +func s:screen_lines(lnum, width) abort return ScreenLines([a:lnum, a:lnum + 2], a:width) -endfunction +endfunc -function! s:compare_lines(expect, actual) +func! s:compare_lines(expect, actual) call assert_equal(join(a:expect, "\n"), join(a:actual, "\n")) -endfunction +endfunc -function s:test_windows(...) +func s:test_windows(...) call NewWindow(10, 20) setl ts=4 sw=4 sts=4 breakindent put =s:input exe get(a:000, 0, '') -endfunction +endfunc -function s:close_windows(...) +func s:close_windows(...) call CloseWindow() exe get(a:000, 0, '') -endfunction +endfunc -function Test_breakindent01() +func Test_breakindent01() " simple breakindent test call s:test_windows('setl briopt=min:0') - let lines=s:screen_lines(line('.'),8) - let expect=[ -\ " abcd", -\ " qrst", -\ " GHIJ", -\ ] + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qrst", + \ " GHIJ", + \ ] call s:compare_lines(expect, lines) call s:close_windows() -endfunction +endfunc -function Test_breakindent02() +func Test_breakindent01_vartabs() + " like 01 but with vartabs feature + if !has("vartabs") + return + endif + call s:test_windows('setl briopt=min:0 vts=4') + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qrst", + \ " GHIJ", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set vts&') +endfunc + +func Test_breakindent02() " simple breakindent test with showbreak set call s:test_windows('setl briopt=min:0 sbr=>>') - let lines=s:screen_lines(line('.'),8) - let expect=[ -\ " abcd", -\ " >>qr", -\ " >>EF", -\ ] + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " >>qr", + \ " >>EF", + \ ] call s:compare_lines(expect, lines) call s:close_windows('set sbr=') -endfunction +endfunc + +func Test_breakindent02_vartabs() + if !has("vartabs") + return + endif + " simple breakindent test with showbreak set + call s:test_windows('setl briopt=min:0 sbr=>> vts=4') + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " >>qr", + \ " >>EF", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set sbr= vts&') +endfunc -function Test_breakindent03() +func Test_breakindent03() " simple breakindent test with showbreak set and briopt including sbr call s:test_windows('setl briopt=sbr,min:0 sbr=++') - let lines=s:screen_lines(line('.'),8) + let lines = s:screen_lines(line('.'),8) let expect=[ \ " abcd", \ "++ qrst", @@ -70,77 +102,177 @@ function Test_breakindent03() call s:compare_lines(expect, lines) " clean up call s:close_windows('set sbr=') -endfunction +endfunc -function Test_breakindent04() +func Test_breakindent03_vartabs() + " simple breakindent test with showbreak set and briopt including sbr + if !has("vartabs") + return + endif + call s:test_windows('setl briopt=sbr,min:0 sbr=++ vts=4') + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ "++ qrst", + \ "++ GHIJ", + \ ] + call s:compare_lines(expect, lines) + " clean up + call s:close_windows('set sbr= vts&') +endfunc + +func Test_breakindent04() " breakindent set with min width 18 call s:test_windows('setl sbr= briopt=min:18') - let lines=s:screen_lines(line('.'),8) - let expect=[ -\ " abcd", -\ " qrstuv", -\ " IJKLMN", -\ ] + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qrstuv", + \ " IJKLMN", + \ ] call s:compare_lines(expect, lines) " clean up call s:close_windows('set sbr=') -endfunction +endfunc + +func Test_breakindent04_vartabs() + " breakindent set with min width 18 + if !has("vartabs") + return + endif + call s:test_windows('setl sbr= briopt=min:18 vts=4') + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qrstuv", + \ " IJKLMN", + \ ] + call s:compare_lines(expect, lines) + " clean up + call s:close_windows('set sbr= vts&') +endfunc -function Test_breakindent05() +func Test_breakindent05() " breakindent set and shift by 2 call s:test_windows('setl briopt=shift:2,min:0') - let lines=s:screen_lines(line('.'),8) - let expect=[ -\ " abcd", -\ " qr", -\ " EF", -\ ] + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qr", + \ " EF", + \ ] call s:compare_lines(expect, lines) call s:close_windows() -endfunction +endfunc -function Test_breakindent06() +func Test_breakindent05_vartabs() + " breakindent set and shift by 2 + if !has("vartabs") + return + endif + call s:test_windows('setl briopt=shift:2,min:0 vts=4') + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qr", + \ " EF", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set vts&') +endfunc + +func Test_breakindent06() " breakindent set and shift by -1 call s:test_windows('setl briopt=shift:-1,min:0') - let lines=s:screen_lines(line('.'),8) - let expect=[ -\ " abcd", -\ " qrstu", -\ " HIJKL", -\ ] + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qrstu", + \ " HIJKL", + \ ] call s:compare_lines(expect, lines) call s:close_windows() -endfunction +endfunc + +func Test_breakindent06_vartabs() + " breakindent set and shift by -1 + if !has("vartabs") + return + endif + call s:test_windows('setl briopt=shift:-1,min:0 vts=4') + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ " abcd", + \ " qrstu", + \ " HIJKL", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set vts&') +endfunc -function Test_breakindent07() +func Test_breakindent07() " breakindent set and shift by 1, Number set sbr=? and briopt:sbr call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 cpo+=n') - let lines=s:screen_lines(line('.'),10) - let expect=[ -\ " 2 ab", -\ "? m", -\ "? x", -\ ] + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ab", + \ "? m", + \ "? x", + \ ] call s:compare_lines(expect, lines) " clean up call s:close_windows('set sbr= cpo-=n') -endfunction +endfunc + +func Test_breakindent07_vartabs() + if !has("vartabs") + return + endif + " breakindent set and shift by 1, Number set sbr=? and briopt:sbr + call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 cpo+=n vts=4') + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ab", + \ "? m", + \ "? x", + \ ] + call s:compare_lines(expect, lines) + " clean up + call s:close_windows('set sbr= cpo-=n vts&') +endfunc -function Test_breakindent07a() +func Test_breakindent07a() " breakindent set and shift by 1, Number set sbr=? and briopt:sbr call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4') - let lines=s:screen_lines(line('.'),10) - let expect=[ -\ " 2 ab", -\ " ? m", -\ " ? x", -\ ] + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ab", + \ " ? m", + \ " ? x", + \ ] call s:compare_lines(expect, lines) " clean up call s:close_windows('set sbr=') -endfunction +endfunc + +func Test_breakindent07a_vartabs() + if !has("vartabs") + return + endif + " breakindent set and shift by 1, Number set sbr=? and briopt:sbr + call s:test_windows('setl briopt=shift:1,sbr,min:0 nu sbr=? nuw=4 vts=4') + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ab", + \ " ? m", + \ " ? x", + \ ] + call s:compare_lines(expect, lines) + " clean up + call s:close_windows('set sbr= vts&') +endfunc -function Test_breakindent08() +func Test_breakindent08() " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list cpo+=n ts=4') " make sure, cache is invalidated! @@ -148,43 +280,96 @@ function Test_breakindent08() redraw! set ts=4 redraw! - let lines=s:screen_lines(line('.'),10) - let expect=[ -\ " 2 ^Iabcd", -\ "# opq", -\ "# BCD", -\ ] + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ^Iabcd", + \ "# opq", + \ "# BCD", + \ ] call s:compare_lines(expect, lines) call s:close_windows('set sbr= cpo-=n') -endfunction +endfunc + +func Test_breakindent08_vartabs() + if !has("vartabs") + return + endif + " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr + call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list cpo+=n ts=4 vts=4') + " make sure, cache is invalidated! + set ts=8 + redraw! + set ts=4 + redraw! + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ^Iabcd", + \ "# opq", + \ "# BCD", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set sbr= cpo-=n vts&') +endfunc -function Test_breakindent08a() +func Test_breakindent08a() " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list') - let lines=s:screen_lines(line('.'),10) - let expect=[ -\ " 2 ^Iabcd", -\ " # opq", -\ " # BCD", -\ ] + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ^Iabcd", + \ " # opq", + \ " # BCD", + \ ] call s:compare_lines(expect, lines) call s:close_windows('set sbr=') -endfunction +endfunc -function Test_breakindent09() +func Test_breakindent08a_vartabs() + if !has("vartabs") + return + endif + " breakindent set and shift by 1, Number and list set sbr=# and briopt:sbr + call s:test_windows('setl briopt=shift:1,sbr,min:0 nu nuw=4 sbr=# list vts=4') + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ^Iabcd", + \ " # opq", + \ " # BCD", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set sbr= vts&') +endfunc + +func Test_breakindent09() " breakindent set and shift by 1, Number and list set sbr=# call s:test_windows('setl briopt=shift:1,min:0 nu nuw=4 sbr=# list') - let lines=s:screen_lines(line('.'),10) - let expect=[ -\ " 2 ^Iabcd", -\ " #op", -\ " #AB", -\ ] + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ^Iabcd", + \ " #op", + \ " #AB", + \ ] call s:compare_lines(expect, lines) call s:close_windows('set sbr=') -endfunction +endfunc + +func Test_breakindent09_vartabs() + if !has("vartabs") + return + endif + " breakindent set and shift by 1, Number and list set sbr=# + call s:test_windows('setl briopt=shift:1,min:0 nu nuw=4 sbr=# list vts=4') + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ^Iabcd", + \ " #op", + \ " #AB", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set sbr= vts&') +endfunc -function Test_breakindent10() +func Test_breakindent10() " breakindent set, Number set sbr=~ call s:test_windows('setl cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0') " make sure, cache is invalidated! @@ -192,41 +377,91 @@ function Test_breakindent10() redraw! set ts=4 redraw! - let lines=s:screen_lines(line('.'),10) - let expect=[ -\ " 2 ab", -\ "~ mn", -\ "~ yz", -\ ] + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ab", + \ "~ mn", + \ "~ yz", + \ ] call s:compare_lines(expect, lines) call s:close_windows('set sbr= cpo-=n') -endfunction +endfunc + +func Test_breakindent10_vartabs() + if !has("vartabs") + return + endif + " breakindent set, Number set sbr=~ + call s:test_windows('setl cpo+=n sbr=~ nu nuw=4 nolist briopt=sbr,min:0 vts=4') + " make sure, cache is invalidated! + set ts=8 + redraw! + set ts=4 + redraw! + let lines = s:screen_lines(line('.'),10) + let expect = [ + \ " 2 ab", + \ "~ mn", + \ "~ yz", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set sbr= cpo-=n vts&') +endfunc -function Test_breakindent11() +func Test_breakindent11() " test strdisplaywidth() call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4') let text=getline(2) let width = strlen(text[1:])+indent(2)+strlen(&sbr)*3 " text wraps 3 times call assert_equal(width, strdisplaywidth(text)) call s:close_windows('set sbr=') -endfunction +endfunc + +func Test_breakindent11_vartabs() + if !has("vartabs") + return + endif + " test strdisplaywidth() + call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4 vts=4') + let text = getline(2) + let width = strlen(text[1:])+indent(2)+strlen(&sbr)*3 " text wraps 3 times + call assert_equal(width, strdisplaywidth(text)) + call s:close_windows('set sbr= vts&') +endfunc -function Test_breakindent12() +func Test_breakindent12() " test breakindent with long indent let s:input="\t\t\t\t\t{" call s:test_windows('setl breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4 list listchars=tab:>-') - let lines=s:screen_lines(2,16) - let expect=[ -\ " 2 >--->--->--->", -\ " ---{ ", -\ "~ ", -\ ] + let lines = s:screen_lines(2,16) + let expect = [ + \ " 2 >--->--->--->", + \ " ---{ ", + \ "~ ", + \ ] call s:compare_lines(expect, lines) call s:close_windows('set nuw=4 listchars=') -endfunction +endfunc + +func Test_breakindent12_vartabs() + if !has("vartabs") + return + endif + " test breakindent with long indent + let s:input = "\t\t\t\t\t{" + call s:test_windows('setl breakindent linebreak briopt=min:10 nu numberwidth=3 ts=4 list listchars=tab:>- vts=4') + let lines = s:screen_lines(2,16) + let expect = [ + \ " 2 >--->--->--->", + \ " ---{ ", + \ "~ ", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set nuw=4 listchars= vts&') +endfunc -function Test_breakindent13() - let s:input="" +func Test_breakindent13() + let s:input = "" call s:test_windows('setl breakindent briopt=min:10 ts=8') vert resize 20 call setline(1, [" a\tb\tc\td\te", " z y x w v"]) @@ -237,65 +472,149 @@ function Test_breakindent13() call assert_equal('d', @a) call assert_equal('w', @b) call s:close_windows() -endfunction +endfunc + +func Test_breakindent13_vartabs() + if !has("vartabs") + return + endif + let s:input = "" + call s:test_windows('setl breakindent briopt=min:10 ts=8 vts=8') + vert resize 20 + call setline(1, [" a\tb\tc\td\te", " z y x w v"]) + 1 + norm! fbgj"ayl + 2 + norm! fygj"byl + call assert_equal('d', @a) + call assert_equal('w', @b) + call s:close_windows('set vts&') +endfunc -function Test_breakindent14() - let s:input="" +func Test_breakindent14() + let s:input = "" call s:test_windows('setl breakindent briopt= ts=8') vert resize 30 norm! 3a1234567890 norm! a abcde exec "norm! 0\<C-V>tex" - let lines=s:screen_lines(line('.'),8) - let expect=[ -\ "e ", -\ "~ ", -\ "~ ", -\ ] + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ "e ", + \ "~ ", + \ "~ ", + \ ] call s:compare_lines(expect, lines) call s:close_windows() -endfunction +endfunc + +func Test_breakindent14_vartabs() + if !has("vartabs") + return + endif + let s:input = "" + call s:test_windows('setl breakindent briopt= ts=8 vts=8') + vert resize 30 + norm! 3a1234567890 + norm! a abcde + exec "norm! 0\<C-V>tex" + let lines = s:screen_lines(line('.'),8) + let expect = [ + \ "e ", + \ "~ ", + \ "~ ", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set vts&') +endfunc -function Test_breakindent15() - let s:input="" +func Test_breakindent15() + let s:input = "" call s:test_windows('setl breakindent briopt= ts=8 sw=8') vert resize 30 norm! 4a1234567890 exe "normal! >>\<C-V>3f0x" - let lines=s:screen_lines(line('.'),20) - let expect=[ -\ " 1234567890 ", -\ "~ ", -\ "~ ", -\ ] + let lines = s:screen_lines(line('.'),20) + let expect = [ + \ " 1234567890 ", + \ "~ ", + \ "~ ", + \ ] call s:compare_lines(expect, lines) call s:close_windows() -endfunction +endfunc + +func Test_breakindent15_vartabs() + if !has("vartabs") + return + endif + let s:input = "" + call s:test_windows('setl breakindent briopt= ts=8 sw=8 vts=8') + vert resize 30 + norm! 4a1234567890 + exe "normal! >>\<C-V>3f0x" + let lines = s:screen_lines(line('.'),20) + let expect = [ + \ " 1234567890 ", + \ "~ ", + \ "~ ", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set vts&') +endfunc -function Test_breakindent16() +func Test_breakindent16() " Check that overlong lines are indented correctly. - let s:input="" + let s:input = "" call s:test_windows('setl breakindent briopt=min:0 ts=4') call setline(1, "\t".repeat("1234567890", 10)) resize 6 norm! 1gg$ redraw! - let lines=s:screen_lines(1,10) - let expect=[ -\ " 789012", -\ " 345678", -\ " 901234", -\ ] + let lines = s:screen_lines(1,10) + let expect = [ + \ " 789012", + \ " 345678", + \ " 901234", + \ ] call s:compare_lines(expect, lines) - let lines=s:screen_lines(4,10) - let expect=[ -\ " 567890", -\ " 123456", -\ " 7890 ", -\ ] + let lines = s:screen_lines(4,10) + let expect = [ + \ " 567890", + \ " 123456", + \ " 7890 ", + \ ] call s:compare_lines(expect, lines) call s:close_windows() -endfunction +endfunc + +func Test_breakindent16_vartabs() + if !has("vartabs") + return + endif + " Check that overlong lines are indented correctly. + let s:input = "" + call s:test_windows('setl breakindent briopt=min:0 ts=4 vts=4') + call setline(1, "\t".repeat("1234567890", 10)) + resize 6 + norm! 1gg$ + redraw! + let lines = s:screen_lines(1,10) + let expect = [ + \ " 789012", + \ " 345678", + \ " 901234", + \ ] + call s:compare_lines(expect, lines) + let lines = s:screen_lines(4,10) + let expect = [ + \ " 567890", + \ " 123456", + \ " 7890 ", + \ ] + call s:compare_lines(expect, lines) + call s:close_windows('set vts&') +endfunc func Test_breakindent17_vartabs() if !has("vartabs") diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index a66aee5e02..489b2477e6 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -569,6 +569,21 @@ func Test_cmdline_complete_user_cmd() delcommand Foo endfunc +func s:ScriptLocalFunction() + echo 'yes' +endfunc + +func Test_cmdline_complete_user_func() + call feedkeys(":func Test_cmdline_complete_user\<Tab>\<Home>\"\<cr>", 'tx') + call assert_match('"func Test_cmdline_complete_user', @:) + call feedkeys(":func s:ScriptL\<Tab>\<Home>\"\<cr>", 'tx') + call assert_match('"func <SNR>\d\+_ScriptLocalFunction', @:) + + " g: prefix also works + call feedkeys(":echo g:Test_cmdline_complete_user_f\<Tab>\<Home>\"\<cr>", 'tx') + call assert_match('"echo g:Test_cmdline_complete_user_func', @:) +endfunc + func Test_cmdline_complete_user_names() if has('unix') && executable('whoami') let whoami = systemlist('whoami')[0] 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_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index ff50d53d86..73b57f302e 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -24,7 +24,7 @@ endfunc func Test_for_invalid() call assert_fails("for x in 99", 'E714:') - call assert_fails("for x in 'asdf'", 'E714:') + call assert_fails("for x in function('winnr')", 'E714:') call assert_fails("for x in {'a': 9}", 'E714:') if 0 diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 6bc5fba5db..fa0bffd96c 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -358,6 +358,7 @@ let s:filename_checks = { \ 'po': ['file.po', 'file.pot'], \ 'pod': ['file.pod'], \ 'pod6': ['file.pod6'], + \ 'poke': ['file.pk'], \ 'postscr': ['file.ps', 'file.pfa', 'file.afm', 'file.eps', 'file.epsf', 'file.epsi', 'file.ai'], \ 'pov': ['file.pov'], \ 'povini': ['.povrayrc'], @@ -370,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'], @@ -520,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'], + \ '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_functions.vim b/src/nvim/testdir/test_functions.vim index 5dae8d681a..555f549743 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1,5 +1,7 @@ " Tests for various functions. + source shared.vim +source check.vim " Must be done first, since the alternate buffer must be unset. func Test_00_bufexists() @@ -171,9 +173,8 @@ func Test_str2nr() endfunc func Test_strftime() - if !exists('*strftime') - return - endif + CheckFunction strftime + " Format of strftime() depends on system. We assume " that basic formats tested here are available and " identical on all systems which support strftime(). @@ -214,6 +215,33 @@ func Test_strftime() endif endfunc +func Test_strptime() + CheckFunction strptime + CheckNotMSWindows + + if exists('$TZ') + let tz = $TZ + endif + let $TZ = 'UTC' + + call assert_equal(1484653763, strptime('%Y-%m-%d %T', '2017-01-17 11:49:23')) + + " Force DST and check that it's considered + let $TZ = 'WINTER0SUMMER,J1,J365' + call assert_equal(1484653763 - 3600, strptime('%Y-%m-%d %T', '2017-01-17 11:49:23')) + + call assert_fails('call strptime()', 'E119:') + call assert_fails('call strptime("xxx")', 'E119:') + call assert_equal(0, strptime("%Y", '')) + call assert_equal(0, strptime("%Y", "xxx")) + + if exists('tz') + let $TZ = tz + else + unlet $TZ + endif +endfunc + func Test_resolve_unix() if !has('unix') return diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim index 9acec51913..d09b25b0e7 100644 --- a/src/nvim/testdir/test_gn.vim +++ b/src/nvim/testdir/test_gn.vim @@ -1,9 +1,8 @@ " Test for gn command func Test_gn_command() - set belloff=all noautocmd new - " replace a single char by itsself quoted: + " replace a single char by itself quoted: call setline('.', 'abc x def x ghi x jkl') let @/ = 'x' exe "norm! cgn'x'\<esc>.." @@ -157,7 +156,6 @@ func Test_gn_command() sil! %d _ set wrapscan&vim - set belloff&vim endfunc func Test_gN_repeat() diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index 4cc4d775d1..ce22de09ca 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -3,6 +3,7 @@ source view_util.vim source screendump.vim source check.vim +source script_util.vim func Test_highlight() " basic test if ":highlight" doesn't crash @@ -623,4 +624,103 @@ func Test_xxlast_highlight_RGB_color() hi clear endfunc +func Test_highlight_clear_restores_links() + let aaa_id = hlID('aaa') + call assert_equal(aaa_id, 0) + + " create default link aaa --> bbb + hi def link aaa bbb + let id_aaa = hlID('aaa') + let hl_aaa_bbb = HighlightArgs('aaa') + + " try to redefine default link aaa --> ccc; check aaa --> bbb + hi def link aaa ccc + call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb) + + " clear aaa; check aaa --> bbb + hi clear aaa + call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb) + + " link aaa --> ccc; clear aaa; check aaa --> bbb + hi link aaa ccc + let id_ccc = hlID('ccc') + call assert_equal(synIDtrans(id_aaa), id_ccc) + hi clear aaa + call assert_equal(HighlightArgs('aaa'), hl_aaa_bbb) + + " forcibly set default link aaa --> ddd + hi! def link aaa ddd + let id_ddd = hlID('ddd') + let hl_aaa_ddd = HighlightArgs('aaa') + call assert_equal(synIDtrans(id_aaa), id_ddd) + + " link aaa --> eee; clear aaa; check aaa --> ddd + hi link aaa eee + let eee_id = hlID('eee') + call assert_equal(synIDtrans(id_aaa), eee_id) + hi clear aaa + call assert_equal(HighlightArgs('aaa'), hl_aaa_ddd) +endfunc + +func Test_highlight_clear_restores_context() + func FuncContextDefault() + hi def link Context ContextDefault + endfun + + func FuncContextRelink() + " Dummy line + hi link Context ContextRelink + endfunc + + let scriptContextDefault = MakeScript("FuncContextDefault") + let scriptContextRelink = MakeScript("FuncContextRelink") + let patContextDefault = fnamemodify(scriptContextDefault, ':t') .. ' line 1' + let patContextRelink = fnamemodify(scriptContextRelink, ':t') .. ' line 2' + + exec "source" scriptContextDefault + let hlContextDefault = execute("verbose hi Context") + call assert_match(patContextDefault, hlContextDefault) + + exec "source" scriptContextRelink + let hlContextRelink = execute("verbose hi Context") + call assert_match(patContextRelink, hlContextRelink) + + hi clear + let hlContextAfterClear = execute("verbose hi Context") + call assert_match(patContextDefault, hlContextAfterClear) + + delfunc FuncContextDefault + delfunc FuncContextRelink + call delete(scriptContextDefault) + call delete(scriptContextRelink) +endfunc + +func Test_highlight_default_colorscheme_restores_links() + hi link TestLink Identifier + hi TestHi ctermbg=red + + let hlTestLinkPre = HighlightArgs('TestLink') + let hlTestHiPre = HighlightArgs('TestHi') + + " Test colorscheme + hi clear + if exists('syntax_on') + syntax reset + endif + let g:colors_name = 'test' + hi link TestLink ErrorMsg + hi TestHi ctermbg=green + + " Restore default highlighting + colorscheme default + " 'default' should work no matter if highlight group was cleared + hi def link TestLink Identifier + hi def TestHi ctermbg=red + let hlTestLinkPost = HighlightArgs('TestLink') + let hlTestHiPost = HighlightArgs('TestHi') + call assert_equal(hlTestLinkPre, hlTestLinkPost) + call assert_equal(hlTestHiPre, hlTestHiPost) + hi clear +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 9435931d41..3da3648fec 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -469,6 +469,34 @@ func Test_pum_with_folds_two_tabs() call delete('Xpumscript') endfunc +" Test for inserting the tag search pattern in insert mode +func Test_ins_compl_tag_sft() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t/^int first() {}$/", + \ "second\tXfoo\t/^int second() {}$/", + \ "third\tXfoo\t/^int third() {}$/"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + set showfulltag + exe "normal isec\<C-X>\<C-]>\<C-N>\<CR>" + call assert_equal('int second() {}', getline(1)) + set noshowfulltag + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe! +endfunc + " Test to ensure 'Scanning...' messages are not recorded in messages history func Test_z1_complete_no_history() new diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim index dcc588120c..4cb609aaf0 100644 --- a/src/nvim/testdir/test_listchars.vim +++ b/src/nvim/testdir/test_listchars.vim @@ -110,6 +110,35 @@ func Test_listchars() \ '.....h>-$', \ 'iii<<<<><<$', '$'], l) + " Test lead and trail + normal ggdG + set listchars=eol:$ + set listchars+=lead:>,trail:<,space:x + set list + + call append(0, [ + \ ' ffff ', + \ ' gg', + \ 'h ', + \ ' ', + \ ' 0 0 ', + \ ]) + + let expected = [ + \ '>>>>ffff<<<<$', + \ '>>>>>>>>>>gg$', + \ 'h<<<<<<<<<<<$', + \ '<<<<<<<<<<<<$', + \ '>>>>0xx0<<<<$', + \ '$' + \ ] + redraw! + for i in range(1, 5) + call cursor(i, 1) + call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) + endfor + + call assert_equal(expected, split(execute("%list"), "\n")) " test nbsp normal ggdG diff --git a/src/nvim/testdir/test_listlbr.vim b/src/nvim/testdir/test_listlbr.vim index d619ac0eb5..e0518de3c2 100644 --- a/src/nvim/testdir/test_listlbr.vim +++ b/src/nvim/testdir/test_listlbr.vim @@ -1,9 +1,5 @@ " Test for linebreak and list option (non-utf8) -" Nvim does not allow setting 'encoding', so skip this test. -finish - -set encoding=latin1 scriptencoding latin1 if !exists("+linebreak") || !has("conceal") @@ -46,6 +42,7 @@ func Test_set_linebreak() endfunc func Test_linebreak_with_list() + throw 'skipped: Nvim does not support enc=latin1' call s:test_windows('setl ts=4 sbr=+ list listchars=') call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ") let lines = s:screen_lines([1, 4], winwidth(0)) @@ -217,6 +214,7 @@ func Test_norm_after_block_visual() endfunc func Test_block_replace_after_wrapping() + throw 'skipped: Nvim does not support enc=latin1' call s:test_windows() call setline(1, repeat("a", 150)) exe "norm! 0yypk147|\<C-V>jr0" diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index c4807797ff..0191dbf33e 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -427,6 +427,30 @@ func Test_error_in_map_expr() exe buf .. 'bwipe!' endfunc +func Test_expr_map_gets_cursor() + new + call setline(1, ['one', 'some w!rd']) + func StoreColumn() + let g:exprLine = line('.') + let g:exprCol = col('.') + return 'x' + endfunc + nnoremap <expr> x StoreColumn() + 2 + nmap ! f!<Ignore>x + call feedkeys("!", 'xt') + call assert_equal('some wrd', getline(2)) + call assert_equal(2, g:exprLine) + call assert_equal(7, g:exprCol) + + bwipe! + unlet g:exprLine + unlet g:exprCol + delfunc StoreColumn + nunmap x + nunmap ! +endfunc + " Test for mapping errors func Test_map_error() call assert_fails('unmap', 'E474:') diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 3ebd048f46..08586dffe1 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -1,5 +1,6 @@ " Tests for :messages, :echomsg, :echoerr +source check.vim source shared.vim func Test_messages() @@ -77,7 +78,7 @@ func Test_echomsg() endfunc func Test_echoerr() - throw 'skipped: Nvim does not support test_ignore_error()' + CheckFunction test_ignore_error call test_ignore_error('IgNoRe') call assert_equal("\nIgNoRe hello", execute(':echoerr "IgNoRe hello"')) call assert_equal("\n12345 IgNoRe", execute(':echoerr 12345 "IgNoRe"')) diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index 7c7804212b..8486f3ff68 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -331,6 +331,20 @@ func Test_mkview_no_balt() %bwipe endfunc +func Test_mksession_no_balt() + edit Xtestfile1 + edit Xtestfile2 + + bdelete Xtestfile1 + mksession! Xtestview + + source Xtestview + call assert_equal(0, buflisted('Xtestfile1')) + + call delete('Xtestview') + %bwipe +endfunc + " Test :mkview with a file argument. func Test_mkview_file() " Create a view with line number and a fold. diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 1202b842fd..5aef33cb09 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -225,11 +225,18 @@ func Test_set_completion() " Expand files and directories. call feedkeys(":set tags=./\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('./samples/ ./sautest/ ./screendump.vim ./setup.vim ./shared.vim', @:) + call assert_match('./samples/ ./sautest/ ./screendump.vim ./script_util.vim ./setup.vim ./shared.vim', @:) call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:) set tags& + + " Expand values for 'filetype' + call feedkeys(":set filetype=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"set filetype=sshdconfig', @:) + call feedkeys(":set filetype=a\<C-A>\<C-B>\"\<CR>", 'xt') + " call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:) + call assert_equal('"set filetype=' .. join(getcompletion('a*', 'filetype')), @:) endfunc func Test_set_errors() @@ -441,6 +448,36 @@ func Test_backupskip() endif endfor + " Duplicates from environment variables should be filtered out (option has + " P_NODUP). Run this in a separate instance and write v:errors in a file, + " so that we see what happens on startup. + let after =<< trim [CODE] + let bsklist = split(&backupskip, ',') + call assert_equal(uniq(copy(bsklist)), bsklist) + call writefile(['errors:'] + v:errors, 'Xtestout') + qall + [CODE] + call writefile(after, 'Xafter') + " let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"' + let cmd = GetVimProg() . ' -S Xafter --cmd "set enc=utf8"' + + let saveenv = {} + for var in ['TMPDIR', 'TMP', 'TEMP'] + let saveenv[var] = getenv(var) + call setenv(var, '/duplicate/path') + endfor + + exe 'silent !' . cmd + call assert_equal(['errors:'], readfile('Xtestout')) + + " restore environment variables + for var in ['TMPDIR', 'TMP', 'TEMP'] + call setenv(var, saveenv[var]) + endfor + + call delete('Xtestout') + call delete('Xafter') + " Duplicates should be filtered out (option has P_NODUP) let backupskip = &backupskip set backupskip= @@ -627,6 +664,28 @@ func Test_opt_winminheight_term() call delete('Xwinminheight') endfunc +func Test_opt_winminheight_term_tabs() + " See test/functional/legacy/options_spec.lua + CheckRunVimInTerminal + + " The tabline should be taken into account. + let lines =<< trim END + set wmh=0 stal=2 + split + split + split + split + tabnew + END + call writefile(lines, 'Xwinminheight') + let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11}) + call term_sendkeys(buf, ":set wmh=1\n") + call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))}) + + call StopVimInTerminal(buf) + call delete('Xwinminheight') +endfunc + " Test for setting option value containing spaces with isfname+=32 func Test_isfname_with_options() set isfname+=32 diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 4ee16558a0..9443958984 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -871,7 +871,7 @@ func Test_popup_complete_backwards_ctrl_p() endfunc fun! Test_complete_o_tab() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override let s:o_char_pressed = 0 fun! s:act_on_text_changed() diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 704fdacdcd..da949f5940 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -2660,7 +2660,7 @@ endfunc " Test for incsearch highlighting of the :vimgrep pattern " This test used to cause "E315: ml_get: invalid lnum" errors. func Test_vimgrep_incsearch() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override enew set incsearch call test_override("char_avail", 1) diff --git a/src/nvim/testdir/test_quotestar.vim b/src/nvim/testdir/test_quotestar.vim index 77a5153a81..6e6f91362b 100644 --- a/src/nvim/testdir/test_quotestar.vim +++ b/src/nvim/testdir/test_quotestar.vim @@ -97,7 +97,7 @@ func Do_test_quotestar_for_x11() if has('unix') && has('gui') && !has('gui_running') let @* = '' - " Running in a terminal and the GUI is avaiable: Tell the server to open + " Running in a terminal and the GUI is available: Tell the server to open " the GUI and check that the remote command still works. " Need to wait for the GUI to start up, otherwise the send hangs in trying " to send to the terminal window. diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index 1bb2ee53de..cacdd68d10 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -1,5 +1,5 @@ " Tests for regexp in latin1 encoding -set encoding=latin1 +" set encoding=latin1 scriptencoding latin1 func s:equivalence_test() @@ -22,11 +22,13 @@ func s:equivalence_test() endfunc func Test_equivalence_re1() + throw 'skipped: Nvim does not support enc=latin1' set re=1 call s:equivalence_test() endfunc func Test_equivalence_re2() + throw 'skipped: Nvim does not support enc=latin1' set re=2 call s:equivalence_test() endfunc @@ -39,6 +41,17 @@ func Test_range_with_newline() bwipe! endfunc +func Test_pattern_compile_speed() + if !exists('+spellcapcheck') || !has('reltime') + return + endif + let start = reltime() + " this used to be very slow, not it should be about a second + set spc=\\v(((((Nxxxxxxx&&xxxx){179})+)+)+){179} + call assert_inrange(0.01, 10.0, reltimefloat(reltime(start))) + set spc= +endfunc + func Test_get_equi_class() new " Incomplete equivalence class caused invalid memory access @@ -87,6 +100,7 @@ func Test_multi_failure() endfunc func Test_recursive_addstate() + throw 'skipped: TODO: ' " This will call addstate() recursively until it runs into the limit. let lnum = search('\v((){328}){389}') call assert_equal(0, lnum) diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 8d2a768ba0..53069b3d31 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -109,6 +109,8 @@ func Test_recording_esc_sequence() bwipe! if exists('save_F2') let &t_F2 = save_F2 + else + set t_F2= endif endfunc diff --git a/src/nvim/testdir/test_rename.vim b/src/nvim/testdir/test_rename.vim new file mode 100644 index 0000000000..e4228188bd --- /dev/null +++ b/src/nvim/testdir/test_rename.vim @@ -0,0 +1,119 @@ +" Test rename() + +func Test_rename_file_to_file() + call writefile(['foo'], 'Xrename1') + + call assert_equal(0, rename('Xrename1', 'Xrename2')) + + call assert_equal('', glob('Xrename1')) + call assert_equal(['foo'], readfile('Xrename2')) + + " When the destination file already exists, it should be overwritten. + call writefile(['foo'], 'Xrename1') + call writefile(['bar'], 'Xrename2') + + call assert_equal(0, rename('Xrename1', 'Xrename2')) + call assert_equal('', glob('Xrename1')) + call assert_equal(['foo'], readfile('Xrename2')) + + call delete('Xrename2') +endfunc + +func Test_rename_file_ignore_case() + " With 'fileignorecase', renaming file will go through a temp file + " when the source and destination file only differ by case. + set fileignorecase + call writefile(['foo'], 'Xrename') + + call assert_equal(0, rename('Xrename', 'XRENAME')) + + call assert_equal(['foo'], readfile('XRENAME')) + + set fileignorecase& + call delete('XRENAME') +endfunc + +func Test_rename_same_file() + call writefile(['foo'], 'Xrename') + + " When the source and destination are the same file, nothing + " should be done. The source file should not be deleted. + call assert_equal(0, rename('Xrename', 'Xrename')) + call assert_equal(['foo'], readfile('Xrename')) + + call assert_equal(0, rename('./Xrename', 'Xrename')) + call assert_equal(['foo'], readfile('Xrename')) + + call delete('Xrename') +endfunc + +func Test_rename_dir_to_dir() + call mkdir('Xrenamedir1') + call writefile(['foo'], 'Xrenamedir1/Xrenamefile') + + call assert_equal(0, rename('Xrenamedir1', 'Xrenamedir2')) + + call assert_equal('', glob('Xrenamedir1')) + call assert_equal(['foo'], readfile('Xrenamedir2/Xrenamefile')) + + call delete('Xrenamedir2/Xrenamefile') + call delete('Xrenamedir2', 'd') +endfunc + +func Test_rename_same_dir() + call mkdir('Xrenamedir') + call writefile(['foo'], 'Xrenamedir/Xrenamefile') + + call assert_equal(0, rename('Xrenamedir', 'Xrenamedir')) + + call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile')) + + call delete('Xrenamedir/Xrenamefile') + call delete('Xrenamedir', 'd') +endfunc + +func Test_rename_copy() + " Check that when original file can't be deleted, rename() + " still succeeds but copies the file. + call mkdir('Xrenamedir') + call writefile(['foo'], 'Xrenamedir/Xrenamefile') + call setfperm('Xrenamedir', 'r-xr-xr-x') + + call assert_equal(0, rename('Xrenamedir/Xrenamefile', 'Xrenamefile')) + + if !has('win32') + " On Windows, the source file is removed despite + " its directory being made not writable. + call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile')) + endif + call assert_equal(['foo'], readfile('Xrenamefile')) + + call setfperm('Xrenamedir', 'rwxrwxrwx') + call delete('Xrenamedir/Xrenamefile') + call delete('Xrenamedir', 'd') + call delete('Xrenamefile') +endfunc + +func Test_rename_fails() + throw 'skipped: TODO: ' + call writefile(['foo'], 'Xrenamefile') + + " Can't rename into a non-existing directory. + call assert_notequal(0, rename('Xrenamefile', 'Xdoesnotexist/Xrenamefile')) + + " Can't rename a non-existing file. + call assert_notequal(0, rename('Xdoesnotexist', 'Xrenamefile2')) + call assert_equal('', glob('Xrenamefile2')) + + " When rename() fails, the destination file should not be deleted. + call assert_notequal(0, rename('Xdoesnotexist', 'Xrenamefile')) + call assert_equal(['foo'], readfile('Xrenamefile')) + + " Can't rename to en empty file name. + call assert_notequal(0, rename('Xrenamefile', '')) + + call assert_fails('call rename("Xrenamefile", [])', 'E730') + call assert_fails('call rename(0z, "Xrenamefile")', 'E976') + + call delete('Xrenamefile') +endfunc diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 0703a6b141..d4d529e4b9 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -2,10 +2,11 @@ source shared.vim source screendump.vim +source check.vim +" See test/functional/legacy/search_spec.lua func Test_search_cmdline() - " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -202,9 +203,9 @@ func Test_search_cmdline() bw! endfunc +" See test/functional/legacy/search_spec.lua func Test_search_cmdline2() - " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -351,7 +352,7 @@ func Test_searchc() endfunc func Cmdline3_prep() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override " need to disable char_avail, " so that expansion of commandline works call test_override("char_avail", 1) @@ -361,14 +362,13 @@ func Cmdline3_prep() endfunc func Incsearch_cleanup() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override set noincsearch call test_override("char_avail", 0) bw! endfunc func Test_search_cmdline3() - throw 'skipped: Nvim does not support test_override()' if !exists('+incsearch') return endif @@ -382,7 +382,6 @@ func Test_search_cmdline3() endfunc func Test_search_cmdline3s() - throw 'skipped: Nvim does not support test_override()' if !exists('+incsearch') return endif @@ -409,7 +408,6 @@ func Test_search_cmdline3s() endfunc func Test_search_cmdline3g() - throw 'skipped: Nvim does not support test_override()' if !exists('+incsearch') return endif @@ -433,7 +431,6 @@ func Test_search_cmdline3g() endfunc func Test_search_cmdline3v() - throw 'skipped: Nvim does not support test_override()' if !exists('+incsearch') return endif @@ -450,9 +447,9 @@ func Test_search_cmdline3v() call Incsearch_cleanup() endfunc +" See test/functional/legacy/search_spec.lua func Test_search_cmdline4() - " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -507,7 +504,7 @@ func Test_search_cmdline5() endfunc func Test_search_cmdline7() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override " Test that pressing <c-g> in an empty command line " does not move the cursor if !exists('+incsearch') @@ -798,7 +795,7 @@ func Test_incsearch_vimgrep_dump() endfunc func Test_keep_last_search_pattern() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -820,7 +817,7 @@ func Test_keep_last_search_pattern() endfunc func Test_word_under_cursor_after_match() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -840,7 +837,7 @@ func Test_word_under_cursor_after_match() endfunc func Test_subst_word_under_cursor() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -882,7 +879,7 @@ func Test_incsearch_with_change() endfunc func Test_incsearch_cmdline_modifier() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -960,7 +957,7 @@ func Test_incsearch_search_dump() endfunc func Test_incsearch_substitute() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -982,7 +979,7 @@ func Test_incsearch_substitute() endfunc func Test_incsearch_substitute_long_line() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override new call test_override("char_avail", 1) set incsearch @@ -1104,7 +1101,7 @@ func Test_one_error_msg() endfunc func Test_incsearch_add_char_under_cursor() - throw 'skipped: Nvim does not support test_override()' + CheckFunction test_override if !exists('+incsearch') return endif @@ -1192,4 +1189,40 @@ func Test_search_smartcase_utf8() close! endfunc +func Test_zzzz_incsearch_highlighting_newline() + CheckRunVimInTerminal + CheckOption incsearch + CheckScreendump + new + call test_override("char_avail", 1) + + let commands =<< trim [CODE] + set incsearch nohls + call setline(1, ['test', 'xxx']) + [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', {}) + 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) + + " clean up + call delete('Xincsearch_nl') + call test_override("char_avail", 0) + bw +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index 4bbd722fdb..9c3a5636ce 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -1,8 +1,7 @@ " Test for signs -if !has('signs') - finish -endif +source check.vim +CheckFeature signs source screendump.vim @@ -1541,7 +1540,7 @@ endfunc " Tests for memory allocation failures in sign functions func Test_sign_memfailures() - throw 'skipped: Nvim does not support test_alloc_fail()' + CheckFunction test_alloc_fail call writefile(repeat(["Sun is shining"], 30), "Xsign") edit Xsign diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index e6ad92f483..e0dc0e0075 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -111,10 +111,9 @@ func Test_pack_in_rtp_when_plugins_run() endfunc func Test_help_arg() - if !has('unix') && has('gui') - " this doesn't work with gvim on MS-Windows - return - endif + " This does not work with a GUI-only binary, such as on MS-Windows. + CheckAnyOf Unix NotGui + if RunVim([], [], '--help >Xtestout') let lines = readfile('Xtestout') call assert_true(len(lines) > 20) @@ -412,6 +411,134 @@ func Test_A_F_H_arg() call delete('Xtestout') endfunc +" Test the --echo-wid argument (for GTK GUI only). +func Test_echo_wid() + CheckCanRunGui + CheckFeature gui_gtk + + if RunVim([], [], '-g --echo-wid -cq >Xtest_echo_wid') + let lines = readfile('Xtest_echo_wid') + call assert_equal(1, len(lines)) + call assert_match('^WID: \d\+$', lines[0]) + endif + + call delete('Xtest_echo_wid') +endfunction + +" Test the -reverse and +reverse arguments (for GUI only). +func Test_reverse() + CheckCanRunGui + CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + + let after =<< trim [CODE] + call writefile([&background], "Xtest_reverse") + qall + [CODE] + if RunVim([], after, '-f -g -reverse') + let lines = readfile('Xtest_reverse') + call assert_equal(['dark'], lines) + endif + if RunVim([], after, '-f -g +reverse') + let lines = readfile('Xtest_reverse') + call assert_equal(['light'], lines) + endif + + call delete('Xtest_reverse') +endfunc + +" Test the -background and -foreground arguments (for GUI only). +func Test_background_foreground() + CheckCanRunGui + CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + + " Is there a better way to check the effect of -background & -foreground + " other than merely looking at &background (dark or light)? + let after =<< trim [CODE] + call writefile([&background], "Xtest_fg_bg") + qall + [CODE] + if RunVim([], after, '-f -g -background darkred -foreground yellow') + let lines = readfile('Xtest_fg_bg') + call assert_equal(['dark'], lines) + endif + if RunVim([], after, '-f -g -background ivory -foreground darkgreen') + let lines = readfile('Xtest_fg_bg') + call assert_equal(['light'], lines) + endif + + call delete('Xtest_fg_bg') +endfunc + +" Test the -font argument (for GUI only). +func Test_font() + CheckCanRunGui + CheckNotMSWindows + + if has('gui_gtk') + let font = 'Courier 14' + elseif has('gui_motif') || has('gui_athena') + let font = '-misc-fixed-bold-*' + else + throw 'Skipped: test does not set a valid font for this GUI' + endif + + let after =<< trim [CODE] + call writefile([&guifont], "Xtest_font") + qall + [CODE] + + if RunVim([], after, '--nofork -g -font "' .. font .. '"') + let lines = readfile('Xtest_font') + call assert_equal([font], lines) + endif + + call delete('Xtest_font') +endfunc + +" Test the -geometry argument (for GUI only). +func Test_geometry() + CheckCanRunGui + CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + + if has('gui_motif') || has('gui_athena') + " FIXME: With GUI Athena or Motif, the value of getwinposx(), + " getwinposy() and getwinpos() do not match exactly the + " value given in -geometry. Why? + " So only check &columns and &lines for those GUIs. + let after =<< trim [CODE] + call writefile([&columns, &lines], "Xtest_geometry") + qall + [CODE] + if RunVim([], after, '-f -g -geometry 31x13+41+43') + let lines = readfile('Xtest_geometry') + call assert_equal(['31', '13'], lines) + endif + else + let after =<< trim [CODE] + call writefile([&columns, &lines, getwinposx(), getwinposy(), string(getwinpos())], "Xtest_geometry") + qall + [CODE] + if RunVim([], after, '-f -g -geometry 31x13+41+43') + let lines = readfile('Xtest_geometry') + call assert_equal(['31', '13', '41', '43', '[41, 43]'], lines) + endif + endif + + call delete('Xtest_geometry') +endfunc + +" Test the -iconic argument (for GUI only). +func Test_iconic() + CheckCanRunGui + CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + + call RunVim([], [], '-f -g -iconic -cq') + + " TODO: currently only start vim iconified, but does not + " check that vim is iconified. How could this be checked? +endfunc + + func Test_invalid_args() if !has('unix') || has('gui_running') " can't get output of Vim. @@ -687,6 +814,34 @@ func Test_v_argv() call assert_equal(['arg1', '--cmd', 'echo v:argv', '--cmd', 'q'']'], list[idx:]) endfunc +" Test for the '-t' option to jump to a tag +func Test_t_arg() + let before =<< trim [CODE] + set tags=Xtags + [CODE] + let after =<< trim [CODE] + let s = bufname('') .. ':L' .. line('.') .. 'C' .. col('.') + call writefile([s], "Xtestout") + qall + [CODE] + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfile1\t/^ \\zsfirst$/", + \ "second\tXfile1\t/^ \\zssecond$/", + \ "third\tXfile1\t/^ \\zsthird$/"], + \ 'Xtags') + call writefile([' first', ' second', ' third'], 'Xfile1') + + for t_arg in ['-t second', '-tsecond'] + if RunVim(before, after, '-t second') + call assert_equal(['Xfile1:L2C5'], readfile('Xtestout'), t_arg) + call delete('Xtestout') + endif + endfor + + call delete('Xtags') + call delete('Xfile1') +endfunc + " Test the '-T' argument which sets the 'term' option. func Test_T_arg() throw 'skipped: Nvim does not support "-T" argument' @@ -735,6 +890,66 @@ func Test_x_arg() call delete('Xtest_x_arg') endfunc +" Test for --not-a-term avoiding escape codes. +func Test_not_a_term() + CheckUnix + CheckNotGui + + if &shellredir =~ '%s' + let redir = printf(&shellredir, 'Xvimout') + else + let redir = &shellredir .. ' Xvimout' + endif + + " Without --not-a-term there are a few escape sequences. + " This will take 2 seconds because of the missing --not-a-term + let cmd = GetVimProg() .. ' --cmd quit ' .. redir + exe "silent !" . cmd + " call assert_match("\<Esc>", readfile('Xvimout')->join()) + call assert_match("\<Esc>", join(readfile('Xvimout'))) + call delete('Xvimout') + + " With --not-a-term there are no escape sequences. + let cmd = GetVimProg() .. ' --not-a-term --cmd quit ' .. redir + exe "silent !" . cmd + " call assert_notmatch("\<Esc>", readfile('Xvimout')->join()) + call assert_notmatch("\<Esc>", join(readfile('Xvimout'))) + call delete('Xvimout') +endfunc + + +" Test for the "-w scriptout" argument +func Test_w_arg() + " Can't catch the output of gvim. + CheckNotGui + + call writefile(["iVim Editor\<Esc>:q!\<CR>"], 'Xscriptin', 'b') + if RunVim([], [], '-s Xscriptin -w Xscriptout') + call assert_equal(["iVim Editor\e:q!\r"], readfile('Xscriptout')) + call delete('Xscriptout') + endif + call delete('Xscriptin') + + " Test for failing to open the script output file. This test works only when + " the language is English. + if !has('win32') && (v:lang == "C" || v:lang =~ '^[Ee]n') + call mkdir("Xdir") + let m = system(GetVimCommand() .. " -w Xdir") + call assert_equal("Cannot open for script output: \"Xdir\"\n", m) + call delete("Xdir", 'rf') + endif + + " A number argument sets the 'window' option + call writefile(["iwindow \<C-R>=&window\<CR>\<Esc>:wq! Xresult\<CR>"], 'Xscriptin', 'b') + for w_arg in ['-w 17', '-w17'] + if RunVim([], [], '-s Xscriptin ' .. w_arg) + call assert_equal(["window 17"], readfile('Xresult'), w_arg) + call delete('Xresult') + endif + endfor + call delete('Xscriptin') +endfunc + " Test starting vim with various names: vim, ex, view, evim, etc. func Test_progname() CheckUnix @@ -777,17 +992,12 @@ func Test_progname() let prognames = ['nvim'] for progname in prognames - if empty($DISPLAY) - if progname =~# 'g' - " Can't run gvim, gview (etc.) if $DISPLAY is not setup. - continue - endif - if has('gui') && (progname ==# 'evim' || progname ==# 'eview') - " evim or eview will start the GUI if there is gui support. - " So don't try to start them either if $DISPLAY is not setup. - continue - endif - endif + let run_with_gui = (progname =~# 'g') || (has('gui') && (progname ==# 'evim' || progname ==# 'eview')) + + if empty($DISPLAY) && run_with_gui + " Can't run gvim, gview (etc.) if $DISPLAY is not setup. + continue + endif exe 'silent !ln -s -f ' ..exepath(GetVimProg()) .. ' Xprogname/' .. progname @@ -801,7 +1011,15 @@ func Test_progname() if progname =~# 'g' && !has('gui') call assert_equal("E25: GUI cannot be used: Not enabled at compile time\n", stdout_stderr, progname) else - call assert_equal('', stdout_stderr, progname) + " GUI motif can output some warnings like this: + " Warning: + " Name: subMenu + " Class: XmCascadeButton + " Illegal mnemonic character; Could not convert X KEYSYM to a keycode + " So don't check that stderr is empty with GUI Motif. + if run_with_gui && !has('gui_motif') + call assert_equal('', stdout_stderr, progname) + endif call assert_equal(expectations[progname], readfile('Xprogname_out'), progname) endif diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index ce2ef4dcd8..48b7b4f2f1 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -440,6 +440,27 @@ func Test_statusline_removed_group() call delete('XTest_statusline') endfunc +func Test_statusline_using_mode() + CheckScreendump + + let lines =<< trim END + set laststatus=2 + let &statusline = '-%{mode()}-' + END + call writefile(lines, 'XTest_statusline') + + let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 5, '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 StopVimInTerminal(buf) + call delete('XTest_statusline') +endfunc + func Test_statusline_after_split_vsplit() only diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 2a27f7a3a1..cc3bfe9f7f 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -746,3 +746,12 @@ func Test_sub_beyond_end() call assert_equal('#', getline(1)) bwipe! endfunc + +func Test_submatch_list_concatenate() + let pat = 'A\(.\)' + let Rep = {-> string([submatch(0, 1)] + [[submatch(1)]])} + " call substitute('A1', pat, Rep, '')->assert_equal("[['A1'], ['1']]") + call assert_equal(substitute('A1', pat, Rep, ''), "[['A1'], ['1']]") +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 4cf0e983b0..66cb0bbe22 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -30,7 +30,7 @@ func Test_syn_iskeyword() \ 'CREATE TABLE FOOBAR(', \ ' DLTD_BY VARCHAR2(100)', \ ');', - \ '']) + \ '']) syntax on set ft=sql @@ -521,7 +521,7 @@ func Test_synstack_synIDtrans() norm f/ call assert_equal(['cComment', 'cCommentStart'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) - call assert_equal(['Comment', 'Comment'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")')) + call assert_equal(['Comment', 'Comment'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")')) norm fA call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index 9cf8690d57..6bbe714d19 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -93,7 +93,6 @@ function! Test_system_exmode() endfunc func Test_system_with_shell_quote() - throw 'skipped: enable after porting method patches' CheckMSWindows call mkdir('Xdir with spaces', 'p') @@ -122,7 +121,8 @@ func Test_system_with_shell_quote() let msg = printf('shell=%s shellxquote=%s', &shell, &shellxquote) try - let out = 'echo 123'->system() + " let out = 'echo 123'->system() + let out = system('echo 123') catch call assert_report(printf('%s: %s', msg, v:exception)) continue diff --git a/src/nvim/testdir/test_tab.vim b/src/nvim/testdir/test_tab.vim index b847dbd962..b8e8dfe062 100644 --- a/src/nvim/testdir/test_tab.vim +++ b/src/nvim/testdir/test_tab.vim @@ -1,3 +1,4 @@ +" Various tests for inserting a Tab. " Tests for "r<Tab>" with 'smarttab' and 'expandtab' set/not set. " Also test that dv_ works correctly @@ -43,3 +44,47 @@ func Test_smarttab() enew! set expandtab& smartindent& copyindent& ts& sw& sts& endfunc + +func Test_softtabstop() + new + set sts=0 sw=0 + exe "normal ix\<Tab>x\<Esc>" + call assert_equal("x\tx", getline(1)) + + call setline(1, '') + set sts=4 + exe "normal ix\<Tab>x\<Esc>" + call assert_equal("x x", getline(1)) + + call setline(1, '') + set sts=-1 sw=4 + exe "normal ix\<Tab>x\<Esc>" + call assert_equal("x x", getline(1)) + + call setline(1, 'x ') + set sts=0 sw=0 backspace=start + exe "normal A\<BS>x\<Esc>" + call assert_equal("x x", getline(1)) + + call setline(1, 'x ') + set sts=4 + exe "normal A\<BS>x\<Esc>" + call assert_equal("x x", getline(1)) + + call setline(1, 'x ') + set sts=-1 sw=4 + exe "normal A\<BS>x\<Esc>" + call assert_equal("x x", getline(1)) + + call setline(1, 'x') + set sts=-1 sw=0 smarttab + exe "normal I\<Tab>\<Esc>" + call assert_equal("\tx", getline(1)) + + call setline(1, 'x') + exe "normal I\<Tab>\<BS>\<Esc>" + call assert_equal("x", getline(1)) + + set sts=0 sw=0 backspace& nosmarttab + bwipe! +endfunc diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index 2b6a89647e..b261b96c3b 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -142,9 +142,6 @@ endfunc " Test autocommands function Test_tabpage_with_autocmd() - if !has('autocmd') - return - endif command -nargs=1 -bar C :call add(s:li, '=== ' . <q-args> . ' ===')|<args> augroup TestTabpageGroup au! diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim index 242aa3a235..ffc1d63b90 100644 --- a/src/nvim/testdir/test_tagfunc.vim +++ b/src/nvim/testdir/test_tagfunc.vim @@ -43,12 +43,24 @@ func Test_tagfunc() call assert_equal('one', g:tagfunc_args[0]) call assert_equal('c', g:tagfunc_args[1]) + let g:tagfunc_args=[] + execute "tag /foo$" + call assert_equal('foo$', g:tagfunc_args[0]) + call assert_equal('r', g:tagfunc_args[1]) + set cpt=t let g:tagfunc_args=[] execute "normal! i\<c-n>\<c-y>" - call assert_equal('ci', g:tagfunc_args[1]) + call assert_equal('\<\k\k', g:tagfunc_args[0]) + call assert_equal('cir', g:tagfunc_args[1]) call assert_equal('nothing1', getline('.')[0:7]) + let g:tagfunc_args=[] + execute "normal! ono\<c-n>\<c-n>\<c-y>" + call assert_equal('\<no', g:tagfunc_args[0]) + call assert_equal('cir', g:tagfunc_args[1]) + call assert_equal('nothing2', getline('.')[0:7]) + func BadTagFunc1(...) return 0 endfunc @@ -81,4 +93,28 @@ func Test_tagfunc() call delete('Xfile1') endfunc +" Test for modifying the tag stack from a tag function and jumping to a tag +" from a tag function +func Test_tagfunc_settagstack() + func Mytagfunc1(pat, flags, info) + call settagstack(1, {'tagname' : 'mytag', 'from' : [0, 10, 1, 0]}) + return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}] + endfunc + set tagfunc=Mytagfunc1 + call writefile([''], 'Xtest') + call assert_fails('tag xyz', 'E986:') + + func Mytagfunc2(pat, flags, info) + tag test_tag + return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}] + endfunc + set tagfunc=Mytagfunc2 + call assert_fails('tag xyz', 'E986:') + + call delete('Xtest') + set tagfunc& + delfunc Mytagfunc1 + delfunc Mytagfunc2 +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 7057cdefb2..9f02af7d8e 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -609,6 +609,295 @@ func Test_tagline() set tags& endfunc +" Test for expanding environment variable in a tag file name +func Test_tag_envvar() + call writefile(["Func1\t$FOO\t/^Func1/"], 'Xtags') + set tags=Xtags + + let $FOO='TagTestEnv' + + let caught_exception = v:false + try + tag Func1 + catch /E429:/ + call assert_match('E429:.*"TagTestEnv".*', v:exception) + let caught_exception = v:true + endtry + call assert_true(caught_exception) + + set tags& + call delete('Xtags') + unlet $FOO +endfunc + +" Test for :ptag +func Test_ptag() + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "second\tXfile1\t2", + \ "third\tXfile1\t3",], + \ 'Xtags') + set tags=Xtags + call writefile(['first', 'second', 'third'], 'Xfile1') + + enew | only + ptag third + call assert_equal(2, winnr()) + call assert_equal(2, winnr('$')) + call assert_equal(1, getwinvar(1, '&previewwindow')) + call assert_equal(0, getwinvar(2, '&previewwindow')) + wincmd w + call assert_equal(3, line('.')) + + " jump to the tag again + ptag third + call assert_equal(3, line('.')) + + " close the preview window + pclose + call assert_equal(1, winnr('$')) + + call delete('Xfile1') + call delete('Xtags') + set tags& +endfunc + +" Tests for guessing the tag location +func Test_tag_guess() + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "func1\tXfoo\t/^int func1(int x)/", + \ "func2\tXfoo\t/^int func2(int y)/", + \ "func3\tXfoo\t/^func3/", + \ "func4\tXfoo\t/^func4/"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + + int FUNC1 (int x) { } + int + func2 (int y) { } + int * func3 () { } + + [CODE] + call writefile(code, 'Xfoo') + + let v:statusmsg = '' + ta func1 + call assert_match('E435:', v:statusmsg) + call assert_equal(2, line('.')) + let v:statusmsg = '' + ta func2 + call assert_match('E435:', v:statusmsg) + call assert_equal(4, line('.')) + let v:statusmsg = '' + ta func3 + call assert_match('E435:', v:statusmsg) + call assert_equal(5, line('.')) + call assert_fails('ta func4', 'E434:') + + call delete('Xtags') + call delete('Xfoo') + set tags& +endfunc + +" Test for an unsorted tags file +func Test_tag_sort() + call writefile([ + \ "first\tXfoo\t1", + \ "ten\tXfoo\t3", + \ "six\tXfoo\t2"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int six() {} + int ten() {} + [CODE] + call writefile(code, 'Xfoo') + + call assert_fails('tag first', 'E432:') + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for an unsorted tags file +func Test_tag_fold() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "!_TAG_FILE_SORTED\t2\t/0=unsorted, 1=sorted, 2=foldcase/", + \ "first\tXfoo\t1", + \ "second\tXfoo\t2", + \ "third\tXfoo\t3"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + tag second + call assert_equal('Xfoo', bufname('')) + call assert_equal(2, line('.')) + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for the :ltag command +func Test_ltag() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t1", + \ "second\tXfoo\t/^int second() {}$/", + \ "third\tXfoo\t3"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + call setloclist(0, [], 'f') + ltag third + call assert_equal('Xfoo', bufname('')) + call assert_equal(3, line('.')) + call assert_equal([{'lnum': 3, 'bufnr': bufnr('Xfoo'), 'col': 0, + \ 'pattern': '', 'valid': 1, 'vcol': 0, 'nr': 0, 'type': '', + \ 'module': '', 'text': 'third'}], getloclist(0)) + + ltag second + call assert_equal(2, line('.')) + call assert_equal([{'lnum': 0, 'bufnr': bufnr('Xfoo'), 'col': 0, + \ 'pattern': '^\Vint second() {}\$', 'valid': 1, 'vcol': 0, 'nr': 0, + \ 'type': '', 'module': '', 'text': 'second'}], getloclist(0)) + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for setting the last search pattern to the tag search pattern +" when cpoptions has 't' +func Test_tag_last_search_pat() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t/^int first() {}/", + \ "second\tXfoo\t/^int second() {}/", + \ "third\tXfoo\t/^int third() {}/"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + let save_cpo = &cpo + set cpo+=t + let @/ = '' + tag second + call assert_equal('^int second() {}', @/) + let &cpo = save_cpo + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for jumping to a tag when the tag stack is full +func Test_tag_stack_full() + let l = [] + for i in range(10, 31) + let l += ["var" .. i .. "\tXfoo\t/^int var" .. i .. ";$/"] + endfor + call writefile(l, 'Xtags') + set tags=Xtags + + let l = [] + for i in range(10, 31) + let l += ["int var" .. i .. ";"] + endfor + call writefile(l, 'Xfoo') + + enew + for i in range(10, 30) + exe "tag var" .. i + endfor + let l = gettagstack() + call assert_equal(20, l.length) + call assert_equal('var11', l.items[0].tagname) + tag var31 + let l = gettagstack() + call assert_equal('var12', l.items[0].tagname) + call assert_equal('var31', l.items[19].tagname) + + " Jump from the top of the stack + call assert_fails('tag', 'E556:') + + " Pop from an unsaved buffer + enew! + call append(1, "sample text") + call assert_fails('pop', 'E37:') + call assert_equal(21, gettagstack().curidx) + enew! + + " Pop all the entries in the tag stack + call assert_fails('30pop', 'E555:') + + " Pop the tag stack when it is empty + call settagstack(1, {'items' : []}) + call assert_fails('pop', 'E73:') + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for browsing multiple matching tags +func Test_tag_multimatch() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t1", + \ "first\tXfoo\t2", + \ "first\tXfoo\t3"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int first() {} + int first() {} + [CODE] + call writefile(code, 'Xfoo') + + tag first + tlast + call assert_equal(3, line('.')) + call assert_fails('tnext', 'E428:') + tfirst + call assert_equal(1, line('.')) + call assert_fails('tprev', 'E425:') + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + " Test for the 'taglength' option func Test_tag_length() set tags=Xtags diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim index d4ff42fd68..e830813081 100644 --- a/src/nvim/testdir/test_taglist.vim +++ b/src/nvim/testdir/test_taglist.vim @@ -126,3 +126,99 @@ func Test_tagsfile_without_trailing_newline() call delete('Xtags') set tags& endfunc + +" Test for ignoring comments in a tags file +func Test_tagfile_ignore_comments() + call writefile([ + \ "!_TAG_PROGRAM_NAME /Test tags generator/", + \ "FBar\tXfoo\t2" .. ';"' .. "\textrafield\tf", + \ "!_TAG_FILE_FORMAT 2 /extended format/", + \ ], 'Xtags') + set tags=Xtags + + let l = taglist('.*') + call assert_equal(1, len(l)) + call assert_equal('FBar', l[0].name) + + set tags& + call delete('Xtags') +endfunc + +" Test for using an excmd in a tags file to position the cursor (instead of a +" search pattern or a line number) +func Test_tagfile_excmd() + call writefile([ + \ "vFoo\tXfoo\tcall cursor(3, 4)" .. '|;"' .. "\tv", + \ ], 'Xtags') + set tags=Xtags + + let l = taglist('.*') + call assert_equal([{ + \ 'cmd' : 'call cursor(3, 4)', + \ 'static' : 0, + \ 'name' : 'vFoo', + \ 'kind' : 'v', + \ 'filename' : 'Xfoo'}], l) + + set tags& + call delete('Xtags') +endfunc + +" Test for duplicate fields in a tag in a tags file +func Test_duplicate_field() + call writefile([ + \ "vFoo\tXfoo\t4" .. ';"' .. "\ttypename:int\ttypename:int\tv", + \ ], 'Xtags') + set tags=Xtags + + let l = taglist('.*') + call assert_equal([{ + \ 'cmd' : '4', + \ 'static' : 0, + \ 'name' : 'vFoo', + \ 'kind' : 'v', + \ 'typename' : 'int', + \ 'filename' : 'Xfoo'}], l) + + set tags& + call delete('Xtags') +endfunc + +" Test for tag address with ; +func Test_tag_addr_with_semicolon() + call writefile([ + \ "Func1\tXfoo\t6;/^Func1/" .. ';"' .. "\tf" + \ ], 'Xtags') + set tags=Xtags + + let l = taglist('.*') + call assert_equal([{ + \ 'cmd' : '6;/^Func1/', + \ 'static' : 0, + \ 'name' : 'Func1', + \ 'kind' : 'f', + \ 'filename' : 'Xfoo'}], l) + + set tags& + call delete('Xtags') +endfunc + +" Test for format error in a tags file +func Test_format_error() + call writefile(['vFoo-Xfoo-4'], 'Xtags') + set tags=Xtags + + let caught_exception = v:false + try + let l = taglist('.*') + catch /E431:/ + " test succeeded + let caught_exception = v:true + catch + call assert_report('Caught ' . v:exception . ' in ' . v:throwpoint) + endtry + call assert_true(caught_exception) + + set tags& + call delete('Xtags') +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_timers.vim b/src/nvim/testdir/test_timers.vim index 13971a918d..ceaa5de92b 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -317,8 +317,8 @@ endfunc " Test that the garbage collector isn't triggered if a timer callback invokes " vgetc(). func Test_nocatch_garbage_collect() - " skipped: Nvim does not support test_garbagecollect_soon(), test_override() - return + CheckFunction test_garbagecollect_soon + CheckFunction test_override " 'uptimetime. must be bigger than the timer timeout set ut=200 call test_garbagecollect_soon() diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index 3b66071d6d..54caed3983 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -3,6 +3,8 @@ " undo-able pieces. Do that by setting 'undolevels'. " Also tests :earlier and :later. +source check.vim + func Test_undotree() new @@ -135,7 +137,7 @@ func BackOne(expected) endfunc func Test_undo_del_chars() - throw 'skipped: Nvim does not support test_settime()' + CheckFunction test_settime " Setup a buffer without creating undo entries new @@ -330,7 +332,7 @@ func Test_insert_expr() endfunc func Test_undofile_earlier() - throw 'skipped: Nvim does not support test_settime()' + CheckFunction test_settime let t0 = localtime() - 43200 call test_settime(t0) diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim new file mode 100644 index 0000000000..2fbf130345 --- /dev/null +++ b/src/nvim/testdir/test_vartabs.vim @@ -0,0 +1,381 @@ +" Test for variable tabstops + +if !has("vartabs") + finish +endif + +source view_util.vim + +func s:compare_lines(expect, actual) + call assert_equal(join(a:expect, "\n"), join(a:actual, "\n")) +endfunc + +func Test_vartabs() + new + %d + + " Test normal operation of tabstops ... + set ts=4 + call setline(1, join(split('aaaaa', '\zs'), "\t")) + retab 8 + let expect = "a a\<tab>a a\<tab>a" + call assert_equal(expect, getline(1)) + + " ... and softtabstops + set ts=8 sts=6 + exe "norm! Sb\<tab>b\<tab>b\<tab>b\<tab>b" + let expect = "b b\<tab> b\<tab> b\<tab>b" + call assert_equal(expect, getline(1)) + + " Test variable tabstops. + set sts=0 vts=4,8,4,8 + exe "norm! Sc\<tab>c\<tab>c\<tab>c\<tab>c\<tab>c" + retab 8 + let expect = "c c\<tab> c\<tab>c\<tab>c\<tab>c" + call assert_equal(expect, getline(1)) + + set et vts=4,8,4,8 + exe "norm! Sd\<tab>d\<tab>d\<tab>d\<tab>d\<tab>d" + let expect = "d d d d d d" + call assert_equal(expect, getline(1)) + + " Changing ts should have no effect if vts is in use. + call cursor(1, 1) + set ts=6 + exe "norm! Se\<tab>e\<tab>e\<tab>e\<tab>e\<tab>e" + let expect = "e e e e e e" + call assert_equal(expect, getline(1)) + + " Clearing vts should revert to using ts. + set vts= + exe "norm! Sf\<tab>f\<tab>f\<tab>f\<tab>f\<tab>f" + let expect = "f f f f f f" + call assert_equal(expect, getline(1)) + + " Test variable softtabstops. + set noet ts=8 vsts=12,2,6 + exe "norm! Sg\<tab>g\<tab>g\<tab>g\<tab>g\<tab>g" + let expect = "g\<tab> g g\<tab> g\<tab> g\<tab>g" + call assert_equal(expect, getline(1)) + + " Variable tabstops and softtabstops combined. + set vsts=6,12,8 vts=4,6,8 + exe "norm! Sh\<tab>h\<tab>h\<tab>h\<tab>h" + let expect = "h\<tab> h\<tab>\<tab>h\<tab>h\<tab>h" + call assert_equal(expect, getline(1)) + + " Retab with a single value, not using vts. + set ts=8 sts=0 vts= vsts= + exe "norm! Si\<tab>i\<tab>i\<tab>i\<tab>i" + retab 4 + let expect = "i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i" + call assert_equal(expect, getline(1)) + + " Retab with a single value, using vts. + set ts=8 sts=0 vts=6 vsts= + exe "norm! Sj\<tab>j\<tab>j\<tab>j\<tab>j" + retab 4 + let expect = "j\<tab> j\<tab>\<tab>j\<tab> j\<tab>\<tab>j" + call assert_equal(expect, getline(1)) + + " Retab with multiple values, not using vts. + set ts=6 sts=0 vts= vsts= + exe "norm! Sk\<tab>k\<tab>k\<tab>k\<tab>k\<tab>k" + retab 4,8 + let expect = "k\<tab> k\<tab>k k\<tab> k\<tab> k" + call assert_equal(expect, getline(1)) + + " Retab with multiple values, using vts. + set ts=8 sts=0 vts=6 vsts= + exe "norm! Sl\<tab>l\<tab>l\<tab>l\<tab>l\<tab>l" + retab 4,8 + let expect = "l\<tab> l\<tab>l l\<tab> l\<tab> l" + call assert_equal(expect, getline(1)) + + " Check that global and local values are set. + set ts=4 vts=6 sts=8 vsts=10 + call assert_equal(&ts, 4) + call assert_equal(&vts, '6') + call assert_equal(&sts, 8) + call assert_equal(&vsts, '10') + new + call assert_equal(&ts, 4) + call assert_equal(&vts, '6') + call assert_equal(&sts, 8) + call assert_equal(&vsts, '10') + bwipeout! + + " Check that local values only are set. + setlocal ts=5 vts=7 sts=9 vsts=11 + call assert_equal(&ts, 5) + call assert_equal(&vts, '7') + call assert_equal(&sts, 9) + call assert_equal(&vsts, '11') + new + call assert_equal(&ts, 4) + call assert_equal(&vts, '6') + call assert_equal(&sts, 8) + call assert_equal(&vsts, '10') + bwipeout! + + " Check that global values only are set. + setglobal ts=6 vts=8 sts=10 vsts=12 + call assert_equal(&ts, 5) + call assert_equal(&vts, '7') + call assert_equal(&sts, 9) + call assert_equal(&vsts, '11') + new + call assert_equal(&ts, 6) + call assert_equal(&vts, '8') + call assert_equal(&sts, 10) + call assert_equal(&vsts, '12') + bwipeout! + + set ts& vts& sts& vsts& et& + bwipeout! +endfunc + +func! Test_vartabs_breakindent() + if !exists("+breakindent") + return + endif + new + %d + + " Test normal operation of tabstops ... + set ts=4 + call setline(1, join(split('aaaaa', '\zs'), "\t")) + retab 8 + let expect = "a a\<tab>a a\<tab>a" + call assert_equal(expect, getline(1)) + + " ... and softtabstops + set ts=8 sts=6 + exe "norm! Sb\<tab>b\<tab>b\<tab>b\<tab>b" + let expect = "b b\<tab> b\<tab> b\<tab>b" + call assert_equal(expect, getline(1)) + + " Test variable tabstops. + set sts=0 vts=4,8,4,8 + exe "norm! Sc\<tab>c\<tab>c\<tab>c\<tab>c\<tab>c" + retab 8 + let expect = "c c\<tab> c\<tab>c\<tab>c\<tab>c" + call assert_equal(expect, getline(1)) + + set et vts=4,8,4,8 + exe "norm! Sd\<tab>d\<tab>d\<tab>d\<tab>d\<tab>d" + let expect = "d d d d d d" + call assert_equal(expect, getline(1)) + + " Changing ts should have no effect if vts is in use. + call cursor(1, 1) + set ts=6 + exe "norm! Se\<tab>e\<tab>e\<tab>e\<tab>e\<tab>e" + let expect = "e e e e e e" + call assert_equal(expect, getline(1)) + + " Clearing vts should revert to using ts. + set vts= + exe "norm! Sf\<tab>f\<tab>f\<tab>f\<tab>f\<tab>f" + let expect = "f f f f f f" + call assert_equal(expect, getline(1)) + + " Test variable softtabstops. + set noet ts=8 vsts=12,2,6 + exe "norm! Sg\<tab>g\<tab>g\<tab>g\<tab>g\<tab>g" + let expect = "g\<tab> g g\<tab> g\<tab> g\<tab>g" + call assert_equal(expect, getline(1)) + + " Variable tabstops and softtabstops combined. + set vsts=6,12,8 vts=4,6,8 + exe "norm! Sh\<tab>h\<tab>h\<tab>h\<tab>h" + let expect = "h\<tab> h\<tab>\<tab>h\<tab>h\<tab>h" + call assert_equal(expect, getline(1)) + + " Retab with a single value, not using vts. + set ts=8 sts=0 vts= vsts= + exe "norm! Si\<tab>i\<tab>i\<tab>i\<tab>i" + retab 4 + let expect = "i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i\<tab>\<tab>i" + call assert_equal(expect, getline(1)) + + " Retab with a single value, using vts. + set ts=8 sts=0 vts=6 vsts= + exe "norm! Sj\<tab>j\<tab>j\<tab>j\<tab>j" + retab 4 + let expect = "j\<tab> j\<tab>\<tab>j\<tab> j\<tab>\<tab>j" + call assert_equal(expect, getline(1)) + + " Retab with multiple values, not using vts. + set ts=6 sts=0 vts= vsts= + exe "norm! Sk\<tab>k\<tab>k\<tab>k\<tab>k\<tab>k" + retab 4,8 + let expect = "k\<tab> k\<tab>k k\<tab> k\<tab> k" + call assert_equal(expect, getline(1)) + + " Retab with multiple values, using vts. + set ts=8 sts=0 vts=6 vsts= + exe "norm! Sl\<tab>l\<tab>l\<tab>l\<tab>l\<tab>l" + retab 4,8 + let expect = "l\<tab> l\<tab>l l\<tab> l\<tab> l" + call assert_equal(expect, getline(1)) + + " Check that global and local values are set. + set ts=4 vts=6 sts=8 vsts=10 + call assert_equal(&ts, 4) + call assert_equal(&vts, '6') + call assert_equal(&sts, 8) + call assert_equal(&vsts, '10') + new + call assert_equal(&ts, 4) + call assert_equal(&vts, '6') + call assert_equal(&sts, 8) + call assert_equal(&vsts, '10') + bwipeout! + + " Check that local values only are set. + setlocal ts=5 vts=7 sts=9 vsts=11 + call assert_equal(&ts, 5) + call assert_equal(&vts, '7') + call assert_equal(&sts, 9) + call assert_equal(&vsts, '11') + new + call assert_equal(&ts, 4) + call assert_equal(&vts, '6') + call assert_equal(&sts, 8) + call assert_equal(&vsts, '10') + bwipeout! + + " Check that global values only are set. + setglobal ts=6 vts=8 sts=10 vsts=12 + call assert_equal(&ts, 5) + call assert_equal(&vts, '7') + call assert_equal(&sts, 9) + call assert_equal(&vsts, '11') + new + call assert_equal(&ts, 6) + call assert_equal(&vts, '8') + call assert_equal(&sts, 10) + call assert_equal(&vsts, '12') + bwipeout! + + bwipeout! +endfunc + +func Test_vartabs_linebreak() + if winwidth(0) < 40 + return + endif + new + 40vnew + %d + setl linebreak vartabstop=10,20,30,40 + call setline(1, "\tx\tx\tx\tx") + + let expect = [' x ', + \ 'x x ', + \ 'x '] + let lines = ScreenLines([1, 3], winwidth(0)) + call s:compare_lines(expect, lines) + setl list listchars=tab:>- + let expect = ['>---------x>------------------ ', + \ 'x>------------------x>------------------', + \ 'x '] + let lines = ScreenLines([1, 3], winwidth(0)) + call s:compare_lines(expect, lines) + setl linebreak vartabstop=40 + let expect = ['>---------------------------------------', + \ 'x>--------------------------------------', + \ 'x>--------------------------------------', + \ 'x>--------------------------------------', + \ 'x '] + let lines = ScreenLines([1, 5], winwidth(0)) + call s:compare_lines(expect, lines) + + " cleanup + bw! + bw! + set nolist listchars&vim +endfunc + +func Test_vartabs_shiftwidth() + "return + if winwidth(0) < 40 + return + endif + new + 40vnew + %d +" setl varsofttabstop=10,20,30,40 + setl shiftwidth=0 vartabstop=10,20,30,40 + call setline(1, "x") + + " Check without any change. + let expect = ['x '] + let lines = ScreenLines(1, winwidth(0)) + call s:compare_lines(expect, lines) + " Test 1: + " shiftwidth depends on the indent, first check with cursor at the end of the + " line (which is the same as the start of the line, since there is only one + " character). + norm! $>> + let expect1 = [' x '] + let lines = ScreenLines(1, winwidth(0)) + call s:compare_lines(expect1, lines) + call assert_equal(10, shiftwidth()) + call assert_equal(10, shiftwidth(1)) + call assert_equal(20, shiftwidth(virtcol('.'))) + norm! $>> + let expect2 = [' x ', '~ '] + let lines = ScreenLines([1, 2], winwidth(0)) + call s:compare_lines(expect2, lines) + call assert_equal(20, shiftwidth(virtcol('.')-2)) + call assert_equal(30, shiftwidth(virtcol('.'))) + norm! $>> + let expect3 = [' ', ' x ', '~ '] + let lines = ScreenLines([1, 3], winwidth(0)) + call s:compare_lines(expect3, lines) + call assert_equal(30, shiftwidth(virtcol('.')-2)) + call assert_equal(40, shiftwidth(virtcol('.'))) + norm! $>> + let expect4 = [' ', ' ', ' x '] + let lines = ScreenLines([1, 3], winwidth(0)) + call assert_equal(40, shiftwidth(virtcol('.'))) + call s:compare_lines(expect4, lines) + + " Test 2: Put the cursor at the first column, result should be the same + call setline(1, "x") + norm! 0>> + let lines = ScreenLines(1, winwidth(0)) + call s:compare_lines(expect1, lines) + norm! 0>> + let lines = ScreenLines([1, 2], winwidth(0)) + call s:compare_lines(expect2, lines) + norm! 0>> + let lines = ScreenLines([1, 3], winwidth(0)) + call s:compare_lines(expect3, lines) + norm! 0>> + let lines = ScreenLines([1, 3], winwidth(0)) + call s:compare_lines(expect4, lines) + + " cleanup + bw! + bw! +endfunc + +func Test_vartabs_failures() + call assert_fails('set vts=8,') + call assert_fails('set vsts=8,') + call assert_fails('set vts=8,,8') + call assert_fails('set vsts=8,,8') + call assert_fails('set vts=8,,8,') + call assert_fails('set vsts=8,,8,') + call assert_fails('set vts=,8') + call assert_fails('set vsts=,8') +endfunc + +func Test_vartabs_reset() + set vts=8 + set all& + call assert_equal('', &vts) +endfunc diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 7f50894f66..73c7960579 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -255,7 +255,6 @@ func TriggerTheProblem() endfunc func Test_visual_mode_reset() - set belloff=all enew let g:msg = "Everything's fine." enew @@ -268,7 +267,6 @@ func Test_visual_mode_reset() exe "normal! GV:call TriggerTheProblem()\<CR>" call assert_equal("Everything's fine.", g:msg) - set belloff& endfunc func Test_Visual_word_textobject() diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 969b75d424..a522705238 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -550,16 +550,29 @@ endfunc func Test_winrestcmd() 2split 3vsplit - let a = winrestcmd() + let restcmd = winrestcmd() call assert_equal(2, winheight(0)) call assert_equal(3, winwidth(0)) wincmd = call assert_notequal(2, winheight(0)) call assert_notequal(3, winwidth(0)) - exe a + exe restcmd call assert_equal(2, winheight(0)) call assert_equal(3, winwidth(0)) only + + wincmd v + wincmd s + wincmd v + redraw + let restcmd = winrestcmd() + wincmd _ + wincmd | + exe restcmd + redraw + call assert_equal(restcmd, winrestcmd()) + + only endfunc function! Fun_RenewFile() @@ -808,6 +821,55 @@ func Test_winnr() only | tabonly endfunc +func Test_win_splitmove() + edit a + leftabove split b + leftabove vsplit c + leftabove split d + call assert_equal(0, win_splitmove(winnr(), winnr('l'))) + call assert_equal(bufname(winbufnr(1)), 'c') + call assert_equal(bufname(winbufnr(2)), 'd') + call assert_equal(bufname(winbufnr(3)), 'b') + call assert_equal(bufname(winbufnr(4)), 'a') + call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'vertical': 1})) + call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'vertical': 1})) + call assert_equal(bufname(winbufnr(1)), 'c') + call assert_equal(bufname(winbufnr(2)), 'b') + call assert_equal(bufname(winbufnr(3)), 'd') + call assert_equal(bufname(winbufnr(4)), 'a') + call assert_equal(0, win_splitmove(winnr(), winnr('k'), {'vertical': 1})) + call assert_equal(bufname(winbufnr(1)), 'd') + call assert_equal(bufname(winbufnr(2)), 'c') + call assert_equal(bufname(winbufnr(3)), 'b') + call assert_equal(bufname(winbufnr(4)), 'a') + call assert_equal(0, win_splitmove(winnr(), winnr('j'), {'rightbelow': v:true})) + call assert_equal(bufname(winbufnr(1)), 'c') + call assert_equal(bufname(winbufnr(2)), 'b') + call assert_equal(bufname(winbufnr(3)), 'a') + call assert_equal(bufname(winbufnr(4)), 'd') + only | bd + + call assert_fails('call win_splitmove(winnr(), 123)', 'E957:') + call assert_fails('call win_splitmove(123, winnr())', 'E957:') + call assert_fails('call win_splitmove(winnr(), winnr())', 'E957:') + + tabnew + call assert_fails('call win_splitmove(1, win_getid(1, 1))', 'E957:') + tabclose +endfunc + +func Test_floatwin_splitmove() + vsplit + let win2 = win_getid() + let popup_winid = nvim_open_win(0, 0, {'relative': 'win', + \ 'row': 3, 'col': 3, 'width': 12, 'height': 3}) + call assert_fails('call win_splitmove(popup_winid, win2)', 'E957:') + call assert_fails('call win_splitmove(win2, popup_winid)', 'E957:') + + call nvim_win_close(popup_winid, 1) + bwipe +endfunc + func Test_window_resize() " Vertical :resize (absolute, relative, min and max size). vsplit diff --git a/src/nvim/ui.c b/src/nvim/ui.c index c6c09c80d7..94b6e9e39d 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -110,6 +110,7 @@ static char uilog_last_event[1024] = { 0 }; void ui_init(void) { default_grid.handle = 1; + msg_grid_adj.target = &default_grid; ui_comp_init(); } diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 06efc9fa99..a2e9266fbb 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -127,6 +127,9 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, bool valid, bool on_top) { bool moved; + + grid->comp_height = height; + grid->comp_width = width; if (grid->comp_index != 0) { moved = (row != grid->comp_row) || (col != grid->comp_col); if (ui_comp_should_draw()) { @@ -181,14 +184,12 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, insert_at--; } // not found: new grid - kv_push(layers, grid); - if (insert_at < kv_size(layers)-1) { - for (size_t i = kv_size(layers)-1; i > insert_at; i--) { - kv_A(layers, i) = kv_A(layers, i-1); - kv_A(layers, i)->comp_index = i; - } - kv_A(layers, insert_at) = grid; + kv_pushp(layers); + for (size_t i = kv_size(layers)-1; i > insert_at; i--) { + kv_A(layers, i) = kv_A(layers, i-1); + kv_A(layers, i)->comp_index = i; } + kv_A(layers, insert_at) = grid; grid->comp_row = row; grid->comp_col = col; @@ -277,6 +278,9 @@ static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, // should configure all grids before entering win_update() if (curgrid != &default_grid) { size_t new_index = kv_size(layers)-1; + if (kv_A(layers, new_index) == &msg_grid) { + new_index--; + } if (kv_A(layers, new_index) == &pum_grid) { new_index--; } @@ -334,17 +338,25 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, sattr_T *bg_attrs = &default_grid.attrs[default_grid.line_offset[row] +(size_t)startcol]; + int grid_width, grid_height; while (col < endcol) { int until = 0; for (size_t i = 0; i < kv_size(layers); i++) { ScreenGrid *g = kv_A(layers, i); - if (g->comp_row > row || row >= g->comp_row + g->Rows + // compose_line may have been called after a shrinking operation but + // before the resize has actually been applied. Therefore, we need to + // first check to see if any grids have pending updates to width/height, + // to ensure that we don't accidentally put any characters into `linebuf` + // that have been invalidated. + grid_width = MIN(g->Columns, g->comp_width); + grid_height = MIN(g->Rows, g->comp_height); + if (g->comp_row > row || row >= g->comp_row + grid_height || g->comp_disabled) { continue; } - if (g->comp_col <= col && col < g->comp_col+g->Columns) { + if (g->comp_col <= col && col < g->comp_col + grid_width) { grid = g; - until = g->comp_col+g->Columns; + until = g->comp_col + grid_width; } else if (g->comp_col > col) { until = MIN(until, g->comp_col); } diff --git a/src/nvim/window.c b/src/nvim/window.c index 0f717a2f90..859f4353b3 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -605,6 +605,7 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) wp->w_vsep_width = 0; win_config_float(wp, fconfig); + win_set_inner_size(wp); wp->w_pos_changed = true; redraw_later(wp, VALID); return wp; @@ -667,6 +668,8 @@ 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; + wp->w_float_config = fconfig; if (!ui_has(kUIMultigrid)) { @@ -676,11 +679,18 @@ void win_config_float(win_T *wp, FloatConfig fconfig) win_set_inner_size(wp); must_redraw = MAX(must_redraw, VALID); + wp->w_pos_changed = true; - if (change_external) { + if (change_external || change_border) { wp->w_hl_needs_update = true; redraw_later(wp, NOT_VALID); } + + // changing border style while keeping border only requires redrawing border + if (fconfig.border) { + wp->w_redr_border = true; + redraw_later(wp, VALID); + } } void win_check_anchored_floats(win_T *win) @@ -710,10 +720,10 @@ int win_fdccol_count(win_T *wp) } -static void ui_ext_win_position(win_T *wp) +void ui_ext_win_position(win_T *wp) { if (!wp->w_floating) { - ui_call_win_pos(wp->w_grid.handle, wp->handle, wp->w_winrow, + ui_call_win_pos(wp->w_grid_alloc.handle, wp->handle, wp->w_winrow, wp->w_wincol, wp->w_width, wp->w_height); return; } @@ -743,8 +753,8 @@ static void ui_ext_win_position(win_T *wp) } if (ui_has(kUIMultigrid)) { String anchor = cstr_to_string(float_anchor_str[c.anchor]); - ui_call_win_float_pos(wp->w_grid.handle, wp->handle, anchor, grid->handle, - row, col, c.focusable); + ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor, + grid->handle, row, col, c.focusable); } else { // TODO(bfredl): ideally, compositor should work like any multigrid UI // and use standard win_pos events. @@ -759,17 +769,17 @@ static void ui_ext_win_position(win_T *wp) wp->w_wincol = comp_col; bool valid = (wp->w_redr_type == 0); bool on_top = (curwin == wp) || !curwin->w_floating; - ui_comp_put_grid(&wp->w_grid, comp_row, comp_col, wp->w_height, - wp->w_width, valid, on_top); - ui_check_cursor_grid(wp->w_grid.handle); - wp->w_grid.focusable = wp->w_float_config.focusable; + ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col, + wp->w_height_outer, wp->w_width_outer, valid, on_top); + ui_check_cursor_grid(wp->w_grid_alloc.handle); + wp->w_grid_alloc.focusable = wp->w_float_config.focusable; if (!valid) { - wp->w_grid.valid = false; + wp->w_grid_alloc.valid = false; redraw_later(wp, NOT_VALID); } } } else { - ui_call_win_external_pos(wp->w_grid.handle, wp->handle); + ui_call_win_external_pos(wp->w_grid_alloc.handle, wp->handle); } } @@ -784,260 +794,12 @@ void ui_ext_win_viewport(win_T *wp) // interact with incomplete final line? Diff filler lines? botline = wp->w_buffer->b_ml.ml_line_count; } - ui_call_win_viewport(wp->w_grid.handle, wp->handle, wp->w_topline-1, + ui_call_win_viewport(wp->w_grid_alloc.handle, wp->handle, wp->w_topline-1, botline, wp->w_cursor.lnum-1, wp->w_cursor.col); wp->w_viewport_invalid = false; } } -static bool parse_float_anchor(String anchor, FloatAnchor *out) -{ - if (anchor.size == 0) { - *out = (FloatAnchor)0; - } - char *str = anchor.data; - if (striequal(str, "NW")) { - *out = 0; // NW is the default - } else if (striequal(str, "NE")) { - *out = kFloatAnchorEast; - } else if (striequal(str, "SW")) { - *out = kFloatAnchorSouth; - } else if (striequal(str, "SE")) { - *out = kFloatAnchorSouth | kFloatAnchorEast; - } else { - return false; - } - return true; -} - -static bool parse_float_relative(String relative, FloatRelative *out) -{ - char *str = relative.data; - if (striequal(str, "editor")) { - *out = kFloatRelativeEditor; - } else if (striequal(str, "win")) { - *out = kFloatRelativeWindow; - } else if (striequal(str, "cursor")) { - *out = kFloatRelativeCursor; - } else { - return false; - } - return true; -} - -static bool parse_float_bufpos(Array bufpos, lpos_T *out) -{ - if (bufpos.size != 2 - || bufpos.items[0].type != kObjectTypeInteger - || bufpos.items[1].type != kObjectTypeInteger) { - return false; - } - out->lnum = bufpos.items[0].data.integer; - out->col = bufpos.items[1].data.integer; - return true; -} - -bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, - Error *err) -{ - // TODO(bfredl): use a get/has_key interface instead and get rid of extra - // flags - bool has_row = false, has_col = false, has_relative = false; - bool has_external = false, has_window = false; - bool has_width = false, has_height = false; - bool has_bufpos = false; - - for (size_t i = 0; i < config.size; i++) { - char *key = config.items[i].key.data; - Object val = config.items[i].value; - if (!strcmp(key, "row")) { - has_row = true; - if (val.type == kObjectTypeInteger) { - fconfig->row = val.data.integer; - } else if (val.type == kObjectTypeFloat) { - fconfig->row = val.data.floating; - } else { - api_set_error(err, kErrorTypeValidation, - "'row' key must be Integer or Float"); - return false; - } - } else if (!strcmp(key, "col")) { - has_col = true; - if (val.type == kObjectTypeInteger) { - fconfig->col = val.data.integer; - } else if (val.type == kObjectTypeFloat) { - fconfig->col = val.data.floating; - } else { - api_set_error(err, kErrorTypeValidation, - "'col' key must be Integer or Float"); - return false; - } - } else if (strequal(key, "width")) { - has_width = true; - if (val.type == kObjectTypeInteger && val.data.integer > 0) { - fconfig->width = val.data.integer; - } else { - api_set_error(err, kErrorTypeValidation, - "'width' key must be a positive Integer"); - return false; - } - } else if (strequal(key, "height")) { - has_height = true; - if (val.type == kObjectTypeInteger && val.data.integer > 0) { - fconfig->height= val.data.integer; - } else { - api_set_error(err, kErrorTypeValidation, - "'height' key must be a positive Integer"); - return false; - } - } else if (!strcmp(key, "anchor")) { - if (val.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "'anchor' key must be String"); - return false; - } - if (!parse_float_anchor(val.data.string, &fconfig->anchor)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'anchor' key"); - return false; - } - } else if (!strcmp(key, "relative")) { - if (val.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "'relative' key must be String"); - return false; - } - // ignore empty string, to match nvim_win_get_config - if (val.data.string.size > 0) { - has_relative = true; - if (!parse_float_relative(val.data.string, &fconfig->relative)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'relative' key"); - return false; - } - } - } else if (!strcmp(key, "win")) { - has_window = true; - if (val.type != kObjectTypeInteger - && val.type != kObjectTypeWindow) { - api_set_error(err, kErrorTypeValidation, - "'win' key must be Integer or Window"); - return false; - } - fconfig->window = val.data.integer; - } else if (!strcmp(key, "bufpos")) { - if (val.type != kObjectTypeArray) { - api_set_error(err, kErrorTypeValidation, - "'bufpos' key must be Array"); - return false; - } - if (!parse_float_bufpos(val.data.array, &fconfig->bufpos)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'bufpos' key"); - return false; - } - has_bufpos = true; - } else if (!strcmp(key, "external")) { - if (val.type == kObjectTypeInteger) { - fconfig->external = val.data.integer; - } else if (val.type == kObjectTypeBoolean) { - fconfig->external = val.data.boolean; - } else { - api_set_error(err, kErrorTypeValidation, - "'external' key must be Boolean"); - return false; - } - has_external = fconfig->external; - } else if (!strcmp(key, "focusable")) { - if (val.type == kObjectTypeInteger) { - fconfig->focusable = val.data.integer; - } else if (val.type == kObjectTypeBoolean) { - fconfig->focusable = val.data.boolean; - } else { - api_set_error(err, kErrorTypeValidation, - "'focusable' key must be Boolean"); - return false; - } - } else if (!strcmp(key, "style")) { - if (val.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, - "'style' key must be String"); - return false; - } - if (val.data.string.data[0] == NUL) { - fconfig->style = kWinStyleUnused; - } else if (striequal(val.data.string.data, "minimal")) { - fconfig->style = kWinStyleMinimal; - } else { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'style' key"); - } - } else { - api_set_error(err, kErrorTypeValidation, - "Invalid key '%s'", key); - return false; - } - } - - if (has_window && !(has_relative - && fconfig->relative == kFloatRelativeWindow)) { - api_set_error(err, kErrorTypeValidation, - "'win' key is only valid with relative='win'"); - return false; - } - - if ((has_relative && fconfig->relative == kFloatRelativeWindow) - && (!has_window || fconfig->window == 0)) { - fconfig->window = curwin->handle; - } - - if (has_window && !has_bufpos) { - fconfig->bufpos.lnum = -1; - } - - if (has_bufpos) { - if (!has_row) { - fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1; - has_row = true; - } - if (!has_col) { - fconfig->col = 0; - has_col = true; - } - } - - if (has_relative && has_external) { - api_set_error(err, kErrorTypeValidation, - "Only one of 'relative' and 'external' must be used"); - return false; - } else if (!reconf && !has_relative && !has_external) { - api_set_error(err, kErrorTypeValidation, - "One of 'relative' and 'external' must be used"); - return false; - } else if (has_relative) { - fconfig->external = false; - } - - if (!reconf && !(has_height && has_width)) { - api_set_error(err, kErrorTypeValidation, - "Must specify 'width' and 'height'"); - return false; - } - - if (fconfig->external && !ui_has(kUIMultigrid)) { - api_set_error(err, kErrorTypeValidation, - "UI doesn't support external windows"); - return false; - } - - if (has_relative != has_row || has_row != has_col) { - api_set_error(err, kErrorTypeValidation, - "'relative' requires 'row'/'col' or 'bufpos'"); - return false; - } - return true; -} - /* * split the current window, implements CTRL-W s and :split * @@ -1539,6 +1301,10 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) p_wh = i; } + if (!win_valid(oldwin)) { + return FAIL; + } + // Send the window positions to the UI oldwin->w_pos_changed = true; @@ -1619,6 +1385,23 @@ static void win_init_some(win_T *newp, win_T *oldp) win_copy_options(oldp, newp); } +/// Return TRUE if "win" is floating window in the current tab page. +/// +/// @param win window to check +bool win_valid_floating(const win_T *win) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (win == NULL) { + return false; + } + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp == win) { + return wp->w_floating; + } + } + return false; +} /// Check if "win" is a pointer to an existing window in the current tabpage. /// @@ -1936,12 +1719,12 @@ static void win_totop(int size, int flags) } if (curwin->w_floating) { - ui_comp_remove_grid(&curwin->w_grid); + ui_comp_remove_grid(&curwin->w_grid_alloc); if (ui_has(kUIMultigrid)) { curwin->w_pos_changed = true; } else { // No longer a float, a non-multigrid UI shouldn't draw it as such - ui_call_win_hide(curwin->w_grid.handle); + ui_call_win_hide(curwin->w_grid_alloc.handle); win_free_grid(curwin, false); } } else { @@ -2564,11 +2347,11 @@ int win_close(win_T *win, bool free_buf) bool was_floating = win->w_floating; if (ui_has(kUIMultigrid)) { - ui_call_win_close(win->w_grid.handle); + ui_call_win_close(win->w_grid_alloc.handle); } if (win->w_floating) { - ui_comp_remove_grid(&win->w_grid); + ui_comp_remove_grid(&win->w_grid_alloc); if (win->w_float_config.external) { for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) { if (tp == curtab) { @@ -3746,9 +3529,11 @@ void win_init_size(void) { firstwin->w_height = ROWS_AVAIL; firstwin->w_height_inner = firstwin->w_height; + firstwin->w_height_outer = firstwin->w_height; topframe->fr_height = ROWS_AVAIL; firstwin->w_width = Columns; firstwin->w_width_inner = firstwin->w_width; + firstwin->w_width_outer = firstwin->w_width; topframe->fr_width = Columns; } @@ -4114,7 +3899,7 @@ static void tabpage_check_windows(tabpage_T *old_curtab) win_remove(wp, old_curtab); win_append(lastwin_nofloating(), wp); } else { - ui_comp_remove_grid(&wp->w_grid); + ui_comp_remove_grid(&wp->w_grid_alloc); } } wp->w_pos_changed = true; @@ -4709,7 +4494,7 @@ static win_T *win_alloc(win_T *after, int hidden) new_wp->handle = ++last_win_id; handle_register_window(new_wp); - grid_assign_handle(&new_wp->w_grid); + grid_assign_handle(&new_wp->w_grid_alloc); // Init w: variables. new_wp->w_vars = tv_dict_alloc(); @@ -4833,15 +4618,14 @@ win_free ( void win_free_grid(win_T *wp, bool reinit) { - if (wp->w_grid.handle != 0 && ui_has(kUIMultigrid)) { - ui_call_grid_destroy(wp->w_grid.handle); - wp->w_grid.handle = 0; + if (wp->w_grid_alloc.handle != 0 && ui_has(kUIMultigrid)) { + ui_call_grid_destroy(wp->w_grid_alloc.handle); } - grid_free(&wp->w_grid); + grid_free(&wp->w_grid_alloc); if (reinit) { // if a float is turned into a split and back into a float, the grid // data structure will be reused - memset(&wp->w_grid, 0, sizeof(wp->w_grid)); + memset(&wp->w_grid_alloc, 0, sizeof(wp->w_grid_alloc)); } } @@ -5501,8 +5285,8 @@ void win_setminheight(void) // loop until there is a 'winminheight' that is possible while (p_wmh > 0) { - const int room = Rows - p_ch - tabline_height(); - const int needed = frame_minheight(topframe, NULL); + const int room = Rows - p_ch; + const int needed = min_rows() - 1; // 1 was added for the cmdline if (room >= needed) { break; } @@ -5946,6 +5730,10 @@ void win_set_inner_size(win_T *wp) if (wp->w_buffer->terminal) { 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; } /// Set the width of a window. @@ -7082,11 +6870,11 @@ void get_framelayout(const frame_T *fr, list_T *l, bool outer) void win_ui_flush(void) { FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_pos_changed && wp->w_grid.chars != NULL) { + if (wp->w_pos_changed && wp->w_grid_alloc.chars != NULL) { if (tp == curtab) { ui_ext_win_position(wp); } else { - ui_call_win_hide(wp->w_grid.handle); + ui_call_win_hide(wp->w_grid_alloc.handle); } wp->w_pos_changed = false; } |