mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	Merge #6112 from ZyX-I/split-eval'/buf_get_changedtick
Better b:changedtick support
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Justin M. Keyes
					Justin M. Keyes