mirror of
https://github.com/neovim/neovim.git
synced 2024-12-24 13:15:09 -07:00
Merge #6112 from ZyX-I/split-eval'/buf_get_changedtick
Better b:changedtick support
This commit is contained in:
commit
c318d8e672
@ -8606,9 +8606,12 @@ This does NOT work: >
|
||||
:lockvar v
|
||||
:let v = 'asdf' " fails!
|
||||
:unlet v
|
||||
< *E741*
|
||||
< *E741* *E940*
|
||||
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
|
||||
|Dictionary|. It specifies how deep the locking goes:
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -368,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
|
||||
@ -377,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
|
||||
@ -393,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
|
||||
@ -405,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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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; \
|
||||
|
@ -489,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. */
|
||||
|
585
src/nvim/eval.c
585
src/nvim/eval.c
@ -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_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";
|
||||
@ -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" */
|
||||
|
||||
// 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
|
||||
#define TFN_NO_DEREF 8 // do not dereference a Funcref
|
||||
/// 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;
|
||||
|
||||
// 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
|
||||
/// 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
|
||||
@ -541,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, },
|
||||
@ -1646,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);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1949,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;
|
||||
@ -2207,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;
|
||||
}
|
||||
|
||||
@ -2299,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;
|
||||
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);
|
||||
// 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;
|
||||
@ -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
|
||||
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;
|
||||
@ -2938,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;
|
||||
@ -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)) {
|
||||
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;
|
||||
@ -3038,13 +3038,14 @@ int do_unlet(char_u *name, int forceit)
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@ -3078,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;
|
||||
|
||||
@ -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 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);
|
||||
}
|
||||
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_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;
|
||||
}
|
||||
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--;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -3302,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;
|
||||
@ -6373,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;
|
||||
@ -6446,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--;
|
||||
}
|
||||
}
|
||||
|
||||
@ -7820,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);
|
||||
}
|
||||
@ -9399,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) {
|
||||
@ -9433,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;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
{
|
||||
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;
|
||||
@ -9470,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);
|
||||
@ -9500,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) {
|
||||
@ -9643,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 {
|
||||
@ -9689,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;
|
||||
}
|
||||
|
||||
@ -9700,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);
|
||||
@ -9713,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;
|
||||
@ -10524,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.
|
||||
@ -12395,13 +12397,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);
|
||||
}
|
||||
@ -12452,38 +12455,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14430,20 +14428,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);
|
||||
@ -14456,7 +14455,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);
|
||||
@ -14727,19 +14726,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;
|
||||
}
|
||||
@ -16384,16 +16383,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;
|
||||
@ -17732,10 +17732,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;
|
||||
@ -19382,23 +19382,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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -20381,7 +20371,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;
|
||||
@ -20557,8 +20547,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;
|
||||
}
|
||||
|
||||
@ -20631,28 +20621,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;
|
||||
@ -20703,28 +20712,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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -21523,11 +21546,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;
|
||||
}
|
||||
|
@ -30,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
|
||||
@ -93,18 +93,18 @@ 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.
|
||||
};
|
||||
|
||||
// Static list with 10 items. Use init_static_list() to initialize.
|
||||
|
@ -6139,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?
|
||||
|
@ -1171,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"));
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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"
|
||||
@ -1788,7 +1789,7 @@ void changed(void)
|
||||
}
|
||||
changed_int();
|
||||
}
|
||||
++curbuf->b_changedtick;
|
||||
buf_set_changedtick(curbuf, curbuf->b_changedtick + 1);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2147,7 +2148,7 @@ unchanged (
|
||||
redraw_tabline = TRUE;
|
||||
need_maketitle = TRUE; /* set window title later */
|
||||
}
|
||||
++buf->b_changedtick;
|
||||
buf_set_changedtick(buf, buf->b_changedtick + 1);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -611,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)
|
||||
|
@ -2,8 +2,11 @@ local helpers = require('test.functional.helpers')(after_each)
|
||||
local clear, nvim, buffer = helpers.clear, helpers.nvim, helpers.buffer
|
||||
local curbuf, curwin, eq = helpers.curbuf, helpers.curwin, helpers.eq
|
||||
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 meth_pcall = helpers.meth_pcall
|
||||
local command = helpers.command
|
||||
|
||||
describe('api/buf', function()
|
||||
before_each(clear)
|
||||
@ -249,6 +252,24 @@ describe('api/buf', function()
|
||||
eq(1, funcs.exists('b:lua'))
|
||||
curbufmeths.del_var('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)
|
||||
|
||||
it('buffer_set_var returns the old value', function()
|
||||
|
@ -6,6 +6,8 @@ local curtabmeths = helpers.curtabmeths
|
||||
local funcs = helpers.funcs
|
||||
local request = helpers.request
|
||||
local NIL = helpers.NIL
|
||||
local meth_pcall = helpers.meth_pcall
|
||||
local command = helpers.command
|
||||
|
||||
describe('api/tabpage', function()
|
||||
before_each(clear)
|
||||
@ -32,6 +34,11 @@ describe('api/tabpage', function()
|
||||
eq(1, funcs.exists('t:lua'))
|
||||
curtabmeths.del_var('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)
|
||||
|
||||
it('tabpage_set_var returns the old value', function()
|
||||
|
@ -7,6 +7,8 @@ local os_name = helpers.os_name
|
||||
local meths = helpers.meths
|
||||
local funcs = helpers.funcs
|
||||
local request = helpers.request
|
||||
local meth_pcall = helpers.meth_pcall
|
||||
local command = helpers.command
|
||||
|
||||
describe('api', function()
|
||||
before_each(clear)
|
||||
@ -43,7 +45,7 @@ describe('api', function()
|
||||
it('works', function()
|
||||
nvim('command', 'let g:v1 = "a"')
|
||||
nvim('command', 'let g:v2 = [1, 2, {"v3": 3}]')
|
||||
eq({v1 = 'a', v2 = {1, 2, {v3 = 3}}}, nvim('eval', 'g:'))
|
||||
eq({v1 = 'a', v2 = { 1, 2, { v3 = 3 } } }, nvim('eval', 'g:'))
|
||||
end)
|
||||
|
||||
it('handles NULL-initialized strings correctly', function()
|
||||
@ -65,7 +67,7 @@ describe('api', function()
|
||||
|
||||
describe('nvim_call_function', function()
|
||||
it('works', function()
|
||||
nvim('call_function', 'setqflist', {{{ filename = 'something', lnum = 17}}, 'r'})
|
||||
nvim('call_function', 'setqflist', { { { filename = 'something', lnum = 17 } }, 'r' })
|
||||
eq(17, nvim('call_function', 'getqflist', {})[1].lnum)
|
||||
eq(17, nvim('call_function', 'eval', {17}))
|
||||
eq('foo', nvim('call_function', 'simplify', {'this/./is//redundant/../../../foo'}))
|
||||
@ -117,6 +119,11 @@ describe('api', function()
|
||||
eq(1, funcs.exists('g:lua'))
|
||||
meths.del_var('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)
|
||||
|
||||
it('vim_set_var returns the old value', function()
|
||||
@ -396,7 +403,7 @@ describe('api', function()
|
||||
eq(1, meths.get_var('avar'))
|
||||
|
||||
req = {
|
||||
{'nvim_set_var', {'bvar', {2,3}}},
|
||||
{ 'nvim_set_var', { 'bvar', { 2, 3 } } },
|
||||
12,
|
||||
}
|
||||
status, err = pcall(meths.call_atomic, req)
|
||||
|
@ -8,6 +8,8 @@ local curwinmeths = helpers.curwinmeths
|
||||
local funcs = helpers.funcs
|
||||
local request = helpers.request
|
||||
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
|
||||
local function is_visible(str)
|
||||
@ -137,6 +139,11 @@ describe('api/win', function()
|
||||
eq(1, funcs.exists('w:lua'))
|
||||
curwinmeths.del_var('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)
|
||||
|
||||
it('window_set_var returns the old value', function()
|
||||
|
142
test/functional/eval/changedtick_spec.lua
Normal file
142
test/functional/eval/changedtick_spec.lua
Normal 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)
|
@ -544,6 +544,14 @@ local function skip_fragile(pending_fn, cond)
|
||||
return false
|
||||
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 meths = create_callindex(nvim)
|
||||
local uimeths = create_callindex(ui)
|
||||
@ -615,6 +623,7 @@ local M = {
|
||||
skip_fragile = skip_fragile,
|
||||
set_shell_powershell = set_shell_powershell,
|
||||
tmpname = tmpname,
|
||||
meth_pcall = meth_pcall,
|
||||
NIL = mpack.NIL,
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,14 @@ describe('context variables', function()
|
||||
-- Test for getbufvar().
|
||||
-- Use strings to test for memory leaks.
|
||||
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'
|
||||
$put =string(gettabvar(1, 'testvar'))
|
||||
$put =string(gettabvar(1, 'testvar'))
|
||||
@ -20,14 +28,14 @@ describe('context variables', function()
|
||||
let def_num = '5678'
|
||||
$put =string(getbufvar(1, 'var_num'))
|
||||
$put =string(getbufvar(1, 'var_num', def_num))
|
||||
$put =string(getbufvar(1, ''))
|
||||
$put =string(getbufvar(1, '', def_num))
|
||||
$put =string(Getbufscope(1))
|
||||
$put =string(Getbufscope(1, def_num))
|
||||
unlet b:var_num
|
||||
$put =string(getbufvar(1, 'var_num', def_num))
|
||||
$put =string(getbufvar(1, ''))
|
||||
$put =string(getbufvar(1, '', def_num))
|
||||
$put =string(getbufvar(9, ''))
|
||||
$put =string(getbufvar(9, '', def_num))
|
||||
$put =string(Getbufscope(1))
|
||||
$put =string(Getbufscope(1, def_num))
|
||||
$put =string(Getbufscope(9))
|
||||
$put =string(Getbufscope(9, def_num))
|
||||
unlet def_num
|
||||
$put =string(getbufvar(1, '&autoindent'))
|
||||
$put =string(getbufvar(1, '&autoindent', 1))
|
||||
|
@ -79,6 +79,13 @@ local function cimport(...)
|
||||
|
||||
-- format it (so that the lines are "unique" statements), also filter out
|
||||
-- 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 = filter_complex_blocks(body)
|
||||
|
||||
|
@ -124,6 +124,7 @@ function Gcc:init_defines()
|
||||
self:define('_GNU_SOURCE')
|
||||
self:define('INCLUDE_GENERATED_DECLARATIONS')
|
||||
self:define('UNIT_TESTING')
|
||||
self:define('UNIT_TESTING_LUA_PREPROCESSING')
|
||||
-- Needed for FreeBSD
|
||||
self:define('_Thread_local', nil, '')
|
||||
-- Needed for macOS Sierra
|
||||
|
Loading…
Reference in New Issue
Block a user