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.c4
-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.c17
-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/cursor.c5
-rw-r--r--src/nvim/diff.c6
-rw-r--r--src/nvim/edit.c11
-rw-r--r--src/nvim/eval.c12
-rw-r--r--src/nvim/eval.lua3
-rw-r--r--src/nvim/eval/funcs.c132
-rw-r--r--src/nvim/eval/typval.c13
-rw-r--r--src/nvim/ex_cmds.c40
-rw-r--r--src/nvim/ex_docmd.c53
-rw-r--r--src/nvim/ex_getln.c11
-rw-r--r--src/nvim/ex_session.c13
-rw-r--r--src/nvim/fileio.c109
-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.lua272
-rw-r--r--src/nvim/macros.h1
-rw-r--r--src/nvim/menu.c20
-rw-r--r--src/nvim/message.c1
-rw-r--r--src/nvim/misc1.c18
-rw-r--r--src/nvim/normal.c8
-rw-r--r--src/nvim/option.c13
-rw-r--r--src/nvim/option.h3
-rw-r--r--src/nvim/option_defs.h8
-rw-r--r--src/nvim/options.lua8
-rw-r--r--src/nvim/path.c2
-rw-r--r--src/nvim/popupmnu.c6
-rw-r--r--src/nvim/quickfix.c271
-rw-r--r--src/nvim/regexp.c32
-rw-r--r--src/nvim/regexp_nfa.c59
-rw-r--r--src/nvim/screen.c11
-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.c5
-rw-r--r--src/nvim/testdir/shared.vim6
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_arglist.vim18
-rw-r--r--src/nvim/testdir/test_autocmd.vim51
-rw-r--r--src/nvim/testdir/test_cmdline.vim115
-rw-r--r--src/nvim/testdir/test_command_count.vim2
-rw-r--r--src/nvim/testdir/test_cscope.vim130
-rw-r--r--src/nvim/testdir/test_diffmode.vim88
-rw-r--r--src/nvim/testdir/test_ex_mode.vim82
-rw-r--r--src/nvim/testdir/test_filetype.vim2
-rw-r--r--src/nvim/testdir/test_functions.vim61
-rw-r--r--src/nvim/testdir/test_glob2regpat.vim4
-rw-r--r--src/nvim/testdir/test_highlight.vim36
-rw-r--r--src/nvim/testdir/test_listdict.vim8
-rw-r--r--src/nvim/testdir/test_partial.vim2
-rw-r--r--src/nvim/testdir/test_quickfix.vim317
-rw-r--r--src/nvim/testdir/test_regexp_latin.vim9
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim46
-rw-r--r--src/nvim/testdir/test_registers.vim3
-rw-r--r--src/nvim/testdir/test_search.vim19
-rw-r--r--src/nvim/testdir/test_search_stat.vim81
-rw-r--r--src/nvim/testdir/test_sort.vim29
-rw-r--r--src/nvim/testdir/test_statusline.vim20
-rw-r--r--src/nvim/testdir/test_true_false.vim7
-rw-r--r--src/nvim/testdir/test_user_func.vim8
-rw-r--r--src/nvim/testdir/test_vimscript.vim34
-rw-r--r--src/nvim/testdir/test_writefile.vim21
-rw-r--r--src/nvim/tui/tui.c4
-rw-r--r--src/nvim/ui_compositor.c22
-rw-r--r--src/nvim/window.c7
74 files changed, 2250 insertions, 856 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 ebc9aeb75f..11a4647d1c 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -477,7 +477,7 @@ void nvim_buf_set_lines(uint64_t channel_id,
goto end;
}
- inserted_bytes += STRLEN(lines[i]) + 1;
+ 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;
@@ -497,7 +497,7 @@ void nvim_buf_set_lines(uint64_t channel_id,
goto end;
}
- inserted_bytes += STRLEN(lines[i]) + 1;
+ inserted_bytes += (bcount_t)strlen(lines[i]) + 1;
// Same as with replacing, but we also need to free lines
xfree(lines[i]);
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..a76cefe294 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
@@ -978,7 +984,7 @@ Dictionary nvim_get_all_options_info(Error *err)
/// Resulting dictionary has keys:
/// - name: Name of the option (like 'filetype')
/// - shortname: Shortened name of the option (like 'ft')
-/// - type: type of option ("string", "integer" or "boolean")
+/// - type: type of option ("string", "number" or "boolean")
/// - default: The default value for the option
/// - was_set: Whether the option was set.
///
@@ -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/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/cursor.c b/src/nvim/cursor.c
index 74a6f77a6d..5d2210dc7d 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -14,6 +14,7 @@
#include "nvim/misc1.h"
#include "nvim/move.h"
#include "nvim/screen.h"
+#include "nvim/extmark.h"
#include "nvim/state.h"
#include "nvim/vim.h"
#include "nvim/ascii.h"
@@ -181,7 +182,7 @@ static int coladvance2(
memset(newline + idx, ' ', (size_t)correct);
ml_replace(pos->lnum, newline, false);
- changed_bytes(pos->lnum, (colnr_T)idx);
+ inserted_bytes(pos->lnum, (colnr_T)idx, 0, correct);
idx += correct;
col = wcol;
} else {
@@ -206,7 +207,7 @@ static int coladvance2(
memcpy(newline + idx + csize, line + idx + 1, n);
ml_replace(pos->lnum, newline, false);
- changed_bytes(pos->lnum, idx);
+ inserted_bytes(pos->lnum, idx, 1, csize);
idx += (csize - 1 + correct);
col += correct;
}
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 31b7b1bd8f..5f8b81822b 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -982,12 +982,14 @@ static int check_external_diff(diffio_T *diffio)
char_u linebuf[LBUFLEN];
for (;;) {
- // There must be a line that contains "1c1".
+ // For normal diff there must be a line that contains
+ // "1c1". For unified diff "@@ -1 +1 @@".
if (vim_fgets(linebuf, LBUFLEN, fd)) {
break;
}
- if (STRNCMP(linebuf, "1c1", 3) == 0) {
+ if (STRNCMP(linebuf, "1c1", 3) == 0
+ || STRNCMP(linebuf, "@@ -1 +1 @@", 11) == 0) {
ok = kTrue;
}
}
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 d67db94339..a75cc78b7e 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -377,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);
@@ -1621,7 +1619,7 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty,
char buf[IOSIZE];
// apply :filter /pat/ to variable name
- xstrlcpy(buf, prefix, IOSIZE - 1);
+ xstrlcpy(buf, prefix, IOSIZE);
xstrlcat(buf, (char *)di->di_key, IOSIZE);
if (message_filtered((char_u *)buf)) {
continue;
@@ -6328,7 +6326,7 @@ void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg,
if (what_arg->v_type == VAR_UNKNOWN) {
tv_list_alloc_ret(rettv, kListLenMayKnow);
if (is_qf || wp != NULL) {
- (void)get_errorlist(NULL, wp, -1, rettv->vval.v_list);
+ (void)get_errorlist(NULL, wp, -1, 0, rettv->vval.v_list);
}
} else {
tv_dict_alloc_ret(rettv);
@@ -7617,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.lua b/src/nvim/eval.lua
index 148804e54c..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}},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 911b3c2c2f..caaf675db2 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -1850,15 +1850,30 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
ptrdiff_t len = end - str;
assert(len > 0);
const char * value = str + len + 1;
- if (tv_dict_find(rettv->vval.v_dict, str, len) != NULL) {
+
+ char c = env[i][len];
+ env[i][len] = NUL;
+
+#ifdef WIN32
+ // Upper-case all the keys for Windows so we can detect duplicates
+ char *const key = strcase_save(str, true);
+#else
+ char *const key = xstrdup(str);
+#endif
+
+ env[i][len] = c;
+
+ if (tv_dict_find(rettv->vval.v_dict, key, len) != NULL) {
// Since we're traversing from the end of the env block to the front, any
// duplicate names encountered should be ignored. This preserves the
// semantics of env vars defined later in the env block taking precedence.
+ xfree(key);
continue;
}
tv_dict_add_str(rettv->vval.v_dict,
- str, len,
+ key, len,
value);
+ xfree(key);
}
os_free_fullenv(env);
}
@@ -2770,10 +2785,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);
@@ -5097,7 +5111,21 @@ static dict_T *create_environment(const dictitem_T *job_env,
}
if (job_env) {
+#ifdef WIN32
+ TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {
+ // Always use upper-case keys for Windows so we detect duplicate keys
+ char *const key = strcase_save((const char *)var->di_key, true);
+ size_t len = strlen(key);
+ dictitem_T *dv = tv_dict_find(env, key, len);
+ if (dv) {
+ tv_dict_item_remove(env, dv);
+ }
+ tv_dict_add_str(env, key, len, tv_get_string(&var->di_tv));
+ xfree(key);
+ });
+#else
tv_dict_extend(env, job_env->di_tv.vval.v_dict, "force");
+#endif
}
if (pty) {
@@ -5540,18 +5568,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;
}
@@ -6661,6 +6707,37 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+// Evaluate "expr" for readdir().
+static varnumber_T readdir_checkitem(typval_T *expr, const char *name)
+{
+ typval_T save_val;
+ typval_T rettv;
+ typval_T argv[2];
+ varnumber_T retval = 0;
+ bool error = false;
+
+ prepare_vimvar(VV_VAL, &save_val);
+ set_vim_var_string(VV_VAL, name, -1);
+ argv[0].v_type = VAR_STRING;
+ argv[0].vval.v_string = (char_u *)name;
+
+ if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
+ goto theend;
+ }
+
+ retval = tv_get_number_chk(&rettv, &error);
+ if (error) {
+ retval = -1;
+ }
+
+ tv_clear(&rettv);
+
+theend:
+ set_vim_var_string(VV_VAL, NULL, 0);
+ restore_vimvar(VV_VAL, &save_val);
+ return retval;
+}
+
// "readdir()" function
static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -6672,14 +6749,43 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_list_alloc_ret(rettv, kListLenUnknown);
path = tv_get_string(&argvars[0]);
expr = &argvars[1];
+ ga_init(&ga, (int)sizeof(char *), 20);
if (!os_scandir(&dir, path)) {
smsg(_(e_notopen), path);
} else {
- readdir_core(&ga, &dir, expr, true);
+ for (;;) {
+ bool ignore;
+
+ path = os_scandir_next(&dir);
+ if (path == NULL) {
+ break;
+ }
+
+ ignore = (path[0] == '.'
+ && (path[1] == NUL || (path[1] == '.' && path[2] == NUL)));
+ if (!ignore && expr->v_type != VAR_UNKNOWN) {
+ varnumber_T r = readdir_checkitem(expr, path);
+
+ if (r < 0) {
+ break;
+ }
+ if (r == 0) {
+ ignore = true;
+ }
+ }
+
+ if (!ignore) {
+ ga_grow(&ga, 1);
+ ((char **)ga.ga_data)[ga.ga_len++] = xstrdup(path);
+ }
+ }
+
+ os_closedir(&dir);
}
if (rettv->vval.v_list != NULL && ga.ga_len > 0) {
+ sort_strings((char_u **)ga.ga_data, ga.ga_len);
for (int i = 0; i < ga.ga_len; i++) {
path = ((const char **)ga.ga_data)[i];
tv_list_append_string(rettv->vval.v_list, path, -1);
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 349c7d6280..6a0a08eee8 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -2434,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.
@@ -2462,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.
@@ -2475,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)) {
@@ -2497,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_docmd.c b/src/nvim/ex_docmd.c
index 555029c1fb..29347def4c 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;
@@ -1772,7 +1772,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
// count, it's a buffer name.
///
if ((ea.argt & EX_COUNT) && ascii_isdigit(*ea.arg)
- && (!(ea.argt & EX_BUFNAME) || *(p = skipdigits(ea.arg)) == NUL
+ && (!(ea.argt & EX_BUFNAME) || *(p = skipdigits(ea.arg + 1)) == NUL
|| ascii_iswhite(*p))) {
n = getdigits_long(&ea.arg, false, -1);
ea.arg = skipwhite(ea.arg);
@@ -2790,15 +2790,18 @@ static struct cmdmod {
*/
int modifier_len(char_u *cmd)
{
- int i, j;
char_u *p = cmd;
- if (ascii_isdigit(*cmd))
- p = skipwhite(skipdigits(cmd));
- for (i = 0; i < (int)ARRAY_SIZE(cmdmods); ++i) {
- for (j = 0; p[j] != NUL; ++j)
- if (p[j] != cmdmods[i].name[j])
+ if (ascii_isdigit(*cmd)) {
+ p = skipwhite(skipdigits(cmd + 1));
+ }
+ for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) {
+ int j;
+ for (j = 0; p[j] != NUL; j++) {
+ if (p[j] != cmdmods[i].name[j]) {
break;
+ }
+ }
if (j >= cmdmods[i].minlen
&& !ASCII_ISALPHA(p[j])
&& (p == cmd || cmdmods[i].has_count)) {
@@ -5014,7 +5017,7 @@ char_u *check_nextcmd(char_u *p)
static int
check_more(
int message, // when FALSE check only, no messages
- int forceit
+ bool forceit
)
{
int n = ARGCOUNT - curwin->w_arg_idx - 1;
@@ -6337,7 +6340,7 @@ void not_exiting(void)
exiting = false;
}
-static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit)
+bool before_quit_autocmds(win_T *wp, bool quit_all, bool forceit)
{
apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer);
@@ -6403,7 +6406,7 @@ static void ex_quit(exarg_T *eap)
return;
}
- // If there are more files or windows we won't exit.
+ // If there is only one relevant window we will exit.
if (check_more(false, eap->forceit) == OK && only_one_window()) {
exiting = true;
}
@@ -6520,6 +6523,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) {
@@ -6589,9 +6598,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);
@@ -6743,7 +6749,7 @@ static void ex_stop(exarg_T *eap)
apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL);
}
-// ":exit", ":xit" and ":wq": Write file and quite the current window.
+// ":exit", ":xit" and ":wq": Write file and quit the current window.
static void ex_exit(exarg_T *eap)
{
if (cmdwin_type != 0) {
@@ -6756,17 +6762,15 @@ static void ex_exit(exarg_T *eap)
return;
}
- if (before_quit_autocmds(curwin, false, eap->forceit)) {
- return;
- }
-
- // if more files or windows we won't exit
+ // we plan to exit if there is only one relevant window
if (check_more(false, eap->forceit) == OK && only_one_window()) {
exiting = true;
}
- if (((eap->cmdidx == CMD_wq
- || curbufIsChanged())
- && do_write(eap) == FAIL)
+ // Write the buffer for ":wq" or when it was changed.
+ // Trigger QuitPre and ExitPre.
+ // Check if we can exit now, after autocommands have changed things.
+ if (((eap->cmdidx == CMD_wq || curbufIsChanged()) && do_write(eap) == FAIL)
+ || before_quit_autocmds(curwin, false, eap->forceit)
|| check_more(true, eap->forceit) == FAIL
|| (only_one_window() && check_changed_any(eap->forceit, false))) {
not_exiting();
@@ -7304,7 +7308,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/ex_session.c b/src/nvim/ex_session.c
index 9e4e69e124..c1e52d6994 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -937,11 +937,13 @@ void ex_mkrc(exarg_T *eap)
if (!view_session || (eap->cmdidx == CMD_mksession
&& (*flagp & SSOP_OPTIONS))) {
- failed |= (makemap(fd, NULL) == FAIL
- || makeset(fd, OPT_GLOBAL, false) == FAIL);
- if (p_hls && fprintf(fd, "%s", "set hlsearch\n") < 0) {
- failed = true;
+ int flags = OPT_GLOBAL;
+
+ if (eap->cmdidx == CMD_mksession && (*flagp & SSOP_SKIP_RTP)) {
+ flags |= OPT_SKIPRTP;
}
+ failed |= (makemap(fd, NULL) == FAIL
+ || makeset(fd, flags, false) == FAIL);
}
if (!failed && view_session) {
@@ -1002,6 +1004,9 @@ void ex_mkrc(exarg_T *eap)
< 0) {
failed = true;
}
+ if (p_hls && fprintf(fd, "%s", "set hlsearch\n") < 0) {
+ failed = true;
+ }
if (no_hlsearch && fprintf(fd, "%s", "nohlsearch\n") < 0) {
failed = true;
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 2037ba5f19..29c29a2884 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -5204,113 +5204,38 @@ static void vim_maketempdir(void)
(void)umask(umask_save);
}
-// Evaluate "expr" for readdir().
-static varnumber_T readdir_checkitem(typval_T *expr, const char *name)
-{
- typval_T save_val;
- typval_T rettv;
- typval_T argv[2];
- varnumber_T retval = 0;
- bool error = false;
-
- prepare_vimvar(VV_VAL, &save_val);
- set_vim_var_string(VV_VAL, name, -1);
- argv[0].v_type = VAR_STRING;
- argv[0].vval.v_string = (char_u *)name;
-
- if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
- goto theend;
- }
-
- retval = tv_get_number_chk(&rettv, &error);
- if (error) {
- retval = -1;
- }
-
- tv_clear(&rettv);
-
-theend:
- set_vim_var_string(VV_VAL, NULL, 0);
- restore_vimvar(VV_VAL, &save_val);
- return retval;
-}
-
-/// Core part of "readdir()" function.
-/// Retrieve the list of files/directories of "dirp" into "gap".
-void readdir_core(
- garray_T *gap,
- Directory *dirp,
- typval_T *expr,
- bool is_checkitem)
-{
- ga_init(gap, (int)sizeof(char *), 20);
-
- for (;;) {
- bool ignore;
-
- const char *path = os_scandir_next(dirp);
- if (path == NULL) {
- break;
- }
-
- ignore = (path[0] == '.'
- && (path[1] == NUL || (path[1] == '.' && path[2] == NUL)));
- if (!ignore && expr != NULL && expr->v_type != VAR_UNKNOWN
- && is_checkitem) {
- varnumber_T r = readdir_checkitem(expr, path);
-
- if (r < 0) {
- break;
- }
- if (r == 0) {
- ignore = true;
- }
- }
-
- if (!ignore) {
- ga_grow(gap, 1);
- ((char **)gap->ga_data)[gap->ga_len++] = xstrdup(path);
- }
- }
-
- if (gap->ga_len > 0) {
- sort_strings((char_u **)gap->ga_data, gap->ga_len);
- }
-
- os_closedir(dirp);
-}
-
/// Delete "name" and everything in it, recursively.
/// @param name The path which should be deleted.
/// @return 0 for success, -1 if some file was not deleted.
int delete_recursive(const char *name)
{
int result = 0;
- char *exp = (char *)vim_strsave((char_u *)name);
- Directory dir;
-
- if (os_isrealdir(name) && os_scandir(&dir, exp)) {
- garray_T ga;
-
- readdir_core(&ga, &dir, NULL, false);
- for (int i = 0; i < ga.ga_len; i++) {
- vim_snprintf((char *)NameBuff, MAXPATHL, "%s/%s", exp,
- ((char_u **)ga.ga_data)[i]);
- if (delete_recursive((const char *)NameBuff) != 0) {
- result = -1;
+ if (os_isrealdir(name)) {
+ snprintf((char *)NameBuff, MAXPATHL, "%s/*", name); // NOLINT
+
+ char_u **files;
+ int file_count;
+ char_u *exp = vim_strsave(NameBuff);
+ if (gen_expand_wildcards(1, &exp, &file_count, &files,
+ EW_DIR | EW_FILE | EW_SILENT | EW_ALLLINKS
+ | EW_DODOT | EW_EMPTYOK) == OK) {
+ for (int i = 0; i < file_count; i++) {
+ if (delete_recursive((const char *)files[i]) != 0) {
+ result = -1;
+ }
}
+ FreeWild(file_count, files);
+ } else {
+ result = -1;
}
- ga_clear_strings(&ga);
-
+ xfree(exp);
os_rmdir(name);
} else {
result = os_remove(name) == 0 ? 0 : -1;
}
- xfree(exp);
-
return result;
}
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 a678432dda..5c9c5103a7 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -39,6 +39,75 @@ assert(vim)
vim.inspect = package.loaded['vim.inspect']
assert(vim.inspect)
+local pathtrails = {}
+vim._so_trails = {}
+for s in (package.cpath..';'):gmatch('[^;]*;') do
+ s = s:sub(1, -2) -- Strip trailing semicolon
+ -- Find out path patterns. pathtrail should contain something like
+ -- /?.so, \?.dll. This allows not to bother determining what correct
+ -- suffixes are.
+ local pathtrail = s:match('[/\\][^/\\]*%?.*$')
+ if pathtrail and not pathtrails[pathtrail] then
+ pathtrails[pathtrail] = true
+ table.insert(vim._so_trails, pathtrail)
+ end
+end
+
+function vim._load_package(name)
+ local basename = name:gsub('%.', '/')
+ local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
+ for _,path in ipairs(paths) do
+ local found = vim.api.nvim_get_runtime_file(path, false)
+ if #found > 0 then
+ local f, err = loadfile(found[1])
+ return f or error(err)
+ end
+ end
+
+ for _,trail in ipairs(vim._so_trails) do
+ local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
+ local found = vim.api.nvim_get_runtime_file(path, false)
+ if #found > 0 then
+ -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
+ -- a) strip prefix up to and including the first dash, if any
+ -- b) replace all dots by underscores
+ -- c) prepend "luaopen_"
+ -- So "foo-bar.baz" should result in "luaopen_bar_baz"
+ local dash = name:find("-", 1, true)
+ local modname = dash and name:sub(dash + 1) or name
+ local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
+ return f or error(err)
+ end
+ end
+ return nil
+end
+
+table.insert(package.loaders, 1, vim._load_package)
+
+-- These are for loading runtime modules lazily since they aren't available in
+-- the nvim binary as specified in executor.c
+setmetatable(vim, {
+ __index = function(t, key)
+ if key == 'treesitter' then
+ t.treesitter = require('vim.treesitter')
+ return t.treesitter
+ elseif key == 'F' then
+ t.F = require('vim.F')
+ return t.F
+ elseif require('vim.uri')[key] ~= nil then
+ -- Expose all `vim.uri` functions on the `vim` module.
+ t[key] = require('vim.uri')[key]
+ return t[key]
+ elseif key == 'lsp' then
+ t.lsp = require('vim.lsp')
+ return t.lsp
+ elseif key == 'highlight' then
+ t.highlight = require('vim.highlight')
+ return t.highlight
+ end
+ end
+})
+
vim.log = {
levels = {
TRACE = 0;
@@ -105,51 +174,6 @@ function vim._os_proc_children(ppid)
return children
end
-local pathtrails = {}
-vim._so_trails = {}
-for s in (package.cpath..';'):gmatch('[^;]*;') do
- s = s:sub(1, -2) -- Strip trailing semicolon
- -- Find out path patterns. pathtrail should contain something like
- -- /?.so, \?.dll. This allows not to bother determining what correct
- -- suffixes are.
- local pathtrail = s:match('[/\\][^/\\]*%?.*$')
- if pathtrail and not pathtrails[pathtrail] then
- pathtrails[pathtrail] = true
- table.insert(vim._so_trails, pathtrail)
- end
-end
-
-function vim._load_package(name)
- local basename = name:gsub('%.', '/')
- local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
- for _,path in ipairs(paths) do
- local found = vim.api.nvim_get_runtime_file(path, false)
- if #found > 0 then
- local f, err = loadfile(found[1])
- return f or error(err)
- end
- end
-
- for _,trail in ipairs(vim._so_trails) do
- local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
- local found = vim.api.nvim_get_runtime_file(path, false)
- if #found > 0 then
- -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
- -- a) strip prefix up to and including the first dash, if any
- -- b) replace all dots by underscores
- -- c) prepend "luaopen_"
- -- So "foo-bar.baz" should result in "luaopen_bar_baz"
- local dash = name:find("-", 1, true)
- local modname = dash and name:sub(dash + 1) or name
- local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
- return f or error(err)
- end
- end
- return nil
-end
-
-table.insert(package.loaders, 1, vim._load_package)
-
-- TODO(ZyX-I): Create compatibility layer.
--- Return a human-readable representation of the given object.
@@ -282,32 +306,6 @@ vim.funcref = function(viml_func_name)
return vim.fn[viml_func_name]
end
--- These are for loading runtime modules lazily since they aren't available in
--- the nvim binary as specified in executor.c
-local function __index(t, key)
- if key == 'treesitter' then
- t.treesitter = require('vim.treesitter')
- return t.treesitter
- elseif require('vim.uri')[key] ~= nil then
- -- Expose all `vim.uri` functions on the `vim` module.
- t[key] = require('vim.uri')[key]
- return t[key]
- elseif key == 'lsp' then
- t.lsp = require('vim.lsp')
- return t.lsp
- elseif key == 'highlight' then
- t.highlight = require('vim.highlight')
- return t.highlight
- elseif key == 'F' then
- t.F = require('vim.F')
- return t.F
- end
-end
-
-setmetatable(vim, {
- __index = __index
-})
-
-- An easier alias for commands.
vim.cmd = function(command)
return vim.api.nvim_exec(command, false)
@@ -315,135 +313,27 @@ end
-- These are the vim.env/v/g/o/bo/wo variable magic accessors.
do
- local a = vim.api
local validate = vim.validate
- local function make_meta_accessor(get, set, del)
+
+ local function make_dict_accessor(scope)
validate {
- get = {get, 'f'};
- set = {set, 'f'};
- del = {del, 'f', true};
+ scope = {scope, 's'};
}
local mt = {}
- if del then
- function mt:__newindex(k, v)
- if v == nil then
- return del(k)
- end
- return set(k, v)
- end
- else
- function mt:__newindex(k, v)
- return set(k, v)
- end
+ function mt:__newindex(k, v)
+ return vim._setvar(scope, 0, k, v)
end
function mt:__index(k)
- return get(k)
+ return vim._getvar(scope, 0, k)
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, ...))
- end
- 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.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
-
- local function getenv(k)
- local v = vim.fn.getenv(k)
- if v == vim.NIL then
- return nil
- end
- return v
- end
- vim.env = make_meta_accessor(getenv, vim.fn.setenv)
- -- TODO(ashkan) if/when these are available from an API, generate them
- -- instead of hardcoding.
- local window_options = {
- arab = true; arabic = true; breakindent = true; breakindentopt = true;
- bri = true; briopt = true; cc = true; cocu = true;
- cole = true; colorcolumn = true; concealcursor = true; conceallevel = true;
- crb = true; cuc = true; cul = true; cursorbind = true;
- cursorcolumn = true; cursorline = true; diff = true; fcs = true;
- fdc = true; fde = true; fdi = true; fdl = true;
- fdm = true; fdn = true; fdt = true; fen = true;
- fillchars = true; fml = true; fmr = true; foldcolumn = true;
- foldenable = true; foldexpr = true; foldignore = true; foldlevel = true;
- foldmarker = true; foldmethod = true; foldminlines = true; foldnestmax = true;
- foldtext = true; lbr = true; lcs = true; linebreak = true;
- list = true; listchars = true; nu = true; number = true;
- numberwidth = true; nuw = true; previewwindow = true; pvw = true;
- relativenumber = true; rightleft = true; rightleftcmd = true; rl = true;
- rlc = true; rnu = true; scb = true; scl = true;
- scr = true; scroll = true; scrollbind = true; signcolumn = true;
- spell = true; statusline = true; stl = true; wfh = true;
- wfw = true; winbl = true; winblend = true; winfixheight = true;
- winfixwidth = true; winhighlight = true; winhl = true; wrap = true;
- }
- --@private
- local function new_buf_opt_accessor(bufnr)
- --@private
- local function get(k)
- if window_options[k] then
- return a.nvim_err_writeln(k.." is a window option, not a buffer option")
- end
- if bufnr == nil and type(k) == "number" then
- return new_buf_opt_accessor(k)
- end
- return a.nvim_buf_get_option(bufnr or 0, k)
- end
-
- --@private
- local function set(k, v)
- if window_options[k] then
- return a.nvim_err_writeln(k.." is a window option, not a buffer option")
- end
- return a.nvim_buf_set_option(bufnr or 0, k, v)
- end
-
- return make_meta_accessor(get, set)
- end
- vim.bo = new_buf_opt_accessor(nil)
-
- --@private
- local function new_win_opt_accessor(winnr)
-
- --@private
- local function get(k)
- if winnr == nil and type(k) == "number" then
- return new_win_opt_accessor(k)
- end
- return a.nvim_win_get_option(winnr or 0, k)
- end
-
- --@private
- local function set(k, v)
- return a.nvim_win_set_option(winnr or 0, k, v)
- end
- return make_meta_accessor(get, set)
- end
- vim.wo = new_win_opt_accessor(nil)
+ 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')
end
--- Get a table of lines with start, end columns for a region marked by two points
@@ -746,4 +636,6 @@ vim._expand_pat_get_parts = function(lua_string)
return parts, search_index
end
+pcall(require, 'vim._meta')
+
return module
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/menu.c b/src/nvim/menu.c
index ac3b7768e6..112f51fc64 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -1069,7 +1069,7 @@ char_u *get_menu_names(expand_T *xp, int idx)
#define TBUFFER_LEN 256
static char_u tbuffer[TBUFFER_LEN]; /*hack*/
char_u *str;
- static int should_advance = FALSE;
+ static bool should_advance = false;
if (idx == 0) { /* first call: start at first item */
menu = expand_menu;
@@ -1089,12 +1089,13 @@ char_u *get_menu_names(expand_T *xp, int idx)
if (menu->modes & expand_modes) {
if (menu->children != NULL) {
- if (should_advance)
- STRLCPY(tbuffer, menu->en_dname, TBUFFER_LEN - 1);
- else {
- STRLCPY(tbuffer, menu->dname, TBUFFER_LEN - 1);
- if (menu->en_dname == NULL)
- should_advance = TRUE;
+ if (should_advance) {
+ STRLCPY(tbuffer, menu->en_dname, TBUFFER_LEN);
+ } else {
+ STRLCPY(tbuffer, menu->dname, TBUFFER_LEN);
+ if (menu->en_dname == NULL) {
+ should_advance = true;
+ }
}
/* hack on menu separators: use a 'magic' char for the separator
* so that '.' in names gets escaped properly */
@@ -1105,8 +1106,9 @@ char_u *get_menu_names(expand_T *xp, int idx)
str = menu->en_dname;
else {
str = menu->dname;
- if (menu->en_dname == NULL)
- should_advance = TRUE;
+ if (menu->en_dname == NULL) {
+ should_advance = true;
+ }
}
}
} else
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/misc1.c b/src/nvim/misc1.c
index 68a1bba78d..38d0a7dadf 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -753,8 +753,12 @@ get_number (
skip_redraw = TRUE; /* skip redraw once */
do_redraw = FALSE;
break;
- } else if (c == CAR || c == NL || c == Ctrl_C || c == ESC)
+ } else if (c == Ctrl_C || c == ESC || c == 'q') {
+ n = 0;
break;
+ } else if (c == CAR || c == NL) {
+ break;
+ }
}
no_mapping--;
return n;
@@ -771,11 +775,13 @@ int prompt_for_number(int *mouse_used)
int save_cmdline_row;
int save_State;
- /* When using ":silent" assume that <CR> was entered. */
- if (mouse_used != NULL)
- MSG_PUTS(_("Type number and <Enter> or click with mouse (empty cancels): "));
- else
- MSG_PUTS(_("Type number and <Enter> (empty cancels): "));
+ // When using ":silent" assume that <CR> was entered.
+ if (mouse_used != NULL) {
+ MSG_PUTS(_("Type number and <Enter> or click with the mouse "
+ "(q or empty cancels): "));
+ } else {
+ MSG_PUTS(_("Type number and <Enter> (q or empty cancels): "));
+ }
/* Set the state such that text can be selected/copied/pasted and we still
* get mouse events. */
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 2f4c441beb..13706fb14a 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1942,10 +1942,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
case OP_FORMAT:
if (*curbuf->b_p_fex != NUL) {
op_formatexpr(oap); // use expression
- } else if (*p_fp != NUL || *curbuf->b_p_fp != NUL) {
- op_colon(oap); // use external command
} else {
- op_format(oap, false); // use internal function
+ if (*p_fp != NUL || *curbuf->b_p_fp != NUL) {
+ op_colon(oap); // use external command
+ } else {
+ op_format(oap, false); // use internal function
+ }
}
break;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 6f28f37d2b..67fb78bcc8 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -3641,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");
}
@@ -4881,7 +4883,7 @@ int get_option_value_strict(char *name,
if (p->flags & P_STRING) {
*stringval = xstrdup(*(char **)(varp));
} else if (p->flags & P_NUM) {
- *numval = *(long *) varp;
+ *numval = *(long *)varp;
} else {
*numval = *(int *)varp;
}
@@ -5230,6 +5232,11 @@ int makeset(FILE *fd, int opt_flags, int local_only)
continue;
}
+ if ((opt_flags & OPT_SKIPRTP)
+ && (p->var == (char_u *)&p_rtp || p->var == (char_u *)&p_pp)) {
+ continue;
+ }
+
round = 2;
if (p->indir != PV_NONE) {
if (p->var == VAR_WIN) {
@@ -7698,7 +7705,7 @@ Dictionary get_vimoption(String name, Error *err)
Dictionary get_all_vimoptions(void)
{
Dictionary retval = ARRAY_DICT_INIT;
- for (size_t i = 0; i < PARAM_COUNT; i++) {
+ for (size_t i = 0; options[i].fullname != NULL; i++) {
Dictionary opt_dict = vimoption2dict(&options[i]);
PUT(retval, options[i].fullname, DICTIONARY_OBJ(opt_dict));
}
diff --git a/src/nvim/option.h b/src/nvim/option.h
index 60f14dea44..c6ee03e052 100644
--- a/src/nvim/option.h
+++ b/src/nvim/option.h
@@ -19,6 +19,9 @@ typedef enum {
OPT_MODELINE = 8, ///< Option in modeline.
OPT_WINONLY = 16, ///< Only set window-local options.
OPT_NOWIN = 32, ///< Don’t set window-local options.
+ OPT_ONECOLUMN = 64, ///< list options one per line
+ OPT_NO_REDRAW = 128, ///< ignore redraw flags on option
+ OPT_SKIPRTP = 256, ///< "skiprtp" in 'sessionoptions'
} OptionFlags;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 16749ba86b..beb62a6a0b 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -559,6 +559,7 @@ EXTERN int p_ri; // 'revins'
EXTERN int p_ru; // 'ruler'
EXTERN char_u *p_ruf; // 'rulerformat'
EXTERN char_u *p_pp; // 'packpath'
+EXTERN char_u *p_qftf; // 'quickfixtextfunc'
EXTERN char_u *p_rtp; // 'runtimepath'
EXTERN long p_scbk; // 'scrollback'
EXTERN long p_sj; // 'scrolljump'
@@ -571,11 +572,12 @@ EXTERN char_u *p_slm; // 'selectmode'
EXTERN char_u *p_ssop; // 'sessionoptions'
EXTERN unsigned ssop_flags;
# ifdef IN_OPTION_C
-// Also used for 'viewoptions'!
+// Also used for 'viewoptions'! Keep in sync with SSOP_ flags.
static char *(p_ssop_values[]) = {
"buffers", "winpos", "resize", "winsize",
"localoptions", "options", "help", "blank", "globals", "slash", "unix",
- "sesdir", "curdir", "folds", "cursor", "tabpages", NULL
+ "sesdir", "curdir", "folds", "cursor", "tabpages", "terminal", "skiprtp",
+ NULL
};
# endif
# define SSOP_BUFFERS 0x001
@@ -594,6 +596,8 @@ static char *(p_ssop_values[]) = {
# define SSOP_FOLDS 0x2000
# define SSOP_CURSOR 0x4000
# define SSOP_TABPAGES 0x8000
+# define SSOP_TERMINAL 0x10000
+# define SSOP_SKIP_RTP 0x20000
EXTERN char_u *p_sh; // 'shell'
EXTERN char_u *p_shcf; // 'shellcmdflag'
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index d12b31bcaf..86dec74f56 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -2074,6 +2074,14 @@ return {
defaults={if_true={vi=0}}
},
{
+ full_name='quickfixtextfunc', abbreviation='qftf',
+ short_desc=N_("customize the quickfix window"),
+ type='string', scope={'global'},
+ vi_def=true,
+ varname='p_qftf',
+ defaults={if_true={vi=""}}
+ },
+ {
full_name='quoteescape', abbreviation='qe',
short_desc=N_("escape characters used in a string"),
type='string', scope={'buffer'},
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 3e1713fbdd..fe50be5ea1 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -65,7 +65,7 @@ FileComparison path_full_compare(char_u *const s1, char_u *const s2,
if (expandenv) {
expand_env(s1, exp1, MAXPATHL);
} else {
- xstrlcpy((char *)exp1, (const char *)s1, MAXPATHL - 1);
+ xstrlcpy((char *)exp1, (const char *)s1, MAXPATHL);
}
bool id_ok_1 = os_fileid((char *)exp1, &file_id_1);
bool id_ok_2 = os_fileid((char *)s2, &file_id_2);
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/quickfix.c b/src/nvim/quickfix.c
index 464d72eccb..1a9bbe26f0 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -100,6 +100,7 @@ typedef struct qf_list_S {
char_u *qf_title; ///< title derived from the command that created
///< the error list or set by setqflist
typval_T *qf_ctx; ///< context set by setqflist/setloclist
+ char_u *qf_qftf; ///< 'quickfixtextfunc' setting for this list
struct dir_stack_T *qf_dir_stack;
char_u *qf_directory;
@@ -1999,6 +2000,11 @@ static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl)
} else {
to_qfl->qf_ctx = NULL;
}
+ if (from_qfl->qf_qftf != NULL) {
+ to_qfl->qf_qftf = vim_strsave(from_qfl->qf_qftf);
+ } else {
+ to_qfl->qf_qftf = NULL;
+ }
if (from_qfl->qf_count) {
if (copy_loclist_entries(from_qfl, to_qfl) == FAIL) {
@@ -2666,7 +2672,7 @@ static void qf_goto_win_with_qfl_file(int qf_fnum)
static int qf_jump_to_usable_window(int qf_fnum, bool newwin,
int *opened_window)
{
- win_T *usable_win_ptr = NULL;
+ win_T *usable_wp = NULL;
bool usable_win = false;
// If opening a new window, then don't use the location list referred by
@@ -2675,8 +2681,8 @@ static int qf_jump_to_usable_window(int qf_fnum, bool newwin,
qf_info_T *ll_ref = newwin ? NULL : curwin->w_llist_ref;
if (ll_ref != NULL) {
// Find a non-quickfix window with this location list
- usable_win_ptr = qf_find_win_with_loclist(ll_ref);
- if (usable_win_ptr != NULL) {
+ usable_wp = qf_find_win_with_loclist(ll_ref);
+ if (usable_wp != NULL) {
usable_win = true;
}
}
@@ -2704,7 +2710,7 @@ static int qf_jump_to_usable_window(int qf_fnum, bool newwin,
*opened_window = true; // close it when fail
} else {
if (curwin->w_llist_ref != NULL) { // In a location window
- qf_goto_win_with_ll_file(usable_win_ptr, qf_fnum, ll_ref);
+ qf_goto_win_with_ll_file(usable_wp, qf_fnum, ll_ref);
} else { // In a quickfix window
qf_goto_win_with_qfl_file(qf_fnum);
}
@@ -3032,14 +3038,11 @@ theend:
qfl->qf_ptr = qf_ptr;
qfl->qf_index = qf_index;
}
- if (p_swb != old_swb && opened_window) {
+ if (p_swb != old_swb && p_swb == empty_option && opened_window) {
// Restore old 'switchbuf' value, but not when an autocommand or
// modeline has changed the value.
- if (p_swb == empty_option) {
- p_swb = old_swb;
- swb_flags = old_swb_flags;
- } else
- free_string_option(old_swb);
+ p_swb = old_swb;
+ swb_flags = old_swb_flags;
}
}
@@ -3382,6 +3385,7 @@ static void qf_free(qf_list_T *qfl)
XFREE_CLEAR(qfl->qf_title);
tv_free(qfl->qf_ctx);
qfl->qf_ctx = NULL;
+ XFREE_CLEAR(qfl->qf_qftf);
qfl->qf_id = 0;
qfl->qf_changedtick = 0L;
}
@@ -3721,7 +3725,7 @@ void ex_copen(exarg_T *eap)
lnum = qfl->qf_index;
// Fill the buffer with the quickfix list.
- qf_fill_buffer(qfl, curbuf, NULL);
+ qf_fill_buffer(qfl, curbuf, NULL, curwin->handle);
decr_quickfix_busy();
@@ -3884,6 +3888,11 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
buf = qf_find_buf(qi);
if (buf != NULL) {
linenr_T old_line_count = buf->b_ml.ml_line_count;
+ int qf_winid = 0;
+
+ if (IS_LL_STACK(qi)) {
+ qf_winid = curwin->handle;
+ }
if (old_last == NULL) {
// set curwin/curbuf to buf and save a few things
@@ -3892,7 +3901,7 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
qf_update_win_titlevar(qi);
- qf_fill_buffer(qf_get_curlist(qi), buf, old_last);
+ qf_fill_buffer(qf_get_curlist(qi), buf, old_last, qf_winid);
buf_inc_changedtick(buf);
if (old_last == NULL) {
@@ -3911,70 +3920,75 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
}
// Add an error line to the quickfix buffer.
-static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp,
- char_u *dirname, bool first_bufline)
- FUNC_ATTR_NONNULL_ALL
+static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum,
+ const qfline_T *qfp, char_u *dirname,
+ char_u *qftf_str, bool first_bufline)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5)
{
int len;
buf_T *errbuf;
- if (qfp->qf_module != NULL) {
- STRLCPY(IObuff, qfp->qf_module, IOSIZE - 1);
- len = (int)STRLEN(IObuff);
- } else if (qfp->qf_fnum != 0
- && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
- && errbuf->b_fname != NULL) {
- if (qfp->qf_type == 1) { // :helpgrep
- STRLCPY(IObuff, path_tail(errbuf->b_fname), IOSIZE - 1);
- } else {
- // Shorten the file name if not done already.
- // For optimization, do this only for the first entry in a
- // buffer.
- if (first_bufline
- && (errbuf->b_sfname == NULL
- || path_is_absolute(errbuf->b_sfname))) {
- if (*dirname == NUL) {
- os_dirname(dirname, MAXPATHL);
+ if (qftf_str != NULL) {
+ STRLCPY(IObuff, qftf_str, IOSIZE);
+ } else {
+ if (qfp->qf_module != NULL) {
+ STRLCPY(IObuff, qfp->qf_module, IOSIZE);
+ len = (int)STRLEN(IObuff);
+ } else if (qfp->qf_fnum != 0
+ && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
+ && errbuf->b_fname != NULL) {
+ if (qfp->qf_type == 1) { // :helpgrep
+ STRLCPY(IObuff, path_tail(errbuf->b_fname), IOSIZE);
+ } else {
+ // Shorten the file name if not done already.
+ // For optimization, do this only for the first entry in a
+ // buffer.
+ if (first_bufline
+ && (errbuf->b_sfname == NULL
+ || path_is_absolute(errbuf->b_sfname))) {
+ if (*dirname == NUL) {
+ os_dirname(dirname, MAXPATHL);
+ }
+ shorten_buf_fname(errbuf, dirname, false);
}
- shorten_buf_fname(errbuf, dirname, false);
+ STRLCPY(IObuff, errbuf->b_fname, IOSIZE);
}
- STRLCPY(IObuff, errbuf->b_fname, IOSIZE - 1);
+ len = (int)STRLEN(IObuff);
+ } else {
+ len = 0;
}
- len = (int)STRLEN(IObuff);
- } else {
- len = 0;
- }
- if (len < IOSIZE - 1) {
- IObuff[len++] = '|';
- }
- if (qfp->qf_lnum > 0) {
- snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%" PRId64,
- (int64_t)qfp->qf_lnum);
- len += (int)STRLEN(IObuff + len);
+ if (len < IOSIZE - 1) {
+ IObuff[len++] = '|';
+ }
+ if (qfp->qf_lnum > 0) {
+ snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%" PRId64,
+ (int64_t)qfp->qf_lnum);
+ len += (int)STRLEN(IObuff + len);
+
+ if (qfp->qf_col > 0) {
+ snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), " col %d",
+ qfp->qf_col);
+ len += (int)STRLEN(IObuff + len);
+ }
- if (qfp->qf_col > 0) {
- snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), " col %d",
- qfp->qf_col);
+ snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%s",
+ (char *)qf_types(qfp->qf_type, qfp->qf_nr));
len += (int)STRLEN(IObuff + len);
+ } else if (qfp->qf_pattern != NULL) {
+ qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len);
+ len += (int)STRLEN(IObuff + len);
+ }
+ if (len < IOSIZE - 2) {
+ IObuff[len++] = '|';
+ IObuff[len++] = ' ';
}
- snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%s",
- (char *)qf_types(qfp->qf_type, qfp->qf_nr));
- len += (int)STRLEN(IObuff + len);
- } else if (qfp->qf_pattern != NULL) {
- qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len);
- len += (int)STRLEN(IObuff + len);
+ // Remove newlines and leading whitespace from the text.
+ // For an unrecognized line keep the indent, the compiler may
+ // mark a word with ^^^^.
+ qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text,
+ IObuff + len, IOSIZE - len);
}
- if (len < IOSIZE - 2) {
- IObuff[len++] = '|';
- IObuff[len++] = ' ';
- }
-
- // Remove newlines and leading whitespace from the text.
- // For an unrecognized line keep the indent, the compiler may
- // mark a word with ^^^^.
- qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text,
- IObuff + len, IOSIZE - len);
if (ml_append_buf(buf, lnum, IObuff,
(colnr_T)STRLEN(IObuff) + 1, false) == FAIL) {
@@ -3983,17 +3997,56 @@ static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp,
return OK;
}
+static list_T *call_qftf_func(qf_list_T *qfl,
+ int qf_winid,
+ long start_idx,
+ long end_idx)
+{
+ char_u *qftf = p_qftf;
+ list_T *qftf_list = NULL;
+
+ // If 'quickfixtextfunc' is set, then use the user-supplied function to get
+ // the text to display. Use the local value of 'quickfixtextfunc' if it is
+ // set.
+ if (qfl->qf_qftf != NULL) {
+ qftf = qfl->qf_qftf;
+ }
+ if (qftf != NULL && *qftf != NUL) {
+ typval_T args[1];
+
+ // create the dict argument
+ dict_T *const dict = tv_dict_alloc_lock(VAR_FIXED);
+
+ tv_dict_add_nr(dict, S_LEN("quickfix"), IS_QF_LIST(qfl));
+ tv_dict_add_nr(dict, S_LEN("winid"), qf_winid);
+ tv_dict_add_nr(dict, S_LEN("id"), qfl->qf_id);
+ tv_dict_add_nr(dict, S_LEN("start_idx"), start_idx);
+ tv_dict_add_nr(dict, S_LEN("end_idx"), end_idx);
+ dict->dv_refcount++;
+ args[0].v_type = VAR_DICT;
+ args[0].vval.v_dict = dict;
+
+ qftf_list = call_func_retlist(qftf, 1, args);
+ dict->dv_refcount--;
+ }
+
+ return qftf_list;
+}
+
/// Fill current buffer with quickfix errors, replacing any previous contents.
/// curbuf must be the quickfix buffer!
/// If "old_last" is not NULL append the items after this one.
/// When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete()
/// is used and autocommands will be triggered.
-static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
+static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last,
+ int qf_winid)
FUNC_ATTR_NONNULL_ARG(2)
{
linenr_T lnum;
qfline_T *qfp;
const bool old_KeyTyped = KeyTyped;
+ list_T *qftf_list = NULL;
+ listitem_T *qftf_li = NULL;
if (old_last == NULL) {
if (buf != curbuf) {
@@ -4026,8 +4079,19 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
}
lnum = buf->b_ml.ml_line_count;
}
+
+ qftf_list = call_qftf_func(qfl, qf_winid, lnum + 1, (long)qfl->qf_count);
+ qftf_li = tv_list_first(qftf_list);
+
while (lnum < qfl->qf_count) {
- if (qf_buf_add_line(buf, lnum, qfp, dirname,
+ char_u *qftf_str = NULL;
+
+ if (qftf_li != NULL) {
+ // Use the text supplied by the user defined function
+ qftf_str = (char_u *)tv_get_string_chk(TV_LIST_ITEM_TV(qftf_li));
+ }
+
+ if (qf_buf_add_line(qfl, buf, lnum, qfp, dirname, qftf_str,
prev_bufnr != qfp->qf_fnum) == FAIL) {
break;
}
@@ -4037,6 +4101,10 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
if (qfp == NULL) {
break;
}
+
+ if (qftf_li != NULL) {
+ qftf_li = TV_LIST_ITEM_NEXT(qftf_list, qftf_li);
+ }
}
if (old_last == NULL) {
// Delete the empty line which is now at the end
@@ -5665,7 +5733,10 @@ static int get_qfline_items(qfline_T *qfp, list_T *list)
/// Add each quickfix error to list "list" as a dictionary.
/// If qf_idx is -1, use the current list. Otherwise, use the specified list.
-int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
+/// If eidx is not 0, then return only the specified entry. Otherwise return
+/// all the entries.
+int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, int eidx,
+ list_T *list)
{
qf_info_T *qi = qi_arg;
qf_list_T *qfl;
@@ -5682,6 +5753,10 @@ int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
}
}
+ if (eidx < 0) {
+ return OK;
+ }
+
if (qf_idx == INVALID_QFIDX) {
qf_idx = qi->qf_curlist;
}
@@ -5696,7 +5771,13 @@ int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
}
FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
- get_qfline_items(qfp, list);
+ if (eidx > 0) {
+ if (eidx == i) {
+ return get_qfline_items(qfp, list);
+ }
+ } else if (get_qfline_items(qfp, list) == FAIL) {
+ return FAIL;
+ }
}
return OK;
@@ -5743,7 +5824,7 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict)
if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat,
true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) {
- (void)get_errorlist(qi, NULL, 0, l);
+ (void)get_errorlist(qi, NULL, 0, 0, l);
qf_free(&qi->qf_lists[0]);
}
xfree(qi);
@@ -5934,11 +6015,13 @@ static int qf_getprop_filewinid(const win_T *wp, const qf_info_T *qi,
return tv_dict_add_nr(retdict, S_LEN("filewinid"), winid);
}
-/// Return the quickfix list items/entries as 'items' in retdict
-static int qf_getprop_items(qf_info_T *qi, int qf_idx, dict_T *retdict)
+/// Return the quickfix list items/entries as 'items' in retdict.
+/// If eidx is not 0, then return the item at the specified index.
+static int qf_getprop_items(qf_info_T *qi, int qf_idx, int eidx,
+ dict_T *retdict)
{
list_T *l = tv_list_alloc(kListLenMayKnow);
- get_errorlist(qi, NULL, qf_idx, l);
+ get_errorlist(qi, NULL, qf_idx, eidx, l);
tv_dict_add_list(retdict, S_LEN("items"), l);
return OK;
@@ -5963,15 +6046,18 @@ static int qf_getprop_ctx(qf_list_T *qfl, dict_T *retdict)
return status;
}
-/// Return the current quickfix list index as 'idx' in retdict
-static int qf_getprop_idx(qf_list_T *qfl, dict_T *retdict)
+/// Return the current quickfix list index as 'idx' in retdict.
+/// If a specific entry index (eidx) is supplied, then use that.
+static int qf_getprop_idx(qf_list_T *qfl, int eidx, dict_T *retdict)
{
- int curidx = qfl->qf_index;
- if (qf_list_empty(qfl)) {
- // For empty lists, current index is set to 0
- curidx = 0;
+ if (eidx == 0) {
+ eidx = qfl->qf_index;
+ if (qf_list_empty(qfl)) {
+ // For empty lists, current index is set to 0
+ eidx = 0;
+ }
}
- return tv_dict_add_nr(retdict, S_LEN("idx"), curidx);
+ return tv_dict_add_nr(retdict, S_LEN("idx"), eidx);
}
/// Return quickfix/location list details (title) as a dictionary.
@@ -5984,6 +6070,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
dictitem_T *di = NULL;
int status = OK;
int qf_idx = INVALID_QFIDX;
+ int eidx = 0;
if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) {
return qf_get_list_from_lines(what, di, retdict);
@@ -6006,6 +6093,14 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
qfl = qf_get_list(qi, qf_idx);
+ // If an entry index is specified, use that
+ if ((di = tv_dict_find(what, S_LEN("idx"))) != NULL) {
+ if (di->di_tv.v_type != VAR_NUMBER) {
+ return FAIL;
+ }
+ eidx = (int)di->di_tv.vval.v_number;
+ }
+
if (flags & QF_GETLIST_TITLE) {
status = qf_getprop_title(qfl, retdict);
}
@@ -6016,7 +6111,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
status = tv_dict_add_nr(retdict, S_LEN("winid"), qf_winid(qi));
}
if ((status == OK) && (flags & QF_GETLIST_ITEMS)) {
- status = qf_getprop_items(qi, qf_idx, retdict);
+ status = qf_getprop_items(qi, qf_idx, eidx, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) {
status = qf_getprop_ctx(qfl, retdict);
@@ -6025,7 +6120,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
status = tv_dict_add_nr(retdict, S_LEN("id"), qfl->qf_id);
}
if ((status == OK) && (flags & QF_GETLIST_IDX)) {
- status = qf_getprop_idx(qfl, retdict);
+ status = qf_getprop_idx(qfl, eidx, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_SIZE)) {
status = tv_dict_add_nr(retdict, S_LEN("size"),
@@ -6042,6 +6137,17 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
return status;
}
+/// Set the current index in the specified quickfix list
+static int qf_setprop_qftf(qf_info_T *qi, qf_list_T *qfl,
+ dictitem_T *di)
+{
+ XFREE_CLEAR(qfl->qf_qftf);
+ if (di->di_tv.v_type == VAR_STRING && di->di_tv.vval.v_string != NULL) {
+ qfl->qf_qftf = vim_strsave(di->di_tv.vval.v_string);
+ }
+ return OK;
+}
+
/// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the
/// items in the dict 'd'. If it is a valid error entry, then set 'valid_entry'
/// to true.
@@ -6407,6 +6513,9 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
if ((di = tv_dict_find(what, S_LEN("idx"))) != NULL) {
retval = qf_setprop_curidx(qi, qfl, di);
}
+ if ((di = tv_dict_find(what, S_LEN("quickfixtextfunc"))) != NULL) {
+ retval = qf_setprop_qftf(qi, qfl, di);
+ }
if (newlist || retval == OK) {
qf_list_changed(qfl);
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index e0cc25421a..accf9b0bb5 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -692,6 +692,7 @@ static char_u *regparse; ///< Input-scan pointer.
static int prevchr_len; ///< byte length of previous char
static int num_complex_braces; ///< Complex \{...} count
static int regnpar; ///< () count.
+static bool wants_nfa; ///< regex should use NFA engine
static int regnzpar; ///< \z() count.
static int re_has_z; ///< \z item detected
static char_u *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE
@@ -3974,17 +3975,25 @@ static bool regmatch(
pos = getmark_buf(rex.reg_buf, mark, false);
if (pos == NULL // mark doesn't exist
- || pos->lnum <= 0 // mark isn't set in reg_buf
- || (pos->lnum == rex.lnum + rex.reg_firstlnum
- ? (pos->col == (colnr_T)(rex.input - rex.line)
- ? (cmp == '<' || cmp == '>')
- : (pos->col < (colnr_T)(rex.input - rex.line)
- ? cmp != '>'
- : cmp != '<'))
- : (pos->lnum < rex.lnum + rex.reg_firstlnum
- ? cmp != '>'
- : cmp != '<'))) {
+ || pos->lnum <= 0) { // mark isn't set in reg_buf
status = RA_NOMATCH;
+ } else {
+ const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
+ && pos->col == MAXCOL
+ ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum))
+ : pos->col;
+
+ if (pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos_col == (colnr_T)(rex.input - rex.line)
+ ? (cmp == '<' || cmp == '>')
+ : (pos_col < (colnr_T)(rex.input - rex.line)
+ ? cmp != '>'
+ : cmp != '<'))
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
+ ? cmp != '>'
+ : cmp != '<')) {
+ status = RA_NOMATCH;
+ }
}
}
break;
@@ -7240,7 +7249,7 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags)
// Check for error compiling regexp with initial engine.
if (prog == NULL) {
#ifdef BT_REGEXP_DEBUG_LOG
- // Debugging log for NFA.
+ // Debugging log for BT engine.
if (regexp_engine != BACKTRACKING_ENGINE) {
FILE *f = fopen(BT_REGEXP_DEBUG_LOG_NAME, "a");
if (f) {
@@ -7257,6 +7266,7 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags)
// But don't try if an error message was given.
if (regexp_engine == AUTOMATIC_ENGINE && !called_emsg) {
regexp_engine = BACKTRACKING_ENGINE;
+ report_re_switch(expr);
prog = bt_regengine.regcomp(expr, re_flags);
}
}
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 923db6422e..5047e0db03 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -328,6 +328,11 @@ static int *post_start; ///< holds the postfix form of r.e.
static int *post_end;
static int *post_ptr;
+// Set when the pattern should use the NFA engine.
+// E.g. [[:upper:]] only allows 8bit characters for BT engine,
+// while NFA engine handles multibyte characters correctly.
+static bool wants_nfa;
+
static int nstate; ///< Number of states in the NFA. Also used when executing.
static int istate; ///< Index in the state vector, used in alloc_state()
@@ -377,6 +382,7 @@ nfa_regcomp_start (
post_start = (int *)xmalloc(postfix_size);
post_ptr = post_start;
post_end = post_start + nstate_max;
+ wants_nfa = false;
rex.nfa_has_zend = false;
rex.nfa_has_backref = false;
@@ -1618,6 +1624,7 @@ collection:
EMIT(NFA_CLASS_GRAPH);
break;
case CLASS_LOWER:
+ wants_nfa = true;
EMIT(NFA_CLASS_LOWER);
break;
case CLASS_PRINT:
@@ -1630,6 +1637,7 @@ collection:
EMIT(NFA_CLASS_SPACE);
break;
case CLASS_UPPER:
+ wants_nfa = true;
EMIT(NFA_CLASS_UPPER);
break;
case CLASS_XDIGIT:
@@ -1998,10 +2006,17 @@ static int nfa_regpiece(void)
return OK;
}
- // The engine is very inefficient (uses too many states) when the maximum
- // is much larger than the minimum and when the maximum is large. Bail out
- // if we can use the other engine.
- if ((nfa_re_flags & RE_AUTO) && (maxval > 500 || maxval > minval + 200)) {
+ // The engine is very inefficient (uses too many states) when the
+ // maximum is much larger than the minimum and when the maximum is
+ // large. However, when maxval is MAX_LIMIT, it is okay, as this
+ // will emit NFA_STAR.
+ // Bail out if we can use the other engine, but only, when the
+ // pattern does not need the NFA engine like (e.g. [[:upper:]]\{2,\}
+ // does not work with with characters > 8 bit with the BT engine)
+ if ((nfa_re_flags & RE_AUTO)
+ && (maxval > 500 || maxval > minval + 200)
+ && (maxval != MAX_LIMIT && minval < 200)
+ && !wants_nfa) {
return FAIL;
}
@@ -6055,21 +6070,27 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
{
pos_T *pos = getmark_buf(rex.reg_buf, t->state->val, false);
- // Compare the mark position to the match position.
- result = (pos != NULL // mark doesn't exist
- && pos->lnum > 0 // mark isn't set in reg_buf
- && (pos->lnum == rex.lnum + rex.reg_firstlnum
- ? (pos->col == (colnr_T)(rex.input - rex.line)
- ? t->state->c == NFA_MARK
- : (pos->col < (colnr_T)(rex.input - rex.line)
- ? t->state->c == NFA_MARK_GT
- : t->state->c == NFA_MARK_LT))
- : (pos->lnum < rex.lnum + rex.reg_firstlnum
- ? t->state->c == NFA_MARK_GT
- : t->state->c == NFA_MARK_LT)));
- if (result) {
- add_here = true;
- add_state = t->state->out;
+ // Compare the mark position to the match position, if the mark
+ // exists and mark is set in reg_buf.
+ if (pos != NULL && pos->lnum > 0) {
+ const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
+ && pos->col == MAXCOL
+ ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum))
+ : pos->col;
+
+ result = pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos_col == (colnr_T)(rex.input - rex.line)
+ ? t->state->c == NFA_MARK
+ : (pos_col < (colnr_T)(rex.input - rex.line)
+ ? t->state->c == NFA_MARK_GT
+ : t->state->c == NFA_MARK_LT))
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
+ ? t->state->c == NFA_MARK_GT
+ : t->state->c == NFA_MARK_LT);
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
}
break;
}
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 5151d82c1b..90ac4ac7aa 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -4138,9 +4138,16 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
// highlight the cursor position itself.
// Also highlight the 'colorcolumn' if it is different than
// 'cursorcolumn'
+ // Also highlight the 'colorcolumn' if 'breakindent' and/or 'showbreak'
+ // options are set
vcol_save_attr = -1;
- if (draw_state == WL_LINE && !lnum_in_visual_area
- && search_attr == 0 && area_attr == 0) {
+ if ((draw_state == WL_LINE
+ || draw_state == WL_BRI
+ || draw_state == WL_SBR)
+ && !lnum_in_visual_area
+ && search_attr == 0
+ && area_attr == 0
+ && filler_todo <= 0) {
if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol
&& lnum != wp->w_cursor.lnum) {
vcol_save_attr = char_attr;
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..ab35c936ca 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);
}
@@ -3011,7 +3010,7 @@ static int find_extra(char_u **pp)
// Repeat for addresses separated with ';'
for (;; ) {
if (ascii_isdigit(*str)) {
- str = skipdigits(str);
+ str = skipdigits(str + 1);
} else if (*str == '/' || *str == '?') {
str = skip_regexp(str + 1, *str, false, NULL);
if (*str != first_char) {
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index 0e20ac1593..f456ff4250 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -179,7 +179,7 @@ endfunc
func s:WaitForCommon(expr, assert, timeout)
" using reltime() is more accurate, but not always available
let slept = 0
- if has('reltime')
+ if exists('*reltimefloat')
let start = reltime()
endif
@@ -204,7 +204,7 @@ func s:WaitForCommon(expr, assert, timeout)
endif
sleep 10m
- if has('reltime')
+ if exists('*reltimefloat')
let slept = float2nr(reltimefloat(reltime(start)) * 1000)
else
let slept += 10
@@ -220,7 +220,7 @@ endfunc
" feeds key-input and resumes process. Return time waited in milliseconds.
" Without +timers it uses simply :sleep.
func Standby(msec)
- if has('timers')
+ if has('timers') && exists('*reltimefloat')
let start = reltime()
let g:_standby_timer = timer_start(a:msec, function('s:feedkeys'))
call getchar()
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_arglist.vim b/src/nvim/testdir/test_arglist.vim
index 08e578a226..a1ef8325ec 100644
--- a/src/nvim/testdir/test_arglist.vim
+++ b/src/nvim/testdir/test_arglist.vim
@@ -26,8 +26,6 @@ func Test_argidx()
endfunc
func Test_argadd()
- call Reset_arglist()
-
%argdelete
argadd a b c
call assert_equal(0, argidx())
@@ -105,11 +103,6 @@ func Init_abc()
next
endfunc
-func Reset_arglist()
- cd
- args a | %argd
-endfunc
-
func Assert_argc(l)
call assert_equal(len(a:l), argc())
let i = 0
@@ -122,7 +115,8 @@ endfunc
" Test for [count]argument and [count]argdelete commands
" Ported from the test_argument_count.in test script
func Test_argument()
- call Reset_arglist()
+ " Clean the argument list
+ arga a | %argd
let save_hidden = &hidden
set hidden
@@ -250,7 +244,8 @@ endfunc
" Test for 0argadd and 0argedit
" Ported from the test_argument_0count.in test script
func Test_zero_argadd()
- call Reset_arglist()
+ " Clean the argument list
+ arga a | %argd
arga a b c d
2argu
@@ -277,6 +272,10 @@ func Test_zero_argadd()
call assert_equal('file with spaces', expand('%'))
endfunc
+func Reset_arglist()
+ args a | %argd
+endfunc
+
" Test for argc()
func Test_argc()
call Reset_arglist()
@@ -409,7 +408,6 @@ endfunc
" Test for the :argdelete command
func Test_argdelete()
call Reset_arglist()
-
args aa a aaa b bb
argdelete a*
call assert_equal(['b', 'bb'], argv())
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 a1968807ac..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()
@@ -863,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
@@ -985,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
@@ -1033,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_command_count.vim b/src/nvim/testdir/test_command_count.vim
index 36ff4cd1bb..55b230373f 100644
--- a/src/nvim/testdir/test_command_count.vim
+++ b/src/nvim/testdir/test_command_count.vim
@@ -103,8 +103,6 @@ endfunc
func Test_command_count_2()
silent! %argd
- cd
-
arga a b c d
call assert_fails('5argu', 'E16:')
diff --git a/src/nvim/testdir/test_cscope.vim b/src/nvim/testdir/test_cscope.vim
index e5dab05511..cc6154af69 100644
--- a/src/nvim/testdir/test_cscope.vim
+++ b/src/nvim/testdir/test_cscope.vim
@@ -22,7 +22,7 @@ endfunc
func Test_cscopeWithCscopeConnections()
call CscopeSetupOrClean(1)
- " Test 0: E568: duplicate cscope database not added
+ " Test: E568: duplicate cscope database not added
try
set nocscopeverbose
cscope add Xcscope.out
@@ -33,44 +33,49 @@ func Test_cscopeWithCscopeConnections()
call assert_fails('cscope add', 'E560')
call assert_fails('cscope add Xcscope.out', 'E568')
call assert_fails('cscope add doesnotexist.out', 'E563')
+ if has('unix')
+ call assert_fails('cscope add /dev/null', 'E564:')
+ endif
- " Test 1: Find this C-Symbol
+ " Test: Find this C-Symbol
for cmd in ['cs find s main', 'cs find 0 main']
let a = execute(cmd)
- " Test 1.1 test where it moves the cursor
+ " Test where it moves the cursor
call assert_equal('main(void)', getline('.'))
- " Test 1.2 test the output of the :cs command
+ " Test the output of the :cs command
call assert_match('\n(1 of 1): <<main>> main(void )', a)
endfor
- " Test 2: Find this definition
- for cmd in ['cs find g test_mf_hash', 'cs find 1 test_mf_hash']
+ " Test: Find this definition
+ for cmd in ['cs find g test_mf_hash',
+ \ 'cs find 1 test_mf_hash',
+ \ 'cs find 1 test_mf_hash'] " leading space ignored.
exe cmd
call assert_equal(['', '/*', ' * Test mf_hash_*() functions.', ' */', ' static void', 'test_mf_hash(void)', '{'], getline(line('.')-5, line('.')+1))
endfor
- " Test 3: Find functions called by this function
+ " Test: Find functions called by this function
for cmd in ['cs find d test_mf_hash', 'cs find 2 test_mf_hash']
let a = execute(cmd)
call assert_match('\n(1 of 42): <<mf_hash_init>> mf_hash_init(&ht);', a)
call assert_equal(' mf_hash_init(&ht);', getline('.'))
endfor
- " Test 4: Find functions calling this function
+ " Test: Find functions calling this function
for cmd in ['cs find c test_mf_hash', 'cs find 3 test_mf_hash']
let a = execute(cmd)
call assert_match('\n(1 of 1): <<main>> test_mf_hash();', a)
call assert_equal(' test_mf_hash();', getline('.'))
endfor
- " Test 5: Find this text string
+ " Test: Find this text string
for cmd in ['cs find t Bram', 'cs find 4 Bram']
let a = execute(cmd)
call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a)
call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.'))
endfor
- " Test 6: Find this egrep pattern
+ " Test: Find this egrep pattern
" test all matches returned by cscope
for cmd in ['cs find e ^\#includ.', 'cs find 6 ^\#includ.']
let a = execute(cmd)
@@ -83,7 +88,7 @@ func Test_cscopeWithCscopeConnections()
call assert_fails('cnext', 'E553:')
endfor
- " Test 7: Find the same egrep pattern using lcscope this time.
+ " Test: Find the same egrep pattern using lcscope this time.
let a = execute('lcs find e ^\#includ.')
call assert_match('\n(1 of 3): <<<unknown>>> #include <assert.h>', a)
call assert_equal('#include <assert.h>', getline('.'))
@@ -93,7 +98,7 @@ func Test_cscopeWithCscopeConnections()
call assert_equal('#include "memfile.c"', getline('.'))
call assert_fails('lnext', 'E553:')
- " Test 8: Find this file
+ " Test: Find this file
for cmd in ['cs find f Xmemfile_test.c', 'cs find 7 Xmemfile_test.c']
enew
let a = execute(cmd)
@@ -101,7 +106,7 @@ func Test_cscopeWithCscopeConnections()
call assert_equal('Xmemfile_test.c', @%)
endfor
- " Test 9: Find files #including this file
+ " Test: Find files #including this file
for cmd in ['cs find i assert.h', 'cs find 8 assert.h']
enew
let a = execute(cmd)
@@ -112,39 +117,42 @@ func Test_cscopeWithCscopeConnections()
call assert_equal('#include <assert.h>', getline('.'))
endfor
- " Test 10: Invalid find command
+ " Test: Invalid find command
+ call assert_fails('cs find', 'E560:')
call assert_fails('cs find x', 'E560:')
- " Test 11: Find places where this symbol is assigned a value
- " this needs a cscope >= 15.8
- " unfortunately, Travis has cscope version 15.7
- let cscope_version = systemlist('cscope --version')[0]
- let cs_version = str2float(matchstr(cscope_version, '\d\+\(\.\d\+\)\?'))
- if cs_version >= 15.8
- for cmd in ['cs find a item', 'cs find 9 item']
- let a = execute(cmd)
- call assert_equal(['', '(1 of 4): <<test_mf_hash>> item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);'], split(a, '\n', 1))
- call assert_equal(' item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);', getline('.'))
- cnext
- call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
- cnext
- call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
- cnext
- call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
- endfor
+ if has('float')
+ " Test: Find places where this symbol is assigned a value
+ " this needs a cscope >= 15.8
+ " unfortunately, Travis has cscope version 15.7
+ let cscope_version = systemlist('cscope --version')[0]
+ let cs_version = str2float(matchstr(cscope_version, '\d\+\(\.\d\+\)\?'))
+ if cs_version >= 15.8
+ for cmd in ['cs find a item', 'cs find 9 item']
+ let a = execute(cmd)
+ call assert_equal(['', '(1 of 4): <<test_mf_hash>> item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);'], split(a, '\n', 1))
+ call assert_equal(' item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);', getline('.'))
+ cnext
+ call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
+ cnext
+ call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
+ cnext
+ call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
+ endfor
+ endif
endif
- " Test 12: leading whitespace is not removed for cscope find text
+ " Test: leading whitespace is not removed for cscope find text
let a = execute('cscope find t test_mf_hash')
call assert_equal(['', '(1 of 1): <<<unknown>>> test_mf_hash();'], split(a, '\n', 1))
call assert_equal(' test_mf_hash();', getline('.'))
- " Test 13: test with scscope
+ " Test: test with scscope
let a = execute('scs find t Bram')
call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a)
call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.'))
- " Test 14: cscope help
+ " Test: cscope help
for cmd in ['cs', 'cs help', 'cs xxx']
let a = execute(cmd)
call assert_match('^cscope commands:\n', a)
@@ -158,28 +166,35 @@ func Test_cscopeWithCscopeConnections()
let a = execute('scscope help')
call assert_match('This cscope command does not support splitting the window\.', a)
- " Test 15: reset connections
+ " Test: reset connections
let a = execute('cscope reset')
call assert_match('\nAdded cscope database.*Xcscope.out (#0)', a)
call assert_match('\nAll cscope databases reset', a)
- " Test 16: cscope show
+ " Test: cscope show
let a = execute('cscope show')
call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a)
- " Test 17: cstag and 'csto' option
+ " Test: cstag and 'csto' option
set csto=0
let a = execute('cstag TEST_COUNT')
call assert_match('(1 of 1): <<TEST_COUNT>> #define TEST_COUNT 50000', a)
call assert_equal('#define TEST_COUNT 50000', getline('.'))
+ call assert_fails('cstag DOES_NOT_EXIST', 'E257:')
set csto=1
let a = execute('cstag index_to_key')
call assert_match('(1 of 1): <<index_to_key>> #define index_to_key(i) ((i) ^ 15167)', a)
call assert_equal('#define index_to_key(i) ((i) ^ 15167)', getline('.'))
- call assert_fails('cstag xxx', 'E257:')
+ call assert_fails('cstag DOES_NOT_EXIST', 'E257:')
call assert_fails('cstag', 'E562:')
+ let save_tags = &tags
+ set tags=
+ call assert_fails('cstag DOES_NOT_EXIST', 'E257:')
+ let a = execute('cstag index_to_key')
+ call assert_match('(1 of 1): <<index_to_key>> #define index_to_key(i) ((i) ^ 15167)', a)
+ let &tags = save_tags
- " Test 18: 'cst' option
+ " Test: 'cst' option
set nocst
call assert_fails('tag TEST_COUNT', 'E426:')
set cst
@@ -189,12 +204,28 @@ func Test_cscopeWithCscopeConnections()
let a = execute('tags')
call assert_match('1 1 TEST_COUNT\s\+\d\+\s\+#define index_to_key', a)
- " Test 19: this should trigger call to cs_print_tags()
+ " Test: 'cscoperelative'
+ call mkdir('Xcscoperelative')
+ cd Xcscoperelative
+ let a = execute('cs find g test_mf_hash')
+ call assert_notequal('test_mf_hash(void)', getline('.'))
+ set cscoperelative
+ let a = execute('cs find g test_mf_hash')
+ call assert_equal('test_mf_hash(void)', getline('.'))
+ set nocscoperelative
+ cd ..
+ call delete('Xcscoperelative', 'd')
+
+ " Test: E259: no match found
+ call assert_fails('cscope find g DOES_NOT_EXIST', 'E259:')
+
+ " Test: this should trigger call to cs_print_tags()
" Unclear how to check result though, we just exercise the code.
set cst cscopequickfix=s0
call feedkeys(":cs find s main\<CR>", 't')
- " Test 20: cscope kill
+ " Test: cscope kill
+ call assert_fails('cscope kill', 'E560:')
call assert_fails('cscope kill 2', 'E261:')
call assert_fails('cscope kill xxx', 'E261:')
@@ -211,20 +242,20 @@ func Test_cscopeWithCscopeConnections()
let a = execute('cscope kill -1')
call assert_equal('', a)
- " Test 21: 'csprg' option
+ " Test: 'csprg' option
call assert_equal('cscope', &csprg)
set csprg=doesnotexist
call assert_fails('cscope add Xcscope2.out', 'E609:')
set csprg=cscope
- " Test 22: multiple cscope connections
+ " Test: multiple cscope connections
cscope add Xcscope.out
cscope add Xcscope2.out . -C
let a = execute('cscope show')
call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a)
call assert_match('\n 1 \d\+.*Xcscope2.out\s*\.', a)
- " Test 23: test Ex command line completion
+ " Test: test Ex command line completion
call feedkeys(":cs \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"cs add find help kill reset show', @:)
@@ -240,19 +271,26 @@ func Test_cscopeWithCscopeConnections()
call feedkeys(":cs add Xcscope\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"cs add Xcscope.out Xcscope2.out', @:)
- " Test 24: cscope_connection()
+ " Test: cscope_connection()
call assert_equal(cscope_connection(), 1)
call assert_equal(cscope_connection(0, 'out'), 1)
call assert_equal(cscope_connection(0, 'xxx'), 1)
+
call assert_equal(cscope_connection(1, 'out'), 1)
call assert_equal(cscope_connection(1, 'xxx'), 0)
+
call assert_equal(cscope_connection(2, 'out'), 0)
+ call assert_equal(cscope_connection(2, getcwd() .. '/Xcscope.out', 1), 1)
+
call assert_equal(cscope_connection(3, 'xxx', '..'), 0)
call assert_equal(cscope_connection(3, 'out', 'xxx'), 0)
call assert_equal(cscope_connection(3, 'out', '.'), 1)
+
call assert_equal(cscope_connection(4, 'out', '.'), 0)
- " CleanUp
+ call assert_equal(cscope_connection(5, 'out'), 0)
+ call assert_equal(cscope_connection(-1, 'out'), 0)
+
call CscopeSetupOrClean(0)
endfunc
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 21c1f98283..8592f48af7 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -781,17 +781,68 @@ func Test_diff_lastline()
bwipe!
endfunc
+func WriteDiffFiles(buf, list1, list2)
+ call writefile(a:list1, 'Xfile1')
+ call writefile(a:list2, 'Xfile2')
+ if a:buf
+ call term_sendkeys(a:buf, ":checktime\<CR>")
+ endif
+endfunc
+" Verify a screendump with both the internal and external diff.
+func VerifyBoth(buf, dumpfile, extra)
+ " trailing : for leaving the cursor on the command line
+ for cmd in [":set diffopt=filler" . a:extra . "\<CR>:", ":set diffopt+=internal\<CR>:"]
+ call term_sendkeys(a:buf, cmd)
+ if VerifyScreenDump(a:buf, a:dumpfile, {}, cmd =~ 'internal' ? 'internal' : 'external')
+ break " don't let the next iteration overwrite the "failed" file.
+ " don't let the next iteration overwrite the "failed" file.
+ return
+ endif
+ endfor
+
+ " also test unified diff
+ call term_sendkeys(a:buf, ":call SetupUnified()\<CR>:")
+ call term_sendkeys(a:buf, ":redraw!\<CR>:")
+ call VerifyScreenDump(a:buf, a:dumpfile, {}, 'unified')
+ call term_sendkeys(a:buf, ":call StopUnified()\<CR>:")
+endfunc
+
+" Verify a screendump with the internal diff only.
+func VerifyInternal(buf, dumpfile, extra)
+ call term_sendkeys(a:buf, ":diffupdate!\<CR>")
+ " trailing : for leaving the cursor on the command line
+ call term_sendkeys(a:buf, ":set diffopt=internal,filler" . a:extra . "\<CR>:")
+ call TermWait(a:buf)
+ call VerifyScreenDump(a:buf, a:dumpfile, {})
+endfunc
+
func Test_diff_screen()
CheckScreendump
CheckFeature menu
+ let lines =<< trim END
+ func UnifiedDiffExpr()
+ " Prepend some text to check diff type detection
+ call writefile(['warning', ' message'], v:fname_out)
+ silent exe '!diff -U0 ' .. v:fname_in .. ' ' .. v:fname_new .. '>>' .. v:fname_out
+ endfunc
+ func SetupUnified()
+ set diffexpr=UnifiedDiffExpr()
+ diffupdate
+ endfunc
+ func StopUnified()
+ set diffexpr=
+ endfunc
+ END
+ call writefile(lines, 'XdiffSetup')
+
" clean up already existing swap files, just in case
call delete('.Xfile1.swp')
call delete('.Xfile2.swp')
" Test 1: Add a line in beginning of file 2
call WriteDiffFiles(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
- let buf = RunVimInTerminal('-d Xfile1 Xfile2', {})
+ let buf = RunVimInTerminal('-d -S XdiffSetup Xfile1 Xfile2', {})
" Set autoread mode, so that Vim won't complain once we re-write the test
" files
call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w")
@@ -911,6 +962,7 @@ func Test_diff_screen()
call StopVimInTerminal(buf)
call delete('Xfile1')
call delete('Xfile2')
+ call delete('XdiffSetup')
endfunc
func Test_diff_with_cursorline()
@@ -1096,4 +1148,38 @@ func Test_diff_and_scroll()
set ls&
endfunc
+func Test_diff_filler_cursorcolumn()
+ CheckScreendump
+
+ let content =<< trim END
+ call setline(1, ['aa', 'bb', 'cc'])
+ vnew
+ call setline(1, ['aa', 'cc'])
+ windo diffthis
+ wincmd p
+ setlocal cursorcolumn foldcolumn=0
+ norm! gg0
+ redraw!
+ END
+ call writefile(content, 'Xtest_diff_cuc')
+ let buf = RunVimInTerminal('-S Xtest_diff_cuc', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_cuc_01', {})
+
+ call term_sendkeys(buf, "l")
+ call term_sendkeys(buf, "\<C-l>")
+ call VerifyScreenDump(buf, 'Test_diff_cuc_02', {})
+ call term_sendkeys(buf, "0j")
+ call term_sendkeys(buf, "\<C-l>")
+ call VerifyScreenDump(buf, 'Test_diff_cuc_03', {})
+ call term_sendkeys(buf, "l")
+ call term_sendkeys(buf, "\<C-l>")
+ call VerifyScreenDump(buf, 'Test_diff_cuc_04', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_diff_cuc')
+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_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 056b953d0b..09fdbf4e20 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -211,7 +211,7 @@ let s:filename_checks = {
\ 'gtkrc': ['.gtkrc', 'gtkrc', '.gtkrc-file', 'gtkrc-file'],
\ 'haml': ['file.haml'],
\ 'hamster': ['file.hsm'],
- \ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot'],
+ \ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot', 'file.hsig'],
\ 'haste': ['file.ht'],
\ 'hastepreproc': ['file.htpp'],
\ 'hb': ['file.hb'],
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index c280aedffb..224ca257ab 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -28,12 +28,14 @@ func Test_empty()
call assert_equal(0, empty(1))
call assert_equal(0, empty(-1))
- call assert_equal(1, empty(0.0))
- call assert_equal(1, empty(-0.0))
- call assert_equal(0, empty(1.0))
- call assert_equal(0, empty(-1.0))
- call assert_equal(0, empty(1.0/0.0))
- call assert_equal(0, empty(0.0/0.0))
+ if has('float')
+ call assert_equal(1, empty(0.0))
+ call assert_equal(1, empty(-0.0))
+ call assert_equal(0, empty(1.0))
+ call assert_equal(0, empty(-1.0))
+ call assert_equal(0, empty(1.0/0.0))
+ call assert_equal(0, empty(0.0/0.0))
+ endif
call assert_equal(1, empty([]))
call assert_equal(0, empty(['a']))
@@ -115,7 +117,9 @@ func Test_strwidth()
call assert_fails('call strwidth({->0})', 'E729:')
call assert_fails('call strwidth([])', 'E730:')
call assert_fails('call strwidth({})', 'E731:')
- call assert_fails('call strwidth(1.2)', 'E806:')
+ if has('float')
+ call assert_fails('call strwidth(1.2)', 'E806:')
+ endif
endfor
set ambiwidth&
@@ -319,19 +323,19 @@ func Test_setbufvar_options()
let prev_id = win_getid()
wincmd j
- let wh = winheight('.')
+ let wh = winheight(0)
let dummy_buf = bufnr('dummy_buf1', v:true)
call setbufvar(dummy_buf, '&buftype', 'nofile')
execute 'belowright vertical split #' . dummy_buf
- call assert_equal(wh, winheight('.'))
+ call assert_equal(wh, winheight(0))
let dum1_id = win_getid()
wincmd h
- let wh = winheight('.')
+ let wh = winheight(0)
let dummy_buf = bufnr('dummy_buf2', v:true)
call setbufvar(dummy_buf, '&buftype', 'nofile')
execute 'belowright vertical split #' . dummy_buf
- call assert_equal(wh, winheight('.'))
+ call assert_equal(wh, winheight(0))
bwipe!
call win_gotoid(prev_id)
@@ -1067,6 +1071,22 @@ func Test_inputlist()
call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>3\<cr>", 'tx')
call assert_equal(3, c)
+ " CR to cancel
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<cr>", 'tx')
+ call assert_equal(0, c)
+
+ " Esc to cancel
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<Esc>", 'tx')
+ call assert_equal(0, c)
+
+ " q to cancel
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>q", 'tx')
+ call assert_equal(0, c)
+
+ " Cancel after inputting a number
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>5q", 'tx')
+ call assert_equal(0, c)
+
call assert_fails('call inputlist("")', 'E686:')
endfunc
@@ -1402,10 +1422,6 @@ func Test_bufadd_bufload()
endfunc
func Test_readdir()
- if isdirectory('Xdir')
- call delete('Xdir', 'rf')
- endif
-
call mkdir('Xdir')
call writefile([], 'Xdir/foo.txt')
call writefile([], 'Xdir/bar.txt')
@@ -1460,19 +1476,4 @@ func Test_default_arg_value()
call assert_equal('msg', HasDefault())
endfunc
-func Test_delete_rf()
- call mkdir('Xdir')
- call writefile([], 'Xdir/foo.txt')
- call writefile([], 'Xdir/bar.txt')
- call mkdir('Xdir/[a-1]') " issue #696
- call writefile([], 'Xdir/[a-1]/foo.txt')
- call writefile([], 'Xdir/[a-1]/bar.txt')
- call assert_true(filereadable('Xdir/foo.txt'))
- call assert_true(filereadable('Xdir/[a-1]/foo.txt'))
-
- call assert_equal(0, delete('Xdir', 'rf'))
- call assert_false(filereadable('Xdir/foo.txt'))
- call assert_false(filereadable('Xdir/[a-1]/foo.txt'))
-endfunc
-
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_glob2regpat.vim b/src/nvim/testdir/test_glob2regpat.vim
index e6e41f13e7..354f239ef1 100644
--- a/src/nvim/testdir/test_glob2regpat.vim
+++ b/src/nvim/testdir/test_glob2regpat.vim
@@ -1,7 +1,9 @@
" Test glob2regpat()
func Test_glob2regpat_invalid()
- call assert_fails('call glob2regpat(1.33)', 'E806:')
+ if has('float')
+ call assert_fails('call glob2regpat(1.33)', 'E806:')
+ endif
call assert_fails('call glob2regpat("}")', 'E219:')
call assert_fails('call glob2regpat("{")', 'E220:')
endfunc
diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim
index ce22de09ca..24c9c3580e 100644
--- a/src/nvim/testdir/test_highlight.vim
+++ b/src/nvim/testdir/test_highlight.vim
@@ -595,6 +595,42 @@ func Test_cursorline_with_visualmode()
call delete('Xtest_cursorline_with_visualmode')
endfunc
+func Test_colorcolumn_bri()
+ CheckScreendump
+
+ " check 'colorcolumn' when 'breakindent' is set
+ let lines =<< trim END
+ call setline(1, 'The quick brown fox jumped over the lazy dogs')
+ END
+ call writefile(lines, 'Xtest_colorcolumn_bri')
+ let buf = RunVimInTerminal('-S Xtest_colorcolumn_bri', {'rows': 10,'columns': 40})
+ call term_sendkeys(buf, ":set co=40 linebreak bri briopt=shift:2 cc=40,41,43\<CR>")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_colorcolumn_2', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_colorcolumn_bri')
+endfunc
+
+func Test_colorcolumn_sbr()
+ CheckScreendump
+
+ " check 'colorcolumn' when 'showbreak' is set
+ let lines =<< trim END
+ call setline(1, 'The quick brown fox jumped over the lazy dogs')
+ END
+ call writefile(lines, 'Xtest_colorcolumn_srb')
+ let buf = RunVimInTerminal('-S Xtest_colorcolumn_srb', {'rows': 10,'columns': 40})
+ call term_sendkeys(buf, ":set co=40 showbreak=+++>\\ cc=40,41,43\<CR>")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_colorcolumn_3', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_colorcolumn_srb')
+endfunc
+
" This test must come before the Test_cursorline test, as it appears this
" defines the Normal highlighting group anyway.
func Test_1_highlight_Normalgroup_exists()
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
index affb141a26..5152af8f58 100644
--- a/src/nvim/testdir/test_listdict.vim
+++ b/src/nvim/testdir/test_listdict.vim
@@ -689,7 +689,9 @@ func Test_listdict_extend()
let l = [1, 2, 3]
call assert_fails("call extend(l, [4, 5, 6], 4)", 'E684:')
call assert_fails("call extend(l, [4, 5, 6], -4)", 'E684:')
- call assert_fails("call extend(l, [4, 5, 6], 1.2)", 'E805:')
+ if has('float')
+ call assert_fails("call extend(l, [4, 5, 6], 1.2)", 'E805:')
+ endif
" Test extend() with dictionaries.
@@ -713,7 +715,9 @@ func Test_listdict_extend()
let d = {'a': 'A', 'b': 'B'}
call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'error')", 'E737:')
call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'xxx')", 'E475:')
- call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 1.2)", 'E806:')
+ if has('float')
+ call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 1.2)", 'E806:')
+ endif
call assert_equal({'a': 'A', 'b': 'B'}, d)
call assert_fails("call extend([1, 2], 1)", 'E712:')
diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim
index 52aac05ea1..8c90f21600 100644
--- a/src/nvim/testdir/test_partial.vim
+++ b/src/nvim/testdir/test_partial.vim
@@ -105,7 +105,7 @@ fun InnerCall(funcref)
endfu
fun OuterCall()
- let opt = { 'func' : function('sin') }
+ let opt = { 'func' : function('max') }
call InnerCall(opt.func)
endfu
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 16b6a5f464..bf15f7f52b 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -14,7 +14,7 @@ func s:setup_commands(cchar)
command! -nargs=* Xaddexpr <mods>caddexpr <args>
command! -nargs=* -count Xolder <mods><count>colder <args>
command! -nargs=* Xnewer <mods>cnewer <args>
- command! -nargs=* Xopen <mods>copen <args>
+ command! -nargs=* Xopen <mods> copen <args>
command! -nargs=* Xwindow <mods>cwindow <args>
command! -nargs=* Xbottom <mods>cbottom <args>
command! -nargs=* Xclose <mods>cclose <args>
@@ -32,8 +32,8 @@ func s:setup_commands(cchar)
command! -count -nargs=* -bang Xnfile <mods><count>cnfile<bang> <args>
command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args>
command! -nargs=* Xexpr <mods>cexpr <args>
- command! -range -nargs=* Xvimgrep <mods><count>vimgrep <args>
- command! -nargs=* Xvimgrepadd <mods>vimgrepadd <args>
+ command! -count -nargs=* Xvimgrep <mods> <count>vimgrep <args>
+ command! -nargs=* Xvimgrepadd <mods> vimgrepadd <args>
command! -nargs=* Xgrep <mods> grep <args>
command! -nargs=* Xgrepadd <mods> grepadd <args>
command! -nargs=* Xhelpgrep helpgrep <args>
@@ -51,7 +51,7 @@ func s:setup_commands(cchar)
command! -nargs=* Xaddexpr <mods>laddexpr <args>
command! -nargs=* -count Xolder <mods><count>lolder <args>
command! -nargs=* Xnewer <mods>lnewer <args>
- command! -nargs=* Xopen <mods>lopen <args>
+ command! -nargs=* Xopen <mods> lopen <args>
command! -nargs=* Xwindow <mods>lwindow <args>
command! -nargs=* Xbottom <mods>lbottom <args>
command! -nargs=* Xclose <mods>lclose <args>
@@ -69,8 +69,8 @@ func s:setup_commands(cchar)
command! -count -nargs=* -bang Xnfile <mods><count>lnfile<bang> <args>
command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args>
command! -nargs=* Xexpr <mods>lexpr <args>
- command! -range -nargs=* Xvimgrep <mods><count>lvimgrep <args>
- command! -nargs=* Xvimgrepadd <mods>lvimgrepadd <args>
+ command! -count -nargs=* Xvimgrep <mods> <count>lvimgrep <args>
+ command! -nargs=* Xvimgrepadd <mods> lvimgrepadd <args>
command! -nargs=* Xgrep <mods> lgrep <args>
command! -nargs=* Xgrepadd <mods> lgrepadd <args>
command! -nargs=* Xhelpgrep lhelpgrep <args>
@@ -157,6 +157,12 @@ func XlistTests(cchar)
\ ' 2 Data.Text:20 col 10 warning 22: ModuleWarning',
\ ' 3 Data/Text.hs:30 col 15 warning 33: FileWarning'], l)
+ " For help entries in the quickfix list, only the filename without directory
+ " should be displayed
+ Xhelpgrep setqflist()
+ let l = split(execute('Xlist 1', ''), "\n")
+ call assert_match('^ 1 [^\\/]\{-}:', l[0])
+
" Error cases
call assert_fails('Xlist abc', 'E488:')
endfunc
@@ -255,13 +261,13 @@ func XwindowTests(cchar)
" Open the window
Xopen 5
call assert_true(winnr('$') == 2 && getline('.') ==# '|| non-error 1'
- \ && winheight('.') == 5)
+ \ && winheight(0) == 5)
" Opening the window again, should move the cursor to that window
wincmd t
Xopen 7
call assert_true(winnr('$') == 2 && winnr() == 2 &&
- \ winheight('.') == 7 &&
+ \ winheight(0) == 7 &&
\ getline('.') ==# '|| non-error 1')
" :cnext in quickfix window should move to the next entry
@@ -272,6 +278,14 @@ func XwindowTests(cchar)
Xwindow
call assert_true(winnr('$') == 1)
+ " Specifying the width should adjust the width for a vertically split
+ " quickfix window.
+ vert Xopen
+ call assert_equal(10, winwidth(0))
+ vert Xopen 12
+ call assert_equal(12, winwidth(0))
+ Xclose
+
if a:cchar == 'c'
" Opening the quickfix window in multiple tab pages should reuse the
" quickfix buffer
@@ -352,6 +366,13 @@ func XfileTests(cchar)
\ l[0].lnum == 222 && l[0].col == 77 && l[0].text ==# 'Line 222' &&
\ l[1].lnum == 333 && l[1].col == 88 && l[1].text ==# 'Line 333')
+ " Test for a file with a long line and without a newline at the end
+ let text = repeat('x', 1024)
+ let t = 'a.txt:18:' . text
+ call writefile([t], 'Xqftestfile1', 'b')
+ silent! Xfile Xqftestfile1
+ call assert_equal(text, g:Xgetlist()[0].text)
+
call delete('Xqftestfile1')
endfunc
@@ -475,6 +496,12 @@ func Xtest_browse(cchar)
call assert_equal(5, g:Xgetlist({'idx':0}).idx)
2Xcc
call assert_equal(2, g:Xgetlist({'idx':0}).idx)
+ if a:cchar == 'c'
+ cc
+ else
+ ll
+ endif
+ call assert_equal(2, g:Xgetlist({'idx':0}).idx)
10Xcc
call assert_equal(6, g:Xgetlist({'idx':0}).idx)
Xlast
@@ -483,6 +510,14 @@ func Xtest_browse(cchar)
call assert_equal(11, line('.'))
call assert_fails('Xnext', 'E553')
call assert_fails('Xnfile', 'E553')
+ " To process the range using quickfix list entries, directly use the
+ " quickfix commands (don't use the user defined commands)
+ if a:cchar == 'c'
+ $cc
+ else
+ $ll
+ endif
+ call assert_equal(6, g:Xgetlist({'idx':0}).idx)
Xrewind
call assert_equal('Xqftestfile1', bufname('%'))
call assert_equal(5, line('.'))
@@ -1011,9 +1046,10 @@ endfunc
" Tests for %D and %X errorformat options
func Test_efm_dirstack()
" Create the directory stack and files
- call mkdir('dir1/a', 'p')
- call mkdir('dir1/a/b', 'p')
- call mkdir('dir1/c', 'p')
+ call mkdir('dir1')
+ call mkdir('dir1/a')
+ call mkdir('dir1/a/b')
+ call mkdir('dir1/c')
call mkdir('dir2')
let lines =<< trim [DATA]
@@ -1094,6 +1130,10 @@ func Xinvalid_efm_Tests(cchar)
set efm=%f:%l:%m,%f:%l:%m:%R
call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E377:')
+ " Invalid regular expression
+ set efm=%\\%%k
+ call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E867:')
+
set efm=
call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E378:')
@@ -1124,6 +1164,11 @@ func Test_efm2()
let l = split(execute('clist', ''), "\n")
call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l)
+ " Test for a long line
+ cexpr 'Xtestfile:' . repeat('a', 1026)
+ let l = getqflist()
+ call assert_equal('^\V' . repeat('a', 1019) . '\$', l[0].pattern)
+
" Test for %P, %Q and %t format specifiers
let lines =<< trim [DATA]
[Xtestfile1]
@@ -1161,6 +1206,14 @@ func Test_efm2()
call delete('Xtestfile2')
call delete('Xtestfile3')
+ " Test for %P, %Q with non-existing files
+ cexpr lines
+ let l = getqflist()
+ call assert_equal(14, len(l))
+ call assert_equal('[Xtestfile1]', l[0].text)
+ call assert_equal('[Xtestfile2]', l[6].text)
+ call assert_equal('[Xtestfile3]', l[9].text)
+
" Tests for %E, %C and %Z format specifiers
let lines =<< trim [DATA]
Error 275
@@ -1202,18 +1255,19 @@ func Test_efm2()
File "/usr/lib/python2.2/unittest.py", line 286, in
failUnlessEqual
raise self.failureException, \\
- AssertionError: 34 != 33
+ W:AssertionError: 34 != 33
--------------------------------------------------------------
Ran 27 tests in 0.063s
[DATA]
- set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%m
+ set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%t:%m
cgetexpr lines
let l = getqflist()
call assert_equal(8, len(l))
call assert_equal(89, l[4].lnum)
call assert_equal(1, l[4].valid)
call assert_equal(expand('unittests/dbfacadeTest.py'), bufname(l[4].bufnr))
+ call assert_equal('W', l[4].type)
" Test for %o
set efm=%f(%o):%l\ %m
@@ -1230,6 +1284,14 @@ func Test_efm2()
bd
call delete("Xotestfile")
+ " Test for a long module name
+ cexpr 'Xtest(' . repeat('m', 1026) . '):15 message'
+ let l = getqflist()
+ " call assert_equal(repeat('m', 1024), l[0].module)
+ call assert_equal(repeat('m', 1023), l[0].module)
+ call assert_equal(15, l[0].lnum)
+ call assert_equal('message', l[0].text)
+
" The following sequence of commands used to crash Vim
set efm=%W%m
cgetexpr ['msg1']
@@ -1715,9 +1777,11 @@ func Test_switchbuf()
call assert_equal(winid, win_getid())
2cnext
call assert_equal(winid, win_getid())
- enew
+ " Test for 'switchbuf' set to search for files in windows in the current
+ " tabpage and jump to an existing window (if present)
set switchbuf=useopen
+ enew
cfirst | cnext
call assert_equal(file1_winid, win_getid())
2cnext
@@ -1725,6 +1789,8 @@ func Test_switchbuf()
2cnext
call assert_equal(file2_winid, win_getid())
+ " Test for 'switchbuf' set to search for files in tabpages and jump to an
+ " existing tabpage (if present)
enew | only
set switchbuf=usetab
tabedit Xqftestfile1
@@ -1743,6 +1809,7 @@ func Test_switchbuf()
call assert_equal(4, tabpagenr())
tabfirst | tabonly | enew
+ " Test for 'switchbuf' set to open a new window for every file
set switchbuf=split
cfirst | cnext
call assert_equal(1, winnr('$'))
@@ -1750,9 +1817,10 @@ func Test_switchbuf()
call assert_equal(2, winnr('$'))
cnext | cnext
call assert_equal(3, winnr('$'))
- enew | only
+ " Test for 'switchbuf' set to open a new tabpage for every file
set switchbuf=newtab
+ enew | only
cfirst | cnext
call assert_equal(1, tabpagenr('$'))
cnext | cnext
@@ -1769,6 +1837,8 @@ func Test_switchbuf()
call assert_equal(last_winid, win_getid())
enew | only
+ " With an empty 'switchbuf', jumping to a quickfix entry should open the
+ " file in an existing window (if present)
set switchbuf=
edit Xqftestfile1
let file1_winid = win_getid()
@@ -1798,6 +1868,32 @@ func Test_switchbuf()
call assert_equal(4, tabpagenr())
tabfirst | tabonly | enew | only
+ " Jumping to a file that is not present in any of the tabpages and the
+ " current tabpage doesn't have any usable windows, should open it in a new
+ " window in the current tabpage.
+ copen | only
+ cfirst
+ call assert_equal(1, tabpagenr())
+ call assert_equal('Xqftestfile1', bufname(''))
+
+ " If opening a file changes 'switchbuf', then the new value should be
+ " retained.
+ call writefile(["vim: switchbuf=split"], 'Xqftestfile1')
+ enew | only
+ set switchbuf&vim
+ cexpr "Xqftestfile1:1:10"
+ call assert_equal('split', &switchbuf)
+ call writefile(["vim: switchbuf=usetab"], 'Xqftestfile1')
+ enew | only
+ set switchbuf=useopen
+ cexpr "Xqftestfile1:1:10"
+ call assert_equal('usetab', &switchbuf)
+ call writefile(["vim: switchbuf&vim"], 'Xqftestfile1')
+ enew | only
+ set switchbuf=useopen
+ cexpr "Xqftestfile1:1:10"
+ call assert_equal('', &switchbuf)
+
call delete('Xqftestfile1')
call delete('Xqftestfile2')
call delete('Xqftestfile3')
@@ -1824,11 +1920,16 @@ func Xadjust_qflnum(cchar)
call append(6, ['Buffer', 'Window'])
let l = g:Xgetlist()
-
call assert_equal(5, l[0].lnum)
call assert_equal(6, l[2].lnum)
call assert_equal(13, l[3].lnum)
+ " If a file doesn't have any quickfix entries, then deleting lines in the
+ " file should not update the quickfix list
+ call g:Xsetlist([], 'f')
+ 1,2delete
+ call assert_equal([], g:Xgetlist())
+
enew!
call delete(fname)
endfunc
@@ -2616,7 +2717,7 @@ func XvimgrepTests(cchar)
call assert_equal(2, len(l))
call assert_equal('Editor:Notepad NOTEPAD', l[0].text)
- Xvimgrep #\cvim#g Xtestfile?
+ 10Xvimgrep #\cvim#g Xtestfile?
let l = g:Xgetlist()
call assert_equal(2, len(l))
call assert_equal(8, l[0].col)
@@ -3484,9 +3585,6 @@ func Xqftick_tests(cchar)
\ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], 'r')
call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
- if isdirectory("Xone")
- call delete("Xone", 'rf')
- endif
call writefile(["F8:80:L80", "F8:81:L81"], "Xone")
Xfile Xone
call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
@@ -3692,6 +3790,41 @@ func Test_vimgrep_autocmd()
call setqflist([], 'f')
endfunc
+" Test for an autocmd changing the current directory when running vimgrep
+func Xvimgrep_autocmd_cd(cchar)
+ call s:setup_commands(a:cchar)
+
+ %bwipe
+ let save_cwd = getcwd()
+
+ augroup QF_Test
+ au!
+ autocmd BufRead * silent cd %:p:h
+ augroup END
+
+ 10Xvimgrep /vim/ Xdir/**
+ let l = g:Xgetlist()
+ call assert_equal('f1.txt', bufname(l[0].bufnr))
+ call assert_equal('f2.txt', fnamemodify(bufname(l[2].bufnr), ':t'))
+
+ augroup QF_Test
+ au!
+ augroup END
+
+ exe 'cd ' . save_cwd
+endfunc
+
+func Test_vimgrep_autocmd_cd()
+ call mkdir('Xdir/a', 'p')
+ call mkdir('Xdir/b', 'p')
+ call writefile(['a_L1_vim', 'a_L2_vim'], 'Xdir/a/f1.txt')
+ call writefile(['b_L1_vim', 'b_L2_vim'], 'Xdir/b/f2.txt')
+ call Xvimgrep_autocmd_cd('c')
+ call Xvimgrep_autocmd_cd('l')
+ %bwipe
+ call delete('Xdir', 'rf')
+endfunc
+
" The following test used to crash Vim
func Test_lhelpgrep_autocmd()
lhelpgrep quickfix
@@ -4788,4 +4921,148 @@ func Test_qfbuf_update()
call Xqfbuf_update('l')
endfunc
+" Test for getting a specific item from a quickfix list
+func Xtest_getqflist_by_idx(cchar)
+ call s:setup_commands(a:cchar)
+ " Empty list
+ call assert_equal([], g:Xgetlist({'idx' : 1, 'items' : 0}).items)
+ Xexpr ['F1:10:L10', 'F1:20:L20']
+ let l = g:Xgetlist({'idx' : 2, 'items' : 0}).items
+ call assert_equal(bufnr('F1'), l[0].bufnr)
+ call assert_equal(20, l[0].lnum)
+ call assert_equal('L20', l[0].text)
+ call assert_equal([], g:Xgetlist({'idx' : -1, 'items' : 0}).items)
+ call assert_equal([], g:Xgetlist({'idx' : 3, 'items' : 0}).items)
+ %bwipe!
+endfunc
+
+func Test_getqflist_by_idx()
+ call Xtest_getqflist_by_idx('c')
+ call Xtest_getqflist_by_idx('l')
+endfunc
+
+" Test for the 'quickfixtextfunc' setting
+func Tqfexpr(info)
+ if a:info.quickfix
+ let qfl = getqflist({'id' : a:info.id, 'items' : 1}).items
+ else
+ let qfl = getloclist(a:info.winid, {'id' : a:info.id, 'items' : 1}).items
+ endif
+
+
+ let l = []
+ for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
+ let e = qfl[idx]
+ let s = ''
+ if e.bufnr != 0
+ let bname = bufname(e.bufnr)
+ let s ..= fnamemodify(bname, ':.')
+ endif
+ let s ..= '-'
+ let s ..= 'L' .. string(e.lnum) .. 'C' .. string(e.col) .. '-'
+ let s ..= e.text
+ call add(l, s)
+ endfor
+
+ return l
+endfunc
+
+func Xtest_qftextfunc(cchar)
+ call s:setup_commands(a:cchar)
+
+ set efm=%f:%l:%c:%m
+ set quickfixtextfunc=Tqfexpr
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal('F1-L10C2-green', getline(1))
+ call assert_equal('F1-L20C4-blue', getline(2))
+ Xclose
+ set quickfixtextfunc&vim
+ Xwindow
+ call assert_equal('F1|10 col 2| green', getline(1))
+ call assert_equal('F1|20 col 4| blue', getline(2))
+ Xclose
+ set efm&
+ set quickfixtextfunc&
+
+ " Test for per list 'quickfixtextfunc' setting
+ func PerQfText(info)
+ if a:info.quickfix
+ let qfl = getqflist({'id' : a:info.id, 'items' : 1}).items
+ else
+ let qfl = getloclist(a:info.winid, {'id' : a:info.id, 'items' : 1}).items
+ endif
+ if empty(qfl)
+ return []
+ endif
+ let l = []
+ for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
+ call add(l, 'Line ' .. qfl[idx].lnum .. ', Col ' .. qfl[idx].col)
+ endfor
+ return l
+ endfunc
+ set quickfixtextfunc=Tqfexpr
+ call g:Xsetlist([], ' ', {'quickfixtextfunc' : "PerQfText"})
+ Xaddexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal('Line 10, Col 2', getline(1))
+ call assert_equal('Line 20, Col 4', getline(2))
+ Xclose
+ " Add entries to the list when the quickfix buffer is hidden
+ Xaddexpr ['F1:30:6:red']
+ Xwindow
+ call assert_equal('Line 30, Col 6', getline(3))
+ Xclose
+ call g:Xsetlist([], 'r', {'quickfixtextfunc' : ''})
+ set quickfixtextfunc&
+ delfunc PerQfText
+
+ " Non-existing function
+ set quickfixtextfunc=Tabc
+ " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:')
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']"
+ call assert_fails("Xwindow", 'E117:')
+ Xclose
+ set quickfixtextfunc&
+
+ " set option to a non-function
+ set quickfixtextfunc=[10,\ 20]
+ " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:')
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']"
+ call assert_fails("Xwindow", 'E117:')
+ Xclose
+ set quickfixtextfunc&
+
+ " set option to a function with different set of arguments
+ func Xqftext(a, b, c)
+ return a:a .. a:b .. a:c
+ endfunc
+ set quickfixtextfunc=Xqftext
+ " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E119:')
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']"
+ call assert_fails("Xwindow", 'E119:')
+ Xclose
+
+ " set option to a function that returns a list with non-strings
+ func Xqftext2(d)
+ return ['one', [], 'two']
+ endfunc
+ set quickfixtextfunc=Xqftext2
+ " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']",
+ " \ 'E730:')
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']
+ call assert_fails('Xwindow', 'E730:')
+ call assert_equal(['one', 'F1|20 col 4| blue', 'two'], getline(1, '$'))
+ Xclose
+
+ set quickfixtextfunc&
+ delfunc Xqftext
+ delfunc Xqftext2
+endfunc
+
+func Test_qftextfunc()
+ call Xtest_qftextfunc('c')
+ call Xtest_qftextfunc('l')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim
index cacdd68d10..712f1e6025 100644
--- a/src/nvim/testdir/test_regexp_latin.vim
+++ b/src/nvim/testdir/test_regexp_latin.vim
@@ -1,7 +1,10 @@
" Tests for regexp in latin1 encoding
+
" set encoding=latin1
scriptencoding latin1
+source check.vim
+
func s:equivalence_test()
let str = "AÀÁÂÃÄÅ B C D EÈÉÊË F G H IÌÍÎÏ J K L M NÑ OÒÓÔÕÖØ P Q R S T UÙÚÛÜ V W X YÝ Z aàáâãäå b c d eèéêë f g h iìíîï j k l m nñ oòóôõöø p q r s t uùúûü v w x yýÿ z"
let groups = split(str)
@@ -42,9 +45,9 @@ func Test_range_with_newline()
endfunc
func Test_pattern_compile_speed()
- if !exists('+spellcapcheck') || !has('reltime')
- return
- endif
+ CheckOption spellcapcheck
+ CheckFunction reltimefloat
+
let start = reltime()
" this used to be very slow, not it should be about a second
set spc=\\v(((((Nxxxxxxx&&xxxx){179})+)+)+){179}
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim
index 513780938e..d8d5797dcf 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/src/nvim/testdir/test_regexp_utf8.vim
@@ -542,6 +542,52 @@ func Test_match_start_of_line_combining()
bwipe!
endfunc
+" Check that [[:upper:]] matches for automatic engine
+func Test_match_char_class_upper()
+ new
+ let _engine=&regexpengine
+
+ " Test 1: [[:upper:]]\{2,\}
+ set regexpengine=0
+ call setline(1, ['05. ПЕСНЯ О ГЕРОЯХ муз. А. Давиденко, М. Коваля и Б. Шехтера ...', '05. PJESNJA O GJEROJAKH mus. A. Davidjenko, M. Kovalja i B. Shjekhtjera ...'])
+ call cursor(1,1)
+ let search_cmd='norm /\<[[:upper:]]\{2,\}\>' .. "\<CR>"
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 1')
+ set regexpengine=1
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 1')
+ set regexpengine=2
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 1')
+
+ " Test 2: [[:upper:]].\+
+ let search_cmd='norm /\<[[:upper:]].\+\>' .. "\<CR>"
+ set regexpengine=0
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 2')
+ set regexpengine=1
+ exe search_cmd
+ call assert_equal(1, searchcount().total, 'TEST 2')
+ set regexpengine=2
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 2')
+
+ " Test 3: [[:lower:]]\+
+ let search_cmd='norm /\<[[:lower:]]\+\>' .. "\<CR>"
+ set regexpengine=0
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 3 lower')
+ set regexpengine=1
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 3 lower')
+ set regexpengine=2
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 3 lower')
+ " clean up
+ let &regexpengine=_engine
+ bwipe!
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index bcadb84ced..53069b3d31 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -43,9 +43,6 @@ func Test_yank_shows_register()
endfunc
func Test_display_registers()
- " Disable clipboard
- let g:clipboard = {}
-
e file1
e file2
call setline(1, ['foo', 'bar'])
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 75d42b986b..b391663e0f 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -1177,13 +1177,28 @@ func Test_look_behind()
bwipe!
endfunc
+func Test_search_visual_area_linewise()
+ new
+ call setline(1, ['aa', 'bb', 'cc'])
+ exe "normal 2GV\<Esc>"
+ for engine in [1, 2]
+ exe 'set regexpengine=' .. engine
+ exe "normal gg/\\%'<\<CR>>"
+ call assert_equal([0, 2, 1, 0, 1], getcurpos(), 'engine ' .. engine)
+ exe "normal gg/\\%'>\<CR>"
+ call assert_equal([0, 2, 2, 0, 2], getcurpos(), 'engine ' .. engine)
+ endfor
+
+ bwipe!
+ set regexpengine&
+endfunc
+
func Test_search_sentence()
new
" this used to cause a crash
- call assert_fails("/\\%')", 'E486')
- call assert_fails("/", 'E486')
/\%'(
/
+ bwipe
endfunc
" Test that there is no crash when there is a last search pattern but no last
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 6d55889641..570c4415c6 100644
--- a/src/nvim/testdir/test_sort.vim
+++ b/src/nvim/testdir/test_sort.vim
@@ -1,5 +1,7 @@
" Tests for the "sort()" function and for the ":sort" command.
+source check.vim
+
func Compare1(a, b) abort
call sort(range(3), 'Compare2')
return a:a - a:b
@@ -59,6 +61,7 @@ func Test_sort_numbers()
endfunc
func Test_sort_float()
+ CheckFeature float
call assert_equal([0.28, 3, 13.5], sort([13.5, 0.28, 3], 'f'))
endfunc
@@ -68,6 +71,8 @@ func Test_sort_nested()
endfunc
func Test_sort_default()
+ CheckFeature float
+
" docs say omitted, empty or zero argument sorts on string representation.
call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"]))
call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], ''))
@@ -1176,30 +1181,6 @@ func Test_sort_cmd()
\ ]
\ },
\ {
- \ 'name' : 'float',
- \ 'cmd' : 'sort f',
- \ 'input' : [
- \ '1.234',
- \ '0.88',
- \ ' + 123.456',
- \ '1.15e-6',
- \ '-1.1e3',
- \ '-1.01e3',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ '',
- \ '',
- \ '-1.1e3',
- \ '-1.01e3',
- \ '1.15e-6',
- \ '0.88',
- \ '1.234',
- \ ' + 123.456'
- \ ]
- \ },
- \ {
\ 'name' : 'alphabetical, sorted input',
\ 'cmd' : 'sort',
\ 'input' : [
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_true_false.vim b/src/nvim/testdir/test_true_false.vim
index 84aca737ac..315ba188cb 100644
--- a/src/nvim/testdir/test_true_false.vim
+++ b/src/nvim/testdir/test_true_false.vim
@@ -1,5 +1,7 @@
" Test behavior of boolean-like values.
+source check.vim
+
" Test what is explained at ":help TRUE" and ":help FALSE".
func Test_if()
if v:false
@@ -41,7 +43,9 @@ func Test_if()
call assert_fails('if [1]', 'E745')
call assert_fails('if {1: 1}', 'E728')
call assert_fails('if function("string")', 'E703')
- call assert_fails('if 1.3")', 'E805')
+ if has('float')
+ call assert_fails('if 1.3")', 'E805')
+ endif
endfunc
function Try_arg_true_false(expr, false_val, true_val)
@@ -113,6 +117,7 @@ func Test_true_false_arg()
endfunc
function Try_arg_non_zero(expr, false_val, true_val)
+ CheckFeature float
for v in ['v:false', '0', '[1]', '{2:3}', '3.4']
let r = eval(substitute(a:expr, '%v%', v, ''))
call assert_equal(a:false_val, r, 'result for ' . v . ' is not ' . a:false_val . ' but ' . r)
diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim
index e9e181ce3d..7dcd92a54b 100644
--- a/src/nvim/testdir/test_user_func.vim
+++ b/src/nvim/testdir/test_user_func.vim
@@ -113,9 +113,11 @@ func MakeBadFunc()
endfunc
func Test_default_arg()
- call assert_equal(1.0, Log(10))
- call assert_equal(log(10), Log(10, exp(1)))
- call assert_fails("call Log(1,2,3)", 'E118')
+ if has('float')
+ call assert_equal(1.0, Log(10))
+ call assert_equal(log(10), Log(10, exp(1)))
+ call assert_fails("call Log(1,2,3)", 'E118')
+ endif
let res = Args(1)
call assert_equal(res.mandatory, 1)
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index 43907de1d9..5922660ef9 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1271,8 +1271,10 @@ func Test_num64()
call assert_equal(-9223372036854775807, -1 / 0)
call assert_equal(-9223372036854775807 - 1, 0 / 0)
- call assert_equal( 0x7FFFffffFFFFffff, float2nr( 1.0e150))
- call assert_equal(-0x7FFFffffFFFFffff, float2nr(-1.0e150))
+ if has('float')
+ call assert_equal( 0x7FFFffffFFFFffff, float2nr( 1.0e150))
+ call assert_equal(-0x7FFFffffFFFFffff, float2nr(-1.0e150))
+ endif
let rng = range(0xFFFFffff, 0x100000001)
call assert_equal([0xFFFFffff, 0x100000000, 0x100000001], rng)
@@ -1531,22 +1533,22 @@ func Test_compound_assignment_operators()
call assert_equal('string', x)
let x += 1
call assert_equal(1, x)
- let x -= 1.5
- call assert_equal(-0.5, x)
if has('float')
- " Test for float
- let x = 0.5
- let x += 4.5
- call assert_equal(5.0, x)
- let x -= 1.5
- call assert_equal(3.5, x)
- let x *= 3.0
- call assert_equal(10.5, x)
- let x /= 2.5
- call assert_equal(4.2, x)
- call assert_fails('let x %= 0.5', 'E734')
- call assert_fails('let x .= "f"', 'E734')
+ " Test for float
+ let x -= 1.5
+ call assert_equal(-0.5, x)
+ let x = 0.5
+ let x += 4.5
+ call assert_equal(5.0, x)
+ let x -= 1.5
+ call assert_equal(3.5, x)
+ let x *= 3.0
+ call assert_equal(10.5, x)
+ let x /= 2.5
+ call assert_equal(4.2, x)
+ call assert_fails('let x %= 0.5', 'E734')
+ call assert_fails('let x .= "f"', 'E734')
endif
" Test for environment variable
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index c7710ff198..6922e2185d 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -361,4 +361,25 @@ func Test_write_file_encoding()
%bw!
endfunc
+" Check that buffer is written before triggering QuitPre
+func Test_wq_quitpre_autocommand()
+ edit Xsomefile
+ call setline(1, 'hello')
+ split
+ let g:seq = []
+ augroup Testing
+ au QuitPre * call add(g:seq, 'QuitPre - ' .. (&modified ? 'modified' : 'not modified'))
+ au BufWritePost * call add(g:seq, 'written')
+ augroup END
+ wq
+ call assert_equal(['written', 'QuitPre - not modified'], g:seq)
+
+ augroup Testing
+ au!
+ augroup END
+ bwipe!
+ unlet g:seq
+ call delete('Xsomefile')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
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 d4d00c0a71..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()) {