diff options
Diffstat (limited to 'src')
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(¤t_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(®match, 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(®match, 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(®match, sub, (char_u *)ga.ga_data - + ga.ga_len + i, TRUE, TRUE, FALSE); + // add the substituted text + (void)vim_regsub(®match, 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(®match, 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(®match, 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); } } |