aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/CMakeLists.txt2
-rw-r--r--src/nvim/api/buffer.c14
-rw-r--r--src/nvim/api/private/helpers.c53
-rw-r--r--src/nvim/api/ui_events.in.h3
-rw-r--r--src/nvim/api/vim.c15
-rw-r--r--src/nvim/api/window.c4
-rw-r--r--src/nvim/buffer.c86
-rw-r--r--src/nvim/buffer_defs.h2
-rw-r--r--src/nvim/channel.c1
-rw-r--r--src/nvim/edit.c11
-rw-r--r--src/nvim/eval.c9
-rw-r--r--src/nvim/eval.h1
-rw-r--r--src/nvim/eval.lua4
-rw-r--r--src/nvim/eval/funcs.c97
-rw-r--r--src/nvim/eval/typval.c13
-rw-r--r--src/nvim/ex_cmds.c58
-rw-r--r--src/nvim/ex_cmds2.c21
-rw-r--r--src/nvim/ex_docmd.c17
-rw-r--r--src/nvim/ex_getln.c11
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/grid_defs.h14
-rw-r--r--src/nvim/highlight.c38
-rw-r--r--src/nvim/lua/executor.c112
-rw-r--r--src/nvim/lua/vim.lua42
-rw-r--r--src/nvim/macros.h1
-rw-r--r--src/nvim/message.c1
-rw-r--r--src/nvim/ops.c53
-rw-r--r--src/nvim/ops.h1
-rw-r--r--src/nvim/option.c25
-rw-r--r--src/nvim/popupmnu.c6
-rw-r--r--src/nvim/search.c376
-rw-r--r--src/nvim/search.h16
-rw-r--r--src/nvim/spell.c1
-rw-r--r--src/nvim/tag.c3
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_autocmd.vim51
-rw-r--r--src/nvim/testdir/test_cmdline.vim130
-rw-r--r--src/nvim/testdir/test_ex_mode.vim82
-rw-r--r--src/nvim/testdir/test_excmd.vim126
-rw-r--r--src/nvim/testdir/test_execute_func.vim53
-rw-r--r--src/nvim/testdir/test_filetype.vim2
-rw-r--r--src/nvim/testdir/test_fnameescape.vim6
-rw-r--r--src/nvim/testdir/test_ga.vim9
-rw-r--r--src/nvim/testdir/test_global.vim8
-rw-r--r--src/nvim/testdir/test_move.vim5
-rw-r--r--src/nvim/testdir/test_options.vim19
-rw-r--r--src/nvim/testdir/test_search_stat.vim81
-rw-r--r--src/nvim/testdir/test_sort.vim210
-rw-r--r--src/nvim/testdir/test_statusline.vim20
-rw-r--r--src/nvim/testdir/test_substitute.vim2
-rw-r--r--src/nvim/testdir/test_winbuf_close.vim14
-rw-r--r--src/nvim/testdir/test_writefile.vim63
-rw-r--r--src/nvim/tui/tui.c4
-rw-r--r--src/nvim/ui_compositor.c22
-rw-r--r--src/nvim/window.c37
55 files changed, 1747 insertions, 311 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 8b422b3abe..e4d7115654 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -505,7 +505,7 @@ if(WIN32)
set(EXTERNAL_BLOBS_SCRIPT
"file(MAKE_DIRECTORY \"${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms\")")
foreach(DEP_FILE IN ITEMS
- ca-bundle.crt
+ curl-ca-bundle.crt
curl.exe
diff.exe
tee.exe
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 915b99486d..11a4647d1c 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -27,6 +27,7 @@
#include "nvim/map_defs.h"
#include "nvim/map.h"
#include "nvim/mark.h"
+#include "nvim/ops.h"
#include "nvim/extmark.h"
#include "nvim/decoration.h"
#include "nvim/fileio.h"
@@ -441,6 +442,8 @@ void nvim_buf_set_lines(uint64_t channel_id,
goto end;
}
+ bcount_t deleted_bytes = get_region_bytecount(curbuf, start, end, 0, 0);
+
// If the size of the range is reducing (ie, new_len < old_len) we
// need to delete some old_len. We do this at the start, by
// repeatedly deleting line "start".
@@ -460,6 +463,7 @@ void nvim_buf_set_lines(uint64_t channel_id,
// new old_len. This is a more efficient operation, as it requires
// less memory allocation and freeing.
size_t to_replace = old_len < new_len ? old_len : new_len;
+ bcount_t inserted_bytes = 0;
for (size_t i = 0; i < to_replace; i++) {
int64_t lnum = start + (int64_t)i;
@@ -472,6 +476,8 @@ void nvim_buf_set_lines(uint64_t channel_id,
api_set_error(err, kErrorTypeException, "Failed to replace line");
goto end;
}
+
+ inserted_bytes += (bcount_t)strlen(lines[i]) + 1;
// Mark lines that haven't been passed to the buffer as they need
// to be freed later
lines[i] = NULL;
@@ -491,6 +497,8 @@ void nvim_buf_set_lines(uint64_t channel_id,
goto end;
}
+ inserted_bytes += (bcount_t)strlen(lines[i]) + 1;
+
// Same as with replacing, but we also need to free lines
xfree(lines[i]);
lines[i] = NULL;
@@ -505,7 +513,11 @@ void nvim_buf_set_lines(uint64_t channel_id,
(linenr_T)(end - 1),
MAXLNUM,
(long)extra,
- kExtmarkUndo);
+ kExtmarkNOOP);
+
+ extmark_splice(curbuf, (int)start-1, 0, (int)(end-start), 0,
+ deleted_bytes, (int)new_len, 0, inserted_bytes,
+ kExtmarkUndo);
changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 24ba6110c4..5abdc33709 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -177,42 +177,47 @@ Object dict_get_value(dict_T *dict, String key, Error *err)
return vim_to_object(&di->di_tv);
}
-/// Set a value in a scope dict. Objects are recursively expanded into their
-/// vimscript equivalents.
-///
-/// @param dict The vimscript dict
-/// @param key The key
-/// @param value The new value
-/// @param del Delete key in place of setting it. Argument `value` is ignored in
-/// this case.
-/// @param retval If true the old value will be converted and returned.
-/// @param[out] err Details of an error that may have occurred
-/// @return The old value if `retval` is true and the key was present, else NIL
-Object dict_set_var(dict_T *dict, String key, Object value, bool del,
- bool retval, Error *err)
+dictitem_T *dict_check_writable(dict_T *dict, String key, bool del, Error *err)
{
- Object rv = OBJECT_INIT;
dictitem_T *di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size);
if (di != NULL) {
if (di->di_flags & DI_FLAGS_RO) {
api_set_error(err, kErrorTypeException, "Key is read-only: %s", key.data);
- return rv;
} else if (di->di_flags & DI_FLAGS_LOCK) {
api_set_error(err, kErrorTypeException, "Key is locked: %s", key.data);
- return rv;
} else if (del && (di->di_flags & DI_FLAGS_FIX)) {
api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data);
- return rv;
}
} else if (dict->dv_lock) {
api_set_error(err, kErrorTypeException, "Dictionary is locked");
- return rv;
} else if (key.size == 0) {
api_set_error(err, kErrorTypeValidation, "Key name is empty");
- return rv;
} else if (key.size > INT_MAX) {
api_set_error(err, kErrorTypeValidation, "Key name is too long");
+ }
+
+ return di;
+}
+
+/// Set a value in a scope dict. Objects are recursively expanded into their
+/// vimscript equivalents.
+///
+/// @param dict The vimscript dict
+/// @param key The key
+/// @param value The new value
+/// @param del Delete key in place of setting it. Argument `value` is ignored in
+/// this case.
+/// @param retval If true the old value will be converted and returned.
+/// @param[out] err Details of an error that may have occurred
+/// @return The old value if `retval` is true and the key was present, else NIL
+Object dict_set_var(dict_T *dict, String key, Object value, bool del,
+ bool retval, Error *err)
+{
+ Object rv = OBJECT_INIT;
+ dictitem_T *di = dict_check_writable(dict, key, del, err);
+
+ if (ERROR_SET(err)) {
return rv;
}
@@ -1909,7 +1914,7 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
} else if (strequal(key, "height")) {
has_height = true;
if (val.type == kObjectTypeInteger && val.data.integer > 0) {
- fconfig->height= (int)val.data.integer;
+ fconfig->height = (int)val.data.integer;
} else {
api_set_error(err, kErrorTypeValidation,
"'height' key must be a positive Integer");
@@ -1983,6 +1988,14 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
"'focusable' key must be Boolean");
return false;
}
+ } else if (strequal(key, "zindex")) {
+ if (val.type == kObjectTypeInteger && val.data.integer > 0) {
+ fconfig->zindex = (int)val.data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "'zindex' key must be a positive Integer");
+ return false;
+ }
} else if (!strcmp(key, "border")) {
parse_border_style(val, fconfig, err);
if (ERROR_SET(err)) {
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index e934d5dc92..11e21a88ea 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -106,7 +106,8 @@ void win_pos(Integer grid, Window win, Integer startrow,
Integer startcol, Integer width, Integer height)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void win_float_pos(Integer grid, Window win, String anchor, Integer anchor_grid,
- Float anchor_row, Float anchor_col, Boolean focusable)
+ Float anchor_row, Float anchor_col, Boolean focusable,
+ Integer zindex)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void win_external_pos(Integer grid, Window win)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index c363c77afb..e9a0b0df2e 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -221,6 +221,12 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
/// in addition the following keys are also recognized:
/// `default`: don't override existing definition,
/// like `hi default`
+/// `ctermfg`: sets foreground of cterm color
+/// `ctermbg`: sets background of cterm color
+/// `cterm` : cterm attribute map. sets attributed for
+/// cterm colors. similer to `hi cterm`
+/// Note: by default cterm attributes are
+/// same as attributes of gui color
/// @param[out] err Error details, if any
///
/// TODO: ns_id = 0, should modify :highlight namespace
@@ -1411,6 +1417,15 @@ void nvim_chan_send(Integer chan, String data, Error *err)
/// - `external`: GUI should display the window as an external
/// top-level window. Currently accepts no other positioning
/// configuration together with this.
+/// - `zindex`: Stacking order. floats with higher `zindex` go on top on
+/// floats with lower indices. Must be larger than zero. The
+/// following screen elements have hard-coded z-indices:
+/// - 100: insert completion popupmenu
+/// - 200: message scrollback
+/// - 250: cmdline completion popupmenu (when wildoptions+=pum)
+/// The default value for floats are 50. In general, values below 100 are
+/// recommended, unless there is a good reason to overshadow builtin
+/// elements.
/// - `style`: Configure the appearance of the window. Currently only takes
/// one non-empty value:
/// - "minimal" Nvim will display the window with many UI options
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index f942d6b19f..158e149628 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -54,7 +54,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err)
return;
}
- if (switch_win(&save_curwin, &save_curtab, win, tab, false) == FAIL) {
+ if (switch_win_noblock(&save_curwin, &save_curtab, win, tab, false) == FAIL) {
api_set_error(err,
kErrorTypeException,
"Failed to switch to window %d",
@@ -74,7 +74,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err)
// So do it now.
validate_cursor();
- restore_win(save_curwin, save_curtab, false);
+ restore_win_noblock(save_curwin, save_curtab, false);
}
/// Gets the (1,0)-indexed cursor position in the window. |api-indexing|
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index ce4163fccf..6a50264e0f 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -85,6 +85,9 @@
# include "buffer.c.generated.h"
#endif
+// Determines how deeply nested %{} blocks will be evaluated in statusline.
+#define MAX_STL_EVAL_DEPTH 100
+
static char *msg_loclist = N_("[Location List]");
static char *msg_qflist = N_("[Quickfix List]");
static char *e_auabort = N_("E855: Autocommands caused command to abort");
@@ -407,7 +410,8 @@ bool buf_valid(buf_T *buf)
/// there to be only one window with this buffer. e.g. when
/// ":quit" is supposed to close the window but autocommands
/// close all other windows.
-void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
+/// @returns true when we got to the end and b_nwindows was decremented.
+bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
{
bool unload_buf = (action != 0);
bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
@@ -444,7 +448,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
// halfway a command that relies on it). Unloading is allowed.
if (buf->b_locked > 0 && (del_buf || wipe_buf)) {
EMSG(_("E937: Attempt to delete a buffer that is in use"));
- return;
+ return false;
}
if (win != NULL // Avoid bogus clang warning.
@@ -471,13 +475,13 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
buf) && !bufref_valid(&bufref)) {
// Autocommands deleted the buffer.
EMSG(_(e_auabort));
- return;
+ return false;
}
buf->b_locked--;
if (abort_if_last && last_nonfloat(win)) {
// Autocommands made this the only window.
EMSG(_(e_auabort));
- return;
+ return false;
}
// When the buffer becomes hidden, but is not unloaded, trigger
@@ -488,17 +492,17 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
buf) && !bufref_valid(&bufref)) {
// Autocommands deleted the buffer.
EMSG(_(e_auabort));
- return;
+ return false;
}
buf->b_locked--;
if (abort_if_last && last_nonfloat(win)) {
// Autocommands made this the only window.
EMSG(_(e_auabort));
- return;
+ return false;
}
}
if (aborting()) { // autocmds may abort script processing
- return;
+ return false;
}
}
@@ -525,7 +529,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
/* Return when a window is displaying the buffer or when it's not
* unloaded. */
if (buf->b_nwindows > 0 || !unload_buf) {
- return;
+ return false;
}
if (buf->terminal) {
@@ -561,11 +565,11 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
if (!bufref_valid(&bufref)) {
// Autocommands may have deleted the buffer.
- return;
+ return false;
}
if (aborting()) {
// Autocmds may abort script processing.
- return;
+ return false;
}
/*
@@ -576,7 +580,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
* deleted buffer.
*/
if (buf == curbuf && !is_curbuf) {
- return;
+ return false;
}
if (win != NULL // Avoid bogus clang warning.
@@ -636,6 +640,8 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
buf->b_p_bl = false;
}
}
+ // NOTE: at this point "curbuf" may be invalid!
+ return true;
}
/// Make buffer not contain a file.
@@ -3569,6 +3575,7 @@ int build_stl_str_hl(
}
int groupdepth = 0;
+ int evaldepth = 0;
int curitem = 0;
bool prevchar_isflag = true;
@@ -3906,6 +3913,13 @@ int build_stl_str_hl(
continue;
}
+ // Denotes end of expanded %{} block
+ if (*fmt_p == '}' && evaldepth > 0) {
+ fmt_p++;
+ evaldepth--;
+ continue;
+ }
+
// An invalid item was specified.
// Continue processing on the next character of the format string.
if (vim_strchr(STL_ALL, *fmt_p) == NULL) {
@@ -3947,18 +3961,30 @@ int build_stl_str_hl(
}
case STL_VIM_EXPR: // '{'
{
+ char_u *block_start = fmt_p - 1;
+ int reevaluate = (*fmt_p == '%');
itemisflag = true;
+ if (reevaluate) {
+ fmt_p++;
+ }
+
// Attempt to copy the expression to evaluate into
// the output buffer as a null-terminated string.
char_u *t = out_p;
- while (*fmt_p != '}' && *fmt_p != NUL && out_p < out_end_p)
+ while ((*fmt_p != '}' || (reevaluate && fmt_p[-1] != '%'))
+ && *fmt_p != NUL && out_p < out_end_p) {
*out_p++ = *fmt_p++;
+ }
if (*fmt_p != '}') { // missing '}' or out of space
break;
}
fmt_p++;
- *out_p = 0;
+ if (reevaluate) {
+ out_p[-1] = 0; // remove the % at the end of %{% expr %}
+ } else {
+ *out_p = 0;
+ }
// Move our position in the output buffer
// to the beginning of the expression
@@ -4004,6 +4030,40 @@ int build_stl_str_hl(
itemisflag = false;
}
}
+
+
+ // If the output of the expression needs to be evaluated
+ // replace the %{} block with the result of evaluation
+ if (reevaluate && str != NULL && *str != 0
+ && strchr((const char *)str, '%') != NULL
+ && evaldepth < MAX_STL_EVAL_DEPTH) {
+ size_t parsed_usefmt = (size_t)(block_start - usefmt);
+ size_t str_length = strlen((const char *)str);
+ size_t fmt_length = strlen((const char *)fmt_p);
+ size_t new_fmt_len = parsed_usefmt
+ + str_length + fmt_length + 3;
+ char_u *new_fmt = (char_u *)xmalloc(new_fmt_len * sizeof(char_u));
+ char_u *new_fmt_p = new_fmt;
+
+ new_fmt_p = (char_u *)memcpy(new_fmt_p, usefmt, parsed_usefmt)
+ + parsed_usefmt;
+ new_fmt_p = (char_u *)memcpy(new_fmt_p , str, str_length)
+ + str_length;
+ new_fmt_p = (char_u *)memcpy(new_fmt_p, "%}", 2) + 2;
+ new_fmt_p = (char_u *)memcpy(new_fmt_p , fmt_p, fmt_length)
+ + fmt_length;
+ *new_fmt_p = 0;
+ new_fmt_p = NULL;
+
+ if (usefmt != fmt) {
+ xfree(usefmt);
+ }
+ XFREE_CLEAR(str);
+ usefmt = new_fmt;
+ fmt_p = usefmt + parsed_usefmt;
+ evaldepth++;
+ continue;
+ }
break;
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index dd24db910e..0c839ba12a 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -1083,6 +1083,7 @@ typedef struct {
FloatRelative relative;
bool external;
bool focusable;
+ int zindex;
WinStyle style;
bool border;
bool shadow;
@@ -1096,6 +1097,7 @@ typedef struct {
.row = 0, .col = 0, .anchor = 0, \
.relative = 0, .external = false, \
.focusable = true, \
+ .zindex = kZIndexFloatDefault, \
.style = kWinStyleUnused })
// Structure to store last cursor position and topline. Used by check_lnums()
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 22eb31513d..60af11e94b 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -162,6 +162,7 @@ void channel_init(void)
/// Channel is allocated with refcount 1, which should be decreased
/// when the underlying stream closes.
Channel *channel_alloc(ChannelStreamType type)
+ FUNC_ATTR_NONNULL_RET
{
Channel *chan = xcalloc(1, sizeof(*chan));
if (type == kChannelStreamStdio) {
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 56b563cba0..1579f3ff98 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -3150,9 +3150,7 @@ static void ins_compl_clear(void)
XFREE_CLEAR(compl_orig_text);
compl_enter_selects = false;
// clear v:completed_item
- dict_T *const d = tv_dict_alloc();
- d->dv_lock = VAR_FIXED;
- set_vim_var_dict(VV_COMPLETED_ITEM, d);
+ set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED));
}
/// Check that Insert completion is active.
@@ -4497,9 +4495,7 @@ static void ins_compl_delete(void)
// causes flicker, thus we can't do that.
changed_cline_bef_curs();
// clear v:completed_item
- dict_T *const d = tv_dict_alloc();
- d->dv_lock = VAR_FIXED;
- set_vim_var_dict(VV_COMPLETED_ITEM, d);
+ set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED));
}
// Insert the new text being completed.
@@ -4520,8 +4516,7 @@ static void ins_compl_insert(int in_compl_func)
static dict_T *ins_compl_dict_alloc(compl_T *match)
{
// { word, abbr, menu, kind, info }
- dict_T *dict = tv_dict_alloc();
- dict->dv_lock = VAR_FIXED;
+ dict_T *dict = tv_dict_alloc_lock(VAR_FIXED);
tv_dict_add_str(
dict, S_LEN("word"),
(const char *)EMPTY_IF_NULL(match->cp_str));
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 079c0dc3c0..04a9abe41a 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -228,6 +228,7 @@ static struct vimvar {
VV(VV_EVENT, "event", VAR_DICT, VV_RO),
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
+ VV(VV_COLLATE, "collate", VAR_STRING, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),
// Neovim
VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO),
@@ -376,11 +377,9 @@ void eval_init(void)
msgpack_types_dict->dv_lock = VAR_FIXED;
set_vim_var_dict(VV_MSGPACK_TYPES, msgpack_types_dict);
- set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc());
+ set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED));
- dict_T *v_event = tv_dict_alloc();
- v_event->dv_lock = VAR_FIXED;
- set_vim_var_dict(VV_EVENT, v_event);
+ set_vim_var_dict(VV_EVENT, tv_dict_alloc_lock(VAR_FIXED));
set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown));
set_vim_var_nr(VV_STDERR, CHAN_STDERR);
set_vim_var_nr(VV_SEARCHFORWARD, 1L);
@@ -7616,7 +7615,7 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
/// @param[out] ret_fnum Set to fnum for marks.
///
/// @return Pointer to position or NULL in case of error (e.g. invalid type).
-pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum,
+pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum,
int *const ret_fnum)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 8188502987..41120b3c78 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -157,6 +157,7 @@ typedef enum {
VV_EVENT,
VV_ECHOSPACE,
VV_ARGV,
+ VV_COLLATE,
VV_EXITING,
// Neovim
VV_STDERR,
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 77e7c7b3a9..33c6fae5cf 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -217,7 +217,7 @@ return {
len={args=1},
libcall={args=3},
libcallnr={args=3},
- line={args=1},
+ line={args={1, 2}},
line2byte={args=1},
lispindent={args=1},
list2str={args={1, 2}},
@@ -286,6 +286,7 @@ return {
screenpos={args=3},
screenrow={},
search={args={1, 4}},
+ searchcount={args={0,1}},
searchdecl={args={1, 3}},
searchpair={args={3, 7}},
searchpairpos={args={3, 7}},
@@ -390,6 +391,7 @@ return {
visualmode={args={0, 1}},
wait={args={2,3}},
wildmenumode={},
+ win_execute={args={2, 3}},
win_findbuf={args=1},
win_getid={args={0,2}},
win_gettype={args={0,1}},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 60af592108..072d206ecb 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -1953,8 +1953,8 @@ static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat)
return (char_u *)(s == NULL ? NULL : xstrdup(s));
}
-// "execute(command)" function
-static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void execute_common(typval_T *argvars, typval_T *rettv, FunPtr fptr,
+ int arg_off)
{
const int save_msg_silent = msg_silent;
const int save_emsg_silent = emsg_silent;
@@ -1968,9 +1968,9 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
- if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[arg_off + 1].v_type != VAR_UNKNOWN) {
char buf[NUMBUFLEN];
- const char *const s = tv_get_string_buf_chk(&argvars[1], buf);
+ const char *const s = tv_get_string_buf_chk(&argvars[arg_off + 1], buf);
if (s == NULL) {
return;
@@ -1997,10 +1997,10 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
msg_col = 0; // prevent leading spaces
}
- if (argvars[0].v_type != VAR_LIST) {
- do_cmdline_cmd(tv_get_string(&argvars[0]));
- } else if (argvars[0].vval.v_list != NULL) {
- list_T *const list = argvars[0].vval.v_list;
+ if (argvars[arg_off].v_type != VAR_LIST) {
+ do_cmdline_cmd(tv_get_string(&argvars[arg_off]));
+ } else if (argvars[arg_off].vval.v_list != NULL) {
+ list_T *const list = argvars[arg_off].vval.v_list;
tv_list_ref(list);
GetListLineCookie cookie = {
.l = list,
@@ -2032,6 +2032,39 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
capture_ga = save_capture_ga;
}
+// "execute(command)" function
+static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ execute_common(argvars, rettv, fptr, 0);
+}
+
+// "win_execute(win_id, command)" function
+static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tabpage_T *tp;
+ win_T *wp = win_id2wp_tp(argvars, &tp);
+ win_T *save_curwin;
+ tabpage_T *save_curtab;
+ // Return an empty string if something fails.
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ if (wp != NULL && tp != NULL) {
+ pos_T curpos = wp->w_cursor;
+ if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) ==
+ OK) {
+ check_cursor();
+ execute_common(argvars, rettv, fptr, 1);
+ }
+ restore_win_noblock(save_curwin, save_curtab, true);
+
+ // Update the status line if the cursor moved.
+ if (win_valid(wp) && !equalpos(curpos, wp->w_cursor)) {
+ wp->w_redr_status = true;
+ }
+ }
+}
+
/// "exepath()" function
static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -2737,10 +2770,9 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
} else if (strcmp(what, "args") == 0) {
rettv->v_type = VAR_LIST;
- if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) {
- for (int i = 0; i < pt->pt_argc; i++) {
- tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
- }
+ tv_list_alloc_ret(rettv, pt->pt_argc);
+ for (int i = 0; i < pt->pt_argc; i++) {
+ tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
}
} else {
EMSG2(_(e_invarg2), what);
@@ -5507,18 +5539,36 @@ static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
libcall_common(argvars, rettv, VAR_NUMBER);
}
-/*
- * "line(string)" function
- */
+// "line(string, [winid])" function
static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
linenr_T lnum = 0;
- pos_T *fp;
+ pos_T *fp = NULL;
int fnum;
- fp = var2fpos(&argvars[0], TRUE, &fnum);
- if (fp != NULL)
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ tabpage_T *tp;
+ win_T *save_curwin;
+ tabpage_T *save_curtab;
+
+ // use window specified in the second argument
+ win_T *wp = win_id2wp_tp(&argvars[1], &tp);
+ if (wp != NULL && tp != NULL) {
+ if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true)
+ == OK) {
+ check_cursor();
+ fp = var2fpos(&argvars[0], true, &fnum);
+ }
+ restore_win_noblock(save_curwin, save_curtab, true);
+ }
+ } else {
+ // use current window
+ fp = var2fpos(&argvars[0], true, &fnum);
+ }
+
+ if (fp != NULL) {
lnum = fp->lnum;
+ }
rettv->vval.v_number = lnum;
}
@@ -9181,6 +9231,7 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// struct storing information about current sort
typedef struct {
int item_compare_ic;
+ bool item_compare_lc;
bool item_compare_numeric;
bool item_compare_numbers;
bool item_compare_float;
@@ -9255,10 +9306,10 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero)
p2 = "";
}
if (!sortinfo->item_compare_numeric) {
- if (sortinfo->item_compare_ic) {
- res = STRICMP(p1, p2);
+ if (sortinfo->item_compare_lc) {
+ res = strcoll(p1, p2);
} else {
- res = STRCMP(p1, p2);
+ res = sortinfo->item_compare_ic ? STRICMP(p1, p2): STRCMP(p1, p2);
}
} else {
double n1, n2;
@@ -9393,6 +9444,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
}
info.item_compare_ic = false;
+ info.item_compare_lc = false;
info.item_compare_numeric = false;
info.item_compare_numbers = false;
info.item_compare_float = false;
@@ -9437,6 +9489,9 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
} else if (strcmp(info.item_compare_func, "i") == 0) {
info.item_compare_func = NULL;
info.item_compare_ic = true;
+ } else if (strcmp(info.item_compare_func, "l") == 0) {
+ info.item_compare_func = NULL;
+ info.item_compare_lc = true;
}
}
}
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 71e4edc667..61de83fc21 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -2098,7 +2098,7 @@ void tv_dict_set_keys_readonly(dict_T *const dict)
///
/// @return [allocated] pointer to the created list.
list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
list_T *const l = tv_list_alloc(len);
tv_list_set_ret(ret_tv, l);
@@ -2106,6 +2106,14 @@ list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len)
return l;
}
+dict_T *tv_dict_alloc_lock(VarLockStatus lock)
+ FUNC_ATTR_NONNULL_RET
+{
+ dict_T *const d = tv_dict_alloc();
+ d->dv_lock = lock;
+ return d;
+}
+
/// Allocate an empty dictionary for a return value
///
/// Also sets reference count.
@@ -2114,9 +2122,8 @@ list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len)
void tv_dict_alloc_ret(typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
- dict_T *const d = tv_dict_alloc();
+ dict_T *const d = tv_dict_alloc_lock(VAR_UNLOCKED);
tv_dict_set_ret(ret_tv, d);
- ret_tv->v_lock = VAR_UNLOCKED;
}
//{{{3 Clear
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 3e330b88a2..6a0a08eee8 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -358,6 +358,7 @@ static int linelen(int *has_tab)
static char_u *sortbuf1;
static char_u *sortbuf2;
+static int sort_lc; ///< sort using locale
static int sort_ic; ///< ignore case
static int sort_nr; ///< sort on number
static int sort_rx; ///< sort on regex instead of skipping it
@@ -381,6 +382,13 @@ typedef struct {
} st_u;
} sorti_T;
+static int string_compare(const void *s1, const void *s2) FUNC_ATTR_NONNULL_ALL
+{
+ if (sort_lc) {
+ return strcoll((char *)s1, (char *)s2);
+ }
+ return sort_ic ? STRICMP(s1, s2) : STRCMP(s1, s2);
+}
static int sort_compare(const void *s1, const void *s2)
{
@@ -424,8 +432,7 @@ static int sort_compare(const void *s1, const void *s2)
l2.st_u.line.end_col_nr - l2.st_u.line.start_col_nr + 1);
sortbuf2[l2.st_u.line.end_col_nr - l2.st_u.line.start_col_nr] = NUL;
- result = sort_ic ? STRICMP(sortbuf1, sortbuf2)
- : STRCMP(sortbuf1, sortbuf2);
+ result = string_compare(sortbuf1, sortbuf2);
}
/* If two lines have the same value, preserve the original line order. */
@@ -466,7 +473,7 @@ void ex_sort(exarg_T *eap)
regmatch.regprog = NULL;
sorti_T *nrs = xmalloc(count * sizeof(sorti_T));
- sort_abort = sort_ic = sort_rx = sort_nr = sort_flt = 0;
+ sort_abort = sort_ic = sort_lc = sort_rx = sort_nr = sort_flt = 0;
size_t format_found = 0;
bool change_occurred = false; // Buffer contents changed.
@@ -474,6 +481,8 @@ void ex_sort(exarg_T *eap)
if (ascii_iswhite(*p)) {
} else if (*p == 'i') {
sort_ic = true;
+ } else if (*p == 'l') {
+ sort_lc = true;
} else if (*p == 'r') {
sort_rx = true;
} else if (*p == 'n') {
@@ -645,8 +654,7 @@ void ex_sort(exarg_T *eap)
s = ml_get(get_lnum);
size_t bytelen = STRLEN(s) + 1; // include EOL in bytelen
old_count += bytelen;
- if (!unique || i == 0
- || (sort_ic ? STRICMP(s, sortbuf1) : STRCMP(s, sortbuf1)) != 0) {
+ if (!unique || i == 0 || string_compare(s, sortbuf1) != 0) {
// Copy the line into a buffer, it may become invalid in
// ml_append(). And it's needed for "unique".
STRCPY(sortbuf1, s);
@@ -2426,21 +2434,25 @@ int do_ecmd(
* is returned by buflist_new(), nothing to do here.
*/
if (buf != curbuf) {
- /*
- * Be careful: The autocommands may delete any buffer and change
- * the current buffer.
- * - If the buffer we are going to edit is deleted, give up.
- * - If the current buffer is deleted, prefer to load the new
- * buffer when loading a buffer is required. This avoids
- * loading another buffer which then must be closed again.
- * - If we ended up in the new buffer already, need to skip a few
- * things, set auto_buf.
- */
+ const int save_cmdwin_type = cmdwin_type;
+
+ // BufLeave applies to the old buffer.
+ cmdwin_type = 0;
+
+ // Be careful: The autocommands may delete any buffer and change
+ // the current buffer.
+ // - If the buffer we are going to edit is deleted, give up.
+ // - If the current buffer is deleted, prefer to load the new
+ // buffer when loading a buffer is required. This avoids
+ // loading another buffer which then must be closed again.
+ // - If we ended up in the new buffer already, need to skip a few
+ // things, set auto_buf.
if (buf->b_fname != NULL) {
new_name = vim_strsave(buf->b_fname);
}
set_bufref(&au_new_curbuf, buf);
apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf);
+ cmdwin_type = save_cmdwin_type;
if (!bufref_valid(&au_new_curbuf)) {
// New buffer has been deleted.
delbuf_msg(new_name); // Frees new_name.
@@ -2454,6 +2466,7 @@ int do_ecmd(
auto_buf = true;
} else {
win_T *the_curwin = curwin;
+ buf_T *was_curbuf = curbuf;
// Set w_closing to avoid that autocommands close the window.
// Set b_locked for the same reason.
@@ -2467,9 +2480,10 @@ int do_ecmd(
// Close the link to the current buffer. This will set
// oldwin->w_buffer to NULL.
u_sync(false);
- close_buffer(oldwin, curbuf,
- (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD,
- false);
+ const bool did_decrement = close_buffer(
+ oldwin, curbuf,
+ (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD,
+ false);
// Autocommands may have closed the window.
if (win_valid(the_curwin)) {
@@ -2489,6 +2503,14 @@ int do_ecmd(
goto theend;
}
if (buf == curbuf) { // already in new buffer
+ // close_buffer() has decremented the window count,
+ // increment it again here and restore w_buffer.
+ if (did_decrement && buf_valid(was_curbuf)) {
+ was_curbuf->b_nwindows++;
+ }
+ if (win_valid_any_tab(oldwin) && oldwin->w_buffer == NULL) {
+ oldwin->w_buffer = was_curbuf;
+ }
auto_buf = true;
} else {
// <VN> We could instead free the synblock
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 7f28c001f9..0a2802397d 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -3621,6 +3621,14 @@ void set_lang_var(void)
loc = get_locale_val(LC_TIME);
# endif
set_vim_var_string(VV_LC_TIME, loc, -1);
+
+# ifdef HAVE_GET_LOCALE_VAL
+ loc = get_locale_val(LC_COLLATE);
+# else
+ // setlocale() not supported: use the default value
+ loc = "C";
+# endif
+ set_vim_var_string(VV_COLLATE, loc, -1);
}
#ifdef HAVE_WORKING_LIBINTL
@@ -3661,6 +3669,10 @@ void ex_language(exarg_T *eap)
what = LC_TIME;
name = skipwhite(p);
whatstr = "time ";
+ } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) {
+ what = LC_COLLATE;
+ name = skipwhite(p);
+ whatstr = "collate ";
}
}
@@ -3705,7 +3717,7 @@ void ex_language(exarg_T *eap)
// Reset $LC_ALL, otherwise it would overrule everything.
os_setenv("LC_ALL", "", 1);
- if (what != LC_TIME) {
+ if (what != LC_TIME && what != LC_COLLATE) {
// Tell gettext() what to translate to. It apparently doesn't
// use the currently effective locale.
if (what == LC_ALL) {
@@ -3720,7 +3732,7 @@ void ex_language(exarg_T *eap)
}
}
- // Set v:lang, v:lc_time and v:ctype to the final result.
+ // Set v:lang, v:lc_time, v:collate and v:ctype to the final result.
set_lang_var();
maketitle();
}
@@ -3805,12 +3817,15 @@ char_u *get_lang_arg(expand_T *xp, int idx)
if (idx == 2) {
return (char_u *)"time";
}
+ if (idx == 3) {
+ return (char_u *)"collate";
+ }
init_locales();
if (locales == NULL) {
return NULL;
}
- return locales[idx - 3];
+ return locales[idx - 4];
}
/// Function given to ExpandGeneric() to obtain the available locales.
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index ae5c334592..c557bb2438 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -212,7 +212,7 @@ void do_exmode(int improved)
while (exmode_active) {
/* Check for a ":normal" command and no more characters left. */
if (ex_normal_busy > 0 && typebuf.tb_len == 0) {
- exmode_active = FALSE;
+ exmode_active = 0;
break;
}
msg_scroll = true;
@@ -3642,7 +3642,8 @@ const char * set_one_cmd_context(
} else {
if (strncmp(arg, "messages", p - arg) == 0
|| strncmp(arg, "ctype", p - arg) == 0
- || strncmp(arg, "time", p - arg) == 0) {
+ || strncmp(arg, "time", p - arg) == 0
+ || strncmp(arg, "collate", p - arg) == 0) {
xp->xp_context = EXPAND_LOCALES;
xp->xp_pattern = skipwhite((const char_u *)p);
} else {
@@ -6519,6 +6520,12 @@ ex_win_close(
int need_hide;
buf_T *buf = win->w_buffer;
+ // Never close the autocommand window.
+ if (win == aucmd_win) {
+ EMSG(_(e_autocmd_close));
+ return;
+ }
+
need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1);
if (need_hide && !buf_hide(buf) && !forceit) {
if ((p_confirm || cmdmod.confirm) && p_write) {
@@ -6588,9 +6595,6 @@ static void ex_tabonly(exarg_T *eap)
// Repeat this up to a 1000 times, because autocommands may
// mess up the lists.
for (int done = 0; done < 1000; done++) {
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- assert(wp != aucmd_win);
- }
FOR_ALL_TABS(tp) {
if (tp->tp_topframe != topframe) {
tabpage_close_other(tp, eap->forceit);
@@ -7303,7 +7307,8 @@ do_exedit(
*/
if (exmode_active && (eap->cmdidx == CMD_visual
|| eap->cmdidx == CMD_view)) {
- exmode_active = FALSE;
+ exmode_active = 0;
+ ex_pressedreturn = false;
if (*eap->arg == NUL) {
/* Special case: ":global/pat/visual\NLvi-commands" */
if (global_busy) {
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 53571ec8da..75ed5dc0e5 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -3571,6 +3571,7 @@ static void save_cmdline(struct cmdline_info *ccp)
* Restore ccline after it has been saved with save_cmdline().
*/
static void restore_cmdline(struct cmdline_info *ccp)
+ FUNC_ATTR_NONNULL_ALL
{
ccline = *ccp;
}
@@ -3580,6 +3581,7 @@ static void restore_cmdline(struct cmdline_info *ccp)
* passed to restore_cmdline_alloc() later.
*/
char_u *save_cmdline_alloc(void)
+ FUNC_ATTR_NONNULL_RET
{
struct cmdline_info *p = xmalloc(sizeof(struct cmdline_info));
save_cmdline(p);
@@ -3590,6 +3592,7 @@ char_u *save_cmdline_alloc(void)
* Restore the command line from the return value of save_cmdline_alloc().
*/
void restore_cmdline_alloc(char_u *p)
+ FUNC_ATTR_NONNULL_ALL
{
restore_cmdline((struct cmdline_info *)p);
xfree(p);
@@ -6635,11 +6638,13 @@ static int open_cmdwin(void)
wp = curwin;
set_bufref(&bufref, curbuf);
win_goto(old_curwin);
- win_close(wp, true);
+ if (win_valid(wp) && wp != curwin) {
+ win_close(wp, true);
+ }
// win_close() may have already wiped the buffer when 'bh' is
- // set to 'wipe'.
- if (bufref_valid(&bufref)) {
+ // set to 'wipe', autocommands may have closed other windows
+ if (bufref_valid(&bufref) && bufref.br_buf != curbuf) {
close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false);
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 624b7c93f3..0ce2b586e3 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -987,6 +987,8 @@ EXTERN char_u e_dirnotf[] INIT(= N_(
"E919: Directory not found in '%s': \"%s\""));
EXTERN char_u e_au_recursive[] INIT(= N_(
"E952: Autocommand caused recursive behavior"));
+EXTERN char_u e_autocmd_close[] INIT(= N_(
+ "E813: Cannot close autocmd window"));
EXTERN char_u e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
EXTERN char_u e_fnametoolong[] INIT(= N_("E856: Filename too long"));
EXTERN char_u e_float_as_string[] INIT(= N_("E806: using Float as a String"));
diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h
index 3b34af46e4..724363674c 100644
--- a/src/nvim/grid_defs.h
+++ b/src/nvim/grid_defs.h
@@ -13,6 +13,15 @@
typedef char_u schar_T[(MAX_MCO+1) * 4 + 1];
typedef int sattr_T;
+enum {
+ kZIndexDefaultGrid = 0,
+ kZIndexFloatDefault = 50,
+ kZIndexPopupMenu = 100,
+ kZIndexMessages = 200,
+ kZIndexCmdlinePopupMenu = 250,
+};
+
+
/// ScreenGrid represents a resizable rectuangular grid displayed by UI clients.
///
/// chars[] contains the UTF-8 text that is currently displayed on the grid.
@@ -73,6 +82,9 @@ struct ScreenGrid {
// whether the grid can be focused with mouse clicks.
bool focusable;
+ // z-index: the order in the stack of grids.
+ int zindex;
+
// Below is state owned by the compositor. Should generally not be set/read
// outside this module, except for specific compatibilty hacks
@@ -96,7 +108,7 @@ struct ScreenGrid {
};
#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \
- false, 0, 0, NULL, false, true, \
+ false, 0, 0, NULL, false, true, 0, \
0, 0, 0, 0, 0, false }
#endif // NVIM_GRID_DEFS_H
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 79801262cb..79e474fa2e 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -806,8 +806,11 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
{
HlAttrs hlattrs = HLATTRS_INIT;
- int32_t fg = -1, bg = -1, sp = -1;
+ int32_t fg = -1, bg = -1, ctermfg = -1, ctermbg = -1, sp = -1;
int16_t mask = 0;
+ int16_t cterm_mask = 0;
+ bool cterm_mask_provided = false;
+
for (size_t i = 0; i < dict.size; i++) {
char *key = dict.items[i].key.data;
Object val = dict.items[i].value;
@@ -837,6 +840,25 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
}
}
+ // Handle cterm attrs
+ if (strequal(key, "cterm") && val.type == kObjectTypeDictionary) {
+ cterm_mask_provided = true;
+ Dictionary cterm_dict = val.data.dictionary;
+ for (size_t l = 0; l < cterm_dict.size; l++) {
+ char *cterm_dict_key = cterm_dict.items[l].key.data;
+ Object cterm_dict_val = cterm_dict.items[l].value;
+ for (int m = 0; flags[m].name; m++) {
+ if (strequal(flags[m].name, cterm_dict_key)) {
+ if (api_object_to_bool(cterm_dict_val, cterm_dict_key, false,
+ err)) {
+ cterm_mask |= flags[m].flag;
+ }
+ break;
+ }
+ }
+ }
+ }
+
struct {
const char *name;
const char *shortname;
@@ -844,6 +866,8 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
} colors[] = {
{ "foreground", "fg", &fg },
{ "background", "bg", &bg },
+ { "ctermfg", NULL, &ctermfg },
+ { "ctermbg", NULL, &ctermbg },
{ "special", "sp", &sp },
{ NULL, NULL, NULL },
};
@@ -867,7 +891,6 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
}
}
-
if (flags[j].name || colors[k].name) {
// handled above
} else if (link_id && strequal(key, "link")) {
@@ -888,13 +911,22 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
}
}
+ // apply gui mask as default for cterm mask
+ if (!cterm_mask_provided) {
+ cterm_mask = mask;
+ }
if (use_rgb) {
hlattrs.rgb_ae_attr = mask;
hlattrs.rgb_bg_color = bg;
hlattrs.rgb_fg_color = fg;
hlattrs.rgb_sp_color = sp;
+ hlattrs.cterm_bg_color =
+ ctermbg == -1 ? cterm_normal_bg_color : ctermbg + 1;
+ hlattrs.cterm_fg_color =
+ ctermfg == -1 ? cterm_normal_fg_color : ctermfg + 1;
+ hlattrs.cterm_ae_attr = cterm_mask;
} else {
- hlattrs.cterm_ae_attr = mask;
+ hlattrs.cterm_ae_attr = cterm_mask;
hlattrs.cterm_bg_color = bg == -1 ? cterm_normal_bg_color : bg + 1;
hlattrs.cterm_fg_color = fg == -1 ? cterm_normal_fg_color : fg + 1;
}
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index f99a2dd0fe..0a52cc16cb 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -471,6 +471,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, &nlua_wait);
lua_setfield(lstate, -2, "wait");
+ // _getvar
+ lua_pushcfunction(lstate, &nlua_getvar);
+ lua_setfield(lstate, -2, "_getvar");
+
+ // _setvar
+ lua_pushcfunction(lstate, &nlua_setvar);
+ lua_setfield(lstate, -2, "_setvar");
+
+
// vim.loop
luv_set_loop(lstate, &main_loop.uv);
luv_set_callback(lstate, nlua_luv_cfpcall);
@@ -870,6 +879,109 @@ check_err:
return request ? 1 : 0;
}
+static dict_T *nlua_get_var_scope(lua_State *lstate) {
+ const char *scope = luaL_checkstring(lstate, 1);
+ handle_T handle = (handle_T)luaL_checkinteger(lstate, 2);
+ dict_T *dict = NULL;
+ Error err = ERROR_INIT;
+ if (strequal(scope, "g")) {
+ dict = &globvardict;
+ } else if (strequal(scope, "v")) {
+ dict = &vimvardict;
+ } else if (strequal(scope, "b")) {
+ buf_T *buf = find_buffer_by_handle(handle, &err);
+ if (buf) {
+ dict = buf->b_vars;
+ }
+ } else if (strequal(scope, "w")) {
+ win_T *win = find_window_by_handle(handle, &err);
+ if (win) {
+ dict = win->w_vars;
+ }
+ } else if (strequal(scope, "t")) {
+ tabpage_T *tabpage = find_tab_by_handle(handle, &err);
+ if (tabpage) {
+ dict = tabpage->tp_vars;
+ }
+ } else {
+ luaL_error(lstate, "invalid scope", err.msg);
+ return NULL;
+ }
+
+ if (ERROR_SET(&err)) {
+ luaL_error(lstate, "FAIL: %s", err.msg);
+ return NULL;
+ }
+ return dict;
+}
+
+
+static int nlua_getvar(lua_State *lstate)
+{
+ // non-local return if not found
+ dict_T *dict = nlua_get_var_scope(lstate);
+ size_t len;
+ const char *name = luaL_checklstring(lstate, 3, &len);
+
+ dictitem_T *di = tv_dict_find(dict, name, (ptrdiff_t)len);
+ if (di == NULL) {
+ return 0; // nil
+ }
+ nlua_push_typval(lstate, &di->di_tv, false);
+ return 1;
+}
+
+static int nlua_setvar(lua_State *lstate)
+{
+ // non-local return if not found
+ dict_T *dict = nlua_get_var_scope(lstate);
+ String key;
+ key.data = (char *)luaL_checklstring(lstate, 3, &key.size);
+
+ bool del = (lua_gettop(lstate) < 4) || lua_isnil(lstate, 4);
+
+ Error err = ERROR_INIT;
+ dictitem_T *di = dict_check_writable(dict, key, del, &err);
+ if (ERROR_SET(&err)) {
+ return 0;
+ }
+
+ if (del) {
+ // Delete the key
+ if (di == NULL) {
+ // Doesn't exist, nothing to do
+ return 0;
+ } else {
+ // Delete the entry
+ tv_dict_item_remove(dict, di);
+ }
+ } else {
+ // Update the key
+ typval_T tv;
+
+ // Convert the lua value to a vimscript type in the temporary variable
+ lua_pushvalue(lstate, 4);
+ if (!nlua_pop_typval(lstate, &tv)) {
+ return luaL_error(lstate, "Couldn't convert lua value");
+ }
+
+ if (di == NULL) {
+ // Need to create an entry
+ di = tv_dict_item_alloc_len(key.data, key.size);
+ tv_dict_add(dict, di);
+ } else {
+ // Clear the old value
+ tv_clear(&di->di_tv);
+ }
+
+ // Update the value
+ tv_copy(&tv, &di->di_tv);
+ // Clear the temporary variable
+ tv_clear(&tv);
+ }
+ return 0;
+}
+
static int nlua_nil_tostring(lua_State *lstate)
{
lua_pushstring(lstate, "vim.NIL");
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 3994c5bc5b..bc0770da31 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -341,32 +341,24 @@ do
end
return setmetatable({}, mt)
end
- local function pcall_ret(status, ...)
- if status then return ... end
- end
- local function nil_wrap(fn)
- return function(...)
- return pcall_ret(pcall(fn, ...))
+ local function make_dict_accessor(scope)
+ validate {
+ scope = {scope, 's'};
+ }
+ local mt = {}
+ function mt:__newindex(k, v)
+ return vim._setvar(scope, 0, k, v)
end
+ function mt:__index(k)
+ return vim._getvar(scope, 0, k)
+ end
+ return setmetatable({}, mt)
end
-
- vim.b = make_meta_accessor(
- nil_wrap(function(v) return a.nvim_buf_get_var(0, v) end),
- function(v, k) return a.nvim_buf_set_var(0, v, k) end,
- function(v) return a.nvim_buf_del_var(0, v) end
- )
- vim.w = make_meta_accessor(
- nil_wrap(function(v) return a.nvim_win_get_var(0, v) end),
- function(v, k) return a.nvim_win_set_var(0, v, k) end,
- function(v) return a.nvim_win_del_var(0, v) end
- )
- vim.t = make_meta_accessor(
- nil_wrap(function(v) return a.nvim_tabpage_get_var(0, v) end),
- function(v, k) return a.nvim_tabpage_set_var(0, v, k) end,
- function(v) return a.nvim_tabpage_del_var(0, v) end
- )
- vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var)
- vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar)
+ vim.g = make_dict_accessor('g')
+ vim.v = make_dict_accessor('v')
+ vim.b = make_dict_accessor('b')
+ vim.w = make_dict_accessor('w')
+ vim.t = make_dict_accessor('t')
vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
local function getenv(k)
@@ -522,6 +514,8 @@ function vim.notify(msg, log_level, _opts)
if log_level == vim.log.levels.ERROR then
vim.api.nvim_err_writeln(msg)
+ elseif log_level == vim.log.levels.WARN then
+ vim.api.nvim_echo({{msg, 'WarningMsg'}}, true, {})
else
vim.api.nvim_echo({{msg}}, true, {})
end
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index 303722b4a8..eb9357d027 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -238,5 +238,6 @@
# define PRAGMA_DIAG_POP
#endif
+#define EMPTY_POS(a) ((a).lnum == 0 && (a).col == 0 && (a).coladd == 0)
#endif // NVIM_MACROS_H
diff --git a/src/nvim/message.c b/src/nvim/message.c
index a34b895033..ec5dabbbc0 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -165,6 +165,7 @@ void msg_grid_validate(void)
// TODO(bfredl): eventually should be set to "invalid". I e all callers
// will use the grid including clear to EOS if necessary.
grid_alloc(&msg_grid, Rows, Columns, false, true);
+ msg_grid.zindex = kZIndexMessages;
xfree(msg_grid.dirty_col);
msg_grid.dirty_col = xcalloc(Rows, sizeof(*msg_grid.dirty_col));
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 54d73b6ae7..0ed116c17f 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -1676,17 +1676,14 @@ int op_delete(oparg_T *oap)
curbuf_splice_pending++;
pos_T startpos = curwin->w_cursor; // start position for delete
- bcount_t deleted_bytes = (bcount_t)STRLEN(
- ml_get(startpos.lnum)) + 1 - startpos.col;
+ bcount_t deleted_bytes = get_region_bytecount(
+ curbuf, startpos.lnum, oap->end.lnum, startpos.col,
+ oap->end.col) + oap->inclusive;
truncate_line(true); // delete from cursor to end of line
curpos = curwin->w_cursor; // remember curwin->w_cursor
curwin->w_cursor.lnum++;
- for (linenr_T i = 1; i <= oap->line_count - 2; i++) {
- deleted_bytes += (bcount_t)STRLEN(
- ml_get(startpos.lnum + i)) + 1;
- }
del_lines(oap->line_count - 2, false);
// delete from start of line until op_end
@@ -1694,7 +1691,6 @@ int op_delete(oparg_T *oap)
curwin->w_cursor.col = 0;
(void)del_bytes((colnr_T)n, !virtual_op,
oap->op_type == OP_DELETE && !oap->is_VIsual);
- deleted_bytes += n;
curwin->w_cursor = curpos; // restore curwin->w_cursor
(void)do_join(2, false, false, false, false);
curbuf_splice_pending--;
@@ -3334,6 +3330,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
changed_cline_bef_curs();
curwin->w_cursor.col += (colnr_T)(totlen - 1);
}
+ changed_bytes(lnum, col);
+ extmark_splice_cols(curbuf, (int)lnum-1, col,
+ 0, (int)totlen, kExtmarkUndo);
}
if (VIsual_active) {
lnum++;
@@ -3345,12 +3344,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
curbuf->b_op_end = curwin->w_cursor;
- /* For "CTRL-O p" in Insert mode, put cursor after last char */
- if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND)))
- ++curwin->w_cursor.col;
- changed_bytes(lnum, col);
- extmark_splice_cols(curbuf, (int)lnum-1, col,
- 0, (int)totlen, kExtmarkUndo);
+ // For "CTRL-O p" in Insert mode, put cursor after last char
+ if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND))) {
+ curwin->w_cursor.col++;
+ }
} else {
// Insert at least one line. When y_type is kMTCharWise, break the first
// line in two.
@@ -6302,3 +6299,33 @@ bool op_reg_set_previous(const char name)
y_previous = &y_regs[i];
return true;
}
+
+/// Get the byte count of buffer region. End-exclusive.
+///
+/// @return number of bytes
+bcount_t get_region_bytecount(buf_T *buf, linenr_T start_lnum,
+ linenr_T end_lnum, colnr_T start_col,
+ colnr_T end_col)
+{
+ linenr_T max_lnum = buf->b_ml.ml_line_count;
+ if (start_lnum > max_lnum) {
+ return 0;
+ }
+ if (start_lnum == end_lnum) {
+ return end_col - start_col;
+ }
+ const char *first = (const char *)ml_get_buf(buf, start_lnum, false);
+ bcount_t deleted_bytes = (bcount_t)STRLEN(first) - start_col + 1;
+
+ for (linenr_T i = 1; i <= end_lnum-start_lnum-1; i++) {
+ if (start_lnum + i > max_lnum) {
+ return deleted_bytes;
+ }
+ deleted_bytes += (bcount_t)STRLEN(
+ ml_get_buf(buf, start_lnum + i, false)) + 1;
+ }
+ if (end_lnum > max_lnum) {
+ return deleted_bytes;
+ }
+ return deleted_bytes + end_col;
+}
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index a8867e02ea..77d6b4435f 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -6,6 +6,7 @@
#include "nvim/macros.h"
#include "nvim/ascii.h"
#include "nvim/types.h"
+#include "nvim/extmark.h"
#include "nvim/eval/typval.h"
#include "nvim/os/time.h"
#include "nvim/normal.h" // for MotionType and oparg_T
diff --git a/src/nvim/option.c b/src/nvim/option.c
index a811d749b9..ad481af7fa 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -855,15 +855,18 @@ void set_init_3(void)
p_srr = (char_u *)">&";
options[idx_srr].def_val[VI_DEFAULT] = p_srr;
}
- } else if ( fnamecmp(p, "sh") == 0
- || fnamecmp(p, "ksh") == 0
- || fnamecmp(p, "mksh") == 0
- || fnamecmp(p, "pdksh") == 0
- || fnamecmp(p, "zsh") == 0
- || fnamecmp(p, "zsh-beta") == 0
- || fnamecmp(p, "bash") == 0
- || fnamecmp(p, "fish") == 0
- ) {
+ } else if (fnamecmp(p, "sh") == 0
+ || fnamecmp(p, "ksh") == 0
+ || fnamecmp(p, "mksh") == 0
+ || fnamecmp(p, "pdksh") == 0
+ || fnamecmp(p, "zsh") == 0
+ || fnamecmp(p, "zsh-beta") == 0
+ || fnamecmp(p, "bash") == 0
+ || fnamecmp(p, "fish") == 0
+ || fnamecmp(p, "ash") == 0
+ || fnamecmp(p, "dash") == 0
+ ) {
+ // Always use POSIX shell style redirection if we reach this
if (do_sp) {
p_sp = (char_u *)"2>&1| tee";
options[idx_sp].def_val[VI_DEFAULT] = p_sp;
@@ -3638,9 +3641,11 @@ char_u *check_stl_option(char_u *s)
return illegal_char(errbuf, sizeof(errbuf), *s);
}
if (*s == '{') {
+ int reevaluate = (*s == '%');
s++;
- while (*s != '}' && *s)
+ while ((*s != '}' || (reevaluate && s[-1] != '%')) && *s) {
s++;
+ }
if (*s != '}') {
return (char_u *)N_("E540: Unclosed expression sequence");
}
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index 32c9750628..7d452d6797 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -421,6 +421,10 @@ void pum_redraw(void)
}
grid_assign_handle(&pum_grid);
+
+ pum_grid.zindex = ((State == CMDLINE)
+ ? kZIndexCmdlinePopupMenu : kZIndexPopupMenu);
+
bool moved = ui_comp_put_grid(&pum_grid, pum_row, pum_col-col_off,
pum_height, grid_width, false, true);
bool invalid_grid = moved || pum_invalid;
@@ -439,7 +443,7 @@ void pum_redraw(void)
int row_off = pum_above ? pum_height : 0;
ui_call_win_float_pos(pum_grid.handle, -1, cstr_to_string(anchor),
pum_anchor_grid, pum_row-row_off, pum_col-col_off,
- false);
+ false, pum_grid.zindex);
}
diff --git a/src/nvim/search.c b/src/nvim/search.c
index abe05bbd12..82fc0f9d8e 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -46,38 +46,36 @@
#include "nvim/window.h"
#include "nvim/os/time.h"
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.c.generated.h"
#endif
-/*
- * This file contains various searching-related routines. These fall into
- * three groups:
- * 1. string searches (for /, ?, n, and N)
- * 2. character searches within a single line (for f, F, t, T, etc)
- * 3. "other" kinds of searches like the '%' command, and 'word' searches.
- */
-/*
- * String searches
- *
- * The string search functions are divided into two levels:
- * lowest: searchit(); uses a pos_T for starting position and found match.
- * Highest: do_search(); uses curwin->w_cursor; calls searchit().
- *
- * The last search pattern is remembered for repeating the same search.
- * This pattern is shared between the :g, :s, ? and / commands.
- * This is in search_regcomp().
- *
- * The actual string matching is done using a heavily modified version of
- * Henry Spencer's regular expression library. See regexp.c.
- */
+// This file contains various searching-related routines. These fall into
+// three groups:
+// 1. string searches (for /, ?, n, and N)
+// 2. character searches within a single line (for f, F, t, T, etc)
+// 3. "other" kinds of searches like the '%' command, and 'word' searches.
+//
+//
+// String searches
+//
+// The string search functions are divided into two levels:
+// lowest: searchit(); uses a pos_T for starting position and found match.
+// Highest: do_search(); uses curwin->w_cursor; calls searchit().
+//
+// The last search pattern is remembered for repeating the same search.
+// This pattern is shared between the :g, :s, ? and / commands.
+// This is in search_regcomp().
+//
+// The actual string matching is done using a heavily modified version of
+// Henry Spencer's regular expression library. See regexp.c.
+//
+//
+//
+// Two search patterns are remembered: One for the :substitute command and
+// one for other searches. last_idx points to the one that was used the last
+// time.
-/*
- * Two search patterns are remembered: One for the :substitute command and
- * one for other searches. last_idx points to the one that was used the last
- * time.
- */
static struct spat spats[2] =
{
// Last used search pattern
@@ -1002,7 +1000,7 @@ static int first_submatch(regmmatch_T *rp)
/*
* Highest level string search function.
* Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc'
- * If 'dirc' is 0: use previous dir.
+ * If 'dirc' is 0: use previous dir.
* If 'pat' is NULL or empty : use previous string.
* If 'options & SEARCH_REV' : go in reverse of previous dir.
* If 'options & SEARCH_ECHO': echo the search command and handle options
@@ -1042,7 +1040,6 @@ int do_search(
char_u *msgbuf = NULL;
size_t len;
bool has_offset = false;
-#define SEARCH_STAT_BUF_LEN 12
/*
* A line offset is not remembered, this is vi compatible.
@@ -1383,11 +1380,14 @@ int do_search(
&& c != FAIL
&& !shortmess(SHM_SEARCHCOUNT)
&& msgbuf != NULL) {
- search_stat(dirc, &pos, show_top_bot_msg, msgbuf,
- (count != 1
- || has_offset
- || (!(fdo_flags & FDO_SEARCH)
- && hasFolding(curwin->w_cursor.lnum, NULL, NULL))));
+ cmdline_search_stat(dirc, &pos, &curwin->w_cursor,
+ show_top_bot_msg, msgbuf,
+ (count != 1 || has_offset
+ || (!(fdo_flags & FDO_SEARCH)
+ && hasFolding(curwin->w_cursor.lnum, NULL,
+ NULL))),
+ SEARCH_STAT_DEF_MAX_COUNT,
+ SEARCH_STAT_DEF_TIMEOUT);
}
// The search command can be followed by a ';' to do another search.
@@ -1715,10 +1715,10 @@ static void find_mps_values(int *initc, int *findc, bool *backwards,
* '#' look for preprocessor directives
* 'R' look for raw string start: R"delim(text)delim" (only backwards)
*
- * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
- * FM_FORWARD search forwards (when initc is '/', '*' or '#')
- * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0)
- * FM_SKIPCOMM skip comments (not implemented yet!)
+ * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
+ * FM_FORWARD search forwards (when initc is '/', '*' or '#')
+ * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0)
+ * FM_SKIPCOMM skip comments (not implemented yet!)
*
* "oap" is only used to set oap->motion_type for a linewise motion, it can be
* NULL
@@ -3003,7 +3003,7 @@ current_word(
/*
* If the start is on white space, and white space should be included
- * (" word"), or start is not on white space, and white space should
+ * (" word"), or start is not on white space, and white space should
* not be included ("word"), find end of word.
*/
if ((cls() == 0) == include) {
@@ -3012,8 +3012,8 @@ current_word(
} else {
/*
* If the start is not on white space, and white space should be
- * included ("word "), or start is on white space and white
- * space should not be included (" "), find start of word.
+ * included ("word "), or start is on white space and white
+ * space should not be included (" "), find start of word.
* If we end up in the first column of the next line (single char
* word) back up to end of the line.
*/
@@ -4347,121 +4347,287 @@ int linewhite(linenr_T lnum)
}
// Add the search count "[3/19]" to "msgbuf".
-// When "recompute" is true Always recompute the numbers.
-static void search_stat(int dirc, pos_T *pos,
- bool show_top_bot_msg, char_u *msgbuf, bool recompute)
+// See update_search_stat() for other arguments.
+static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos,
+ bool show_top_bot_msg, char_u *msgbuf,
+ bool recompute, int maxcount, long timeout)
+{
+ searchstat_T stat;
+
+ update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount,
+ timeout);
+ if (stat.cur > 0) {
+ char t[SEARCH_STAT_BUF_LEN];
+
+ if (curwin->w_p_rl && *curwin->w_p_rlc == 's') {
+ if (stat.incomplete == 1) {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
+ } else if (stat.cnt > maxcount && stat.cur > maxcount) {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
+ maxcount, maxcount);
+ } else if (stat.cnt > maxcount) {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]",
+ maxcount, stat.cur);
+ } else {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
+ stat.cnt, stat.cur);
+ }
+ } else {
+ if (stat.incomplete == 1) {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
+ } else if (stat.cnt > maxcount && stat.cur > maxcount) {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
+ maxcount, maxcount);
+ } else if (stat.cnt > maxcount) {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]",
+ stat.cur, maxcount);
+ } else {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
+ stat.cur, stat.cnt);
+ }
+ }
+
+ size_t len = strlen(t);
+ if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) {
+ memmove(t + 2, t, len);
+ t[0] = 'W';
+ t[1] = ' ';
+ len += 2;
+ }
+
+ memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
+ if (dirc == '?' && stat.cur == maxcount + 1) {
+ stat.cur = -1;
+ }
+
+ // keep the message even after redraw, but don't put in history
+ msg_hist_off = true;
+ msg_ext_set_kind("search_count");
+ give_warning(msgbuf, false);
+ msg_hist_off = false;
+ }
+}
+
+// Add the search count information to "stat".
+// "stat" must not be NULL.
+// When "recompute" is true always recompute the numbers.
+// dirc == 0: don't find the next/previous match (only set the result to "stat")
+// dirc == '/': find the next match
+// dirc == '?': find the previous match
+static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos,
+ searchstat_T *stat, bool recompute, int maxcount,
+ long timeout)
{
- int save_ws = p_ws;
- int wraparound = false;
- pos_T p = (*pos);
- static pos_T lastpos = { 0, 0, 0 };
+ int save_ws = p_ws;
+ bool wraparound = false;
+ pos_T p = (*pos);
+ static pos_T lastpos = { 0, 0, 0 };
static int cur = 0;
static int cnt = 0;
+ static bool exact_match = false;
+ static int incomplete = 0;
+ static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT;
static int chgtick = 0;
static char_u *lastpat = NULL;
static buf_T *lbuf = NULL;
- proftime_T start;
-#define OUT_OF_TIME 999
+ proftime_T start;
+ memset(stat, 0, sizeof(searchstat_T));
+
+ if (dirc == 0 && !recompute && !EMPTY_POS(lastpos)) {
+ stat->cur = cur;
+ stat->cnt = cnt;
+ stat->exact_match = exact_match;
+ stat->incomplete = incomplete;
+ stat->last_maxcount = last_maxcount;
+ return;
+ }
+ last_maxcount = maxcount;
wraparound = ((dirc == '?' && lt(lastpos, p))
|| (dirc == '/' && lt(p, lastpos)));
// If anything relevant changed the count has to be recomputed.
// STRNICMP ignores case, but we should not ignore case.
// Unfortunately, there is no STRNICMP function.
+ // XXX: above comment should be "no MB_STRCMP function" ?
if (!(chgtick == buf_get_changedtick(curbuf)
&& lastpat != NULL // supress clang/NULL passed as nonnull parameter
&& STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0
&& STRLEN(lastpat) == STRLEN(spats[last_idx].pat)
- && equalpos(lastpos, curwin->w_cursor)
+ && equalpos(lastpos, *cursor_pos)
&& lbuf == curbuf)
- || wraparound || cur < 0 || cur > 99 || recompute) {
+ || wraparound || cur < 0 || (maxcount > 0 && cur > maxcount)
+ || recompute) {
cur = 0;
cnt = 0;
+ exact_match = false;
+ incomplete = 0;
clearpos(&lastpos);
lbuf = curbuf;
}
- if (equalpos(lastpos, curwin->w_cursor) && !wraparound
- && (dirc == '/' ? cur < cnt : cur > 0)) {
- cur += dirc == '/' ? 1 : -1;
+ if (equalpos(lastpos, *cursor_pos) && !wraparound
+ && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 0)) {
+ cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1;
} else {
+ bool done_search = false;
+ pos_T endpos = { 0, 0, 0 };
p_ws = false;
- start = profile_setlimit(20L);
- while (!got_int && searchit(curwin, curbuf, &lastpos, NULL,
+ if (timeout > 0) {
+ start = profile_setlimit(timeout);
+ }
+ while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST,
NULL) != FAIL) {
+ done_search = true;
// Stop after passing the time limit.
- if (profile_passed_limit(start)) {
- cnt = OUT_OF_TIME;
- cur = OUT_OF_TIME;
+ if (timeout > 0 && profile_passed_limit(start)) {
+ incomplete = 1;
break;
}
cnt++;
if (ltoreq(lastpos, p)) {
- cur++;
+ cur = cnt;
+ if (lt(p, endpos)) {
+ exact_match = true;
+ }
}
fast_breakcheck();
- if (cnt > 99) {
+ if (maxcount > 0 && cnt > maxcount) {
+ incomplete = 2; // max count exceeded
break;
}
}
if (got_int) {
cur = -1; // abort
}
+ if (done_search) {
+ xfree(lastpat);
+ lastpat = vim_strsave(spats[last_idx].pat);
+ chgtick = buf_get_changedtick(curbuf);
+ lbuf = curbuf;
+ lastpos = p;
+ }
}
- if (cur > 0) {
- char t[SEARCH_STAT_BUF_LEN] = "";
- int len;
+ stat->cur = cur;
+ stat->cnt = cnt;
+ stat->exact_match = exact_match;
+ stat->incomplete = incomplete;
+ stat->last_maxcount = last_maxcount;
+ p_ws = save_ws;
+}
- if (curwin->w_p_rl && *curwin->w_p_rlc == 's') {
- if (cur == OUT_OF_TIME) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?\?/?]");
- } else if (cnt > 99 && cur > 99) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]");
- } else if (cnt > 99) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/%d]", cur);
- } else {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", cnt, cur);
+// "searchcount()" function
+void f_searchcount(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ pos_T pos = curwin->w_cursor;
+ char_u *pattern = NULL;
+ int maxcount = SEARCH_STAT_DEF_MAX_COUNT;
+ long timeout = SEARCH_STAT_DEF_TIMEOUT;
+ bool recompute = true;
+ searchstat_T stat;
+
+ tv_dict_alloc_ret(rettv);
+
+ if (shortmess(SHM_SEARCHCOUNT)) { // 'shortmess' contains 'S' flag
+ recompute = true;
+ }
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ dict_T *dict;
+ dictitem_T *di;
+ listitem_T *li;
+ bool error = false;
+
+ if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+ dict = argvars[0].vval.v_dict;
+ di = tv_dict_find(dict, (const char *)"timeout", -1);
+ if (di != NULL) {
+ timeout = (long)tv_get_number_chk(&di->di_tv, &error);
+ if (error) {
+ return;
}
- } else {
- if (cur == OUT_OF_TIME) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
- } else if (cnt > 99 && cur > 99) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]");
- } else if (cnt > 99) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>99]", cur);
- } else {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", cur, cnt);
+ }
+ di = tv_dict_find(dict, (const char *)"maxcount", -1);
+ if (di != NULL) {
+ maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
+ if (error) {
+ return;
}
}
-
- len = STRLEN(t);
- if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) {
- memmove(t + 2, t, len);
- t[0] = 'W';
- t[1] = ' ';
- len += 2;
+ di = tv_dict_find(dict, (const char *)"recompute", -1);
+ if (di != NULL) {
+ recompute = tv_get_number_chk(&di->di_tv, &error);
+ if (error) {
+ return;
+ }
}
+ di = tv_dict_find(dict, (const char *)"pattern", -1);
+ if (di != NULL) {
+ pattern = (char_u *)tv_get_string_chk(&di->di_tv);
+ if (pattern == NULL) {
+ return;
+ }
+ }
+ di = tv_dict_find(dict, (const char *)"pos", -1);
+ if (di != NULL) {
+ if (di->di_tv.v_type != VAR_LIST) {
+ EMSG2(_(e_invarg2), "pos");
+ return;
+ }
+ if (tv_list_len(di->di_tv.vval.v_list) != 3) {
+ EMSG2(_(e_invarg2), "List format should be [lnum, col, off]");
+ return;
+ }
+ li = tv_list_find(di->di_tv.vval.v_list, 0L);
+ if (li != NULL) {
+ pos.lnum = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
+ if (error) {
+ return;
+ }
+ }
+ li = tv_list_find(di->di_tv.vval.v_list, 1L);
+ if (li != NULL) {
+ pos.col = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error) - 1;
+ if (error) {
+ return;
+ }
+ }
+ li = tv_list_find(di->di_tv.vval.v_list, 2L);
+ if (li != NULL) {
+ pos.coladd = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
+ if (error) {
+ return;
+ }
+ }
+ }
+ }
- memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
- if (dirc == '?' && cur == 100) {
- cur = -1;
+ save_last_search_pattern();
+ if (pattern != NULL) {
+ if (*pattern == NUL) {
+ goto the_end;
}
+ xfree(spats[last_idx].pat);
+ spats[last_idx].pat = vim_strsave(pattern);
+ }
+ if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL) {
+ goto the_end; // the previous pattern was never defined
+ }
- xfree(lastpat);
- lastpat = vim_strsave(spats[last_idx].pat);
- chgtick = buf_get_changedtick(curbuf);
- lbuf = curbuf;
- lastpos = p;
+ update_search_stat(0, &pos, &pos, &stat, recompute, maxcount, timeout);
- // keep the message even after redraw, but don't put in history
- msg_hist_off = true;
- msg_ext_set_kind("search_count");
- give_warning(msgbuf, false);
- msg_hist_off = false;
- }
- p_ws = save_ws;
+ tv_dict_add_nr(rettv->vval.v_dict, S_LEN("current"), stat.cur);
+ tv_dict_add_nr(rettv->vval.v_dict, S_LEN("total"), stat.cnt);
+ tv_dict_add_nr(rettv->vval.v_dict, S_LEN("exact_match"), stat.exact_match);
+ tv_dict_add_nr(rettv->vval.v_dict, S_LEN("incomplete"), stat.incomplete);
+ tv_dict_add_nr(rettv->vval.v_dict, S_LEN("maxcount"), stat.last_maxcount);
+
+the_end:
+ restore_last_search_pattern();
}
/*
diff --git a/src/nvim/search.h b/src/nvim/search.h
index 0366aee8a1..98ddaa5eeb 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -6,6 +6,7 @@
#include "nvim/vim.h"
#include "nvim/buffer_defs.h"
+#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
#include "nvim/normal.h"
#include "nvim/os/time.h"
@@ -49,6 +50,11 @@
#define RE_BOTH 2 /* save pat in both patterns */
#define RE_LAST 2 /* use last used pattern if "pat" is NULL */
+// Values for searchcount()
+#define SEARCH_STAT_DEF_TIMEOUT 40L
+#define SEARCH_STAT_DEF_MAX_COUNT 99
+#define SEARCH_STAT_BUF_LEN 12
+
/// Structure containing offset definition for the last search pattern
///
/// @note Only offset for the last search pattern is used, not for the last
@@ -78,6 +84,16 @@ typedef struct {
int sa_wrapped; ///< search wrapped around
} searchit_arg_T;
+typedef struct searchstat
+{
+ int cur; // current position of found words
+ int cnt; // total count of found words
+ int exact_match; // TRUE if matched exactly on specified position
+ int incomplete; // 0: search was fully completed
+ // 1: recomputing was timed out
+ // 2: max count exceeded
+ int last_maxcount; // the max count of the last search
+} searchstat_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.h.generated.h"
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index f6dc3a04a7..d1428b0117 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -1677,6 +1677,7 @@ static void int_wordlist_spl(char_u *fname)
// Allocate a new slang_T for language "lang". "lang" can be NULL.
// Caller must fill "sl_next".
slang_T *slang_alloc(char_u *lang)
+ FUNC_ATTR_NONNULL_RET
{
slang_T *lp = xcalloc(1, sizeof(slang_T));
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index a6310344e9..f0e48013b2 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -1143,7 +1143,6 @@ static int find_tagfunc_tags(
typval_T args[4];
typval_T rettv;
char_u flagString[4];
- dict_T *d;
taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx];
if (*curbuf->b_p_tfu == NUL) {
@@ -1156,7 +1155,7 @@ static int find_tagfunc_tags(
args[1].vval.v_string = flagString;
// create 'info' dict argument
- d = tv_dict_alloc();
+ dict_T *const d = tv_dict_alloc_lock(VAR_FIXED);
if (tag->user_data != NULL) {
tv_dict_add_str(d, S_LEN("user_data"), (const char *)tag->user_data);
}
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index e50602ccad..b5c50b5894 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -10,6 +10,7 @@ source test_cursor_func.vim
source test_ex_equal.vim
source test_ex_undo.vim
source test_ex_z.vim
+source test_ex_mode.vim
source test_execute_func.vim
source test_expand_func.vim
source test_feedkeys.vim
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 5611560b1b..bb84fa498e 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -190,7 +190,6 @@ func Test_autocmd_bufunload_avoiding_SEGV_02()
normal! i1
call assert_fails('edit a.txt', 'E517:')
- call feedkeys("\<CR>")
autocmd! test_autocmd_bufunload
augroup! test_autocmd_bufunload
@@ -452,6 +451,27 @@ func Test_autocmd_bufwipe_in_SessLoadPost()
endfor
endfunc
+" Using :blast and :ball for many events caused a crash, because b_nwindows was
+" not incremented correctly.
+func Test_autocmd_blast_badd()
+ let content =<< trim [CODE]
+ au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* blast
+ edit foo1
+ au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* ball
+ edit foo2
+ call writefile(['OK'], 'Xerrors')
+ qall
+ [CODE]
+
+ call writefile(content, 'XblastBall')
+ call system(GetVimCommand() .. ' --clean -S XblastBall')
+ " call assert_match('OK', readfile('Xerrors')->join())
+ call assert_match('OK', join(readfile('Xerrors')))
+
+ call delete('XblastBall')
+ call delete('Xerrors')
+endfunc
+
" SEGV occurs in older versions.
func Test_autocmd_bufwipe_in_SessLoadPost2()
tabnew
@@ -1949,6 +1969,26 @@ func Test_autocmd_window()
%bw!
endfunc
+" Test for trying to close the tab that has the temporary window for exeucing
+" an autocmd.
+func Test_close_autocmd_tab()
+ edit one.txt
+ tabnew two.txt
+ augroup aucmd_win_test
+ au!
+ au BufEnter * if expand('<afile>') == 'one.txt' | tabfirst | tabonly | endif
+ augroup END
+
+ call assert_fails('doautoall BufEnter', 'E813:')
+
+ tabonly
+ augroup aucmd_win_test
+ au!
+ augroup END
+ augroup! aucmd_win_test
+ %bwipe!
+endfunc
+
func Test_autocmd_closes_window()
au BufNew,BufWinLeave * e %e
file yyy
@@ -1960,4 +2000,13 @@ func Test_autocmd_closes_window()
au! BufWinLeave
endfunc
+func Test_autocmd_closing_cmdwin()
+ au BufWinLeave * nested q
+ call assert_fails("norm 7q?\n", 'E855:')
+
+ au! BufWinLeave
+ new
+ only
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 489b2477e6..34126b49fa 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -477,7 +477,7 @@ func Test_expand_star_star()
call delete('a', 'rf')
endfunc
-func Test_paste_in_cmdline()
+func Test_cmdline_paste()
let @a = "def"
call feedkeys(":abc \<C-R>a ghi\<C-B>\"\<CR>", 'tx')
call assert_equal('"abc def ghi', @:)
@@ -517,18 +517,38 @@ func Test_paste_in_cmdline()
bwipe!
endfunc
-func Test_remove_char_in_cmdline()
- call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"abc ef', @:)
+func Test_cmdline_remove_char()
+ let encoding_save = &encoding
+
+ " for e in ['utf8', 'latin1']
+ for e in ['utf8']
+ exe 'set encoding=' . e
+
+ call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"abc ef', @:, e)
+
+ call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"abcdef', @:)
+
+ call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"abc ghi', @:, e)
+
+ call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"def', @:, e)
+ endfor
- call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"abcdef', @:)
+ let &encoding = encoding_save
+endfunc
- call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"abc ghi', @:)
+func Test_cmdline_keymap_ctrl_hat()
+ if !has('keymap')
+ return
+ endif
- call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"def', @:)
+ set keymap=esperanto
+ call feedkeys(":\"Jxauxdo \<C-^>Jxauxdo \<C-^>Jxauxdo\<CR>", 'tx')
+ call assert_equal('"Jxauxdo Ĵaŭdo Jxauxdo', @:)
+ set keymap=
endfunc
func Test_illegal_address1()
@@ -615,10 +635,20 @@ func Test_cmdline_complete_bang()
endfunc
funct Test_cmdline_complete_languages()
+ let lang = substitute(execute('language time'), '.*"\(.*\)"$', '\1', '')
+ call assert_equal(lang, v:lc_time)
+
+ let lang = substitute(execute('language ctype'), '.*"\(.*\)"$', '\1', '')
+ call assert_equal(lang, v:ctype)
+
+ let lang = substitute(execute('language collate'), '.*"\(.*\)"$', '\1', '')
+ call assert_equal(lang, v:collate)
+
let lang = substitute(execute('language messages'), '.*"\(.*\)"$', '\1', '')
+ call assert_equal(lang, v:lang)
call feedkeys(":language \<c-a>\<c-b>\"\<cr>", 'tx')
- call assert_match('^"language .*\<ctype\>.*\<messages\>.*\<time\>', @:)
+ call assert_match('^"language .*\<collate\>.*\<ctype\>.*\<messages\>.*\<time\>', @:)
if has('unix')
" TODO: these tests don't work on Windows. lang appears to be 'C'
@@ -633,6 +663,9 @@ funct Test_cmdline_complete_languages()
call feedkeys(":language time \<c-a>\<c-b>\"\<cr>", 'tx')
call assert_match('^"language .*\<' . lang . '\>', @:)
+
+ call feedkeys(":language collate \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<' . lang . '\>', @:)
endif
endfunc
@@ -850,20 +883,20 @@ func Test_cmdline_overstrike()
" Test overstrike in the middle of the command line.
call feedkeys(":\"01234\<home>\<right>\<right>ab\<right>\<insert>cd\<enter>", 'xt')
- call assert_equal('"0ab1cd4', @:)
+ call assert_equal('"0ab1cd4', @:, e)
" Test overstrike going beyond end of command line.
call feedkeys(":\"01234\<home>\<right>\<right>ab\<right>\<insert>cdefgh\<enter>", 'xt')
- call assert_equal('"0ab1cdefgh', @:)
+ call assert_equal('"0ab1cdefgh', @:, e)
" Test toggling insert/overstrike a few times.
call feedkeys(":\"01234\<home>\<right>ab\<right>\<insert>cd\<right>\<insert>ef\<enter>", 'xt')
- call assert_equal('"ab0cd3ef4', @:)
+ call assert_equal('"ab0cd3ef4', @:, e)
endfor
" Test overstrike with multi-byte characters.
call feedkeys(":\"テキストエディタ\<home>\<right>\<right>ab\<right>\<insert>cd\<enter>", 'xt')
- call assert_equal('"テabキcdエディタ', @:)
+ call assert_equal('"テabキcdエディタ', @:, e)
let &encoding = encoding_save
endfunc
@@ -972,6 +1005,25 @@ func Test_buffers_lastused()
bwipeout bufc
endfunc
+" Test for CmdwinEnter autocmd
+func Test_cmdwin_autocmd()
+ CheckFeature cmdwin
+
+ augroup CmdWin
+ au!
+ autocmd BufLeave * if &buftype == '' | update | endif
+ autocmd CmdwinEnter * startinsert
+ augroup END
+
+ call assert_fails('call feedkeys("q:xyz\<CR>", "xt")', 'E492:')
+ call assert_equal('xyz', @:)
+
+ augroup CmdWin
+ au!
+ augroup END
+ augroup! CmdWin
+endfunc
+
func Test_cmdlineclear_tabenter()
" See test/functional/legacy/cmdline_spec.lua
CheckScreendump
@@ -1020,4 +1072,52 @@ func Test_read_shellcmd()
endif
endfunc
+" Test for recalling newer or older cmdline from history with <Up>, <Down>,
+" <S-Up>, <S-Down>, <PageUp>, <PageDown>, <C-p>, or <C-n>.
+func Test_recalling_cmdline()
+ CheckFeature cmdline_hist
+
+ let g:cmdlines = []
+ cnoremap <Plug>(save-cmdline) <Cmd>let g:cmdlines += [getcmdline()]<CR>
+
+ let histories = [
+ \ {'name': 'cmd', 'enter': ':', 'exit': "\<Esc>"},
+ \ {'name': 'search', 'enter': '/', 'exit': "\<Esc>"},
+ \ {'name': 'expr', 'enter': ":\<C-r>=", 'exit': "\<Esc>\<Esc>"},
+ \ {'name': 'input', 'enter': ":call input('')\<CR>", 'exit': "\<CR>"},
+ "\ TODO: {'name': 'debug', ...}
+ \]
+ let keypairs = [
+ \ {'older': "\<Up>", 'newer': "\<Down>", 'prefixmatch': v:true},
+ \ {'older': "\<S-Up>", 'newer': "\<S-Down>", 'prefixmatch': v:false},
+ \ {'older': "\<PageUp>", 'newer': "\<PageDown>", 'prefixmatch': v:false},
+ \ {'older': "\<C-p>", 'newer': "\<C-n>", 'prefixmatch': v:false},
+ \]
+ let prefix = 'vi'
+ for h in histories
+ call histadd(h.name, 'vim')
+ call histadd(h.name, 'virtue')
+ call histadd(h.name, 'Virgo')
+ call histadd(h.name, 'vogue')
+ call histadd(h.name, 'emacs')
+ for k in keypairs
+ let g:cmdlines = []
+ let keyseqs = h.enter
+ \ .. prefix
+ \ .. repeat(k.older .. "\<Plug>(save-cmdline)", 2)
+ \ .. repeat(k.newer .. "\<Plug>(save-cmdline)", 2)
+ \ .. h.exit
+ call feedkeys(keyseqs, 'xt')
+ call histdel(h.name, -1) " delete the history added by feedkeys above
+ let expect = k.prefixmatch
+ \ ? ['virtue', 'vim', 'virtue', prefix]
+ \ : ['emacs', 'vogue', 'emacs', prefix]
+ call assert_equal(expect, g:cmdlines)
+ endfor
+ endfor
+
+ unlet g:cmdlines
+ cunmap <Plug>(save-cmdline)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim
new file mode 100644
index 0000000000..f70cb261e0
--- /dev/null
+++ b/src/nvim/testdir/test_ex_mode.vim
@@ -0,0 +1,82 @@
+" Test editing line in Ex mode (see :help Q and :help gQ).
+
+" Helper function to test editing line in Q Ex mode
+func Ex_Q(cmd)
+ " Is there a simpler way to test editing Ex line?
+ call feedkeys("Q"
+ \ .. "let s:test_ex =<< END\<CR>"
+ \ .. a:cmd .. "\<CR>"
+ \ .. "END\<CR>"
+ \ .. "visual\<CR>", 'tx')
+ return s:test_ex[0]
+endfunc
+
+" Helper function to test editing line in gQ Ex mode
+func Ex_gQ(cmd)
+ call feedkeys("gQ" .. a:cmd .. "\<C-b>\"\<CR>", 'tx')
+ let ret = @:[1:] " Remove leading quote.
+ call feedkeys("visual\<CR>", 'tx')
+ return ret
+endfunc
+
+" Helper function to test editing line with both Q and gQ Ex mode.
+func Ex(cmd)
+ return [Ex_Q(a:cmd), Ex_gQ(a:cmd)]
+endfunc
+
+" Test editing line in Ex mode (both Q and gQ)
+func Test_ex_mode()
+ throw 'skipped: TODO: '
+ let encoding_save = &encoding
+ set sw=2
+
+ " for e in ['utf8', 'latin1']
+ for e in ['utf8']
+ exe 'set encoding=' . e
+
+ call assert_equal(['bar', 'bar'], Ex("foo bar\<C-u>bar"), e)
+ call assert_equal(["1\<C-u>2", "1\<C-u>2"], Ex("1\<C-v>\<C-u>2"), e)
+ call assert_equal(["1\<C-b>2\<C-e>3", '213'], Ex("1\<C-b>2\<C-e>3"), e)
+ call assert_equal(['0123', '2013'], Ex("01\<Home>2\<End>3"), e)
+ call assert_equal(['0123', '0213'], Ex("01\<Left>2\<Right>3"), e)
+ call assert_equal(['01234', '0342'], Ex("012\<Left>\<Left>\<Insert>3\<Insert>4"), e)
+ call assert_equal(["foo bar\<C-w>", 'foo '], Ex("foo bar\<C-w>"), e)
+ call assert_equal(['foo', 'foo'], Ex("fooba\<Del>\<Del>"), e)
+ call assert_equal(["foo\tbar", 'foobar'], Ex("foo\<Tab>bar"), e)
+ call assert_equal(["abbrev\t", 'abbreviate'], Ex("abbrev\<Tab>"), e)
+ call assert_equal([' 1', "1\<C-t>\<C-t>"], Ex("1\<C-t>\<C-t>"), e)
+ call assert_equal([' 1', "1\<C-t>\<C-t>"], Ex("1\<C-t>\<C-t>\<C-d>"), e)
+ call assert_equal([' foo', ' foo'], Ex(" foo\<C-d>"), e)
+ call assert_equal(['foo', ' foo0'], Ex(" foo0\<C-d>"), e)
+ call assert_equal(['foo', ' foo^'], Ex(" foo^\<C-d>"), e)
+ endfor
+
+ set sw&
+ let &encoding = encoding_save
+endfunc
+
+func Test_ex_mode_errors()
+ " Not allowed to enter ex mode when text is locked
+ au InsertCharPre <buffer> normal! gQ<CR>
+ let caught_e523 = 0
+ try
+ call feedkeys("ix\<esc>", 'xt')
+ catch /^Vim\%((\a\+)\)\=:E523/ " catch E523
+ let caught_e523 = 1
+ endtry
+ call assert_equal(1, caught_e523)
+ au! InsertCharPre
+
+ new
+ au CmdLineEnter * call ExEnterFunc()
+ func ExEnterFunc()
+
+ endfunc
+ call feedkeys("gQvi\r", 'xt')
+
+ au! CmdLineEnter
+ delfunc ExEnterFunc
+ quit
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
index 4c7452fe69..ed2bb2c06b 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/src/nvim/testdir/test_excmd.vim
@@ -54,6 +54,132 @@ func Test_buffers_lastused()
bwipeout bufc
endfunc
+" Test for the :copy command
+func Test_copy()
+ new
+
+ call setline(1, ['L1', 'L2', 'L3', 'L4'])
+ " copy lines in a range to inside the range
+ 1,3copy 2
+ call assert_equal(['L1', 'L2', 'L1', 'L2', 'L3', 'L3', 'L4'], getline(1, 7))
+
+ close!
+endfunc
+
+" Test for the :file command
+func Test_file_cmd()
+ call assert_fails('3file', 'E474:')
+ call assert_fails('0,0file', 'E474:')
+ call assert_fails('0file abc', 'E474:')
+endfunc
+
+" Test for the :drop command
+func Test_drop_cmd()
+ call writefile(['L1', 'L2'], 'Xfile')
+ enew | only
+ drop Xfile
+ call assert_equal('L2', getline(2))
+ " Test for switching to an existing window
+ below new
+ drop Xfile
+ call assert_equal(1, winnr())
+ " Test for splitting the current window
+ enew | only
+ set modified
+ drop Xfile
+ call assert_equal(2, winnr('$'))
+ " Check for setting the argument list
+ call assert_equal(['Xfile'], argv())
+ enew | only!
+ call delete('Xfile')
+endfunc
+
+" Test for the :append command
+func Test_append_cmd()
+ new
+ call setline(1, [' L1'])
+ call feedkeys(":append\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L2', ' L3'], getline(1, '$'))
+ %delete _
+ " append after a specific line
+ call setline(1, [' L1', ' L2', ' L3'])
+ call feedkeys(":2append\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L2', ' L4', ' L5', ' L3'], getline(1, '$'))
+ %delete _
+ " append with toggling 'autoindent'
+ call setline(1, [' L1'])
+ call feedkeys(":append!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L2', ' L3'], getline(1, '$'))
+ call assert_false(&autoindent)
+ %delete _
+ " append with 'autoindent' set and toggling 'autoindent'
+ set autoindent
+ call setline(1, [' L1'])
+ call feedkeys(":append!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L2', ' L3'], getline(1, '$'))
+ call assert_true(&autoindent)
+ set autoindent&
+ close!
+endfunc
+
+" Test for the :insert command
+func Test_insert_cmd()
+ set noautoindent " test assumes noautoindent, but it's on by default in Nvim
+ new
+ call setline(1, [' L1'])
+ call feedkeys(":insert\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L2', ' L3', ' L1'], getline(1, '$'))
+ %delete _
+ " insert before a specific line
+ call setline(1, [' L1', ' L2', ' L3'])
+ call feedkeys(":2insert\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L4', ' L5', ' L2', ' L3'], getline(1, '$'))
+ %delete _
+ " insert with toggling 'autoindent'
+ call setline(1, [' L1'])
+ call feedkeys(":insert!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L2', ' L3', ' L1'], getline(1, '$'))
+ call assert_false(&autoindent)
+ %delete _
+ " insert with 'autoindent' set and toggling 'autoindent'
+ set autoindent
+ call setline(1, [' L1'])
+ call feedkeys(":insert!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L2', ' L3', ' L1'], getline(1, '$'))
+ call assert_true(&autoindent)
+ set autoindent&
+ close!
+endfunc
+
+" Test for the :change command
+func Test_change_cmd()
+ set noautoindent " test assumes noautoindent, but it's on by default in Nvim
+ new
+ call setline(1, [' L1', 'L2', 'L3'])
+ call feedkeys(":change\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$'))
+ %delete _
+ " change a specific line
+ call setline(1, [' L1', ' L2', ' L3'])
+ call feedkeys(":2change\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L4', ' L5', ' L3'], getline(1, '$'))
+ %delete _
+ " change with toggling 'autoindent'
+ call setline(1, [' L1', 'L2', 'L3'])
+ call feedkeys(":change!\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$'))
+ call assert_false(&autoindent)
+ %delete _
+ " change with 'autoindent' set and toggling 'autoindent'
+ set autoindent
+ call setline(1, [' L1', 'L2', 'L3'])
+ call feedkeys(":change!\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$'))
+ call assert_true(&autoindent)
+ set autoindent&
+ close!
+endfunc
+
" Test for the :confirm command dialog
func Test_confirm_cmd()
CheckNotGui
diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim
index eb84a6739d..15ba894dbe 100644
--- a/src/nvim/testdir/test_execute_func.vim
+++ b/src/nvim/testdir/test_execute_func.vim
@@ -1,5 +1,7 @@
" test execute()
+source view_util.vim
+
func NestedEval()
let nested = execute('echo "nested\nlines"')
echo 'got: "' . nested . '"'
@@ -82,3 +84,54 @@ func Test_execute_not_silent()
endfor
call assert_equal('xyz ', text2)
endfunc
+
+func Test_win_execute()
+ let thiswin = win_getid()
+ new
+ let otherwin = win_getid()
+ call setline(1, 'the new window')
+ call win_gotoid(thiswin)
+ let line = win_execute(otherwin, 'echo getline(1)')
+ call assert_match('the new window', line)
+ let line = win_execute(134343, 'echo getline(1)')
+ call assert_equal('', line)
+
+ if has('textprop')
+ let popupwin = popup_create('the popup win', {'line': 2, 'col': 3})
+ redraw
+ let line = win_execute(popupwin, 'echo getline(1)')
+ call assert_match('the popup win', line)
+
+ call popup_close(popupwin)
+ endif
+
+ call win_gotoid(otherwin)
+ bwipe!
+endfunc
+
+func Test_win_execute_update_ruler()
+ enew
+ call setline(1, range(500))
+ 20
+ split
+ let winid = win_getid()
+ set ruler
+ wincmd w
+ let height = winheight(winid)
+ redraw
+ call assert_match('20,1', Screenline(height + 1))
+ let line = win_execute(winid, 'call cursor(100, 1)')
+ redraw
+ call assert_match('100,1', Screenline(height + 1))
+
+ bwipe!
+endfunc
+
+func Test_win_execute_other_tab()
+ let thiswin = win_getid()
+ tabnew
+ call win_execute(thiswin, 'let xyz = 1')
+ call assert_equal(1, xyz)
+ tabclose
+ unlet xyz
+endfunc
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 6b1e484c3a..056b953d0b 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -148,7 +148,7 @@ let s:filename_checks = {
\ 'dnsmasq': ['/etc/dnsmasq.conf', '/etc/dnsmasq.d/file', 'any/etc/dnsmasq.conf', 'any/etc/dnsmasq.d/file'],
\ 'dockerfile': ['Containerfile', 'Dockerfile', 'file.Dockerfile'],
\ 'dosbatch': ['file.bat', 'file.sys'],
- \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/pacman.conf', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file'],
+ \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/pacman.conf', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'],
\ 'dot': ['file.dot', 'file.gv'],
\ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'],
\ 'dsl': ['file.dsl'],
diff --git a/src/nvim/testdir/test_fnameescape.vim b/src/nvim/testdir/test_fnameescape.vim
index 5382b89aa6..0bafdc29fb 100644
--- a/src/nvim/testdir/test_fnameescape.vim
+++ b/src/nvim/testdir/test_fnameescape.vim
@@ -18,4 +18,10 @@ func Test_fnameescape()
endtry
call assert_true(status, "ExclamationMark")
call delete(fname)
+
+ call assert_equal('\-', fnameescape('-'))
+ call assert_equal('\+', fnameescape('+'))
+ call assert_equal('\>', fnameescape('>'))
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ga.vim b/src/nvim/testdir/test_ga.vim
index 87f1382342..ce31edfc7a 100644
--- a/src/nvim/testdir/test_ga.vim
+++ b/src/nvim/testdir/test_ga.vim
@@ -18,6 +18,7 @@ func Test_ga_command()
call assert_equal("\nNUL", Do_ga(''))
call assert_equal("\n<^A> 1, Hex 01, Oct 001, Digr SH", Do_ga("\x01"))
call assert_equal("\n<^I> 9, Hex 09, Oct 011, Digr HT", Do_ga("\t"))
+ call assert_equal("\n<^@> 0, Hex 00, Octal 000", Do_ga("\n"))
call assert_equal("\n<e> 101, Hex 65, Octal 145", Do_ga('e'))
@@ -30,5 +31,13 @@ func Test_ga_command()
call assert_equal("\n<e> 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401", Do_ga("e\u0301"))
call assert_equal("\n<e> 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401 < ̱> 817, Hex 0331, Octal 1461", Do_ga("e\u0301\u0331"))
call assert_equal("\n<e> 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401 < ̱> 817, Hex 0331, Octal 1461 < ̸> 824, Hex 0338, Octal 1470", Do_ga("e\u0301\u0331\u0338"))
+
+ " When using Mac fileformat, CR instead of NL is used for line termination
+ enew!
+ set fileformat=mac
+ call assert_equal("\n<^J> 10, Hex 0a, Oct 012, Digr NU", Do_ga("\r"))
+
bwipe!
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim
index 7ccf2812ff..2de2c412de 100644
--- a/src/nvim/testdir/test_global.vim
+++ b/src/nvim/testdir/test_global.vim
@@ -29,3 +29,11 @@ func Test_nested_global()
call assert_equal(['nothing', '++found', 'found bad', 'bad'], getline(1, 4))
bwipe!
endfunc
+
+func Test_global_error()
+ call assert_fails('g\\a', 'E10:')
+ call assert_fails('g', 'E148:')
+ call assert_fails('g/\(/y', 'E476:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_move.vim b/src/nvim/testdir/test_move.vim
index d774c93dbd..f666a904b0 100644
--- a/src/nvim/testdir/test_move.vim
+++ b/src/nvim/testdir/test_move.vim
@@ -35,6 +35,11 @@ func Test_move()
call assert_fails('1,2move 1', 'E134')
call assert_fails('2,3move 2', 'E134')
+ call assert_fails("move -100", 'E16:')
+ call assert_fails("move +100", 'E16:')
+ call assert_fails('move', 'E16:')
%bwipeout!
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index 5aef33cb09..8796af7a20 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -629,6 +629,25 @@ func Test_visualbell()
set belloff=all
endfunc
+" Test for the 'write' option
+func Test_write()
+ new
+ call setline(1, ['L1'])
+ set nowrite
+ call assert_fails('write Xfile', 'E142:')
+ set write
+ close!
+endfunc
+
+" Test for 'buftype' option
+func Test_buftype()
+ new
+ call setline(1, ['L1'])
+ set buftype=nowrite
+ call assert_fails('write', 'E382:')
+ close!
+endfunc
+
" Test for setting option values using v:false and v:true
func Test_opt_boolean()
set number&
diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim
index 11c6489ca2..335a51268d 100644
--- a/src/nvim/testdir/test_search_stat.vim
+++ b/src/nvim/testdir/test_search_stat.vim
@@ -8,6 +8,41 @@ func Test_search_stat()
set shortmess-=S
" Append 50 lines with text to search for, "foobar" appears 20 times
call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10))
+ call nvim_win_set_cursor(0, [1, 0])
+
+ " searchcount() returns an empty dictionary when previous pattern was not set
+ call assert_equal({}, searchcount(#{pattern: ''}))
+ " but setting @/ should also work (even 'n' nor 'N' was executed)
+ " recompute the count when the last position is different.
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 40, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'foo'}))
+ call assert_equal(
+ \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar'}))
+ call assert_equal(
+ \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [2, 1, 0]}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [3, 1, 0]}))
+ " on last char of match
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [3, 9, 0]}))
+ " on char after match
+ call assert_equal(
+ \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [3, 10, 0]}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0]}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 0, total: 2, incomplete: 2, maxcount: 1},
+ \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0], maxcount: 1}))
+ call assert_equal(
+ \ #{current: 0, exact_match: 0, total: 2, incomplete: 2, maxcount: 1},
+ \ searchcount(#{pattern: 'fooooobar', maxcount: 1}))
" match at second line
call cursor(1, 1)
@@ -17,6 +52,9 @@ func Test_search_stat()
let stat = '\[2/50\]'
let pat = escape(@/, '()*?'). '\s\+'
call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
" didn't get added to message history
call assert_equal(messages_before, execute('messages'))
@@ -25,6 +63,9 @@ func Test_search_stat()
let g:a = execute(':unsilent :norm! n')
let stat = '\[50/50\]'
call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
" No search stat
set shortmess+=S
@@ -32,6 +73,14 @@ func Test_search_stat()
let stat = '\[2/50\]'
let g:a = execute(':unsilent :norm! n')
call assert_notmatch(pat .. stat, g:a)
+ call writefile(getline(1, '$'), 'sample.txt')
+ " n does not update search stat
+ call assert_equal(
+ \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+ call assert_equal(
+ \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: v:true}))
set shortmess-=S
" Many matches
@@ -41,10 +90,28 @@ func Test_search_stat()
let g:a = execute(':unsilent :norm! n')
let stat = '\[>99/>99\]'
call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 100, exact_match: 0, total: 100, incomplete: 2, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+ call assert_equal(
+ \ #{current: 272, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: v:true, maxcount: 0, timeout: 200}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: 1, maxcount: 0, pos: [1, 1, 0], timeout: 200}))
call cursor(line('$'), 1)
let g:a = execute(':unsilent :norm! n')
let stat = 'W \[1/>99\]'
call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 100, incomplete: 2, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: 1, maxcount: 0, timeout: 200}))
+ call assert_equal(
+ \ #{current: 271, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: 1, maxcount: 0, pos: [line('$')-2, 1, 0], timeout: 200}))
" Many matches
call cursor(1, 1)
@@ -180,12 +247,22 @@ func Test_search_stat()
call assert_match('^\s\+' .. stat, g:b)
unmap n
+ " Time out
+ %delete _
+ call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 100000))
+ call cursor(1, 1)
+ call assert_equal(1, searchcount(#{pattern: 'foo', maxcount: 0, timeout: 1}).incomplete)
+
" Clean up
set shortmess+=S
" close the window
bwipe!
endfunc
+func Test_searchcount_fails()
+ call assert_fails('echo searchcount("boo!")', 'E715:')
+endfunc
+
func Test_search_stat_foldopen()
CheckScreendump
@@ -252,9 +329,9 @@ func Test_searchcount_in_statusline()
function TestSearchCount() abort
let search_count = searchcount()
if !empty(search_count)
- return '[' . search_count.current . '/' . search_count.total . ']'
+ return '[' . search_count.current . '/' . search_count.total . ']'
else
- return ''
+ return ''
endif
endfunction
set hlsearch
diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim
index 7533eaf2e8..6d55889641 100644
--- a/src/nvim/testdir/test_sort.vim
+++ b/src/nvim/testdir/test_sort.vim
@@ -13,6 +13,37 @@ func Test_sort_strings()
" numbers compared as strings
call assert_equal([1, 2, 3], sort([3, 2, 1]))
call assert_equal([13, 28, 3], sort([3, 28, 13]))
+
+ call assert_equal(['A', 'O', 'P', 'a', 'o', 'p', 'Ä', 'Ô', 'ä', 'ô', 'Œ', 'œ'],
+ \ sort(['A', 'O', 'P', 'a', 'o', 'p', 'Ä', 'Ô', 'ä', 'ô', 'œ', 'Œ']))
+
+ call assert_equal(['A', 'a', 'o', 'O', 'p', 'P', 'Ä', 'Ô', 'ä', 'ô', 'Œ', 'œ'],
+ \ sort(['A', 'a', 'o', 'O', 'œ', 'Œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'i'))
+
+ " This does not appear to work correctly on Mac.
+ if !has('mac')
+ if v:collate =~? '^\(en\|fr\)_ca.utf-\?8$'
+ " with Canadian English capitals come before lower case.
+ " 'Œ' is omitted because it can sort before or after 'œ'
+ call assert_equal(['A', 'a', 'Ä', 'ä', 'O', 'o', 'Ô', 'ô', 'œ', 'P', 'p'],
+ \ sort(['A', 'a', 'o', 'O', 'œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l'))
+ elseif v:collate =~? '^\(en\|es\|de\|fr\|it\|nl\).*\.utf-\?8$'
+ " With the following locales, the accentuated letters are ordered
+ " similarly to the non-accentuated letters...
+ call assert_equal(['a', 'A', 'ä', 'Ä', 'o', 'O', 'ô', 'Ô', 'œ', 'Œ', 'p', 'P'],
+ \ sort(['A', 'a', 'o', 'O', 'œ', 'Œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l'))
+ elseif v:collate =~? '^sv.*utf-\?8$'
+ " ... whereas with a Swedish locale, the accentuated letters are ordered
+ " after Z.
+ call assert_equal(['a', 'A', 'o', 'O', 'p', 'P', 'ä', 'Ä', 'œ', 'œ', 'ô', 'Ô'],
+ \ sort(['A', 'a', 'o', 'O', 'œ', 'œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l'))
+ endif
+ endif
+endfunc
+
+func Test_sort_null_string()
+ " null strings are sorted as empty strings.
+ call assert_equal(['', 'a', 'b'], sort(['b', v:_null_string, 'a']))
endfunc
func Test_sort_numeric()
@@ -1150,7 +1181,7 @@ func Test_sort_cmd()
\ 'input' : [
\ '1.234',
\ '0.88',
- \ '123.456',
+ \ ' + 123.456',
\ '1.15e-6',
\ '-1.1e3',
\ '-1.01e3',
@@ -1165,7 +1196,7 @@ func Test_sort_cmd()
\ '1.15e-6',
\ '0.88',
\ '1.234',
- \ '123.456'
+ \ ' + 123.456'
\ ]
\ },
\ {
@@ -1197,8 +1228,133 @@ func Test_sort_cmd()
\ 'cc',
\ ]
\ },
+ \ {
+ \ 'name' : 'sort one line buffer',
+ \ 'cmd' : 'sort',
+ \ 'input' : [
+ \ 'single line'
+ \ ],
+ \ 'expected' : [
+ \ 'single line'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'sort ignoring case',
+ \ 'cmd' : '%sort i',
+ \ 'input' : [
+ \ 'BB',
+ \ 'Cc',
+ \ 'aa'
+ \ ],
+ \ 'expected' : [
+ \ 'aa',
+ \ 'BB',
+ \ 'Cc'
+ \ ]
+ \ },
\ ]
+ " This does not appear to work correctly on Mac.
+ if !has('mac')
+ if v:collate =~? '^\(en\|fr\)_ca.utf-\?8$'
+ " en_CA.utf-8 sorts capitals before lower case
+ " 'Œ' is omitted because it can sort before or after 'œ'
+ let tests += [
+ \ {
+ \ 'name' : 'sort with locale ' .. v:collate,
+ \ 'cmd' : '%sort l',
+ \ 'input' : [
+ \ 'A',
+ \ 'E',
+ \ 'O',
+ \ 'À',
+ \ 'È',
+ \ 'É',
+ \ 'Ô',
+ \ 'Z',
+ \ 'a',
+ \ 'e',
+ \ 'o',
+ \ 'à',
+ \ 'è',
+ \ 'é',
+ \ 'ô',
+ \ 'œ',
+ \ 'z'
+ \ ],
+ \ 'expected' : [
+ \ 'A',
+ \ 'a',
+ \ 'À',
+ \ 'à',
+ \ 'E',
+ \ 'e',
+ \ 'É',
+ \ 'é',
+ \ 'È',
+ \ 'è',
+ \ 'O',
+ \ 'o',
+ \ 'Ô',
+ \ 'ô',
+ \ 'œ',
+ \ 'Z',
+ \ 'z'
+ \ ]
+ \ },
+ \ ]
+ elseif v:collate =~? '^\(en\|es\|de\|fr\|it\|nl\).*\.utf-\?8$'
+ " With these locales, the accentuated letters are ordered
+ " similarly to the non-accentuated letters.
+ let tests += [
+ \ {
+ \ 'name' : 'sort with locale ' .. v:collate,
+ \ 'cmd' : '%sort l',
+ \ 'input' : [
+ \ 'A',
+ \ 'E',
+ \ 'O',
+ \ 'À',
+ \ 'È',
+ \ 'É',
+ \ 'Ô',
+ \ 'Œ',
+ \ 'Z',
+ \ 'a',
+ \ 'e',
+ \ 'o',
+ \ 'à',
+ \ 'è',
+ \ 'é',
+ \ 'ô',
+ \ 'œ',
+ \ 'z'
+ \ ],
+ \ 'expected' : [
+ \ 'a',
+ \ 'A',
+ \ 'à',
+ \ 'À',
+ \ 'e',
+ \ 'E',
+ \ 'é',
+ \ 'É',
+ \ 'è',
+ \ 'È',
+ \ 'o',
+ \ 'O',
+ \ 'ô',
+ \ 'Ô',
+ \ 'œ',
+ \ 'Œ',
+ \ 'z',
+ \ 'Z'
+ \ ]
+ \ },
+ \ ]
+ endif
+ endif
+
for t in tests
enew!
call append(0, t.input)
@@ -1217,7 +1373,11 @@ func Test_sort_cmd()
endif
endfor
- call assert_fails('sort no', 'E474')
+ " Needs atleast two lines for this test
+ call setline(1, ['line1', 'line2'])
+ call assert_fails('sort no', 'E474:')
+ call assert_fails('sort c', 'E475:')
+ call assert_fails('sort #pat%', 'E682:')
enew!
endfunc
@@ -1319,4 +1479,46 @@ func Test_sort_cmd_report()
" the output comes from the :g command, not from the :sort
call assert_match("6 fewer lines", res)
enew!
- endfunc
+endfunc
+
+" Test for a :sort command followed by another command
+func Test_sort_followed_by_cmd()
+ new
+ let var = ''
+ call setline(1, ['cc', 'aa', 'bb'])
+ %sort | let var = "sortcmdtest"
+ call assert_equal(var, "sortcmdtest")
+ call assert_equal(['aa', 'bb', 'cc'], getline(1, '$'))
+ " Test for :sort followed by a comment
+ call setline(1, ['3b', '1c', '2a'])
+ %sort /\d\+/ " sort alphabetically
+ call assert_equal(['2a', '3b', '1c'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for :sort using last search pattern
+func Test_sort_last_search_pat()
+ new
+ let @/ = '\d\+'
+ call setline(1, ['3b', '1c', '2a'])
+ sort //
+ call assert_equal(['2a', '3b', '1c'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for retaining marks across a :sort
+func Test_sort_with_marks()
+ new
+ call setline(1, ['cc', 'aa', 'bb'])
+ call setpos("'c", [0, 1, 0, 0])
+ call setpos("'a", [0, 2, 0, 0])
+ call setpos("'b", [0, 3, 0, 0])
+ %sort
+ call assert_equal(['aa', 'bb', 'cc'], getline(1, '$'))
+ call assert_equal(2, line("'a"))
+ call assert_equal(3, line("'b"))
+ call assert_equal(1, line("'c"))
+ close!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index f5b6446108..a3e4dcdd25 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -241,6 +241,26 @@ func Test_statusline()
call assert_match('^vimLineComment\s*$', s:get_statusline())
syntax off
+ "%{%expr%}: evaluates enxpressions present in result of expr
+ func! Inner_eval()
+ return '%n some other text'
+ endfunc
+ func! Outer_eval()
+ return 'some text %{%Inner_eval()%}'
+ endfunc
+ set statusline=%{%Outer_eval()%}
+ call assert_match('^some text ' . bufnr() . ' some other text\s*$', s:get_statusline())
+ delfunc Inner_eval
+ delfunc Outer_eval
+
+ "%{%expr%}: Doesn't get stuck in recursion
+ func! Recurse_eval()
+ return '%{%Recurse_eval()%}'
+ endfunc
+ set statusline=%{%Recurse_eval()%}
+ call assert_match('^%{%Recurse_eval()%}\s*$', s:get_statusline())
+ delfunc Recurse_eval
+
"%(: Start of item group.
set statusline=ab%(cd%q%)de
call assert_match('^abde\s*$', s:get_statusline())
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index 32167a45ba..e7f9bb76f2 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -426,6 +426,8 @@ func Test_substitute_errors()
call assert_fails('s/FOO/bar/', 'E486:')
call assert_fails('s/foo/bar/@', 'E488:')
call assert_fails('s/\(/bar/', 'E476:')
+ call assert_fails('s afooabara', 'E146:')
+ call assert_fails('s\\a', 'E10:')
setl nomodifiable
call assert_fails('s/foo/bar/', 'E21:')
diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim
index ee43540fdd..7f5b80e8d3 100644
--- a/src/nvim/testdir/test_winbuf_close.vim
+++ b/src/nvim/testdir/test_winbuf_close.vim
@@ -160,7 +160,7 @@ func Test_winfixwidth_on_close()
endfunction
" Test that 'winfixheight' will be respected even there is non-leaf frame
-fun! Test_winfixheight_non_leaf_frame()
+func Test_winfixheight_non_leaf_frame()
vsplit
botright 11new
let l:wid = win_getid()
@@ -173,7 +173,7 @@ fun! Test_winfixheight_non_leaf_frame()
endf
" Test that 'winfixwidth' will be respected even there is non-leaf frame
-fun! Test_winfixwidth_non_leaf_frame()
+func Test_winfixwidth_non_leaf_frame()
split
topleft 11vnew
let l:wid = win_getid()
@@ -184,3 +184,13 @@ fun! Test_winfixwidth_non_leaf_frame()
call assert_equal(11, winwidth(l:wid))
%bwipe!
endf
+
+func Test_tabwin_close()
+ enew
+ let l:wid = win_getid()
+ tabedit
+ call win_execute(l:wid, 'close')
+ " Should not crash.
+ call assert_true(v:true)
+ %bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index c62c01d5f3..c7710ff198 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -164,6 +164,69 @@ func Test_writefile_autowrite_nowrite()
set noautowrite
endfunc
+" Test for ':w !<cmd>' to pipe lines from the current buffer to an external
+" command.
+func Test_write_pipe_to_cmd()
+ if !has('unix')
+ return
+ endif
+ new
+ call setline(1, ['L1', 'L2', 'L3', 'L4'])
+ 2,3w !cat > Xfile
+ call assert_equal(['L2', 'L3'], readfile('Xfile'))
+ close!
+ call delete('Xfile')
+endfunc
+
+" Test for :saveas
+func Test_saveas()
+ call assert_fails('saveas', 'E471:')
+ call writefile(['L1'], 'Xfile')
+ new Xfile
+ new
+ call setline(1, ['L1'])
+ call assert_fails('saveas Xfile', 'E139:')
+ close!
+ enew | only
+ call delete('Xfile')
+endfunc
+
+func Test_write_errors()
+ " Test for writing partial buffer
+ call writefile(['L1', 'L2', 'L3'], 'Xfile')
+ new Xfile
+ call assert_fails('1,2write', 'E140:')
+ close!
+
+ " Try to overwrite a directory
+ if has('unix')
+ call mkdir('Xdir1')
+ call assert_fails('write Xdir1', 'E17:')
+ call delete('Xdir1', 'd')
+ endif
+
+ " Test for :wall for a buffer with no name
+ enew | only
+ call setline(1, ['L1'])
+ call assert_fails('wall', 'E141:')
+ enew!
+
+ " Test for writing a 'readonly' file
+ new Xfile
+ set readonly
+ call assert_fails('write', 'E45:')
+ close
+
+ " Test for writing to a read-only file
+ new Xfile
+ call setfperm('Xfile', 'r--r--r--')
+ call assert_fails('write', 'E505:')
+ call setfperm('Xfile', 'rw-rw-rw-')
+ close
+
+ call delete('Xfile')
+endfunc
+
func Test_writefile_sync_dev_stdout()
if !has('unix')
return
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index ed40a64c66..fd83681aed 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -2009,9 +2009,9 @@ static void augment_terminfo(TUIData *data, const char *term,
}
data->unibi_ext.save_title = (int)unibi_add_ext_str(
- ut, "ext.save_title", "\x1b[22;0;0t");
+ ut, "ext.save_title", "\x1b[22;0t");
data->unibi_ext.restore_title = (int)unibi_add_ext_str(
- ut, "ext.restore_title", "\x1b[23;0;0t");
+ ut, "ext.restore_title", "\x1b[23;0t");
/// Terminals usually ignore unrecognized private modes, and there is no
/// known ambiguity with these. So we just set them unconditionally.
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index c1e4a40ef2..1ec5189795 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -165,22 +165,13 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width,
}
#endif
- // TODO(bfredl): this is pretty ad-hoc, add a proper z-order/priority
- // scheme. For now:
- // - msg_grid is always on top.
- // - pum_grid is on top of all windows but not msg_grid. Except for when
- // wildoptions=pum, and completing the cmdline with scrolled messages,
- // then the pum has to be drawn over the scrolled messages.
size_t insert_at = kv_size(layers);
- bool cmd_completion = (grid == &pum_grid && (State & CMDLINE)
- && (wop_flags & WOP_PUM));
- if (kv_A(layers, insert_at-1) == &msg_grid && !cmd_completion) {
- insert_at--;
- }
- if (kv_A(layers, insert_at-1) == &pum_grid && (grid != &msg_grid)) {
+ while (insert_at > 0 && kv_A(layers, insert_at-1)->zindex > grid->zindex) {
insert_at--;
}
+
if (curwin && kv_A(layers, insert_at-1) == &curwin->w_grid_alloc
+ && kv_A(layers, insert_at-1)->zindex == grid->zindex
&& !on_top) {
insert_at--;
}
@@ -279,12 +270,11 @@ 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) {
+
+ while (new_index > 1 && kv_A(layers, new_index)->zindex > curgrid->zindex) {
new_index--;
}
+
if (curgrid->comp_index < new_index) {
ui_comp_raise_grid(curgrid, new_index);
}
diff --git a/src/nvim/window.c b/src/nvim/window.c
index c070f0a32e..936bfa8c5b 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -763,10 +763,13 @@ void ui_ext_win_position(win_T *wp)
}
api_clear_error(&dummy);
}
+
+ wp->w_grid_alloc.zindex = wp->w_float_config.zindex;
if (ui_has(kUIMultigrid)) {
String anchor = cstr_to_string(float_anchor_str[c.anchor]);
ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor,
- grid->handle, row, col, c.focusable);
+ grid->handle, row, col, c.focusable,
+ wp->w_grid_alloc.zindex);
} else {
// TODO(bfredl): ideally, compositor should work like any multigrid UI
// and use standard win_pos events.
@@ -2286,7 +2289,7 @@ int win_close(win_T *win, bool free_buf)
return FAIL; // window is already being closed
}
if (win == aucmd_win) {
- EMSG(_("E813: Cannot close autocmd window"));
+ EMSG(_(e_autocmd_close));
return FAIL;
}
if ((firstwin == aucmd_win || lastwin == aucmd_win) && one_window()) {
@@ -3466,6 +3469,9 @@ int win_alloc_first(void)
first_tabpage = alloc_tabpage();
first_tabpage->tp_topframe = topframe;
curtab = first_tabpage;
+ curtab->tp_firstwin = firstwin;
+ curtab->tp_lastwin = lastwin;
+ curtab->tp_curwin = curwin;
return OK;
}
@@ -3634,6 +3640,8 @@ int win_new_tabpage(int after, char_u *filename)
newtp->tp_next = tp->tp_next;
tp->tp_next = newtp;
}
+ newtp->tp_firstwin = newtp->tp_lastwin = newtp->tp_curwin = curwin;
+
win_init_size();
firstwin->w_winrow = tabline_height();
win_comp_scroll(curwin);
@@ -6336,6 +6344,13 @@ static win_T *get_snapshot_focus(int idx)
int switch_win(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp, int no_display)
{
block_autocmds();
+ return switch_win_noblock(save_curwin, save_curtab, win, tp, no_display);
+}
+
+// As switch_win() but without blocking autocommands.
+int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab,
+ win_T *win, tabpage_T *tp, int no_display)
+{
*save_curwin = curwin;
if (tp != NULL) {
*save_curtab = curtab;
@@ -6361,6 +6376,14 @@ int switch_win(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage
// triggered.
void restore_win(win_T *save_curwin, tabpage_T *save_curtab, bool no_display)
{
+ restore_win_noblock(save_curwin, save_curtab, no_display);
+ unblock_autocmds();
+}
+
+// As restore_win() but without unblocking autocommands.
+void restore_win_noblock(win_T *save_curwin, tabpage_T *save_curtab,
+ bool no_display)
+{
if (save_curtab != NULL && valid_tabpage(save_curtab)) {
if (no_display) {
curtab->tp_firstwin = firstwin;
@@ -6375,7 +6398,6 @@ void restore_win(win_T *save_curwin, tabpage_T *save_curtab, bool no_display)
curwin = save_curwin;
curbuf = curwin->w_buffer;
}
- unblock_autocmds();
}
/// Make "buf" the current buffer.
@@ -6808,10 +6830,19 @@ void win_id2tabwin(typval_T *const argvars, typval_T *const rettv)
win_T * win_id2wp(typval_T *argvars)
{
+ return win_id2wp_tp(argvars, NULL);
+}
+
+// Return the window and tab pointer of window "id".
+win_T * win_id2wp_tp(typval_T *argvars, tabpage_T **tpp)
+{
int id = tv_get_number(&argvars[0]);
FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->handle == id) {
+ if (tpp != NULL) {
+ *tpp = tp;
+ }
return wp;
}
}