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 | 				:lockvar v | ||||||
| 				:let v = 'asdf'		" fails! | 				:let v = 'asdf'		" fails! | ||||||
| 				:unlet v | 				:unlet v | ||||||
| <							*E741* | <							*E741* *E940* | ||||||
| 			If you try to change a locked variable you get an | 			If you try to change a locked variable you get an | ||||||
| 			error message: "E741: Value is locked: {name}" | 			error message: "E741: Value is locked: {name}". | ||||||
|  | 			If you try to lock or unlock a built-in variable you  | ||||||
|  | 			will get an error message "E940: Cannot lock or unlock  | ||||||
|  | 			variable {name}". | ||||||
|  |  | ||||||
| 			[depth] is relevant when locking a |List| or | 			[depth] is relevant when locking a |List| or | ||||||
| 			|Dictionary|.  It specifies how deep the locking goes: | 			|Dictionary|.  It specifies how deep the locking goes: | ||||||
|   | |||||||
| @@ -411,10 +411,10 @@ end: | |||||||
|  |  | ||||||
| /// Gets a buffer-scoped (b:) variable. | /// Gets a buffer-scoped (b:) variable. | ||||||
| /// | /// | ||||||
| /// @param buffer     Buffer handle | /// @param buffer The buffer handle | ||||||
| /// @param name       Variable name | /// @param name The variable name | ||||||
| /// @param[out] err   Error details, if any | /// @param[out] err Details of an error that may have occurred | ||||||
| /// @return Variable value | /// @return The variable value | ||||||
| Object nvim_buf_get_var(Buffer buffer, String name, Error *err) | Object nvim_buf_get_var(Buffer buffer, String name, Error *err) | ||||||
| { | { | ||||||
|   buf_T *buf = find_buffer_by_handle(buffer, err); |   buf_T *buf = find_buffer_by_handle(buffer, err); | ||||||
| @@ -426,6 +426,22 @@ Object nvim_buf_get_var(Buffer buffer, String name, Error *err) | |||||||
|   return dict_get_value(buf->b_vars, name, err); |   return dict_get_value(buf->b_vars, name, err); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// Gets a changed tick of a buffer | ||||||
|  | /// | ||||||
|  | /// @param[in]  buffer  The buffer handle. | ||||||
|  | /// | ||||||
|  | /// @return `b:changedtick` value. | ||||||
|  | Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) | ||||||
|  | { | ||||||
|  |   const buf_T *const buf = find_buffer_by_handle(buffer, err); | ||||||
|  |  | ||||||
|  |   if (!buf) { | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return buf->b_changedtick; | ||||||
|  | } | ||||||
|  |  | ||||||
| /// Sets a buffer-scoped (b:) variable | /// Sets a buffer-scoped (b:) variable | ||||||
| /// | /// | ||||||
| /// @param buffer     Buffer handle | /// @param buffer     Buffer handle | ||||||
| @@ -440,7 +456,7 @@ void nvim_buf_set_var(Buffer buffer, String name, Object value, Error *err) | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   dict_set_value(buf->b_vars, name, value, false, false, err); |   dict_set_var(buf->b_vars, name, value, false, false, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Removes a buffer-scoped (b:) variable | /// Removes a buffer-scoped (b:) variable | ||||||
| @@ -456,7 +472,7 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err) | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   dict_set_value(buf->b_vars, name, NIL, true, false, err); |   dict_set_var(buf->b_vars, name, NIL, true, false, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Sets a buffer-scoped (b:) variable | /// Sets a buffer-scoped (b:) variable | ||||||
| @@ -479,7 +495,7 @@ Object buffer_set_var(Buffer buffer, String name, Object value, Error *err) | |||||||
|     return (Object) OBJECT_INIT; |     return (Object) OBJECT_INIT; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return dict_set_value(buf->b_vars, name, value, false, true, err); |   return dict_set_var(buf->b_vars, name, value, false, true, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Removes a buffer-scoped (b:) variable | /// Removes a buffer-scoped (b:) variable | ||||||
| @@ -498,7 +514,7 @@ Object buffer_del_var(Buffer buffer, String name, Error *err) | |||||||
|     return (Object) OBJECT_INIT; |     return (Object) OBJECT_INIT; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return dict_set_value(buf->b_vars, name, NIL, true, true, err); |   return dict_set_var(buf->b_vars, name, NIL, true, true, err); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -98,7 +98,7 @@ Object dict_get_value(dict_T *dict, String key, Error *err) | |||||||
|   return vim_to_object(&di->di_tv); |   return vim_to_object(&di->di_tv); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Set a value in a dict. Objects are recursively expanded into their | /// Set a value in a scope dict. Objects are recursively expanded into their | ||||||
| /// vimscript equivalents. | /// vimscript equivalents. | ||||||
| /// | /// | ||||||
| /// @param dict The vimscript dict | /// @param dict The vimscript dict | ||||||
| @@ -109,7 +109,7 @@ Object dict_get_value(dict_T *dict, String key, Error *err) | |||||||
| /// @param retval If true the old value will be converted and returned. | /// @param retval If true the old value will be converted and returned. | ||||||
| /// @param[out] err Details of an error that may have occurred | /// @param[out] err Details of an error that may have occurred | ||||||
| /// @return The old value if `retval` is true and the key was present, else NIL | /// @return The old value if `retval` is true and the key was present, else NIL | ||||||
| Object dict_set_value(dict_T *dict, String key, Object value, bool del, | Object dict_set_var(dict_T *dict, String key, Object value, bool del, | ||||||
|                     bool retval, Error *err) |                     bool retval, Error *err) | ||||||
| { | { | ||||||
|   Object rv = OBJECT_INIT; |   Object rv = OBJECT_INIT; | ||||||
| @@ -120,7 +120,7 @@ Object dict_set_value(dict_T *dict, String key, Object value, bool del, | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (key.size == 0) { |   if (key.size == 0) { | ||||||
|     api_set_error(err, Validation, _("Empty dictionary keys aren't allowed")); |     api_set_error(err, Validation, _("Empty variable names aren't allowed")); | ||||||
|     return rv; |     return rv; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -129,7 +129,20 @@ Object dict_set_value(dict_T *dict, String key, Object value, bool del, | |||||||
|     return rv; |     return rv; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   dictitem_T *di = dict_find(dict, (uint8_t *)key.data, (int)key.size); |   dictitem_T *di = dict_find(dict, (char_u *)key.data, (int)key.size); | ||||||
|  |  | ||||||
|  |   if (di != NULL) { | ||||||
|  |     if (di->di_flags & DI_FLAGS_RO) { | ||||||
|  |       api_set_error(err, Exception, _("Key is read-only: %s"), key.data); | ||||||
|  |       return rv; | ||||||
|  |     } else if (di->di_flags & DI_FLAGS_FIX) { | ||||||
|  |       api_set_error(err, Exception, _("Key is fixed: %s"), key.data); | ||||||
|  |       return rv; | ||||||
|  |     } else if (di->di_flags & DI_FLAGS_LOCK) { | ||||||
|  |       api_set_error(err, Exception, _("Key is locked: %s"), key.data); | ||||||
|  |       return rv; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if (del) { |   if (del) { | ||||||
|     // Delete the key |     // Delete the key | ||||||
|   | |||||||
| @@ -71,7 +71,7 @@ void nvim_tabpage_set_var(Tabpage tabpage, | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   dict_set_value(tab->tp_vars, name, value, false, false, err); |   dict_set_var(tab->tp_vars, name, value, false, false, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Removes a tab-scoped (t:) variable | /// Removes a tab-scoped (t:) variable | ||||||
| @@ -87,7 +87,7 @@ void nvim_tabpage_del_var(Tabpage tabpage, String name, Error *err) | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   dict_set_value(tab->tp_vars, name, NIL, true, false, err); |   dict_set_var(tab->tp_vars, name, NIL, true, false, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Sets a tab-scoped (t:) variable | /// Sets a tab-scoped (t:) variable | ||||||
| @@ -110,7 +110,7 @@ Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err) | |||||||
|     return (Object) OBJECT_INIT; |     return (Object) OBJECT_INIT; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return dict_set_value(tab->tp_vars, name, value, false, true, err); |   return dict_set_var(tab->tp_vars, name, value, false, true, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Removes a tab-scoped (t:) variable | /// Removes a tab-scoped (t:) variable | ||||||
| @@ -129,7 +129,7 @@ Object tabpage_del_var(Tabpage tabpage, String name, Error *err) | |||||||
|     return (Object) OBJECT_INIT; |     return (Object) OBJECT_INIT; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return dict_set_value(tab->tp_vars, name, NIL, true, true, err); |   return dict_set_var(tab->tp_vars, name, NIL, true, true, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Gets the current window in a tabpage | /// Gets the current window in a tabpage | ||||||
|   | |||||||
| @@ -368,7 +368,7 @@ Object nvim_get_var(String name, Error *err) | |||||||
| /// @param[out] err Error details, if any | /// @param[out] err Error details, if any | ||||||
| void nvim_set_var(String name, Object value, Error *err) | void nvim_set_var(String name, Object value, Error *err) | ||||||
| { | { | ||||||
|   dict_set_value(&globvardict, name, value, false, false, err); |   dict_set_var(&globvardict, name, value, false, false, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Removes a global (g:) variable | /// Removes a global (g:) variable | ||||||
| @@ -377,7 +377,7 @@ void nvim_set_var(String name, Object value, Error *err) | |||||||
| /// @param[out] err Error details, if any | /// @param[out] err Error details, if any | ||||||
| void nvim_del_var(String name, Error *err) | void nvim_del_var(String name, Error *err) | ||||||
| { | { | ||||||
|   dict_set_value(&globvardict, name, NIL, true, false, err); |   dict_set_var(&globvardict, name, NIL, true, false, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Sets a global variable | /// Sets a global variable | ||||||
| @@ -393,7 +393,7 @@ void nvim_del_var(String name, Error *err) | |||||||
| ///                  or if previous value was `v:null`. | ///                  or if previous value was `v:null`. | ||||||
| Object vim_set_var(String name, Object value, Error *err) | Object vim_set_var(String name, Object value, Error *err) | ||||||
| { | { | ||||||
|   return dict_set_value(&globvardict, name, value, false, true, err); |   return dict_set_var(&globvardict, name, value, false, true, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Removes a global variable | /// Removes a global variable | ||||||
| @@ -405,7 +405,7 @@ Object vim_set_var(String name, Object value, Error *err) | |||||||
| /// @return Old value | /// @return Old value | ||||||
| Object vim_del_var(String name, Error *err) | Object vim_del_var(String name, Error *err) | ||||||
| { | { | ||||||
|   return dict_set_value(&globvardict, name, NIL, true, true, err); |   return dict_set_var(&globvardict, name, NIL, true, true, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Gets a v: variable | /// Gets a v: variable | ||||||
|   | |||||||
| @@ -210,7 +210,7 @@ void nvim_win_set_var(Window window, String name, Object value, Error *err) | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   dict_set_value(win->w_vars, name, value, false, false, err); |   dict_set_var(win->w_vars, name, value, false, false, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Removes a window-scoped (w:) variable | /// Removes a window-scoped (w:) variable | ||||||
| @@ -226,7 +226,7 @@ void nvim_win_del_var(Window window, String name, Error *err) | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   dict_set_value(win->w_vars, name, NIL, true, false, err); |   dict_set_var(win->w_vars, name, NIL, true, false, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Sets a window-scoped (w:) variable | /// Sets a window-scoped (w:) variable | ||||||
| @@ -249,7 +249,7 @@ Object window_set_var(Window window, String name, Object value, Error *err) | |||||||
|     return (Object) OBJECT_INIT; |     return (Object) OBJECT_INIT; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return dict_set_value(win->w_vars, name, value, false, true, err); |   return dict_set_var(win->w_vars, name, value, false, true, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Removes a window-scoped (w:) variable | /// Removes a window-scoped (w:) variable | ||||||
| @@ -268,7 +268,7 @@ Object window_del_var(Window window, String name, Error *err) | |||||||
|     return (Object) OBJECT_INIT; |     return (Object) OBJECT_INIT; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return dict_set_value(win->w_vars, name, NIL, true, true, err); |   return dict_set_var(win->w_vars, name, NIL, true, true, err); | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Gets a window option value | /// Gets a window option value | ||||||
|   | |||||||
| @@ -686,8 +686,18 @@ free_buffer_stuff ( | |||||||
|     free_buf_options(buf, true); |     free_buf_options(buf, true); | ||||||
|     ga_clear(&buf->b_s.b_langp); |     ga_clear(&buf->b_s.b_langp); | ||||||
|   } |   } | ||||||
|  |   { | ||||||
|  |     // Avoid loosing b:changedtick when deleting buffer: clearing variables | ||||||
|  |     // implies using clear_tv() on b:changedtick and that sets changedtick to | ||||||
|  |     // zero. | ||||||
|  |     hashitem_T *const changedtick_hi = hash_find( | ||||||
|  |         &buf->b_vars->dv_hashtab, (const char_u *)"changedtick"); | ||||||
|  |     assert(changedtick_hi != NULL); | ||||||
|  |     hash_remove(&buf->b_vars->dv_hashtab, changedtick_hi); | ||||||
|  |   } | ||||||
|   vars_clear(&buf->b_vars->dv_hashtab);   // free all internal variables |   vars_clear(&buf->b_vars->dv_hashtab);   // free all internal variables | ||||||
|   hash_init(&buf->b_vars->dv_hashtab); |   hash_init(&buf->b_vars->dv_hashtab); | ||||||
|  |   buf_init_changedtick(buf); | ||||||
|   uc_clear(&buf->b_ucmds);              // clear local user commands |   uc_clear(&buf->b_ucmds);              // clear local user commands | ||||||
|   buf_delete_signs(buf);                // delete any signs |   buf_delete_signs(buf);                // delete any signs | ||||||
|   bufhl_clear_all(buf);                // delete any highligts |   bufhl_clear_all(buf);                // delete any highligts | ||||||
| @@ -1436,6 +1446,26 @@ void do_autochdir(void) | |||||||
|  |  | ||||||
| static int top_file_num = 1;            ///< highest file number | static int top_file_num = 1;            ///< highest file number | ||||||
|  |  | ||||||
|  | /// Initialize b:changedtick and changedtick_val attribute | ||||||
|  | /// | ||||||
|  | /// @param[out]  buf  Buffer to intialize for. | ||||||
|  | static inline void buf_init_changedtick(buf_T *const buf) | ||||||
|  |   FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL | ||||||
|  | { | ||||||
|  |   STATIC_ASSERT(sizeof("changedtick") <= sizeof(buf->changedtick_di.di_key), | ||||||
|  |                 "buf->changedtick_di cannot hold large enough keys"); | ||||||
|  |   buf->changedtick_di = (dictitem16_T) { | ||||||
|  |     .di_flags = DI_FLAGS_RO|DI_FLAGS_FIX,  // Must not include DI_FLAGS_ALLOC. | ||||||
|  |     .di_tv = (typval_T) { | ||||||
|  |       .v_type = VAR_NUMBER, | ||||||
|  |       .v_lock = VAR_FIXED, | ||||||
|  |       .vval.v_number = buf->b_changedtick, | ||||||
|  |     }, | ||||||
|  |     .di_key = "changedtick", | ||||||
|  |   }; | ||||||
|  |   dict_add(buf->b_vars, (dictitem_T *)&buf->changedtick_di); | ||||||
|  | } | ||||||
|  |  | ||||||
| /// Add a file name to the buffer list. | /// Add a file name to the buffer list. | ||||||
| /// If the same file name already exists return a pointer to that buffer. | /// If the same file name already exists return a pointer to that buffer. | ||||||
| /// If it does not exist, or if fname == NULL, a new entry is created. | /// If it does not exist, or if fname == NULL, a new entry is created. | ||||||
| @@ -1530,6 +1560,7 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) | |||||||
|     // init b: variables |     // init b: variables | ||||||
|     buf->b_vars = dict_alloc(); |     buf->b_vars = dict_alloc(); | ||||||
|     init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); |     init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); | ||||||
|  |     buf_init_changedtick(buf); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (ffname != NULL) { |   if (ffname != NULL) { | ||||||
|   | |||||||
| @@ -5,6 +5,8 @@ | |||||||
| #include "nvim/pos.h"  // for linenr_T | #include "nvim/pos.h"  // for linenr_T | ||||||
| #include "nvim/ex_cmds_defs.h"  // for exarg_T | #include "nvim/ex_cmds_defs.h"  // for exarg_T | ||||||
| #include "nvim/screen.h"  // for StlClickRecord | #include "nvim/screen.h"  // for StlClickRecord | ||||||
|  | #include "nvim/func_attr.h" | ||||||
|  | #include "nvim/eval.h" | ||||||
|  |  | ||||||
| // Values for buflist_getfile() | // Values for buflist_getfile() | ||||||
| enum getf_values { | enum getf_values { | ||||||
| @@ -79,6 +81,31 @@ static inline void restore_win_for_buf(win_T *save_curwin, | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static inline void buf_set_changedtick(buf_T *const buf, const int changedtick) | ||||||
|  |   REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; | ||||||
|  |  | ||||||
|  | /// Set b_changedtick and corresponding variable | ||||||
|  | /// | ||||||
|  | /// @param[out]  buf  Buffer to set changedtick in. | ||||||
|  | /// @param[in]  changedtick  New value. | ||||||
|  | static inline void buf_set_changedtick(buf_T *const buf, const int changedtick) | ||||||
|  | { | ||||||
|  | #ifndef NDEBUG | ||||||
|  |   dictitem_T *const changedtick_di = dict_find( | ||||||
|  |       buf->b_vars, (char_u *)"changedtick", sizeof("changedtick") - 1); | ||||||
|  |   assert(changedtick_di != NULL); | ||||||
|  |   assert(changedtick_di->di_tv.v_type == VAR_NUMBER); | ||||||
|  |   assert(changedtick_di->di_tv.v_lock == VAR_FIXED); | ||||||
|  |   // For some reason formatc does not like the below. | ||||||
|  | # ifndef UNIT_TESTING_LUA_PREPROCESSING | ||||||
|  |   assert(changedtick_di->di_flags == (DI_FLAGS_RO|DI_FLAGS_FIX)); | ||||||
|  | # endif | ||||||
|  |   assert(changedtick_di == (dictitem_T *)&buf->changedtick_di); | ||||||
|  |   assert(&buf->b_changedtick == &buf->changedtick_di.di_tv.vval.v_number); | ||||||
|  | #endif | ||||||
|  |   buf->b_changedtick = changedtick; | ||||||
|  | } | ||||||
|  |  | ||||||
| #define WITH_BUFFER(b, code) \ | #define WITH_BUFFER(b, code) \ | ||||||
|   do { \ |   do { \ | ||||||
|     win_T *save_curwin = NULL; \ |     win_T *save_curwin = NULL; \ | ||||||
|   | |||||||
| @@ -489,7 +489,9 @@ struct file_buffer { | |||||||
|  |  | ||||||
|   int b_changed;                // 'modified': Set to true if something in the |   int b_changed;                // 'modified': Set to true if something in the | ||||||
|                                 // file has been changed and not written out. |                                 // file has been changed and not written out. | ||||||
|   int b_changedtick;            // incremented for each change, also for undo | /// Change identifier incremented for each change, including undo | ||||||
|  | #define b_changedtick changedtick_di.di_tv.vval.v_number | ||||||
|  |   dictitem16_T changedtick_di;  // b:changedtick dictionary item. | ||||||
|  |  | ||||||
|   bool b_saving;                /* Set to true if we are in the middle of |   bool b_saving;                /* Set to true if we are in the middle of | ||||||
|                                    saving the buffer. */ |                                    saving the buffer. */ | ||||||
|   | |||||||
							
								
								
									
										495
									
								
								src/nvim/eval.c
									
									
									
									
									
								
							
							
						
						
									
										495
									
								
								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_nofunc = N_("E130: Unknown function: %s"); | ||||||
| static char *e_illvar = N_("E461: Illegal variable name: %s"); | static char *e_illvar = N_("E461: Illegal variable name: %s"); | ||||||
| static char *e_float_as_string = N_("E806: using Float as a String"); | static char *e_float_as_string = N_("E806: using Float as a String"); | ||||||
|  | static const char *e_readonlyvar = N_( | ||||||
|  |     "E46: Cannot change read-only variable \"%.*s\""); | ||||||
|  |  | ||||||
| static char_u * const empty_string = (char_u *)""; | static char_u * const empty_string = (char_u *)""; | ||||||
| static char_u * const namespace_char = (char_u *)"abglstvw"; | static char_u * const namespace_char = (char_u *)"abglstvw"; | ||||||
| @@ -201,15 +203,22 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL}; | |||||||
|  |  | ||||||
| static int echo_attr = 0;   /* attributes used for ":echo" */ | static int echo_attr = 0;   /* attributes used for ":echo" */ | ||||||
|  |  | ||||||
| // Values for trans_function_name() argument: | /// trans_function_name() flags | ||||||
| #define TFN_INT         1       // internal function name OK | typedef enum { | ||||||
| #define TFN_QUIET       2       // no error messages |   TFN_INT = 1,  ///< May use internal function name | ||||||
| #define TFN_NO_AUTOLOAD 4       // do not use script autoloading |   TFN_QUIET = 2,  ///< Do not emit error messages. | ||||||
| #define TFN_NO_DEREF    8       // do not dereference a Funcref |   TFN_NO_AUTOLOAD = 4,  ///< Do not use script autoloading. | ||||||
|  |   TFN_NO_DEREF = 8,  ///< Do not dereference a Funcref. | ||||||
|  |   TFN_READ_ONLY = 16,  ///< Will not change the variable. | ||||||
|  | } TransFunctionNameFlags; | ||||||
|  |  | ||||||
| // Values for get_lval() flags argument: | /// get_lval() flags | ||||||
| #define GLV_QUIET       TFN_QUIET        // no error messages | typedef enum { | ||||||
| #define GLV_NO_AUTOLOAD TFN_NO_AUTOLOAD  // do not use script autoloading |   GLV_QUIET = TFN_QUIET,  ///< Do not emit error messages. | ||||||
|  |   GLV_NO_AUTOLOAD = TFN_NO_AUTOLOAD,  ///< Do not use script autoloading. | ||||||
|  |   GLV_READ_ONLY = TFN_READ_ONLY,  ///< Indicates that caller will not change | ||||||
|  |                                   ///< the value (prevents error message). | ||||||
|  | } GetLvalFlags; | ||||||
|  |  | ||||||
| // function flags | // function flags | ||||||
| #define FC_ABORT    0x01          // abort function on error | #define FC_ABORT    0x01          // abort function on error | ||||||
| @@ -541,8 +550,8 @@ void eval_init(void) | |||||||
|     list_T *const type_list = list_alloc(); |     list_T *const type_list = list_alloc(); | ||||||
|     type_list->lv_lock = VAR_FIXED; |     type_list->lv_lock = VAR_FIXED; | ||||||
|     type_list->lv_refcount = 1; |     type_list->lv_refcount = 1; | ||||||
|     dictitem_T *const di = dictitem_alloc((char_u *) msgpack_type_names[i]); |     dictitem_T *const di = dictitem_alloc((char_u *)msgpack_type_names[i]); | ||||||
|     di->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; |     di->di_flags |= DI_FLAGS_RO|DI_FLAGS_FIX; | ||||||
|     di->di_tv = (typval_T) { |     di->di_tv = (typval_T) { | ||||||
|       .v_type = VAR_LIST, |       .v_type = VAR_LIST, | ||||||
|       .vval = { .v_list = type_list, }, |       .vval = { .v_list = type_list, }, | ||||||
| @@ -1646,13 +1655,7 @@ static void list_glob_vars(int *first) | |||||||
|  */ |  */ | ||||||
| static void list_buf_vars(int *first) | static void list_buf_vars(int *first) | ||||||
| { | { | ||||||
|   char numbuf[NUMBUFLEN]; |  | ||||||
|  |  | ||||||
|   list_hashtable_vars(&curbuf->b_vars->dv_hashtab, "b:", true, first); |   list_hashtable_vars(&curbuf->b_vars->dv_hashtab, "b:", true, first); | ||||||
|  |  | ||||||
|   snprintf(numbuf, sizeof(numbuf), "%d", curbuf->b_changedtick); |  | ||||||
|   list_one_var_a("b:", "changedtick", sizeof("changedtick") - 1, VAR_NUMBER, |  | ||||||
|                  numbuf, first); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -1949,46 +1952,33 @@ ex_let_one ( | |||||||
|   return arg_end; |   return arg_end; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /// Get an lvalue | ||||||
|  * If "arg" is equal to "b:changedtick" give an error and return TRUE. | /// | ||||||
|  */ | /// Lvalue may be | ||||||
| static int check_changedtick(char_u *arg) | /// - variable: "name", "na{me}" | ||||||
| { | /// - dictionary item: "dict.key", "dict['key']" | ||||||
|   if (STRNCMP(arg, "b:changedtick", 13) == 0 && !eval_isnamec(arg[13])) { | /// - list item: "list[expr]" | ||||||
|     EMSG2(_(e_readonlyvar), arg); | /// - list slice: "list[expr:expr]" | ||||||
|     return TRUE; | /// | ||||||
|   } | /// Indexing only works if trying to use it with an existing List or Dictionary. | ||||||
|   return FALSE; | /// | ||||||
| } | /// @param[in]  name  Name to parse. | ||||||
|  | /// @param  rettv  Pointer to the value to be assigned or NULL. | ||||||
| /* | /// @param[out]  lp  Lvalue definition. When evaluation errors occur `->ll_name` | ||||||
|  * Get an lval: variable, Dict item or List item that can be assigned a value | ///                  is NULL. | ||||||
|  * to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", | /// @param[in]  unlet  True if using `:unlet`. This results in slightly | ||||||
|  * "name.key", "name.key[expr]" etc. | ///                    different behaviour when something is wrong; must end in | ||||||
|  * Indexing only works if "name" is an existing List or Dictionary. | ///                    space or cmd separator. | ||||||
|  * "name" points to the start of the name. | /// @param[in]  skip  True when skipping. | ||||||
|  * If "rettv" is not NULL it points to the value to be assigned. | /// @param[in]  flags  @see GetLvalFlags. | ||||||
|  * "unlet" is TRUE for ":unlet": slightly different behavior when something is | /// @param[in]  fne_flags  Flags for find_name_end(). | ||||||
|  * wrong; must end in space or cmd separator. | /// | ||||||
|  * | /// @return A pointer to just after the name, including indexes. Returns NULL | ||||||
|  * flags: | ///         for a parsing error, but it is still needed to free items in lp. | ||||||
|  *  GLV_QUIET:       do not give error messages | static char_u *get_lval(char_u *const name, typval_T *const rettv, | ||||||
|  *  GLV_NO_AUTOLOAD: do not use script autoloading |                         lval_T *const lp, const bool unlet, const bool skip, | ||||||
|  * |                         const int flags, const int fne_flags) | ||||||
|  * Returns a pointer to just after the name, including indexes. |   FUNC_ATTR_NONNULL_ARG(1, 3) | ||||||
|  * When an evaluation error occurs "lp->ll_name" is NULL; |  | ||||||
|  * Returns NULL for a parsing error.  Still need to free items in "lp"! |  | ||||||
|  */ |  | ||||||
| static char_u * |  | ||||||
| get_lval ( |  | ||||||
|     char_u *name, |  | ||||||
|     typval_T *rettv, |  | ||||||
|     lval_T *lp, |  | ||||||
|     int unlet, |  | ||||||
|     int skip, |  | ||||||
|     int flags,                  /* GLV_ values */ |  | ||||||
|     int fne_flags              /* flags for find_name_end() */ |  | ||||||
| ) |  | ||||||
| { | { | ||||||
|   char_u      *p; |   char_u      *p; | ||||||
|   char_u      *expr_start, *expr_end; |   char_u      *expr_start, *expr_end; | ||||||
| @@ -2207,8 +2197,13 @@ get_lval ( | |||||||
|         if (len == -1) |         if (len == -1) | ||||||
|           clear_tv(&var1); |           clear_tv(&var1); | ||||||
|         break; |         break; | ||||||
|       } else if (var_check_ro(lp->ll_di->di_flags, name, false)) { |  | ||||||
|       // existing variable, need to check if it can be changed |       // existing variable, need to check if it can be changed | ||||||
|  |       } else if (!(flags & GLV_READ_ONLY) && var_check_ro(lp->ll_di->di_flags, | ||||||
|  |                                                           (const char *)name, | ||||||
|  |                                                           (size_t)(p - name))) { | ||||||
|  |         if (len == -1) { | ||||||
|  |           clear_tv(&var1); | ||||||
|  |         } | ||||||
|         return NULL; |         return NULL; | ||||||
|       } |       } | ||||||
|  |  | ||||||
| @@ -2299,7 +2294,6 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch | |||||||
|   dictitem_T  *di; |   dictitem_T  *di; | ||||||
|  |  | ||||||
|   if (lp->ll_tv == NULL) { |   if (lp->ll_tv == NULL) { | ||||||
|     if (!check_changedtick(lp->ll_name)) { |  | ||||||
|     cc = *endp; |     cc = *endp; | ||||||
|     *endp = NUL; |     *endp = NUL; | ||||||
|     if (op != NULL && *op != '=') { |     if (op != NULL && *op != '=') { | ||||||
| @@ -2310,21 +2304,23 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch | |||||||
|       if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), |       if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), | ||||||
|                      &tv, &di, true, false) == OK) { |                      &tv, &di, true, false) == OK) { | ||||||
|         if ((di == NULL |         if ((di == NULL | ||||||
|                || (!var_check_ro(di->di_flags, lp->ll_name, false) |              || (!var_check_ro(di->di_flags, (const char *)lp->ll_name, | ||||||
|                    && !tv_check_lock(di->di_tv.v_lock, lp->ll_name, false))) |                                STRLEN(lp->ll_name)) | ||||||
|  |                  && !tv_check_lock(di->di_tv.v_lock, (const char *)lp->ll_name, | ||||||
|  |                                    STRLEN(lp->ll_name)))) | ||||||
|             && tv_op(&tv, rettv, op) == OK) { |             && tv_op(&tv, rettv, op) == OK) { | ||||||
|           set_var(lp->ll_name, &tv, false); |           set_var(lp->ll_name, &tv, false); | ||||||
|         } |         } | ||||||
|         clear_tv(&tv); |         clear_tv(&tv); | ||||||
|       } |       } | ||||||
|       } else |     } else { | ||||||
|       set_var(lp->ll_name, rettv, copy); |       set_var(lp->ll_name, rettv, copy); | ||||||
|       *endp = cc; |  | ||||||
|     } |     } | ||||||
|  |     *endp = cc; | ||||||
|   } else if (tv_check_lock(lp->ll_newkey == NULL |   } else if (tv_check_lock(lp->ll_newkey == NULL | ||||||
|                            ? lp->ll_tv->v_lock |                            ? lp->ll_tv->v_lock | ||||||
|                            : lp->ll_tv->vval.v_dict->dv_lock, |                            : lp->ll_tv->vval.v_dict->dv_lock, | ||||||
|                            lp->ll_name, false)) { |                            (const char *)lp->ll_name, STRLEN(lp->ll_name))) { | ||||||
|   } else if (lp->ll_range) { |   } else if (lp->ll_range) { | ||||||
|     listitem_T *ll_li = lp->ll_li; |     listitem_T *ll_li = lp->ll_li; | ||||||
|     int ll_n1 = lp->ll_n1; |     int ll_n1 = lp->ll_n1; | ||||||
| @@ -2332,7 +2328,8 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, ch | |||||||
|     // Check whether any of the list items is locked |     // Check whether any of the list items is locked | ||||||
|     for (listitem_T *ri = rettv->vval.v_list->lv_first; |     for (listitem_T *ri = rettv->vval.v_list->lv_first; | ||||||
|          ri != NULL && ll_li != NULL; ) { |          ri != NULL && ll_li != NULL; ) { | ||||||
|       if (tv_check_lock(ll_li->li_tv.v_lock, lp->ll_name, false)) { |       if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, | ||||||
|  |                         STRLEN(lp->ll_name))) { | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       ri = ri->li_next; |       ri = ri->li_next; | ||||||
| @@ -2938,16 +2935,18 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit) | |||||||
|     cc = *name_end; |     cc = *name_end; | ||||||
|     *name_end = NUL; |     *name_end = NUL; | ||||||
|  |  | ||||||
|     /* Normal name or expanded name. */ |     // Normal name or expanded name. | ||||||
|     if (check_changedtick(lp->ll_name)) |     if (do_unlet(lp->ll_name, forceit) == FAIL) { | ||||||
|       ret = FAIL; |  | ||||||
|     else if (do_unlet(lp->ll_name, forceit) == FAIL) |  | ||||||
|       ret = FAIL; |       ret = FAIL; | ||||||
|  |     } | ||||||
|     *name_end = cc; |     *name_end = cc; | ||||||
|   } else if ((lp->ll_list != NULL |   } else if ((lp->ll_list != NULL | ||||||
|               && tv_check_lock(lp->ll_list->lv_lock, lp->ll_name, false)) |               && tv_check_lock(lp->ll_list->lv_lock, (const char *)lp->ll_name, | ||||||
|  |                                STRLEN(lp->ll_name))) | ||||||
|              || (lp->ll_dict != NULL |              || (lp->ll_dict != NULL | ||||||
|                  && tv_check_lock(lp->ll_dict->dv_lock, lp->ll_name, false))) { |                  && tv_check_lock(lp->ll_dict->dv_lock, | ||||||
|  |                                   (const char *)lp->ll_name, | ||||||
|  |                                   STRLEN(lp->ll_name)))) { | ||||||
|     return FAIL; |     return FAIL; | ||||||
|   } else if (lp->ll_range) { |   } else if (lp->ll_range) { | ||||||
|     listitem_T    *li; |     listitem_T    *li; | ||||||
| @@ -2956,7 +2955,8 @@ static int do_unlet_var(lval_T *lp, char_u *name_end, int forceit) | |||||||
|  |  | ||||||
|     while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) { |     while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) { | ||||||
|       li = ll_li->li_next; |       li = ll_li->li_next; | ||||||
|       if (tv_check_lock(ll_li->li_tv.v_lock, lp->ll_name, false)) { |       if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, | ||||||
|  |                         STRLEN(lp->ll_name))) { | ||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
|       ll_li = li; |       ll_li = li; | ||||||
| @@ -3038,13 +3038,14 @@ int do_unlet(char_u *name, int forceit) | |||||||
|     } |     } | ||||||
|     if (hi != NULL && !HASHITEM_EMPTY(hi)) { |     if (hi != NULL && !HASHITEM_EMPTY(hi)) { | ||||||
|       di = HI2DI(hi); |       di = HI2DI(hi); | ||||||
|       if (var_check_fixed(di->di_flags, name, false) |       if (var_check_fixed(di->di_flags, (const char *)name, STRLEN(name)) | ||||||
|           || var_check_ro(di->di_flags, name, false) |           || var_check_ro(di->di_flags, (const char *)name, STRLEN(name)) | ||||||
|           || tv_check_lock(d->dv_lock, name, false)) { |           || tv_check_lock(d->dv_lock, (const char *)name, STRLEN(name))) { | ||||||
|         return FAIL; |         return FAIL; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (d == NULL || tv_check_lock(d->dv_lock, name, false)) { |       if (d == NULL | ||||||
|  |           || tv_check_lock(d->dv_lock, (const char *)name, STRLEN(name))) { | ||||||
|         return FAIL; |         return FAIL; | ||||||
|       } |       } | ||||||
|  |  | ||||||
| @@ -3078,23 +3079,25 @@ int do_unlet(char_u *name, int forceit) | |||||||
| static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock) | static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock) | ||||||
| { | { | ||||||
|   int ret = OK; |   int ret = OK; | ||||||
|   int cc; |  | ||||||
|   dictitem_T  *di; |  | ||||||
|  |  | ||||||
|   if (deep == 0)        /* nothing to do */ |   if (deep == 0) {  // Nothing to do. | ||||||
|     return OK; |     return OK; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if (lp->ll_tv == NULL) { |   if (lp->ll_tv == NULL) { | ||||||
|     cc = *name_end; |  | ||||||
|     *name_end = NUL; |  | ||||||
|  |  | ||||||
|     // Normal name or expanded name. |     // Normal name or expanded name. | ||||||
|     if (check_changedtick(lp->ll_name)) { |     const size_t name_len = (size_t)(name_end - lp->ll_name); | ||||||
|       ret = FAIL; |     dictitem_T *const di = find_var( | ||||||
|     } else { |         (const char *)lp->ll_name, name_len, NULL, | ||||||
|       di = find_var((const char *)lp->ll_name, STRLEN(lp->ll_name), NULL, true); |         true); | ||||||
|     if (di == NULL) { |     if (di == NULL) { | ||||||
|       ret = FAIL; |       ret = FAIL; | ||||||
|  |     } else if ((di->di_flags & DI_FLAGS_FIX) | ||||||
|  |                && di->di_tv.v_type != VAR_DICT | ||||||
|  |                && di->di_tv.v_type != VAR_LIST) { | ||||||
|  |       // For historical reasons this error is not given for Lists and | ||||||
|  |       // Dictionaries. E.g. b: dictionary may be locked/unlocked. | ||||||
|  |       emsgf(_("E940: Cannot lock or unlock variable %s"), lp->ll_name); | ||||||
|     } else { |     } else { | ||||||
|       if (lock) { |       if (lock) { | ||||||
|         di->di_flags |= DI_FLAGS_LOCK; |         di->di_flags |= DI_FLAGS_LOCK; | ||||||
| @@ -3103,8 +3106,6 @@ static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock) | |||||||
|       } |       } | ||||||
|       item_lock(&di->di_tv, deep, lock); |       item_lock(&di->di_tv, deep, lock); | ||||||
|     } |     } | ||||||
|     } |  | ||||||
|     *name_end = cc; |  | ||||||
|   } else if (lp->ll_range) { |   } else if (lp->ll_range) { | ||||||
|     listitem_T    *li = lp->ll_li; |     listitem_T    *li = lp->ll_li; | ||||||
|  |  | ||||||
| @@ -3131,68 +3132,72 @@ static int do_lock_var(lval_T *lp, char_u *name_end, int deep, int lock) | |||||||
| static void item_lock(typval_T *tv, int deep, int lock) | static void item_lock(typval_T *tv, int deep, int lock) | ||||||
| { | { | ||||||
|   static int recurse = 0; |   static int recurse = 0; | ||||||
|   list_T      *l; |  | ||||||
|   listitem_T  *li; |  | ||||||
|   dict_T      *d; |  | ||||||
|   hashitem_T  *hi; |  | ||||||
|   int todo; |  | ||||||
|  |  | ||||||
|   if (recurse >= DICT_MAXNEST) { |   if (recurse >= DICT_MAXNEST) { | ||||||
|     EMSG(_("E743: variable nested too deep for (un)lock")); |     EMSG(_("E743: variable nested too deep for (un)lock")); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   if (deep == 0) |   if (deep == 0) { | ||||||
|     return; |     return; | ||||||
|   ++recurse; |   } | ||||||
|  |   recurse++; | ||||||
|  |  | ||||||
|   /* lock/unlock the item itself */ |   // lock/unlock the item itself | ||||||
|   if (lock) | #define CHANGE_LOCK(var, lock) \ | ||||||
|     tv->v_lock |= VAR_LOCKED; |   do { \ | ||||||
|   else |     var = ((VarLockStatus[]) { \ | ||||||
|     tv->v_lock &= ~VAR_LOCKED; |       [VAR_UNLOCKED] = (lock ? VAR_LOCKED : VAR_UNLOCKED), \ | ||||||
|  |       [VAR_LOCKED] = (lock ? VAR_LOCKED : VAR_UNLOCKED), \ | ||||||
|  |       [VAR_FIXED] = VAR_FIXED, \ | ||||||
|  |     })[var]; \ | ||||||
|  |   } while (0) | ||||||
|  |   CHANGE_LOCK(tv->v_lock, lock); | ||||||
|  |  | ||||||
|   switch (tv->v_type) { |   switch (tv->v_type) { | ||||||
|   case VAR_LIST: |     case VAR_LIST: { | ||||||
|     if ((l = tv->vval.v_list) != NULL) { |       list_T *const l = tv->vval.v_list; | ||||||
|       if (lock) |       if (l != NULL) { | ||||||
|         l->lv_lock |= VAR_LOCKED; |         CHANGE_LOCK(l->lv_lock, lock); | ||||||
|       else |         if (deep < 0 || deep > 1) { | ||||||
|         l->lv_lock &= ~VAR_LOCKED; |           // Recursive: lock/unlock the items the List contains. | ||||||
|       if (deep < 0 || deep > 1) |           for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) { | ||||||
|         /* recursive: lock/unlock the items the List contains */ |  | ||||||
|         for (li = l->lv_first; li != NULL; li = li->li_next) |  | ||||||
|             item_lock(&li->li_tv, deep - 1, lock); |             item_lock(&li->li_tv, deep - 1, lock); | ||||||
|           } |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|       break; |       break; | ||||||
|   case VAR_DICT: |     } | ||||||
|     if ((d = tv->vval.v_dict) != NULL) { |     case VAR_DICT: { | ||||||
|       if (lock) |       dict_T *const d = tv->vval.v_dict; | ||||||
|         d->dv_lock |= VAR_LOCKED; |       if (d != NULL) { | ||||||
|       else |         CHANGE_LOCK(d->dv_lock, lock); | ||||||
|         d->dv_lock &= ~VAR_LOCKED; |  | ||||||
|         if (deep < 0 || deep > 1) { |         if (deep < 0 || deep > 1) { | ||||||
|         /* recursive: lock/unlock the items the List contains */ |           // Recursive: lock/unlock the items the List contains. | ||||||
|         todo = (int)d->dv_hashtab.ht_used; |           int todo = (int)d->dv_hashtab.ht_used; | ||||||
|         for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { |           for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) { | ||||||
|             if (!HASHITEM_EMPTY(hi)) { |             if (!HASHITEM_EMPTY(hi)) { | ||||||
|             --todo; |               todo--; | ||||||
|               item_lock(&HI2DI(hi)->di_tv, deep - 1, lock); |               item_lock(&HI2DI(hi)->di_tv, deep - 1, lock); | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|  |     } | ||||||
|     case VAR_NUMBER: |     case VAR_NUMBER: | ||||||
|     case VAR_FLOAT: |     case VAR_FLOAT: | ||||||
|     case VAR_STRING: |     case VAR_STRING: | ||||||
|     case VAR_FUNC: |     case VAR_FUNC: | ||||||
|     case VAR_PARTIAL: |     case VAR_PARTIAL: | ||||||
|   case VAR_SPECIAL: |     case VAR_SPECIAL: { | ||||||
|       break; |       break; | ||||||
|   case VAR_UNKNOWN: |     } | ||||||
|  |     case VAR_UNKNOWN: { | ||||||
|       assert(false); |       assert(false); | ||||||
|     } |     } | ||||||
|   --recurse; |   } | ||||||
|  | #undef CHANGE_LOCK | ||||||
|  |   recurse--; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -3302,10 +3307,6 @@ char_u *get_user_var_name(expand_T *xp, int idx) | |||||||
|       ++hi; |       ++hi; | ||||||
|     return cat_prefix_varname('b', hi->hi_key); |     return cat_prefix_varname('b', hi->hi_key); | ||||||
|   } |   } | ||||||
|   if (bdone == ht->ht_used) { |  | ||||||
|     ++bdone; |  | ||||||
|     return (char_u *)"b:changedtick"; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /* w: variables */ |   /* w: variables */ | ||||||
|   ht = &curwin->w_vars->dv_hashtab; |   ht = &curwin->w_vars->dv_hashtab; | ||||||
| @@ -6373,7 +6374,7 @@ dict_T *dict_alloc(void) FUNC_ATTR_NONNULL_RET | |||||||
|   first_dict = d; |   first_dict = d; | ||||||
|  |  | ||||||
|   hash_init(&d->dv_hashtab); |   hash_init(&d->dv_hashtab); | ||||||
|   d->dv_lock = 0; |   d->dv_lock = VAR_UNLOCKED; | ||||||
|   d->dv_scope = 0; |   d->dv_scope = 0; | ||||||
|   d->dv_refcount = 0; |   d->dv_refcount = 0; | ||||||
|   d->dv_copyID = 0; |   d->dv_copyID = 0; | ||||||
| @@ -6446,9 +6447,8 @@ static void dict_free_contents(dict_T *d) { | |||||||
|        * something recursive causing trouble. */ |        * something recursive causing trouble. */ | ||||||
|       di = HI2DI(hi); |       di = HI2DI(hi); | ||||||
|       hash_remove(&d->dv_hashtab, hi); |       hash_remove(&d->dv_hashtab, hi); | ||||||
|       clear_tv(&di->di_tv); |       dictitem_free(di); | ||||||
|       xfree(di); |       todo--; | ||||||
|       --todo; |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -7820,9 +7820,10 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) | |||||||
|  |  | ||||||
|   rettv->vval.v_number = 1;   /* Default: Failed */ |   rettv->vval.v_number = 1;   /* Default: Failed */ | ||||||
|   if (argvars[0].v_type == VAR_LIST) { |   if (argvars[0].v_type == VAR_LIST) { | ||||||
|  |     const char *const arg_errmsg = _("add() argument"); | ||||||
|  |     const size_t arg_errmsg_len = strlen(arg_errmsg); | ||||||
|     if ((l = argvars[0].vval.v_list) != NULL |     if ((l = argvars[0].vval.v_list) != NULL | ||||||
|         && !tv_check_lock(l->lv_lock, |         && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { | ||||||
|                           (char_u *)N_("add() argument"), true)) { |  | ||||||
|       list_append_tv(l, &argvars[1]); |       list_append_tv(l, &argvars[1]); | ||||||
|       copy_tv(&argvars[0], rettv); |       copy_tv(&argvars[0], rettv); | ||||||
|     } |     } | ||||||
| @@ -9399,7 +9400,8 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action) | |||||||
|   hashitem_T  *hi2; |   hashitem_T  *hi2; | ||||||
|   int todo; |   int todo; | ||||||
|   bool watched = is_watched(d1); |   bool watched = is_watched(d1); | ||||||
|   char_u *arg_errmsg = (char_u *)N_("extend() argument"); |   const char *const arg_errmsg = _("extend() argument"); | ||||||
|  |   const size_t arg_errmsg_len = strlen(arg_errmsg); | ||||||
|  |  | ||||||
|   todo = (int)d2->dv_hashtab.ht_used; |   todo = (int)d2->dv_hashtab.ht_used; | ||||||
|   for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2) { |   for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2) { | ||||||
| @@ -9433,8 +9435,8 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action) | |||||||
|       } else if (*action == 'f' && HI2DI(hi2) != di1) { |       } else if (*action == 'f' && HI2DI(hi2) != di1) { | ||||||
|         typval_T oldtv; |         typval_T oldtv; | ||||||
|  |  | ||||||
|         if (tv_check_lock(di1->di_tv.v_lock, arg_errmsg, true) |         if (tv_check_lock(di1->di_tv.v_lock, arg_errmsg, arg_errmsg_len) | ||||||
|             || var_check_ro(di1->di_flags, arg_errmsg, true)) { |             || var_check_ro(di1->di_flags, arg_errmsg, arg_errmsg_len)) { | ||||||
|           break; |           break; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -9460,7 +9462,8 @@ void dict_extend(dict_T *d1, dict_T *d2, char_u *action) | |||||||
|  */ |  */ | ||||||
| static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) | static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) | ||||||
| { | { | ||||||
|   char_u *arg_errmsg = (char_u *)N_("extend() argument"); |   const char *const arg_errmsg = N_("extend() argument"); | ||||||
|  |   const size_t arg_errmsg_len = strlen(arg_errmsg); | ||||||
|  |  | ||||||
|   if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { |   if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { | ||||||
|     list_T          *l1, *l2; |     list_T          *l1, *l2; | ||||||
| @@ -9470,7 +9473,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) | |||||||
|  |  | ||||||
|     l1 = argvars[0].vval.v_list; |     l1 = argvars[0].vval.v_list; | ||||||
|     l2 = argvars[1].vval.v_list; |     l2 = argvars[1].vval.v_list; | ||||||
|     if (l1 != NULL && !tv_check_lock(l1->lv_lock, arg_errmsg, true) |     if (l1 != NULL && !tv_check_lock(l1->lv_lock, arg_errmsg, arg_errmsg_len) | ||||||
|         && l2 != NULL) { |         && l2 != NULL) { | ||||||
|       if (argvars[2].v_type != VAR_UNKNOWN) { |       if (argvars[2].v_type != VAR_UNKNOWN) { | ||||||
|         before = get_tv_number_chk(&argvars[2], &error); |         before = get_tv_number_chk(&argvars[2], &error); | ||||||
| @@ -9500,7 +9503,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) | |||||||
|  |  | ||||||
|     d1 = argvars[0].vval.v_dict; |     d1 = argvars[0].vval.v_dict; | ||||||
|     d2 = argvars[1].vval.v_dict; |     d2 = argvars[1].vval.v_dict; | ||||||
|     if (d1 != NULL && !tv_check_lock(d1->dv_lock, arg_errmsg, true) |     if (d1 != NULL && !tv_check_lock(d1->dv_lock, arg_errmsg, arg_errmsg_len) | ||||||
|         && d2 != NULL) { |         && d2 != NULL) { | ||||||
|       /* Check the third argument. */ |       /* Check the third argument. */ | ||||||
|       if (argvars[2].v_type != VAR_UNKNOWN) { |       if (argvars[2].v_type != VAR_UNKNOWN) { | ||||||
| @@ -9644,19 +9647,21 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) | |||||||
|   int rem = false; |   int rem = false; | ||||||
|   int todo; |   int todo; | ||||||
|   char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); |   char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); | ||||||
|   char_u      *arg_errmsg = (char_u *)(map ? N_("map() argument") |   const char *const arg_errmsg = (map | ||||||
|                                        : N_("filter() argument")); |                                   ? _("map() argument") | ||||||
|  |                                   : _("filter() argument")); | ||||||
|  |   const size_t arg_errmsg_len = strlen(arg_errmsg); | ||||||
|   int save_did_emsg; |   int save_did_emsg; | ||||||
|   int idx = 0; |   int idx = 0; | ||||||
|  |  | ||||||
|   if (argvars[0].v_type == VAR_LIST) { |   if (argvars[0].v_type == VAR_LIST) { | ||||||
|     if ((l = argvars[0].vval.v_list) == NULL |     if ((l = argvars[0].vval.v_list) == NULL | ||||||
|         || (!map && tv_check_lock(l->lv_lock, arg_errmsg, true))) { |         || (!map && tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len))) { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   } else if (argvars[0].v_type == VAR_DICT) { |   } else if (argvars[0].v_type == VAR_DICT) { | ||||||
|     if ((d = argvars[0].vval.v_dict) == NULL |     if ((d = argvars[0].vval.v_dict) == NULL | ||||||
|         || (!map && tv_check_lock(d->dv_lock, arg_errmsg, true))) { |         || (!map && tv_check_lock(d->dv_lock, arg_errmsg, arg_errmsg_len))) { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   } else { |   } else { | ||||||
| @@ -9689,8 +9694,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) | |||||||
|  |  | ||||||
|           di = HI2DI(hi); |           di = HI2DI(hi); | ||||||
|           if (map |           if (map | ||||||
|               && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, true) |               && (tv_check_lock(di->di_tv.v_lock, arg_errmsg, arg_errmsg_len) | ||||||
|                   || var_check_ro(di->di_flags, arg_errmsg, true))) { |                   || var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len))) { | ||||||
|             break; |             break; | ||||||
|           } |           } | ||||||
|  |  | ||||||
| @@ -9700,8 +9705,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) | |||||||
|           if (r == FAIL || did_emsg) |           if (r == FAIL || did_emsg) | ||||||
|             break; |             break; | ||||||
|           if (!map && rem) { |           if (!map && rem) { | ||||||
|             if (var_check_fixed(di->di_flags, arg_errmsg, true) |             if (var_check_fixed(di->di_flags, arg_errmsg, arg_errmsg_len) | ||||||
|                 || var_check_ro(di->di_flags, arg_errmsg, true)) { |                 || var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len)) { | ||||||
|               break; |               break; | ||||||
|             } |             } | ||||||
|             dictitem_remove(d, di); |             dictitem_remove(d, di); | ||||||
| @@ -9713,7 +9718,8 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) | |||||||
|       vimvars[VV_KEY].vv_type = VAR_NUMBER; |       vimvars[VV_KEY].vv_type = VAR_NUMBER; | ||||||
|  |  | ||||||
|       for (li = l->lv_first; li != NULL; li = nli) { |       for (li = l->lv_first; li != NULL; li = nli) { | ||||||
|         if (map && tv_check_lock(li->li_tv.v_lock, arg_errmsg, true)) { |         if (map | ||||||
|  |             && tv_check_lock(li->li_tv.v_lock, arg_errmsg, arg_errmsg_len)) { | ||||||
|           break; |           break; | ||||||
|         } |         } | ||||||
|         nli = li->li_next; |         nli = li->li_next; | ||||||
| @@ -10524,10 +10530,6 @@ static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) | |||||||
|         // buffer-local-option |         // buffer-local-option | ||||||
|         done = true; |         done = true; | ||||||
|       } |       } | ||||||
|     } else if (STRCMP(varname, "changedtick") == 0) { |  | ||||||
|       rettv->v_type = VAR_NUMBER; |  | ||||||
|       rettv->vval.v_number = curbuf->b_changedtick; |  | ||||||
|       done = true; |  | ||||||
|     } else { |     } else { | ||||||
|       // Look up the variable. |       // Look up the variable. | ||||||
|       // Let getbufvar({nr}, "") return the "b:" dictionary. |       // Let getbufvar({nr}, "") return the "b:" dictionary. | ||||||
| @@ -12395,13 +12397,14 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) | |||||||
|   long before = 0; |   long before = 0; | ||||||
|   listitem_T  *item; |   listitem_T  *item; | ||||||
|   list_T      *l; |   list_T      *l; | ||||||
|   int error = FALSE; |   int error = false; | ||||||
|  |   const char *const arg_errmsg = _("insert() argument"); | ||||||
|  |   const size_t arg_errmsg_len = strlen(arg_errmsg); | ||||||
|  |  | ||||||
|   if (argvars[0].v_type != VAR_LIST) { |   if (argvars[0].v_type != VAR_LIST) { | ||||||
|     EMSG2(_(e_listarg), "insert()"); |     EMSG2(_(e_listarg), "insert()"); | ||||||
|   } else if ((l = argvars[0].vval.v_list) != NULL |   } else if ((l = argvars[0].vval.v_list) != NULL | ||||||
|              && !tv_check_lock(l->lv_lock, |              && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { | ||||||
|                                (char_u *)N_("insert() argument"), true)) { |  | ||||||
|     if (argvars[2].v_type != VAR_UNKNOWN) { |     if (argvars[2].v_type != VAR_UNKNOWN) { | ||||||
|       before = get_tv_number_chk(&argvars[2], &error); |       before = get_tv_number_chk(&argvars[2], &error); | ||||||
|     } |     } | ||||||
| @@ -12452,40 +12455,35 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) | |||||||
|   dictitem_T  *di; |   dictitem_T  *di; | ||||||
|  |  | ||||||
|   rettv->vval.v_number = -1; |   rettv->vval.v_number = -1; | ||||||
|   end = get_lval(get_tv_string(&argvars[0]), NULL, &lv, FALSE, FALSE, |   end = get_lval(get_tv_string(&argvars[0]), NULL, &lv, false, false, | ||||||
|       GLV_NO_AUTOLOAD, FNE_CHECK_START); |                  GLV_NO_AUTOLOAD|GLV_READ_ONLY, FNE_CHECK_START); | ||||||
|   if (end != NULL && lv.ll_name != NULL) { |   if (end != NULL && lv.ll_name != NULL) { | ||||||
|     if (*end != NUL) |     if (*end != NUL) | ||||||
|       EMSG(_(e_trailing)); |       EMSG(_(e_trailing)); | ||||||
|     else { |     else { | ||||||
|       if (lv.ll_tv == NULL) { |       if (lv.ll_tv == NULL) { | ||||||
|         if (check_changedtick(lv.ll_name)) { |         di = find_var((const char *)lv.ll_name, STRLEN(lv.ll_name), NULL, true); | ||||||
|           rettv->vval.v_number = 1;  // Always locked. |  | ||||||
|         } else { |  | ||||||
|           di = find_var((const char *)lv.ll_name, STRLEN(lv.ll_name), NULL, |  | ||||||
|                         true); |  | ||||||
|         if (di != NULL) { |         if (di != NULL) { | ||||||
|             /* Consider a variable locked when: |           // Consider a variable locked when: | ||||||
|              * 1. the variable itself is locked |           // 1. the variable itself is locked | ||||||
|              * 2. the value of the variable is locked. |           // 2. the value of the variable is locked. | ||||||
|              * 3. the List or Dict value is locked. |           // 3. the List or Dict value is locked. | ||||||
|              */ |  | ||||||
|           rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK) |           rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK) | ||||||
|                                   || tv_islocked(&di->di_tv)); |                                   || tv_islocked(&di->di_tv)); | ||||||
|         } |         } | ||||||
|         } |       } else if (lv.ll_range) { | ||||||
|       } else if (lv.ll_range) |  | ||||||
|         EMSG(_("E786: Range not allowed")); |         EMSG(_("E786: Range not allowed")); | ||||||
|       else if (lv.ll_newkey != NULL) |       } else if (lv.ll_newkey != NULL) { | ||||||
|         EMSG2(_(e_dictkey), lv.ll_newkey); |         EMSG2(_(e_dictkey), lv.ll_newkey); | ||||||
|       else if (lv.ll_list != NULL) |       } else if (lv.ll_list != NULL) { | ||||||
|         /* List item. */ |         // List item. | ||||||
|         rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv); |         rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv); | ||||||
|       else |       } else { | ||||||
|         /* Dictionary item. */ |         // Dictionary item. | ||||||
|         rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); |         rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   clear_lval(&lv); |   clear_lval(&lv); | ||||||
| } | } | ||||||
| @@ -14430,20 +14428,21 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) | |||||||
|   char_u      *key; |   char_u      *key; | ||||||
|   dict_T      *d; |   dict_T      *d; | ||||||
|   dictitem_T  *di; |   dictitem_T  *di; | ||||||
|   char_u      *arg_errmsg = (char_u *)N_("remove() argument"); |   const char *const arg_errmsg = _("remove() argument"); | ||||||
|  |   const size_t arg_errmsg_len = strlen(arg_errmsg); | ||||||
|  |  | ||||||
|   if (argvars[0].v_type == VAR_DICT) { |   if (argvars[0].v_type == VAR_DICT) { | ||||||
|     if (argvars[2].v_type != VAR_UNKNOWN) { |     if (argvars[2].v_type != VAR_UNKNOWN) { | ||||||
|       EMSG2(_(e_toomanyarg), "remove()"); |       EMSG2(_(e_toomanyarg), "remove()"); | ||||||
|     } else if ((d = argvars[0].vval.v_dict) != NULL |     } else if ((d = argvars[0].vval.v_dict) != NULL | ||||||
|                && !tv_check_lock(d->dv_lock, arg_errmsg, true)) { |                && !tv_check_lock(d->dv_lock, arg_errmsg, arg_errmsg_len)) { | ||||||
|       key = get_tv_string_chk(&argvars[1]); |       key = get_tv_string_chk(&argvars[1]); | ||||||
|       if (key != NULL) { |       if (key != NULL) { | ||||||
|         di = dict_find(d, key, -1); |         di = dict_find(d, key, -1); | ||||||
|         if (di == NULL) { |         if (di == NULL) { | ||||||
|           EMSG2(_(e_dictkey), key); |           EMSG2(_(e_dictkey), key); | ||||||
|         } else if (!var_check_fixed(di->di_flags, arg_errmsg, true) |         } else if (!var_check_fixed(di->di_flags, arg_errmsg, arg_errmsg_len) | ||||||
|                    && !var_check_ro(di->di_flags, arg_errmsg, true)) { |                    && !var_check_ro(di->di_flags, arg_errmsg, arg_errmsg_len)) { | ||||||
|           *rettv = di->di_tv; |           *rettv = di->di_tv; | ||||||
|           init_tv(&di->di_tv); |           init_tv(&di->di_tv); | ||||||
|           dictitem_remove(d, di); |           dictitem_remove(d, di); | ||||||
| @@ -14456,7 +14455,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) | |||||||
|   } else if (argvars[0].v_type != VAR_LIST) { |   } else if (argvars[0].v_type != VAR_LIST) { | ||||||
|     EMSG2(_(e_listdictarg), "remove()"); |     EMSG2(_(e_listdictarg), "remove()"); | ||||||
|   } else if ((l = argvars[0].vval.v_list) != NULL |   } else if ((l = argvars[0].vval.v_list) != NULL | ||||||
|              && !tv_check_lock(l->lv_lock, arg_errmsg, true)) { |              && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { | ||||||
|     int error = (int)false; |     int error = (int)false; | ||||||
|  |  | ||||||
|     idx = get_tv_number_chk(&argvars[1], &error); |     idx = get_tv_number_chk(&argvars[1], &error); | ||||||
| @@ -14727,19 +14726,19 @@ fail: | |||||||
|  */ |  */ | ||||||
| static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) | static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) | ||||||
| { | { | ||||||
|   list_T      *l; |   const char *const arg_errmsg = _("reverse() argument"); | ||||||
|   listitem_T  *li, *ni; |   const size_t arg_errmsg_len = strlen(arg_errmsg); | ||||||
|  |  | ||||||
|  |   list_T *l; | ||||||
|   if (argvars[0].v_type != VAR_LIST) { |   if (argvars[0].v_type != VAR_LIST) { | ||||||
|     EMSG2(_(e_listarg), "reverse()"); |     EMSG2(_(e_listarg), "reverse()"); | ||||||
|   } else if ((l = argvars[0].vval.v_list) != NULL |   } else if ((l = argvars[0].vval.v_list) != NULL | ||||||
|              && !tv_check_lock(l->lv_lock, |              && !tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { | ||||||
|                                (char_u *)N_("reverse() argument"), true)) { |     listitem_T *li = l->lv_last; | ||||||
|     li = l->lv_last; |  | ||||||
|     l->lv_first = l->lv_last = NULL; |     l->lv_first = l->lv_last = NULL; | ||||||
|     l->lv_len = 0; |     l->lv_len = 0; | ||||||
|     while (li != NULL) { |     while (li != NULL) { | ||||||
|       ni = li->li_prev; |       listitem_T *const ni = li->li_prev; | ||||||
|       list_append(l, li); |       list_append(l, li); | ||||||
|       li = ni; |       li = ni; | ||||||
|     } |     } | ||||||
| @@ -16384,16 +16383,17 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) | |||||||
|   sortinfo_T *old_sortinfo = sortinfo; |   sortinfo_T *old_sortinfo = sortinfo; | ||||||
|   sortinfo = &info; |   sortinfo = &info; | ||||||
|  |  | ||||||
|  |   const char *const arg_errmsg = (sort | ||||||
|  |                                   ? _("sort() argument") | ||||||
|  |                                   : _("uniq() argument")); | ||||||
|  |   const size_t arg_errmsg_len = strlen(arg_errmsg); | ||||||
|  |  | ||||||
|   if (argvars[0].v_type != VAR_LIST) { |   if (argvars[0].v_type != VAR_LIST) { | ||||||
|     EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); |     EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); | ||||||
|   } else { |   } else { | ||||||
|     l = argvars[0].vval.v_list; |     l = argvars[0].vval.v_list; | ||||||
|     if (l == NULL |     if (l == NULL | ||||||
|         || tv_check_lock(l->lv_lock, |         || tv_check_lock(l->lv_lock, arg_errmsg, arg_errmsg_len)) { | ||||||
|                          (char_u *)(sort |  | ||||||
|                                     ? N_("sort() argument") |  | ||||||
|                                     : N_("uniq() argument")), |  | ||||||
|                          true)) { |  | ||||||
|         goto theend; |         goto theend; | ||||||
|     } |     } | ||||||
|     rettv->vval.v_list = l; |     rettv->vval.v_list = l; | ||||||
| @@ -17732,9 +17732,9 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) | |||||||
|   (void)setfname(curbuf, (uint8_t *)buf, NULL, true); |   (void)setfname(curbuf, (uint8_t *)buf, NULL, true); | ||||||
|   // Save the job id and pid in b:terminal_job_{id,pid} |   // Save the job id and pid in b:terminal_job_{id,pid} | ||||||
|   Error err; |   Error err; | ||||||
|   dict_set_value(curbuf->b_vars, cstr_as_string("terminal_job_id"), |   dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"), | ||||||
|                INTEGER_OBJ(rettv->vval.v_number), false, false, &err); |                INTEGER_OBJ(rettv->vval.v_number), false, false, &err); | ||||||
|   dict_set_value(curbuf->b_vars, cstr_as_string("terminal_job_pid"), |   dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"), | ||||||
|                INTEGER_OBJ(pid), false, false, &err); |                INTEGER_OBJ(pid), false, false, &err); | ||||||
|  |  | ||||||
|   Terminal *term = terminal_open(topts); |   Terminal *term = terminal_open(topts); | ||||||
| @@ -19382,17 +19382,8 @@ static int get_var_tv( | |||||||
| { | { | ||||||
|   int ret = OK; |   int ret = OK; | ||||||
|   typval_T    *tv = NULL; |   typval_T    *tv = NULL; | ||||||
|   typval_T atv; |  | ||||||
|   dictitem_T  *v; |   dictitem_T  *v; | ||||||
|  |  | ||||||
|   // Check for "b:changedtick". |  | ||||||
|   if (sizeof("b:changedtick") - 1 == len |  | ||||||
|       && STRNCMP(name, "b:changedtick", len) == 0) { |  | ||||||
|     atv.v_type = VAR_NUMBER; |  | ||||||
|     atv.vval.v_number = curbuf->b_changedtick; |  | ||||||
|     tv = &atv; |  | ||||||
|   } else { |  | ||||||
|     // Check for user-defined variables. |  | ||||||
|   v = find_var(name, (size_t)len, NULL, no_autoload); |   v = find_var(name, (size_t)len, NULL, no_autoload); | ||||||
|   if (v != NULL) { |   if (v != NULL) { | ||||||
|     tv = &v->di_tv; |     tv = &v->di_tv; | ||||||
| @@ -19400,7 +19391,6 @@ static int get_var_tv( | |||||||
|       *dip = v; |       *dip = v; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (tv == NULL) { |   if (tv == NULL) { | ||||||
|     if (rettv != NULL && verbose) { |     if (rettv != NULL && verbose) { | ||||||
| @@ -20381,7 +20371,7 @@ void new_script_vars(scid_T id) | |||||||
| void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope) | void init_var_dict(dict_T *dict, dictitem_T *dict_var, int scope) | ||||||
| { | { | ||||||
|   hash_init(&dict->dv_hashtab); |   hash_init(&dict->dv_hashtab); | ||||||
|   dict->dv_lock = 0; |   dict->dv_lock = VAR_UNLOCKED; | ||||||
|   dict->dv_scope = scope; |   dict->dv_scope = scope; | ||||||
|   dict->dv_refcount = DO_NOT_FREE_CNT; |   dict->dv_refcount = DO_NOT_FREE_CNT; | ||||||
|   dict->dv_copyID = 0; |   dict->dv_copyID = 0; | ||||||
| @@ -20557,8 +20547,8 @@ set_var ( | |||||||
|  |  | ||||||
|   if (v != NULL) { |   if (v != NULL) { | ||||||
|     // existing variable, need to clear the value |     // existing variable, need to clear the value | ||||||
|     if (var_check_ro(v->di_flags, name, false) |     if (var_check_ro(v->di_flags, (const char *)name, name_len) | ||||||
|         || tv_check_lock(v->di_tv.v_lock, name, false)) { |         || tv_check_lock(v->di_tv.v_lock, (const char *)name, name_len)) { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -20631,28 +20621,47 @@ set_var ( | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| // Return true if di_flags "flags" indicates variable "name" is read-only. | /// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX) | ||||||
| // Also give an error message. | /// | ||||||
| static bool var_check_ro(int flags, char_u *name, bool use_gettext) | /// Also gives an error message. | ||||||
|  | /// | ||||||
|  | /// @param[in]  flags  di_flags attribute value. | ||||||
|  | /// @param[in]  name  Variable name, for use in error message. | ||||||
|  | /// @param[in]  name_len  Variable name length. | ||||||
|  | /// | ||||||
|  | /// @return True if variable is read-only: either always or in sandbox when | ||||||
|  | ///         sandbox is enabled, false otherwise. | ||||||
|  | static bool var_check_ro(const int flags, const char *const name, | ||||||
|  |                          const size_t name_len) | ||||||
|  |   FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL | ||||||
| { | { | ||||||
|   if (flags & DI_FLAGS_RO) { |   if (flags & DI_FLAGS_RO) { | ||||||
|     EMSG2(_(e_readonlyvar), use_gettext ? (char_u *)_(name) : name); |     emsgf(_(e_readonlyvar), (int)name_len, name); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   if ((flags & DI_FLAGS_RO_SBX) && sandbox) { |   if ((flags & DI_FLAGS_RO_SBX) && sandbox) { | ||||||
|     EMSG2(_(e_readonlysbx), use_gettext ? (char_u *)_(name) : name); |     emsgf(_("E794: Cannot set variable in the sandbox: \"%.*s\""), | ||||||
|  |           (int)name_len, name); | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   return false; |   return false; | ||||||
| } | } | ||||||
|  |  | ||||||
| // Return true if di_flags "flags" indicates variable "name" is fixed. | /// Check whether variable is fixed (DI_FLAGS_FIX) | ||||||
| // Also give an error message. | /// | ||||||
| static bool var_check_fixed(int flags, char_u *name, bool use_gettext) | /// Also gives an error message. | ||||||
|  | /// | ||||||
|  | /// @param[in]  flags  di_flags attribute value. | ||||||
|  | /// @param[in]  name  Variable name, for use in error message. | ||||||
|  | /// @param[in]  name_len  Variable name length. | ||||||
|  | /// | ||||||
|  | /// @return True if variable is fixed, false otherwise. | ||||||
|  | static bool var_check_fixed(const int flags, const char *const name, | ||||||
|  |                             const size_t name_len) | ||||||
|  |   FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL | ||||||
| { | { | ||||||
|   if (flags & DI_FLAGS_FIX) { |   if (flags & DI_FLAGS_FIX) { | ||||||
|     EMSG2(_("E795: Cannot delete variable %s"), |     emsgf(_("E795: Cannot delete variable %.*s"), (int)name_len, name); | ||||||
|           use_gettext ? (char_u *)_(name) : name); |  | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|   return false; |   return false; | ||||||
| @@ -20703,28 +20712,42 @@ static int valid_varname(char_u *varname) | |||||||
|   return TRUE; |   return TRUE; | ||||||
| } | } | ||||||
|  |  | ||||||
| // Return true if typeval "tv" is set to be locked (immutable). | /// Check whether typval is locked | ||||||
| // Also give an error message, using "name" or _("name") when use_gettext is | /// | ||||||
| // true. | /// Also gives an error message. | ||||||
| static bool tv_check_lock(int lock, char_u *name, bool use_gettext) | /// | ||||||
|  | /// @param[in]  lock  Lock status. | ||||||
|  | /// @param[in]  name  Value name, for use in error message. | ||||||
|  | /// @param[in]  name_len  Value name length. | ||||||
|  | /// | ||||||
|  | /// @return True if value is locked. | ||||||
|  | static bool tv_check_lock(const VarLockStatus lock, | ||||||
|  |                           const char *const name, | ||||||
|  |                           const size_t name_len) | ||||||
|  |   FUNC_ATTR_WARN_UNUSED_RESULT | ||||||
| { | { | ||||||
|   if (lock & VAR_LOCKED) { |   const char *error_message = NULL; | ||||||
|     EMSG2(_("E741: Value is locked: %s"), |   switch (lock) { | ||||||
|           name == NULL |     case VAR_UNLOCKED: { | ||||||
|           ? (char_u *)_("Unknown") |  | ||||||
|           : use_gettext ? (char_u *)_(name) |  | ||||||
|           : name); |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|   if (lock & VAR_FIXED) { |  | ||||||
|     EMSG2(_("E742: Cannot change value of %s"), |  | ||||||
|           name == NULL |  | ||||||
|           ? (char_u *)_("Unknown") |  | ||||||
|           : use_gettext ? (char_u *)_(name) |  | ||||||
|           : name); |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|       return false; |       return false; | ||||||
|  |     } | ||||||
|  |     case VAR_LOCKED: { | ||||||
|  |       error_message = N_("E741: Value is locked: %.*s"); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     case VAR_FIXED: { | ||||||
|  |       error_message = N_("E742: Cannot change value of %.*s"); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   assert(error_message != NULL); | ||||||
|  |  | ||||||
|  |   const char *const unknown_name = _("Unknown"); | ||||||
|  |  | ||||||
|  |   emsgf(_(error_message), (name != NULL ? name_len : strlen(unknown_name)), | ||||||
|  |         (name != NULL ? name : unknown_name)); | ||||||
|  |  | ||||||
|  |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -21523,11 +21546,13 @@ void ex_function(exarg_T *eap) | |||||||
|       goto erret; |       goto erret; | ||||||
|     } |     } | ||||||
|     if (fudi.fd_di == NULL) { |     if (fudi.fd_di == NULL) { | ||||||
|       if (tv_check_lock(fudi.fd_dict->dv_lock, eap->arg, false)) { |       if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, | ||||||
|  |                         STRLEN(eap->arg))) { | ||||||
|         // Can't add a function to a locked dictionary |         // Can't add a function to a locked dictionary | ||||||
|         goto erret; |         goto erret; | ||||||
|       } |       } | ||||||
|     } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, eap->arg, false)) { |     } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, | ||||||
|  |                              STRLEN(eap->arg))) { | ||||||
|       // Can't change an existing function if it is locked |       // Can't change an existing function if it is locked | ||||||
|       goto erret; |       goto erret; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -30,8 +30,8 @@ typedef enum { | |||||||
| /// Variable lock status for typval_T.v_lock | /// Variable lock status for typval_T.v_lock | ||||||
| typedef enum { | typedef enum { | ||||||
|   VAR_UNLOCKED = 0,  ///< Not locked. |   VAR_UNLOCKED = 0,  ///< Not locked. | ||||||
|   VAR_LOCKED,        ///< User lock, can be unlocked. |   VAR_LOCKED = 1,    ///< User lock, can be unlocked. | ||||||
|   VAR_FIXED,         ///< Locked forever. |   VAR_FIXED = 2,     ///< Locked forever. | ||||||
| } VarLockStatus; | } VarLockStatus; | ||||||
|  |  | ||||||
| /// VimL variable types, for use in typval_T.v_type | /// VimL variable types, for use in typval_T.v_type | ||||||
| @@ -93,18 +93,18 @@ struct listwatch_S { | |||||||
|  * Structure to hold info about a list. |  * Structure to hold info about a list. | ||||||
|  */ |  */ | ||||||
| struct listvar_S { | struct listvar_S { | ||||||
|   listitem_T  *lv_first;        /* first item, NULL if none */ |   listitem_T *lv_first;  ///< First item, NULL if none. | ||||||
|   listitem_T  *lv_last;         /* last item, NULL if none */ |   listitem_T *lv_last;  ///< Last item, NULL if none. | ||||||
|   int lv_refcount;              /* reference count */ |   int lv_refcount;  ///< Reference count. | ||||||
|   int lv_len;                   /* number of items */ |   int lv_len;  ///< Number of items. | ||||||
|   listwatch_T *lv_watch;        /* first watcher, NULL if none */ |   listwatch_T *lv_watch;  ///< First watcher, NULL if none. | ||||||
|   int lv_idx;                   /* cached index of an item */ |   int lv_idx;  ///< Index of a cached item, used for optimising repeated l[idx]. | ||||||
|   listitem_T  *lv_idx_item;     /* when not NULL item at index "lv_idx" */ |   listitem_T *lv_idx_item;  ///< When not NULL item at index "lv_idx". | ||||||
|   int lv_copyID;                /* ID used by deepcopy() */ |   int lv_copyID;  ///< ID used by deepcopy(). | ||||||
|   list_T      *lv_copylist;     /* copied list used by deepcopy() */ |   list_T *lv_copylist;  ///< Copied list used by deepcopy(). | ||||||
|   char lv_lock;                 /* zero, VAR_LOCKED, VAR_FIXED */ |   VarLockStatus lv_lock;  ///< Zero, VAR_LOCKED, VAR_FIXED. | ||||||
|   list_T      *lv_used_next;    /* next list in used lists list */ |   list_T *lv_used_next;  ///< next list in used lists list. | ||||||
|   list_T      *lv_used_prev;    /* previous list in used lists list */ |   list_T *lv_used_prev;  ///< Previous list in used lists list. | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // Static list with 10 items. Use init_static_list() to initialize. | // Static list with 10 items. Use init_static_list() to initialize. | ||||||
|   | |||||||
| @@ -6139,7 +6139,7 @@ void ex_substitute(exarg_T *eap) | |||||||
|     // restore it so that undotree() is identical before/after the preview. |     // restore it so that undotree() is identical before/after the preview. | ||||||
|     curbuf->b_u_newhead = save_b_u_newhead; |     curbuf->b_u_newhead = save_b_u_newhead; | ||||||
|     curbuf->b_u_time_cur = save_b_u_time_cur; |     curbuf->b_u_time_cur = save_b_u_time_cur; | ||||||
|     curbuf->b_changedtick = save_changedtick; |     buf_set_changedtick(curbuf, save_changedtick); | ||||||
|   } |   } | ||||||
|   if (buf_valid(preview_buf)) { |   if (buf_valid(preview_buf)) { | ||||||
|     // XXX: Must do this *after* u_undo_and_forget(), why? |     // XXX: Must do this *after* u_undo_and_forget(), why? | ||||||
|   | |||||||
| @@ -1172,10 +1172,6 @@ EXTERN char_u e_re_damg[] INIT(= N_("E43: Damaged match string")); | |||||||
| EXTERN char_u e_re_corr[] INIT(= N_("E44: Corrupted regexp program")); | EXTERN char_u e_re_corr[] INIT(= N_("E44: Corrupted regexp program")); | ||||||
| EXTERN char_u e_readonly[] INIT(= N_( | EXTERN char_u e_readonly[] INIT(= N_( | ||||||
|     "E45: 'readonly' option is set (add ! to override)")); |     "E45: 'readonly' option is set (add ! to override)")); | ||||||
| EXTERN char_u e_readonlyvar[] INIT(= N_( |  | ||||||
|         "E46: Cannot change read-only variable \"%s\"")); |  | ||||||
| EXTERN char_u e_readonlysbx[] INIT(= N_( |  | ||||||
|         "E794: Cannot set variable in the sandbox: \"%s\"")); |  | ||||||
| EXTERN char_u e_readerrf[] INIT(= N_("E47: Error while reading errorfile")); | EXTERN char_u e_readerrf[] INIT(= N_("E47: Error while reading errorfile")); | ||||||
| EXTERN char_u e_sandbox[] INIT(= N_("E48: Not allowed in sandbox")); | EXTERN char_u e_sandbox[] INIT(= N_("E48: Not allowed in sandbox")); | ||||||
| EXTERN char_u e_secure[] INIT(= N_("E523: Not allowed here")); | EXTERN char_u e_secure[] INIT(= N_("E523: Not allowed here")); | ||||||
|   | |||||||
| @@ -576,9 +576,9 @@ void getout(int exitval) | |||||||
|         buf_T *buf = wp->w_buffer; |         buf_T *buf = wp->w_buffer; | ||||||
|         if (buf->b_changedtick != -1) { |         if (buf->b_changedtick != -1) { | ||||||
|           apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, |           apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, | ||||||
|               buf->b_fname, FALSE, buf); |                          buf->b_fname, false, buf); | ||||||
|           buf->b_changedtick = -1;            /* note that we did it already */ |           buf_set_changedtick(buf, -1);  // note that we did it already | ||||||
|           /* start all over, autocommands may mess up the lists */ |           // start all over, autocommands may mess up the lists | ||||||
|           next_tp = first_tabpage; |           next_tp = first_tabpage; | ||||||
|           break; |           break; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1180,7 +1180,7 @@ void ml_recover(void) | |||||||
|      * empty.  Don't set the modified flag then. */ |      * empty.  Don't set the modified flag then. */ | ||||||
|     if (!(curbuf->b_ml.ml_line_count == 2 && *ml_get(1) == NUL)) { |     if (!(curbuf->b_ml.ml_line_count == 2 && *ml_get(1) == NUL)) { | ||||||
|       changed_int(); |       changed_int(); | ||||||
|       ++curbuf->b_changedtick; |       buf_set_changedtick(curbuf, curbuf->b_changedtick + 1); | ||||||
|     } |     } | ||||||
|   } else { |   } else { | ||||||
|     for (idx = 1; idx <= lnum; ++idx) { |     for (idx = 1; idx <= lnum; ++idx) { | ||||||
| @@ -1190,7 +1190,7 @@ void ml_recover(void) | |||||||
|       xfree(p); |       xfree(p); | ||||||
|       if (i != 0) { |       if (i != 0) { | ||||||
|         changed_int(); |         changed_int(); | ||||||
|         ++curbuf->b_changedtick; |         buf_set_changedtick(curbuf, curbuf->b_changedtick + 1); | ||||||
|         break; |         break; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -625,18 +625,6 @@ void free_all_mem(void) | |||||||
|   /* Destroy all windows.  Must come before freeing buffers. */ |   /* Destroy all windows.  Must come before freeing buffers. */ | ||||||
|   win_free_all(); |   win_free_all(); | ||||||
|  |  | ||||||
|   /* Free all buffers.  Reset 'autochdir' to avoid accessing things that |  | ||||||
|    * were freed already. */ |  | ||||||
|   p_acd = false; |  | ||||||
|   for (buf = firstbuf; buf != NULL; ) { |  | ||||||
|     bufref_T bufref; |  | ||||||
|     set_bufref(&bufref, buf); |  | ||||||
|     nextbuf = buf->b_next; |  | ||||||
|     close_buffer(NULL, buf, DOBUF_WIPE, false); |  | ||||||
|     // Didn't work, try next one. |  | ||||||
|     buf = bufref_valid(&bufref) ? nextbuf : firstbuf; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   free_cmdline_buf(); |   free_cmdline_buf(); | ||||||
|  |  | ||||||
|   /* Clear registers. */ |   /* Clear registers. */ | ||||||
| @@ -660,6 +648,20 @@ void free_all_mem(void) | |||||||
|  |  | ||||||
|   eval_clear(); |   eval_clear(); | ||||||
|  |  | ||||||
|  |   // Free all buffers.  Reset 'autochdir' to avoid accessing things that | ||||||
|  |   // were freed already. | ||||||
|  |   // Must be after eval_clear to avoid it trying to access b:changedtick after | ||||||
|  |   // freeing it. | ||||||
|  |   p_acd = false; | ||||||
|  |   for (buf = firstbuf; buf != NULL; ) { | ||||||
|  |     bufref_T bufref; | ||||||
|  |     set_bufref(&bufref, buf); | ||||||
|  |     nextbuf = buf->b_next; | ||||||
|  |     close_buffer(NULL, buf, DOBUF_WIPE, false); | ||||||
|  |     // Didn't work, try next one. | ||||||
|  |     buf = bufref_valid(&bufref) ? nextbuf : firstbuf; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /* screenlines (can't display anything now!) */ |   /* screenlines (can't display anything now!) */ | ||||||
|   free_screenlines(); |   free_screenlines(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -51,6 +51,7 @@ | |||||||
| #include "nvim/os/input.h" | #include "nvim/os/input.h" | ||||||
| #include "nvim/os/time.h" | #include "nvim/os/time.h" | ||||||
| #include "nvim/event/stream.h" | #include "nvim/event/stream.h" | ||||||
|  | #include "nvim/buffer.h" | ||||||
|  |  | ||||||
| #ifdef INCLUDE_GENERATED_DECLARATIONS | #ifdef INCLUDE_GENERATED_DECLARATIONS | ||||||
| # include "misc1.c.generated.h" | # include "misc1.c.generated.h" | ||||||
| @@ -1788,7 +1789,7 @@ void changed(void) | |||||||
|     } |     } | ||||||
|     changed_int(); |     changed_int(); | ||||||
|   } |   } | ||||||
|   ++curbuf->b_changedtick; |   buf_set_changedtick(curbuf, curbuf->b_changedtick + 1); | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -2147,7 +2148,7 @@ unchanged ( | |||||||
|     redraw_tabline = TRUE; |     redraw_tabline = TRUE; | ||||||
|     need_maketitle = TRUE;          /* set window title later */ |     need_maketitle = TRUE;          /* set window title later */ | ||||||
|   } |   } | ||||||
|   ++buf->b_changedtick; |   buf_set_changedtick(buf, buf->b_changedtick + 1); | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|   | |||||||
| @@ -611,8 +611,8 @@ static void buf_set_term_title(buf_T *buf, char *title) | |||||||
|     FUNC_ATTR_NONNULL_ALL |     FUNC_ATTR_NONNULL_ALL | ||||||
| { | { | ||||||
|   Error err; |   Error err; | ||||||
|   dict_set_value(buf->b_vars, |   dict_set_var(buf->b_vars, | ||||||
|                  cstr_as_string("term_title"), |                STATIC_CSTR_AS_STRING("term_title"), | ||||||
|                STRING_OBJ(cstr_as_string(title)), |                STRING_OBJ(cstr_as_string(title)), | ||||||
|                false, |                false, | ||||||
|                false, |                false, | ||||||
|   | |||||||
| @@ -2,8 +2,11 @@ local helpers = require('test.functional.helpers')(after_each) | |||||||
| local clear, nvim, buffer = helpers.clear, helpers.nvim, helpers.buffer | local clear, nvim, buffer = helpers.clear, helpers.nvim, helpers.buffer | ||||||
| local curbuf, curwin, eq = helpers.curbuf, helpers.curwin, helpers.eq | local curbuf, curwin, eq = helpers.curbuf, helpers.curwin, helpers.eq | ||||||
| local curbufmeths, ok = helpers.curbufmeths, helpers.ok | local curbufmeths, ok = helpers.curbufmeths, helpers.ok | ||||||
| local funcs, request = helpers.funcs, helpers.request | local funcs = helpers.funcs | ||||||
|  | local request = helpers.request | ||||||
| local NIL = helpers.NIL | local NIL = helpers.NIL | ||||||
|  | local meth_pcall = helpers.meth_pcall | ||||||
|  | local command = helpers.command | ||||||
|  |  | ||||||
| describe('api/buf', function() | describe('api/buf', function() | ||||||
|   before_each(clear) |   before_each(clear) | ||||||
| @@ -249,6 +252,24 @@ describe('api/buf', function() | |||||||
|       eq(1, funcs.exists('b:lua')) |       eq(1, funcs.exists('b:lua')) | ||||||
|       curbufmeths.del_var('lua') |       curbufmeths.del_var('lua') | ||||||
|       eq(0, funcs.exists('b:lua')) |       eq(0, funcs.exists('b:lua')) | ||||||
|  |       eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(curbufmeths.del_var, 'lua')) | ||||||
|  |       curbufmeths.set_var('lua', 1) | ||||||
|  |       command('lockvar b:lua') | ||||||
|  |       eq({false, 'Key is locked: lua'}, meth_pcall(curbufmeths.del_var, 'lua')) | ||||||
|  |       eq({false, 'Key is locked: lua'}, meth_pcall(curbufmeths.set_var, 'lua', 1)) | ||||||
|  |       eq({false, 'Key is read-only: changedtick'}, | ||||||
|  |          meth_pcall(curbufmeths.del_var, 'changedtick')) | ||||||
|  |       eq({false, 'Key is read-only: changedtick'}, | ||||||
|  |          meth_pcall(curbufmeths.set_var, 'changedtick', 1)) | ||||||
|  |     end) | ||||||
|  |   end) | ||||||
|  |  | ||||||
|  |   describe('get_changedtick', function() | ||||||
|  |     it('works', function() | ||||||
|  |       eq(2, curbufmeths.get_changedtick()) | ||||||
|  |       curbufmeths.set_lines(0, 1, false, {'abc\0', '\0def', 'ghi'}) | ||||||
|  |       eq(3, curbufmeths.get_changedtick()) | ||||||
|  |       eq(3, curbufmeths.get_var('changedtick')) | ||||||
|     end) |     end) | ||||||
|  |  | ||||||
|     it('buffer_set_var returns the old value', function() |     it('buffer_set_var returns the old value', function() | ||||||
|   | |||||||
| @@ -6,6 +6,8 @@ local curtabmeths = helpers.curtabmeths | |||||||
| local funcs = helpers.funcs | local funcs = helpers.funcs | ||||||
| local request = helpers.request | local request = helpers.request | ||||||
| local NIL = helpers.NIL | local NIL = helpers.NIL | ||||||
|  | local meth_pcall = helpers.meth_pcall | ||||||
|  | local command = helpers.command | ||||||
|  |  | ||||||
| describe('api/tabpage', function() | describe('api/tabpage', function() | ||||||
|   before_each(clear) |   before_each(clear) | ||||||
| @@ -32,6 +34,11 @@ describe('api/tabpage', function() | |||||||
|       eq(1, funcs.exists('t:lua')) |       eq(1, funcs.exists('t:lua')) | ||||||
|       curtabmeths.del_var('lua') |       curtabmeths.del_var('lua') | ||||||
|       eq(0, funcs.exists('t:lua')) |       eq(0, funcs.exists('t:lua')) | ||||||
|  |       eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(curtabmeths.del_var, 'lua')) | ||||||
|  |       curtabmeths.set_var('lua', 1) | ||||||
|  |       command('lockvar t:lua') | ||||||
|  |       eq({false, 'Key is locked: lua'}, meth_pcall(curtabmeths.del_var, 'lua')) | ||||||
|  |       eq({false, 'Key is locked: lua'}, meth_pcall(curtabmeths.set_var, 'lua', 1)) | ||||||
|     end) |     end) | ||||||
|  |  | ||||||
|     it('tabpage_set_var returns the old value', function() |     it('tabpage_set_var returns the old value', function() | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ local os_name = helpers.os_name | |||||||
| local meths = helpers.meths | local meths = helpers.meths | ||||||
| local funcs = helpers.funcs | local funcs = helpers.funcs | ||||||
| local request = helpers.request | local request = helpers.request | ||||||
|  | local meth_pcall = helpers.meth_pcall | ||||||
|  | local command = helpers.command | ||||||
|  |  | ||||||
| describe('api', function() | describe('api', function() | ||||||
|   before_each(clear) |   before_each(clear) | ||||||
| @@ -43,7 +45,7 @@ describe('api', function() | |||||||
|     it('works', function() |     it('works', function() | ||||||
|       nvim('command', 'let g:v1 = "a"') |       nvim('command', 'let g:v1 = "a"') | ||||||
|       nvim('command', 'let g:v2 = [1, 2, {"v3": 3}]') |       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) |     end) | ||||||
|  |  | ||||||
|     it('handles NULL-initialized strings correctly', function() |     it('handles NULL-initialized strings correctly', function() | ||||||
| @@ -65,7 +67,7 @@ describe('api', function() | |||||||
|  |  | ||||||
|   describe('nvim_call_function', function() |   describe('nvim_call_function', function() | ||||||
|     it('works', 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', 'getqflist', {})[1].lnum) | ||||||
|       eq(17, nvim('call_function', 'eval', {17})) |       eq(17, nvim('call_function', 'eval', {17})) | ||||||
|       eq('foo', nvim('call_function', 'simplify', {'this/./is//redundant/../../../foo'})) |       eq('foo', nvim('call_function', 'simplify', {'this/./is//redundant/../../../foo'})) | ||||||
| @@ -117,6 +119,11 @@ describe('api', function() | |||||||
|       eq(1, funcs.exists('g:lua')) |       eq(1, funcs.exists('g:lua')) | ||||||
|       meths.del_var('lua') |       meths.del_var('lua') | ||||||
|       eq(0, funcs.exists('g:lua')) |       eq(0, funcs.exists('g:lua')) | ||||||
|  |       eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(meths.del_var, 'lua')) | ||||||
|  |       meths.set_var('lua', 1) | ||||||
|  |       command('lockvar lua') | ||||||
|  |       eq({false, 'Key is locked: lua'}, meth_pcall(meths.del_var, 'lua')) | ||||||
|  |       eq({false, 'Key is locked: lua'}, meth_pcall(meths.set_var, 'lua', 1)) | ||||||
|     end) |     end) | ||||||
|  |  | ||||||
|     it('vim_set_var returns the old value', function() |     it('vim_set_var returns the old value', function() | ||||||
| @@ -396,7 +403,7 @@ describe('api', function() | |||||||
|       eq(1, meths.get_var('avar')) |       eq(1, meths.get_var('avar')) | ||||||
|  |  | ||||||
|       req = { |       req = { | ||||||
|         {'nvim_set_var', {'bvar', {2,3}}}, |         { 'nvim_set_var', { 'bvar', { 2, 3 } } }, | ||||||
|         12, |         12, | ||||||
|       } |       } | ||||||
|       status, err = pcall(meths.call_atomic, req) |       status, err = pcall(meths.call_atomic, req) | ||||||
|   | |||||||
| @@ -8,6 +8,8 @@ local curwinmeths = helpers.curwinmeths | |||||||
| local funcs = helpers.funcs | local funcs = helpers.funcs | ||||||
| local request = helpers.request | local request = helpers.request | ||||||
| local NIL = helpers.NIL | local NIL = helpers.NIL | ||||||
|  | local meth_pcall = helpers.meth_pcall | ||||||
|  | local command = helpers.command | ||||||
|  |  | ||||||
| -- check if str is visible at the beginning of some line | -- check if str is visible at the beginning of some line | ||||||
| local function is_visible(str) | local function is_visible(str) | ||||||
| @@ -137,6 +139,11 @@ describe('api/win', function() | |||||||
|       eq(1, funcs.exists('w:lua')) |       eq(1, funcs.exists('w:lua')) | ||||||
|       curwinmeths.del_var('lua') |       curwinmeths.del_var('lua') | ||||||
|       eq(0, funcs.exists('w:lua')) |       eq(0, funcs.exists('w:lua')) | ||||||
|  |       eq({false, 'Key "lua" doesn\'t exist'}, meth_pcall(curwinmeths.del_var, 'lua')) | ||||||
|  |       curwinmeths.set_var('lua', 1) | ||||||
|  |       command('lockvar w:lua') | ||||||
|  |       eq({false, 'Key is locked: lua'}, meth_pcall(curwinmeths.del_var, 'lua')) | ||||||
|  |       eq({false, 'Key is locked: lua'}, meth_pcall(curwinmeths.set_var, 'lua', 1)) | ||||||
|     end) |     end) | ||||||
|  |  | ||||||
|     it('window_set_var returns the old value', function() |     it('window_set_var returns the old value', function() | ||||||
|   | |||||||
							
								
								
									
										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 |   return false | ||||||
| end | end | ||||||
|  |  | ||||||
|  | local function meth_pcall(...) | ||||||
|  |   local ret = {pcall(...)} | ||||||
|  |   if type(ret[2]) == 'string' then | ||||||
|  |     ret[2] = ret[2]:gsub('^[^:]+:%d+: ', '') | ||||||
|  |   end | ||||||
|  |   return ret | ||||||
|  | end | ||||||
|  |  | ||||||
| local funcs = create_callindex(nvim_call) | local funcs = create_callindex(nvim_call) | ||||||
| local meths = create_callindex(nvim) | local meths = create_callindex(nvim) | ||||||
| local uimeths = create_callindex(ui) | local uimeths = create_callindex(ui) | ||||||
| @@ -615,6 +623,7 @@ local M = { | |||||||
|   skip_fragile = skip_fragile, |   skip_fragile = skip_fragile, | ||||||
|   set_shell_powershell = set_shell_powershell, |   set_shell_powershell = set_shell_powershell, | ||||||
|   tmpname = tmpname, |   tmpname = tmpname, | ||||||
|  |   meth_pcall = meth_pcall, | ||||||
|   NIL = mpack.NIL, |   NIL = mpack.NIL, | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,6 +13,14 @@ describe('context variables', function() | |||||||
|     -- Test for getbufvar(). |     -- Test for getbufvar(). | ||||||
|     -- Use strings to test for memory leaks. |     -- Use strings to test for memory leaks. | ||||||
|     source([[ |     source([[ | ||||||
|  |       function Getbufscope(buf, ...) | ||||||
|  |         let ret = call('getbufvar', [a:buf, ''] + a:000) | ||||||
|  |         if type(ret) == type({}) | ||||||
|  |           return filter(copy(ret), 'v:key isnot# "changedtick"') | ||||||
|  |         else | ||||||
|  |           return ret | ||||||
|  |         endif | ||||||
|  |       endfunction | ||||||
|       let t:testvar='abcd' |       let t:testvar='abcd' | ||||||
|       $put =string(gettabvar(1, 'testvar')) |       $put =string(gettabvar(1, 'testvar')) | ||||||
|       $put =string(gettabvar(1, 'testvar')) |       $put =string(gettabvar(1, 'testvar')) | ||||||
| @@ -20,14 +28,14 @@ describe('context variables', function() | |||||||
|       let def_num = '5678' |       let def_num = '5678' | ||||||
|       $put =string(getbufvar(1, 'var_num')) |       $put =string(getbufvar(1, 'var_num')) | ||||||
|       $put =string(getbufvar(1, 'var_num', def_num)) |       $put =string(getbufvar(1, 'var_num', def_num)) | ||||||
|       $put =string(getbufvar(1, '')) |       $put =string(Getbufscope(1)) | ||||||
|       $put =string(getbufvar(1, '', def_num)) |       $put =string(Getbufscope(1, def_num)) | ||||||
|       unlet b:var_num |       unlet b:var_num | ||||||
|       $put =string(getbufvar(1, 'var_num', def_num)) |       $put =string(getbufvar(1, 'var_num', def_num)) | ||||||
|       $put =string(getbufvar(1, '')) |       $put =string(Getbufscope(1)) | ||||||
|       $put =string(getbufvar(1, '', def_num)) |       $put =string(Getbufscope(1, def_num)) | ||||||
|       $put =string(getbufvar(9, '')) |       $put =string(Getbufscope(9)) | ||||||
|       $put =string(getbufvar(9, '', def_num)) |       $put =string(Getbufscope(9, def_num)) | ||||||
|       unlet def_num |       unlet def_num | ||||||
|       $put =string(getbufvar(1, '&autoindent')) |       $put =string(getbufvar(1, '&autoindent')) | ||||||
|       $put =string(getbufvar(1, '&autoindent', 1)) |       $put =string(getbufvar(1, '&autoindent', 1)) | ||||||
|   | |||||||
| @@ -79,6 +79,13 @@ local function cimport(...) | |||||||
|  |  | ||||||
|   -- format it (so that the lines are "unique" statements), also filter out |   -- format it (so that the lines are "unique" statements), also filter out | ||||||
|   -- Objective-C blocks |   -- Objective-C blocks | ||||||
|  |   if os.getenv('NVIM_TEST_PRINT_I') == '1' then | ||||||
|  |     local lnum = 0 | ||||||
|  |     for line in body:gmatch('[^\n]+') do | ||||||
|  |       lnum = lnum + 1 | ||||||
|  |       print(lnum, line) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|   body = formatc(body) |   body = formatc(body) | ||||||
|   body = filter_complex_blocks(body) |   body = filter_complex_blocks(body) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -124,6 +124,7 @@ function Gcc:init_defines() | |||||||
|   self:define('_GNU_SOURCE') |   self:define('_GNU_SOURCE') | ||||||
|   self:define('INCLUDE_GENERATED_DECLARATIONS') |   self:define('INCLUDE_GENERATED_DECLARATIONS') | ||||||
|   self:define('UNIT_TESTING') |   self:define('UNIT_TESTING') | ||||||
|  |   self:define('UNIT_TESTING_LUA_PREPROCESSING') | ||||||
|   -- Needed for FreeBSD |   -- Needed for FreeBSD | ||||||
|   self:define('_Thread_local', nil, '') |   self:define('_Thread_local', nil, '') | ||||||
|   -- Needed for macOS Sierra |   -- Needed for macOS Sierra | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Justin M. Keyes
					Justin M. Keyes