Merge #6112 from ZyX-I/split-eval'/buf_get_changedtick

Better b:changedtick support
This commit is contained in:
Justin M. Keyes 2017-02-27 10:29:46 +01:00 committed by GitHub
commit c318d8e672
27 changed files with 688 additions and 363 deletions

View File

@ -8606,9 +8606,12 @@ This does NOT work: >
:lockvar v :lockvar v
:let v = 'asdf' " fails! :let v = 'asdf' " fails!
:unlet v :unlet v
< *E741* < *E741* *E940*
If you try to change a locked variable you get an If you try to change a locked variable you get an
error message: "E741: Value is locked: {name}" error message: "E741: Value is locked: {name}".
If you try to lock or unlock a built-in variable you
will get an error message "E940: Cannot lock or unlock
variable {name}".
[depth] is relevant when locking a |List| or [depth] is relevant when locking a |List| or
|Dictionary|. It specifies how deep the locking goes: |Dictionary|. It specifies how deep the locking goes:

View File

@ -411,10 +411,10 @@ end:
/// Gets a buffer-scoped (b:) variable. /// Gets a buffer-scoped (b:) variable.
/// ///
/// @param buffer Buffer handle /// @param buffer The buffer handle
/// @param name Variable name /// @param name The variable name
/// @param[out] err Error details, if any /// @param[out] err Details of an error that may have occurred
/// @return Variable value /// @return The variable value
Object nvim_buf_get_var(Buffer buffer, String name, Error *err) Object nvim_buf_get_var(Buffer buffer, String name, Error *err)
{ {
buf_T *buf = find_buffer_by_handle(buffer, 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); 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 /// Sets a buffer-scoped (b:) variable
/// ///
/// @param buffer Buffer handle /// @param buffer Buffer handle
@ -440,7 +456,7 @@ void nvim_buf_set_var(Buffer buffer, String name, Object value, Error *err)
return; 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 /// Removes a buffer-scoped (b:) variable
@ -456,7 +472,7 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err)
return; 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 /// 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 (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 /// 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 (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);
} }

View File

@ -98,7 +98,7 @@ Object dict_get_value(dict_T *dict, String key, Error *err)
return vim_to_object(&di->di_tv); 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. /// vimscript equivalents.
/// ///
/// @param dict The vimscript dict /// @param dict The vimscript dict
@ -109,7 +109,7 @@ Object dict_get_value(dict_T *dict, String key, Error *err)
/// @param retval If true the old value will be converted and returned. /// @param retval If true the old value will be converted and returned.
/// @param[out] err Details of an error that may have occurred /// @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 /// @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, Object dict_set_var(dict_T *dict, String key, Object value, bool del,
bool retval, Error *err) bool retval, Error *err)
{ {
Object rv = OBJECT_INIT; 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) { 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; return rv;
} }
@ -129,7 +129,20 @@ Object dict_set_value(dict_T *dict, String key, Object value, bool del,
return rv; 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) { if (del) {
// Delete the key // Delete the key

View File

@ -71,7 +71,7 @@ void nvim_tabpage_set_var(Tabpage tabpage,
return; 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 /// Removes a tab-scoped (t:) variable
@ -87,7 +87,7 @@ void nvim_tabpage_del_var(Tabpage tabpage, String name, Error *err)
return; 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 /// 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 (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 /// 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 (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 /// Gets the current window in a tabpage

View File

@ -368,7 +368,7 @@ Object nvim_get_var(String name, Error *err)
/// @param[out] err Error details, if any /// @param[out] err Error details, if any
void nvim_set_var(String name, Object value, Error *err) 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 /// Removes a global (g:) variable
@ -377,7 +377,7 @@ void nvim_set_var(String name, Object value, Error *err)
/// @param[out] err Error details, if any /// @param[out] err Error details, if any
void nvim_del_var(String name, Error *err) 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 /// Sets a global variable
@ -393,7 +393,7 @@ void nvim_del_var(String name, Error *err)
/// or if previous value was `v:null`. /// or if previous value was `v:null`.
Object vim_set_var(String name, Object value, Error *err) 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 /// Removes a global variable
@ -405,7 +405,7 @@ Object vim_set_var(String name, Object value, Error *err)
/// @return Old value /// @return Old value
Object vim_del_var(String name, Error *err) 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 /// Gets a v: variable

View File

@ -210,7 +210,7 @@ void nvim_win_set_var(Window window, String name, Object value, Error *err)
return; 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 /// Removes a window-scoped (w:) variable
@ -226,7 +226,7 @@ void nvim_win_del_var(Window window, String name, Error *err)
return; 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 /// 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 (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 /// 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 (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 /// Gets a window option value

View File

@ -686,8 +686,18 @@ free_buffer_stuff (
free_buf_options(buf, true); free_buf_options(buf, true);
ga_clear(&buf->b_s.b_langp); 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 vars_clear(&buf->b_vars->dv_hashtab); // free all internal variables
hash_init(&buf->b_vars->dv_hashtab); hash_init(&buf->b_vars->dv_hashtab);
buf_init_changedtick(buf);
uc_clear(&buf->b_ucmds); // clear local user commands uc_clear(&buf->b_ucmds); // clear local user commands
buf_delete_signs(buf); // delete any signs buf_delete_signs(buf); // delete any signs
bufhl_clear_all(buf); // delete any highligts bufhl_clear_all(buf); // delete any highligts
@ -1436,6 +1446,26 @@ void do_autochdir(void)
static int top_file_num = 1; ///< highest file number 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. /// Add a file name to the buffer list.
/// If the same file name already exists return a pointer to that buffer. /// 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. /// 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 // init b: variables
buf->b_vars = dict_alloc(); buf->b_vars = dict_alloc();
init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE);
buf_init_changedtick(buf);
} }
if (ffname != NULL) { if (ffname != NULL) {

View File

@ -5,6 +5,8 @@
#include "nvim/pos.h" // for linenr_T #include "nvim/pos.h" // for linenr_T
#include "nvim/ex_cmds_defs.h" // for exarg_T #include "nvim/ex_cmds_defs.h" // for exarg_T
#include "nvim/screen.h" // for StlClickRecord #include "nvim/screen.h" // for StlClickRecord
#include "nvim/func_attr.h"
#include "nvim/eval.h"
// Values for buflist_getfile() // Values for buflist_getfile()
enum getf_values { 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) \ #define WITH_BUFFER(b, code) \
do { \ do { \
win_T *save_curwin = NULL; \ win_T *save_curwin = NULL; \

View File

@ -489,7 +489,9 @@ struct file_buffer {
int b_changed; // 'modified': Set to true if something in the int b_changed; // 'modified': Set to true if something in the
// file has been changed and not written out. // 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 bool b_saving; /* Set to true if we are in the middle of
saving the buffer. */ saving the buffer. */

View File

@ -171,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_nofunc = N_("E130: Unknown function: %s");
static char *e_illvar = N_("E461: Illegal variable name: %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 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 empty_string = (char_u *)"";
static char_u * const namespace_char = (char_u *)"abglstvw"; static char_u * const namespace_char = (char_u *)"abglstvw";
@ -201,15 +203,22 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL};
static int echo_attr = 0; /* attributes used for ":echo" */ static int echo_attr = 0; /* attributes used for ":echo" */
// Values for trans_function_name() argument: /// trans_function_name() flags
#define TFN_INT 1 // internal function name OK typedef enum {
#define TFN_QUIET 2 // no error messages TFN_INT = 1, ///< May use internal function name
#define TFN_NO_AUTOLOAD 4 // do not use script autoloading TFN_QUIET = 2, ///< Do not emit error messages.
#define TFN_NO_DEREF 8 // do not dereference a Funcref 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;
// Values for get_lval() flags argument: /// get_lval() flags
#define GLV_QUIET TFN_QUIET // no error messages typedef enum {
#define GLV_NO_AUTOLOAD TFN_NO_AUTOLOAD // do not use script autoloading 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 // function flags
#define FC_ABORT 0x01 // abort function on error #define FC_ABORT 0x01 // abort function on error
@ -542,7 +551,7 @@ void eval_init(void)
type_list->lv_lock = VAR_FIXED; type_list->lv_lock = VAR_FIXED;
type_list->lv_refcount = 1; type_list->lv_refcount = 1;
dictitem_T *const di = dictitem_alloc((char_u *)msgpack_type_names[i]); dictitem_T *const di = dictitem_alloc((char_u *)msgpack_type_names[i]);
di->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; di->di_flags |= DI_FLAGS_RO|DI_FLAGS_FIX;
di->di_tv = (typval_T) { di->di_tv = (typval_T) {
.v_type = VAR_LIST, .v_type = VAR_LIST,
.vval = { .v_list = type_list, }, .vval = { .v_list = type_list, },
@ -1646,13 +1655,7 @@ static void list_glob_vars(int *first)
*/ */
static void list_buf_vars(int *first) static void list_buf_vars(int *first)
{ {
char numbuf[NUMBUFLEN];
list_hashtable_vars(&curbuf->b_vars->dv_hashtab, "b:", true, first); 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);
} }
/* /*
@ -1949,46 +1952,33 @@ ex_let_one (
return arg_end; return arg_end;
} }
/* /// Get an lvalue
* If "arg" is equal to "b:changedtick" give an error and return TRUE. ///
*/ /// Lvalue may be
static int check_changedtick(char_u *arg) /// - variable: "name", "na{me}"
{ /// - dictionary item: "dict.key", "dict['key']"
if (STRNCMP(arg, "b:changedtick", 13) == 0 && !eval_isnamec(arg[13])) { /// - list item: "list[expr]"
EMSG2(_(e_readonlyvar), arg); /// - list slice: "list[expr:expr]"
return TRUE; ///
} /// Indexing only works if trying to use it with an existing List or Dictionary.
return FALSE; ///
} /// @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`
* Get an lval: variable, Dict item or List item that can be assigned a value /// is NULL.
* to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", /// @param[in] unlet True if using `:unlet`. This results in slightly
* "name.key", "name.key[expr]" etc. /// different behaviour when something is wrong; must end in
* Indexing only works if "name" is an existing List or Dictionary. /// space or cmd separator.
* "name" points to the start of the name. /// @param[in] skip True when skipping.
* If "rettv" is not NULL it points to the value to be assigned. /// @param[in] flags @see GetLvalFlags.
* "unlet" is TRUE for ":unlet": slightly different behavior when something is /// @param[in] fne_flags Flags for find_name_end().
* wrong; must end in space or cmd separator. ///
* /// @return A pointer to just after the name, including indexes. Returns NULL
* flags: /// for a parsing error, but it is still needed to free items in lp.
* GLV_QUIET: do not give error messages static char_u *get_lval(char_u *const name, typval_T *const rettv,
* GLV_NO_AUTOLOAD: do not use script autoloading lval_T *const lp, const bool unlet, const bool skip,
* const int flags, const int fne_flags)
* Returns a pointer to just after the name, including indexes. FUNC_ATTR_NONNULL_ARG(1, 3)
* 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() */
)
{ {
char_u *p; char_u *p;
char_u *expr_start, *expr_end; char_u *expr_start, *expr_end;
@ -2207,8 +2197,13 @@ get_lval (
if (len == -1) if (len == -1)
clear_tv(&var1); clear_tv(&var1);
break; 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; return NULL;
} }
@ -2299,7 +2294,6 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch
dictitem_T *di; dictitem_T *di;
if (lp->ll_tv == NULL) { if (lp->ll_tv == NULL) {
if (!check_changedtick(lp->ll_name)) {
cc = *endp; cc = *endp;
*endp = NUL; *endp = NUL;
if (op != NULL && *op != '=') { if (op != NULL && *op != '=') {
@ -2310,21 +2304,23 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch
if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name),
&tv, &di, true, false) == OK) { &tv, &di, true, false) == OK) {
if ((di == NULL if ((di == NULL
|| (!var_check_ro(di->di_flags, lp->ll_name, false) || (!var_check_ro(di->di_flags, (const char *)lp->ll_name,
&& !tv_check_lock(di->di_tv.v_lock, lp->ll_name, false))) 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) { && tv_op(&tv, rettv, op) == OK) {
set_var(lp->ll_name, &tv, false); set_var(lp->ll_name, &tv, false);
} }
clear_tv(&tv); clear_tv(&tv);
} }
} else } else {
set_var(lp->ll_name, rettv, copy); set_var(lp->ll_name, rettv, copy);
*endp = cc;
} }
*endp = cc;
} else if (tv_check_lock(lp->ll_newkey == NULL } else if (tv_check_lock(lp->ll_newkey == NULL
? lp->ll_tv->v_lock ? lp->ll_tv->v_lock
: lp->ll_tv->vval.v_dict->dv_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) { } else if (lp->ll_range) {
listitem_T *ll_li = lp->ll_li; listitem_T *ll_li = lp->ll_li;
int ll_n1 = lp->ll_n1; int ll_n1 = lp->ll_n1;
@ -2332,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 // Check whether any of the list items is locked
for (listitem_T *ri = rettv->vval.v_list->lv_first; for (listitem_T *ri = rettv->vval.v_list->lv_first;
ri != NULL && ll_li != NULL; ) { 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; return;
} }
ri = ri->li_next; ri = ri->li_next;
@ -2938,16 +2935,18 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit)
cc = *name_end; cc = *name_end;
*name_end = NUL; *name_end = NUL;
/* Normal name or expanded name. */ // Normal name or expanded name.
if (check_changedtick(lp->ll_name)) if (do_unlet(lp->ll_name, forceit) == FAIL) {
ret = FAIL;
else if (do_unlet(lp->ll_name, forceit) == FAIL)
ret = FAIL; ret = FAIL;
}
*name_end = cc; *name_end = cc;
} else if ((lp->ll_list != NULL } 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 || (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; return FAIL;
} else if (lp->ll_range) { } else if (lp->ll_range) {
listitem_T *li; listitem_T *li;
@ -2956,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)) { while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) {
li = ll_li->li_next; 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; return false;
} }
ll_li = li; ll_li = li;
@ -3038,13 +3038,14 @@ int do_unlet(char_u *name, int forceit)
} }
if (hi != NULL && !HASHITEM_EMPTY(hi)) { if (hi != NULL && !HASHITEM_EMPTY(hi)) {
di = HI2DI(hi); di = HI2DI(hi);
if (var_check_fixed(di->di_flags, name, false) if (var_check_fixed(di->di_flags, (const char *)name, STRLEN(name))
|| var_check_ro(di->di_flags, name, false) || var_check_ro(di->di_flags, (const char *)name, STRLEN(name))
|| tv_check_lock(d->dv_lock, name, false)) { || tv_check_lock(d->dv_lock, (const char *)name, STRLEN(name))) {
return FAIL; 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; return FAIL;
} }
@ -3078,23 +3079,25 @@ int do_unlet(char_u *name, int forceit)
static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock) static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock)
{ {
int ret = OK; int ret = OK;
int cc;
dictitem_T *di;
if (deep == 0) /* nothing to do */ if (deep == 0) { // Nothing to do.
return OK; return OK;
}
if (lp->ll_tv == NULL) { if (lp->ll_tv == NULL) {
cc = *name_end;
*name_end = NUL;
// Normal name or expanded name. // Normal name or expanded name.
if (check_changedtick(lp->ll_name)) { const size_t name_len = (size_t)(name_end - lp->ll_name);
ret = FAIL; dictitem_T *const di = find_var(
} else { (const char *)lp->ll_name, name_len, NULL,
di = find_var((const char *)lp->ll_name, STRLEN(lp->ll_name), NULL, true); true);
if (di == NULL) { if (di == NULL) {
ret = FAIL; 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 { } else {
if (lock) { if (lock) {
di->di_flags |= DI_FLAGS_LOCK; di->di_flags |= DI_FLAGS_LOCK;
@ -3103,8 +3106,6 @@ static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock)
} }
item_lock(&di->di_tv, deep, lock); item_lock(&di->di_tv, deep, lock);
} }
}
*name_end = cc;
} else if (lp->ll_range) { } else if (lp->ll_range) {
listitem_T *li = lp->ll_li; listitem_T *li = lp->ll_li;
@ -3131,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 void item_lock(typval_T *tv, int deep, int lock)
{ {
static int recurse = 0; static int recurse = 0;
list_T *l;
listitem_T *li;
dict_T *d;
hashitem_T *hi;
int todo;
if (recurse >= DICT_MAXNEST) { if (recurse >= DICT_MAXNEST) {
EMSG(_("E743: variable nested too deep for (un)lock")); EMSG(_("E743: variable nested too deep for (un)lock"));
return; return;
} }
if (deep == 0) if (deep == 0) {
return; return;
++recurse; }
recurse++;
/* lock/unlock the item itself */ // lock/unlock the item itself
if (lock) #define CHANGE_LOCK(var, lock) \
tv->v_lock |= VAR_LOCKED; do { \
else var = ((VarLockStatus[]) { \
tv->v_lock &= ~VAR_LOCKED; [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) { switch (tv->v_type) {
case VAR_LIST: case VAR_LIST: {
if ((l = tv->vval.v_list) != NULL) { list_T *const l = tv->vval.v_list;
if (lock) if (l != NULL) {
l->lv_lock |= VAR_LOCKED; CHANGE_LOCK(l->lv_lock, lock);
else if (deep < 0 || deep > 1) {
l->lv_lock &= ~VAR_LOCKED; // Recursive: lock/unlock the items the List contains.
if (deep < 0 || deep > 1) for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) {
/* 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); item_lock(&li->li_tv, deep - 1, lock);
} }
}
}
break; break;
case VAR_DICT: }
if ((d = tv->vval.v_dict) != NULL) { case VAR_DICT: {
if (lock) dict_T *const d = tv->vval.v_dict;
d->dv_lock |= VAR_LOCKED; if (d != NULL) {
else CHANGE_LOCK(d->dv_lock, lock);
d->dv_lock &= ~VAR_LOCKED;
if (deep < 0 || deep > 1) { if (deep < 0 || deep > 1) {
/* recursive: lock/unlock the items the List contains */ // Recursive: lock/unlock the items the List contains.
todo = (int)d->dv_hashtab.ht_used; int todo = (int)d->dv_hashtab.ht_used;
for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) { if (!HASHITEM_EMPTY(hi)) {
--todo; todo--;
item_lock(&HI2DI(hi)->di_tv, deep - 1, lock); item_lock(&HI2DI(hi)->di_tv, deep - 1, lock);
} }
} }
} }
} }
break; break;
}
case VAR_NUMBER: case VAR_NUMBER:
case VAR_FLOAT: case VAR_FLOAT:
case VAR_STRING: case VAR_STRING:
case VAR_FUNC: case VAR_FUNC:
case VAR_PARTIAL: case VAR_PARTIAL:
case VAR_SPECIAL: case VAR_SPECIAL: {
break; break;
case VAR_UNKNOWN: }
case VAR_UNKNOWN: {
assert(false); assert(false);
} }
--recurse; }
#undef CHANGE_LOCK
recurse--;
} }
/* /*
@ -3302,10 +3307,6 @@ char_u *get_user_var_name(expand_T *xp, int idx)
++hi; ++hi;
return cat_prefix_varname('b', hi->hi_key); return cat_prefix_varname('b', hi->hi_key);
} }
if (bdone == ht->ht_used) {
++bdone;
return (char_u *)"b:changedtick";
}
/* w: variables */ /* w: variables */
ht = &curwin->w_vars->dv_hashtab; ht = &curwin->w_vars->dv_hashtab;
@ -6373,7 +6374,7 @@ dict_T *dict_alloc(void) FUNC_ATTR_NONNULL_RET
first_dict = d; first_dict = d;
hash_init(&d->dv_hashtab); hash_init(&d->dv_hashtab);
d->dv_lock = 0; d->dv_lock = VAR_UNLOCKED;
d->dv_scope = 0; d->dv_scope = 0;
d->dv_refcount = 0; d->dv_refcount = 0;
d->dv_copyID = 0; d->dv_copyID = 0;
@ -6446,9 +6447,8 @@ static void dict_free_contents(dict_T *d) {
* something recursive causing trouble. */ * something recursive causing trouble. */
di = HI2DI(hi); di = HI2DI(hi);
hash_remove(&d->dv_hashtab, hi); hash_remove(&d->dv_hashtab, hi);
clear_tv(&di->di_tv); dictitem_free(di);
xfree(di); todo--;
--todo;
} }
} }
@ -7820,9 +7820,10 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = 1; /* Default: Failed */ rettv->vval.v_number = 1; /* Default: Failed */
if (argvars[0].v_type == VAR_LIST) { 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 if ((l = argvars[0].vval.v_list) != NULL
&& !tv_check_lock(l->lv_lock, && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) {
(char_u *)N_("add() argument"), true)) {
list_append_tv(l, &argvars[1]); list_append_tv(l, &argvars[1]);
copy_tv(&argvars[0], rettv); copy_tv(&argvars[0], rettv);
} }
@ -9399,7 +9400,8 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action)
hashitem_T *hi2; hashitem_T *hi2;
int todo; int todo;
bool watched = is_watched(d1); 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; todo = (int)d2->dv_hashtab.ht_used;
for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2) { for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2) {
@ -9433,8 +9435,8 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action)
} else if (*action == 'f' && HI2DI(hi2) != di1) { } else if (*action == 'f' && HI2DI(hi2) != di1) {
typval_T oldtv; typval_T oldtv;
if (tv_check_lock(di1->di_tv.v_lock, 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, true)) { || var_check_ro(di1->di_flags, arg_errmsg, arg_errmsg_len)) {
break; break;
} }
@ -9460,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) 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) { if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
list_T *l1, *l2; list_T *l1, *l2;
@ -9470,7 +9473,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
l1 = argvars[0].vval.v_list; l1 = argvars[0].vval.v_list;
l2 = argvars[1].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) { && l2 != NULL) {
if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) {
before = get_tv_number_chk(&argvars[2], &error); before = get_tv_number_chk(&argvars[2], &error);
@ -9500,7 +9503,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
d1 = argvars[0].vval.v_dict; d1 = argvars[0].vval.v_dict;
d2 = argvars[1].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) { && d2 != NULL) {
/* Check the third argument. */ /* Check the third argument. */
if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) {
@ -9644,19 +9647,21 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
int rem = false; int rem = false;
int todo; int todo;
char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); char_u *ermsg = (char_u *)(map ? "map()" : "filter()");
char_u *arg_errmsg = (char_u *)(map ? N_("map() argument") const char *const arg_errmsg = (map
: N_("filter() argument")); ? _("map() argument")
: _("filter() argument"));
const size_t arg_errmsg_len = strlen(arg_errmsg);
int save_did_emsg; int save_did_emsg;
int idx = 0; int idx = 0;
if (argvars[0].v_type == VAR_LIST) { if (argvars[0].v_type == VAR_LIST) {
if ((l = argvars[0].vval.v_list) == NULL 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; return;
} }
} else if (argvars[0].v_type == VAR_DICT) { } else if (argvars[0].v_type == VAR_DICT) {
if ((d = argvars[0].vval.v_dict) == NULL 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; return;
} }
} else { } else {
@ -9689,8 +9694,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
di = HI2DI(hi); di = HI2DI(hi);
if (map if (map
&& (tv_check_lock(di->di_tv.v_lock, arg_errmsg, true) && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, arg_errmsg_len)
|| var_check_ro(di->di_flags, arg_errmsg, true))) { || var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len))) {
break; break;
} }
@ -9700,8 +9705,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
if (r == FAIL || did_emsg) if (r == FAIL || did_emsg)
break; break;
if (!map && rem) { if (!map && rem) {
if (var_check_fixed(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, true)) { || var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len)) {
break; break;
} }
dictitem_remove(d, di); dictitem_remove(d, di);
@ -9713,7 +9718,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
vimvars[VV_KEY].vv_type = VAR_NUMBER; vimvars[VV_KEY].vv_type = VAR_NUMBER;
for (li = l->lv_first; li != NULL; li = nli) { 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; break;
} }
nli = li->li_next; nli = li->li_next;
@ -10524,10 +10530,6 @@ static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// buffer-local-option // buffer-local-option
done = true; done = true;
} }
} else if (STRCMP(varname, "changedtick") == 0) {
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = curbuf->b_changedtick;
done = true;
} else { } else {
// Look up the variable. // Look up the variable.
// Let getbufvar({nr}, "") return the "b:" dictionary. // Let getbufvar({nr}, "") return the "b:" dictionary.
@ -12395,13 +12397,14 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
long before = 0; long before = 0;
listitem_T *item; listitem_T *item;
list_T *l; 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) { if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "insert()"); EMSG2(_(e_listarg), "insert()");
} else if ((l = argvars[0].vval.v_list) != NULL } else if ((l = argvars[0].vval.v_list) != NULL
&& !tv_check_lock(l->lv_lock, && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) {
(char_u *)N_("insert() argument"), true)) {
if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) {
before = get_tv_number_chk(&argvars[2], &error); before = get_tv_number_chk(&argvars[2], &error);
} }
@ -12452,40 +12455,35 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr)
dictitem_T *di; dictitem_T *di;
rettv->vval.v_number = -1; rettv->vval.v_number = -1;
end = get_lval(get_tv_string(&argvars[0]), NULL, &lv, FALSE, FALSE, end = get_lval(get_tv_string(&argvars[0]), NULL, &lv, false, false,
GLV_NO_AUTOLOAD, FNE_CHECK_START); GLV_NO_AUTOLOAD|GLV_READ_ONLY, FNE_CHECK_START);
if (end != NULL && lv.ll_name != NULL) { if (end != NULL && lv.ll_name != NULL) {
if (*end != NUL) if (*end != NUL)
EMSG(_(e_trailing)); EMSG(_(e_trailing));
else { else {
if (lv.ll_tv == NULL) { if (lv.ll_tv == NULL) {
if (check_changedtick(lv.ll_name)) { di = find_var((const char *)lv.ll_name, STRLEN(lv.ll_name), NULL, true);
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) { if (di != NULL) {
/* Consider a variable locked when: // Consider a variable locked when:
* 1. the variable itself is locked // 1. the variable itself is locked
* 2. the value of the variable is locked. // 2. the value of the variable is locked.
* 3. the List or Dict value is locked. // 3. the List or Dict value is locked.
*/
rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK) rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK)
|| tv_islocked(&di->di_tv)); || tv_islocked(&di->di_tv));
} }
} } else if (lv.ll_range) {
} else if (lv.ll_range)
EMSG(_("E786: Range not allowed")); EMSG(_("E786: Range not allowed"));
else if (lv.ll_newkey != NULL) } else if (lv.ll_newkey != NULL) {
EMSG2(_(e_dictkey), lv.ll_newkey); EMSG2(_(e_dictkey), lv.ll_newkey);
else if (lv.ll_list != NULL) } else if (lv.ll_list != NULL) {
/* List item. */ // List item.
rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv); rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv);
else } else {
/* Dictionary item. */ // Dictionary item.
rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv);
} }
} }
}
clear_lval(&lv); clear_lval(&lv);
} }
@ -14430,20 +14428,21 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
char_u *key; char_u *key;
dict_T *d; dict_T *d;
dictitem_T *di; 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[0].v_type == VAR_DICT) {
if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) {
EMSG2(_(e_toomanyarg), "remove()"); EMSG2(_(e_toomanyarg), "remove()");
} else if ((d = argvars[0].vval.v_dict) != NULL } 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]); key = get_tv_string_chk(&argvars[1]);
if (key != NULL) { if (key != NULL) {
di = dict_find(d, key, -1); di = dict_find(d, key, -1);
if (di == NULL) { if (di == NULL) {
EMSG2(_(e_dictkey), key); EMSG2(_(e_dictkey), key);
} else if (!var_check_fixed(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, true)) { && !var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len)) {
*rettv = di->di_tv; *rettv = di->di_tv;
init_tv(&di->di_tv); init_tv(&di->di_tv);
dictitem_remove(d, di); dictitem_remove(d, di);
@ -14456,7 +14455,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} else if (argvars[0].v_type != VAR_LIST) { } else if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listdictarg), "remove()"); EMSG2(_(e_listdictarg), "remove()");
} else if ((l = argvars[0].vval.v_list) != NULL } 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; int error = (int)false;
idx = get_tv_number_chk(&argvars[1], &error); idx = get_tv_number_chk(&argvars[1], &error);
@ -14727,19 +14726,19 @@ fail:
*/ */
static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{ {
list_T *l; const char *const arg_errmsg = _("reverse() argument");
listitem_T *li, *ni; const size_t arg_errmsg_len = strlen(arg_errmsg);
list_T *l;
if (argvars[0].v_type != VAR_LIST) { if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), "reverse()"); EMSG2(_(e_listarg), "reverse()");
} else if ((l = argvars[0].vval.v_list) != NULL } else if ((l = argvars[0].vval.v_list) != NULL
&& !tv_check_lock(l->lv_lock, && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) {
(char_u *)N_("reverse() argument"), true)) { listitem_T *li = l->lv_last;
li = l->lv_last;
l->lv_first = l->lv_last = NULL; l->lv_first = l->lv_last = NULL;
l->lv_len = 0; l->lv_len = 0;
while (li != NULL) { while (li != NULL) {
ni = li->li_prev; listitem_T *const ni = li->li_prev;
list_append(l, li); list_append(l, li);
li = ni; li = ni;
} }
@ -16384,16 +16383,17 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
sortinfo_T *old_sortinfo = sortinfo; sortinfo_T *old_sortinfo = sortinfo;
sortinfo = &info; 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) { if (argvars[0].v_type != VAR_LIST) {
EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); EMSG2(_(e_listarg), sort ? "sort()" : "uniq()");
} else { } else {
l = argvars[0].vval.v_list; l = argvars[0].vval.v_list;
if (l == NULL if (l == NULL
|| tv_check_lock(l->lv_lock, || tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) {
(char_u *)(sort
? N_("sort() argument")
: N_("uniq() argument")),
true)) {
goto theend; goto theend;
} }
rettv->vval.v_list = l; rettv->vval.v_list = l;
@ -17732,9 +17732,9 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
(void)setfname(curbuf, (uint8_t *)buf, NULL, true); (void)setfname(curbuf, (uint8_t *)buf, NULL, true);
// Save the job id and pid in b:terminal_job_{id,pid} // Save the job id and pid in b:terminal_job_{id,pid}
Error err; Error err;
dict_set_value(curbuf->b_vars, cstr_as_string("terminal_job_id"), dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"),
INTEGER_OBJ(rettv->vval.v_number), false, false, &err); INTEGER_OBJ(rettv->vval.v_number), false, false, &err);
dict_set_value(curbuf->b_vars, cstr_as_string("terminal_job_pid"), dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"),
INTEGER_OBJ(pid), false, false, &err); INTEGER_OBJ(pid), false, false, &err);
Terminal *term = terminal_open(topts); Terminal *term = terminal_open(topts);
@ -19382,17 +19382,8 @@ static int get_var_tv(
{ {
int ret = OK; int ret = OK;
typval_T *tv = NULL; typval_T *tv = NULL;
typval_T atv;
dictitem_T *v; 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); v = find_var(name, (size_t)len, NULL, no_autoload);
if (v != NULL) { if (v != NULL) {
tv = &v->di_tv; tv = &v->di_tv;
@ -19400,7 +19391,6 @@ static int get_var_tv(
*dip = v; *dip = v;
} }
} }
}
if (tv == NULL) { if (tv == NULL) {
if (rettv != NULL && verbose) { if (rettv != NULL && verbose) {
@ -20381,7 +20371,7 @@ void new_script_vars(scid_T id)
void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope) void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope)
{ {
hash_init(&dict->dv_hashtab); hash_init(&dict->dv_hashtab);
dict->dv_lock = 0; dict->dv_lock = VAR_UNLOCKED;
dict->dv_scope = scope; dict->dv_scope = scope;
dict->dv_refcount = DO_NOT_FREE_CNT; dict->dv_refcount = DO_NOT_FREE_CNT;
dict->dv_copyID = 0; dict->dv_copyID = 0;
@ -20557,8 +20547,8 @@ set_var (
if (v != NULL) { if (v != NULL) {
// existing variable, need to clear the value // existing variable, need to clear the value
if (var_check_ro(v->di_flags, name, false) if (var_check_ro(v->di_flags, (const char *)name, name_len)
|| tv_check_lock(v->di_tv.v_lock, name, false)) { || tv_check_lock(v->di_tv.v_lock, (const char *)name, name_len)) {
return; return;
} }
@ -20631,28 +20621,47 @@ set_var (
} }
} }
// Return true if di_flags "flags" indicates variable "name" is read-only. /// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX)
// Also give an error message. ///
static bool var_check_ro(int flags, char_u *name, bool use_gettext) /// 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) { if (flags & DI_FLAGS_RO) {
EMSG2(_(e_readonlyvar), use_gettext ? (char_u *)_(name) : name); emsgf(_(e_readonlyvar), (int)name_len, name);
return true; return true;
} }
if ((flags & DI_FLAGS_RO_SBX) && sandbox) { 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 true;
} }
return false; return false;
} }
// Return true if di_flags "flags" indicates variable "name" is fixed. /// Check whether variable is fixed (DI_FLAGS_FIX)
// Also give an error message. ///
static bool var_check_fixed(int flags, char_u *name, bool use_gettext) /// 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) { if (flags & DI_FLAGS_FIX) {
EMSG2(_("E795: Cannot delete variable %s"), emsgf(_("E795: Cannot delete variable %.*s"), (int)name_len, name);
use_gettext ? (char_u *)_(name) : name);
return true; return true;
} }
return false; return false;
@ -20703,29 +20712,43 @@ static int valid_varname(char_u *varname)
return TRUE; return TRUE;
} }
// Return true if typeval "tv" is set to be locked (immutable). /// Check whether typval is locked
// Also give an error message, using "name" or _("name") when use_gettext is ///
// true. /// Also gives an error message.
static bool tv_check_lock(int lock, char_u *name, bool use_gettext) ///
/// @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) { const char *error_message = NULL;
EMSG2(_("E741: Value is locked: %s"), switch (lock) {
name == NULL case VAR_UNLOCKED: {
? (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;
}
return false; 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;
}
}
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;
}
/* /*
* Copy the values from typval_T "from" to typval_T "to". * Copy the values from typval_T "from" to typval_T "to".
@ -21523,11 +21546,13 @@ void ex_function(exarg_T *eap)
goto erret; goto erret;
} }
if (fudi.fd_di == NULL) { 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 // Can't add a function to a locked dictionary
goto erret; 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 // Can't change an existing function if it is locked
goto erret; goto erret;
} }

View File

@ -30,8 +30,8 @@ typedef enum {
/// Variable lock status for typval_T.v_lock /// Variable lock status for typval_T.v_lock
typedef enum { typedef enum {
VAR_UNLOCKED = 0, ///< Not locked. VAR_UNLOCKED = 0, ///< Not locked.
VAR_LOCKED, ///< User lock, can be unlocked. VAR_LOCKED = 1, ///< User lock, can be unlocked.
VAR_FIXED, ///< Locked forever. VAR_FIXED = 2, ///< Locked forever.
} VarLockStatus; } VarLockStatus;
/// VimL variable types, for use in typval_T.v_type /// VimL variable types, for use in typval_T.v_type
@ -93,18 +93,18 @@ struct listwatch_S {
* Structure to hold info about a list. * Structure to hold info about a list.
*/ */
struct listvar_S { struct listvar_S {
listitem_T *lv_first; /* first item, NULL if none */ listitem_T *lv_first; ///< First item, NULL if none.
listitem_T *lv_last; /* last item, NULL if none */ listitem_T *lv_last; ///< Last item, NULL if none.
int lv_refcount; /* reference count */ int lv_refcount; ///< Reference count.
int lv_len; /* number of items */ int lv_len; ///< Number of items.
listwatch_T *lv_watch; /* first watcher, NULL if none */ listwatch_T *lv_watch; ///< First watcher, NULL if none.
int lv_idx; /* cached index of an item */ 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" */ listitem_T *lv_idx_item; ///< When not NULL item at index "lv_idx".
int lv_copyID; /* ID used by deepcopy() */ int lv_copyID; ///< ID used by deepcopy().
list_T *lv_copylist; /* copied list used by deepcopy() */ list_T *lv_copylist; ///< Copied list used by deepcopy().
char lv_lock; /* zero, VAR_LOCKED, VAR_FIXED */ VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED.
list_T *lv_used_next; /* next list in used lists list */ list_T *lv_used_next; ///< next list in used lists list.
list_T *lv_used_prev; /* previous list in used lists list */ list_T *lv_used_prev; ///< Previous list in used lists list.
}; };
// Static list with 10 items. Use init_static_list() to initialize. // Static list with 10 items. Use init_static_list() to initialize.

View File

@ -6139,7 +6139,7 @@ void ex_substitute(exarg_T *eap)
// restore it so that undotree() is identical before/after the preview. // restore it so that undotree() is identical before/after the preview.
curbuf->b_u_newhead = save_b_u_newhead; curbuf->b_u_newhead = save_b_u_newhead;
curbuf->b_u_time_cur = save_b_u_time_cur; 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)) { if (buf_valid(preview_buf)) {
// XXX: Must do this *after* u_undo_and_forget(), why? // XXX: Must do this *after* u_undo_and_forget(), why?

View File

@ -1172,10 +1172,6 @@ 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_re_corr[] INIT(= N_("E44: Corrupted regexp program"));
EXTERN char_u e_readonly[] INIT(= N_( EXTERN char_u e_readonly[] INIT(= N_(
"E45: 'readonly' option is set (add ! to override)")); "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\""));
EXTERN char_u e_readerrf[] INIT(= N_("E47: Error while reading errorfile")); 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_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
EXTERN char_u e_secure[] INIT(= N_("E523: Not allowed here")); EXTERN char_u e_secure[] INIT(= N_("E523: Not allowed here"));

View File

@ -576,9 +576,9 @@ void getout(int exitval)
buf_T *buf = wp->w_buffer; buf_T *buf = wp->w_buffer;
if (buf->b_changedtick != -1) { if (buf->b_changedtick != -1) {
apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname,
buf->b_fname, FALSE, buf); buf->b_fname, false, buf);
buf->b_changedtick = -1; /* note that we did it already */ buf_set_changedtick(buf, -1); // note that we did it already
/* start all over, autocommands may mess up the lists */ // start all over, autocommands may mess up the lists
next_tp = first_tabpage; next_tp = first_tabpage;
break; break;
} }

View File

@ -1180,7 +1180,7 @@ void ml_recover(void)
* empty. Don't set the modified flag then. */ * empty. Don't set the modified flag then. */
if (!(curbuf->b_ml.ml_line_count == 2 && *ml_get(1) == NUL)) { if (!(curbuf->b_ml.ml_line_count == 2 && *ml_get(1) == NUL)) {
changed_int(); changed_int();
++curbuf->b_changedtick; buf_set_changedtick(curbuf, curbuf->b_changedtick + 1);
} }
} else { } else {
for (idx = 1; idx <= lnum; ++idx) { for (idx = 1; idx <= lnum; ++idx) {
@ -1190,7 +1190,7 @@ void ml_recover(void)
xfree(p); xfree(p);
if (i != 0) { if (i != 0) {
changed_int(); changed_int();
++curbuf->b_changedtick; buf_set_changedtick(curbuf, curbuf->b_changedtick + 1);
break; break;
} }
} }

View File

@ -625,18 +625,6 @@ void free_all_mem(void)
/* Destroy all windows. Must come before freeing buffers. */ /* Destroy all windows. Must come before freeing buffers. */
win_free_all(); 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(); free_cmdline_buf();
/* Clear registers. */ /* Clear registers. */
@ -660,6 +648,20 @@ void free_all_mem(void)
eval_clear(); 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!) */ /* screenlines (can't display anything now!) */
free_screenlines(); free_screenlines();

View File

@ -51,6 +51,7 @@
#include "nvim/os/input.h" #include "nvim/os/input.h"
#include "nvim/os/time.h" #include "nvim/os/time.h"
#include "nvim/event/stream.h" #include "nvim/event/stream.h"
#include "nvim/buffer.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "misc1.c.generated.h" # include "misc1.c.generated.h"
@ -1788,7 +1789,7 @@ void changed(void)
} }
changed_int(); changed_int();
} }
++curbuf->b_changedtick; buf_set_changedtick(curbuf, curbuf->b_changedtick + 1);
} }
/* /*
@ -2147,7 +2148,7 @@ unchanged (
redraw_tabline = TRUE; redraw_tabline = TRUE;
need_maketitle = TRUE; /* set window title later */ need_maketitle = TRUE; /* set window title later */
} }
++buf->b_changedtick; buf_set_changedtick(buf, buf->b_changedtick + 1);
} }
/* /*

View File

@ -611,8 +611,8 @@ static void buf_set_term_title(buf_T *buf, char *title)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_ALL
{ {
Error err; Error err;
dict_set_value(buf->b_vars, dict_set_var(buf->b_vars,
cstr_as_string("term_title"), STATIC_CSTR_AS_STRING("term_title"),
STRING_OBJ(cstr_as_string(title)), STRING_OBJ(cstr_as_string(title)),
false, false,
false, false,

View File

@ -2,8 +2,11 @@ local helpers = require('test.functional.helpers')(after_each)
local clear, nvim, buffer = helpers.clear, helpers.nvim, helpers.buffer local clear, nvim, buffer = helpers.clear, helpers.nvim, helpers.buffer
local curbuf, curwin, eq = helpers.curbuf, helpers.curwin, helpers.eq local curbuf, curwin, eq = helpers.curbuf, helpers.curwin, helpers.eq
local curbufmeths, ok = helpers.curbufmeths, helpers.ok local curbufmeths, ok = helpers.curbufmeths, helpers.ok
local funcs, request = helpers.funcs, helpers.request local funcs = helpers.funcs
local request = helpers.request
local NIL = helpers.NIL local NIL = helpers.NIL
local meth_pcall = helpers.meth_pcall
local command = helpers.command
describe('api/buf', function() describe('api/buf', function()
before_each(clear) before_each(clear)
@ -249,6 +252,24 @@ describe('api/buf', function()
eq(1, funcs.exists('b:lua')) eq(1, funcs.exists('b:lua'))
curbufmeths.del_var('lua') curbufmeths.del_var('lua')
eq(0, funcs.exists('b:lua')) eq(0, funcs.exists('b:lua'))
eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(curbufmeths.del_var, 'lua'))
curbufmeths.set_var('lua', 1)
command('lockvar b:lua')
eq({false, 'Key is locked: lua'}, meth_pcall(curbufmeths.del_var, 'lua'))
eq({false, 'Key is locked: lua'}, meth_pcall(curbufmeths.set_var, 'lua', 1))
eq({false, 'Key is read-only: changedtick'},
meth_pcall(curbufmeths.del_var, 'changedtick'))
eq({false, 'Key is read-only: changedtick'},
meth_pcall(curbufmeths.set_var, 'changedtick', 1))
end)
end)
describe('get_changedtick', function()
it('works', function()
eq(2, curbufmeths.get_changedtick())
curbufmeths.set_lines(0, 1, false, {'abc\0', '\0def', 'ghi'})
eq(3, curbufmeths.get_changedtick())
eq(3, curbufmeths.get_var('changedtick'))
end) end)
it('buffer_set_var returns the old value', function() it('buffer_set_var returns the old value', function()

View File

@ -6,6 +6,8 @@ local curtabmeths = helpers.curtabmeths
local funcs = helpers.funcs local funcs = helpers.funcs
local request = helpers.request local request = helpers.request
local NIL = helpers.NIL local NIL = helpers.NIL
local meth_pcall = helpers.meth_pcall
local command = helpers.command
describe('api/tabpage', function() describe('api/tabpage', function()
before_each(clear) before_each(clear)
@ -32,6 +34,11 @@ describe('api/tabpage', function()
eq(1, funcs.exists('t:lua')) eq(1, funcs.exists('t:lua'))
curtabmeths.del_var('lua') curtabmeths.del_var('lua')
eq(0, funcs.exists('t:lua')) eq(0, funcs.exists('t:lua'))
eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(curtabmeths.del_var, 'lua'))
curtabmeths.set_var('lua', 1)
command('lockvar t:lua')
eq({false, 'Key is locked: lua'}, meth_pcall(curtabmeths.del_var, 'lua'))
eq({false, 'Key is locked: lua'}, meth_pcall(curtabmeths.set_var, 'lua', 1))
end) end)
it('tabpage_set_var returns the old value', function() it('tabpage_set_var returns the old value', function()

View File

@ -7,6 +7,8 @@ local os_name = helpers.os_name
local meths = helpers.meths local meths = helpers.meths
local funcs = helpers.funcs local funcs = helpers.funcs
local request = helpers.request local request = helpers.request
local meth_pcall = helpers.meth_pcall
local command = helpers.command
describe('api', function() describe('api', function()
before_each(clear) before_each(clear)
@ -117,6 +119,11 @@ describe('api', function()
eq(1, funcs.exists('g:lua')) eq(1, funcs.exists('g:lua'))
meths.del_var('lua') meths.del_var('lua')
eq(0, funcs.exists('g:lua')) eq(0, funcs.exists('g:lua'))
eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(meths.del_var, 'lua'))
meths.set_var('lua', 1)
command('lockvar lua')
eq({false, 'Key is locked: lua'}, meth_pcall(meths.del_var, 'lua'))
eq({false, 'Key is locked: lua'}, meth_pcall(meths.set_var, 'lua', 1))
end) end)
it('vim_set_var returns the old value', function() it('vim_set_var returns the old value', function()

View File

@ -8,6 +8,8 @@ local curwinmeths = helpers.curwinmeths
local funcs = helpers.funcs local funcs = helpers.funcs
local request = helpers.request local request = helpers.request
local NIL = helpers.NIL local NIL = helpers.NIL
local meth_pcall = helpers.meth_pcall
local command = helpers.command
-- check if str is visible at the beginning of some line -- check if str is visible at the beginning of some line
local function is_visible(str) local function is_visible(str)
@ -137,6 +139,11 @@ describe('api/win', function()
eq(1, funcs.exists('w:lua')) eq(1, funcs.exists('w:lua'))
curwinmeths.del_var('lua') curwinmeths.del_var('lua')
eq(0, funcs.exists('w:lua')) eq(0, funcs.exists('w:lua'))
eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(curwinmeths.del_var, 'lua'))
curwinmeths.set_var('lua', 1)
command('lockvar w:lua')
eq({false, 'Key is locked: lua'}, meth_pcall(curwinmeths.del_var, 'lua'))
eq({false, 'Key is locked: lua'}, meth_pcall(curwinmeths.set_var, 'lua', 1))
end) end)
it('window_set_var returns the old value', function() it('window_set_var returns the old value', function()

View File

@ -0,0 +1,142 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local eval = helpers.eval
local feed = helpers.feed
local clear = helpers.clear
local funcs = helpers.funcs
local meths = helpers.meths
local command = helpers.command
local exc_exec = helpers.exc_exec
local redir_exec = helpers.redir_exec
local meth_pcall = helpers.meth_pcall
local curbufmeths = helpers.curbufmeths
before_each(clear)
local function changedtick()
local ct = curbufmeths.get_changedtick()
eq(ct, curbufmeths.get_var('changedtick'))
eq(ct, curbufmeths.get_var('changedtick'))
eq(ct, eval('b:changedtick'))
eq(ct, eval('b:["changedtick"]'))
eq(ct, eval('b:.changedtick'))
eq(ct, funcs.getbufvar('%', 'changedtick'))
eq(ct, funcs.getbufvar('%', '').changedtick)
eq(ct, eval('b:').changedtick)
return ct
end
describe('b:changedtick', function()
-- Ported tests from Vim-8.0.333
it('increments', function() -- Test_changedtick_increments
-- New buffer has an empty line, tick starts at 2
eq(2, changedtick())
funcs.setline(1, 'hello')
eq(3, changedtick())
eq(0, exc_exec('undo'))
-- Somehow undo counts as two changes
eq(5, changedtick())
end)
it('is present in b: dictionary', function()
eq(2, changedtick())
command('let d = b:')
eq(2, meths.get_var('d').changedtick)
end)
it('increments at bdel', function()
command('new')
eq(2, changedtick())
local bnr = curbufmeths.get_number()
eq(2, bnr)
command('bdel')
eq(3, funcs.getbufvar(bnr, 'changedtick'))
eq(1, curbufmeths.get_number())
end)
it('fails to be changed by user', function()
local ct = changedtick()
local ctn = ct + 100500
eq(0, exc_exec('let d = b:'))
eq('\nE46: Cannot change read-only variable "b:changedtick"',
redir_exec('let b:changedtick = ' .. ctn))
eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
redir_exec('let b:["changedtick"] = ' .. ctn))
eq('\nE46: Cannot change read-only variable "b:.changedtick"',
redir_exec('let b:.changedtick = ' .. ctn))
eq('\nE46: Cannot change read-only variable "d.changedtick"',
redir_exec('let d.changedtick = ' .. ctn))
eq({false, 'Key is read-only: changedtick'},
meth_pcall(curbufmeths.set_var, 'changedtick', ctn))
eq('\nE795: Cannot delete variable b:changedtick',
redir_exec('unlet b:changedtick'))
eq('\nE46: Cannot change read-only variable "b:.changedtick"',
redir_exec('unlet b:.changedtick'))
eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
redir_exec('unlet b:["changedtick"]'))
eq('\nE46: Cannot change read-only variable "d.changedtick"',
redir_exec('unlet d.changedtick'))
eq({false, 'Key is read-only: changedtick'},
meth_pcall(curbufmeths.del_var, 'changedtick'))
eq(ct, changedtick())
eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
redir_exec('let b:["changedtick"] += ' .. ctn))
eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
redir_exec('let b:["changedtick"] -= ' .. ctn))
eq('\nE46: Cannot change read-only variable "b:["changedtick"]"',
redir_exec('let b:["changedtick"] .= ' .. ctn))
eq(ct, changedtick())
funcs.setline(1, 'hello')
eq(ct + 1, changedtick())
end)
it('is listed in :let output', function()
eq('\nb:changedtick #2',
redir_exec(':let b:'))
end)
it('fails to unlock b:changedtick', function()
eq(0, exc_exec('let d = b:'))
eq(0, funcs.islocked('b:changedtick'))
eq(0, funcs.islocked('d.changedtick'))
eq('\nE940: Cannot lock or unlock variable b:changedtick',
redir_exec('unlockvar b:changedtick'))
eq('\nE46: Cannot change read-only variable "d.changedtick"',
redir_exec('unlockvar d.changedtick'))
eq(0, funcs.islocked('b:changedtick'))
eq(0, funcs.islocked('d.changedtick'))
eq('\nE940: Cannot lock or unlock variable b:changedtick',
redir_exec('lockvar b:changedtick'))
eq('\nE46: Cannot change read-only variable "d.changedtick"',
redir_exec('lockvar d.changedtick'))
eq(0, funcs.islocked('b:changedtick'))
eq(0, funcs.islocked('d.changedtick'))
end)
it('is being completed', function()
feed(':echo b:<Tab><Home>let cmdline="<End>"<CR>')
eq('echo b:changedtick', meths.get_var('cmdline'))
end)
it('cannot be changed by filter() or map()', function()
eq(2, changedtick())
eq('\nE795: Cannot delete variable filter() argument',
redir_exec('call filter(b:, 0)'))
eq('\nE742: Cannot change value of map() argument',
redir_exec('call map(b:, 0)'))
eq('\nE742: Cannot change value of map() argument',
redir_exec('call map(b:, "v:val")'))
eq(2, changedtick())
end)
it('cannot be remove()d', function()
eq(2, changedtick())
eq('\nE795: Cannot delete variable remove() argument',
redir_exec('call remove(b:, "changedtick")'))
eq(2, changedtick())
end)
it('does not inherit VAR_FIXED when copying dictionary over', function()
eq(2, changedtick())
eq('', redir_exec('let d1 = copy(b:)|let d1.changedtick = 42'))
eq('', redir_exec('let d2 = copy(b:)|unlet d2.changedtick'))
eq(2, changedtick())
end)
end)

View File

@ -544,6 +544,14 @@ local function skip_fragile(pending_fn, cond)
return false return false
end end
local function meth_pcall(...)
local ret = {pcall(...)}
if type(ret[2]) == 'string' then
ret[2] = ret[2]:gsub('^[^:]+:%d+: ', '')
end
return ret
end
local funcs = create_callindex(nvim_call) local funcs = create_callindex(nvim_call)
local meths = create_callindex(nvim) local meths = create_callindex(nvim)
local uimeths = create_callindex(ui) local uimeths = create_callindex(ui)
@ -615,6 +623,7 @@ local M = {
skip_fragile = skip_fragile, skip_fragile = skip_fragile,
set_shell_powershell = set_shell_powershell, set_shell_powershell = set_shell_powershell,
tmpname = tmpname, tmpname = tmpname,
meth_pcall = meth_pcall,
NIL = mpack.NIL, NIL = mpack.NIL,
} }

View File

@ -13,6 +13,14 @@ describe('context variables', function()
-- Test for getbufvar(). -- Test for getbufvar().
-- Use strings to test for memory leaks. -- Use strings to test for memory leaks.
source([[ source([[
function Getbufscope(buf, ...)
let ret = call('getbufvar', [a:buf, ''] + a:000)
if type(ret) == type({})
return filter(copy(ret), 'v:key isnot# "changedtick"')
else
return ret
endif
endfunction
let t:testvar='abcd' let t:testvar='abcd'
$put =string(gettabvar(1, 'testvar')) $put =string(gettabvar(1, 'testvar'))
$put =string(gettabvar(1, 'testvar')) $put =string(gettabvar(1, 'testvar'))
@ -20,14 +28,14 @@ describe('context variables', function()
let def_num = '5678' let def_num = '5678'
$put =string(getbufvar(1, 'var_num')) $put =string(getbufvar(1, 'var_num'))
$put =string(getbufvar(1, 'var_num', def_num)) $put =string(getbufvar(1, 'var_num', def_num))
$put =string(getbufvar(1, '')) $put =string(Getbufscope(1))
$put =string(getbufvar(1, '', def_num)) $put =string(Getbufscope(1, def_num))
unlet b:var_num unlet b:var_num
$put =string(getbufvar(1, 'var_num', def_num)) $put =string(getbufvar(1, 'var_num', def_num))
$put =string(getbufvar(1, '')) $put =string(Getbufscope(1))
$put =string(getbufvar(1, '', def_num)) $put =string(Getbufscope(1, def_num))
$put =string(getbufvar(9, '')) $put =string(Getbufscope(9))
$put =string(getbufvar(9, '', def_num)) $put =string(Getbufscope(9, def_num))
unlet def_num unlet def_num
$put =string(getbufvar(1, '&autoindent')) $put =string(getbufvar(1, '&autoindent'))
$put =string(getbufvar(1, '&autoindent', 1)) $put =string(getbufvar(1, '&autoindent', 1))

View File

@ -79,6 +79,13 @@ local function cimport(...)
-- format it (so that the lines are "unique" statements), also filter out -- format it (so that the lines are "unique" statements), also filter out
-- Objective-C blocks -- Objective-C blocks
if os.getenv('NVIM_TEST_PRINT_I') == '1' then
local lnum = 0
for line in body:gmatch('[^\n]+') do
lnum = lnum + 1
print(lnum, line)
end
end
body = formatc(body) body = formatc(body)
body = filter_complex_blocks(body) body = filter_complex_blocks(body)

View File

@ -124,6 +124,7 @@ function Gcc:init_defines()
self:define('_GNU_SOURCE') self:define('_GNU_SOURCE')
self:define('INCLUDE_GENERATED_DECLARATIONS') self:define('INCLUDE_GENERATED_DECLARATIONS')
self:define('UNIT_TESTING') self:define('UNIT_TESTING')
self:define('UNIT_TESTING_LUA_PREPROCESSING')
-- Needed for FreeBSD -- Needed for FreeBSD
self:define('_Thread_local', nil, '') self:define('_Thread_local', nil, '')
-- Needed for macOS Sierra -- Needed for macOS Sierra