aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.valgrind.supp2
-rw-r--r--src/nvim/api/buffer.c32
-rw-r--r--src/nvim/api/private/helpers.c23
-rw-r--r--src/nvim/api/tabpage.c8
-rw-r--r--src/nvim/api/vim.c15
-rw-r--r--src/nvim/api/window.c8
-rw-r--r--src/nvim/buffer.c31
-rw-r--r--src/nvim/buffer.h27
-rw-r--r--src/nvim/buffer_defs.h28
-rw-r--r--src/nvim/edit.c7
-rw-r--r--src/nvim/eval.c2284
-rw-r--r--src/nvim/eval.h38
-rw-r--r--src/nvim/eval.lua6
-rw-r--r--src/nvim/eval/typval_encode.c.h2
-rw-r--r--src/nvim/eval_defs.h132
-rw-r--r--src/nvim/ex_cmds.c13
-rw-r--r--src/nvim/getchar.c14
-rw-r--r--src/nvim/globals.h10
-rw-r--r--src/nvim/main.c11
-rw-r--r--src/nvim/memline.c4
-rw-r--r--src/nvim/memory.c26
-rw-r--r--src/nvim/misc1.c5
-rw-r--r--src/nvim/msgpack_rpc/channel.c33
-rw-r--r--src/nvim/normal.c38
-rw-r--r--src/nvim/option.c53
-rw-r--r--src/nvim/option_defs.h6
-rw-r--r--src/nvim/options.lua8
-rw-r--r--src/nvim/os/fileio.c8
-rw-r--r--src/nvim/os/fileio.h2
-rw-r--r--src/nvim/quickfix.c1226
-rw-r--r--src/nvim/regexp.c156
-rw-r--r--src/nvim/screen.c3
-rw-r--r--src/nvim/search.c14
-rw-r--r--src/nvim/tag.c8
-rw-r--r--src/nvim/terminal.c219
-rw-r--r--src/nvim/testdir/Makefile2
-rw-r--r--src/nvim/testdir/runtest.vim6
-rw-r--r--src/nvim/testdir/test_alot.vim3
-rw-r--r--src/nvim/testdir/test_cmdline.vim5
-rw-r--r--src/nvim/testdir/test_expr.vim65
-rw-r--r--src/nvim/testdir/test_filter_map.vim81
-rw-r--r--src/nvim/testdir/test_lambda.vim287
-rw-r--r--src/nvim/testdir/test_partial.vim21
-rw-r--r--src/nvim/testdir/test_popup.vim20
-rw-r--r--src/nvim/testdir/test_quickfix.vim63
-rw-r--r--src/nvim/testdir/test_tagcase.vim73
-rw-r--r--src/nvim/undo.c10
-rw-r--r--src/nvim/version.c52
-rw-r--r--src/nvim/window.c3
49 files changed, 3594 insertions, 1597 deletions
diff --git a/src/.valgrind.supp b/src/.valgrind.supp
index 8b630fcaaf..cce22bd632 100644
--- a/src/.valgrind.supp
+++ b/src/.valgrind.supp
@@ -10,7 +10,7 @@
Memcheck:Leak
fun:malloc
fun:uv_spawn
- fun:pipe_process_spawn
+ fun:libuv_process_spawn
fun:process_spawn
fun:job_start
}
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 738a5ae091..7f6e1093ed 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -411,10 +411,10 @@ end:
/// Gets a buffer-scoped (b:) variable.
///
-/// @param buffer Buffer handle
-/// @param name Variable name
-/// @param[out] err Error details, if any
-/// @return Variable value
+/// @param buffer The buffer handle
+/// @param name The variable name
+/// @param[out] err Details of an error that may have occurred
+/// @return The variable value
Object nvim_buf_get_var(Buffer buffer, String name, Error *err)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -426,6 +426,22 @@ Object nvim_buf_get_var(Buffer buffer, String name, Error *err)
return dict_get_value(buf->b_vars, name, err);
}
+/// Gets a changed tick of a buffer
+///
+/// @param[in] buffer The buffer handle.
+///
+/// @return `b:changedtick` value.
+Integer nvim_buf_get_changedtick(Buffer buffer, Error *err)
+{
+ const buf_T *const buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return -1;
+ }
+
+ return buf->b_changedtick;
+}
+
/// Sets a buffer-scoped (b:) variable
///
/// @param buffer Buffer handle
@@ -440,7 +456,7 @@ void nvim_buf_set_var(Buffer buffer, String name, Object value, Error *err)
return;
}
- dict_set_value(buf->b_vars, name, value, false, false, err);
+ dict_set_var(buf->b_vars, name, value, false, false, err);
}
/// Removes a buffer-scoped (b:) variable
@@ -456,7 +472,7 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err)
return;
}
- dict_set_value(buf->b_vars, name, NIL, true, false, err);
+ dict_set_var(buf->b_vars, name, NIL, true, false, err);
}
/// Sets a buffer-scoped (b:) variable
@@ -479,7 +495,7 @@ Object buffer_set_var(Buffer buffer, String name, Object value, Error *err)
return (Object) OBJECT_INIT;
}
- return dict_set_value(buf->b_vars, name, value, false, true, err);
+ return dict_set_var(buf->b_vars, name, value, false, true, err);
}
/// Removes a buffer-scoped (b:) variable
@@ -498,7 +514,7 @@ Object buffer_del_var(Buffer buffer, String name, Error *err)
return (Object) OBJECT_INIT;
}
- return dict_set_value(buf->b_vars, name, NIL, true, true, err);
+ return dict_set_var(buf->b_vars, name, NIL, true, true, err);
}
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index ba4d005e9a..7efa086af2 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -98,7 +98,7 @@ Object dict_get_value(dict_T *dict, String key, Error *err)
return vim_to_object(&di->di_tv);
}
-/// Set a value in a dict. Objects are recursively expanded into their
+/// Set a value in a scope dict. Objects are recursively expanded into their
/// vimscript equivalents.
///
/// @param dict The vimscript dict
@@ -109,8 +109,8 @@ Object dict_get_value(dict_T *dict, String key, Error *err)
/// @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_value(dict_T *dict, String key, Object value, bool del,
- bool retval, Error *err)
+Object dict_set_var(dict_T *dict, String key, Object value, bool del,
+ bool retval, Error *err)
{
Object rv = OBJECT_INIT;
@@ -120,7 +120,7 @@ Object dict_set_value(dict_T *dict, String key, Object value, bool del,
}
if (key.size == 0) {
- api_set_error(err, Validation, _("Empty dictionary keys aren't allowed"));
+ api_set_error(err, Validation, _("Empty variable names aren't allowed"));
return rv;
}
@@ -129,7 +129,20 @@ Object dict_set_value(dict_T *dict, String key, Object value, bool del,
return rv;
}
- dictitem_T *di = dict_find(dict, (uint8_t *)key.data, (int)key.size);
+ dictitem_T *di = dict_find(dict, (char_u *)key.data, (int)key.size);
+
+ if (di != NULL) {
+ if (di->di_flags & DI_FLAGS_RO) {
+ api_set_error(err, Exception, _("Key is read-only: %s"), key.data);
+ return rv;
+ } else if (di->di_flags & DI_FLAGS_FIX) {
+ api_set_error(err, Exception, _("Key is fixed: %s"), key.data);
+ return rv;
+ } else if (di->di_flags & DI_FLAGS_LOCK) {
+ api_set_error(err, Exception, _("Key is locked: %s"), key.data);
+ return rv;
+ }
+ }
if (del) {
// Delete the key
diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c
index 9e61ec1871..d24250d032 100644
--- a/src/nvim/api/tabpage.c
+++ b/src/nvim/api/tabpage.c
@@ -71,7 +71,7 @@ void nvim_tabpage_set_var(Tabpage tabpage,
return;
}
- dict_set_value(tab->tp_vars, name, value, false, false, err);
+ dict_set_var(tab->tp_vars, name, value, false, false, err);
}
/// Removes a tab-scoped (t:) variable
@@ -87,7 +87,7 @@ void nvim_tabpage_del_var(Tabpage tabpage, String name, Error *err)
return;
}
- dict_set_value(tab->tp_vars, name, NIL, true, false, err);
+ dict_set_var(tab->tp_vars, name, NIL, true, false, err);
}
/// Sets a tab-scoped (t:) variable
@@ -110,7 +110,7 @@ Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err)
return (Object) OBJECT_INIT;
}
- return dict_set_value(tab->tp_vars, name, value, false, true, err);
+ return dict_set_var(tab->tp_vars, name, value, false, true, err);
}
/// Removes a tab-scoped (t:) variable
@@ -129,7 +129,7 @@ Object tabpage_del_var(Tabpage tabpage, String name, Error *err)
return (Object) OBJECT_INIT;
}
- return dict_set_value(tab->tp_vars, name, NIL, true, true, err);
+ return dict_set_var(tab->tp_vars, name, NIL, true, true, err);
}
/// Gets the current window in a tabpage
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 1732ee0bae..bf11795d9e 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -221,11 +221,10 @@ Object nvim_call_function(String fname, Array args, Error *err)
// Call the function
typval_T rettv;
int dummy;
- int r = call_func((char_u *) fname.data, (int) fname.size,
- &rettv, (int) args.size, vim_args,
+ int r = call_func((char_u *)fname.data, (int)fname.size,
+ &rettv, (int)args.size, vim_args, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy,
- true,
- NULL, NULL);
+ true, NULL, NULL);
if (r == FAIL) {
api_set_error(err, Exception, _("Error calling function."));
}
@@ -369,7 +368,7 @@ Object nvim_get_var(String name, Error *err)
/// @param[out] err Error details, if any
void nvim_set_var(String name, Object value, Error *err)
{
- dict_set_value(&globvardict, name, value, false, false, err);
+ dict_set_var(&globvardict, name, value, false, false, err);
}
/// Removes a global (g:) variable
@@ -378,7 +377,7 @@ void nvim_set_var(String name, Object value, Error *err)
/// @param[out] err Error details, if any
void nvim_del_var(String name, Error *err)
{
- dict_set_value(&globvardict, name, NIL, true, false, err);
+ dict_set_var(&globvardict, name, NIL, true, false, err);
}
/// Sets a global variable
@@ -394,7 +393,7 @@ void nvim_del_var(String name, Error *err)
/// or if previous value was `v:null`.
Object vim_set_var(String name, Object value, Error *err)
{
- return dict_set_value(&globvardict, name, value, false, true, err);
+ return dict_set_var(&globvardict, name, value, false, true, err);
}
/// Removes a global variable
@@ -406,7 +405,7 @@ Object vim_set_var(String name, Object value, Error *err)
/// @return Old value
Object vim_del_var(String name, Error *err)
{
- return dict_set_value(&globvardict, name, NIL, true, true, err);
+ return dict_set_var(&globvardict, name, NIL, true, true, err);
}
/// Gets a v: variable
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 1f555a6a05..13294e6bf2 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -210,7 +210,7 @@ void nvim_win_set_var(Window window, String name, Object value, Error *err)
return;
}
- dict_set_value(win->w_vars, name, value, false, false, err);
+ dict_set_var(win->w_vars, name, value, false, false, err);
}
/// Removes a window-scoped (w:) variable
@@ -226,7 +226,7 @@ void nvim_win_del_var(Window window, String name, Error *err)
return;
}
- dict_set_value(win->w_vars, name, NIL, true, false, err);
+ dict_set_var(win->w_vars, name, NIL, true, false, err);
}
/// Sets a window-scoped (w:) variable
@@ -249,7 +249,7 @@ Object window_set_var(Window window, String name, Object value, Error *err)
return (Object) OBJECT_INIT;
}
- return dict_set_value(win->w_vars, name, value, false, true, err);
+ return dict_set_var(win->w_vars, name, value, false, true, err);
}
/// Removes a window-scoped (w:) variable
@@ -268,7 +268,7 @@ Object window_del_var(Window window, String name, Error *err)
return (Object) OBJECT_INIT;
}
- return dict_set_value(win->w_vars, name, NIL, true, true, err);
+ return dict_set_var(win->w_vars, name, NIL, true, true, err);
}
/// Gets a window option value
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index a471ebf06f..083ed43047 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -686,8 +686,18 @@ free_buffer_stuff (
free_buf_options(buf, true);
ga_clear(&buf->b_s.b_langp);
}
+ {
+ // Avoid loosing b:changedtick when deleting buffer: clearing variables
+ // implies using clear_tv() on b:changedtick and that sets changedtick to
+ // zero.
+ hashitem_T *const changedtick_hi = hash_find(
+ &buf->b_vars->dv_hashtab, (const char_u *)"changedtick");
+ assert(changedtick_hi != NULL);
+ hash_remove(&buf->b_vars->dv_hashtab, changedtick_hi);
+ }
vars_clear(&buf->b_vars->dv_hashtab); // free all internal variables
hash_init(&buf->b_vars->dv_hashtab);
+ buf_init_changedtick(buf);
uc_clear(&buf->b_ucmds); // clear local user commands
buf_delete_signs(buf); // delete any signs
bufhl_clear_all(buf); // delete any highligts
@@ -1436,6 +1446,26 @@ void do_autochdir(void)
static int top_file_num = 1; ///< highest file number
+/// Initialize b:changedtick and changedtick_val attribute
+///
+/// @param[out] buf Buffer to intialize for.
+static inline void buf_init_changedtick(buf_T *const buf)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
+{
+ STATIC_ASSERT(sizeof("changedtick") <= sizeof(buf->changedtick_di.di_key),
+ "buf->changedtick_di cannot hold large enough keys");
+ buf->changedtick_di = (dictitem16_T) {
+ .di_flags = DI_FLAGS_RO|DI_FLAGS_FIX, // Must not include DI_FLAGS_ALLOC.
+ .di_tv = (typval_T) {
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_FIXED,
+ .vval.v_number = buf->b_changedtick,
+ },
+ .di_key = "changedtick",
+ };
+ dict_add(buf->b_vars, (dictitem_T *)&buf->changedtick_di);
+}
+
/// Add a file name to the buffer list.
/// If the same file name already exists return a pointer to that buffer.
/// If it does not exist, or if fname == NULL, a new entry is created.
@@ -1530,6 +1560,7 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags)
// init b: variables
buf->b_vars = dict_alloc();
init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE);
+ buf_init_changedtick(buf);
}
if (ffname != NULL) {
diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h
index 193c9f73d8..ed3e6ab6cc 100644
--- a/src/nvim/buffer.h
+++ b/src/nvim/buffer.h
@@ -5,6 +5,8 @@
#include "nvim/pos.h" // for linenr_T
#include "nvim/ex_cmds_defs.h" // for exarg_T
#include "nvim/screen.h" // for StlClickRecord
+#include "nvim/func_attr.h"
+#include "nvim/eval.h"
// Values for buflist_getfile()
enum getf_values {
@@ -79,6 +81,31 @@ static inline void restore_win_for_buf(win_T *save_curwin,
}
}
+static inline void buf_set_changedtick(buf_T *const buf, const int changedtick)
+ REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE;
+
+/// Set b_changedtick and corresponding variable
+///
+/// @param[out] buf Buffer to set changedtick in.
+/// @param[in] changedtick New value.
+static inline void buf_set_changedtick(buf_T *const buf, const int changedtick)
+{
+#ifndef NDEBUG
+ dictitem_T *const changedtick_di = dict_find(
+ buf->b_vars, (char_u *)"changedtick", sizeof("changedtick") - 1);
+ assert(changedtick_di != NULL);
+ assert(changedtick_di->di_tv.v_type == VAR_NUMBER);
+ assert(changedtick_di->di_tv.v_lock == VAR_FIXED);
+ // For some reason formatc does not like the below.
+# ifndef UNIT_TESTING_LUA_PREPROCESSING
+ assert(changedtick_di->di_flags == (DI_FLAGS_RO|DI_FLAGS_FIX));
+# endif
+ assert(changedtick_di == (dictitem_T *)&buf->changedtick_di);
+ assert(&buf->b_changedtick == &buf->changedtick_di.di_tv.vval.v_number);
+#endif
+ buf->b_changedtick = changedtick;
+}
+
#define WITH_BUFFER(b, code) \
do { \
win_T *save_curwin = NULL; \
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 73d06de964..a1b5633c32 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -31,14 +31,12 @@ typedef struct {
#include "nvim/hashtab.h"
// for dict_T
#include "nvim/eval_defs.h"
-// for proftime_T
-#include "nvim/profile.h"
// for String
#include "nvim/api/private/defs.h"
// for Map(K, V)
#include "nvim/map.h"
-#define MODIFIABLE(buf) (!buf->terminal && buf->b_p_ma)
+#define MODIFIABLE(buf) (buf->b_p_ma)
/*
* Flags for w_valid.
@@ -90,36 +88,25 @@ typedef struct {
typedef struct window_S win_T;
typedef struct wininfo_S wininfo_T;
typedef struct frame_S frame_T;
-typedef int scid_T; /* script ID */
// for struct memline (it needs memfile_T)
#include "nvim/memline_defs.h"
-
// for struct memfile, bhdr_T, blocknr_T... (it needs buf_T)
#include "nvim/memfile_defs.h"
-/*
- * This is here because regexp_defs.h needs win_T and buf_T. regprog_T is
- * used below.
- */
+// for regprog_T. Needs win_T and buf_T.
#include "nvim/regexp_defs.h"
-
-// for synstate_T (needs reg_extmatch_T, win_T and buf_T)
+// for synstate_T (needs reg_extmatch_T, win_T, buf_T)
#include "nvim/syntax_defs.h"
-
// for signlist_T
#include "nvim/sign_defs.h"
-
// for bufhl_*_T
#include "nvim/bufhl_defs.h"
typedef Map(linenr_T, bufhl_vec_T) bufhl_info_T;
-// for FileID
-#include "nvim/os/fs_defs.h"
-
-// for Terminal
-#include "nvim/terminal.h"
+#include "nvim/os/fs_defs.h" // for FileID
+#include "nvim/terminal.h" // for Terminal
/*
* The taggy struct is used to store the information about a :tag command.
@@ -502,7 +489,9 @@ struct file_buffer {
int b_changed; // 'modified': Set to true if something in the
// file has been changed and not written out.
- int b_changedtick; // incremented for each change, also for undo
+/// Change identifier incremented for each change, including undo
+#define b_changedtick changedtick_di.di_tv.vval.v_number
+ dictitem16_T changedtick_di; // b:changedtick dictionary item.
bool b_saving; /* Set to true if we are in the middle of
saving the buffer. */
@@ -664,6 +653,7 @@ struct file_buffer {
char_u *b_p_qe; ///< 'quoteescape'
int b_p_ro; ///< 'readonly'
long b_p_sw; ///< 'shiftwidth'
+ long b_p_scbk; ///< 'scrollback'
int b_p_si; ///< 'smartindent'
long b_p_sts; ///< 'softtabstop'
long b_p_sts_nopaste; ///< b_p_sts saved for paste mode
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 9a8ff21263..ecc794fb14 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -801,8 +801,8 @@ static int insert_handle_key(InsertState *s)
goto normalchar; // insert CTRL-Z as normal char
}
do_cmdline_cmd("stop");
- s->c = Ctrl_O;
- // FALLTHROUGH
+ ui_cursor_shape(); // may need to update cursor shape
+ break;
case Ctrl_O: // execute one command
if (ctrl_x_mode == CTRL_X_OMNI) {
@@ -3001,6 +3001,9 @@ static void ins_compl_addleader(int c)
{
int cc;
+ if (stop_arrow() == FAIL) {
+ return;
+ }
if (has_mbyte && (cc = (*mb_char2len)(c)) > 1) {
char_u buf[MB_MAXBYTES + 1];
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 18b5de7e62..672c9158ce 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -30,6 +30,7 @@
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
+#include "nvim/os/fileio.h"
#include "nvim/func_attr.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
@@ -170,6 +171,8 @@ static char *e_letwrong = N_("E734: Wrong variable type for %s=");
static char *e_nofunc = N_("E130: Unknown function: %s");
static char *e_illvar = N_("E461: Illegal variable name: %s");
static char *e_float_as_string = N_("E806: using Float as a String");
+static const char *e_readonlyvar = N_(
+ "E46: Cannot change read-only variable \"%.*s\"");
static char_u * const empty_string = (char_u *)"";
static char_u * const namespace_char = (char_u *)"abglstvw";
@@ -200,22 +203,33 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL};
static int echo_attr = 0; /* attributes used for ":echo" */
-/* Values for trans_function_name() argument: */
-#define TFN_INT 1 /* internal function name OK */
-#define TFN_QUIET 2 /* no error messages */
-#define TFN_NO_AUTOLOAD 4 /* do not use script autoloading */
-
-/* Values for get_lval() flags argument: */
-#define GLV_QUIET TFN_QUIET /* no error messages */
-#define GLV_NO_AUTOLOAD TFN_NO_AUTOLOAD /* do not use script autoloading */
-
-/* function flags */
-#define FC_ABORT 1 /* abort function on error */
-#define FC_RANGE 2 /* function accepts range */
-#define FC_DICT 4 /* Dict function, uses "self" */
-
-/* The names of packages that once were loaded are remembered. */
-static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL};
+/// trans_function_name() flags
+typedef enum {
+ TFN_INT = 1, ///< May use internal function name
+ TFN_QUIET = 2, ///< Do not emit error messages.
+ TFN_NO_AUTOLOAD = 4, ///< Do not use script autoloading.
+ TFN_NO_DEREF = 8, ///< Do not dereference a Funcref.
+ TFN_READ_ONLY = 16, ///< Will not change the variable.
+} TransFunctionNameFlags;
+
+/// get_lval() flags
+typedef enum {
+ GLV_QUIET = TFN_QUIET, ///< Do not emit error messages.
+ GLV_NO_AUTOLOAD = TFN_NO_AUTOLOAD, ///< Do not use script autoloading.
+ GLV_READ_ONLY = TFN_READ_ONLY, ///< Indicates that caller will not change
+ ///< the value (prevents error message).
+} GetLvalFlags;
+
+// function flags
+#define FC_ABORT 0x01 // abort function on error
+#define FC_RANGE 0x02 // function accepts range
+#define FC_DICT 0x04 // Dict function, uses "self"
+#define FC_CLOSURE 0x08 // closure, uses outer scope variables
+#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
+#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
+
+// The names of packages that once were loaded are remembered.
+static garray_T ga_loaded = { 0, 0, sizeof(char_u *), 4, NULL };
// List heads for garbage collection. Although there can be a reference loop
// from partial to dict to partial, we don't need to keep track of the partial,
@@ -223,38 +237,11 @@ static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL};
static dict_T *first_dict = NULL; // list of all dicts
static list_T *first_list = NULL; // list of all lists
+#define FLEN_FIXED 40
+
#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]
#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j]
-#define VAR_SHORT_LEN 20 /* short variable name length */
-#define FIXVAR_CNT 12 /* number of fixed variables */
-
-/* structure to hold info for a function that is currently being executed. */
-typedef struct funccall_S funccall_T;
-
-struct funccall_S {
- ufunc_T *func; /* function being called */
- int linenr; /* next line to be executed */
- int returned; /* ":return" used */
- struct /* fixed variables for arguments */
- {
- dictitem_T var; /* variable (without room for name) */
- char_u room[VAR_SHORT_LEN]; /* room for the name */
- } fixvar[FIXVAR_CNT];
- dict_T l_vars; /* l: local function variables */
- dictitem_T l_vars_var; /* variable for l: scope */
- dict_T l_avars; /* a: argument variables */
- dictitem_T l_avars_var; /* variable for a: scope */
- list_T l_varlist; /* list for a:000 */
- listitem_T l_listitems[MAX_FUNC_ARGS]; /* listitems for a:000 */
- typval_T *rettv; /* return value */
- linenr_T breakpoint; /* next line with breakpoint or zero */
- int dbg_tick; /* debug_tick when breakpoint was set */
- int level; /* top nesting level of executed function */
- proftime_T prof_child; /* time spent in a child */
- funccall_T *caller; /* calling function or NULL */
-};
-
/*
* Info used by a ":for" loop.
*/
@@ -266,15 +253,6 @@ typedef struct {
} forinfo_T;
/*
- * Struct used by trans_function_name()
- */
-typedef struct {
- dict_T *fd_dict; /* Dictionary used */
- char_u *fd_newkey; /* new key in "dict" in allocated memory */
- dictitem_T *fd_di; /* Dictionary item used */
-} funcdict_T;
-
-/*
* enum used by var_flavour()
*/
typedef enum {
@@ -389,6 +367,7 @@ static struct vimvar {
VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO),
VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO),
VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO),
+ VV(VV_TESTING, "testing", VAR_NUMBER, 0),
VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO),
VV(VV_TYPE_STRING, "t_string", VAR_NUMBER, VV_RO),
VV(VV_TYPE_FUNC, "t_func", VAR_NUMBER, VV_RO),
@@ -571,8 +550,8 @@ void eval_init(void)
list_T *const type_list = list_alloc();
type_list->lv_lock = VAR_FIXED;
type_list->lv_refcount = 1;
- dictitem_T *const di = dictitem_alloc((char_u *) msgpack_type_names[i]);
- di->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+ dictitem_T *const di = dictitem_alloc((char_u *)msgpack_type_names[i]);
+ di->di_flags |= DI_FLAGS_RO|DI_FLAGS_FIX;
di->di_tv = (typval_T) {
.v_type = VAR_LIST,
.vval = { .v_list = type_list, },
@@ -648,12 +627,11 @@ void eval_clear(void)
xfree(SCRIPT_SV(i));
ga_clear(&ga_scripts);
- /* unreferenced lists and dicts */
- (void)garbage_collect();
+ // unreferenced lists and dicts
+ (void)garbage_collect(false);
- /* functions */
+ // functions
free_all_functions();
- hash_clear(&func_hashtab);
}
#endif
@@ -1229,8 +1207,8 @@ int call_vim_function(
++sandbox;
}
- rettv->v_type = VAR_UNKNOWN; /* clear_tv() uses this */
- ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars,
+ rettv->v_type = VAR_UNKNOWN; // clear_tv() uses this
+ ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&doesrange, true, NULL, NULL);
if (safe) {
@@ -1677,13 +1655,7 @@ static void list_glob_vars(int *first)
*/
static void list_buf_vars(int *first)
{
- char numbuf[NUMBUFLEN];
-
list_hashtable_vars(&curbuf->b_vars->dv_hashtab, "b:", true, first);
-
- snprintf(numbuf, sizeof(numbuf), "%d", curbuf->b_changedtick);
- list_one_var_a("b:", "changedtick", sizeof("changedtick") - 1, VAR_NUMBER,
- numbuf, first);
}
/*
@@ -1980,46 +1952,33 @@ ex_let_one (
return arg_end;
}
-/*
- * If "arg" is equal to "b:changedtick" give an error and return TRUE.
- */
-static int check_changedtick(char_u *arg)
-{
- if (STRNCMP(arg, "b:changedtick", 13) == 0 && !eval_isnamec(arg[13])) {
- EMSG2(_(e_readonlyvar), arg);
- return TRUE;
- }
- return FALSE;
-}
-
-/*
- * Get an lval: variable, Dict item or List item that can be assigned a value
- * to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]",
- * "name.key", "name.key[expr]" etc.
- * Indexing only works if "name" is an existing List or Dictionary.
- * "name" points to the start of the name.
- * If "rettv" is not NULL it points to the value to be assigned.
- * "unlet" is TRUE for ":unlet": slightly different behavior when something is
- * wrong; must end in space or cmd separator.
- *
- * flags:
- * GLV_QUIET: do not give error messages
- * GLV_NO_AUTOLOAD: do not use script autoloading
- *
- * Returns a pointer to just after the name, including indexes.
- * When an evaluation error occurs "lp->ll_name" is NULL;
- * Returns NULL for a parsing error. Still need to free items in "lp"!
- */
-static char_u *
-get_lval (
- char_u *name,
- typval_T *rettv,
- lval_T *lp,
- int unlet,
- int skip,
- int flags, /* GLV_ values */
- int fne_flags /* flags for find_name_end() */
-)
+/// Get an lvalue
+///
+/// Lvalue may be
+/// - variable: "name", "na{me}"
+/// - dictionary item: "dict.key", "dict['key']"
+/// - list item: "list[expr]"
+/// - list slice: "list[expr:expr]"
+///
+/// Indexing only works if trying to use it with an existing List or Dictionary.
+///
+/// @param[in] name Name to parse.
+/// @param rettv Pointer to the value to be assigned or NULL.
+/// @param[out] lp Lvalue definition. When evaluation errors occur `->ll_name`
+/// is NULL.
+/// @param[in] unlet True if using `:unlet`. This results in slightly
+/// different behaviour when something is wrong; must end in
+/// space or cmd separator.
+/// @param[in] skip True when skipping.
+/// @param[in] flags @see GetLvalFlags.
+/// @param[in] fne_flags Flags for find_name_end().
+///
+/// @return A pointer to just after the name, including indexes. Returns NULL
+/// for a parsing error, but it is still needed to free items in lp.
+static char_u *get_lval(char_u *const name, typval_T *const rettv,
+ lval_T *const lp, const bool unlet, const bool skip,
+ const int flags, const int fne_flags)
+ FUNC_ATTR_NONNULL_ARG(1, 3)
{
char_u *p;
char_u *expr_start, *expr_end;
@@ -2238,8 +2197,13 @@ get_lval (
if (len == -1)
clear_tv(&var1);
break;
- } else if (var_check_ro(lp->ll_di->di_flags, name, false)) {
- // existing variable, need to check if it can be changed
+ // existing variable, need to check if it can be changed
+ } else if (!(flags & GLV_READ_ONLY) && var_check_ro(lp->ll_di->di_flags,
+ (const char *)name,
+ (size_t)(p - name))) {
+ if (len == -1) {
+ clear_tv(&var1);
+ }
return NULL;
}
@@ -2330,32 +2294,33 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch
dictitem_T *di;
if (lp->ll_tv == NULL) {
- if (!check_changedtick(lp->ll_name)) {
- cc = *endp;
- *endp = NUL;
- if (op != NULL && *op != '=') {
- typval_T tv;
-
- // handle +=, -= and .=
- di = NULL;
- if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name),
- &tv, &di, true, false) == OK) {
- if ((di == NULL
- || (!var_check_ro(di->di_flags, lp->ll_name, false)
- && !tv_check_lock(di->di_tv.v_lock, lp->ll_name, false)))
- && tv_op(&tv, rettv, op) == OK) {
- set_var(lp->ll_name, &tv, false);
- }
- clear_tv(&tv);
+ cc = *endp;
+ *endp = NUL;
+ if (op != NULL && *op != '=') {
+ typval_T tv;
+
+ // handle +=, -= and .=
+ di = NULL;
+ if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name),
+ &tv, &di, true, false) == OK) {
+ if ((di == NULL
+ || (!var_check_ro(di->di_flags, (const char *)lp->ll_name,
+ STRLEN(lp->ll_name))
+ && !tv_check_lock(di->di_tv.v_lock, (const char *)lp->ll_name,
+ STRLEN(lp->ll_name))))
+ && tv_op(&tv, rettv, op) == OK) {
+ set_var(lp->ll_name, &tv, false);
}
- } else
- set_var(lp->ll_name, rettv, copy);
- *endp = cc;
+ clear_tv(&tv);
+ }
+ } else {
+ set_var(lp->ll_name, rettv, copy);
}
+ *endp = cc;
} else if (tv_check_lock(lp->ll_newkey == NULL
? lp->ll_tv->v_lock
: lp->ll_tv->vval.v_dict->dv_lock,
- lp->ll_name, false)) {
+ (const char *)lp->ll_name, STRLEN(lp->ll_name))) {
} else if (lp->ll_range) {
listitem_T *ll_li = lp->ll_li;
int ll_n1 = lp->ll_n1;
@@ -2363,7 +2328,8 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch
// Check whether any of the list items is locked
for (listitem_T *ri = rettv->vval.v_list->lv_first;
ri != NULL && ll_li != NULL; ) {
- if (tv_check_lock(ll_li->li_tv.v_lock, lp->ll_name, false)) {
+ if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name,
+ STRLEN(lp->ll_name))) {
return;
}
ri = ri->li_next;
@@ -2969,16 +2935,18 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit)
cc = *name_end;
*name_end = NUL;
- /* Normal name or expanded name. */
- if (check_changedtick(lp->ll_name))
- ret = FAIL;
- else if (do_unlet(lp->ll_name, forceit) == FAIL)
+ // Normal name or expanded name.
+ if (do_unlet(lp->ll_name, forceit) == FAIL) {
ret = FAIL;
+ }
*name_end = cc;
} else if ((lp->ll_list != NULL
- && tv_check_lock(lp->ll_list->lv_lock, lp->ll_name, false))
+ && tv_check_lock(lp->ll_list->lv_lock, (const char *)lp->ll_name,
+ STRLEN(lp->ll_name)))
|| (lp->ll_dict != NULL
- && tv_check_lock(lp->ll_dict->dv_lock, lp->ll_name, false))) {
+ && tv_check_lock(lp->ll_dict->dv_lock,
+ (const char *)lp->ll_name,
+ STRLEN(lp->ll_name)))) {
return FAIL;
} else if (lp->ll_range) {
listitem_T *li;
@@ -2987,7 +2955,8 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit)
while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) {
li = ll_li->li_next;
- if (tv_check_lock(ll_li->li_tv.v_lock, lp->ll_name, false)) {
+ if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name,
+ STRLEN(lp->ll_name))) {
return false;
}
ll_li = li;
@@ -3064,15 +3033,19 @@ int do_unlet(char_u *name, int forceit)
return FAIL;
}
hi = hash_find(ht, varname);
- if (!HASHITEM_EMPTY(hi)) {
+ if (HASHITEM_EMPTY(hi)) {
+ hi = find_hi_in_scoped_ht((const char *)name, &ht);
+ }
+ if (hi != NULL && !HASHITEM_EMPTY(hi)) {
di = HI2DI(hi);
- if (var_check_fixed(di->di_flags, name, false)
- || var_check_ro(di->di_flags, name, false)
- || tv_check_lock(d->dv_lock, name, false)) {
+ if (var_check_fixed(di->di_flags, (const char *)name, STRLEN(name))
+ || var_check_ro(di->di_flags, (const char *)name, STRLEN(name))
+ || tv_check_lock(d->dv_lock, (const char *)name, STRLEN(name))) {
return FAIL;
}
- if (d == NULL || tv_check_lock(d->dv_lock, name, false)) {
+ if (d == NULL
+ || tv_check_lock(d->dv_lock, (const char *)name, STRLEN(name))) {
return FAIL;
}
@@ -3106,33 +3079,33 @@ int do_unlet(char_u *name, int forceit)
static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock)
{
int ret = OK;
- int cc;
- dictitem_T *di;
- if (deep == 0) /* nothing to do */
+ if (deep == 0) { // Nothing to do.
return OK;
+ }
if (lp->ll_tv == NULL) {
- cc = *name_end;
- *name_end = NUL;
-
// Normal name or expanded name.
- if (check_changedtick(lp->ll_name)) {
+ const size_t name_len = (size_t)(name_end - lp->ll_name);
+ dictitem_T *const di = find_var(
+ (const char *)lp->ll_name, name_len, NULL,
+ true);
+ if (di == NULL) {
ret = FAIL;
+ } else if ((di->di_flags & DI_FLAGS_FIX)
+ && di->di_tv.v_type != VAR_DICT
+ && di->di_tv.v_type != VAR_LIST) {
+ // For historical reasons this error is not given for Lists and
+ // Dictionaries. E.g. b: dictionary may be locked/unlocked.
+ emsgf(_("E940: Cannot lock or unlock variable %s"), lp->ll_name);
} else {
- di = find_var((const char *)lp->ll_name, STRLEN(lp->ll_name), NULL, true);
- if (di == NULL) {
- ret = FAIL;
+ if (lock) {
+ di->di_flags |= DI_FLAGS_LOCK;
} else {
- if (lock) {
- di->di_flags |= DI_FLAGS_LOCK;
- } else {
- di->di_flags &= ~DI_FLAGS_LOCK;
- }
- item_lock(&di->di_tv, deep, lock);
+ di->di_flags &= ~DI_FLAGS_LOCK;
}
+ item_lock(&di->di_tv, deep, lock);
}
- *name_end = cc;
} else if (lp->ll_range) {
listitem_T *li = lp->ll_li;
@@ -3159,68 +3132,72 @@ static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock)
static void item_lock(typval_T *tv, int deep, int lock)
{
static int recurse = 0;
- list_T *l;
- listitem_T *li;
- dict_T *d;
- hashitem_T *hi;
- int todo;
if (recurse >= DICT_MAXNEST) {
EMSG(_("E743: variable nested too deep for (un)lock"));
return;
}
- if (deep == 0)
+ if (deep == 0) {
return;
- ++recurse;
+ }
+ recurse++;
- /* lock/unlock the item itself */
- if (lock)
- tv->v_lock |= VAR_LOCKED;
- else
- tv->v_lock &= ~VAR_LOCKED;
+ // lock/unlock the item itself
+#define CHANGE_LOCK(var, lock) \
+ do { \
+ var = ((VarLockStatus[]) { \
+ [VAR_UNLOCKED] = (lock ? VAR_LOCKED : VAR_UNLOCKED), \
+ [VAR_LOCKED] = (lock ? VAR_LOCKED : VAR_UNLOCKED), \
+ [VAR_FIXED] = VAR_FIXED, \
+ })[var]; \
+ } while (0)
+ CHANGE_LOCK(tv->v_lock, lock);
switch (tv->v_type) {
- case VAR_LIST:
- if ((l = tv->vval.v_list) != NULL) {
- if (lock)
- l->lv_lock |= VAR_LOCKED;
- else
- l->lv_lock &= ~VAR_LOCKED;
- if (deep < 0 || deep > 1)
- /* recursive: lock/unlock the items the List contains */
- for (li = l->lv_first; li != NULL; li = li->li_next)
- item_lock(&li->li_tv, deep - 1, lock);
+ case VAR_LIST: {
+ list_T *const l = tv->vval.v_list;
+ if (l != NULL) {
+ CHANGE_LOCK(l->lv_lock, lock);
+ if (deep < 0 || deep > 1) {
+ // Recursive: lock/unlock the items the List contains.
+ for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) {
+ item_lock(&li->li_tv, deep - 1, lock);
+ }
+ }
+ }
+ break;
}
- break;
- case VAR_DICT:
- if ((d = tv->vval.v_dict) != NULL) {
- if (lock)
- d->dv_lock |= VAR_LOCKED;
- else
- d->dv_lock &= ~VAR_LOCKED;
- if (deep < 0 || deep > 1) {
- /* recursive: lock/unlock the items the List contains */
- todo = (int)d->dv_hashtab.ht_used;
- for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) {
- if (!HASHITEM_EMPTY(hi)) {
- --todo;
- item_lock(&HI2DI(hi)->di_tv, deep - 1, lock);
+ case VAR_DICT: {
+ dict_T *const d = tv->vval.v_dict;
+ if (d != NULL) {
+ CHANGE_LOCK(d->dv_lock, lock);
+ if (deep < 0 || deep > 1) {
+ // Recursive: lock/unlock the items the List contains.
+ int todo = (int)d->dv_hashtab.ht_used;
+ for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ item_lock(&HI2DI(hi)->di_tv, deep - 1, lock);
+ }
}
}
}
+ break;
+ }
+ case VAR_NUMBER:
+ case VAR_FLOAT:
+ case VAR_STRING:
+ case VAR_FUNC:
+ case VAR_PARTIAL:
+ case VAR_SPECIAL: {
+ break;
+ }
+ case VAR_UNKNOWN: {
+ assert(false);
}
- break;
- case VAR_NUMBER:
- case VAR_FLOAT:
- case VAR_STRING:
- case VAR_FUNC:
- case VAR_PARTIAL:
- case VAR_SPECIAL:
- break;
- case VAR_UNKNOWN:
- assert(false);
}
- --recurse;
+#undef CHANGE_LOCK
+ recurse--;
}
/*
@@ -3330,10 +3307,6 @@ char_u *get_user_var_name(expand_T *xp, int idx)
++hi;
return cat_prefix_varname('b', hi->hi_key);
}
- if (bdone == ht->ht_used) {
- ++bdone;
- return (char_u *)"b:changedtick";
- }
/* w: variables */
ht = &curwin->w_vars->dv_hashtab;
@@ -4300,8 +4273,12 @@ static int eval7(
case '[': ret = get_list_tv(arg, rettv, evaluate);
break;
+ // Lambda: {arg, arg -> expr}
// Dictionary: {key: val, key: val}
- case '{': ret = get_dict_tv(arg, rettv, evaluate);
+ case '{': ret = get_lambda_tv(arg, rettv, evaluate);
+ if (ret == NOTDONE) {
+ ret = get_dict_tv(arg, rettv, evaluate);
+ }
break;
// Option value: &name
@@ -4354,6 +4331,11 @@ static int eval7(
} else {
if (**arg == '(') { // recursive!
partial_T *partial;
+
+ if (!evaluate) {
+ check_vars((const char *)s, len);
+ }
+
// If "s" is the name of a variable of type VAR_FUNC
// use its contents.
s = deref_func_name((const char *)s, &len, &partial, !evaluate);
@@ -4383,6 +4365,7 @@ static int eval7(
} else if (evaluate) {
ret = get_var_tv((const char *)s, len, rettv, NULL, true, false);
} else {
+ check_vars((const char *)s, len);
ret = OK;
}
}
@@ -4936,6 +4919,15 @@ static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate)
return OK;
}
+/// @return the function name of the partial.
+char_u *partial_name(partial_T *pt)
+{
+ if (pt->pt_name != NULL) {
+ return pt->pt_name;
+ }
+ return pt->pt_func->uf_name;
+}
+
static void partial_free(partial_T *pt)
{
for (int i = 0; i < pt->pt_argc; i++) {
@@ -4943,8 +4935,12 @@ static void partial_free(partial_T *pt)
}
xfree(pt->pt_argv);
dict_unref(pt->pt_dict);
- func_unref(pt->pt_name);
- xfree(pt->pt_name);
+ if (pt->pt_name != NULL) {
+ func_unref(pt->pt_name);
+ xfree(pt->pt_name);
+ } else {
+ func_ptr_unref(pt->pt_func);
+ }
xfree(pt);
}
@@ -5208,12 +5204,12 @@ static bool func_equal(
// empty and NULL function name considered the same
s1 = tv1->v_type == VAR_FUNC ? tv1->vval.v_string
- : tv1->vval.v_partial->pt_name;
+ : partial_name(tv1->vval.v_partial);
if (s1 != NULL && *s1 == NUL) {
s1 = NULL;
}
s2 = tv2->v_type == VAR_FUNC ? tv2->vval.v_string
- : tv2->vval.v_partial->pt_name;
+ : partial_name(tv2->vval.v_partial);
if (s2 != NULL && *s2 == NUL) {
s2 = NULL;
}
@@ -5282,7 +5278,8 @@ tv_equal (
return TRUE;
}
- // For VAR_FUNC and VAR_PARTIAL only compare the function name.
+ // For VAR_FUNC and VAR_PARTIAL compare the function name, bound dict and
+ // arguments.
if ((tv1->v_type == VAR_FUNC
|| (tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial != NULL))
&& (tv2->v_type == VAR_FUNC
@@ -5811,6 +5808,9 @@ int get_copyID(void)
return current_copyID;
}
+// Used by get_func_tv()
+static garray_T funcargs = GA_EMPTY_INIT_VALUE;
+
/*
* Garbage collection for lists and dictionaries.
*
@@ -5833,19 +5833,22 @@ int get_copyID(void)
/// Do garbage collection for lists and dicts.
///
+/// @param testing true if called from test_garbagecollect_now().
/// @returns true if some memory was freed.
-bool garbage_collect(void)
+bool garbage_collect(bool testing)
{
bool abort = false;
#define ABORTING(func) abort = abort || func
- // Only do this once.
- want_garbage_collect = false;
- may_garbage_collect = false;
- garbage_collect_at_exit = false;
+ if (!testing) {
+ // Only do this once.
+ want_garbage_collect = false;
+ may_garbage_collect = false;
+ garbage_collect_at_exit = false;
+ }
- // We advance by two because we add one for items referenced through
- // previous_funccal.
+ // We advance by two (COPYID_INC) because we add one for items referenced
+ // through previous_funccal.
const int copyID = get_copyID();
// 1. Go through all accessible variables and mark all lists and dicts
@@ -5855,6 +5858,7 @@ bool garbage_collect(void)
// referenced through previous_funccal. This must be first, because if
// the item is referenced elsewhere the funccal must not be freed.
for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
+ fc->fc_copyID = copyID + 1;
ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
}
@@ -5930,10 +5934,14 @@ bool garbage_collect(void)
// function-local variables
for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
+ fc->fc_copyID = copyID;
ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID, NULL);
ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID, NULL);
}
+ // named functions (matters for closures)
+ ABORTING(set_ref_in_functions(copyID));
+
// Jobs
{
TerminalJobData *data;
@@ -5952,6 +5960,12 @@ bool garbage_collect(void)
})
}
+ // function call arguments, if v:testing is set.
+ for (int i = 0; i < funcargs.ga_len; i++) {
+ ABORTING(set_ref_in_item)(((typval_T **)funcargs.ga_data)[i],
+ copyID, NULL, NULL);
+ }
+
// v: vars
ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL);
@@ -6006,7 +6020,7 @@ bool garbage_collect(void)
if (did_free_funccal) {
// When a funccal was freed some more items might be garbage
// collected, so run again.
- (void)garbage_collect();
+ (void)garbage_collect(testing);
}
} else if (p_verbose > 0) {
verb_msg((char_u *)_(
@@ -6235,6 +6249,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
// A partial does not have a copyID, because it cannot contain itself.
if (pt != NULL) {
+ abort = set_ref_in_func(pt->pt_name, pt->pt_func, copyID);
if (pt->pt_dict != NULL) {
typval_T dtv;
@@ -6251,6 +6266,8 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
break;
}
case VAR_FUNC:
+ abort = set_ref_in_func(tv->vval.v_string, NULL, copyID);
+ break;
case VAR_UNKNOWN:
case VAR_SPECIAL:
case VAR_FLOAT:
@@ -6262,6 +6279,29 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
return abort;
}
+/// Set "copyID" in all functions available by name.
+bool set_ref_in_functions(int copyID)
+{
+ int todo;
+ hashitem_T *hi = NULL;
+ bool abort = false;
+ ufunc_T *fp;
+
+ todo = (int)func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ fp = HI2UF(hi);
+ if (!func_name_refcount(fp->uf_name)) {
+ abort = abort || set_ref_in_func(NULL, fp, copyID);
+ }
+ }
+ }
+ return abort;
+}
+
+
+
/// Mark all lists and dicts referenced in given mark
///
/// @returns true if setting references failed somehow.
@@ -6308,9 +6348,20 @@ static inline bool set_ref_dict(dict_T *dict, int copyID)
return false;
}
-/*
- * Allocate an empty header for a dictionary.
- */
+static bool set_ref_in_funccal(funccall_T *fc, int copyID)
+{
+ bool abort = false;
+
+ if (fc->fc_copyID != copyID) {
+ fc->fc_copyID = copyID;
+ abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
+ abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
+ abort = abort || set_ref_in_func(NULL, fc->func, copyID);
+ }
+ return abort;
+}
+
+/// Allocate an empty header for a dictionary.
dict_T *dict_alloc(void) FUNC_ATTR_NONNULL_RET
{
dict_T *d = xmalloc(sizeof(dict_T));
@@ -6323,7 +6374,7 @@ dict_T *dict_alloc(void) FUNC_ATTR_NONNULL_RET
first_dict = d;
hash_init(&d->dv_hashtab);
- d->dv_lock = 0;
+ d->dv_lock = VAR_UNLOCKED;
d->dv_scope = 0;
d->dv_refcount = 0;
d->dv_copyID = 0;
@@ -6396,9 +6447,8 @@ static void dict_free_contents(dict_T *d) {
* something recursive causing trouble. */
di = HI2DI(hi);
hash_remove(&d->dv_hashtab, hi);
- clear_tv(&di->di_tv);
- xfree(di);
- --todo;
+ dictitem_free(di);
+ todo--;
}
}
@@ -6855,6 +6905,232 @@ failret:
return OK;
}
+/// Get function arguments.
+static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
+ int *varargs, bool skip)
+{
+ bool mustend = false;
+ char_u *arg = *argp;
+ char_u *p = arg;
+ int c;
+ int i;
+
+ if (newargs != NULL) {
+ ga_init(newargs, (int)sizeof(char_u *), 3);
+ }
+
+ if (varargs != NULL) {
+ *varargs = false;
+ }
+
+ // Isolate the arguments: "arg1, arg2, ...)"
+ while (*p != endchar) {
+ if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
+ if (varargs != NULL) {
+ *varargs = true;
+ }
+ p += 3;
+ mustend = true;
+ } else {
+ arg = p;
+ while (ASCII_ISALNUM(*p) || *p == '_') {
+ p++;
+ }
+ if (arg == p || isdigit(*arg)
+ || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
+ || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) {
+ if (!skip) {
+ EMSG2(_("E125: Illegal argument: %s"), arg);
+ }
+ break;
+ }
+ if (newargs != NULL) {
+ ga_grow(newargs, 1);
+ c = *p;
+ *p = NUL;
+ arg = vim_strsave(arg);
+ if (arg == NULL) {
+ *p = c;
+ goto err_ret;
+ }
+
+ // Check for duplicate argument name.
+ for (i = 0; i < newargs->ga_len; i++) {
+ if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) {
+ EMSG2(_("E853: Duplicate argument name: %s"), arg);
+ xfree(arg);
+ goto err_ret;
+ }
+ }
+ ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg;
+ newargs->ga_len++;
+
+ *p = c;
+ }
+ if (*p == ',') {
+ p++;
+ } else {
+ mustend = true;
+ }
+ }
+ p = skipwhite(p);
+ if (mustend && *p != endchar) {
+ if (!skip) {
+ EMSG2(_(e_invarg2), *argp);
+ }
+ break;
+ }
+ }
+ if (*p != endchar) {
+ goto err_ret;
+ }
+ p++; // skip "endchar"
+
+ *argp = p;
+ return OK;
+
+err_ret:
+ if (newargs != NULL) {
+ ga_clear_strings(newargs);
+ }
+ return FAIL;
+}
+
+/// Register function "fp" as using "current_funccal" as its scope.
+static void register_closure(ufunc_T *fp)
+{
+ if (fp->uf_scoped == current_funccal) {
+ // no change
+ return;
+ }
+ funccal_unref(fp->uf_scoped, fp, false);
+ fp->uf_scoped = current_funccal;
+ current_funccal->fc_refcount++;
+ ga_grow(&current_funccal->fc_funcs, 1);
+ ((ufunc_T **)current_funccal->fc_funcs.ga_data)
+ [current_funccal->fc_funcs.ga_len++] = fp;
+}
+
+/// Parse a lambda expression and get a Funcref from "*arg".
+///
+/// @return OK or FAIL. Returns NOTDONE for dict or {expr}.
+static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
+{
+ garray_T newargs = GA_EMPTY_INIT_VALUE;
+ garray_T *pnewargs;
+ ufunc_T *fp = NULL;
+ int varargs;
+ int ret;
+ char_u *start = skipwhite(*arg + 1);
+ char_u *s, *e;
+ static int lambda_no = 0;
+ int *old_eval_lavars = eval_lavars_used;
+ int eval_lavars = false;
+
+ // First, check if this is a lambda expression. "->" must exists.
+ ret = get_function_args(&start, '-', NULL, NULL, true);
+ if (ret == FAIL || *start != '>') {
+ return NOTDONE;
+ }
+
+ // Parse the arguments again.
+ if (evaluate) {
+ pnewargs = &newargs;
+ } else {
+ pnewargs = NULL;
+ }
+ *arg = skipwhite(*arg + 1);
+ ret = get_function_args(arg, '-', pnewargs, &varargs, false);
+ if (ret == FAIL || **arg != '>') {
+ goto errret;
+ }
+
+ // Set up a flag for checking local variables and arguments.
+ if (evaluate) {
+ eval_lavars_used = &eval_lavars;
+ }
+
+ // Get the start and the end of the expression.
+ *arg = skipwhite(*arg + 1);
+ s = *arg;
+ ret = skip_expr(arg);
+ if (ret == FAIL) {
+ goto errret;
+ }
+ e = *arg;
+ *arg = skipwhite(*arg);
+ if (**arg != '}') {
+ goto errret;
+ }
+ (*arg)++;
+
+ if (evaluate) {
+ int len, flags = 0;
+ char_u *p;
+ char_u name[20];
+ partial_T *pt;
+ garray_T newlines;
+
+ lambda_no++;
+ snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no);
+
+ fp = (ufunc_T *)xcalloc(1, sizeof(ufunc_T) + STRLEN(name));
+ pt = (partial_T *)xcalloc(1, sizeof(partial_T));
+ if (pt == NULL) {
+ xfree(fp);
+ goto errret;
+ }
+
+ ga_init(&newlines, (int)sizeof(char_u *), 1);
+ ga_grow(&newlines, 1);
+
+ // Add "return " before the expression.
+ len = 7 + e - s + 1;
+ p = (char_u *)xmalloc(len);
+ ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
+ STRCPY(p, "return ");
+ STRLCPY(p + 7, s, e - s + 1);
+
+ fp->uf_refcount = 1;
+ STRCPY(fp->uf_name, name);
+ hash_add(&func_hashtab, UF2HIKEY(fp));
+ fp->uf_args = newargs;
+ fp->uf_lines = newlines;
+ if (current_funccal != NULL && eval_lavars) {
+ flags |= FC_CLOSURE;
+ register_closure(fp);
+ } else {
+ fp->uf_scoped = NULL;
+ }
+
+ fp->uf_tml_count = NULL;
+ fp->uf_tml_total = NULL;
+ fp->uf_tml_self = NULL;
+ fp->uf_profiling = false;
+ if (prof_def_func()) {
+ func_do_profile(fp);
+ }
+ fp->uf_varargs = true;
+ fp->uf_flags = flags;
+ fp->uf_calls = 0;
+ fp->uf_script_ID = current_SID;
+
+ pt->pt_func = fp;
+ pt->pt_refcount = 1;
+ rettv->vval.v_partial = pt;
+ rettv->v_type = VAR_PARTIAL;
+ }
+
+ eval_lavars_used = old_eval_lavars;
+ return OK;
+
+errret:
+ ga_clear_strings(&newargs);
+ xfree(fp);
+ eval_lavars_used = old_eval_lavars;
+ return FAIL;
+}
+
/// Convert the string to a floating point number
///
/// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to
@@ -6977,9 +7253,6 @@ char_u *get_expr_name(expand_T *xp, int idx)
return get_user_var_name(xp, ++intidx);
}
-
-
-
/// Find internal function in hash functions
///
/// @param[in] name Name of the function.
@@ -7035,8 +7308,9 @@ static char_u *deref_func_name(const char *name, int *lenp,
if (partialp != NULL) {
*partialp = pt;
}
- *lenp = (int)STRLEN(pt->pt_name);
- return pt->pt_name;
+ char_u *s = partial_name(pt);
+ *lenp = (int)STRLEN(s);
+ return s;
}
return (char_u *)name;
@@ -7088,9 +7362,24 @@ get_func_tv (
ret = FAIL;
if (ret == OK) {
- ret = call_func(name, len, rettv, argcount, argvars,
+ int i = 0;
+
+ if (get_vim_var_nr(VV_TESTING)) {
+ // Prepare for calling garbagecollect_for_testing(), need to know
+ // what variables are used on the call stack.
+ if (funcargs.ga_itemsize == 0) {
+ ga_init(&funcargs, (int)sizeof(typval_T *), 50);
+ }
+ for (i = 0; i < argcount; i++) {
+ ga_grow(&funcargs, 1);
+ ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i];
+ }
+ }
+ ret = call_func(name, len, rettv, argcount, argvars, NULL,
firstline, lastline, doesrange, evaluate,
partial, selfdict);
+
+ funcargs.ga_len -= i;
} else if (!aborting()) {
if (argcount == MAX_FUNC_ARGS) {
emsg_funcname(N_("E740: Too many arguments for function %s"), name);
@@ -7115,6 +7404,7 @@ typedef enum {
ERROR_NONE,
ERROR_OTHER,
ERROR_BOTH,
+ ERROR_DELETED,
} FnameTransError;
#define FLEN_FIXED 40
@@ -7176,7 +7466,43 @@ static char_u *fname_trans_sid(const char_u *const name,
return fname;
}
+/// Mark all lists and dicts referenced through function "name" with "copyID".
+/// "list_stack" is used to add lists to be marked. Can be NULL.
+/// "ht_stack" is used to add hashtabs to be marked. Can be NULL.
+///
+/// @return true if setting references failed somehow.
+bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
+{
+ ufunc_T *fp = fp_in;
+ funccall_T *fc;
+ int error = ERROR_NONE;
+ char_u fname_buf[FLEN_FIXED + 1];
+ char_u *tofree = NULL;
+ char_u *fname;
+ bool abort = false;
+ if (name == NULL && fp_in == NULL) {
+ return false;
+ }
+
+ if (fp_in == NULL) {
+ fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+ fp = find_func(fname);
+ }
+ if (fp != NULL) {
+ for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) {
+ abort = abort || set_ref_in_funccal(fc, copyID);
+ }
+ }
+ xfree(tofree);
+ return abort;
+}
+
/// Call a function with its resolved parameters
+///
+/// "argv_func", when not NULL, can be used to fill in arguments only when the
+/// invoked function uses them. It is called like this:
+/// new_argcount = argv_func(current_argcount, argv, called_func_argcount)
+///
/// Return FAIL when the function can't be called, OK otherwise.
/// Also returns OK when an error was encountered while executing the function.
int
@@ -7187,6 +7513,7 @@ call_func(
int argcount_in, // number of "argvars"
typval_T *argvars_in, // vars for arguments, must have "argcount"
// PLUS ONE elements!
+ ArgvFunc argv_func, // function to fill in argvars
linenr_T firstline, // first line of range
linenr_T lastline, // last line of range
int *doesrange, // return: function handled range
@@ -7255,9 +7582,13 @@ call_func(
if (!builtin_function((const char *)rfname, -1)) {
// User defined function.
- fp = find_func(rfname);
+ if (partial != NULL && partial->pt_func != NULL) {
+ fp = partial->pt_func;
+ } else {
+ fp = find_func(rfname);
+ }
- /* Trigger FuncUndefined event, may load the function. */
+ // Trigger FuncUndefined event, may load the function.
if (fp == NULL
&& apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL)
&& !aborting()) {
@@ -7271,19 +7602,25 @@ call_func(
fp = find_func(rfname);
}
- if (fp != NULL) {
- if (fp->uf_flags & FC_RANGE)
- *doesrange = TRUE;
- if (argcount < fp->uf_args.ga_len)
+ if (fp != NULL && (fp->uf_flags & FC_DELETED)) {
+ error = ERROR_DELETED;
+ } else if (fp != NULL) {
+ if (argv_func != NULL) {
+ argcount = argv_func(argcount, argvars, fp->uf_args.ga_len);
+ }
+ if (fp->uf_flags & FC_RANGE) {
+ *doesrange = true;
+ }
+ if (argcount < fp->uf_args.ga_len) {
error = ERROR_TOOFEW;
- else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len)
+ } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) {
error = ERROR_TOOMANY;
- else if ((fp->uf_flags & FC_DICT) && selfdict == NULL)
+ } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) {
error = ERROR_DICT;
- else {
+ } else {
// Call the user function.
call_user_func(fp, argcount, argvars, rettv, firstline, lastline,
- (fp->uf_flags & FC_DICT) ? selfdict : NULL);
+ (fp->uf_flags & FC_DICT) ? selfdict : NULL);
error = ERROR_NONE;
}
}
@@ -7326,6 +7663,9 @@ call_func(
case ERROR_UNKNOWN:
emsg_funcname(N_("E117: Unknown function: %s"), name);
break;
+ case ERROR_DELETED:
+ emsg_funcname(N_("E933: Function was deleted: %s"), name);
+ break;
case ERROR_TOOMANY:
emsg_funcname(e_toomanyarg, name);
break;
@@ -7480,9 +7820,10 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = 1; /* Default: Failed */
if (argvars[0].v_type == VAR_LIST) {
+ const char *const arg_errmsg = _("add() argument");
+ const size_t arg_errmsg_len = strlen(arg_errmsg);
if ((l = argvars[0].vval.v_list) != NULL
- && !tv_check_lock(l->lv_lock,
- (char_u *)N_("add() argument"), true)) {
+ && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) {
list_append_tv(l, &argvars[1]);
copy_tv(&argvars[0], rettv);
}
@@ -8131,7 +8472,7 @@ int func_call(char_u *name, typval_T *args, partial_T *partial,
}
if (item == NULL) {
- r = call_func(name, (int)STRLEN(name), rettv, argc, argv,
+ r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&dummy, true, partial, selfdict);
}
@@ -8161,7 +8502,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
func = argvars[0].vval.v_string;
} else if (argvars[0].v_type == VAR_PARTIAL) {
partial = argvars[0].vval.v_partial;
- func = partial->pt_name;
+ func = partial_name(partial);
} else {
func = get_tv_string(&argvars[0]);
}
@@ -8594,11 +8935,6 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
- if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) {
- EMSG2(e_invarg2, "funcref");
- return;
- }
-
char *key_pattern = (char *)get_tv_string_chk(argvars + 1);
assert(key_pattern);
const size_t key_len = STRLEN(argvars[1].vval.v_string);
@@ -8610,6 +8946,7 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
Callback callback;
if (!callback_from_typval(&callback, &argvars[2])) {
+ EMSG2(e_invarg2, "funcref");
return;
}
@@ -8952,7 +9289,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
n = false; // Trailing garbage.
}
} else if (*p == '*') { // Internal or user defined function.
- n = function_exists(p + 1);
+ n = function_exists(p + 1, false);
} else if (*p == ':') {
n = cmd_exists(p + 1);
} else if (*p == '#') {
@@ -9063,7 +9400,8 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action)
hashitem_T *hi2;
int todo;
bool watched = is_watched(d1);
- char_u *arg_errmsg = (char_u *)N_("extend() argument");
+ const char *const arg_errmsg = _("extend() argument");
+ const size_t arg_errmsg_len = strlen(arg_errmsg);
todo = (int)d2->dv_hashtab.ht_used;
for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2) {
@@ -9097,8 +9435,8 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action)
} else if (*action == 'f' && HI2DI(hi2) != di1) {
typval_T oldtv;
- if (tv_check_lock(di1->di_tv.v_lock, arg_errmsg, true)
- || var_check_ro(di1->di_flags, arg_errmsg, true)) {
+ if (tv_check_lock(di1->di_tv.v_lock, arg_errmsg, arg_errmsg_len)
+ || var_check_ro(di1->di_flags, arg_errmsg, arg_errmsg_len)) {
break;
}
@@ -9124,7 +9462,8 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action)
*/
static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- char_u *arg_errmsg = (char_u *)N_("extend() argument");
+ const char *const arg_errmsg = N_("extend() argument");
+ const size_t arg_errmsg_len = strlen(arg_errmsg);
if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
list_T *l1, *l2;
@@ -9134,7 +9473,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
l1 = argvars[0].vval.v_list;
l2 = argvars[1].vval.v_list;
- if (l1 != NULL && !tv_check_lock(l1->lv_lock, arg_errmsg, true)
+ if (l1 != NULL && !tv_check_lock(l1->lv_lock, arg_errmsg, arg_errmsg_len)
&& l2 != NULL) {
if (argvars[2].v_type != VAR_UNKNOWN) {
before = get_tv_number_chk(&argvars[2], &error);
@@ -9164,7 +9503,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
d1 = argvars[0].vval.v_dict;
d2 = argvars[1].vval.v_dict;
- if (d1 != NULL && !tv_check_lock(d1->dv_lock, arg_errmsg, true)
+ if (d1 != NULL && !tv_check_lock(d1->dv_lock, arg_errmsg, arg_errmsg_len)
&& d2 != NULL) {
/* Check the third argument. */
if (argvars[2].v_type != VAR_UNKNOWN) {
@@ -9296,8 +9635,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
*/
static void filter_map(typval_T *argvars, typval_T *rettv, int map)
{
- char_u buf[NUMBUFLEN];
- char_u *expr;
+ typval_T *expr;
listitem_T *li, *nli;
list_T *l = NULL;
dictitem_T *di;
@@ -9308,20 +9646,22 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
typval_T save_key;
int rem = false;
int todo;
- char_u *ermsg = (char_u *)(map ? "map()" : "filter()");
- char_u *arg_errmsg = (char_u *)(map ? N_("map() argument")
- : N_("filter() argument"));
+ char_u *ermsg = (char_u *)(map ? "map()" : "filter()");
+ const char *const arg_errmsg = (map
+ ? _("map() argument")
+ : _("filter() argument"));
+ const size_t arg_errmsg_len = strlen(arg_errmsg);
int save_did_emsg;
int idx = 0;
if (argvars[0].v_type == VAR_LIST) {
if ((l = argvars[0].vval.v_list) == NULL
- || (!map && tv_check_lock(l->lv_lock, arg_errmsg, true))) {
+ || (!map && tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len))) {
return;
}
} else if (argvars[0].v_type == VAR_DICT) {
if ((d = argvars[0].vval.v_dict) == NULL
- || (!map && tv_check_lock(d->dv_lock, arg_errmsg, true))) {
+ || (!map && tv_check_lock(d->dv_lock, arg_errmsg, arg_errmsg_len))) {
return;
}
} else {
@@ -9329,16 +9669,15 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
return;
}
- expr = get_tv_string_buf_chk(&argvars[1], buf);
- /* On type errors, the preceding call has already displayed an error
- * message. Avoid a misleading error message for an empty string that
- * was not passed as argument. */
- if (expr != NULL) {
+ expr = &argvars[1];
+ // On type errors, the preceding call has already displayed an error
+ // message. Avoid a misleading error message for an empty string that
+ // was not passed as argument.
+ if (expr->v_type != VAR_UNKNOWN) {
prepare_vimvar(VV_VAL, &save_val);
- expr = skipwhite(expr);
- /* We reset "did_emsg" to be able to detect whether an error
- * occurred during evaluation of the expression. */
+ // We reset "did_emsg" to be able to detect whether an error
+ // occurred during evaluation of the expression.
save_did_emsg = did_emsg;
did_emsg = FALSE;
@@ -9355,8 +9694,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
di = HI2DI(hi);
if (map
- && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, true)
- || var_check_ro(di->di_flags, arg_errmsg, true))) {
+ && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, arg_errmsg_len)
+ || var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len))) {
break;
}
@@ -9366,8 +9705,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
if (r == FAIL || did_emsg)
break;
if (!map && rem) {
- if (var_check_fixed(di->di_flags, arg_errmsg, true)
- || var_check_ro(di->di_flags, arg_errmsg, true)) {
+ if (var_check_fixed(di->di_flags, arg_errmsg, arg_errmsg_len)
+ || var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len)) {
break;
}
dictitem_remove(d, di);
@@ -9379,7 +9718,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
vimvars[VV_KEY].vv_type = VAR_NUMBER;
for (li = l->lv_first; li != NULL; li = nli) {
- if (map && tv_check_lock(li->li_tv.v_lock, arg_errmsg, true)) {
+ if (map
+ && tv_check_lock(li->li_tv.v_lock, arg_errmsg, arg_errmsg_len)) {
break;
}
nli = li->li_next;
@@ -9402,20 +9742,46 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
copy_tv(&argvars[0], rettv);
}
-static int filter_map_one(typval_T *tv, char_u *expr, int map, int *remp)
+static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp)
{
typval_T rettv;
+ typval_T argv[3];
+ char_u buf[NUMBUFLEN];
char_u *s;
int retval = FAIL;
+ int dummy;
copy_tv(tv, &vimvars[VV_VAL].vv_tv);
- s = expr;
- if (eval1(&s, &rettv, TRUE) == FAIL)
- goto theend;
- if (*s != NUL) { /* check for trailing chars after expr */
- EMSG2(_(e_invexpr2), s);
- clear_tv(&rettv);
- goto theend;
+ argv[0] = vimvars[VV_KEY].vv_tv;
+ argv[1] = vimvars[VV_VAL].vv_tv;
+ if (expr->v_type == VAR_FUNC) {
+ s = expr->vval.v_string;
+ if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL,
+ 0L, 0L, &dummy, true, NULL, NULL) == FAIL) {
+ goto theend;
+ }
+ } else if (expr->v_type == VAR_PARTIAL) {
+ partial_T *partial = expr->vval.v_partial;
+
+ s = partial_name(partial);
+ if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL,
+ 0L, 0L, &dummy, true, partial, NULL) == FAIL) {
+ goto theend;
+ }
+ } else {
+ s = get_tv_string_buf_chk(expr, buf);
+ if (s == NULL) {
+ goto theend;
+ }
+ s = skipwhite(s);
+ if (eval1(&s, &rettv, true) == FAIL) {
+ goto theend;
+ }
+
+ if (*s != NUL) { // check for trailing chars after expr
+ EMSG2(_(e_invexpr2), s);
+ goto theend;
+ }
}
if (map) {
/* map(): replace the list item value */
@@ -9674,15 +10040,14 @@ static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
}
-/*
- * "function()" function
- */
-static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void common_function(typval_T *argvars, typval_T *rettv,
+ bool is_funcref, FunPtr fptr)
{
char_u *s;
char_u *name;
bool use_string = false;
partial_T *arg_pt = NULL;
+ char_u *trans_name = NULL;
if (argvars[0].v_type == VAR_FUNC) {
// function(MyFunc, [arg], dict)
@@ -9691,18 +10056,29 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
&& argvars[0].vval.v_partial != NULL) {
// function(dict.MyFunc, [arg])
arg_pt = argvars[0].vval.v_partial;
- s = arg_pt->pt_name;
+ s = partial_name(arg_pt);
} else {
// function('MyFunc', [arg], dict)
s = get_tv_string(&argvars[0]);
use_string = true;
}
- if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))) {
- EMSG2(_(e_invarg2), s);
- } else if (use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL
- && !function_exists((const char *)s)) {
+ if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) {
+ name = s;
+ trans_name = trans_function_name(&name, false,
+ TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD
+ | TFN_NO_DEREF, NULL, NULL);
+ if (*name != NUL) {
+ s = NULL;
+ }
+ }
+ if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))
+ || (is_funcref && trans_name == NULL)) {
+ EMSG2(_(e_invarg2), use_string ? get_tv_string(&argvars[0]) : s);
// Don't check an autoload name for existence here.
+ } else if (trans_name != NULL
+ && (is_funcref ? find_func(trans_name) == NULL
+ : !translated_function_exists((const char *)trans_name))) {
EMSG2(_("E700: Unknown function: %s"), s);
} else {
int dict_idx = 0;
@@ -9743,7 +10119,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (argvars[dict_idx].v_type != VAR_DICT) {
EMSG(_("E922: expected a dict"));
xfree(name);
- return;
+ goto theend;
}
if (argvars[dict_idx].vval.v_dict == NULL) {
dict_idx = 0;
@@ -9754,7 +10130,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
EMSG(_("E923: Second argument of function() must be "
"a list or a dict"));
xfree(name);
- return;
+ goto theend;
}
list = argvars[arg_idx].vval.v_list;
if (list == NULL || list->lv_len == 0) {
@@ -9762,7 +10138,7 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
}
- if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL) {
+ if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref) {
partial_T *const pt = xcalloc(1, sizeof(*pt));
// result is a VAR_PARTIAL
@@ -9775,18 +10151,17 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (pt->pt_argv == NULL) {
xfree(pt);
xfree(name);
- return;
- } else {
- int i = 0;
- for (; i < arg_len; i++) {
- copy_tv(&arg_pt->pt_argv[i], &pt->pt_argv[i]);
- }
- if (lv_len > 0) {
- for (listitem_T *li = list->lv_first;
- li != NULL;
- li = li->li_next) {
- copy_tv(&li->li_tv, &pt->pt_argv[i++]);
- }
+ goto theend;
+ }
+ int i = 0;
+ for (; i < arg_len; i++) {
+ copy_tv(&arg_pt->pt_argv[i], &pt->pt_argv[i]);
+ }
+ if (lv_len > 0) {
+ for (listitem_T *li = list->lv_first;
+ li != NULL;
+ li = li->li_next) {
+ copy_tv(&li->li_tv, &pt->pt_argv[i++]);
}
}
}
@@ -9808,8 +10183,18 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
pt->pt_refcount = 1;
- pt->pt_name = name;
- func_ref(pt->pt_name);
+ if (arg_pt != NULL && arg_pt->pt_func != NULL) {
+ pt->pt_func = arg_pt->pt_func;
+ func_ptr_ref(pt->pt_func);
+ xfree(name);
+ } else if (is_funcref) {
+ pt->pt_func = find_func(trans_name);
+ func_ptr_ref(pt->pt_func);
+ xfree(name);
+ } else {
+ pt->pt_name = name;
+ func_ref(name);
+ }
rettv->v_type = VAR_PARTIAL;
rettv->vval.v_partial = pt;
@@ -9820,6 +10205,18 @@ static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
func_ref(name);
}
}
+theend:
+ xfree(trans_name);
+}
+
+static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ common_function(argvars, rettv, true, fptr);
+}
+
+static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ common_function(argvars, rettv, false, fptr);
}
/// "garbagecollect()" function
@@ -9873,11 +10270,18 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (pt != NULL) {
char_u *what = get_tv_string(&argvars[1]);
+ char_u *n;
if (STRCMP(what, "func") == 0 || STRCMP(what, "name") == 0) {
rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING);
- if (pt->pt_name != NULL) {
- rettv->vval.v_string = vim_strsave(pt->pt_name);
+ n = partial_name(pt);
+ if (n == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = vim_strsave(n);
+ if (rettv->v_type == VAR_FUNC) {
+ func_ref(rettv->vval.v_string);
+ }
}
} else if (STRCMP(what, "dict") == 0) {
rettv->v_type = VAR_DICT;
@@ -10126,10 +10530,6 @@ static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// buffer-local-option
done = true;
}
- } else if (STRCMP(varname, "changedtick") == 0) {
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = curbuf->b_changedtick;
- done = true;
} else {
// Look up the variable.
// Let getbufvar({nr}, "") return the "b:" dictionary.
@@ -10665,6 +11065,37 @@ static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
get_buffer_lines(curbuf, lnum, end, retlist, rettv);
}
+static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg,
+ typval_T *rettv)
+{
+ if (what_arg->v_type == VAR_UNKNOWN) {
+ rettv_list_alloc(rettv);
+ if (is_qf || wp != NULL) {
+ (void)get_errorlist(wp, -1, rettv->vval.v_list);
+ }
+ } else {
+ rettv_dict_alloc(rettv);
+ if (is_qf || wp != NULL) {
+ if (what_arg->v_type == VAR_DICT) {
+ dict_T *d = what_arg->vval.v_dict;
+
+ if (d != NULL) {
+ get_errorlist_properties(wp, d, rettv->vval.v_dict);
+ }
+ } else {
+ EMSG(_(e_dictreq));
+ }
+ }
+ }
+}
+
+/// "getloclist()" function
+static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = find_win_by_nr(&argvars[0], NULL);
+ get_qf_loc_list(false, wp, &argvars[1], rettv);
+}
+
/*
* "getmatches()" function
*/
@@ -10766,20 +11197,10 @@ static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
getpos_both(argvars, rettv, false);
}
-/*
- * "getqflist()" and "getloclist()" functions
- */
+/// "getqflist()" functions
static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- rettv_list_alloc(rettv);
- win_T *wp = NULL;
- if (argvars[0].v_type != VAR_UNKNOWN) { /* getloclist() */
- wp = find_win_by_nr(&argvars[0], NULL);
- if (wp == NULL) {
- return;
- }
- }
- (void)get_errorlist(wp, rettv->vval.v_list);
+ get_qf_loc_list(true, NULL, &argvars[0], rettv);
}
/// "getreg()" function
@@ -11338,6 +11759,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
"insert_expand",
"jumplist",
"keymap",
+ "lambda",
"langmap",
"libcall",
"linebreak",
@@ -11996,13 +12418,14 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
long before = 0;
listitem_T *item;
list_T *l;
- int error = FALSE;
+ int error = false;
+ const char *const arg_errmsg = _("insert() argument");
+ const size_t arg_errmsg_len = strlen(arg_errmsg);
if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "insert()");
} else if ((l = argvars[0].vval.v_list) != NULL
- && !tv_check_lock(l->lv_lock,
- (char_u *)N_("insert() argument"), true)) {
+ && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) {
if (argvars[2].v_type != VAR_UNKNOWN) {
before = get_tv_number_chk(&argvars[2], &error);
}
@@ -12053,38 +12476,33 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr)
dictitem_T *di;
rettv->vval.v_number = -1;
- end = get_lval(get_tv_string(&argvars[0]), NULL, &lv, FALSE, FALSE,
- GLV_NO_AUTOLOAD, FNE_CHECK_START);
+ end = get_lval(get_tv_string(&argvars[0]), NULL, &lv, false, false,
+ GLV_NO_AUTOLOAD|GLV_READ_ONLY, FNE_CHECK_START);
if (end != NULL && lv.ll_name != NULL) {
if (*end != NUL)
EMSG(_(e_trailing));
else {
if (lv.ll_tv == NULL) {
- if (check_changedtick(lv.ll_name)) {
- rettv->vval.v_number = 1; // Always locked.
- } else {
- di = find_var((const char *)lv.ll_name, STRLEN(lv.ll_name), NULL,
- true);
- if (di != NULL) {
- /* Consider a variable locked when:
- * 1. the variable itself is locked
- * 2. the value of the variable is locked.
- * 3. the List or Dict value is locked.
- */
- rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK)
- || tv_islocked(&di->di_tv));
- }
+ di = find_var((const char *)lv.ll_name, STRLEN(lv.ll_name), NULL, true);
+ if (di != NULL) {
+ // Consider a variable locked when:
+ // 1. the variable itself is locked
+ // 2. the value of the variable is locked.
+ // 3. the List or Dict value is locked.
+ rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK)
+ || tv_islocked(&di->di_tv));
}
- } else if (lv.ll_range)
+ } else if (lv.ll_range) {
EMSG(_("E786: Range not allowed"));
- else if (lv.ll_newkey != NULL)
+ } else if (lv.ll_newkey != NULL) {
EMSG2(_(e_dictkey), lv.ll_newkey);
- else if (lv.ll_list != NULL)
- /* List item. */
+ } else if (lv.ll_list != NULL) {
+ // List item.
rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv);
- else
- /* Dictionary item. */
+ } else {
+ // Dictionary item.
rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv);
+ }
}
}
@@ -14031,20 +14449,21 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
char_u *key;
dict_T *d;
dictitem_T *di;
- char_u *arg_errmsg = (char_u *)N_("remove() argument");
+ const char *const arg_errmsg = _("remove() argument");
+ const size_t arg_errmsg_len = strlen(arg_errmsg);
if (argvars[0].v_type == VAR_DICT) {
if (argvars[2].v_type != VAR_UNKNOWN) {
EMSG2(_(e_toomanyarg), "remove()");
} else if ((d = argvars[0].vval.v_dict) != NULL
- && !tv_check_lock(d->dv_lock, arg_errmsg, true)) {
+ && !tv_check_lock(d->dv_lock, arg_errmsg, arg_errmsg_len)) {
key = get_tv_string_chk(&argvars[1]);
if (key != NULL) {
di = dict_find(d, key, -1);
if (di == NULL) {
EMSG2(_(e_dictkey), key);
- } else if (!var_check_fixed(di->di_flags, arg_errmsg, true)
- && !var_check_ro(di->di_flags, arg_errmsg, true)) {
+ } else if (!var_check_fixed(di->di_flags, arg_errmsg, arg_errmsg_len)
+ && !var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len)) {
*rettv = di->di_tv;
init_tv(&di->di_tv);
dictitem_remove(d, di);
@@ -14057,7 +14476,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} else if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listdictarg), "remove()");
} else if ((l = argvars[0].vval.v_list) != NULL
- && !tv_check_lock(l->lv_lock, arg_errmsg, true)) {
+ && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) {
int error = (int)false;
idx = get_tv_number_chk(&argvars[1], &error);
@@ -14328,19 +14747,19 @@ fail:
*/
static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- list_T *l;
- listitem_T *li, *ni;
+ const char *const arg_errmsg = _("reverse() argument");
+ const size_t arg_errmsg_len = strlen(arg_errmsg);
+ list_T *l;
if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "reverse()");
} else if ((l = argvars[0].vval.v_list) != NULL
- && !tv_check_lock(l->lv_lock,
- (char_u *)N_("reverse() argument"), true)) {
- li = l->lv_last;
+ && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) {
+ listitem_T *li = l->lv_last;
l->lv_first = l->lv_last = NULL;
l->lv_len = 0;
while (li != NULL) {
- ni = li->li_prev;
+ listitem_T *const ni = li->li_prev;
list_append(l, li);
li = ni;
}
@@ -15323,7 +15742,7 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// Create quickfix/location list from VimL values
///
/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid
-/// list_arg, action_arg and title_arg arguments in which case errors out,
+/// list_arg, action_arg and what_arg arguments in which case errors out,
/// including VAR_UNKNOWN parameters.
///
/// @param[in,out] wp Window to create location list for. May be NULL in
@@ -15340,6 +15759,7 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
char_u *title = NULL;
int action = ' ';
rettv->vval.v_number = -1;
+ dict_T *d = NULL;
typval_T *list_arg = &args[0];
if (list_arg->v_type != VAR_LIST) {
@@ -15367,10 +15787,16 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
if (title_arg->v_type == VAR_UNKNOWN) {
// Option argument was not given.
goto skip_args;
- }
- title = get_tv_string_chk(title_arg);
- if (!title) {
- // Type error. Error already printed by get_tv_string_chk().
+ } else if (title_arg->v_type == VAR_STRING) {
+ title = get_tv_string_chk(title_arg);
+ if (!title) {
+ // Type error. Error already printed by get_tv_string_chk().
+ return;
+ }
+ } else if (title_arg->v_type == VAR_DICT) {
+ d = title_arg->vval.v_dict;
+ } else {
+ EMSG(_(e_dictreq));
return;
}
@@ -15380,7 +15806,7 @@ skip_args:
}
list_T *l = list_arg->vval.v_list;
- if (l && set_errorlist(wp, l, action, title) == OK) {
+ if (l && set_errorlist(wp, l, action, title, d) == OK) {
rettv->vval.v_number = 0;
}
}
@@ -15923,8 +16349,9 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
if (partial == NULL) {
func_name = sortinfo->item_compare_func;
} else {
- func_name = partial->pt_name;
+ func_name = partial_name(partial);
}
+
// Copy the values. This is needed to be able to set v_lock to VAR_FIXED
// in the copy without changing the original list items.
copy_tv(&si1->item->li_tv, &argv[0]);
@@ -15933,7 +16360,7 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
rettv.v_type = VAR_UNKNOWN; // clear_tv() uses this
res = call_func(func_name,
(int)STRLEN(func_name),
- &rettv, 2, argv, 0L, 0L, &dummy, true,
+ &rettv, 2, argv, NULL, 0L, 0L, &dummy, true,
partial, sortinfo->item_compare_selfdict);
clear_tv(&argv[0]);
clear_tv(&argv[1]);
@@ -15984,16 +16411,17 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
sortinfo_T *old_sortinfo = sortinfo;
sortinfo = &info;
+ const char *const arg_errmsg = (sort
+ ? _("sort() argument")
+ : _("uniq() argument"));
+ const size_t arg_errmsg_len = strlen(arg_errmsg);
+
if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), sort ? "sort()" : "uniq()");
} else {
l = argvars[0].vval.v_list;
if (l == NULL
- || tv_check_lock(l->lv_lock,
- (char_u *)(sort
- ? N_("sort() argument")
- : N_("uniq() argument")),
- true)) {
+ || tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) {
goto theend;
}
rettv->vval.v_list = l;
@@ -16741,12 +17169,16 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
+ if (no < 0 || no >= NSUBEXP) {
+ EMSGN(_("E935: invalid submatch number: %d"), no);
+ return;
+ }
int retList = 0;
if (argvars[1].v_type != VAR_UNKNOWN) {
retList = get_tv_number_chk(&argvars[1], &error);
if (error) {
- return;
+ return;
}
}
@@ -16770,19 +17202,26 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
char_u *str = get_tv_string_chk(&argvars[0]);
char_u *pat = get_tv_string_buf_chk(&argvars[1], patbuf);
- char_u *sub = get_tv_string_buf_chk(&argvars[2], subbuf);
+ char_u *sub = NULL;
+ typval_T *expr = NULL;
char_u *flg = get_tv_string_buf_chk(&argvars[3], flagsbuf);
+ if (argvars[2].v_type == VAR_FUNC || argvars[2].v_type == VAR_PARTIAL) {
+ expr = &argvars[2];
+ } else {
+ sub = get_tv_string_buf_chk(&argvars[2], subbuf);
+ }
+
rettv->v_type = VAR_STRING;
- if (str == NULL || pat == NULL || sub == NULL || flg == NULL)
+ if (str == NULL || pat == NULL || (sub == NULL && expr == NULL)
+ || flg == NULL) {
rettv->vval.v_string = NULL;
- else
- rettv->vval.v_string = do_string_sub(str, pat, sub, flg);
+ } else {
+ rettv->vval.v_string = do_string_sub(str, pat, sub, expr, flg);
+ }
}
-/*
- * "synID(lnum, col, trans)" function
- */
+/// "synID(lnum, col, trans)" function
static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
int id = 0;
@@ -17321,10 +17760,10 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
(void)setfname(curbuf, (uint8_t *)buf, NULL, true);
// Save the job id and pid in b:terminal_job_{id,pid}
Error err;
- dict_set_value(curbuf->b_vars, cstr_as_string("terminal_job_id"),
- INTEGER_OBJ(rettv->vval.v_number), false, false, &err);
- dict_set_value(curbuf->b_vars, cstr_as_string("terminal_job_pid"),
- INTEGER_OBJ(pid), false, false, &err);
+ dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"),
+ INTEGER_OBJ(rettv->vval.v_number), false, false, &err);
+ dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"),
+ INTEGER_OBJ(pid), false, false, &err);
Terminal *term = terminal_open(topts);
data->term = term;
@@ -17333,6 +17772,15 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
+// "test_garbagecollect_now()" function
+static void f_test_garbagecollect_now(typval_T *argvars,
+ typval_T *rettv, FunPtr fptr)
+{
+ // This is dangerous, any Lists and Dicts used internally may be freed
+ // while still in use.
+ garbage_collect(true);
+}
+
static bool callback_from_typval(Callback *callback, typval_T *arg)
{
if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) {
@@ -17411,7 +17859,7 @@ static bool callback_call(Callback *callback, int argcount_in,
case kCallbackPartial:
partial = callback->data.partial;
- name = partial->pt_name;
+ name = partial_name(partial);
break;
case kCallbackNone:
@@ -17424,7 +17872,7 @@ static bool callback_call(Callback *callback, int argcount_in,
int dummy;
return call_func(name, (int)STRLEN(name), rettv, argcount_in, argvars_in,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy,
+ NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy,
true, partial, NULL);
}
@@ -18048,29 +18496,88 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
/// Writes list of strings to file
-static bool write_list(FILE *fd, list_T *list, bool binary)
-{
- int ret = true;
-
- for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) {
- for (char_u *s = get_tv_string(&li->li_tv); *s != NUL; ++s) {
- if (putc(*s == '\n' ? NUL : *s, fd) == EOF) {
- ret = false;
- break;
+///
+/// @param fp File to write to.
+/// @param[in] list List to write.
+/// @param[in] binary Whether to write in binary mode.
+///
+/// @return true in case of success, false otherwise.
+static bool write_list(FileDescriptor *const fp, const list_T *const list,
+ const bool binary)
+{
+ int error = 0;
+ for (const listitem_T *li = list->lv_first; li != NULL; li = li->li_next) {
+ const char *const s = (const char *)get_tv_string_chk(
+ (typval_T *)&li->li_tv);
+ if (s == NULL) {
+ return false;
+ }
+ const char *hunk_start = s;
+ for (const char *p = hunk_start;; p++) {
+ if (*p == NUL || *p == NL) {
+ if (p != hunk_start) {
+ const ptrdiff_t written = file_write(fp, hunk_start,
+ (size_t)(p - hunk_start));
+ if (written < 0) {
+ error = (int)written;
+ goto write_list_error;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ } else {
+ hunk_start = p + 1;
+ const ptrdiff_t written = file_write(fp, (char[]){ NUL }, 1);
+ if (written < 0) {
+ error = (int)written;
+ break;
+ }
+ }
}
}
if (!binary || li->li_next != NULL) {
- if (putc('\n', fd) == EOF) {
- ret = false;
- break;
+ const ptrdiff_t written = file_write(fp, "\n", 1);
+ if (written < 0) {
+ error = (int)written;
+ goto write_list_error;
}
}
- if (ret == false) {
- EMSG(_(e_write));
- break;
+ }
+ if ((error = file_fsync(fp)) != 0) {
+ goto write_list_error;
+ }
+ return true;
+write_list_error:
+ emsgf(_("E80: Error while writing: %s"), os_strerror(error));
+ return false;
+}
+
+/// Initializes a static list with 10 items.
+void init_static_list(staticList10_T *sl)
+{
+ list_T *l = &sl->sl_list;
+
+ memset(sl, 0, sizeof(staticList10_T));
+ l->lv_first = &sl->sl_items[0];
+ l->lv_last = &sl->sl_items[9];
+ l->lv_refcount = DO_NOT_FREE_CNT;
+ l->lv_lock = VAR_FIXED;
+ sl->sl_list.lv_len = 10;
+
+ for (int i = 0; i < 10; i++) {
+ listitem_T *li = &sl->sl_items[i];
+
+ if (i == 0) {
+ li->li_prev = NULL;
+ } else {
+ li->li_prev = li - 1;
+ }
+ if (i == 9) {
+ li->li_next = NULL;
+ } else {
+ li->li_next = li + 1;
}
}
- return ret;
}
/// Saves a typval_T as a string.
@@ -18170,27 +18677,42 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool binary = false;
bool append = false;
if (argvars[2].v_type != VAR_UNKNOWN) {
- if (vim_strchr(get_tv_string(&argvars[2]), 'b')) {
+ const char *const flags = (const char *)get_tv_string_chk(&argvars[2]);
+ if (flags == NULL) {
+ return;
+ }
+ if (strchr(flags, 'b')) {
binary = true;
}
- if (vim_strchr(get_tv_string(&argvars[2]), 'a')) {
+ if (strchr(flags, 'a')) {
append = true;
}
}
- // Always open the file in binary mode, library functions have a mind of
- // their own about CR-LF conversion.
- char_u *fname = get_tv_string(&argvars[1]);
- FILE *fd;
- if (*fname == NUL || (fd = mch_fopen((char *)fname,
- append ? APPENDBIN : WRITEBIN)) == NULL) {
- EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("<empty>") : fname);
- rettv->vval.v_number = -1;
+ const char buf[NUMBUFLEN];
+ const char *const fname = (const char *)get_tv_string_buf_chk(&argvars[1],
+ (char_u *)buf);
+ if (fname == NULL) {
+ return;
+ }
+ FileDescriptor *fp;
+ int error;
+ rettv->vval.v_number = -1;
+ if (*fname == NUL) {
+ EMSG(_("E482: Can't open file with an empty name"));
+ } else if ((fp = file_open_new(&error, fname,
+ ((append ? kFileAppend : kFileTruncate)
+ | kFileCreate), 0666)) == NULL) {
+ emsgf(_("E482: Can't open file %s for writing: %s"),
+ fname, os_strerror(error));
} else {
- if (write_list(fd, argvars[0].vval.v_list, binary) == false) {
- rettv->vval.v_number = -1;
+ if (write_list(fp, argvars[0].vval.v_list, binary)) {
+ rettv->vval.v_number = 0;
+ }
+ if ((error = file_free(fp)) != 0) {
+ emsgf(_("E80: Error when closing file %s: %s"),
+ fname, os_strerror(error));
}
- fclose(fd);
}
}
/*
@@ -18888,23 +19410,13 @@ static int get_var_tv(
{
int ret = OK;
typval_T *tv = NULL;
- typval_T atv;
dictitem_T *v;
- // Check for "b:changedtick".
- if (sizeof("b:changedtick") - 1 == len
- && STRNCMP(name, "b:changedtick", len) == 0) {
- atv.v_type = VAR_NUMBER;
- atv.vval.v_number = curbuf->b_changedtick;
- tv = &atv;
- } else {
- // Check for user-defined variables.
- v = find_var(name, (size_t)len, NULL, no_autoload);
- if (v != NULL) {
- tv = &v->di_tv;
- if (dip != NULL) {
- *dip = v;
- }
+ v = find_var(name, (size_t)len, NULL, no_autoload);
+ if (v != NULL) {
+ tv = &v->di_tv;
+ if (dip != NULL) {
+ *dip = v;
}
}
@@ -18920,11 +19432,27 @@ static int get_var_tv(
return ret;
}
-/*
- * Handle expr[expr], expr[expr:expr] subscript and .name lookup.
- * Also handle function call with Funcref variable: func(expr)
- * Can all be combined: dict.func(expr)[idx]['func'](expr)
- */
+/// Check if variable "name[len]" is a local variable or an argument.
+/// If so, "*eval_lavars_used" is set to TRUE.
+static void check_vars(const char *name, size_t len)
+{
+ if (eval_lavars_used == NULL) {
+ return;
+ }
+
+ const char *varname;
+ hashtab_T *ht = find_var_ht(name, len, &varname);
+
+ if (ht == get_funccal_local_ht() || ht == get_funccal_args_ht()) {
+ if (find_var(name, len, NULL, true) != NULL) {
+ *eval_lavars_used = true;
+ }
+ }
+}
+
+/// Handle expr[expr], expr[expr:expr] subscript and .name lookup.
+/// Also handle function call with Funcref variable: func(expr)
+/// Can all be combined: dict.func(expr)[idx]['func'](expr)
static int
handle_subscript(
const char **const arg,
@@ -18955,7 +19483,7 @@ handle_subscript(
// Invoke the function. Recursive!
if (functv.v_type == VAR_PARTIAL) {
pt = functv.vval.v_partial;
- s = pt->pt_name;
+ s = partial_name(pt);
} else {
s = functv.vval.v_string;
}
@@ -19015,19 +19543,23 @@ static void set_selfdict(typval_T *rettv, dict_T *selfdict)
&& rettv->vval.v_partial->pt_dict != NULL) {
return;
}
- char_u *fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING
- ? rettv->vval.v_string
- : rettv->vval.v_partial->pt_name;
+ char_u *fname;
char_u *tofree = NULL;
ufunc_T *fp;
char_u fname_buf[FLEN_FIXED + 1];
int error;
- // Translate "s:func" to the stored function name.
- fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
-
- fp = find_func(fname);
- xfree(tofree);
+ if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) {
+ fp = rettv->vval.v_partial->pt_func;
+ } else {
+ fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING
+ ? rettv->vval.v_string
+ : rettv->vval.v_partial->pt_name;
+ // Translate "s:func" to the stored function name.
+ fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
+ fp = find_func(fname);
+ xfree(tofree);
+ }
// Turn "dict.Func" into a partial for "Func" with "dict".
if (fp != NULL && (fp->uf_flags & FC_DICT)) {
@@ -19048,8 +19580,13 @@ static void set_selfdict(typval_T *rettv, dict_T *selfdict)
// Partial: copy the function name, use selfdict and copy
// args. Can't take over name or args, the partial might
// be referenced elsewhere.
- pt->pt_name = vim_strsave(ret_pt->pt_name);
- func_ref(pt->pt_name);
+ if (ret_pt->pt_name != NULL) {
+ pt->pt_name = vim_strsave(ret_pt->pt_name);
+ func_ref(pt->pt_name);
+ } else {
+ pt->pt_func = ret_pt->pt_func;
+ func_ptr_ref(pt->pt_func);
+ }
if (ret_pt->pt_argc > 0) {
size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc;
pt->pt_argv = (typval_T *)xmalloc(arg_size);
@@ -19563,7 +20100,7 @@ char_u *get_tv_string_chk(const typval_T *varp)
return get_tv_string_buf_chk(varp, mybuf);
}
-static char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf)
+char_u *get_tv_string_buf_chk(const typval_T *varp, char_u *buf)
FUNC_ATTR_NONNULL_ALL
{
switch (varp->v_type) {
@@ -19615,9 +20152,15 @@ static dictitem_T *find_var(const char *const name, const size_t name_len,
if (ht == NULL) {
return NULL;
}
- return find_var_in_ht(ht, *name,
- varname, name_len - (size_t)(varname - name),
- no_autoload || htp != NULL);
+ dictitem_T *ret = find_var_in_ht(ht, *name,
+ varname, name_len - (size_t)(varname - name),
+ no_autoload || htp != NULL);
+ if (ret != NULL) {
+ return ret;
+ }
+
+ // Search in parent scope for lambda
+ return find_var_in_scoped_ht(name, name_len, no_autoload || htp != NULL);
}
/// Find variable in hashtab
@@ -19697,6 +20240,26 @@ static funccall_T *get_funccal(void)
return funccal;
}
+/// Return the hashtable used for argument in the current funccal.
+/// Return NULL if there is no current funccal.
+static hashtab_T *get_funccal_args_ht(void)
+{
+ if (current_funccal == NULL) {
+ return NULL;
+ }
+ return &get_funccal()->l_avars.dv_hashtab;
+}
+
+/// Return the hashtable used for local variables in the current funccal.
+/// Return NULL if there is no current funccal.
+static hashtab_T *get_funccal_local_ht(void)
+{
+ if (current_funccal == NULL) {
+ return NULL;
+ }
+ return &get_funccal()->l_vars.dv_hashtab;
+}
+
/// Find the dict and hashtable used for a variable
///
/// @param[in] name Variable name, possibly with scope prefix.
@@ -19836,7 +20399,7 @@ void new_script_vars(scid_T id)
void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope)
{
hash_init(&dict->dv_hashtab);
- dict->dv_lock = 0;
+ dict->dv_lock = VAR_UNLOCKED;
dict->dv_scope = scope;
dict->dv_refcount = DO_NOT_FREE_CNT;
dict->dv_copyID = 0;
@@ -20000,6 +20563,11 @@ set_var (
(const char *)varname, name_len - (size_t)(varname - name),
true);
+ // Search in parent scope which is possible to reference from lambda
+ if (v == NULL) {
+ v = find_var_in_scoped_ht((const char *)name, name_len, true);
+ }
+
if ((tv->v_type == VAR_FUNC || tv->v_type == VAR_PARTIAL)
&& var_check_func_name(name, v == NULL)) {
return;
@@ -20007,8 +20575,8 @@ set_var (
if (v != NULL) {
// existing variable, need to clear the value
- if (var_check_ro(v->di_flags, name, false)
- || tv_check_lock(v->di_tv.v_lock, name, false)) {
+ if (var_check_ro(v->di_flags, (const char *)name, name_len)
+ || tv_check_lock(v->di_tv.v_lock, (const char *)name, name_len)) {
return;
}
@@ -20081,28 +20649,47 @@ set_var (
}
}
-// Return true if di_flags "flags" indicates variable "name" is read-only.
-// Also give an error message.
-static bool var_check_ro(int flags, char_u *name, bool use_gettext)
+/// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX)
+///
+/// Also gives an error message.
+///
+/// @param[in] flags di_flags attribute value.
+/// @param[in] name Variable name, for use in error message.
+/// @param[in] name_len Variable name length.
+///
+/// @return True if variable is read-only: either always or in sandbox when
+/// sandbox is enabled, false otherwise.
+static bool var_check_ro(const int flags, const char *const name,
+ const size_t name_len)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
if (flags & DI_FLAGS_RO) {
- EMSG2(_(e_readonlyvar), use_gettext ? (char_u *)_(name) : name);
+ emsgf(_(e_readonlyvar), (int)name_len, name);
return true;
}
if ((flags & DI_FLAGS_RO_SBX) && sandbox) {
- EMSG2(_(e_readonlysbx), use_gettext ? (char_u *)_(name) : name);
+ emsgf(_("E794: Cannot set variable in the sandbox: \"%.*s\""),
+ (int)name_len, name);
return true;
}
return false;
}
-// Return true if di_flags "flags" indicates variable "name" is fixed.
-// Also give an error message.
-static bool var_check_fixed(int flags, char_u *name, bool use_gettext)
+/// Check whether variable is fixed (DI_FLAGS_FIX)
+///
+/// Also gives an error message.
+///
+/// @param[in] flags di_flags attribute value.
+/// @param[in] name Variable name, for use in error message.
+/// @param[in] name_len Variable name length.
+///
+/// @return True if variable is fixed, false otherwise.
+static bool var_check_fixed(const int flags, const char *const name,
+ const size_t name_len)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
if (flags & DI_FLAGS_FIX) {
- EMSG2(_("E795: Cannot delete variable %s"),
- use_gettext ? (char_u *)_(name) : name);
+ emsgf(_("E795: Cannot delete variable %.*s"), (int)name_len, name);
return true;
}
return false;
@@ -20128,7 +20715,7 @@ var_check_func_name (
// Don't allow hiding a function. When "v" is not NULL we might be
// assigning another function to the same var, the type is checked
// below.
- if (new_var && function_exists((const char *)name)) {
+ if (new_var && function_exists((const char *)name, false)) {
EMSG2(_("E705: Variable name conflicts with existing function: %s"),
name);
return true;
@@ -20153,28 +20740,42 @@ static int valid_varname(char_u *varname)
return TRUE;
}
-// Return true if typeval "tv" is set to be locked (immutable).
-// Also give an error message, using "name" or _("name") when use_gettext is
-// true.
-static bool tv_check_lock(int lock, char_u *name, bool use_gettext)
+/// Check whether typval is locked
+///
+/// Also gives an error message.
+///
+/// @param[in] lock Lock status.
+/// @param[in] name Value name, for use in error message.
+/// @param[in] name_len Value name length.
+///
+/// @return True if value is locked.
+static bool tv_check_lock(const VarLockStatus lock,
+ const char *const name,
+ const size_t name_len)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (lock & VAR_LOCKED) {
- EMSG2(_("E741: Value is locked: %s"),
- name == NULL
- ? (char_u *)_("Unknown")
- : use_gettext ? (char_u *)_(name)
- : name);
- return true;
- }
- if (lock & VAR_FIXED) {
- EMSG2(_("E742: Cannot change value of %s"),
- name == NULL
- ? (char_u *)_("Unknown")
- : use_gettext ? (char_u *)_(name)
- : name);
- return true;
+ const char *error_message = NULL;
+ switch (lock) {
+ case VAR_UNLOCKED: {
+ return false;
+ }
+ case VAR_LOCKED: {
+ error_message = N_("E741: Value is locked: %.*s");
+ break;
+ }
+ case VAR_FIXED: {
+ error_message = N_("E742: Cannot change value of %.*s");
+ break;
+ }
}
- return false;
+ assert(error_message != NULL);
+
+ const char *const unknown_name = _("Unknown");
+
+ emsgf(_(error_message), (name != NULL ? name_len : strlen(unknown_name)),
+ (name != NULL ? name : unknown_name));
+
+ return true;
}
/*
@@ -20542,10 +21143,10 @@ void ex_function(exarg_T *eap)
char_u *line_arg = NULL;
garray_T newargs;
garray_T newlines;
- int varargs = FALSE;
- int mustend = FALSE;
+ int varargs = false;
int flags = 0;
ufunc_T *fp;
+ bool overwrite = false;
int indent;
int nesting;
char_u *skip_until = NULL;
@@ -20568,8 +21169,9 @@ void ex_function(exarg_T *eap)
if (!HASHITEM_EMPTY(hi)) {
--todo;
fp = HI2UF(hi);
- if (!isdigit(*fp->uf_name))
- list_func_head(fp, FALSE);
+ if (!func_name_refcount(fp->uf_name)) {
+ list_func_head(fp, false);
+ }
}
}
}
@@ -20726,59 +21328,11 @@ void ex_function(exarg_T *eap)
EMSG(_("E862: Cannot use g: here"));
}
- /*
- * Isolate the arguments: "arg1, arg2, ...)"
- */
- while (*p != ')') {
- if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
- varargs = TRUE;
- p += 3;
- mustend = TRUE;
- } else {
- arg = p;
- while (ASCII_ISALNUM(*p) || *p == '_')
- ++p;
- if (arg == p || isdigit(*arg)
- || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
- || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) {
- if (!eap->skip)
- EMSG2(_("E125: Illegal argument: %s"), arg);
- break;
- }
- ga_grow(&newargs, 1);
- c = *p;
- *p = NUL;
- arg = vim_strsave(arg);
-
- /* Check for duplicate argument name. */
- for (int i = 0; i < newargs.ga_len; ++i)
- if (STRCMP(((char_u **)(newargs.ga_data))[i], arg) == 0) {
- EMSG2(_("E853: Duplicate argument name: %s"), arg);
- xfree(arg);
- goto erret;
- }
-
- ((char_u **)(newargs.ga_data))[newargs.ga_len] = arg;
- *p = c;
- newargs.ga_len++;
- if (*p == ',')
- ++p;
- else
- mustend = TRUE;
- }
- p = skipwhite(p);
- if (mustend && *p != ')') {
- if (!eap->skip)
- EMSG2(_(e_invarg2), eap->arg);
- break;
- }
- }
- if (*p != ')') {
- goto erret;
+ if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) {
+ goto errret_2;
}
- ++p; // skip the ')'
- /* find extra arguments "range", "dict" and "abort" */
+ // find extra arguments "range", "dict", "abort" and "closure"
for (;; ) {
p = skipwhite(p);
if (STRNCMP(p, "range", 5) == 0) {
@@ -20790,8 +21344,18 @@ void ex_function(exarg_T *eap)
} else if (STRNCMP(p, "abort", 5) == 0) {
flags |= FC_ABORT;
p += 5;
- } else
+ } else if (STRNCMP(p, "closure", 7) == 0) {
+ flags |= FC_CLOSURE;
+ p += 7;
+ if (current_funccal == NULL) {
+ emsg_funcname(N_
+ ("E932 Closure function should not be at top level: %s"),
+ name == NULL ? (char_u *)"" : name);
+ goto erret;
+ }
+ } else {
break;
+ }
}
/* When there is a line break use what follows for the function body.
@@ -20981,16 +21545,25 @@ void ex_function(exarg_T *eap)
emsg_funcname(e_funcexts, name);
goto erret;
}
- if (fp->uf_refcount > 1 || fp->uf_calls > 0) {
+ if (fp->uf_calls > 0) {
emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"),
name);
goto erret;
}
- /* redefine existing function */
- ga_clear_strings(&(fp->uf_args));
- ga_clear_strings(&(fp->uf_lines));
- xfree(name);
- name = NULL;
+ if (fp->uf_refcount > 1) {
+ // This function is referenced somewhere, don't redefine it but
+ // create a new one.
+ (fp->uf_refcount)--;
+ fp->uf_flags |= FC_REMOVED;
+ fp = NULL;
+ overwrite = true;
+ } else {
+ // redefine existing function
+ ga_clear_strings(&(fp->uf_args));
+ ga_clear_strings(&(fp->uf_lines));
+ xfree(name);
+ name = NULL;
+ }
}
} else {
char numbuf[20];
@@ -21001,11 +21574,13 @@ void ex_function(exarg_T *eap)
goto erret;
}
if (fudi.fd_di == NULL) {
- if (tv_check_lock(fudi.fd_dict->dv_lock, eap->arg, false)) {
+ if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg,
+ STRLEN(eap->arg))) {
// Can't add a function to a locked dictionary
goto erret;
}
- } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, eap->arg, false)) {
+ } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg,
+ STRLEN(eap->arg))) {
// Can't change an existing function if it is locked
goto erret;
}
@@ -21042,7 +21617,7 @@ void ex_function(exarg_T *eap)
}
}
- fp = xmalloc(sizeof(ufunc_T) + STRLEN(name));
+ fp = xcalloc(1, sizeof(ufunc_T) + STRLEN(name));
if (fudi.fd_dict != NULL) {
if (fudi.fd_di == NULL) {
@@ -21066,14 +21641,22 @@ void ex_function(exarg_T *eap)
/* insert the new function in the function list */
STRCPY(fp->uf_name, name);
- if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) {
- xfree(fp);
- goto erret;
+ if (overwrite) {
+ hi = hash_find(&func_hashtab, name);
+ hi->hi_key = UF2HIKEY(fp);
+ } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) {
+ xfree(fp);
+ goto erret;
}
+ fp->uf_refcount = 1;
}
- fp->uf_refcount = 1;
fp->uf_args = newargs;
fp->uf_lines = newlines;
+ if ((flags & FC_CLOSURE) != 0) {
+ register_closure(fp);
+ } else {
+ fp->uf_scoped = NULL;
+ }
fp->uf_tml_count = NULL;
fp->uf_tml_total = NULL;
fp->uf_tml_self = NULL;
@@ -21088,6 +21671,7 @@ void ex_function(exarg_T *eap)
erret:
ga_clear_strings(&newargs);
+errret_2:
ga_clear_strings(&newlines);
ret_free:
xfree(skip_until);
@@ -21097,18 +21681,18 @@ ret_free:
need_wait_return |= saved_wait_return;
}
-/*
- * Get a function name, translating "<SID>" and "<SNR>".
- * Also handles a Funcref in a List or Dictionary.
- * Returns the function name in allocated memory, or NULL for failure.
- * flags:
- * TFN_INT: internal function name OK
- * TFN_QUIET: be quiet
- * TFN_NO_AUTOLOAD: do not use script autoloading
- * Advances "pp" to just after the function name (if no error).
- */
-static char_u *
-trans_function_name (
+/// Get a function name, translating "<SID>" and "<SNR>".
+/// Also handles a Funcref in a List or Dictionary.
+/// flags:
+/// TFN_INT: internal function name OK
+/// TFN_QUIET: be quiet
+/// TFN_NO_AUTOLOAD: do not use script autoloading
+/// TFN_NO_DEREF: do not dereference a Funcref
+/// Advances "pp" to just after the function name (if no error).
+///
+/// @return the function name in allocated memory, or NULL for failure.
+char_u *
+trans_function_name(
char_u **pp,
int skip, /* only find the end, don't evaluate */
int flags,
@@ -21178,7 +21762,7 @@ trans_function_name (
*pp = end;
} else if (lv.ll_tv->v_type == VAR_PARTIAL
&& lv.ll_tv->vval.v_partial != NULL) {
- name = vim_strsave(lv.ll_tv->vval.v_partial->pt_name);
+ name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial));
*pp = end;
if (partial != NULL) {
*partial = lv.ll_tv->vval.v_partial;
@@ -21210,7 +21794,7 @@ trans_function_name (
if (name == lv.ll_exp_name) {
name = NULL;
}
- } else {
+ } else if (!(flags & TFN_NO_DEREF)) {
len = (int)(end - *pp);
name = deref_func_name((const char *)(*pp), &len, partial,
flags & TFN_NO_AUTOLOAD);
@@ -21275,7 +21859,7 @@ trans_function_name (
goto theend;
}
- if (!skip && !(flags & TFN_QUIET)) {
+ if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) {
char_u *cp = vim_strchr(lv.ll_name, ':');
if (cp != NULL && cp < end) {
@@ -21375,15 +21959,16 @@ static void list_func_head(ufunc_T *fp, int indent)
if (fp->uf_flags & FC_DICT) {
msg_puts(" dict");
}
+ if (fp->uf_flags & FC_CLOSURE) {
+ msg_puts(" closure");
+ }
msg_clr_eos();
if (p_verbose > 0)
last_set_msg(fp->uf_script_ID);
}
-/*
- * Find a function by name, return pointer to it in ufuncs.
- * Return NULL for unknown function.
- */
+/// Find a function by name, return pointer to it in ufuncs.
+/// @return NULL for unknown function.
static ufunc_T *find_func(const char_u *name)
{
hashitem_T *hi;
@@ -21398,15 +21983,60 @@ static ufunc_T *find_func(const char_u *name)
void free_all_functions(void)
{
hashitem_T *hi;
+ ufunc_T *fp;
+ uint64_t skipped = 0;
+ uint64_t todo = 1;
+ uint64_t used;
+
+ // First clear what the functions contain. Since this may lower the
+ // reference count of a function, it may also free a function and change
+ // the hash table. Restart if that happens.
+ while (todo > 0) {
+ todo = func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ // Only free functions that are not refcounted, those are
+ // supposed to be freed when no longer referenced.
+ fp = HI2UF(hi);
+ if (func_name_refcount(fp->uf_name)) {
+ skipped++;
+ } else {
+ used = func_hashtab.ht_used;
+ func_clear(fp, true);
+ if (used != func_hashtab.ht_used) {
+ skipped = 0;
+ break;
+ }
+ }
+ todo--;
+ }
+ }
+ }
- /* Need to start all over every time, because func_free() may change the
- * hash table. */
- while (func_hashtab.ht_used > 0)
- for (hi = func_hashtab.ht_array;; ++hi)
+ // Now actually free the functions. Need to start all over every time,
+ // because func_free() may change the hash table.
+ skipped = 0;
+ while (func_hashtab.ht_used > skipped) {
+ todo = func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
- func_free(HI2UF(hi));
- break;
+ todo--;
+ // Only free functions that are not refcounted, those are
+ // supposed to be freed when no longer referenced.
+ fp = HI2UF(hi);
+ if (func_name_refcount(fp->uf_name)) {
+ skipped++;
+ } else {
+ func_free(fp);
+ skipped = 0;
+ break;
+ }
}
+ }
+ }
+ if (skipped == 0) {
+ hash_clear(&func_hashtab);
+ }
}
#endif
@@ -21422,16 +22052,19 @@ bool translated_function_exists(const char *name)
/// Check whether function with the given name exists
///
/// @param[in] name Function name.
+/// @param[in] no_deref Whether to dereference a Funcref.
///
/// @return True if it exists, false otherwise.
-static bool function_exists(const char *const name)
+static bool function_exists(const char *const name, bool no_deref)
{
char_u *nm = (char_u *)name;
bool n = false;
+ int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD;
- char *const p = (char *)trans_function_name(&nm, false,
- TFN_INT|TFN_QUIET|TFN_NO_AUTOLOAD,
- NULL, NULL);
+ if (no_deref) {
+ flag |= TFN_NO_DEREF;
+ }
+ char *const p = (char *)trans_function_name(&nm, false, flag, NULL, NULL);
nm = skipwhite(nm);
/* Only accept "funcname", "funcname ", "funcname (..." and
@@ -21443,9 +22076,13 @@ static bool function_exists(const char *const name)
return n;
}
-/// Return TRUE if "name" looks like a builtin function name: starts with a
+/// Checks if a builtin function with the given name exists.
+///
+/// @param[in] name name of the builtin function to check.
+/// @param[in] len length of "name", or -1 for NUL terminated.
+///
+/// @return true if "name" looks like a builtin function name: starts with a
/// lower case letter and doesn't contain AUTOLOAD_CHAR.
-/// "len" is the length of "name", or -1 for NUL terminated.
static bool builtin_function(const char *name, int len)
{
if (!ASCII_ISLOWER(name[0])) {
@@ -21719,8 +22356,10 @@ char_u *get_user_func_name(expand_T *xp, int idx)
++hi;
fp = HI2UF(hi);
- if (fp->uf_flags & FC_DICT)
- return (char_u *)""; /* don't show dict functions */
+ if ((fp->uf_flags & FC_DICT)
+ || STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
+ return (char_u *)""; // don't show dict and lambda functions
+ }
if (STRLEN(fp->uf_name) + 4 >= IOSIZE)
return fp->uf_name; /* prevents overflow */
@@ -21751,9 +22390,18 @@ static void cat_func_name(char_u *buf, ufunc_T *fp)
STRCPY(buf, fp->uf_name);
}
-/*
- * ":delfunction {name}"
- */
+/// There are two kinds of function names:
+/// 1. ordinary names, function defined with :function
+/// 2. numbered functions and lambdas
+/// For the first we only count the name stored in func_hashtab as a reference,
+/// using function() does not count as a reference, because the function is
+/// looked up by name.
+static bool func_name_refcount(char_u *name)
+{
+ return isdigit(*name) || *name == '<';
+}
+
+/// ":delfunction {name}"
void ex_delfunction(exarg_T *eap)
{
ufunc_T *fp = NULL;
@@ -21803,98 +22451,167 @@ void ex_delfunction(exarg_T *eap)
/* Delete the dict item that refers to the function, it will
* invoke func_unref() and possibly delete the function. */
dictitem_remove(fudi.fd_dict, fudi.fd_di);
- } else
- func_free(fp);
+ } else {
+ // A normal function (not a numbered function or lambda) has a
+ // refcount of 1 for the entry in the hashtable. When deleting
+ // it and the refcount is more than one, it should be kept.
+ // A numbered function or lambda should be kept if the refcount is
+ // one or more.
+ if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) {
+ // Function is still referenced somewhere. Don't free it but
+ // do remove it from the hashtable.
+ if (func_remove(fp)) {
+ fp->uf_refcount--;
+ }
+ fp->uf_flags |= FC_DELETED;
+ } else {
+ func_clear_free(fp, false);
+ }
+ }
}
}
-/*
- * Free a function and remove it from the list of functions.
- */
-static void func_free(ufunc_T *fp)
+/// Remove the function from the function hashtable. If the function was
+/// deleted while it still has references this was already done.
+///
+/// @return true if the entry was deleted, false if it wasn't found.
+static bool func_remove(ufunc_T *fp)
{
- hashitem_T *hi;
+ hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp));
+
+ if (!HASHITEM_EMPTY(hi)) {
+ hash_remove(&func_hashtab, hi);
+ return true;
+ }
+
+ return false;
+}
+
+/// Free all things that a function contains. Does not free the function
+/// itself, use func_free() for that.
+///
+/// param[in] force When true, we are exiting.
+static void func_clear(ufunc_T *fp, bool force)
+{
+ if (fp->uf_cleared) {
+ return;
+ }
+ fp->uf_cleared = true;
- /* clear this function */
+ // clear this function
ga_clear_strings(&(fp->uf_args));
ga_clear_strings(&(fp->uf_lines));
xfree(fp->uf_tml_count);
xfree(fp->uf_tml_total);
xfree(fp->uf_tml_self);
+ funccal_unref(fp->uf_scoped, fp, force);
+}
- /* remove the function from the function hashtable */
- hi = hash_find(&func_hashtab, UF2HIKEY(fp));
- if (HASHITEM_EMPTY(hi))
- EMSG2(_(e_intern2), "func_free()");
- else
- hash_remove(&func_hashtab, hi);
-
+/// Free a function and remove it from the list of functions. Does not free
+/// what a function contains, call func_clear() first.
+///
+/// param[in] fp The function to free.
+static void func_free(ufunc_T *fp)
+{
+ // only remove it when not done already, otherwise we would remove a newer
+ // version of the function
+ if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) {
+ func_remove(fp);
+ }
xfree(fp);
}
+/// Free all things that a function contains and free the function itself.
+///
+/// param[in] force When true, we are exiting.
+static void func_clear_free(ufunc_T *fp, bool force)
+{
+ func_clear(fp, force);
+ func_free(fp);
+}
+
/*
* Unreference a Function: decrement the reference count and free it when it
- * becomes zero. Only for numbered functions.
+ * becomes zero.
*/
void func_unref(char_u *name)
{
- ufunc_T *fp;
+ ufunc_T *fp = NULL;
- if (name != NULL && isdigit(*name)) {
- fp = find_func(name);
- if (fp == NULL) {
+ if (name == NULL || !func_name_refcount(name)) {
+ return;
+ }
+
+ fp = find_func(name);
+ if (fp == NULL && isdigit(*name)) {
#ifdef EXITFREE
- if (!entered_free_all_mem) {
- EMSG2(_(e_intern2), "func_unref()");
- }
+ if (!entered_free_all_mem) {
+ EMSG2(_(e_intern2), "func_unref()");
+ abort();
+ }
#else
EMSG2(_(e_intern2), "func_unref()");
+ abort();
#endif
- } else {
- user_func_unref(fp);
+ }
+ if (fp != NULL && --fp->uf_refcount <= 0) {
+ // Only delete it when it's not being used. Otherwise it's done
+ // when "uf_calls" becomes zero.
+ if (fp->uf_calls == 0) {
+ func_clear_free(fp, false);
}
}
}
-static void user_func_unref(ufunc_T *fp)
+/// Unreference a Function: decrement the reference count and free it when it
+/// becomes zero.
+void func_ptr_unref(ufunc_T *fp)
{
- if (--fp->uf_refcount <= 0) {
- // Only delete it when it's not being used. Otherwise it's done
+ if (fp != NULL && --fp->uf_refcount <= 0) {
+ // Only delete it when it's not being used. Otherwise it's done
// when "uf_calls" becomes zero.
if (fp->uf_calls == 0) {
- func_free(fp);
+ func_clear_free(fp, false);
}
}
}
-/*
- * Count a reference to a Function.
- */
+/// Count a reference to a Function.
void func_ref(char_u *name)
{
ufunc_T *fp;
- if (name != NULL && isdigit(*name)) {
- fp = find_func(name);
- if (fp == NULL)
- EMSG2(_(e_intern2), "func_ref()");
- else
- ++fp->uf_refcount;
+ if (name == NULL || !func_name_refcount(name)) {
+ return;
+ }
+ fp = find_func(name);
+ if (fp != NULL) {
+ (fp->uf_refcount)++;
+ } else if (isdigit(*name)) {
+ // Only give an error for a numbered function.
+ // Fail silently, when named or lambda function isn't found.
+ EMSG2(_(e_intern2), "func_ref()");
}
}
-/*
- * Call a user function.
- */
-static void
-call_user_func (
- ufunc_T *fp, /* pointer to function */
- int argcount, /* nr of args */
- typval_T *argvars, /* arguments */
- typval_T *rettv, /* return value */
- linenr_T firstline, /* first line of range */
- linenr_T lastline, /* last line of range */
- dict_T *selfdict /* Dictionary for "self" */
+/// Count a reference to a Function.
+void func_ptr_ref(ufunc_T *fp)
+{
+ if (fp != NULL) {
+ (fp->uf_refcount)++;
+ }
+}
+
+/// Call a user function.
+static void
+call_user_func(
+ ufunc_T *fp, // pointer to function
+ int argcount, // nr of args
+ typval_T *argvars, // arguments
+ typval_T *rettv, // return value
+ linenr_T firstline, // first line of range
+ linenr_T lastline, // last line of range
+ dict_T *selfdict // Dictionary for "self"
)
{
char_u *save_sourcing_name;
@@ -21906,6 +22623,7 @@ call_user_func (
dictitem_T *v;
int fixvar_idx = 0; /* index in fixvar[] */
int ai;
+ bool islambda = false;
char_u numbuf[NUMBUFLEN];
char_u *name;
proftime_T wait_start;
@@ -21943,14 +22661,21 @@ call_user_func (
fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0);
fc->dbg_tick = debug_tick;
- /*
- * Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
- * with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
- * each argument variable and saves a lot of time.
- */
- /*
- * Init l: variables.
- */
+ // Set up fields for closure.
+ fc->fc_refcount = 0;
+ fc->fc_copyID = 0;
+ ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1);
+ func_ptr_ref(fp);
+
+ if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
+ islambda = true;
+ }
+
+ // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
+ // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
+ // each argument variable and saves a lot of time.
+ //
+ // Init l: variables.
init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE);
if (selfdict != NULL) {
/* Set l:self to "selfdict". Use "name" to avoid a warning from
@@ -21992,23 +22717,26 @@ call_user_func (
fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT;
fc->l_varlist.lv_lock = VAR_FIXED;
- /*
- * Set a:firstline to "firstline" and a:lastline to "lastline".
- * Set a:name to named arguments.
- * Set a:N to the "..." arguments.
- */
+ // Set a:firstline to "firstline" and a:lastline to "lastline".
+ // Set a:name to named arguments.
+ // Set a:N to the "..." arguments.
add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "firstline",
- (varnumber_T)firstline);
+ (varnumber_T)firstline);
add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "lastline",
- (varnumber_T)lastline);
- for (int i = 0; i < argcount; ++i) {
+ (varnumber_T)lastline);
+ for (int i = 0; i < argcount; i++) {
+ bool addlocal = false;
+
ai = i - fp->uf_args.ga_len;
- if (ai < 0)
- /* named argument a:name */
+ if (ai < 0) {
+ // named argument a:name
name = FUNCARG(fp, i);
- else {
- /* "..." argument a:1, a:2, etc. */
- sprintf((char *)numbuf, "%d", ai + 1);
+ if (islambda) {
+ addlocal = true;
+ }
+ } else {
+ // "..." argument a:1, a:2, etc.
+ snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1);
name = numbuf;
}
if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) {
@@ -22019,13 +22747,21 @@ call_user_func (
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
}
STRCPY(v->di_key, name);
- hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v));
- /* Note: the values are copied directly to avoid alloc/free.
- * "argvars" must have VAR_FIXED for v_lock. */
+ // Note: the values are copied directly to avoid alloc/free.
+ // "argvars" must have VAR_FIXED for v_lock.
v->di_tv = argvars[i];
v->di_tv.v_lock = VAR_FIXED;
+ if (addlocal) {
+ // Named arguments can be accessed without the "a:" prefix in lambda
+ // expressions. Add to the l: dict.
+ copy_tv(&v->di_tv, &v->di_tv);
+ hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v));
+ } else {
+ hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v));
+ }
+
if (ai >= 0 && ai < MAX_FUNC_ARGS) {
list_append(&fc->l_varlist, &fc->l_listitems[ai]);
fc->l_listitems[ai].li_tv = argvars[i];
@@ -22211,20 +22947,21 @@ call_user_func (
current_funccal = fc->caller;
--depth;
- /* If the a:000 list and the l: and a: dicts are not referenced we can
- * free the funccall_T and what's in it. */
+ // If the a:000 list and the l: and a: dicts are not referenced and there
+ // is no closure using it, we can free the funccall_T and what's in it.
if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT
&& fc->l_vars.dv_refcount == DO_NOT_FREE_CNT
- && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) {
- free_funccal(fc, FALSE);
+ && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT
+ && fc->fc_refcount <= 0) {
+ free_funccal(fc, false);
} else {
hashitem_T *hi;
listitem_T *li;
int todo;
- /* "fc" is still in use. This can happen when returning "a:000" or
- * assigning "l:" to a global variable.
- * Link "fc" in the list for garbage collection later. */
+ // "fc" is still in use. This can happen when returning "a:000",
+ // assigning "l:" to a global variable or defining a closure.
+ // Link "fc" in the list for garbage collection later.
fc->caller = previous_funccal;
previous_funccal = fc;
@@ -22243,9 +22980,9 @@ call_user_func (
copy_tv(&li->li_tv, &li->li_tv);
}
- if (--fp->uf_calls <= 0 && isdigit(*fp->uf_name) && fp->uf_refcount <= 0) {
+ if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) {
// Function was unreferenced while being used, free it now.
- func_free(fp);
+ func_clear_free(fp, false);
}
// restore search patterns and redo buffer
if (did_save_redo) {
@@ -22254,15 +22991,46 @@ call_user_func (
restore_search_patterns();
}
-/*
- * Return TRUE if items in "fc" do not have "copyID". That means they are not
- * referenced from anywhere that is in use.
- */
+/// Unreference "fc": decrement the reference count and free it when it
+/// becomes zero. "fp" is detached from "fc".
+///
+/// @param[in] force When true, we are exiting.
+static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)
+{
+ funccall_T **pfc;
+ int i;
+
+ if (fc == NULL) {
+ return;
+ }
+
+ if (--fc->fc_refcount <= 0 && (force || (
+ fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT
+ && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT
+ && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT))) {
+ for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) {
+ if (fc == *pfc) {
+ *pfc = fc->caller;
+ free_funccal(fc, true);
+ return;
+ }
+ }
+ }
+ for (i = 0; i < fc->fc_funcs.ga_len; i++) {
+ if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) {
+ ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL;
+ }
+ }
+}
+
+/// @return true if items in "fc" do not have "copyID". That means they are not
+/// referenced from anywhere that is in use.
static int can_free_funccal(funccall_T *fc, int copyID)
{
return fc->l_varlist.lv_copyID != copyID
&& fc->l_vars.dv_copyID != copyID
- && fc->l_avars.dv_copyID != copyID;
+ && fc->l_avars.dv_copyID != copyID
+ && fc->fc_copyID != copyID;
}
/*
@@ -22276,18 +23044,34 @@ free_funccal (
{
listitem_T *li;
- /* The a: variables typevals may not have been allocated, only free the
- * allocated variables. */
+ for (int i = 0; i < fc->fc_funcs.ga_len; i++) {
+ ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
+
+ // When garbage collecting a funccall_T may be freed before the
+ // function that references it, clear its uf_scoped field.
+ // The function may have been redefined and point to another
+ // funccal_T, don't clear it then.
+ if (fp != NULL && fp->uf_scoped == fc) {
+ fp->uf_scoped = NULL;
+ }
+ }
+ ga_clear(&fc->fc_funcs);
+
+ // The a: variables typevals may not have been allocated, only free the
+ // allocated variables.
vars_clear_ext(&fc->l_avars.dv_hashtab, free_val);
/* free all l: variables */
vars_clear(&fc->l_vars.dv_hashtab);
- /* Free the a:000 variables if they were allocated. */
- if (free_val)
- for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next)
+ // Free the a:000 variables if they were allocated.
+ if (free_val) {
+ for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) {
clear_tv(&li->li_tv);
+ }
+ }
+ func_ptr_unref(fc->func);
xfree(fc);
}
@@ -22606,6 +23390,72 @@ static var_flavour_T var_flavour(char_u *varname)
}
}
+/// Search hashitem in parent scope.
+hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
+{
+ if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
+ return NULL;
+ }
+
+ funccall_T *old_current_funccal = current_funccal;
+ hashitem_T *hi = NULL;
+ const size_t namelen = strlen(name);
+ const char *varname;
+
+ // Search in parent scope which is possible to reference from lambda
+ current_funccal = current_funccal->func->uf_scoped;
+ while (current_funccal != NULL) {
+ hashtab_T *ht = find_var_ht(name, namelen, &varname);
+ if (ht != NULL && *varname != NUL) {
+ hi = hash_find_len(ht, varname, namelen - (varname - name));
+ if (!HASHITEM_EMPTY(hi)) {
+ *pht = ht;
+ break;
+ }
+ }
+ if (current_funccal == current_funccal->func->uf_scoped) {
+ break;
+ }
+ current_funccal = current_funccal->func->uf_scoped;
+ }
+ current_funccal = old_current_funccal;
+
+ return hi;
+}
+
+/// Search variable in parent scope.
+dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen,
+ int no_autoload)
+{
+ if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
+ return NULL;
+ }
+
+ dictitem_T *v = NULL;
+ funccall_T *old_current_funccal = current_funccal;
+ const char *varname;
+
+ // Search in parent scope which is possible to reference from lambda
+ current_funccal = current_funccal->func->uf_scoped;
+ while (current_funccal) {
+ hashtab_T *ht = find_var_ht(name, namelen, &varname);
+ if (ht != NULL && *varname != NUL) {
+ v = find_var_in_ht(ht, *name, varname,
+ namelen - (size_t)(varname - name), no_autoload);
+ if (v != NULL) {
+ break;
+ }
+ }
+ if (current_funccal == current_funccal->func->uf_scoped) {
+ break;
+ }
+ current_funccal = current_funccal->func->uf_scoped;
+ }
+ current_funccal = old_current_funccal;
+
+ return v;
+}
+
/// Iterate over global variables
///
/// @warning No modifications to global variable dictionary must be performed
@@ -23005,7 +23855,7 @@ repeat:
sub = vim_strnsave(s, (int)(p - s));
str = vim_strnsave(*fnamep, *fnamelen);
*usedlen = (size_t)(p + 1 - src);
- s = do_string_sub(str, pat, sub, flags);
+ s = do_string_sub(str, pat, sub, NULL, flags);
*fnamep = s;
*fnamelen = STRLEN(s);
xfree(*bufp);
@@ -23041,12 +23891,12 @@ repeat:
return valid;
}
-/*
- * Perform a substitution on "str" with pattern "pat" and substitute "sub".
- * "flags" can be "g" to do a global substitute.
- * Returns an allocated string, NULL for error.
- */
-char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)
+/// Perform a substitution on "str" with pattern "pat" and substitute "sub".
+/// When "sub" is NULL "expr" is used, must be a VAR_FUNC or VAR_PARTIAL.
+/// "flags" can be "g" to do a global substitute.
+/// Returns an allocated string, NULL for error.
+char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub,
+ typval_T *expr, char_u *flags)
{
int sublen;
regmatch_T regmatch;
@@ -23084,23 +23934,21 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)
zero_width = regmatch.startp[0];
}
- /*
- * Get some space for a temporary buffer to do the substitution
- * into. It will contain:
- * - The text up to where the match is.
- * - The substituted text.
- * - The text after the match.
- */
- sublen = vim_regsub(&regmatch, sub, tail, FALSE, TRUE, FALSE);
+ // Get some space for a temporary buffer to do the substitution
+ // into. It will contain:
+ // - The text up to where the match is.
+ // - The substituted text.
+ // - The text after the match.
+ sublen = vim_regsub(&regmatch, sub, expr, tail, false, true, false);
ga_grow(&ga, (int)((end - tail) + sublen -
(regmatch.endp[0] - regmatch.startp[0])));
/* copy the text up to where the match is */
int i = (int)(regmatch.startp[0] - tail);
memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i);
- /* add the substituted text */
- (void)vim_regsub(&regmatch, sub, (char_u *)ga.ga_data
- + ga.ga_len + i, TRUE, TRUE, FALSE);
+ // add the substituted text
+ (void)vim_regsub(&regmatch, sub, expr, (char_u *)ga.ga_data
+ + ga.ga_len + i, true, true, false);
ga.ga_len += i + sublen - 1;
tail = regmatch.endp[0];
if (*tail == NUL)
@@ -23117,11 +23965,12 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)
char_u *ret = vim_strsave(ga.ga_data == NULL ? str : (char_u *)ga.ga_data);
ga_clear(&ga);
- if (p_cpo == empty_option)
+ if (p_cpo == empty_option) {
p_cpo = save_cpo;
- else
- /* Darn, evaluating {sub} expression changed the value. */
+ } else {
+ // Darn, evaluating {sub} expression or {expr} changed the value.
free_string_option(save_cpo);
+ }
return ret;
}
@@ -23487,6 +24336,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
&rettv,
2,
argvars,
+ NULL,
curwin->w_cursor.lnum,
curwin->w_cursor.lnum,
&dummy,
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 630e309442..57fee5c5a2 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -12,39 +12,6 @@
// All user-defined functions are found in this hashtable.
extern hashtab_T func_hashtab;
-
-// Structure to hold info for a user function.
-typedef struct ufunc ufunc_T;
-
-struct ufunc {
- int uf_varargs; ///< variable nr of arguments
- int uf_flags;
- int uf_calls; ///< nr of active calls
- garray_T uf_args; ///< arguments
- garray_T uf_lines; ///< function lines
- int uf_profiling; ///< true when func is being profiled
- // Profiling the function as a whole.
- int uf_tm_count; ///< nr of calls
- proftime_T uf_tm_total; ///< time spent in function + children
- proftime_T uf_tm_self; ///< time spent in function itself
- proftime_T uf_tm_children; ///< time spent in children this call
- // Profiling the function per line.
- int *uf_tml_count; ///< nr of times line was executed
- proftime_T *uf_tml_total; ///< time spent in a line + children
- proftime_T *uf_tml_self; ///< time spent in a line itself
- proftime_T uf_tml_start; ///< start time for current line
- proftime_T uf_tml_children; ///< time spent in children for this line
- proftime_T uf_tml_wait; ///< start wait time for current line
- int uf_tml_idx; ///< index of line being timed; -1 if none
- int uf_tml_execed; ///< line being timed was executed
- scid_T uf_script_ID; ///< ID of script where function was defined,
- // used for s: variables
- int uf_refcount; ///< for numbered function: reference count
- char_u uf_name[1]; ///< name of function (actually longer); can
- // start with <SNR>123_ (<SNR> is K_SPECIAL
- // KS_EXTRA KE_SNR)
-};
-
// From user function to hashitem and back.
EXTERN ufunc_T dumuf;
#define UF2HIKEY(fp) ((fp)->uf_name)
@@ -127,6 +94,7 @@ typedef enum {
VV__NULL_LIST, // List with NULL value. For test purposes only.
VV__NULL_DICT, // Dictionary with NULL value. For test purposes only.
VV_VIM_DID_ENTER,
+ VV_TESTING,
VV_TYPE_NUMBER,
VV_TYPE_STRING,
VV_TYPE_FUNC,
@@ -156,8 +124,8 @@ extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1];
#undef LAST_MSGPACK_TYPE
-/// Maximum number of function arguments
-#define MAX_FUNC_ARGS 20
+typedef int (*ArgvFunc)(int current_argcount, typval_T *argv,
+ int called_func_argcount);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.h.generated.h"
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 964b061e95..fa19ff209e 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -103,6 +103,7 @@ return {
foldtext={},
foldtextresult={args=1},
foreground={},
+ funcref={args={1, 3}},
['function']={args={1, 3}},
garbagecollect={args={0, 1}},
get={args={2, 3}},
@@ -125,11 +126,11 @@ return {
getftime={args=1},
getftype={args=1},
getline={args={1, 2}},
- getloclist={args=1, func='f_getqflist'},
+ getloclist={args={1, 2}},
getmatches={},
getpid={},
getpos={args=1},
- getqflist={},
+ getqflist={args={0, 1}},
getreg={args={0, 3}},
getregtype={args={0, 1}},
gettabinfo={args={0, 1}},
@@ -303,6 +304,7 @@ return {
tanh={args=1, func="float_op_wrapper", data="&tanh"},
tempname={},
termopen={args={1, 2}},
+ test_garbagecollect_now={},
timer_start={args={2,3}},
timer_stop={args=1},
tolower={args=1},
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index 34f88cbc98..4ff5589887 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -344,7 +344,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
case VAR_PARTIAL: {
partial_T *const pt = tv->vval.v_partial;
(void)pt;
- TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : pt->pt_name));
+ TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt)));
_mp_push(*mpstack, ((MPConvStackVal) {
.type = kMPConvPartial,
.tv = tv,
diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h
index 616c89671b..fb2822b851 100644
--- a/src/nvim/eval_defs.h
+++ b/src/nvim/eval_defs.h
@@ -6,6 +6,9 @@
#include "nvim/hashtab.h"
#include "nvim/lib/queue.h"
+#include "nvim/garray.h" // for garray_T
+#include "nvim/profile.h" // for proftime_T
+#include "nvim/pos.h" // for linenr_T
typedef int varnumber_T;
typedef double float_T;
@@ -27,8 +30,8 @@ typedef enum {
/// Variable lock status for typval_T.v_lock
typedef enum {
VAR_UNLOCKED = 0, ///< Not locked.
- VAR_LOCKED, ///< User lock, can be unlocked.
- VAR_FIXED, ///< Locked forever.
+ VAR_LOCKED = 1, ///< User lock, can be unlocked.
+ VAR_FIXED = 2, ///< Locked forever.
} VarLockStatus;
/// VimL variable types, for use in typval_T.v_type
@@ -90,29 +93,33 @@ struct listwatch_S {
* Structure to hold info about a list.
*/
struct listvar_S {
- listitem_T *lv_first; /* first item, NULL if none */
- listitem_T *lv_last; /* last item, NULL if none */
- int lv_refcount; /* reference count */
- int lv_len; /* number of items */
- listwatch_T *lv_watch; /* first watcher, NULL if none */
- int lv_idx; /* cached index of an item */
- listitem_T *lv_idx_item; /* when not NULL item at index "lv_idx" */
- int lv_copyID; /* ID used by deepcopy() */
- list_T *lv_copylist; /* copied list used by deepcopy() */
- char lv_lock; /* zero, VAR_LOCKED, VAR_FIXED */
- list_T *lv_used_next; /* next list in used lists list */
- list_T *lv_used_prev; /* previous list in used lists list */
+ listitem_T *lv_first; ///< First item, NULL if none.
+ listitem_T *lv_last; ///< Last item, NULL if none.
+ int lv_refcount; ///< Reference count.
+ int lv_len; ///< Number of items.
+ listwatch_T *lv_watch; ///< First watcher, NULL if none.
+ int lv_idx; ///< Index of a cached item, used for optimising repeated l[idx].
+ listitem_T *lv_idx_item; ///< When not NULL item at index "lv_idx".
+ int lv_copyID; ///< ID used by deepcopy().
+ list_T *lv_copylist; ///< Copied list used by deepcopy().
+ VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED.
+ list_T *lv_used_next; ///< next list in used lists list.
+ list_T *lv_used_prev; ///< Previous list in used lists list.
};
-/*
- * Structure to hold an item of a Dictionary.
- * Also used for a variable.
- * The key is copied into "di_key" to avoid an extra alloc/free for it.
- */
+// Static list with 10 items. Use init_static_list() to initialize.
+typedef struct {
+ list_T sl_list; // must be first
+ listitem_T sl_items[10];
+} staticList10_T;
+
+// Structure to hold an item of a Dictionary.
+// Also used for a variable.
+// The key is copied into "di_key" to avoid an extra alloc/free for it.
struct dictitem_S {
- typval_T di_tv; /* type and value of the variable */
- char_u di_flags; /* flags (only used for variable) */
- char_u di_key[1]; /* key (actually longer!) */
+ typval_T di_tv; ///< type and value of the variable
+ char_u di_flags; ///< flags (only used for variable)
+ char_u di_key[1]; ///< key (actually longer!)
};
typedef struct dictitem_S dictitem_T;
@@ -147,9 +154,88 @@ struct dictvar_S {
QUEUE watchers; ///< Dictionary key watchers set by user code.
};
+typedef int scid_T; // script ID
+typedef struct funccall_S funccall_T;
+
+// Structure to hold info for a user function.
+typedef struct ufunc ufunc_T;
+
+struct ufunc {
+ int uf_varargs; ///< variable nr of arguments
+ int uf_flags;
+ int uf_calls; ///< nr of active calls
+ bool uf_cleared; ///< func_clear() was already called
+ garray_T uf_args; ///< arguments
+ garray_T uf_lines; ///< function lines
+ int uf_profiling; ///< true when func is being profiled
+ // Profiling the function as a whole.
+ int uf_tm_count; ///< nr of calls
+ proftime_T uf_tm_total; ///< time spent in function + children
+ proftime_T uf_tm_self; ///< time spent in function itself
+ proftime_T uf_tm_children; ///< time spent in children this call
+ // Profiling the function per line.
+ int *uf_tml_count; ///< nr of times line was executed
+ proftime_T *uf_tml_total; ///< time spent in a line + children
+ proftime_T *uf_tml_self; ///< time spent in a line itself
+ proftime_T uf_tml_start; ///< start time for current line
+ proftime_T uf_tml_children; ///< time spent in children for this line
+ proftime_T uf_tml_wait; ///< start wait time for current line
+ int uf_tml_idx; ///< index of line being timed; -1 if none
+ int uf_tml_execed; ///< line being timed was executed
+ scid_T uf_script_ID; ///< ID of script where function was defined,
+ // used for s: variables
+ int uf_refcount; ///< reference count, see func_name_refcount()
+ funccall_T *uf_scoped; ///< l: local variables for closure
+ char_u uf_name[1]; ///< name of function (actually longer); can
+ // start with <SNR>123_ (<SNR> is K_SPECIAL
+ // KS_EXTRA KE_SNR)
+};
+
+/// Maximum number of function arguments
+#define MAX_FUNC_ARGS 20
+#define VAR_SHORT_LEN 20 // short variable name length
+#define FIXVAR_CNT 12 // number of fixed variables
+
+// structure to hold info for a function that is currently being executed.
+struct funccall_S {
+ ufunc_T *func; ///< function being called
+ int linenr; ///< next line to be executed
+ int returned; ///< ":return" used
+ struct { ///< fixed variables for arguments
+ dictitem_T var; ///< variable (without room for name)
+ char_u room[VAR_SHORT_LEN]; ///< room for the name
+ } fixvar[FIXVAR_CNT];
+ dict_T l_vars; ///< l: local function variables
+ dictitem_T l_vars_var; ///< variable for l: scope
+ dict_T l_avars; ///< a: argument variables
+ dictitem_T l_avars_var; ///< variable for a: scope
+ list_T l_varlist; ///< list for a:000
+ listitem_T l_listitems[MAX_FUNC_ARGS]; ///< listitems for a:000
+ typval_T *rettv; ///< return value
+ linenr_T breakpoint; ///< next line with breakpoint or zero
+ int dbg_tick; ///< debug_tick when breakpoint was set
+ int level; ///< top nesting level of executed function
+ proftime_T prof_child; ///< time spent in a child
+ funccall_T *caller; ///< calling function or NULL
+ int fc_refcount; ///< number of user functions that reference
+ // this funccal
+ int fc_copyID; ///< for garbage collection
+ garray_T fc_funcs; ///< list of ufunc_T* which keep a reference
+ // to "func"
+};
+
+// structure used by trans_function_name()
+typedef struct {
+ dict_T *fd_dict; ///< Dictionary used.
+ char_u *fd_newkey; ///< New key in "dict" in allocated memory.
+ dictitem_T *fd_di; ///< Dictionary item used.
+} funcdict_T;
+
struct partial_S {
int pt_refcount; ///< Reference count.
- char_u *pt_name; ///< Function name.
+ char_u *pt_name; ///< Function name; when NULL use pt_func->name.
+ ufunc_T *pt_func; ///< Function pointer; when NULL lookup function
+ ///< with pt_name.
bool pt_auto; ///< when true the partial was created for using
///< dict.member in handle_subscript().
int pt_argc; ///< Number of arguments.
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index a03878dec2..159e027793 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -1131,11 +1131,12 @@ static void do_filter(
*/
++no_wait_return; /* don't call wait_return() while busy */
if (itmp != NULL && buf_write(curbuf, itmp, NULL, line1, line2, eap,
- FALSE, FALSE, FALSE, TRUE) == FAIL) {
- msg_putchar('\n'); /* keep message from buf_write() */
- --no_wait_return;
- if (!aborting())
- (void)EMSG2(_(e_notcreate), itmp); /* will call wait_return */
+ false, false, false, true) == FAIL) {
+ msg_putchar('\n'); // Keep message from buf_write().
+ no_wait_return--;
+ if (!aborting()) {
+ EMSG2(_("E482: Can't create file %s"), itmp); // Will call wait_return.
+ }
goto filterend;
}
if (curbuf != old_curbuf)
@@ -6138,7 +6139,7 @@ void ex_substitute(exarg_T *eap)
// restore it so that undotree() is identical before/after the preview.
curbuf->b_u_newhead = save_b_u_newhead;
curbuf->b_u_time_cur = save_b_u_time_cur;
- curbuf->b_changedtick = save_changedtick;
+ buf_set_changedtick(curbuf, save_changedtick);
}
if (buf_valid(preview_buf)) {
// XXX: Must do this *after* u_undo_and_forget(), why?
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index a471830c56..b7c6fd41f2 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1327,8 +1327,9 @@ int using_script(void)
void before_blocking(void)
{
updatescript(0);
- if (may_garbage_collect)
- garbage_collect();
+ if (may_garbage_collect) {
+ garbage_collect(false);
+ }
}
/*
@@ -1366,10 +1367,11 @@ int vgetc(void)
char_u buf[MB_MAXBYTES + 1];
int i;
- /* Do garbage collection when garbagecollect() was called previously and
- * we are now at the toplevel. */
- if (may_garbage_collect && want_garbage_collect)
- garbage_collect();
+ // Do garbage collection when garbagecollect() was called previously and
+ // we are now at the toplevel.
+ if (may_garbage_collect && want_garbage_collect) {
+ garbage_collect(false);
+ }
/*
* If a character was put back with vungetc, it was already processed.
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 54551484ee..07ea045c13 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -1150,7 +1150,6 @@ EXTERN char_u e_noprev[] INIT(= N_("E34: No previous command"));
EXTERN char_u e_noprevre[] INIT(= N_("E35: No previous regular expression"));
EXTERN char_u e_norange[] INIT(= N_("E481: No range allowed"));
EXTERN char_u e_noroom[] INIT(= N_("E36: Not enough room"));
-EXTERN char_u e_notcreate[] INIT(= N_("E482: Can't create file %s"));
EXTERN char_u e_notmp[] INIT(= N_("E483: Can't get temp file name"));
EXTERN char_u e_notopen[] INIT(= N_("E484: Can't open file %s"));
EXTERN char_u e_notread[] INIT(= N_("E485: Can't read file %s"));
@@ -1172,11 +1171,7 @@ EXTERN char_u e_loclist[] INIT(= N_("E776: No location list"));
EXTERN char_u e_re_damg[] INIT(= N_("E43: Damaged match string"));
EXTERN char_u e_re_corr[] INIT(= N_("E44: Corrupted regexp program"));
EXTERN char_u e_readonly[] INIT(= N_(
- "E45: 'readonly' option is set (add ! to override)"));
-EXTERN char_u e_readonlyvar[] INIT(= N_(
- "E46: Cannot change read-only variable \"%s\""));
-EXTERN char_u e_readonlysbx[] INIT(= N_(
- "E794: Cannot set variable in the sandbox: \"%s\""));
+ "E45: 'readonly' option is set (add ! to override)"));
EXTERN char_u e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
EXTERN char_u e_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
EXTERN char_u e_secure[] INIT(= N_("E523: Not allowed here"));
@@ -1236,6 +1231,9 @@ EXTERN char *ignoredp;
EXTERN bool in_free_unref_items INIT(= false);
+// Used for checking if local variables or arguments used in a lambda.
+EXTERN int *eval_lavars_used INIT(= NULL);
+
// If a msgpack-rpc channel should be started over stdin/stdout
EXTERN bool embedded_mode INIT(= false);
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 88c1990786..d7baa7acfa 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -576,9 +576,9 @@ void getout(int exitval)
buf_T *buf = wp->w_buffer;
if (buf->b_changedtick != -1) {
apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname,
- buf->b_fname, FALSE, buf);
- buf->b_changedtick = -1; /* note that we did it already */
- /* start all over, autocommands may mess up the lists */
+ buf->b_fname, false, buf);
+ buf_set_changedtick(buf, -1); // note that we did it already
+ // start all over, autocommands may mess up the lists
next_tp = first_tabpage;
break;
}
@@ -624,8 +624,9 @@ void getout(int exitval)
iconv_end();
#endif
cs_end();
- if (garbage_collect_at_exit)
- garbage_collect();
+ if (garbage_collect_at_exit) {
+ garbage_collect(false);
+ }
mch_exit(exitval);
}
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 91dab16e27..b67f550358 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -1180,7 +1180,7 @@ void ml_recover(void)
* empty. Don't set the modified flag then. */
if (!(curbuf->b_ml.ml_line_count == 2 && *ml_get(1) == NUL)) {
changed_int();
- ++curbuf->b_changedtick;
+ buf_set_changedtick(curbuf, curbuf->b_changedtick + 1);
}
} else {
for (idx = 1; idx <= lnum; ++idx) {
@@ -1190,7 +1190,7 @@ void ml_recover(void)
xfree(p);
if (i != 0) {
changed_int();
- ++curbuf->b_changedtick;
+ buf_set_changedtick(curbuf, curbuf->b_changedtick + 1);
break;
}
}
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 51e0dd926e..b593936d7b 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -625,18 +625,6 @@ void free_all_mem(void)
/* Destroy all windows. Must come before freeing buffers. */
win_free_all();
- /* Free all buffers. Reset 'autochdir' to avoid accessing things that
- * were freed already. */
- p_acd = false;
- for (buf = firstbuf; buf != NULL; ) {
- bufref_T bufref;
- set_bufref(&bufref, buf);
- nextbuf = buf->b_next;
- close_buffer(NULL, buf, DOBUF_WIPE, false);
- // Didn't work, try next one.
- buf = bufref_valid(&bufref) ? nextbuf : firstbuf;
- }
-
free_cmdline_buf();
/* Clear registers. */
@@ -660,6 +648,20 @@ void free_all_mem(void)
eval_clear();
+ // Free all buffers. Reset 'autochdir' to avoid accessing things that
+ // were freed already.
+ // Must be after eval_clear to avoid it trying to access b:changedtick after
+ // freeing it.
+ p_acd = false;
+ for (buf = firstbuf; buf != NULL; ) {
+ bufref_T bufref;
+ set_bufref(&bufref, buf);
+ nextbuf = buf->b_next;
+ close_buffer(NULL, buf, DOBUF_WIPE, false);
+ // Didn't work, try next one.
+ buf = bufref_valid(&bufref) ? nextbuf : firstbuf;
+ }
+
/* screenlines (can't display anything now!) */
free_screenlines();
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index eee3c81acf..d49d1d8a21 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -51,6 +51,7 @@
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/event/stream.h"
+#include "nvim/buffer.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "misc1.c.generated.h"
@@ -1784,7 +1785,7 @@ void changed(void)
}
changed_int();
}
- ++curbuf->b_changedtick;
+ buf_set_changedtick(curbuf, curbuf->b_changedtick + 1);
}
/*
@@ -2143,7 +2144,7 @@ unchanged (
redraw_tabline = TRUE;
need_maketitle = TRUE; /* set window title later */
}
- ++buf->b_changedtick;
+ buf_set_changedtick(buf, buf->b_changedtick + 1);
}
/*
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 98636263b9..71db7506ad 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -321,14 +321,15 @@ static void parse_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data,
if (eof) {
close_channel(channel);
- call_set_error(channel, "Channel was closed by the client");
+ char buf[256];
+ snprintf(buf, sizeof(buf), "channel %" PRIu64 " was closed by the client",
+ channel->id);
+ call_set_error(channel, buf);
goto end;
}
size_t count = rbuffer_size(rbuf);
- DLOG("Feeding the msgpack parser with %u bytes of data from Stream(%p)",
- count,
- stream);
+ DLOG("parsing %u bytes of msgpack data from Stream(%p)", count, stream);
// Feed the unpacker with data
msgpack_unpacker_reserve_buffer(channel->unpacker, count);
@@ -350,11 +351,9 @@ static void parse_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data,
complete_call(&unpacked.data, channel);
} else {
char buf[256];
- snprintf(buf,
- sizeof(buf),
- "Channel %" PRIu64 " returned a response that doesn't have "
- "a matching request id. Ensure the client is properly "
- "synchronized",
+ snprintf(buf, sizeof(buf),
+ "channel %" PRIu64 " sent a response without a matching "
+ "request id. Ensure the client is properly synchronized",
channel->id);
call_set_error(channel, buf);
}
@@ -406,7 +405,7 @@ static void handle_request(Channel *channel, msgpack_object *request)
&out_buffer))) {
char buf[256];
snprintf(buf, sizeof(buf),
- "Channel %" PRIu64 " sent an invalid message, closed.",
+ "channel %" PRIu64 " sent an invalid message, closed.",
channel->id);
call_set_error(channel, buf);
}
@@ -716,7 +715,7 @@ static void complete_call(msgpack_object *obj, Channel *channel)
static void call_set_error(Channel *channel, char *msg)
{
- ELOG("msgpack-rpc: %s", msg);
+ ELOG("RPC: %s", msg);
for (size_t i = 0; i < kv_size(channel->call_stack); i++) {
ChannelCallFrame *frame = kv_A(channel->call_stack, i);
frame->returned = true;
@@ -789,10 +788,10 @@ static void decref(Channel *channel)
}
#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL
-#define REQ "[request] "
-#define RES "[response] "
-#define NOT "[notification] "
-#define ERR "[error] "
+#define REQ "[request] "
+#define RES "[response] "
+#define NOT "[notify] "
+#define ERR "[error] "
// Cannot define array with negative offsets, so this one is needed to be added
// to MSGPACK_UNPACK_\* values.
@@ -810,7 +809,7 @@ static void log_server_msg(uint64_t channel_id,
{
msgpack_unpacked unpacked;
msgpack_unpacked_init(&unpacked);
- DLOGN("[msgpack-rpc] nvim -> client(%" PRIu64 ") ", channel_id);
+ DLOGN("RPC ->ch %" PRIu64 ": ", channel_id);
const msgpack_unpack_return result =
msgpack_unpack_next(&unpacked, packed->data, packed->size, NULL);
switch (result) {
@@ -847,7 +846,7 @@ static void log_client_msg(uint64_t channel_id,
bool is_request,
msgpack_object msg)
{
- DLOGN("[msgpack-rpc] client(%" PRIu64 ") -> nvim ", channel_id);
+ DLOGN("RPC <-ch %" PRIu64 ": ", channel_id);
log_lock();
FILE *f = open_log_file();
fprintf(f, is_request ? REQ : RES);
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 641658532d..dbd8e153a8 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -2460,11 +2460,11 @@ do_mouse (
};
typval_T rettv;
int doesrange;
- (void) call_func((char_u *) tab_page_click_defs[mouse_col].func,
- (int) strlen(tab_page_click_defs[mouse_col].func),
- &rettv, ARRAY_SIZE(argv), argv,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &doesrange, true, NULL, NULL);
+ (void)call_func((char_u *)tab_page_click_defs[mouse_col].func,
+ (int)strlen(tab_page_click_defs[mouse_col].func),
+ &rettv, ARRAY_SIZE(argv), argv, NULL,
+ curwin->w_cursor.lnum, curwin->w_cursor.lnum,
+ &doesrange, true, NULL, NULL);
clear_tv(&rettv);
break;
}
@@ -2696,8 +2696,6 @@ do_mouse (
else if (((mod_mask & MOD_MASK_CTRL)
|| (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)
&& bt_quickfix(curbuf)) {
- if (State & INSERT)
- stuffcharReadbuff(Ctrl_O);
if (curwin->w_llist_ref == NULL) { // quickfix window
do_cmdline_cmd(".cc");
} else { // location list window
@@ -7420,27 +7418,23 @@ static void nv_esc(cmdarg_T *cap)
restart_edit = 'a';
}
-/*
- * Handle "A", "a", "I", "i" and <Insert> commands.
- */
+/// Handle "A", "a", "I", "i" and <Insert> commands.
static void nv_edit(cmdarg_T *cap)
{
- /* <Insert> is equal to "i" */
- if (cap->cmdchar == K_INS || cap->cmdchar == K_KINS)
+ // <Insert> is equal to "i"
+ if (cap->cmdchar == K_INS || cap->cmdchar == K_KINS) {
cap->cmdchar = 'i';
+ }
- /* in Visual mode "A" and "I" are an operator */
- if (VIsual_active && (cap->cmdchar == 'A' || cap->cmdchar == 'I'))
+ // in Visual mode "A" and "I" are an operator
+ if (VIsual_active && (cap->cmdchar == 'A' || cap->cmdchar == 'I')) {
v_visop(cap);
-
- /* in Visual mode and after an operator "a" and "i" are for text objects */
- else if ((cap->cmdchar == 'a' || cap->cmdchar == 'i')
- && (cap->oap->op_type != OP_NOP
- || VIsual_active
- )) {
+ // in Visual mode and after an operator "a" and "i" are for text objects
+ } else if ((cap->cmdchar == 'a' || cap->cmdchar == 'i')
+ && (cap->oap->op_type != OP_NOP || VIsual_active)) {
nv_object(cap);
- } else if (!curbuf->b_p_ma && !p_im) {
- /* Only give this error when 'insertmode' is off. */
+ } else if (!curbuf->b_p_ma && !p_im && !curbuf->terminal) {
+ // Only give this error when 'insertmode' is off.
EMSG(_(e_modifiable));
clearop(cap->oap);
} else if (!checkclearopq(cap->oap)) {
diff --git a/src/nvim/option.c b/src/nvim/option.c
index e697ab3f51..8990b59f57 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -1,24 +1,18 @@
-/*
- * Code to handle user-settable options. This is all pretty much table-
- * driven. Checklist for adding a new option:
- * - Put it in the options array below (copy an existing entry).
- * - For a global option: Add a variable for it in option_defs.h.
- * - For a buffer or window local option:
- * - Add a PV_XX entry to the enum below.
- * - Add a variable to the window or buffer struct in buffer_defs.h.
- * - For a window option, add some code to copy_winopt().
- * - For a buffer option, add some code to buf_copy_options().
- * - For a buffer string option, add code to check_buf_options().
- * - If it's a numeric option, add any necessary bounds checks to do_set().
- * - If it's a list of flags, add some code in do_set(), search for WW_ALL.
- * - When adding an option with expansion (P_EXPAND), but with a different
- * default for Vi and Vim (no P_VI_DEF), add some code at VIMEXP.
- * - Add documentation! One line in doc/help.txt, full description in
- * options.txt, and any other related places.
- * - Add an entry in runtime/optwin.vim.
- * When making changes:
- * - Adjust the help for the option in doc/option.txt.
- */
+// User-settable options. Checklist for adding a new option:
+// - Put it in options.lua
+// - For a global option: Add a variable for it in option_defs.h.
+// - For a buffer or window local option:
+// - Add a BV_XX or WV_XX entry to option_defs.h
+// - Add a variable to the window or buffer struct in buffer_defs.h.
+// - For a window option, add some code to copy_winopt().
+// - For a buffer option, add some code to buf_copy_options().
+// - For a buffer string option, add code to check_buf_options().
+// - If it's a numeric option, add any necessary bounds checks to do_set().
+// - If it's a list of flags, add some code in do_set(), search for WW_ALL.
+// - When adding an option with expansion (P_EXPAND), but with a different
+// default for Vi and Vim (no P_VI_DEF), add some code at VIMEXP.
+// - Add documentation! doc/options.txt, and any other related places.
+// - Add an entry in runtime/optwin.vim.
#define IN_OPTION_C
#include <assert.h>
@@ -161,6 +155,7 @@ static long p_ts;
static long p_tw;
static int p_udf;
static long p_wm;
+static long p_scbk;
static char_u *p_keymap;
/* Saved values for when 'bin' is set. */
@@ -4201,7 +4196,19 @@ set_num_option (
FOR_ALL_TAB_WINDOWS(tp, wp) {
check_colorcolumn(wp);
}
-
+ } else if (pp == &curbuf->b_p_scbk) {
+ // 'scrollback'
+ if (!curbuf->terminal) {
+ errmsg = e_invarg;
+ curbuf->b_p_scbk = -1;
+ } else {
+ if (curbuf->b_p_scbk < -1 || curbuf->b_p_scbk > 100000) {
+ errmsg = e_invarg;
+ curbuf->b_p_scbk = 1000;
+ }
+ // Force the scrollback to take effect.
+ terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX);
+ }
}
/*
@@ -5426,6 +5433,7 @@ static char_u *get_varp(vimoption_T *p)
case PV_PI: return (char_u *)&(curbuf->b_p_pi);
case PV_QE: return (char_u *)&(curbuf->b_p_qe);
case PV_RO: return (char_u *)&(curbuf->b_p_ro);
+ case PV_SCBK: return (char_u *)&(curbuf->b_p_scbk);
case PV_SI: return (char_u *)&(curbuf->b_p_si);
case PV_STS: return (char_u *)&(curbuf->b_p_sts);
case PV_SUA: return (char_u *)&(curbuf->b_p_sua);
@@ -5636,6 +5644,7 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_ai = p_ai;
buf->b_p_ai_nopaste = p_ai_nopaste;
buf->b_p_sw = p_sw;
+ buf->b_p_scbk = -1;
buf->b_p_tw = p_tw;
buf->b_p_tw_nopaste = p_tw_nopaste;
buf->b_p_tw_nobin = p_tw_nobin;
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 602497461d..9c6393e014 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -601,11 +601,14 @@ EXTERN int p_tbs; ///< 'tagbsearch'
EXTERN char_u *p_tc; ///< 'tagcase'
EXTERN unsigned tc_flags; ///< flags from 'tagcase'
#ifdef IN_OPTION_C
-static char *(p_tc_values[]) = { "followic", "ignore", "match", NULL };
+static char *(p_tc_values[]) =
+ { "followic", "ignore", "match", "followscs", "smart", NULL };
#endif
#define TC_FOLLOWIC 0x01
#define TC_IGNORE 0x02
#define TC_MATCH 0x04
+#define TC_FOLLOWSCS 0x08
+#define TC_SMART 0x10
EXTERN long p_tl; ///< 'taglength'
EXTERN int p_tr; ///< 'tagrelative'
EXTERN char_u *p_tags; ///< 'tags'
@@ -736,6 +739,7 @@ enum {
, BV_PI
, BV_QE
, BV_RO
+ , BV_SCBK
, BV_SI
, BV_SMC
, BV_SYN
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 4130e69858..e12860c0cc 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -1913,6 +1913,14 @@ return {
defaults={if_true={vi=12}}
},
{
+ full_name='scrollback', abbreviation='scbk',
+ type='number', scope={'buffer'},
+ vi_def=true,
+ varname='p_scbk',
+ redraw={'current_buffer'},
+ defaults={if_true={vi=-1}}
+ },
+ {
full_name='scrollbind', abbreviation='scb',
type='bool', scope={'window'},
vi_def=true,
diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c
index cf5bfd60ae..775f2bd449 100644
--- a/src/nvim/os/fileio.c
+++ b/src/nvim/os/fileio.c
@@ -62,6 +62,8 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname,
FLAG(flags, kFileCreate, O_CREAT|O_WRONLY, kTrue, !(flags & kFileCreateOnly));
FLAG(flags, kFileTruncate, O_TRUNC|O_WRONLY, kTrue,
!(flags & kFileCreateOnly));
+ FLAG(flags, kFileAppend, O_APPEND|O_WRONLY, kTrue,
+ !(flags & kFileCreateOnly));
FLAG(flags, kFileReadOnly, O_RDONLY, kFalse, wr != kTrue);
#ifdef O_NOFOLLOW
FLAG(flags, kFileNoSymlink, O_NOFOLLOW, kNone, true);
@@ -153,7 +155,11 @@ int file_fsync(FileDescriptor *const fp)
fp->_error = 0;
return error;
}
- return os_fsync(fp->fd);
+ const int error = os_fsync(fp->fd);
+ if (error != UV_EINVAL && error != UV_EROFS) {
+ return error;
+ }
+ return 0;
}
/// Buffer used for writing
diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h
index 2cffd5c851..0b55cc695f 100644
--- a/src/nvim/os/fileio.h
+++ b/src/nvim/os/fileio.h
@@ -30,6 +30,8 @@ typedef enum {
kFileTruncate = 32, ///< Truncate the file if it exists.
///< Implies kFileWriteOnly. Cannot be used with
///< kFileCreateOnly.
+ kFileAppend = 64, ///< Append to the file. Implies kFileWriteOnly. Cannot
+ ///< be used with kFileCreateOnly.
} FileOpenFlags;
static inline bool file_eof(const FileDescriptor *const fp)
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 0da98713df..efa1401498 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -137,6 +137,40 @@ struct efm_S {
int conthere; /* %> used */
};
+enum {
+ QF_FAIL = 0,
+ QF_OK = 1,
+ QF_END_OF_INPUT = 2,
+ QF_NOMEM = 3,
+ QF_IGNORE_LINE = 4
+};
+
+typedef struct {
+ char_u *linebuf;
+ size_t linelen;
+ char_u *growbuf;
+ size_t growbufsiz;
+ FILE *fd;
+ typval_T *tv;
+ char_u *p_str;
+ listitem_T *p_li;
+ buf_T *buf;
+ linenr_T buflnum;
+ linenr_T lnumlast;
+} qfstate_T;
+
+typedef struct {
+ char_u *namebuf;
+ char_u *errmsg;
+ size_t errmsglen;
+ long lnum;
+ int col;
+ bool use_viscol;
+ char_u *pattern;
+ int enr;
+ char_u type;
+ bool valid;
+} qffields_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.c.generated.h"
@@ -179,22 +213,6 @@ qf_init (
// Maximum number of bytes allowed per line while reading an errorfile.
static const size_t LINE_MAXLEN = 4096;
-static char_u *qf_grow_linebuf(char_u **growbuf, size_t *growbufsiz,
- size_t newsz, size_t *allocsz)
-{
- // If the line exceeds LINE_MAXLEN exclude the last
- // byte since it's not a NL character.
- *allocsz = newsz > LINE_MAXLEN ? LINE_MAXLEN - 1 : newsz;
- if (*growbuf == NULL) {
- *growbuf = xmalloc(*allocsz + 1);
- *growbufsiz = *allocsz;
- } else if (*allocsz > *growbufsiz) {
- *growbuf = xrealloc(*growbuf, *allocsz + 1);
- *growbufsiz = *allocsz;
- }
- return *growbuf;
-}
-
static struct fmtpattern
{
char_u convchar;
@@ -428,6 +446,505 @@ parse_efm_end:
return fmt_first;
}
+static char_u *qf_grow_linebuf(qfstate_T *state, size_t newsz)
+{
+ // If the line exceeds LINE_MAXLEN exclude the last
+ // byte since it's not a NL character.
+ state->linelen = newsz > LINE_MAXLEN ? LINE_MAXLEN - 1 : newsz;
+ if (state->growbuf == NULL) {
+ state->growbuf = xmalloc(state->linelen + 1);
+ state->growbufsiz = state->linelen;
+ } else if (state->linelen > state->growbufsiz) {
+ state->growbuf = xrealloc(state->growbuf, state->linelen + 1);
+ state->growbufsiz = state->linelen;
+ }
+ return state->growbuf;
+}
+
+/// Get the next string (separated by newline) from state->p_str.
+static int qf_get_next_str_line(qfstate_T *state)
+{
+ // Get the next line from the supplied string
+ char_u *p_str = state->p_str;
+ char_u *p;
+ size_t len;
+
+ if (*p_str == NUL) { // Reached the end of the string
+ return QF_END_OF_INPUT;
+ }
+
+ p = vim_strchr(p_str, '\n');
+ if (p != NULL) {
+ len = (size_t)(p - p_str) + 1;
+ } else {
+ len = STRLEN(p_str);
+ }
+
+ if (len > IOSIZE - 2) {
+ state->linebuf = qf_grow_linebuf(state, len);
+ } else {
+ state->linebuf = IObuff;
+ state->linelen = len;
+ }
+ STRLCPY(state->linebuf, p_str, state->linelen + 1);
+
+ // Increment using len in order to discard the rest of the line if it
+ // exceeds LINE_MAXLEN.
+ p_str += len;
+ state->p_str = p_str;
+
+ return QF_OK;
+}
+
+/// Get the next string from state->p_Li.
+static int qf_get_next_list_line(qfstate_T *state)
+{
+ listitem_T *p_li = state->p_li;
+ size_t len;
+
+ // Get the next line from the supplied list
+ while (p_li != NULL
+ && (p_li->li_tv.v_type != VAR_STRING
+ || p_li->li_tv.vval.v_string == NULL)) {
+ p_li = p_li->li_next; // Skip non-string items
+ }
+
+ if (p_li == NULL) { // End of the list
+ state->p_li = NULL;
+ return QF_END_OF_INPUT;
+ }
+
+ len = STRLEN(p_li->li_tv.vval.v_string);
+ if (len > IOSIZE - 2) {
+ state->linebuf = qf_grow_linebuf(state, len);
+ } else {
+ state->linebuf = IObuff;
+ state->linelen = len;
+ }
+
+ STRLCPY(state->linebuf, p_li->li_tv.vval.v_string, state->linelen + 1);
+
+ state->p_li = p_li->li_next; // next item
+ return QF_OK;
+}
+
+/// Get the next string from state->buf.
+static int qf_get_next_buf_line(qfstate_T *state)
+{
+ char_u *p_buf = NULL;
+ size_t len;
+
+ // Get the next line from the supplied buffer
+ if (state->buflnum > state->lnumlast) {
+ return QF_END_OF_INPUT;
+ }
+ p_buf = ml_get_buf(state->buf, state->buflnum, false);
+ state->buflnum += 1;
+
+ len = STRLEN(p_buf);
+ if (len > IOSIZE - 2) {
+ state->linebuf = qf_grow_linebuf(state, len);
+ } else {
+ state->linebuf = IObuff;
+ state->linelen = len;
+ }
+ STRLCPY(state->linebuf, p_buf, state->linelen + 1);
+
+ return QF_OK;
+}
+
+/// Get the next string from file state->fd.
+static int qf_get_next_file_line(qfstate_T *state)
+{
+ size_t growbuflen;
+
+ if (fgets((char *)IObuff, IOSIZE, state->fd) == NULL) {
+ return QF_END_OF_INPUT;
+ }
+
+ bool discard = false;
+ state->linelen = STRLEN(IObuff);
+ if (state->linelen == IOSIZE - 1 && !(IObuff[state->linelen - 1] == '\n'
+#ifdef USE_CRNL
+ || IObuff[state->linelen - 1] == '\r'
+#endif
+ )) { // NOLINT(whitespace/parens)
+ // The current line exceeds IObuff, continue reading using growbuf
+ // until EOL or LINE_MAXLEN bytes is read.
+ if (state->growbuf == NULL) {
+ state->growbufsiz = 2 * (IOSIZE - 1);
+ state->growbuf = xmalloc(state->growbufsiz);
+ }
+
+ // Copy the read part of the line, excluding null-terminator
+ memcpy(state->growbuf, IObuff, IOSIZE - 1);
+ growbuflen = state->linelen;
+
+ for (;;) {
+ if (fgets((char *)state->growbuf + growbuflen,
+ (int)(state->growbufsiz - growbuflen), state->fd) == NULL) {
+ break;
+ }
+ state->linelen = STRLEN(state->growbuf + growbuflen);
+ growbuflen += state->linelen;
+ if (state->growbuf[growbuflen - 1] == '\n'
+#ifdef USE_CRNL
+ || state->growbuf[growbuflen - 1] == '\r'
+#endif
+ ) {
+ break;
+ }
+ if (state->growbufsiz == LINE_MAXLEN) {
+ discard = true;
+ break;
+ }
+
+ state->growbufsiz = (2 * state->growbufsiz < LINE_MAXLEN)
+ ? 2 * state->growbufsiz : LINE_MAXLEN;
+ state->growbuf = xrealloc(state->growbuf, state->growbufsiz);
+ }
+
+ while (discard) {
+ // The current line is longer than LINE_MAXLEN, continue reading but
+ // discard everything until EOL or EOF is reached.
+ if (fgets((char *)IObuff, IOSIZE, state->fd) == NULL
+ || STRLEN(IObuff) < IOSIZE - 1
+ || IObuff[IOSIZE - 1] == '\n'
+#ifdef USE_CRNL
+ || IObuff[IOSIZE - 1] == '\r'
+#endif
+ ) {
+ break;
+ }
+ }
+
+ state->linebuf = state->growbuf;
+ state->linelen = growbuflen;
+ } else {
+ state->linebuf = IObuff;
+ }
+ return QF_OK;
+}
+
+/// Get the next string from a file/buffer/list/string.
+static int qf_get_nextline(qfstate_T *state)
+{
+ int status = QF_FAIL;
+
+ if (state->fd == NULL) {
+ if (state->tv != NULL) {
+ if (state->tv->v_type == VAR_STRING) {
+ // Get the next line from the supplied string
+ status = qf_get_next_str_line(state);
+ } else if (state->tv->v_type == VAR_LIST) {
+ // Get the next line from the supplied list
+ status = qf_get_next_list_line(state);
+ }
+ } else {
+ // Get the next line from the supplied buffer
+ status = qf_get_next_buf_line(state);
+ }
+ } else {
+ // Get the next line from the supplied file
+ status = qf_get_next_file_line(state);
+ }
+
+ if (status != QF_OK) {
+ return status;
+ }
+
+ if (state->linelen > 0 && state->linebuf[state->linelen - 1] == '\n') {
+ state->linebuf[state->linelen - 1] = NUL;
+ }
+#ifdef USE_CRNL
+ if (state->linelen > 0 && state->linebuf[state->linelen - 1] == '\r') {
+ state->linebuf[state->linelen - 1] = NUL;
+ }
+#endif
+
+ remove_bom(state->linebuf);
+
+ return QF_OK;
+}
+
+
+/// Parse a line and get the quickfix fields.
+/// Return the QF_ status.
+static int qf_parse_line(qf_info_T *qi, char_u *linebuf, size_t linelen,
+ efm_T *fmt_first, qffields_T *fields)
+{
+ efm_T *fmt_ptr;
+ static efm_T *fmt_start = NULL; // cached across calls
+ size_t len;
+ int i;
+ int idx = 0;
+ char_u *tail = NULL;
+ regmatch_T regmatch;
+
+
+ // Always ignore case when looking for a matching error.
+ regmatch.rm_ic = true;
+
+ // If there was no %> item start at the first pattern
+ if (fmt_start == NULL) {
+ fmt_ptr = fmt_first;
+ } else {
+ fmt_ptr = fmt_start;
+ fmt_start = NULL;
+ }
+
+ // Try to match each part of 'errorformat' until we find a complete
+ // match or no match.
+ fields->valid = true;
+restofline:
+ for (; fmt_ptr != NULL; fmt_ptr = fmt_ptr->next) {
+ idx = fmt_ptr->prefix;
+ if (qi->qf_multiscan && vim_strchr((char_u *)"OPQ", idx) == NULL) {
+ continue;
+ }
+ fields->namebuf[0] = NUL;
+ fields->pattern[0] = NUL;
+ if (!qi->qf_multiscan) {
+ fields->errmsg[0] = NUL;
+ }
+ fields->lnum = 0;
+ fields->col = 0;
+ fields->use_viscol = false;
+ fields->enr = -1;
+ fields->type = 0;
+ tail = NULL;
+
+ regmatch.regprog = fmt_ptr->prog;
+ int r = vim_regexec(&regmatch, linebuf, (colnr_T)0);
+ fmt_ptr->prog = regmatch.regprog;
+ if (r) {
+ if ((idx == 'C' || idx == 'Z') && !qi->qf_multiline) {
+ continue;
+ }
+ if (vim_strchr((char_u *)"EWI", idx) != NULL) {
+ fields->type = (char_u)idx;
+ } else {
+ fields->type = 0;
+ }
+ // Extract error message data from matched line.
+ // We check for an actual submatch, because "\[" and "\]" in
+ // the 'errorformat' may cause the wrong submatch to be used.
+ if ((i = (int)fmt_ptr->addr[0]) > 0) { // %f
+ if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
+ continue;
+ }
+ // Expand ~/file and $HOME/file to full path.
+ char_u c = *regmatch.endp[i];
+ *regmatch.endp[i] = NUL;
+ expand_env(regmatch.startp[i], fields->namebuf, CMDBUFFSIZE);
+ *regmatch.endp[i] = c;
+
+ if (vim_strchr((char_u *)"OPQ", idx) != NULL
+ && !os_path_exists(fields->namebuf)) {
+ continue;
+ }
+ }
+ if ((i = (int)fmt_ptr->addr[1]) > 0) { // %n
+ if (regmatch.startp[i] == NULL) {
+ continue;
+ }
+ fields->enr = (int)atol((char *)regmatch.startp[i]);
+ }
+ if ((i = (int)fmt_ptr->addr[2]) > 0) { // %l
+ if (regmatch.startp[i] == NULL) {
+ continue;
+ }
+ fields->lnum = atol((char *)regmatch.startp[i]);
+ }
+ if ((i = (int)fmt_ptr->addr[3]) > 0) { // %c
+ if (regmatch.startp[i] == NULL) {
+ continue;
+ }
+ fields->col = (int)atol((char *)regmatch.startp[i]);
+ }
+ if ((i = (int)fmt_ptr->addr[4]) > 0) { // %t
+ if (regmatch.startp[i] == NULL) {
+ continue;
+ }
+ fields->type = *regmatch.startp[i];
+ }
+ if (fmt_ptr->flags == '+' && !qi->qf_multiscan) { // %+
+ if (linelen > fields->errmsglen) {
+ // linelen + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
+ fields->errmsglen = linelen + 1;
+ }
+ STRLCPY(fields->errmsg, linebuf, linelen + 1);
+ } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m
+ if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
+ continue;
+ }
+ len = (size_t)(regmatch.endp[i] - regmatch.startp[i]);
+ if (len > fields->errmsglen) {
+ // len + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, len + 1);
+ fields->errmsglen = len + 1;
+ }
+ STRLCPY(fields->errmsg, regmatch.startp[i], len + 1);
+ }
+ if ((i = (int)fmt_ptr->addr[6]) > 0) { // %r
+ if (regmatch.startp[i] == NULL) {
+ continue;
+ }
+ tail = regmatch.startp[i];
+ }
+ if ((i = (int)fmt_ptr->addr[7]) > 0) { // %p
+ char_u *match_ptr;
+
+ if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
+ continue;
+ }
+ fields->col = 0;
+ for (match_ptr = regmatch.startp[i];
+ match_ptr != regmatch.endp[i]; match_ptr++) {
+ fields->col++;
+ if (*match_ptr == TAB) {
+ fields->col += 7;
+ fields->col -= fields->col % 8;
+ }
+ }
+ fields->col++;
+ fields->use_viscol = true;
+ }
+ if ((i = (int)fmt_ptr->addr[8]) > 0) { // %v
+ if (regmatch.startp[i] == NULL) {
+ continue;
+ }
+ fields->col = (int)atol((char *)regmatch.startp[i]);
+ fields->use_viscol = true;
+ }
+ if ((i = (int)fmt_ptr->addr[9]) > 0) { // %s
+ if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
+ continue;
+ }
+ len = (size_t)(regmatch.endp[i] - regmatch.startp[i]);
+ if (len > CMDBUFFSIZE - 5) {
+ len = CMDBUFFSIZE - 5;
+ }
+ STRCPY(fields->pattern, "^\\V");
+ xstrlcat((char *)fields->pattern, (char *)regmatch.startp[i],
+ CMDBUFFSIZE+1);
+ fields->pattern[len + 3] = '\\';
+ fields->pattern[len + 4] = '$';
+ fields->pattern[len + 5] = NUL;
+ }
+ break;
+ }
+ }
+ qi->qf_multiscan = false;
+
+ if (fmt_ptr == NULL || idx == 'D' || idx == 'X') {
+ if (fmt_ptr != NULL) {
+ if (idx == 'D') { // enter directory
+ if (*fields->namebuf == NUL) {
+ EMSG(_("E379: Missing or empty directory name"));
+ return QF_FAIL;
+ }
+ qi->qf_directory = qf_push_dir(fields->namebuf, &qi->qf_dir_stack,
+ false);
+ if (qi->qf_directory == NULL) {
+ return QF_FAIL;
+ }
+ } else if (idx == 'X') { // leave directory
+ qi->qf_directory = qf_pop_dir(&qi->qf_dir_stack);
+ }
+ }
+ fields->namebuf[0] = NUL; // no match found, remove file name
+ fields->lnum = 0; // don't jump to this line
+ fields->valid = false;
+ if (linelen > fields->errmsglen) {
+ // linelen + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
+ fields->errmsglen = linelen + 1;
+ }
+ // copy whole line to error message
+ STRLCPY(fields->errmsg, linebuf, linelen + 1);
+ if (fmt_ptr == NULL) {
+ qi->qf_multiline = qi->qf_multiignore = false;
+ }
+ } else if (fmt_ptr != NULL) {
+ // honor %> item
+ if (fmt_ptr->conthere) {
+ fmt_start = fmt_ptr;
+ }
+
+ if (vim_strchr((char_u *)"AEWI", idx) != NULL) {
+ qi->qf_multiline = true; // start of a multi-line message
+ qi->qf_multiignore = false; // reset continuation
+ } else if (vim_strchr((char_u *)"CZ", idx)
+ != NULL) { // continuation of multi-line msg
+ qfline_T *qfprev = qi->qf_lists[qi->qf_curlist].qf_last;
+ if (qfprev == NULL) {
+ return QF_FAIL;
+ }
+ if (*fields->errmsg && !qi->qf_multiignore) {
+ size_t len = STRLEN(qfprev->qf_text);
+ qfprev->qf_text = xrealloc(qfprev->qf_text,
+ len + STRLEN(fields->errmsg) + 2);
+ qfprev->qf_text[len] = '\n';
+ STRCPY(qfprev->qf_text + len + 1, fields->errmsg);
+ }
+ if (qfprev->qf_nr == -1) {
+ qfprev->qf_nr = fields->enr;
+ }
+ if (vim_isprintc(fields->type) && !qfprev->qf_type) {
+ qfprev->qf_type = fields->type; // only printable chars allowed
+ }
+ if (!qfprev->qf_lnum) {
+ qfprev->qf_lnum = fields->lnum;
+ }
+ if (!qfprev->qf_col) {
+ qfprev->qf_col = fields->col;
+ }
+ qfprev->qf_viscol = fields->use_viscol;
+ if (!qfprev->qf_fnum) {
+ qfprev->qf_fnum = qf_get_fnum(qi, qi->qf_directory,
+ *fields->namebuf || qi->qf_directory
+ ? fields->namebuf
+ : qi->qf_currfile && fields->valid
+ ? qi->qf_currfile : 0);
+ }
+ if (idx == 'Z') {
+ qi->qf_multiline = qi->qf_multiignore = false;
+ }
+
+ line_breakcheck();
+ return QF_IGNORE_LINE;
+ } else if (vim_strchr((char_u *)"OPQ", idx) != NULL) {
+ // global file names
+ fields->valid = false;
+ if (*fields->namebuf == NUL || os_path_exists(fields->namebuf)) {
+ if (*fields->namebuf && idx == 'P') {
+ qi->qf_currfile = qf_push_dir(fields->namebuf, &qi->qf_file_stack,
+ true);
+ } else if (idx == 'Q') {
+ qi->qf_currfile = qf_pop_dir(&qi->qf_file_stack);
+ }
+ *fields->namebuf = NUL;
+ if (tail && *tail) {
+ STRMOVE(IObuff, skipwhite(tail));
+ qi->qf_multiscan = true;
+ goto restofline;
+ }
+ }
+ }
+ if (fmt_ptr->flags == '-') { // generally exclude this line
+ if (qi->qf_multiline) {
+ // also exclude continuation lines
+ qi->qf_multiignore = true;
+ }
+ return QF_IGNORE_LINE;
+ }
+ }
+
+ return QF_OK;
+}
+
// Read the errorfile "efile" into memory, line by line, building the error
// list.
// Alternative: when "efile" is NULL read errors from buffer "buf".
@@ -449,45 +966,22 @@ qf_init_ext(
char_u *qf_title
)
{
- char_u *namebuf;
- char_u *errmsg;
- size_t errmsglen;
- char_u *pattern;
- char_u *growbuf = NULL;
- size_t growbuflen;
- size_t growbufsiz = 0;
- char_u *linebuf = NULL;
- size_t linelen = 0;
- bool discard;
- int col = 0;
- bool use_viscol = false;
- char_u type = 0;
- linenr_T buflnum = lnumfirst;
- long lnum = 0L;
- int enr = 0;
- FILE *fd = NULL;
+ qfstate_T state = { NULL, 0, NULL, 0, NULL, NULL, NULL, NULL,
+ NULL, 0, 0 };
+ qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 };
qfline_T *old_last = NULL;
static efm_T *fmt_first = NULL;
- efm_T *fmt_ptr;
- efm_T *fmt_start = NULL;
char_u *efm;
static char_u *last_efm = NULL;
- size_t len;
- int i;
- int idx = 0;
int retval = -1; // default: return error flag
- char_u *tail = NULL;
- char_u *p_buf = NULL;
- char_u *p_str = NULL;
- listitem_T *p_li = NULL;
- regmatch_T regmatch;
+ int status;
- namebuf = xmalloc(CMDBUFFSIZE + 1);
- errmsglen = CMDBUFFSIZE + 1;
- errmsg = xmalloc(errmsglen);
- pattern = xmalloc(CMDBUFFSIZE + 1);
+ fields.namebuf = xmalloc(CMDBUFFSIZE + 1);
+ fields.errmsglen = CMDBUFFSIZE + 1;
+ fields.errmsg = xmalloc(fields.errmsglen);
+ fields.pattern = xmalloc(CMDBUFFSIZE + 1);
- if (efile != NULL && (fd = mch_fopen((char *)efile, "r")) == NULL) {
+ if (efile != NULL && (state.fd = mch_fopen((char *)efile, "r")) == NULL) {
EMSG2(_(e_openerrf), efile);
goto qf_init_end;
}
@@ -500,15 +994,12 @@ qf_init_ext(
old_last = qi->qf_lists[qi->qf_curlist].qf_last;
}
- /*
- * Each part of the format string is copied and modified from errorformat to
- * regex prog. Only a few % characters are allowed.
- */
- /* Use the local value of 'errorformat' if it's set. */
- if (errorformat == p_efm && tv == NULL && *buf->b_p_efm != NUL)
+ // Use the local value of 'errorformat' if it's set.
+ if (errorformat == p_efm && tv == NULL && *buf->b_p_efm != NUL) {
efm = buf->b_p_efm;
- else
+ } else {
efm = errorformat;
+ }
// If we are not adding or adding to another list: clear the state.
if (newlist || qi->qf_curlist != qi->qf_dir_curlist) {
@@ -547,424 +1038,57 @@ qf_init_ext(
*/
got_int = FALSE;
- /* Always ignore case when looking for a matching error. */
- regmatch.rm_ic = TRUE;
-
if (tv != NULL) {
- if (tv->v_type == VAR_STRING)
- p_str = tv->vval.v_string;
- else if (tv->v_type == VAR_LIST)
- p_li = tv->vval.v_list->lv_first;
+ if (tv->v_type == VAR_STRING) {
+ state.p_str = tv->vval.v_string;
+ } else if (tv->v_type == VAR_LIST) {
+ state.p_li = tv->vval.v_list->lv_first;
+ }
+ state.tv = tv;
}
+ state.buf = buf;
+ state.buflnum = lnumfirst;
+ state.lnumlast = lnumlast;
/*
* Read the lines in the error file one by one.
* Try to recognize one of the error formats in each line.
*/
while (!got_int) {
- /* Get the next line. */
- if (fd == NULL) {
- if (tv != NULL) {
- if (tv->v_type == VAR_STRING) {
- /* Get the next line from the supplied string */
- char_u *p;
-
- if (*p_str == NUL) { // Reached the end of the string
- break;
- }
-
- p = vim_strchr(p_str, '\n');
- if (p != NULL) {
- len = (size_t)(p - p_str) + 1;
- } else {
- len = STRLEN(p_str);
- }
-
- if (len > IOSIZE - 2) {
- linebuf = qf_grow_linebuf(&growbuf, &growbufsiz, len, &linelen);
- } else {
- linebuf = IObuff;
- linelen = len;
- }
- STRLCPY(linebuf, p_str, linelen + 1);
-
- // Increment using len in order to discard the rest of the line if it
- // exceeds LINE_MAXLEN.
- p_str += len;
- } else if (tv->v_type == VAR_LIST) {
- // Get the next line from the supplied list
- while (p_li != NULL
- && (p_li->li_tv.v_type != VAR_STRING
- || p_li->li_tv.vval.v_string == NULL)) {
- p_li = p_li->li_next; // Skip non-string items
- }
-
- if (p_li == NULL) { // End of the list
- break;
- }
-
- len = STRLEN(p_li->li_tv.vval.v_string);
- if (len > IOSIZE - 2) {
- linebuf = qf_grow_linebuf(&growbuf, &growbufsiz, len, &linelen);
- } else {
- linebuf = IObuff;
- linelen = len;
- }
-
- STRLCPY(linebuf, p_li->li_tv.vval.v_string, linelen + 1);
-
- p_li = p_li->li_next; /* next item */
- }
- } else {
- /* Get the next line from the supplied buffer */
- if (buflnum > lnumlast)
- break;
- p_buf = ml_get_buf(buf, buflnum++, false);
- len = STRLEN(p_buf);
- if (len > IOSIZE - 2) {
- linebuf = qf_grow_linebuf(&growbuf, &growbufsiz, len, &linelen);
- } else {
- linebuf = IObuff;
- linelen = len;
- }
- STRLCPY(linebuf, p_buf, linelen + 1);
- }
- } else {
- if (fgets((char *)IObuff, IOSIZE, fd) == NULL) {
- break;
- }
-
- discard = false;
- linelen = STRLEN(IObuff);
- if (linelen == IOSIZE - 1 && !(IObuff[linelen - 1] == '\n'
-#ifdef USE_CRNL
- || IObuff[linelen - 1] == '\r'
-#endif
- )) { // NOLINT(whitespace/parens)
- // The current line exceeds IObuff, continue reading using growbuf
- // until EOL or LINE_MAXLEN bytes is read.
- if (growbuf == NULL) {
- growbufsiz = 2 * (IOSIZE - 1);
- growbuf = xmalloc(growbufsiz);
- }
-
- // Copy the read part of the line, excluding null-terminator
- memcpy(growbuf, IObuff, IOSIZE - 1);
- growbuflen = linelen;
-
- for (;;) {
- if (fgets((char *)growbuf + growbuflen,
- (int)(growbufsiz - growbuflen), fd) == NULL) {
- break;
- }
- linelen = STRLEN(growbuf + growbuflen);
- growbuflen += linelen;
- if (growbuf[growbuflen - 1] == '\n'
-#ifdef USE_CRNL
- || growbuf[growbuflen - 1] == '\r'
-#endif
- ) {
- break;
- }
- if (growbufsiz == LINE_MAXLEN) {
- discard = true;
- break;
- }
-
- growbufsiz = (2 * growbufsiz < LINE_MAXLEN)
- ? 2 * growbufsiz : LINE_MAXLEN;
- growbuf = xrealloc(growbuf, 2 * growbufsiz);
- }
-
- while (discard) {
- // The current line is longer than LINE_MAXLEN, continue reading but
- // discard everything until EOL or EOF is reached.
- if (fgets((char *)IObuff, IOSIZE, fd) == NULL
- || STRLEN(IObuff) < IOSIZE - 1
- || IObuff[IOSIZE - 1] == '\n'
-#ifdef USE_CRNL
- || IObuff[IOSIZE - 1] == '\r'
-#endif
- ) {
- break;
- }
- }
-
- linebuf = growbuf;
- linelen = growbuflen;
- } else {
- linebuf = IObuff;
- }
- }
-
- if (linelen > 0 && linebuf[linelen - 1] == '\n') {
- linebuf[linelen - 1] = NUL;
- }
-#ifdef USE_CRNL
- if (linelen > 0 && linebuf[linelen - 1] == '\r') {
- linebuf[linelen - 1] = NUL;
- }
-#endif
-
- remove_bom(linebuf);
-
- /* If there was no %> item start at the first pattern */
- if (fmt_start == NULL)
- fmt_ptr = fmt_first;
- else {
- fmt_ptr = fmt_start;
- fmt_start = NULL;
+ // Get the next line from a file/buffer/list/string
+ status = qf_get_nextline(&state);
+ if (status == QF_END_OF_INPUT) { // end of input
+ break;
}
- // Try to match each part of 'errorformat' until we find a complete
- // match or no match.
- bool valid = true;
-restofline:
- for (; fmt_ptr != NULL; fmt_ptr = fmt_ptr->next) {
- idx = fmt_ptr->prefix;
- if (qi->qf_multiscan && vim_strchr((char_u *)"OPQ", idx) == NULL) {
- continue;
- }
- namebuf[0] = NUL;
- pattern[0] = NUL;
- if (!qi->qf_multiscan) {
- errmsg[0] = NUL;
- }
- lnum = 0;
- col = 0;
- use_viscol = false;
- enr = -1;
- type = 0;
- tail = NULL;
-
- regmatch.regprog = fmt_ptr->prog;
- int r = vim_regexec(&regmatch, linebuf, (colnr_T)0);
- fmt_ptr->prog = regmatch.regprog;
- if (r) {
- if ((idx == 'C' || idx == 'Z') && !qi->qf_multiline) {
- continue;
- }
- if (vim_strchr((char_u *)"EWI", idx) != NULL) {
- type = (char_u)idx;
- } else {
- type = 0;
- }
- // Extract error message data from matched line.
- // We check for an actual submatch, because "\[" and "\]" in
- // the 'errorformat' may cause the wrong submatch to be used.
- if ((i = (int)fmt_ptr->addr[0]) > 0) { // %f
- if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
- continue;
- }
- // Expand ~/file and $HOME/file to full path.
- char_u c = *regmatch.endp[i];
- *regmatch.endp[i] = NUL;
- expand_env(regmatch.startp[i], namebuf, CMDBUFFSIZE);
- *regmatch.endp[i] = c;
-
- if (vim_strchr((char_u *)"OPQ", idx) != NULL
- && !os_path_exists(namebuf)) {
- continue;
- }
- }
- if ((i = (int)fmt_ptr->addr[1]) > 0) { /* %n */
- if (regmatch.startp[i] == NULL)
- continue;
- enr = (int)atol((char *)regmatch.startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[2]) > 0) { /* %l */
- if (regmatch.startp[i] == NULL)
- continue;
- lnum = atol((char *)regmatch.startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[3]) > 0) { /* %c */
- if (regmatch.startp[i] == NULL)
- continue;
- col = (int)atol((char *)regmatch.startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[4]) > 0) { /* %t */
- if (regmatch.startp[i] == NULL)
- continue;
- type = *regmatch.startp[i];
- }
- if (fmt_ptr->flags == '+' && !qi->qf_multiscan) { // %+
- if (linelen > errmsglen) {
- // linelen + null terminator
- errmsg = xrealloc(errmsg, linelen + 1);
- errmsglen = linelen + 1;
- }
- STRLCPY(errmsg, linebuf, linelen + 1);
- } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m
- if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
- continue;
- }
- len = (size_t)(regmatch.endp[i] - regmatch.startp[i]);
- if (len > errmsglen) {
- // len + null terminator
- errmsg = xrealloc(errmsg, len + 1);
- errmsglen = len + 1;
- }
- STRLCPY(errmsg, regmatch.startp[i], len + 1);
- }
- if ((i = (int)fmt_ptr->addr[6]) > 0) { /* %r */
- if (regmatch.startp[i] == NULL)
- continue;
- tail = regmatch.startp[i];
- }
- if ((i = (int)fmt_ptr->addr[7]) > 0) { /* %p */
- char_u *match_ptr;
-
- if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL)
- continue;
- col = 0;
- for (match_ptr = regmatch.startp[i];
- match_ptr != regmatch.endp[i]; ++match_ptr) {
- ++col;
- if (*match_ptr == TAB) {
- col += 7;
- col -= col % 8;
- }
- }
- col++;
- use_viscol = true;
- }
- if ((i = (int)fmt_ptr->addr[8]) > 0) { /* %v */
- if (regmatch.startp[i] == NULL)
- continue;
- col = (int)atol((char *)regmatch.startp[i]);
- use_viscol = true;
- }
- if ((i = (int)fmt_ptr->addr[9]) > 0) { /* %s */
- if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL)
- continue;
- len = (size_t)(regmatch.endp[i] - regmatch.startp[i]);
- if (len > CMDBUFFSIZE - 5) {
- len = CMDBUFFSIZE - 5;
- }
- STRCPY(pattern, "^\\V");
- STRNCAT(pattern, regmatch.startp[i], len);
- pattern[len + 3] = '\\';
- pattern[len + 4] = '$';
- pattern[len + 5] = NUL;
- }
- break;
- }
+ status = qf_parse_line(qi, state.linebuf, state.linelen, fmt_first,
+ &fields);
+ if (status == QF_FAIL) {
+ goto error2;
}
- qi->qf_multiscan = false;
-
- if (fmt_ptr == NULL || idx == 'D' || idx == 'X') {
- if (fmt_ptr != NULL) {
- if (idx == 'D') { /* enter directory */
- if (*namebuf == NUL) {
- EMSG(_("E379: Missing or empty directory name"));
- goto error2;
- }
- qi->qf_directory = qf_push_dir(namebuf, &qi->qf_dir_stack, false);
- if (qi->qf_directory == NULL) {
- goto error2;
- }
- } else if (idx == 'X') { // leave directory
- qi->qf_directory = qf_pop_dir(&qi->qf_dir_stack);
- }
- }
- namebuf[0] = NUL; // no match found, remove file name
- lnum = 0; // don't jump to this line
- valid = false;
- if (linelen > errmsglen) {
- // linelen + null terminator
- errmsg = xrealloc(errmsg, linelen + 1);
- }
- // copy whole line to error message
- STRLCPY(errmsg, linebuf, linelen + 1);
- if (fmt_ptr == NULL) {
- qi->qf_multiline = qi->qf_multiignore = false;
- }
- } else if (fmt_ptr != NULL) {
- /* honor %> item */
- if (fmt_ptr->conthere)
- fmt_start = fmt_ptr;
-
- if (vim_strchr((char_u *)"AEWI", idx) != NULL) {
- qi->qf_multiline = true; // start of a multi-line message
- qi->qf_multiignore = false; // reset continuation
- } else if (vim_strchr((char_u *)"CZ", idx)
- != NULL) { // continuation of multi-line msg
- qfline_T *qfprev = qi->qf_lists[qi->qf_curlist].qf_last;
- if (qfprev == NULL) {
- goto error2;
- }
- if (*errmsg && !qi->qf_multiignore) {
- size_t len = STRLEN(qfprev->qf_text);
- qfprev->qf_text = xrealloc(qfprev->qf_text, len + STRLEN(errmsg) + 2);
- qfprev->qf_text[len] = '\n';
- STRCPY(qfprev->qf_text + len + 1, errmsg);
- }
- if (qfprev->qf_nr == -1)
- qfprev->qf_nr = enr;
- if (vim_isprintc(type) && !qfprev->qf_type)
- qfprev->qf_type = type; /* only printable chars allowed */
- if (!qfprev->qf_lnum)
- qfprev->qf_lnum = lnum;
- if (!qfprev->qf_col)
- qfprev->qf_col = col;
- qfprev->qf_viscol = use_viscol;
- if (!qfprev->qf_fnum) {
- qfprev->qf_fnum = qf_get_fnum(qi, qi->qf_directory,
- *namebuf
- || qi->qf_directory
- ? namebuf : qi->qf_currfile
- && valid ? qi->qf_currfile : 0);
- }
- if (idx == 'Z') {
- qi->qf_multiline = qi->qf_multiignore = false;
- }
- line_breakcheck();
- continue;
- } else if (vim_strchr((char_u *)"OPQ", idx) != NULL) {
- // global file names
- valid = false;
- if (*namebuf == NUL || os_path_exists(namebuf)) {
- if (*namebuf && idx == 'P') {
- qi->qf_currfile = qf_push_dir(namebuf, &qi->qf_file_stack, true);
- } else if (idx == 'Q') {
- qi->qf_currfile = qf_pop_dir(&qi->qf_file_stack);
- }
- *namebuf = NUL;
- if (tail && *tail) {
- STRMOVE(IObuff, skipwhite(tail));
- qi->qf_multiscan = true;
- goto restofline;
- }
- }
- }
- if (fmt_ptr->flags == '-') { // generally exclude this line
- if (qi->qf_multiline) {
- // also exclude continuation lines
- qi->qf_multiignore = true;
- }
- continue;
- }
+ if (status == QF_IGNORE_LINE) {
+ continue;
}
if (qf_add_entry(qi,
qi->qf_directory,
- (*namebuf || qi->qf_directory)
- ? namebuf : ((qi->qf_currfile && valid)
- ? qi->qf_currfile : (char_u *)NULL),
+ (*fields.namebuf || qi->qf_directory)
+ ? fields.namebuf : ((qi->qf_currfile && fields.valid)
+ ? qi->qf_currfile : (char_u *)NULL),
0,
- errmsg,
- lnum,
- col,
- use_viscol,
- pattern,
- enr,
- type,
- valid) == FAIL) {
+ fields.errmsg,
+ fields.lnum,
+ fields.col,
+ fields.use_viscol,
+ fields.pattern,
+ fields.enr,
+ fields.type,
+ fields.valid) == FAIL) {
goto error2;
}
line_breakcheck();
}
- if (fd == NULL || !ferror(fd)) {
+ if (state.fd == NULL || !ferror(state.fd)) {
if (qi->qf_lists[qi->qf_curlist].qf_index == 0) {
/* no valid entry found */
qi->qf_lists[qi->qf_curlist].qf_ptr =
@@ -989,13 +1113,13 @@ error2:
qi->qf_curlist--;
}
qf_init_end:
- if (fd != NULL) {
- fclose(fd);
+ if (state.fd != NULL) {
+ fclose(state.fd);
}
- xfree(namebuf);
- xfree(errmsg);
- xfree(pattern);
- xfree(growbuf);
+ xfree(fields.namebuf);
+ xfree(fields.errmsg);
+ xfree(fields.pattern);
+ xfree(state.growbuf);
qf_update_buffer(qi, old_last);
@@ -2637,6 +2761,19 @@ static buf_T *qf_find_buf(qf_info_T *qi)
return NULL;
}
+/// Update the w:quickfix_title variable in the quickfix/location list window
+static void qf_update_win_titlevar(qf_info_T *qi)
+{
+ win_T *win;
+
+ if ((win = qf_find_win(qi)) != NULL) {
+ win_T *curwin_save = curwin;
+ curwin = win;
+ qf_set_title_var(qi);
+ curwin = curwin_save;
+ }
+}
+
/*
* Find the quickfix buffer. If it exists, update the contents.
*/
@@ -2644,7 +2781,6 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
{
buf_T *buf;
win_T *win;
- win_T *curwin_save;
aco_save_T aco;
/* Check if a buffer for the quickfix list exists. Update it. */
@@ -2657,12 +2793,7 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
aucmd_prepbuf(&aco, buf);
}
- if ((win = qf_find_win(qi)) != NULL) {
- curwin_save = curwin;
- curwin = win;
- qf_set_title_var(qi);
- curwin = curwin_save;
- }
+ qf_update_win_titlevar(qi);
qf_fill_buffer(qi, buf, old_last);
@@ -3849,10 +3980,9 @@ static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start)
}
}
-/*
- * Add each quickfix error to list "list" as a dictionary.
- */
-int get_errorlist(win_T *wp, 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(win_T *wp, int qf_idx, list_T *list)
{
qf_info_T *qi = &ql_info;
dict_T *dict;
@@ -3867,13 +3997,18 @@ int get_errorlist(win_T *wp, list_T *list)
return FAIL;
}
- if (qi->qf_curlist >= qi->qf_listcount
- || qi->qf_lists[qi->qf_curlist].qf_count == 0)
+ if (qf_idx == -1) {
+ qf_idx = qi->qf_curlist;
+ }
+
+ if (qf_idx >= qi->qf_listcount
+ || qi->qf_lists[qf_idx].qf_count == 0) {
return FAIL;
+ }
- qfp = qi->qf_lists[qi->qf_curlist].qf_start;
- for (i = 1; !got_int && i <= qi->qf_lists[qi->qf_curlist].qf_count; ++i) {
- /* Handle entries with a non-existing buffer number. */
+ qfp = qi->qf_lists[qf_idx].qf_start;
+ for (i = 1; !got_int && i <= qi->qf_lists[qf_idx].qf_count; i++) {
+ // Handle entries with a non-existing buffer number.
bufnum = qfp->qf_fnum;
if (bufnum != 0 && (buflist_findnr(bufnum) == NULL))
bufnum = 0;
@@ -3904,22 +4039,91 @@ int get_errorlist(win_T *wp, list_T *list)
return OK;
}
-// Populate the quickfix list with the items supplied in the list
-// of dictionaries. "title" will be copied to w:quickfix_title
-// "action" is 'a' for add, 'r' for replace. Otherwise create a new list.
-int set_errorlist(win_T *wp, list_T *list, int action, char_u *title)
+/// Flags used by getqflist()/getloclist() to determine which fields to return.
+enum {
+ QF_GETLIST_NONE = 0x0,
+ QF_GETLIST_TITLE = 0x1,
+ QF_GETLIST_ITEMS = 0x2,
+ QF_GETLIST_NR = 0x4,
+ QF_GETLIST_WINID = 0x8,
+ QF_GETLIST_ALL = 0xFF
+};
+
+/// Return quickfix/location list details (title) as a
+/// dictionary. 'what' contains the details to return. If 'list_idx' is -1,
+/// then current list is used. Otherwise the specified list is used.
+int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict)
+{
+ qf_info_T *qi = &ql_info;
+
+ if (wp != NULL) {
+ qi = GET_LOC_LIST(wp);
+ if (qi == NULL) {
+ return FAIL;
+ }
+ }
+
+ int status = OK;
+ dictitem_T *di;
+ int flags = QF_GETLIST_NONE;
+
+ int qf_idx = qi->qf_curlist; // default is the current list
+ if ((di = dict_find(what, (char_u *)"nr", -1)) != NULL) {
+ // Use the specified quickfix/location list
+ if (di->di_tv.v_type == VAR_NUMBER) {
+ qf_idx = di->di_tv.vval.v_number - 1;
+ if (qf_idx < 0 || qf_idx >= qi->qf_listcount) {
+ return FAIL;
+ }
+ flags |= QF_GETLIST_NR;
+ } else {
+ return FAIL;
+ }
+ }
+
+ if (dict_find(what, (char_u *)"all", -1) != NULL) {
+ flags |= QF_GETLIST_ALL;
+ }
+
+ if (dict_find(what, (char_u *)"title", -1) != NULL) {
+ flags |= QF_GETLIST_TITLE;
+ }
+
+ if (dict_find(what, (char_u *)"winid", -1) != NULL) {
+ flags |= QF_GETLIST_WINID;
+ }
+
+ if (flags & QF_GETLIST_TITLE) {
+ char_u *t = qi->qf_lists[qf_idx].qf_title;
+ if (t == NULL) {
+ t = (char_u *)"";
+ }
+ status = dict_add_nr_str(retdict, "title", 0L, t);
+ }
+ if ((status == OK) && (flags & QF_GETLIST_NR)) {
+ status = dict_add_nr_str(retdict, "nr", qf_idx + 1, NULL);
+ }
+ if ((status == OK) && (flags & QF_GETLIST_WINID)) {
+ win_T *win = qf_find_win(qi);
+ if (win != NULL) {
+ status = dict_add_nr_str(retdict, "winid", win->handle, NULL);
+ }
+ }
+
+ return status;
+}
+
+/// Add list of entries to quickfix/location list. Each list entry is
+/// a dictionary with item information.
+static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title,
+ int action)
{
listitem_T *li;
dict_T *d;
qfline_T *old_last = NULL;
int retval = OK;
- qf_info_T *qi = &ql_info;
bool did_bufnr_emsg = false;
- if (wp != NULL) {
- qi = ll_get_or_alloc_list(wp);
- }
-
if (action == ' ' || qi->qf_curlist == qi->qf_listcount) {
// make place for a new list
qf_new_list(qi, title);
@@ -4010,6 +4214,60 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title)
return retval;
}
+static int qf_set_properties(qf_info_T *qi, dict_T *what)
+{
+ dictitem_T *di;
+ int retval = FAIL;
+
+ int qf_idx = qi->qf_curlist; // default is the current list
+ if ((di = dict_find(what, (char_u *)"nr", -1)) != NULL) {
+ // Use the specified quickfix/location list
+ if (di->di_tv.v_type == VAR_NUMBER) {
+ qf_idx = di->di_tv.vval.v_number - 1;
+ if (qf_idx < 0 || qf_idx >= qi->qf_listcount) {
+ return FAIL;
+ }
+ } else {
+ return FAIL;
+ }
+ }
+
+ if ((di = dict_find(what, (char_u *)"title", -1)) != NULL) {
+ if (di->di_tv.v_type == VAR_STRING) {
+ xfree(qi->qf_lists[qf_idx].qf_title);
+ qi->qf_lists[qf_idx].qf_title = get_dict_string(what, "title", true);
+ if (qf_idx == qi->qf_curlist) {
+ qf_update_win_titlevar(qi);
+ }
+ retval = OK;
+ }
+ }
+
+ return retval;
+}
+
+// Populate the quickfix list with the items supplied in the list
+// of dictionaries. "title" will be copied to w:quickfix_title
+// "action" is 'a' for add, 'r' for replace. Otherwise create a new list.
+int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,
+ dict_T *what)
+{
+ qf_info_T *qi = &ql_info;
+ int retval = OK;
+
+ if (wp != NULL) {
+ qi = ll_get_or_alloc_list(wp);
+ }
+
+ if (what != NULL) {
+ retval = qf_set_properties(qi, what);
+ } else {
+ retval = qf_add_entries(qi, list, title, action);
+ }
+
+ return retval;
+}
+
/*
* ":[range]cbuffer [bufnr]" command.
* ":[range]caddbuffer [bufnr]" command.
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 6613d284d7..1cd334abcd 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -6442,32 +6442,72 @@ static linenr_T submatch_firstlnum;
static linenr_T submatch_maxline;
static int submatch_line_lbr;
-/*
- * vim_regsub() - perform substitutions after a vim_regexec() or
- * vim_regexec_multi() match.
- *
- * If "copy" is TRUE really copy into "dest".
- * If "copy" is FALSE nothing is copied, this is just to find out the length
- * of the result.
- *
- * If "backslash" is TRUE, a backslash will be removed later, need to double
- * them to keep them, and insert a backslash before a CR to avoid it being
- * replaced with a line break later.
- *
- * Note: The matched text must not change between the call of
- * vim_regexec()/vim_regexec_multi() and vim_regsub()! It would make the back
- * references invalid!
- *
- * Returns the size of the replacement, including terminating NUL.
- */
-int vim_regsub(regmatch_T *rmp, char_u *source, char_u *dest, int copy, int magic, int backslash)
+/// Put the submatches in "argv[0]" which is a list passed into call_func() by
+/// vim_regsub_both().
+static int fill_submatch_list(int argc, typval_T *argv, int argcount)
+{
+ listitem_T *li;
+ int i;
+ char_u *s;
+
+ if (argcount == 0) {
+ // called function doesn't take an argument
+ return 0;
+ }
+
+ // Relies on sl_list to be the first item in staticList10_T.
+ init_static_list((staticList10_T *)(argv->vval.v_list));
+
+ // There are always 10 list items in staticList10_T.
+ li = argv->vval.v_list->lv_first;
+ for (i = 0; i < 10; i++) {
+ s = submatch_match->startp[i];
+ if (s == NULL || submatch_match->endp[i] == NULL) {
+ s = NULL;
+ } else {
+ s = vim_strnsave(s, (int)(submatch_match->endp[i] - s));
+ }
+ li->li_tv.v_type = VAR_STRING;
+ li->li_tv.vval.v_string = s;
+ li = li->li_next;
+ }
+ return 1;
+}
+
+static void clear_submatch_list(staticList10_T *sl)
+{
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ xfree(sl->sl_items[i].li_tv.vval.v_string);
+ }
+}
+
+/// vim_regsub() - perform substitutions after a vim_regexec() or
+/// vim_regexec_multi() match.
+///
+/// If "copy" is TRUE really copy into "dest".
+/// If "copy" is FALSE nothing is copied, this is just to find out the length
+/// of the result.
+///
+/// If "backslash" is TRUE, a backslash will be removed later, need to double
+/// them to keep them, and insert a backslash before a CR to avoid it being
+/// replaced with a line break later.
+///
+/// Note: The matched text must not change between the call of
+/// vim_regexec()/vim_regexec_multi() and vim_regsub()! It would make the back
+/// references invalid!
+///
+/// Returns the size of the replacement, including terminating NUL.
+int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest,
+ int copy, int magic, int backslash)
{
reg_match = rmp;
reg_mmatch = NULL;
reg_maxline = 0;
reg_buf = curbuf;
- reg_line_lbr = TRUE;
- return vim_regsub_both(source, dest, copy, magic, backslash);
+ reg_line_lbr = true;
+ return vim_regsub_both(source, expr, dest, copy, magic, backslash);
}
int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, int copy, int magic, int backslash)
@@ -6477,11 +6517,12 @@ int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *de
reg_buf = curbuf; /* always works on the current buffer! */
reg_firstlnum = lnum;
reg_maxline = curbuf->b_ml.ml_line_count - lnum;
- reg_line_lbr = FALSE;
- return vim_regsub_both(source, dest, copy, magic, backslash);
+ reg_line_lbr = false;
+ return vim_regsub_both(source, NULL, dest, copy, magic, backslash);
}
-static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, int backslash)
+static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
+ int copy, int magic, int backslash)
{
char_u *src;
char_u *dst;
@@ -6495,8 +6536,8 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in
int len = 0; /* init for GCC */
static char_u *eval_result = NULL;
- /* Be paranoid... */
- if (source == NULL || dest == NULL) {
+ // Be paranoid...
+ if ((source == NULL && expr == NULL) || dest == NULL) {
EMSG(_(e_null));
return 0;
}
@@ -6505,16 +6546,13 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in
src = source;
dst = dest;
- /*
- * When the substitute part starts with "\=" evaluate it as an expression.
- */
- if (source[0] == '\\' && source[1] == '='
- && !can_f_submatch /* can't do this recursively */
- ) {
- /* To make sure that the length doesn't change between checking the
- * length and copying the string, and to speed up things, the
- * resulting string is saved from the call with "copy" == FALSE to the
- * call with "copy" == TRUE. */
+ // When the substitute part starts with "\=" evaluate it as an expression.
+ if (expr != NULL || (source[0] == '\\' && source[1] == '='
+ && !can_f_submatch)) { // can't do this recursively
+ // To make sure that the length doesn't change between checking the
+ // length and copying the string, and to speed up things, the
+ // resulting string is saved from the call with "copy" == FALSE to the
+ // call with "copy" == TRUE.
if (copy) {
if (eval_result != NULL) {
STRCPY(dest, eval_result);
@@ -6525,6 +6563,7 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in
} else {
win_T *save_reg_win;
int save_ireg_ic;
+ bool prev_can_f_submatch = can_f_submatch;
xfree(eval_result);
@@ -6539,9 +6578,50 @@ static int vim_regsub_both(char_u *source, char_u *dest, int copy, int magic, in
submatch_line_lbr = reg_line_lbr;
save_reg_win = reg_win;
save_ireg_ic = ireg_ic;
- can_f_submatch = TRUE;
+ can_f_submatch = true;
+
+ if (expr != NULL) {
+ typval_T argv[2];
+ int dummy;
+ char_u buf[NUMBUFLEN];
+ typval_T rettv;
+ staticList10_T matchList;
+
+ rettv.v_type = VAR_STRING;
+ rettv.vval.v_string = NULL;
+ if (prev_can_f_submatch) {
+ // can't do this recursively
+ } else {
+ argv[0].v_type = VAR_LIST;
+ argv[0].vval.v_list = &matchList.sl_list;
+ matchList.sl_list.lv_len = 0;
+ if (expr->v_type == VAR_FUNC) {
+ s = expr->vval.v_string;
+ call_func(s, (int)STRLEN(s), &rettv, 1, argv,
+ fill_submatch_list, 0L, 0L, &dummy,
+ true, NULL, NULL);
+ } else if (expr->v_type == VAR_PARTIAL) {
+ partial_T *partial = expr->vval.v_partial;
+
+ s = partial_name(partial);
+ call_func(s, (int)STRLEN(s), &rettv, 1, argv,
+ fill_submatch_list, 0L, 0L, &dummy,
+ true, partial, NULL);
+ }
+ if (matchList.sl_list.lv_len > 0) {
+ // fill_submatch_list() was called.
+ clear_submatch_list(&matchList);
+ }
+ }
+ eval_result = get_tv_string_buf_chk(&rettv, buf);
+ if (eval_result != NULL) {
+ eval_result = vim_strsave(eval_result);
+ }
+ clear_tv(&rettv);
+ } else {
+ eval_result = eval_to_string(source + 2, NULL, true);
+ }
- eval_result = eval_to_string(source + 2, NULL, TRUE);
if (eval_result != NULL) {
int had_backslash = FALSE;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index bb1f4b9a14..b98e59ed06 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -7110,8 +7110,9 @@ void showruler(int always)
}
if ((*p_stl != NUL || *curwin->w_p_stl != NUL) && curwin->w_status_height) {
redraw_custom_statusline(curwin);
- } else
+ } else {
win_redr_ruler(curwin, always);
+ }
if (need_maketitle
|| (p_icon && (stl_syntax & STL_IN_ICON))
diff --git a/src/nvim/search.c b/src/nvim/search.c
index a9766a406e..ba6c4e6548 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -308,13 +308,19 @@ void free_search_patterns(void)
*/
int ignorecase(char_u *pat)
{
- int ic = p_ic;
+ return ignorecase_opt(pat, p_ic, p_scs);
+}
- if (ic && !no_smartcase && p_scs
+/// As ignorecase() put pass the "ic" and "scs" flags.
+int ignorecase_opt(char_u *pat, int ic_in, int scs)
+{
+ int ic = ic_in;
+ if (ic && !no_smartcase && scs
&& !(ctrl_x_mode && curbuf->b_p_inf)
- )
+ ) {
ic = !pat_has_uppercase(pat);
- no_smartcase = FALSE;
+ }
+ no_smartcase = false;
return ic;
}
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index e723ea20c9..59f4c1e968 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -789,7 +789,7 @@ do_tag (
}
vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag);
- set_errorlist(curwin, list, ' ', IObuff);
+ set_errorlist(curwin, list, ' ', IObuff, NULL);
list_free(list);
xfree(fname);
@@ -1164,6 +1164,12 @@ find_tags (
case TC_MATCH:
p_ic = false;
break;
+ case TC_FOLLOWSCS:
+ p_ic = ignorecase(pat);
+ break;
+ case TC_SMART:
+ p_ic = ignorecase_opt(pat, true, true);
+ break;
default:
assert(false);
}
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index b0104a4e4c..e23c2e5748 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -1,18 +1,17 @@
-// VT220/xterm-like terminal emulator implementation for nvim. Powered by
-// libvterm (http://www.leonerd.org.uk/code/libvterm/).
+// VT220/xterm-like terminal emulator.
+// Powered by libvterm http://www.leonerd.org.uk/code/libvterm
//
// libvterm is a pure C99 terminal emulation library with abstract input and
// display. This means that the library needs to read data from the master fd
// and feed VTerm instances, which will invoke user callbacks with screen
// update instructions that must be mirrored to the real display.
//
-// Keys are pressed in VTerm instances by calling
+// Keys are sent to VTerm instances by calling
// vterm_keyboard_key/vterm_keyboard_unichar, which generates byte streams that
// must be fed back to the master fd.
//
-// This implementation uses nvim buffers as the display mechanism for both
-// the visible screen and the scrollback buffer. When focused, the window
-// "pins" to the bottom of the buffer and mirrors libvterm screen state.
+// Nvim buffers are used as the display mechanism for both the visible screen
+// and the scrollback buffer.
//
// When a line becomes invisible due to a decrease in screen height or because
// a line was pushed up during normal terminal output, we store the line
@@ -23,18 +22,17 @@
// scrollback buffer, which is mirrored in the nvim buffer displaying lines
// that were previously invisible.
//
-// The vterm->nvim synchronization is performed in intervals of 10
-// milliseconds. This is done to minimize screen updates when receiving large
-// bursts of data.
+// The vterm->nvim synchronization is performed in intervals of 10 milliseconds,
+// to minimize screen updates when receiving large bursts of data.
//
// This module is decoupled from the processes that normally feed it data, so
// it's possible to use it as a general purpose console buffer (possibly as a
// log/display mechanism for nvim in the future)
//
-// Inspired by vimshell (http://www.wana.at/vimshell/) and
-// Conque (https://code.google.com/p/conque/). Libvterm usage instructions (plus
-// some extra code) were taken from
-// pangoterm (http://www.leonerd.org.uk/code/pangoterm/)
+// Inspired by: vimshell http://www.wana.at/vimshell
+// Conque https://code.google.com/p/conque
+// Some code from pangoterm http://www.leonerd.org.uk/code/pangoterm
+
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
@@ -87,10 +85,10 @@ typedef struct terminal_state {
# include "terminal.c.generated.h"
#endif
-#define SCROLLBACK_BUFFER_DEFAULT_SIZE 1000
+#define SB_MAX 100000 // Maximum 'scrollback' value.
+
// Delay for refreshing the terminal buffer after receiving updates from
-// libvterm. This is greatly improves performance when receiving large bursts
-// of data.
+// libvterm. Improves performance when receiving large bursts of data.
#define REFRESH_DELAY 10
static TimeWatcher refresh_timer;
@@ -102,27 +100,23 @@ typedef struct {
} ScrollbackLine;
struct terminal {
- // options passed to terminal_open
- TerminalOptions opts;
- // libvterm structures
+ TerminalOptions opts; // options passed to terminal_open
VTerm *vt;
VTermScreen *vts;
// buffer used to:
// - convert VTermScreen cell arrays into utf8 strings
// - receive data from libvterm as a result of key presses.
char textbuf[0x1fff];
- // Scrollback buffer storage for libvterm.
- // TODO(tarruda): Use a doubly-linked list
- ScrollbackLine **sb_buffer;
- // number of rows pushed to sb_buffer
- size_t sb_current;
- // sb_buffer size;
- size_t sb_size;
+
+ ScrollbackLine **sb_buffer; // Scrollback buffer storage for libvterm
+ size_t sb_current; // number of rows pushed to sb_buffer
+ size_t sb_size; // sb_buffer size
// "virtual index" that points to the first sb_buffer row that we need to
// push to the terminal buffer when refreshing the scrollback. When negative,
// it actually points to entries that are no longer in sb_buffer (because the
// window height has increased) and must be deleted from the terminal buffer
int sb_pending;
+
// buf_T instance that acts as a "drawing surface" for libvterm
// we can't store a direct reference to the buffer because the
// refresh_timer_cb may be called after the buffer was freed, and there's
@@ -130,20 +124,18 @@ struct terminal {
handle_T buf_handle;
// program exited
bool closed, destroy;
+
// some vterm properties
bool forward_mouse;
- // invalid rows libvterm screen
- int invalid_start, invalid_end;
+ int invalid_start, invalid_end; // invalid rows in libvterm screen
struct {
int row, col;
bool visible;
} cursor;
- // which mouse button is pressed
- int pressed_button;
- // pending width/height
- bool pending_resize;
- // With a reference count of 0 the terminal can be freed.
- size_t refcount;
+ int pressed_button; // which mouse button is pressed
+ bool pending_resize; // pending width/height
+
+ size_t refcount; // reference count
};
static VTermScreenCallbacks vterm_screen_callbacks = {
@@ -174,7 +166,7 @@ void terminal_init(void)
VTerm *vt = vterm_new(24, 80);
VTermState *state = vterm_obtain_state(vt);
- for (int color_index = 0; color_index < 256; color_index++) {
+ for (int color_index = 255; color_index >= 0; color_index--) {
VTermColor color;
// Some of the default 16 colors has the same color as the later
// 240 colors. To avoid collisions, we will use the custom colors
@@ -237,25 +229,22 @@ Terminal *terminal_open(TerminalOptions opts)
rv->invalid_end = opts.height;
refresh_screen(rv, curbuf);
set_option_value((uint8_t *)"buftype", 0, (uint8_t *)"terminal", OPT_LOCAL);
- // some sane settings for terminal buffers
+
+ // Default settings for terminal buffers
+ curbuf->b_p_ma = false; // 'nomodifiable'
+ curbuf->b_p_ul = -1; // disable undo
+ curbuf->b_p_scbk = 1000; // 'scrollback'
set_option_value((uint8_t *)"wrap", false, NULL, OPT_LOCAL);
set_option_value((uint8_t *)"number", false, NULL, OPT_LOCAL);
set_option_value((uint8_t *)"relativenumber", false, NULL, OPT_LOCAL);
buf_set_term_title(curbuf, (char *)curbuf->b_ffname);
RESET_BINDING(curwin);
- // Apply TermOpen autocmds so the user can configure the terminal
+
+ // Apply TermOpen autocmds _before_ configuring the scrollback buffer.
apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, curbuf);
- // Configure the scrollback buffer. Try to get the size from:
- //
- // - b:terminal_scrollback_buffer_size
- // - g:terminal_scrollback_buffer_size
- // - SCROLLBACK_BUFFER_DEFAULT_SIZE
- //
- // but limit to 100k.
- int size = get_config_int("terminal_scrollback_buffer_size");
- rv->sb_size = size > 0 ? (size_t)size : SCROLLBACK_BUFFER_DEFAULT_SIZE;
- rv->sb_size = MIN(rv->sb_size, 100000);
+ // Configure the scrollback buffer.
+ rv->sb_size = curbuf->b_p_scbk < 0 ? SB_MAX : (size_t)curbuf->b_p_scbk;;
rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size);
if (!true_color) {
@@ -334,22 +323,22 @@ void terminal_close(Terminal *term, char *msg)
void terminal_resize(Terminal *term, uint16_t width, uint16_t height)
{
if (term->closed) {
- // will be called after exited if two windows display the same terminal and
- // one of the is closed as a consequence of pressing a key.
+ // If two windows display the same terminal and one is closed by keypress.
return;
}
+ bool force = width == UINT16_MAX || height == UINT16_MAX;
int curwidth, curheight;
vterm_get_size(term->vt, &curheight, &curwidth);
- if (!width) {
+ if (force || !width) {
width = (uint16_t)curwidth;
}
- if (!height) {
+ if (force || !height) {
height = (uint16_t)curheight;
}
- if (curheight == height && curwidth == width) {
+ if (!force && curheight == height && curwidth == width) {
return;
}
@@ -381,8 +370,7 @@ void terminal_enter(void)
State = TERM_FOCUS;
mapped_ctrl_c |= TERM_FOCUS; // Always map CTRL-C to avoid interrupt.
RedrawingDisabled = false;
- // go to the bottom when the terminal is focused
- adjust_topline(s->term, buf, false);
+ adjust_topline(s->term, buf, 0); // scroll to end
// erase the unfocused cursor
invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1);
showmode();
@@ -623,12 +611,12 @@ static void buf_set_term_title(buf_T *buf, char *title)
FUNC_ATTR_NONNULL_ALL
{
Error err;
- dict_set_value(buf->b_vars,
- cstr_as_string("term_title"),
- STRING_OBJ(cstr_as_string(title)),
- false,
- false,
- &err);
+ dict_set_var(buf->b_vars,
+ STATIC_CSTR_AS_STRING("term_title"),
+ STRING_OBJ(cstr_as_string(title)),
+ false,
+ false,
+ &err);
}
static int term_settermprop(VTermProp prop, VTermValue *val, void *data)
@@ -667,10 +655,15 @@ static int term_bell(void *data)
return 1;
}
-// the scrollback push/pop handlers were copied almost verbatim from pangoterm
+// Scrollback push handler (from pangoterm).
static int term_sb_push(int cols, const VTermScreenCell *cells, void *data)
{
Terminal *term = data;
+
+ if (!term->sb_size) {
+ return 0;
+ }
+
// copy vterm cells into sb_buffer
size_t c = (size_t)cols;
ScrollbackLine *sbrow = NULL;
@@ -682,10 +675,12 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data)
xfree(term->sb_buffer[term->sb_current - 1]);
}
+ // Make room at the start by shifting to the right.
memmove(term->sb_buffer + 1, term->sb_buffer,
sizeof(term->sb_buffer[0]) * (term->sb_current - 1));
} else if (term->sb_current > 0) {
+ // Make room at the start by shifting to the right.
memmove(term->sb_buffer + 1, term->sb_buffer,
sizeof(term->sb_buffer[0]) * term->sb_current);
}
@@ -695,6 +690,7 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data)
sbrow->cols = c;
}
+ // New row is added at the start of the storage buffer.
term->sb_buffer[0] = sbrow;
if (term->sb_current < term->sb_size) {
term->sb_current++;
@@ -710,6 +706,11 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data)
return 1;
}
+/// Scrollback pop handler (from pangoterm).
+///
+/// @param cols
+/// @param cells VTerm state to update.
+/// @param data Terminal
static int term_sb_pop(int cols, VTermScreenCell *cells, void *data)
{
Terminal *term = data;
@@ -722,24 +723,24 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data)
term->sb_pending--;
}
- // restore vterm state
- size_t c = (size_t)cols;
ScrollbackLine *sbrow = term->sb_buffer[0];
term->sb_current--;
+ // Forget the "popped" row by shifting the rest onto it.
memmove(term->sb_buffer, term->sb_buffer + 1,
sizeof(term->sb_buffer[0]) * (term->sb_current));
- size_t cols_to_copy = c;
+ size_t cols_to_copy = (size_t)cols;
if (cols_to_copy > sbrow->cols) {
cols_to_copy = sbrow->cols;
}
// copy to vterm state
memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy);
- for (size_t col = cols_to_copy; col < c; col++) {
+ for (size_t col = cols_to_copy; col < (size_t)cols; col++) {
cells[col].chars[0] = 0;
cells[col].width = 1;
}
+
xfree(sbrow);
pmap_put(ptr_t)(invalidated_terminals, term, NULL);
@@ -885,7 +886,7 @@ static bool send_mouse_event(Terminal *term, int c)
// terminal buffer refresh & misc {{{
-void fetch_row(Terminal *term, int row, int end_col)
+static void fetch_row(Terminal *term, int row, int end_col)
{
int col = 0;
size_t line_len = 0;
@@ -958,23 +959,23 @@ static void refresh_terminal(Terminal *term)
buf_T *buf = handle_get_buffer(term->buf_handle);
bool valid = true;
if (!buf || !(valid = buf_valid(buf))) {
- // destroyed by `close_buffer`. Dont do anything else
+ // Destroyed by `close_buffer`. Do not do anything else.
if (!valid) {
term->buf_handle = 0;
}
return;
}
- bool pending_resize = term->pending_resize;
+ long ml_before = buf->b_ml.ml_line_count;
WITH_BUFFER(buf, {
refresh_size(term, buf);
refresh_scrollback(term, buf);
refresh_screen(term, buf);
redraw_buf_later(buf, NOT_VALID);
});
- adjust_topline(term, buf, pending_resize);
+ long ml_added = buf->b_ml.ml_line_count - ml_before;
+ adjust_topline(term, buf, ml_added);
}
-// libuv timer callback. This will enqueue on_refresh to be processed as an
-// event.
+// Calls refresh_terminal() on all invalidated_terminals.
static void refresh_timer_cb(TimeWatcher *watcher, void *data)
{
if (exiting) { // Cannot redraw (requires event loop) during teardown/exit.
@@ -1008,7 +1009,37 @@ static void refresh_size(Terminal *term, buf_T *buf)
term->opts.resize_cb((uint16_t)width, (uint16_t)height, term->opts.data);
}
-// Refresh the scrollback of a invalidated terminal
+/// Adjusts scrollback storage after 'scrollback' option changed.
+static void on_scrollback_option_changed(Terminal *term, buf_T *buf)
+{
+ const size_t scbk = curbuf->b_p_scbk < 0
+ ? SB_MAX : (size_t)MAX(1, curbuf->b_p_scbk);
+ assert(term->sb_current < SIZE_MAX);
+ if (term->sb_pending > 0) { // Pending rows must be processed first.
+ abort();
+ }
+
+ // Delete lines exceeding the new 'scrollback' limit.
+ if (scbk < term->sb_current) {
+ size_t diff = term->sb_current - scbk;
+ for (size_t i = 0; i < diff; i++) {
+ ml_delete(1, false);
+ term->sb_current--;
+ xfree(term->sb_buffer[term->sb_current]);
+ }
+ deleted_lines(1, (long)diff);
+ }
+
+ // Resize the scrollback storage.
+ size_t sb_region = sizeof(ScrollbackLine *) * scbk;
+ if (scbk != term->sb_size) {
+ term->sb_buffer = xrealloc(term->sb_buffer, sb_region);
+ }
+
+ term->sb_size = scbk;
+}
+
+// Refresh the scrollback of an invalidated terminal.
static void refresh_scrollback(Terminal *term, buf_T *buf)
{
int width, height;
@@ -1037,9 +1068,11 @@ static void refresh_scrollback(Terminal *term, buf_T *buf)
ml_delete(buf->b_ml.ml_line_count, false);
deleted_lines(buf->b_ml.ml_line_count, 1);
}
+
+ on_scrollback_option_changed(term, buf);
}
-// Refresh the screen(visible part of the buffer when the terminal is
+// Refresh the screen (visible part of the buffer when the terminal is
// focused) of a invalidated terminal
static void refresh_screen(Terminal *term, buf_T *buf)
{
@@ -1048,8 +1081,7 @@ static void refresh_screen(Terminal *term, buf_T *buf)
int height;
int width;
vterm_get_size(term->vt, &height, &width);
- // It's possible that the terminal height decreased and `term->invalid_end`
- // doesn't reflect it yet
+ // Terminal height may have decreased before `invalid_end` reflects it.
term->invalid_end = MIN(term->invalid_end, height);
for (int r = term->invalid_start, linenr = row_to_linenr(term, r);
@@ -1094,14 +1126,6 @@ static void redraw(bool restore_cursor)
update_screen(0);
}
- redraw_statuslines();
-
- if (need_maketitle) {
- maketitle();
- }
-
- showruler(false);
-
if (term && is_focused(term)) {
curwin->w_wrow = term->cursor.row;
curwin->w_wcol = term->cursor.col + win_col_off(curwin);
@@ -1121,21 +1145,21 @@ static void redraw(bool restore_cursor)
ui_flush();
}
-static void adjust_topline(Terminal *term, buf_T *buf, bool force)
+static void adjust_topline(Terminal *term, buf_T *buf, long added)
{
int height, width;
vterm_get_size(term->vt, &height, &width);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer == buf) {
- // for every window that displays a terminal, ensure the cursor is in a
- // valid line
- wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, buf->b_ml.ml_line_count);
- if (force || curbuf != buf || is_focused(term)) {
- // if the terminal is not in the current window or if it's focused,
- // adjust topline/cursor so the window will "follow" the terminal
- // output
- wp->w_cursor.lnum = buf->b_ml.ml_line_count;
+ linenr_T ml_end = buf->b_ml.ml_line_count;
+ bool following = ml_end == wp->w_cursor.lnum + added; // cursor at end?
+ if (following || (wp == curwin && is_focused(term))) {
+ // "Follow" the terminal output
+ wp->w_cursor.lnum = ml_end;
set_topline(wp, MAX(wp->w_cursor.lnum - height + 1, 1));
+ } else {
+ // Ensure valid cursor for each window displaying this terminal.
+ wp->w_cursor.lnum = MIN(wp->w_cursor.lnum, ml_end);
}
}
}
@@ -1178,17 +1202,6 @@ static char *get_config_string(char *key)
return NULL;
}
-static int get_config_int(char *key)
-{
- Object obj;
- GET_CONFIG_VALUE(key, obj);
- if (obj.type == kObjectTypeInteger) {
- return (int)obj.data.integer;
- }
- api_free_object(obj);
- return 0;
-}
-
// }}}
// vim: foldmethod=marker
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index 721300c334..6f4e0fe49f 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -34,12 +34,14 @@ NEW_TESTS ?= \
test_cscope.res \
test_digraph.res \
test_diffmode.res \
+ test_filter_map.res \
test_gn.res \
test_hardcopy.res \
test_help_tagjump.res \
test_history.res \
test_increment.res \
test_increment_dbcs.res \
+ test_lambda.res \
test_langmap.res \
test_match.res \
test_matchadd_conceal.res \
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index 316aba968d..5299fec7c2 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -62,6 +62,12 @@ lang mess C
" Always use forward slashes.
set shellslash
+" Make sure $HOME does not get read or written.
+let $HOME = '/does/not/exist'
+
+" Prepare for calling garbagecollect_for_testing().
+let v:testing = 1
+
" Align with vim defaults.
set directory^=.
set nohidden
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index 60248bf430..04cc279619 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -9,8 +9,10 @@ source test_ex_undo.vim
source test_expr.vim
source test_expr_utf8.vim
source test_feedkeys.vim
+source test_filter_map.vim
source test_goto.vim
source test_jumps.vim
+source test_lambda.vim
source test_match.vim
source test_matchadd_conceal_utf8.vim
source test_menu.vim
@@ -23,6 +25,7 @@ source test_statusline.vim
source test_syn_attr.vim
source test_tabline.vim
source test_tabpage.vim
+source test_tagcase.vim
source test_tagjump.vim
source test_unlet.vim
source test_window_cmd.vim
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 21bb057fe1..40db227d97 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -85,6 +85,11 @@ func Test_getcompletion()
let l = getcompletion('paint', 'function')
call assert_equal([], l)
+ let Flambda = {-> 'hello'}
+ let l = getcompletion('', 'function')
+ let l = filter(l, {i, v -> v =~ 'lambda'})
+ call assert_equal(0, len(l))
+
let l = getcompletion('run', 'file')
call assert_true(index(l, 'runtest.vim') >= 0)
let l = getcompletion('walk', 'file')
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index 39dcacb55f..7ceef2834f 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -106,3 +106,68 @@ func Test_setmatches()
call setmatches(set)
call assert_equal(exp, getmatches())
endfunc
+
+func Test_substitute_expr()
+ let g:val = 'XXX'
+ call assert_equal('XXX', substitute('yyy', 'y*', '\=g:val', ''))
+ call assert_equal('XXX', substitute('yyy', 'y*', {-> g:val}, ''))
+ call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)',
+ \ '\=nr2char("0x" . submatch(1))', 'g'))
+ call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)',
+ \ {-> nr2char("0x" . submatch(1))}, 'g'))
+
+ call assert_equal('231', substitute('123', '\(.\)\(.\)\(.\)',
+ \ {-> submatch(2) . submatch(3) . submatch(1)}, ''))
+
+ func Recurse()
+ return substitute('yyy', 'y*', {-> g:val}, '')
+ endfunc
+ call assert_equal('--', substitute('xxx', 'x*', {-> '-' . Recurse() . '-'}, ''))
+endfunc
+
+func Test_invalid_submatch()
+ " This was causing invalid memory access in Vim-7.4.2232 and older
+ call assert_fails("call substitute('x', '.', {-> submatch(10)}, '')", 'E935:')
+endfunc
+
+func Test_substitute_expr_arg()
+ call assert_equal('123456789-123456789=', substitute('123456789',
+ \ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)',
+ \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
+
+ call assert_equal('123456-123456=789', substitute('123456789',
+ \ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)',
+ \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
+
+ call assert_equal('123456789-123456789x=', substitute('123456789',
+ \ '\(.\)\(.\)\(.*\)',
+ \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, ''))
+
+ call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:')
+ call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:')
+ call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:')
+ call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:')
+endfunc
+
+func Test_function_with_funcref()
+ let s:f = function('type')
+ let s:fref = function(s:f)
+ call assert_equal(v:t_string, s:fref('x'))
+ call assert_fails("call function('s:f')", 'E700:')
+endfunc
+
+func Test_funcref()
+ func! One()
+ return 1
+ endfunc
+ let OneByName = function('One')
+ let OneByRef = funcref('One')
+ func! One()
+ return 2
+ endfunc
+ call assert_equal(2, OneByName())
+ call assert_equal(1, OneByRef())
+ let OneByRef = funcref('One')
+ call assert_equal(2, OneByRef())
+ call assert_fails('echo funcref("{")', 'E475:')
+endfunc
diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim
new file mode 100644
index 0000000000..c8d64ce0a4
--- /dev/null
+++ b/src/nvim/testdir/test_filter_map.vim
@@ -0,0 +1,81 @@
+" Test filter() and map()
+
+" list with expression string
+func Test_filter_map_list_expr_string()
+ " filter()
+ call assert_equal([2, 3, 4], filter([1, 2, 3, 4], 'v:val > 1'))
+ call assert_equal([3, 4], filter([1, 2, 3, 4], 'v:key > 1'))
+ call assert_equal([], filter([1, 2, 3, 4], 0))
+
+ " map()
+ call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2'))
+ call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
+ call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9))
+endfunc
+
+" dict with expression string
+func Test_filter_map_dict_expr_string()
+ let dict = {"foo": 1, "bar": 2, "baz": 3}
+
+ " filter()
+ call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), 'v:val > 1'))
+ call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), 'v:key > "bar"'))
+ call assert_equal({}, filter(copy(dict), 0))
+
+ " map()
+ call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2'))
+ call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
+ call assert_equal({"foo": 9, "bar": 9, "baz": 9}, map(copy(dict), 9))
+endfunc
+
+" list with funcref
+func Test_filter_map_list_expr_funcref()
+ " filter()
+ func! s:filter1(index, val) abort
+ return a:val > 1
+ endfunc
+ call assert_equal([2, 3, 4], filter([1, 2, 3, 4], function('s:filter1')))
+
+ func! s:filter2(index, val) abort
+ return a:index > 1
+ endfunc
+ call assert_equal([3, 4], filter([1, 2, 3, 4], function('s:filter2')))
+
+ " map()
+ func! s:filter3(index, val) abort
+ return a:val * 2
+ endfunc
+ call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], function('s:filter3')))
+
+ func! s:filter4(index, val) abort
+ return a:index * 2
+ endfunc
+ call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
+endfunc
+
+" dict with funcref
+func Test_filter_map_dict_expr_funcref()
+ let dict = {"foo": 1, "bar": 2, "baz": 3}
+
+ " filter()
+ func! s:filter1(key, val) abort
+ return a:val > 1
+ endfunc
+ call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), function('s:filter1')))
+
+ func! s:filter2(key, val) abort
+ return a:key > "bar"
+ endfunc
+ call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), function('s:filter2')))
+
+ " map()
+ func! s:filter3(key, val) abort
+ return a:val * 2
+ endfunc
+ call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), function('s:filter3')))
+
+ func! s:filter4(key, val) abort
+ return a:key[0]
+ endfunc
+ call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
+endfunc
diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim
new file mode 100644
index 0000000000..311cc6e2cb
--- /dev/null
+++ b/src/nvim/testdir/test_lambda.vim
@@ -0,0 +1,287 @@
+" Test for lambda and closure
+
+function! Test_lambda_feature()
+ call assert_equal(1, has('lambda'))
+endfunction
+
+function! Test_lambda_with_filter()
+ let s:x = 2
+ call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x}))
+endfunction
+
+function! Test_lambda_with_map()
+ let s:x = 1
+ call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x}))
+endfunction
+
+function! Test_lambda_with_sort()
+ call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b}))
+endfunction
+
+function! Test_lambda_with_timer()
+ if !has('timers')
+ return
+ endif
+
+ let s:n = 0
+ let s:timer_id = 0
+ function! s:Foo()
+ "let n = 0
+ let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1})
+ endfunction
+
+ call s:Foo()
+ sleep 200ms
+ " do not collect lambda
+ call garbagecollect()
+ let m = s:n
+ sleep 200ms
+ call timer_stop(s:timer_id)
+ call assert_true(m > 1)
+ call assert_true(s:n > m + 1)
+ call assert_true(s:n < 9)
+endfunction
+
+function! Test_lambda_with_partial()
+ let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two'])
+ call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three'))
+endfunction
+
+function Test_lambda_fails()
+ call assert_equal(3, {a, b -> a + b}(1, 2))
+ call assert_fails('echo {a, a -> a + a}(1, 2)', 'E15:')
+ call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:')
+endfunc
+
+func Test_not_lambda()
+ let x = {'>' : 'foo'}
+ call assert_equal('foo', x['>'])
+endfunc
+
+function! Test_lambda_capture_by_reference()
+ let v = 1
+ let l:F = {x -> x + v}
+ let v = 2
+ call assert_equal(12, l:F(10))
+endfunction
+
+function! Test_lambda_side_effect()
+ function! s:update_and_return(arr)
+ let a:arr[1] = 5
+ return a:arr
+ endfunction
+
+ function! s:foo(arr)
+ return {-> s:update_and_return(a:arr)}
+ endfunction
+
+ let arr = [3,2,1]
+ call assert_equal([3, 5, 1], s:foo(arr)())
+endfunction
+
+function! Test_lambda_refer_local_variable_from_other_scope()
+ function! s:foo(X)
+ return a:X() " refer l:x in s:bar()
+ endfunction
+
+ function! s:bar()
+ let x = 123
+ return s:foo({-> x})
+ endfunction
+
+ call assert_equal(123, s:bar())
+endfunction
+
+function! Test_lambda_do_not_share_local_variable()
+ function! s:define_funcs()
+ let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]}
+ let l:Two = {-> exists("a") ? a : "no"}
+ return [l:One, l:Two]
+ endfunction
+
+ let l:F = s:define_funcs()
+
+ call assert_equal('no', l:F[1]())
+ call assert_equal('abc', l:F[0]())
+ call assert_equal('no', l:F[1]())
+endfunction
+
+function! Test_lambda_closure_counter()
+ function! s:foo()
+ let x = 0
+ return {-> [execute("let x += 1"), x][-1]}
+ endfunction
+
+ let l:F = s:foo()
+ call garbagecollect()
+ call assert_equal(1, l:F())
+ call assert_equal(2, l:F())
+ call assert_equal(3, l:F())
+ call assert_equal(4, l:F())
+endfunction
+
+function! Test_lambda_with_a_var()
+ function! s:foo()
+ let x = 2
+ return {... -> a:000 + [x]}
+ endfunction
+ function! s:bar()
+ return s:foo()(1)
+ endfunction
+
+ call assert_equal([1, 2], s:bar())
+endfunction
+
+function! Test_lambda_call_lambda_from_lambda()
+ function! s:foo(x)
+ let l:F1 = {-> {-> a:x}}
+ return {-> l:F1()}
+ endfunction
+
+ let l:F = s:foo(1)
+ call assert_equal(1, l:F()())
+endfunction
+
+function! Test_lambda_delfunc()
+ function! s:gen()
+ let pl = l:
+ let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))}
+ let l:Bar = l:Foo
+ delfunction l:Foo
+ return l:Bar
+ endfunction
+
+ let l:F = s:gen()
+ call assert_fails(':call l:F()', 'E933:')
+endfunction
+
+function! Test_lambda_scope()
+ function! s:NewCounter()
+ let c = 0
+ return {-> [execute('let c += 1'), c][-1]}
+ endfunction
+
+ function! s:NewCounter2()
+ return {-> [execute('let c += 100'), c][-1]}
+ endfunction
+
+ let l:C = s:NewCounter()
+ let l:D = s:NewCounter2()
+
+ call assert_equal(1, l:C())
+ call assert_fails(':call l:D()', 'E15:') " E121: then E15:
+ call assert_equal(2, l:C())
+endfunction
+
+function! Test_lambda_share_scope()
+ function! s:New()
+ let c = 0
+ let l:Inc0 = {-> [execute('let c += 1'), c][-1]}
+ let l:Dec0 = {-> [execute('let c -= 1'), c][-1]}
+ return [l:Inc0, l:Dec0]
+ endfunction
+
+ let [l:Inc, l:Dec] = s:New()
+
+ call assert_equal(1, l:Inc())
+ call assert_equal(2, l:Inc())
+ call assert_equal(1, l:Dec())
+endfunction
+
+function! Test_lambda_circular_reference()
+ function! s:Foo()
+ let d = {}
+ let d.f = {-> d}
+ return d.f
+ endfunction
+
+ call s:Foo()
+ call garbagecollect()
+ let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile
+ call garbagecollect()
+endfunction
+
+function! Test_lambda_combination()
+ call assert_equal(2, {x -> {x -> x}}(1)(2))
+ call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z}))
+ call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0))
+ call assert_equal(6, {x -> {y -> {z -> x + y + z}}}(1)(2)(3))
+
+ call assert_equal(6, {x -> {f -> f(x)}}(3)({x -> x * 2}))
+ call assert_equal(6, {f -> {x -> f(x)}}({x -> x * 2})(3))
+
+ " Z combinator
+ let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})}
+ let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}}
+ call assert_equal(120, Z(Fact)(5))
+endfunction
+
+function! Test_closure_counter()
+ function! s:foo()
+ let x = 0
+ function! s:bar() closure
+ let x += 1
+ return x
+ endfunction
+ return function('s:bar')
+ endfunction
+
+ let l:F = s:foo()
+ call garbagecollect()
+ call assert_equal(1, l:F())
+ call assert_equal(2, l:F())
+ call assert_equal(3, l:F())
+ call assert_equal(4, l:F())
+endfunction
+
+function! Test_closure_unlet()
+ function! s:foo()
+ let x = 1
+ function! s:bar() closure
+ unlet x
+ endfunction
+ call s:bar()
+ return l:
+ endfunction
+
+ call assert_false(has_key(s:foo(), 'x'))
+ call garbagecollect()
+endfunction
+
+function! LambdaFoo()
+ let x = 0
+ function! LambdaBar() closure
+ let x += 1
+ return x
+ endfunction
+ return function('LambdaBar')
+endfunction
+
+func Test_closure_refcount()
+ let g:Count = LambdaFoo()
+ call test_garbagecollect_now()
+ call assert_equal(1, g:Count())
+ let g:Count2 = LambdaFoo()
+ call test_garbagecollect_now()
+ call assert_equal(1, g:Count2())
+ call assert_equal(2, g:Count())
+ call assert_equal(3, g:Count2())
+
+ delfunc LambdaFoo
+ delfunc LambdaBar
+endfunc
+
+" This test is causing a use-after-free on shutdown.
+func Test_named_function_closure()
+ func! Afoo()
+ let x = 14
+ func! s:Abar() closure
+ return x
+ endfunc
+ call assert_equal(14, s:Abar())
+ endfunc
+ call Afoo()
+ call assert_equal(14, s:Abar())
+ call garbagecollect()
+ call assert_equal(14, s:Abar())
+endfunc
diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim
index 3a6e162453..de5c26c2dd 100644
--- a/src/nvim/testdir/test_partial.vim
+++ b/src/nvim/testdir/test_partial.vim
@@ -14,6 +14,14 @@ func MySort(up, one, two)
return a:one < a:two ? 1 : -1
endfunc
+func MyMap(sub, index, val)
+ return a:val - a:sub
+endfunc
+
+func MyFilter(threshold, index, val)
+ return a:val > a:threshold
+endfunc
+
func Test_partial_args()
let Cb = function('MyFunc', ["foo", "bar"])
@@ -36,6 +44,16 @@ func Test_partial_args()
call assert_equal([1, 2, 3], sort([3, 1, 2], Sort))
let Sort = function('MySort', [0])
call assert_equal([3, 2, 1], sort([3, 1, 2], Sort))
+
+ let Map = function('MyMap', [2])
+ call assert_equal([-1, 0, 1], map([1, 2, 3], Map))
+ let Map = function('MyMap', [3])
+ call assert_equal([-2, -1, 0], map([1, 2, 3], Map))
+
+ let Filter = function('MyFilter', [1])
+ call assert_equal([2, 3], filter([1, 2, 3], Filter))
+ let Filter = function('MyFilter', [2])
+ call assert_equal([3], filter([1, 2, 3], Filter))
endfunc
func MyDictFunc(arg1, arg2) dict
@@ -59,6 +77,9 @@ func Test_partial_dict()
call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy"))
call assert_fails('Cb("fff")', 'E492:')
+ let Cb = function('MyDictFunc', dict)
+ call assert_equal({"foo": "hello/foo/1", "bar": "hello/bar/2"}, map({"foo": 1, "bar": 2}, Cb))
+
let dict = {"tr": function('tr', ['hello', 'h', 'H'])}
call assert_equal("Hello", dict.tr())
endfunc
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index 50110dd622..edc6336aa7 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -482,4 +482,24 @@ func Test_completion_ctrl_e_without_autowrap()
q!
endfunc
+func CompleteUndo() abort
+ call complete(1, g:months)
+ return ''
+endfunc
+
+func Test_completion_can_undo()
+ inoremap <Right> <c-r>=CompleteUndo()<cr>
+ set completeopt+=noinsert,noselect
+
+ new
+ call feedkeys("a\<Right>a\<Esc>", 'xt')
+ call assert_equal('a', getline(1))
+ undo
+ call assert_equal('', getline(1))
+
+ bwipe!
+ set completeopt&
+ iunmap <Right>
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 7464a11abd..02f8da8b23 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -904,6 +904,30 @@ function! Test_efm2()
call assert_equal('E', l[0].type)
call assert_equal("\nunknown variable 'i'", l[0].text)
+ " Test for %A, %C and other formats
+ let lines = [
+ \"==============================================================",
+ \"FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest)",
+ \"--------------------------------------------------------------",
+ \"Traceback (most recent call last):",
+ \' File "unittests/dbfacadeTest.py", line 89, in testFoo',
+ \" self.assertEquals(34, dtid)",
+ \' File "/usr/lib/python2.2/unittest.py", line 286, in',
+ \" failUnlessEqual",
+ \" raise self.failureException, \\",
+ \"AssertionError: 34 != 33",
+ \"",
+ \"--------------------------------------------------------------",
+ \"Ran 27 tests in 0.063s"
+ \]
+ set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%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('unittests/dbfacadeTest.py', bufname(l[4].bufnr))
+
let &efm = save_efm
endfunction
@@ -1462,3 +1486,42 @@ func Test_duplicate_buf()
call delete('Xgrepthis')
endfunc
+
+" Quickfix/Location list set/get properties tests
+function Xproperty_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Error cases
+ call assert_fails('call g:Xgetlist(99)', 'E715:')
+ call assert_fails('call g:Xsetlist(99)', 'E714:')
+ call assert_fails('call g:Xsetlist([], "a", [])', 'E715:')
+
+ " Set and get the title
+ Xopen
+ wincmd p
+ call g:Xsetlist([{'filename':'foo', 'lnum':27}])
+ call g:Xsetlist([], 'a', {'title' : 'Sample'})
+ let d = g:Xgetlist({"title":1})
+ call assert_equal('Sample', d.title)
+
+ Xopen
+ call assert_equal('Sample', w:quickfix_title)
+ Xclose
+
+ " Invalid arguments
+ call assert_fails('call g:Xgetlist([])', 'E715')
+ call assert_fails('call g:Xsetlist([], "a", [])', 'E715')
+ let s = g:Xsetlist([], 'a', {'abc':1})
+ call assert_equal(-1, s)
+
+ call assert_equal({}, g:Xgetlist({'abc':1}))
+
+ if a:cchar == 'l'
+ call assert_equal({}, getloclist(99, ['title']))
+ endif
+endfunction
+
+function Test_qf_property()
+ call Xproperty_tests('c')
+ call Xproperty_tests('l')
+endfunction
diff --git a/src/nvim/testdir/test_tagcase.vim b/src/nvim/testdir/test_tagcase.vim
new file mode 100644
index 0000000000..833cb9f990
--- /dev/null
+++ b/src/nvim/testdir/test_tagcase.vim
@@ -0,0 +1,73 @@
+" test 'tagcase' option
+
+func Test_tagcase()
+ call writefile(["Bar\tXtext\t3", "Foo\tXtext\t2", "foo\tXtext\t4"], 'Xtags')
+ set tags=Xtags
+ e Xtext
+
+ for &ic in [0, 1]
+ for &scs in [0, 1]
+ for &g:tc in ["followic", "ignore", "match", "followscs", "smart"]
+ for &l:tc in ["", "followic", "ignore", "match", "followscs", "smart"]
+ let smart = 0
+ if &l:tc != ''
+ let tc = &l:tc
+ else
+ let tc = &g:tc
+ endif
+ if tc == 'followic'
+ let ic = &ic
+ elseif tc == 'ignore'
+ let ic = 1
+ elseif tc == 'followscs'
+ let ic = &ic
+ let smart = &scs
+ elseif tc == 'smart'
+ let ic = 1
+ let smart = 1
+ else
+ let ic = 0
+ endif
+ if ic && smart
+ call assert_equal(['foo', 'Foo'], map(taglist("^foo$"), {i, v -> v.name}))
+ call assert_equal(['Foo'], map(taglist("^Foo$"), {i, v -> v.name}))
+ elseif ic
+ call assert_equal(['foo', 'Foo'], map(taglist("^foo$"), {i, v -> v.name}))
+ call assert_equal(['Foo', 'foo'], map(taglist("^Foo$"), {i, v -> v.name}))
+ else
+ call assert_equal(['foo'], map(taglist("^foo$"), {i, v -> v.name}))
+ call assert_equal(['Foo'], map(taglist("^Foo$"), {i, v -> v.name}))
+ endif
+ endfor
+ endfor
+ endfor
+ endfor
+
+ call delete('Xtags')
+ set ic&
+ setg tc&
+ setl tc&
+ set scs&
+endfunc
+
+func Test_set_tagcase()
+ " Verify default values.
+ set ic&
+ setg tc&
+ setl tc&
+ call assert_equal(0, &ic)
+ call assert_equal('followic', &g:tc)
+ call assert_equal('followic', &l:tc)
+ call assert_equal('followic', &tc)
+
+ " Verify that the local setting accepts <empty> but that the global setting
+ " does not. The first of these (setting the local value to <empty>) should
+ " succeed; the other two should fail.
+ setl tc=
+ call assert_fails('setg tc=', 'E474:')
+ call assert_fails('set tc=', 'E474:')
+
+ set ic&
+ setg tc&
+ setl tc&
+endfunc
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 8cedfcb905..c95a795587 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -305,16 +305,12 @@ bool undo_allowed(void)
return true;
}
-/*
- * Get the undolevle value for the current buffer.
- */
+/// Get the 'undolevels' value for the current buffer.
static long get_undolevel(void)
{
- if (curbuf->terminal) {
- return -1;
- }
- if (curbuf->b_p_ul == NO_LOCAL_UNDOLEVEL)
+ if (curbuf->b_p_ul == NO_LOCAL_UNDOLEVEL) {
return p_ul;
+ }
return curbuf->b_p_ul;
}
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 096af415ba..fe72ee11f2 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -205,12 +205,12 @@ static int included_patches[] = {
// 2238 NA
2237,
// 2236,
- // 2235,
+ 2235,
// 2234 NA
- // 2233,
+ 2233,
// 2232 NA
// 2231,
- // 2230,
+ 2230,
// 2229,
// 2228,
2227,
@@ -240,10 +240,10 @@ static int included_patches[] = {
// 2203 NA
// 2202 NA
2201,
- // 2200,
+ 2200,
// 2199 NA
// 2198 NA
- // 2197,
+ 2197,
// 2196,
// 2195 NA
2194,
@@ -297,16 +297,16 @@ static int included_patches[] = {
2146,
// 2145 NA
// 2144,
- // 2143,
- // 2142,
- // 2141,
+ 2143,
+ 2142,
+ 2141,
// 2140 NA
- // 2139,
+ 2139,
// 2138 NA
- // 2137,
- // 2136,
+ 2137,
+ 2136,
// 2135,
- // 2134,
+ 2134,
// 2133 NA
// 2132 NA
// 2131 NA
@@ -319,9 +319,9 @@ static int included_patches[] = {
2124,
2123,
// 2122 NA
- // 2121,
- // 2120,
- // 2119,
+ 2121,
+ 2120,
+ 2119,
// 2118 NA
2117,
// 2116 NA
@@ -344,13 +344,13 @@ static int included_patches[] = {
2099,
// 2098,
// 2097,
- // 2096,
+ 2096,
// 2095,
// 2094 NA
// 2093 NA
// 2092 NA
// 2091 NA
- // 2090,
+ 2090,
// 2089 NA
2088,
2087,
@@ -364,11 +364,11 @@ static int included_patches[] = {
// 2079 NA
// 2078 NA
2077,
- // 2076,
+ 2076,
2075,
2074,
// 2073 NA
- // 2072,
+ 2072,
2071,
// 2070 NA
// 2069,
@@ -379,7 +379,7 @@ static int included_patches[] = {
2064,
// 2063 NA
2062,
- // 2061,
+ 2061,
// 2060 NA
// 2059 NA
// 2058,
@@ -393,10 +393,10 @@ static int included_patches[] = {
2050,
2049,
// 2048 NA
- // 2047,
- // 2046,
+ 2047,
+ 2046,
// 2045 NA
- // 2044,
+ 2044,
2043,
// 2042 NA
// 2041 NA
@@ -438,7 +438,7 @@ static int included_patches[] = {
2005,
// 2004 NA
// 2003 NA
- // 2002,
+ 2002,
// 2001 NA
2000,
1999,
@@ -451,7 +451,7 @@ static int included_patches[] = {
// 1992,
// 1991,
1990,
- // 1989,
+ 1989,
// 1988 NA
// 1987 NA
// 1986,
@@ -714,7 +714,7 @@ static int included_patches[] = {
1730,
// 1729 NA
1728,
- // 1727 NA
+ 1727,
// 1726 NA
// 1725 NA
// 1724 NA
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 28269e8889..73a60b2e04 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -4848,7 +4848,7 @@ void scroll_to_fraction(win_T *wp, int prev_height)
if (wp->w_buffer->terminal) {
terminal_resize(wp->w_buffer->terminal, 0, wp->w_height);
- redraw_win_later(wp, CLEAR);
+ redraw_win_later(wp, NOT_VALID);
}
}
@@ -4872,7 +4872,6 @@ void win_new_width(win_T *wp, int width)
if (wp->w_height != 0) {
terminal_resize(wp->w_buffer->terminal, wp->w_width, 0);
}
- redraw_win_later(wp, CLEAR);
}
}