mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	refactor(options)!: unify set_option and set_string_option
				
					
				
			While the interfaces for setting number and boolean options are now unified by #25394, there is still a separate `set_string_option` function that is used for setting a string option. This PR removes that function and merges it with set_option. BREAKING CHANGE: `v:option_old` is now the old global value for all global-local options, instead of just string global-local options. Local value for a global-local number/boolean option is now unset when the option is set (e.g. using `:set` or `nvim_set_option_value`) without a scope, which means they now behave the same way as string options. Ref: #25672
This commit is contained in:
		| @@ -778,11 +778,10 @@ OptionSet			After setting an option (except during | ||||
| 				This does not set |<abuf>|, you could use | ||||
| 				|bufnr()|. | ||||
|  | ||||
| 				Note that when setting a |global-local| string | ||||
| 				option with |:set|, then |v:option_old| is the | ||||
| 				old global value. However, for all other kinds | ||||
| 				of options (local string options, global-local | ||||
| 				number options, ...) it is the old local | ||||
| 				Note that when setting a |global-local| option | ||||
| 				with |:set|, then |v:option_old| is the old | ||||
| 				global value. However, for all options that | ||||
| 				are not global-local it is the old local | ||||
| 				value. | ||||
|  | ||||
| 				OptionSet is not triggered on startup and for | ||||
|   | ||||
| @@ -73,7 +73,12 @@ The following changes may require adaptations in user config or plugins. | ||||
|  | ||||
| • |OptionSet| autocommand args |v:option_new|, |v:option_old|, | ||||
|   |v:option_oldlocal|, |v:option_oldglobal| now have the type of the option | ||||
|   instead of always being strings. | ||||
|   instead of always being strings. |v:option_old| is now the old global value | ||||
|   for all global-local options, instead of just string global-local options. | ||||
|  | ||||
| • Local value for a global-local number/boolean option is now unset when | ||||
|   the option is set (e.g. using |:set| or |nvim_set_option_value()|) without a | ||||
|   scope, which means they now behave the same way as string options. | ||||
|  | ||||
| ============================================================================== | ||||
| NEW FEATURES                                                    *news-features* | ||||
|   | ||||
| @@ -289,6 +289,10 @@ Normal commands: | ||||
|   |Q| replays the last recorded macro instead of switching to Ex mode (|gQ|). | ||||
|  | ||||
| Options: | ||||
|   Local values for global-local number/boolean options are unset when the | ||||
|   option is set without a scope (e.g. by using |:set|), similarly to how | ||||
|   global-local string options work. | ||||
|  | ||||
|   'autoread'    works in the terminal (if it supports "focus" events) | ||||
|   'cpoptions'   flags: |cpo-_| | ||||
|   'diffopt'     "linematch" feature | ||||
| @@ -381,7 +385,8 @@ Variables: | ||||
|   |v:windowid| is always available (for use by external UIs) | ||||
|   |OptionSet| autocommand args |v:option_new|, |v:option_old|, | ||||
|   |v:option_oldlocal|, |v:option_oldglobal| have the type of the option | ||||
|   instead of always being strings. | ||||
|   instead of always being strings. |v:option_old| is now the old global value | ||||
|   for all global-local options, instead of just string global-local options. | ||||
|  | ||||
| Vimscript: | ||||
|   |:redir| nested in |execute()| works. | ||||
|   | ||||
| @@ -681,21 +681,6 @@ static void set_option_to(uint64_t channel_id, void *to, OptReqScope req_scope, | ||||
|     return; | ||||
|   }); | ||||
|  | ||||
|   if (value.type == kObjectTypeNil) { | ||||
|     if (req_scope == kOptReqGlobal) { | ||||
|       api_set_error(err, kErrorTypeException, "Cannot unset option '%s'", name.data); | ||||
|       return; | ||||
|     } else if (!(flags & SOPT_GLOBAL)) { | ||||
|       api_set_error(err, kErrorTypeException, | ||||
|                     "Cannot unset option '%s' because it doesn't have a global value", | ||||
|                     name.data); | ||||
|       return; | ||||
|     } else { | ||||
|       unset_global_local_option(name.data, to); | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   bool error = false; | ||||
|   OptVal optval = object_as_optval(value, &error); | ||||
|  | ||||
|   | ||||
| @@ -7229,12 +7229,11 @@ void set_vim_var_dict(const VimVarIndex idx, dict_T *const val) | ||||
| /// Set v:variable to tv. | ||||
| /// | ||||
| /// @param[in]  idx  Index of variable to set. | ||||
| /// @param[in,out]  val  Value to set to. Reference count will be incremented. | ||||
| ///                      Also keys of the dictionary will be made read-only. | ||||
| /// @param[in]  val  Value to set to. Will be copied. | ||||
| void set_vim_var_tv(const VimVarIndex idx, typval_T *const tv) | ||||
| { | ||||
|   tv_clear(&vimvars[idx].vv_di.di_tv); | ||||
|   vimvars[idx].vv_di.di_tv = *tv; | ||||
|   tv_copy(tv, &vimvars[idx].vv_di.di_tv); | ||||
| } | ||||
|  | ||||
| /// Set the v:argv list. | ||||
|   | ||||
| @@ -574,14 +574,14 @@ void free_all_options(void) | ||||
|     if (options[i].indir == PV_NONE) { | ||||
|       // global option: free value and default value. | ||||
|       if ((options[i].flags & P_ALLOCED) && options[i].var != NULL) { | ||||
|         free_string_option(*(char **)options[i].var); | ||||
|         optval_free(optval_from_varp(i, options[i].var)); | ||||
|       } | ||||
|       if (options[i].flags & P_DEF_ALLOCED) { | ||||
|         free_string_option(options[i].def_val); | ||||
|         optval_free(optval_from_varp(i, &options[i].def_val)); | ||||
|       } | ||||
|     } else if (options[i].var != VAR_WIN && (options[i].flags & P_STRING)) { | ||||
|     } else if (options[i].var != VAR_WIN) { | ||||
|       // buffer-local option: free global value | ||||
|       clear_string_option((char **)options[i].var); | ||||
|       optval_free(optval_from_varp(i, options[i].var)); | ||||
|     } | ||||
|   } | ||||
|   free_operatorfunc_option(); | ||||
| @@ -1142,8 +1142,11 @@ static OptVal get_option_newval(int opt_idx, int opt_flags, set_prefix_T prefix, | ||||
| { | ||||
|   assert(varp != NULL); | ||||
|  | ||||
|   vimoption_T *opt = &options[opt_idx]; | ||||
|   char *arg = *argp; | ||||
|   OptVal oldval = optval_from_varp(opt_idx, varp); | ||||
|   // When setting the local value of a global option, the old value may be the global value. | ||||
|   const bool oldval_is_global = ((int)opt->indir & PV_BOTH) && (opt_flags & OPT_LOCAL); | ||||
|   OptVal oldval = optval_from_varp(opt_idx, oldval_is_global ? get_varp(opt) : varp); | ||||
|   OptVal newval = NIL_OPTVAL; | ||||
|  | ||||
|   switch (oldval.type) { | ||||
| @@ -1250,17 +1253,7 @@ static OptVal get_option_newval(int opt_idx, int opt_flags, set_prefix_T prefix, | ||||
|     break; | ||||
|   } | ||||
|   case kOptValTypeString: { | ||||
|     char *oldval_str; | ||||
|     vimoption_T *opt = get_option(opt_idx); | ||||
|  | ||||
|     // When setting the local value of a global option, the old value may be | ||||
|     // the global value. | ||||
|     if (((int)opt->indir & PV_BOTH) && (opt_flags & OPT_LOCAL)) { | ||||
|       oldval_str = *(char **)get_varp(opt); | ||||
|     } else { | ||||
|       oldval_str = *(char **)varp; | ||||
|     } | ||||
|  | ||||
|     char *oldval_str = oldval.data.string.data; | ||||
|     // Get the new value for the option | ||||
|     char *newval_str = stropt_get_newval(nextchar, opt_idx, argp, varp, oldval_str, &op, flags); | ||||
|     newval = CSTR_AS_OPTVAL(newval_str); | ||||
| @@ -1416,8 +1409,6 @@ static void do_one_set_option(int opt_flags, char **argp, bool *did_show, char * | ||||
|   } | ||||
|  | ||||
|   *errmsg = set_option(opt_idx, varp, newval, opt_flags, op == OP_NONE, errbuf, errbuflen); | ||||
|   // `set_option` copies the new option value, so it needs to be freed here. | ||||
|   optval_free(newval); | ||||
| } | ||||
|  | ||||
| /// Parse 'arg' for option settings. | ||||
| @@ -2753,6 +2744,47 @@ static const char *did_set_wrap(optset_T *args) | ||||
|  | ||||
|   return NULL; | ||||
| } | ||||
|  | ||||
| // When 'syntax' is set, load the syntax of that name | ||||
| static void do_syntax_autocmd(buf_T *buf, bool value_changed) | ||||
| { | ||||
|   static int syn_recursive = 0; | ||||
|  | ||||
|   syn_recursive++; | ||||
|   // Only pass true for "force" when the value changed or not used | ||||
|   // recursively, to avoid endless recurrence. | ||||
|   apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname, | ||||
|                  value_changed || syn_recursive == 1, buf); | ||||
|   buf->b_flags |= BF_SYN_SET; | ||||
|   syn_recursive--; | ||||
| } | ||||
|  | ||||
| static void do_spelllang_source(win_T *win) | ||||
| { | ||||
|   char fname[200]; | ||||
|   char *q = win->w_s->b_p_spl; | ||||
|  | ||||
|   // Skip the first name if it is "cjk". | ||||
|   if (strncmp(q, "cjk,", 4) == 0) { | ||||
|     q += 4; | ||||
|   } | ||||
|  | ||||
|   // Source the spell/LANG.{vim,lua} in 'runtimepath'. | ||||
|   // They could set 'spellcapcheck' depending on the language. | ||||
|   // Use the first name in 'spelllang' up to '_region' or | ||||
|   // '.encoding'. | ||||
|   char *p; | ||||
|   for (p = q; *p != NUL; p++) { | ||||
|     if (!ASCII_ISALNUM(*p) && *p != '-') { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   if (p > q) { | ||||
|     vim_snprintf(fname, sizeof(fname), "spell/%.*s.*", (int)(p - q), q); | ||||
|     source_runtime_vim_lua(fname, DIP_ALL); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// Check the bounds of numeric options. | ||||
| static const char *check_num_option_bounds(OptInt *pp, OptInt old_value, char *errbuf, | ||||
|                                            size_t errbuflen, const char *errmsg) | ||||
| @@ -3193,7 +3225,10 @@ void optval_free(OptVal o) | ||||
|   case kOptValTypeNumber: | ||||
|     break; | ||||
|   case kOptValTypeString: | ||||
|     api_free_string(o.data.string); | ||||
|     // Don't free empty string option | ||||
|     if (o.data.string.data != empty_string_option) { | ||||
|       api_free_string(o.data.string); | ||||
|     } | ||||
|     break; | ||||
|   } | ||||
| } | ||||
| @@ -3271,11 +3306,19 @@ OptVal optval_from_varp(int opt_idx, void *varp) | ||||
|  | ||||
| /// Set option var pointer value from Optval. | ||||
| /// | ||||
| /// @param  varp   Pointer to option variable. | ||||
| /// @param  value  Option value. | ||||
| static void set_option_varp(void *varp, OptVal value) | ||||
|   FUNC_ATTR_NONNULL_ARG(1) | ||||
| /// @param       opt_idx      Option index in options[] table. | ||||
| /// @param[out]  varp         Pointer to option variable. | ||||
| /// @param[in]   value        New option value. | ||||
| /// @param       free_oldval  Free old value. | ||||
| static void set_option_varp(int opt_idx, void *varp, OptVal value, bool free_oldval) | ||||
|   FUNC_ATTR_NONNULL_ARG(2) | ||||
| { | ||||
|   assert(optval_match_type(value, opt_idx)); | ||||
|  | ||||
|   if (free_oldval) { | ||||
|     optval_free(optval_from_varp(opt_idx, varp)); | ||||
|   } | ||||
|  | ||||
|   switch (value.type) { | ||||
|   case kOptValTypeNil: | ||||
|     return; | ||||
| @@ -3356,43 +3399,36 @@ OptVal object_as_optval(Object o, bool *error) | ||||
|   UNREACHABLE; | ||||
| } | ||||
|  | ||||
| /// Clear an option value. | ||||
| /// Unset the local value of an option. The exact semantics of this depend on the option. | ||||
| /// TODO(famiu): Remove this once we have a dedicated OptVal type for unset local options. | ||||
| /// | ||||
| /// The exact semantics of this depend on the option. | ||||
| static OptVal optval_clear(const char *name, uint32_t flags, void *varp, buf_T *buf, win_T *win) | ||||
| /// @param      opt_idx  Option index in options[] table. | ||||
| /// @param[in]  varp  Pointer to option variable. | ||||
| /// | ||||
| /// @return [allocated] Option value equal to the unset value for the option. | ||||
| static OptVal optval_unset_local(int opt_idx, void *varp) | ||||
| { | ||||
|   OptVal v = NIL_OPTVAL; | ||||
|  | ||||
|   // Change the type of the OptVal to the type used by the option so that it can be cleared. | ||||
|   // TODO(famiu): Clean up all of this after set_(num|bool|string)_option() is unified. | ||||
|  | ||||
|   if (flags & P_BOOL) { | ||||
|     v.type = kOptValTypeBoolean; | ||||
|     if ((int *)varp == &buf->b_p_ar) { | ||||
|       // TODO(lewis6991): replace this with a more general condition that | ||||
|       // indicates we are setting the local value of a global-local option | ||||
|       v.data.boolean = kNone; | ||||
|     } else { | ||||
|       v = get_option_value(name, NULL, OPT_GLOBAL, NULL); | ||||
|   vimoption_T *opt = &options[opt_idx]; | ||||
|   // For global-local options, use the unset value of the local value. | ||||
|   if (opt->indir & PV_BOTH) { | ||||
|     // String global-local options always use an empty string for the unset value. | ||||
|     if (opt->flags & P_STRING) { | ||||
|       return STATIC_CSTR_TO_OPTVAL(""); | ||||
|     } | ||||
|   } else if (flags & P_NUM) { | ||||
|     v.type = kOptValTypeNumber; | ||||
|     if ((OptInt *)varp == &curbuf->b_p_ul) { | ||||
|       // The one true special case | ||||
|       v.data.number = NO_LOCAL_UNDOLEVEL; | ||||
|     } else if ((OptInt *)varp == &win->w_p_so || (OptInt *)varp == &win->w_p_siso) { | ||||
|       // TODO(lewis6991): replace this with a more general condition that | ||||
|       // indicates we are setting the local value of a global-local option | ||||
|       v.data.number = -1; | ||||
|  | ||||
|     if ((int *)varp == &curbuf->b_p_ar) { | ||||
|       return BOOLEAN_OPTVAL(kNone); | ||||
|     } else if ((OptInt *)varp == &curwin->w_p_so || (OptInt *)varp == &curwin->w_p_siso) { | ||||
|       return NUMBER_OPTVAL(-1); | ||||
|     } else if ((OptInt *)varp == &curbuf->b_p_ul) { | ||||
|       return NUMBER_OPTVAL(NO_LOCAL_UNDOLEVEL); | ||||
|     } else { | ||||
|       v = get_option_value(name, NULL, OPT_GLOBAL, NULL); | ||||
|       // This should never happen. | ||||
|       abort(); | ||||
|     } | ||||
|   } else if (flags & P_STRING) { | ||||
|     v.type = kOptValTypeString; | ||||
|     v.data.string.data = NULL; | ||||
|   } | ||||
|  | ||||
|   return v; | ||||
|   // For options that aren't global-local, just set the local value to the global value. | ||||
|   return get_option_value(opt->fullname, NULL, OPT_GLOBAL, NULL); | ||||
| } | ||||
|  | ||||
| /// Get an allocated string containing a list of valid types for an option. | ||||
| @@ -3485,36 +3521,254 @@ vimoption_T *get_option(int opt_idx) | ||||
|   return &options[opt_idx]; | ||||
| } | ||||
|  | ||||
| /// Check if local value of global-local option is unset for current buffer / window. | ||||
| /// Always returns false for options that aren't global-local. | ||||
| /// | ||||
| /// TODO(famiu): Remove this once we have an OptVal type to indicate an unset local value. | ||||
| static bool is_option_local_value_unset(vimoption_T *opt, buf_T *buf, win_T *win) | ||||
| { | ||||
|   // Local value of option that isn't global-local is always considered set. | ||||
|   if (!((int)opt->indir & PV_BOTH)) { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // Get pointer to local value in varp_local, and a pointer to the currently used value in varp. | ||||
|   // If the local value is the one currently being used, that indicates that it's set. | ||||
|   // Otherwise it indicates the local value is unset. | ||||
|   void *varp = get_varp_from(opt, buf, win); | ||||
|   void *varp_local = get_varp_scope_from(opt, OPT_LOCAL, buf, win); | ||||
|  | ||||
|   return varp != varp_local; | ||||
| } | ||||
|  | ||||
| /// Handle side-effects of setting an option. | ||||
| /// | ||||
| /// @param       opt_idx         Index in options[] table. Must be >= 0. | ||||
| /// @param[in]   varp            Option variable pointer, cannot be NULL. | ||||
| /// @param       old_value       Old option value. | ||||
| /// @param       new_value       New option value. | ||||
| /// @param       opt_flags       Option flags. | ||||
| /// @param[out]  doskip          Whether option should be processed further. | ||||
| /// @param[out]  value_checked   Value was checked to be safe, no need to set P_INSECURE. | ||||
| /// @param       value_replaced  Value was replaced completely. | ||||
| /// @param[out]  errbuf          Buffer for error message. | ||||
| /// @param       errbuflen       Length of error buffer. | ||||
| /// | ||||
| /// @return  NULL on success, an untranslated error message on error. | ||||
| static const char *did_set_option(int opt_idx, void *varp, OptVal old_value, OptVal new_value, | ||||
|                                   int opt_flags, bool *doskip, bool *value_checked, | ||||
|                                   bool value_replaced, char *errbuf, size_t errbuflen) | ||||
| { | ||||
|   vimoption_T *opt = &options[opt_idx]; | ||||
|   const char *errmsg = NULL; | ||||
|   bool restore_chartab = false; | ||||
|   bool free_oldval = (opt->flags & P_ALLOCED); | ||||
|   bool value_changed = false; | ||||
|  | ||||
|   opt_did_set_cb_T did_set_cb; | ||||
|   optset_T did_set_cb_args = { | ||||
|     .os_varp = varp, | ||||
|     .os_idx = opt_idx, | ||||
|     .os_flags = opt_flags, | ||||
|     .os_oldval = old_value.data, | ||||
|     .os_newval = new_value.data, | ||||
|     .os_value_checked = false, | ||||
|     .os_value_changed = false, | ||||
|     .os_restore_chartab = false, | ||||
|     .os_doskip = false, | ||||
|     .os_errbuf = errbuf, | ||||
|     .os_errbuflen = errbuflen, | ||||
|     .os_buf = curbuf, | ||||
|     .os_win = curwin | ||||
|   }; | ||||
|  | ||||
|   if ((int *)varp == &p_force_on) { | ||||
|     did_set_cb = did_set_force_on; | ||||
|   } else if ((int *)varp == &p_force_off) { | ||||
|     did_set_cb = did_set_force_off; | ||||
|   } else { | ||||
|     did_set_cb = opt->opt_did_set_cb; | ||||
|   } | ||||
|  | ||||
|   // Disallow changing some options from secure mode | ||||
|   if ((secure || sandbox != 0) && (opt->flags & P_SECURE)) { | ||||
|     errmsg = e_secure; | ||||
|     // Check for a "normal" directory or file name in some string options. | ||||
|   } else if (new_value.type == kOptValTypeString | ||||
|              && check_illegal_path_names(*(char **)varp, opt->flags)) { | ||||
|     errmsg = e_invarg; | ||||
|   } else if (did_set_cb != NULL) { | ||||
|     // Invoke the option specific callback function to validate and apply the new value. | ||||
|     errmsg = did_set_cb(&did_set_cb_args); | ||||
|     // Whether option should be processed further or skipped. | ||||
|     *doskip = did_set_cb_args.os_doskip; | ||||
|     // The 'filetype' and 'syntax' option callback functions may change the os_value_changed field. | ||||
|     value_changed = did_set_cb_args.os_value_changed; | ||||
|     // The 'keymap', 'filetype' and 'syntax' option callback functions may change the | ||||
|     // os_value_checked field. | ||||
|     *value_checked = did_set_cb_args.os_value_checked; | ||||
|     // The 'isident', 'iskeyword', 'isprint' and 'isfname' options may change the character table. | ||||
|     // On failure, this needs to be restored. | ||||
|     restore_chartab = did_set_cb_args.os_restore_chartab; | ||||
|   } | ||||
|  | ||||
|   // If an error is detected, restore the previous value. | ||||
|   if (errmsg != NULL) { | ||||
|     set_option_varp(opt_idx, varp, old_value, true); | ||||
|     // When resetting some values, need to act on it. | ||||
|     if (restore_chartab) { | ||||
|       (void)buf_init_chartab(curbuf, true); | ||||
|     } | ||||
|  | ||||
|     // Unset new_value as it is no longer valid. | ||||
|     new_value = NIL_OPTVAL;  // NOLINT(clang-analyzer-deadcode.DeadStores) | ||||
|   } else { | ||||
|     // Re-assign the new value as its value may get freed or modified by the option callback. | ||||
|     new_value = optval_from_varp(opt_idx, varp); | ||||
|  | ||||
|     // Remember where the option was set. | ||||
|     set_option_sctx_idx(opt_idx, opt_flags, current_sctx); | ||||
|     // Free options that are in allocated memory. | ||||
|     // Use "free_oldval", because recursiveness may change the flags (esp. init_highlight()). | ||||
|     if (free_oldval) { | ||||
|       optval_free(old_value); | ||||
|     } | ||||
|     opt->flags |= P_ALLOCED; | ||||
|  | ||||
|     // Check the bound for num options. | ||||
|     if (new_value.type == kOptValTypeNumber) { | ||||
|       errmsg = check_num_option_bounds((OptInt *)varp, old_value.data.number, errbuf, errbuflen, | ||||
|                                        errmsg); | ||||
|       // Re-assign new_value because the new value was modified by the bound check. | ||||
|       new_value = optval_from_varp(opt_idx, varp); | ||||
|     } | ||||
|  | ||||
|     if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 && (opt->indir & PV_BOTH)) { | ||||
|       // Global option with local value set to use global value. | ||||
|       // Free the local value and clear it. | ||||
|       void *varp_local = get_varp_scope(opt, OPT_LOCAL); | ||||
|       OptVal local_unset_value = optval_unset_local(opt_idx, varp_local); | ||||
|       set_option_varp(opt_idx, varp_local, local_unset_value, true); | ||||
|     } else if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { | ||||
|       // May set global value for local option. | ||||
|       void *varp_global = get_varp_scope(opt, OPT_GLOBAL); | ||||
|       set_option_varp(opt_idx, varp_global, optval_copy(new_value), true); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Skip processing the option further if asked to do so. | ||||
|   if (*doskip) { | ||||
|     return errmsg; | ||||
|   } | ||||
|  | ||||
|   if (errmsg == NULL) { | ||||
|     // Trigger the autocommand only after setting the flags. | ||||
|     if (varp == &curbuf->b_p_syn) { | ||||
|       do_syntax_autocmd(curbuf, value_changed); | ||||
|     } else if (varp == &curbuf->b_p_ft) { | ||||
|       // 'filetype' is set, trigger the FileType autocommand | ||||
|       // Skip this when called from a modeline | ||||
|       // Force autocmd when the filetype was changed | ||||
|       if (!(opt_flags & OPT_MODELINE) || value_changed) { | ||||
|         do_filetype_autocmd(curbuf, value_changed); | ||||
|       } | ||||
|     } else if (varp == &curwin->w_s->b_p_spl) { | ||||
|       do_spelllang_source(curwin); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // In case 'columns' or 'ls' changed. | ||||
|   comp_col(); | ||||
|  | ||||
|   if (varp == &p_mouse) { | ||||
|     setmouse();  // in case 'mouse' changed | ||||
|   } else if ((varp == &p_flp || varp == &(curbuf->b_p_flp)) && curwin->w_briopt_list) { | ||||
|     // Changing Formatlistpattern when briopt includes the list setting: | ||||
|     // redraw | ||||
|     redraw_all_later(UPD_NOT_VALID); | ||||
|   } else if (varp == &p_wbr || varp == &(curwin->w_p_wbr)) { | ||||
|     // add / remove window bars for 'winbar' | ||||
|     set_winbar(true); | ||||
|   } | ||||
|  | ||||
|   if (curwin->w_curswant != MAXCOL && (opt->flags & (P_CURSWANT | P_RALL)) != 0) { | ||||
|     curwin->w_set_curswant = true; | ||||
|   } | ||||
|  | ||||
|   check_redraw(opt->flags); | ||||
|  | ||||
|   if (errmsg == NULL) { | ||||
|     uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); | ||||
|     opt->flags |= P_WAS_SET; | ||||
|  | ||||
|     // When an option is set in the sandbox, from a modeline or in secure mode set the P_INSECURE | ||||
|     // flag.  Otherwise, if a new value is stored reset the flag. | ||||
|     if (!value_checked && (secure || sandbox != 0 || (opt_flags & OPT_MODELINE))) { | ||||
|       *p |= P_INSECURE; | ||||
|     } else if (value_replaced) { | ||||
|       *p &= ~P_INSECURE; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return errmsg; | ||||
| } | ||||
|  | ||||
| /// Set the value of an option using an OptVal. | ||||
| /// | ||||
| /// @param       opt_idx    Option index. Must be >=0. | ||||
| /// @param[in]   varp       Option variable pointer, cannot be NULL. | ||||
| /// @param       value      New option value. | ||||
| /// @param       opt_flags  Option flags. | ||||
| /// @param       new_value  Whether value was replaced completely. | ||||
| /// @param[out]  errbuf     Buffer for error message. | ||||
| /// @param       errbuflen  Length of error buffer. | ||||
| /// @param       opt_idx         Index in options[] table. Must be >= 0. | ||||
| /// @param[in]   varp            Option variable pointer, cannot be NULL. | ||||
| /// @param       value           New option value. Might get freed. | ||||
| /// @param       opt_flags       Option flags. | ||||
| /// @param       value_replaced  Value was replaced completely. | ||||
| /// @param[out]  errbuf          Buffer for error message. | ||||
| /// @param       errbuflen       Length of error buffer. | ||||
| /// | ||||
| /// @return  Error message. NULL if there are no errors. | ||||
| /// @return  NULL on success, an untranslated error message on error. | ||||
| static const char *set_option(const int opt_idx, void *varp, OptVal value, int opt_flags, | ||||
|                               const bool new_value, char *errbuf, size_t errbuflen) | ||||
|                               const bool value_replaced, char *errbuf, size_t errbuflen) | ||||
| { | ||||
|   assert(opt_idx >= 0 && varp != NULL); | ||||
|  | ||||
|   const char *errmsg = NULL; | ||||
|   bool value_checked = false; | ||||
|  | ||||
|   vimoption_T *opt = &options[opt_idx]; | ||||
|  | ||||
|   // TODO(famiu): Unify set_string_option with set_option. | ||||
|   if (value.type == kOptValTypeString) { | ||||
|     errmsg = set_string_option(opt_idx, varp, value.data.string.data, opt_flags, true, | ||||
|                                &value_checked, errbuf, errbuflen); | ||||
|     goto end; | ||||
|   static const char *optval_type_names[] = { | ||||
|     [kOptValTypeNil] = "Nil", | ||||
|     [kOptValTypeBoolean] = "Boolean", | ||||
|     [kOptValTypeNumber] = "Number", | ||||
|     [kOptValTypeString] = "String" | ||||
|   }; | ||||
|  | ||||
|   if (value.type == kOptValTypeNil) { | ||||
|     // Don't try to unset local value if scope is global. | ||||
|     // TODO(famiu): Change this to forbid changing all non-local scopes when the API scope bug is | ||||
|     // fixed. | ||||
|     if (opt_flags == OPT_GLOBAL) { | ||||
|       errmsg = _("Cannot unset global option value"); | ||||
|     } else { | ||||
|       optval_free(value); | ||||
|       value = optval_unset_local(opt_idx, varp); | ||||
|     } | ||||
|   } else if (!optval_match_type(value, opt_idx)) { | ||||
|     char *rep = optval_to_cstr(value); | ||||
|     char *valid_types = option_get_valid_types(opt_idx); | ||||
|     snprintf(errbuf, IOSIZE, _("Invalid value for option '%s': expected %s, got %s %s"), | ||||
|              opt->fullname, valid_types, optval_type_names[value.type], rep); | ||||
|     xfree(rep); | ||||
|     xfree(valid_types); | ||||
|     errmsg = errbuf; | ||||
|   } | ||||
|  | ||||
|   // Disallow changing some options from secure mode. | ||||
|   if ((secure || sandbox != 0) && (options[opt_idx].flags & P_SECURE)) { | ||||
|     return e_secure; | ||||
|   if (errmsg != NULL) { | ||||
|     goto err; | ||||
|   } | ||||
|  | ||||
|   // When using ":set opt=val" for a global option with a local value the local value will be reset, | ||||
|   // use the global value here. | ||||
|   if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 && ((int)opt->indir & PV_BOTH)) { | ||||
|     varp = opt->var; | ||||
|   } | ||||
|  | ||||
|   OptVal old_value = optval_from_varp(opt_idx, varp); | ||||
| @@ -3527,100 +3781,84 @@ static const char *set_option(const int opt_idx, void *varp, OptVal value, int o | ||||
|   // TODO(famiu): This needs to be changed to use the current type of the old value instead of | ||||
|   // value.type, when multi-type options are added. | ||||
|   if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { | ||||
|     old_local_value = optval_from_varp(opt_idx, get_varp_scope(opt, OPT_LOCAL)); | ||||
|     old_global_value = optval_from_varp(opt_idx, get_varp_scope(opt, OPT_GLOBAL)); | ||||
|     old_local_value = optval_from_varp(opt_idx, get_varp_scope(opt, OPT_LOCAL)); | ||||
|  | ||||
|     // If local value of global-local option is unset, use global value as local value. | ||||
|     if (is_option_local_value_unset(opt, curbuf, curwin)) { | ||||
|       old_local_value = old_global_value; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Value that's actually being used. | ||||
|   // For local scope of a global-local option, it is equal to the global value. | ||||
|   // In every other case, it is the same as old_value. | ||||
|   const bool oldval_is_global = ((int)opt->indir & PV_BOTH) && (opt_flags & OPT_LOCAL); | ||||
|   OptVal used_old_value = oldval_is_global ? optval_from_varp(opt_idx, get_varp(opt)) : old_value; | ||||
|  | ||||
|   if (value.type == kOptValTypeNumber) { | ||||
|     errmsg = validate_num_option((OptInt *)varp, &value.data.number); | ||||
|  | ||||
|     // Don't change the value and return early if validation failed. | ||||
|     if (errmsg != NULL) { | ||||
|       return errmsg; | ||||
|       goto err; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // Set the new option value. | ||||
|   set_option_varp(varp, value); | ||||
|   // Remember where the option was set. | ||||
|   set_option_sctx_idx(opt_idx, opt_flags, current_sctx); | ||||
|   // May set global value for local option. | ||||
|   if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { | ||||
|     set_option_varp(get_varp_scope(opt, OPT_GLOBAL), value); | ||||
|   set_option_varp(opt_idx, varp, value, false); | ||||
|  | ||||
|   OptVal saved_used_value = optval_copy(used_old_value); | ||||
|   OptVal saved_old_global_value = optval_copy(old_global_value); | ||||
|   OptVal saved_old_local_value = optval_copy(old_local_value); | ||||
|   // New value (and varp) may become invalid if the buffer is closed by autocommands. | ||||
|   OptVal saved_new_value = optval_copy(value); | ||||
|  | ||||
|   uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); | ||||
|   const int secure_saved = secure; | ||||
|  | ||||
|   // When an option is set in the sandbox, from a modeline or in secure mode, then deal with side | ||||
|   // effects in secure mode. Also when the value was set with the P_INSECURE flag and is not | ||||
|   // completely replaced. | ||||
|   if ((opt_flags & OPT_MODELINE) || sandbox != 0 || (!value_replaced && (*p & P_INSECURE))) { | ||||
|     secure = 1; | ||||
|   } | ||||
|  | ||||
|   // Invoke the option specific callback function to validate and apply the new value. | ||||
|   bool doskip = false; | ||||
|   opt_did_set_cb_T did_set_cb; | ||||
|  | ||||
|   if ((int *)varp == &p_force_on) { | ||||
|     did_set_cb = did_set_force_on; | ||||
|   } else if ((int *)varp == &p_force_off) { | ||||
|     did_set_cb = did_set_force_off; | ||||
|   } else { | ||||
|     did_set_cb = opt->opt_did_set_cb; | ||||
|   } | ||||
|   if (did_set_cb != NULL) { | ||||
|     // TODO(famiu): make os_oldval and os_newval use OptVal. | ||||
|     optset_T did_set_cb_args = { | ||||
|       .os_varp = varp, | ||||
|       .os_flags = opt_flags, | ||||
|       .os_oldval = old_value.data, | ||||
|       .os_newval = value.data, | ||||
|       .os_doskip = false, | ||||
|       .os_errbuf = NULL, | ||||
|       .os_errbuflen = 0, | ||||
|       .os_buf = curbuf, | ||||
|       .os_win = curwin | ||||
|     }; | ||||
|   errmsg = did_set_option(opt_idx, varp, old_value, value, opt_flags, &doskip, &value_checked, | ||||
|                           value_replaced, errbuf, errbuflen); | ||||
|  | ||||
|     errmsg = did_set_cb(&did_set_cb_args); | ||||
|     doskip = did_set_cb_args.os_doskip; | ||||
|   } | ||||
|   secure = secure_saved; | ||||
|  | ||||
|   // Stop processing option further if asked to do so. | ||||
|   if (doskip) { | ||||
|     return errmsg; | ||||
|     goto end; | ||||
|   } | ||||
|  | ||||
|   // Check the bound for num options. | ||||
|   if (value.type == kOptValTypeNumber) { | ||||
|     errmsg | ||||
|       = check_num_option_bounds((OptInt *)varp, old_value.data.number, errbuf, errbuflen, errmsg); | ||||
|   } | ||||
|  | ||||
|   apply_optionset_autocmd(opt_idx, opt_flags, | ||||
|                           old_value, | ||||
|                           old_global_value, | ||||
|                           old_local_value, | ||||
|                           value, | ||||
|                           errmsg); | ||||
|  | ||||
|   if (opt->flags & P_UI_OPTION) { | ||||
|     OptVal value_copy = optval_copy(optval_from_varp(opt_idx, varp)); | ||||
|     ui_call_option_set(cstr_as_string(opt->fullname), | ||||
|                        optval_as_object(value_copy)); | ||||
|   } | ||||
|  | ||||
|   comp_col();  // in case 'columns' or 'ls' changed | ||||
|   if (curwin->w_curswant != MAXCOL | ||||
|       && (opt->flags & (P_CURSWANT | P_RALL)) != 0) { | ||||
|     curwin->w_set_curswant = true; | ||||
|   } | ||||
|   check_redraw(opt->flags); | ||||
|  | ||||
| end: | ||||
|   if (errmsg == NULL) { | ||||
|     opt->flags |= P_WAS_SET; | ||||
|  | ||||
|     // When an option is set in the sandbox, from a modeline or in secure mode set the P_INSECURE | ||||
|     // flag.  Otherwise, if a new value is stored reset the flag. | ||||
|     uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); | ||||
|     if (!value_checked && (secure || sandbox != 0 || (opt_flags & OPT_MODELINE))) { | ||||
|       *p |= P_INSECURE; | ||||
|     } else if (new_value) { | ||||
|       *p &= ~P_INSECURE; | ||||
|     if (!starting) { | ||||
|       apply_optionset_autocmd(opt_idx, opt_flags, saved_used_value, saved_old_global_value, | ||||
|                               saved_old_local_value, saved_new_value, errmsg); | ||||
|     } | ||||
|     if (opt->flags & P_UI_OPTION) { | ||||
|       // Calculate saved_new_value again as its value might be changed by bound checks. | ||||
|       // NOTE: Currently there are no buffer/window local UI options, but if there ever are buffer | ||||
|       // or window local UI options added in the future, varp might become invalid if the buffer or | ||||
|       // window is closed during an autocommand, and a check would have to be added for it. | ||||
|       optval_free(saved_new_value); | ||||
|       saved_new_value = optval_copy(optval_from_varp(opt_idx, varp)); | ||||
|       ui_call_option_set(cstr_as_string(opt->fullname), optval_as_object(saved_new_value)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
| end: | ||||
|   // Free copied values as they are not needed anymore | ||||
|   optval_free(saved_used_value); | ||||
|   optval_free(saved_old_local_value); | ||||
|   optval_free(saved_old_global_value); | ||||
|   optval_free(saved_new_value); | ||||
|   return errmsg; | ||||
| err: | ||||
|   optval_free(value); | ||||
|   return errmsg; | ||||
| } | ||||
|  | ||||
| @@ -3630,17 +3868,10 @@ end: | ||||
| /// @param[in]  value      Option value. If NIL_OPTVAL, the option value is cleared. | ||||
| /// @param[in]  opt_flags  Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both). | ||||
| /// | ||||
| /// @return NULL on success, an untranslated error message on error. | ||||
| /// @return  NULL on success, an untranslated error message on error. | ||||
| const char *set_option_value(const char *const name, const OptVal value, int opt_flags) | ||||
|   FUNC_ATTR_NONNULL_ARG(1) | ||||
| { | ||||
|   static const char *optval_type_names[] = { | ||||
|     [kOptValTypeNil] = "Nil", | ||||
|     [kOptValTypeBoolean] = "Boolean", | ||||
|     [kOptValTypeNumber] = "Number", | ||||
|     [kOptValTypeString] = "String" | ||||
|   }; | ||||
|  | ||||
|   static char errbuf[IOSIZE]; | ||||
|  | ||||
|   if (is_tty_option(name)) { | ||||
| @@ -3666,26 +3897,9 @@ const char *set_option_value(const char *const name, const OptVal value, int opt | ||||
|   } | ||||
|  | ||||
|   const char *errmsg = NULL; | ||||
|   // Copy the value so we can modify the copy. | ||||
|   OptVal v = optval_copy(value); | ||||
|  | ||||
|   if (v.type == kOptValTypeNil) { | ||||
|     v = optval_clear(name, flags, varp, curbuf, curwin); | ||||
|   } else if (!optval_match_type(v, opt_idx)) { | ||||
|     char *rep = optval_to_cstr(v); | ||||
|     char *valid_types = option_get_valid_types(opt_idx); | ||||
|     snprintf(errbuf, IOSIZE, _("Invalid value for option '%s': expected %s, got %s %s"), | ||||
|              name, valid_types, optval_type_names[v.type], rep); | ||||
|     xfree(rep); | ||||
|     xfree(valid_types); | ||||
|     errmsg = errbuf; | ||||
|     goto end; | ||||
|   } | ||||
|   errmsg = set_option(opt_idx, varp, optval_copy(value), opt_flags, true, errbuf, sizeof(errbuf)); | ||||
|  | ||||
|   errmsg = set_option(opt_idx, varp, v, opt_flags, true, errbuf, sizeof(errbuf)); | ||||
|  | ||||
| end: | ||||
|   optval_free(v);  // Free the copied OptVal. | ||||
|   return errmsg; | ||||
| } | ||||
|  | ||||
| @@ -4143,115 +4357,6 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value) | ||||
|   return OK; | ||||
| } | ||||
|  | ||||
| // Unset local option value, similar to ":set opt<". | ||||
| void unset_global_local_option(char *name, void *from) | ||||
| { | ||||
|   vimoption_T *p; | ||||
|   buf_T *buf = (buf_T *)from; | ||||
|  | ||||
|   int opt_idx = findoption(name); | ||||
|   if (opt_idx < 0) { | ||||
|     semsg(_("E355: Unknown option: %s"), name); | ||||
|     return; | ||||
|   } | ||||
|   p = &(options[opt_idx]); | ||||
|  | ||||
|   switch ((int)p->indir) { | ||||
|   // global option with local value: use local value if it's been set | ||||
|   case PV_EP: | ||||
|     clear_string_option(&buf->b_p_ep); | ||||
|     break; | ||||
|   case PV_KP: | ||||
|     clear_string_option(&buf->b_p_kp); | ||||
|     break; | ||||
|   case PV_PATH: | ||||
|     clear_string_option(&buf->b_p_path); | ||||
|     break; | ||||
|   case PV_AR: | ||||
|     buf->b_p_ar = -1; | ||||
|     break; | ||||
|   case PV_BKC: | ||||
|     clear_string_option(&buf->b_p_bkc); | ||||
|     buf->b_bkc_flags = 0; | ||||
|     break; | ||||
|   case PV_TAGS: | ||||
|     clear_string_option(&buf->b_p_tags); | ||||
|     break; | ||||
|   case PV_TC: | ||||
|     clear_string_option(&buf->b_p_tc); | ||||
|     buf->b_tc_flags = 0; | ||||
|     break; | ||||
|   case PV_SISO: | ||||
|     curwin->w_p_siso = -1; | ||||
|     break; | ||||
|   case PV_SO: | ||||
|     curwin->w_p_so = -1; | ||||
|     break; | ||||
|   case PV_DEF: | ||||
|     clear_string_option(&buf->b_p_def); | ||||
|     break; | ||||
|   case PV_INC: | ||||
|     clear_string_option(&buf->b_p_inc); | ||||
|     break; | ||||
|   case PV_DICT: | ||||
|     clear_string_option(&buf->b_p_dict); | ||||
|     break; | ||||
|   case PV_TSR: | ||||
|     clear_string_option(&buf->b_p_tsr); | ||||
|     break; | ||||
|   case PV_TSRFU: | ||||
|     clear_string_option(&buf->b_p_tsrfu); | ||||
|     break; | ||||
|   case PV_FP: | ||||
|     clear_string_option(&buf->b_p_fp); | ||||
|     break; | ||||
|   case PV_EFM: | ||||
|     clear_string_option(&buf->b_p_efm); | ||||
|     break; | ||||
|   case PV_GP: | ||||
|     clear_string_option(&buf->b_p_gp); | ||||
|     break; | ||||
|   case PV_MP: | ||||
|     clear_string_option(&buf->b_p_mp); | ||||
|     break; | ||||
|   case PV_SBR: | ||||
|     clear_string_option(&((win_T *)from)->w_p_sbr); | ||||
|     break; | ||||
|   case PV_STL: | ||||
|     clear_string_option(&((win_T *)from)->w_p_stl); | ||||
|     break; | ||||
|   case PV_WBR: | ||||
|     clear_string_option(&((win_T *)from)->w_p_wbr); | ||||
|     break; | ||||
|   case PV_UL: | ||||
|     buf->b_p_ul = NO_LOCAL_UNDOLEVEL; | ||||
|     break; | ||||
|   case PV_LW: | ||||
|     clear_string_option(&buf->b_p_lw); | ||||
|     break; | ||||
|   case PV_MENC: | ||||
|     clear_string_option(&buf->b_p_menc); | ||||
|     break; | ||||
|   case PV_LCS: | ||||
|     clear_string_option(&((win_T *)from)->w_p_lcs); | ||||
|     set_listchars_option((win_T *)from, ((win_T *)from)->w_p_lcs, true); | ||||
|     redraw_later((win_T *)from, UPD_NOT_VALID); | ||||
|     break; | ||||
|   case PV_FCS: | ||||
|     clear_string_option(&((win_T *)from)->w_p_fcs); | ||||
|     set_fillchars_option((win_T *)from, ((win_T *)from)->w_p_fcs, true); | ||||
|     redraw_later((win_T *)from, UPD_NOT_VALID); | ||||
|     break; | ||||
|   case PV_VE: | ||||
|     clear_string_option(&((win_T *)from)->w_p_ve); | ||||
|     ((win_T *)from)->w_ve_flags = 0; | ||||
|     break; | ||||
|   case PV_STC: | ||||
|     clear_string_option(&((win_T *)from)->w_p_stc); | ||||
|     break; | ||||
|   } | ||||
| } | ||||
|  | ||||
| void *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win) | ||||
| { | ||||
|   if ((scope & OPT_GLOBAL) && p->indir != PV_NONE) { | ||||
| @@ -4634,12 +4739,6 @@ static inline void *get_varp(vimoption_T *p) | ||||
|   return get_varp_from(p, curbuf, curwin); | ||||
| } | ||||
|  | ||||
| /// Return the did_set callback function for the option at 'opt_idx' | ||||
| opt_did_set_cb_T get_option_did_set_cb(int opt_idx) | ||||
| { | ||||
|   return options[opt_idx].opt_did_set_cb; | ||||
| } | ||||
|  | ||||
| /// Get the value of 'equalprg', either the buffer-local one or the global one. | ||||
| char *get_equalprg(void) | ||||
| { | ||||
|   | ||||
| @@ -56,6 +56,7 @@ typedef struct vimoption { | ||||
|   /// cmdline. Only useful for string options. | ||||
|   opt_expand_cb_T opt_expand_cb; | ||||
|  | ||||
|   // TODO(famiu): Use OptVal for def_val. | ||||
|   void *def_val;     ///< default values for variable (neovim!!) | ||||
|   LastSet last_set;  ///< script in which the option was last set | ||||
| } vimoption_T; | ||||
| @@ -72,6 +73,7 @@ enum { | ||||
| /// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global | ||||
| /// values, get local value. | ||||
| typedef enum { | ||||
|   // TODO(famiu): See if `OPT_FREE` is really necessary and remove it if not. | ||||
|   OPT_FREE      = 0x01,   ///< Free old value if it was allocated. | ||||
|   OPT_GLOBAL    = 0x02,   ///< Use global value. | ||||
|   OPT_LOCAL     = 0x04,   ///< Use local value. | ||||
|   | ||||
| @@ -173,51 +173,7 @@ void didset_string_options(void) | ||||
|   (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); | ||||
| } | ||||
|  | ||||
| /// Trigger the OptionSet autocommand. | ||||
| /// "opt_idx"   is the index of the option being set. | ||||
| /// "opt_flags" can be OPT_LOCAL etc. | ||||
| /// "oldval"    the old value | ||||
| /// "oldval_l"  the old local value (only non-NULL if global and local value are set) | ||||
| /// "oldval_g"  the old global value (only non-NULL if global and local value are set) | ||||
| /// "newval"    the new value | ||||
| void trigger_optionset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l, | ||||
|                               char *oldval_g, char *newval) | ||||
| { | ||||
|   // Don't do this recursively. | ||||
|   if (oldval == NULL || newval == NULL | ||||
|       || *get_vim_var_str(VV_OPTION_TYPE) != NUL) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   char buf_type[7]; | ||||
|  | ||||
|   vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", | ||||
|                (opt_flags & OPT_LOCAL) ? "local" : "global"); | ||||
|   set_vim_var_string(VV_OPTION_OLD, oldval, -1); | ||||
|   set_vim_var_string(VV_OPTION_NEW, newval, -1); | ||||
|   set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); | ||||
|   if (opt_flags & OPT_LOCAL) { | ||||
|     set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); | ||||
|     set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); | ||||
|   } | ||||
|   if (opt_flags & OPT_GLOBAL) { | ||||
|     set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); | ||||
|     set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval, -1); | ||||
|   } | ||||
|   if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { | ||||
|     set_vim_var_string(VV_OPTION_COMMAND, "set", -1); | ||||
|     set_vim_var_string(VV_OPTION_OLDLOCAL, oldval_l, -1); | ||||
|     set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval_g, -1); | ||||
|   } | ||||
|   if (opt_flags & OPT_MODELINE) { | ||||
|     set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); | ||||
|     set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); | ||||
|   } | ||||
|   apply_autocmds(EVENT_OPTIONSET, get_option(opt_idx)->fullname, NULL, false, NULL); | ||||
|   reset_v_option_vars(); | ||||
| } | ||||
|  | ||||
| static char *illegal_char(char *errbuf, size_t errbuflen, int c) | ||||
| char *illegal_char(char *errbuf, size_t errbuflen, int c) | ||||
| { | ||||
|   if (errbuf == NULL) { | ||||
|     return ""; | ||||
| @@ -340,7 +296,9 @@ static void set_string_option_global(vimoption_T *opt, char **varp) | ||||
| /// "set_sid" is SID_NONE don't set the scriptID.  Otherwise set the scriptID to | ||||
| /// "set_sid". | ||||
| /// | ||||
| /// @param opt_flags  OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL | ||||
| /// @param opt_flags  OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL. | ||||
| /// | ||||
| /// TODO(famiu): Remove this and its win/buf variants. | ||||
| void set_string_option_direct(const char *name, int opt_idx, const char *val, int opt_flags, | ||||
|                               int set_sid) | ||||
| { | ||||
| @@ -431,100 +389,6 @@ void set_string_option_direct_in_buf(buf_T *buf, const char *name, int opt_idx, | ||||
|   unblock_autocmds(); | ||||
| } | ||||
|  | ||||
| /// Set a string option to a new value, handling the effects | ||||
| /// Must not be called with a hidden option! | ||||
| /// | ||||
| /// @param[in]  opt_idx  Option to set. | ||||
| /// @param[in]  value  New value. | ||||
| /// @param[in]  opt_flags  Option flags: expected to contain #OPT_LOCAL and/or | ||||
| ///                        #OPT_GLOBAL. | ||||
| /// | ||||
| /// @return NULL on success, an untranslated error message on error. | ||||
| const char *set_string_option(const int opt_idx, void *varp, const char *value, const int opt_flags, | ||||
|                               const bool new_value, bool *value_checked, char *const errbuf, | ||||
|                               const size_t errbuflen) | ||||
|   FUNC_ATTR_WARN_UNUSED_RESULT | ||||
| { | ||||
|   vimoption_T *opt = get_option(opt_idx); | ||||
|  | ||||
|   char *origval_l = NULL; | ||||
|   char *origval_g = NULL; | ||||
|  | ||||
|   // When using ":set opt=val" for a global option | ||||
|   // with a local value the local value will be | ||||
|   // reset, use the global value here. | ||||
|   if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 | ||||
|       && ((int)opt->indir & PV_BOTH)) { | ||||
|     varp = opt->var; | ||||
|   } | ||||
|  | ||||
|   // The old value is kept until we are sure that the new value is valid. | ||||
|   char *oldval = *(char **)varp; | ||||
|  | ||||
|   if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { | ||||
|     origval_l = *(char **)get_varp_scope(opt, OPT_LOCAL); | ||||
|     origval_g = *(char **)get_varp_scope(opt, OPT_GLOBAL); | ||||
|  | ||||
|     // A global-local string option might have an empty option as value to | ||||
|     // indicate that the global value should be used. | ||||
|     if (((int)opt->indir & PV_BOTH) && origval_l == empty_string_option) { | ||||
|       origval_l = origval_g; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   char *origval; | ||||
|   // When setting the local value of a global option, the old value may be | ||||
|   // the global value. | ||||
|   if (((int)opt->indir & PV_BOTH) && (opt_flags & OPT_LOCAL)) { | ||||
|     origval = *(char **)get_varp_from(opt, curbuf, curwin); | ||||
|   } else { | ||||
|     origval = oldval; | ||||
|   } | ||||
|  | ||||
|   *(char **)varp = xstrdup(value != NULL ? value : empty_string_option); | ||||
|  | ||||
|   char *const saved_origval = (origval != NULL) ? xstrdup(origval) : NULL; | ||||
|   char *const saved_oldval_l = (origval_l != NULL) ? xstrdup(origval_l) : 0; | ||||
|   char *const saved_oldval_g = (origval_g != NULL) ? xstrdup(origval_g) : 0; | ||||
|  | ||||
|   // newval (and varp) may become invalid if the buffer is closed by | ||||
|   // autocommands. | ||||
|   char *const saved_newval = xstrdup(*(char **)varp); | ||||
|  | ||||
|   const int secure_saved = secure; | ||||
|   const uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); | ||||
|  | ||||
|   // When an option is set in the sandbox, from a modeline or in secure mode, then deal with side | ||||
|   // effects in secure mode. Also when the value was set with the P_INSECURE flag and is not | ||||
|   // completely replaced. | ||||
|   if ((opt_flags & OPT_MODELINE) || sandbox != 0 || (!new_value && (*p & P_INSECURE))) { | ||||
|     secure = 1; | ||||
|   } | ||||
|  | ||||
|   const char *const errmsg = did_set_string_option(curbuf, curwin, opt_idx, varp, oldval, | ||||
|                                                    errbuf, errbuflen, opt_flags, value_checked); | ||||
|  | ||||
|   secure = secure_saved; | ||||
|  | ||||
|   // call autocommand after handling side effects | ||||
|   if (errmsg == NULL) { | ||||
|     if (!starting) { | ||||
|       trigger_optionset_string(opt_idx, opt_flags, saved_origval, saved_oldval_l, | ||||
|                                saved_oldval_g, saved_newval); | ||||
|     } | ||||
|     if (opt->flags & P_UI_OPTION) { | ||||
|       ui_call_option_set(cstr_as_string(opt->fullname), | ||||
|                          CSTR_AS_OBJ(saved_newval)); | ||||
|     } | ||||
|   } | ||||
|   xfree(saved_origval); | ||||
|   xfree(saved_oldval_l); | ||||
|   xfree(saved_oldval_g); | ||||
|   xfree(saved_newval); | ||||
|  | ||||
|   return errmsg; | ||||
| } | ||||
|  | ||||
| /// Return true if "val" is a valid 'filetype' name. | ||||
| /// Also used for 'syntax' and 'keymap'. | ||||
| static bool valid_filetype(const char *val) | ||||
| @@ -636,7 +500,7 @@ const char *check_stl_option(char *s) | ||||
| /// Check for a "normal" directory or file name in some options.  Disallow a | ||||
| /// path separator (slash and/or backslash), wildcards and characters that are | ||||
| /// often illegal in a file name. Be more permissive if "secure" is off. | ||||
| static bool check_illegal_path_names(char *val, uint32_t flags) | ||||
| bool check_illegal_path_names(char *val, uint32_t flags) | ||||
| { | ||||
|   return (((flags & P_NFNAME) | ||||
|            && strpbrk(val, (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL) | ||||
| @@ -2676,177 +2540,6 @@ int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches) | ||||
|   return expand_set_opt_generic(args, get_highlight_name, numMatches, matches); | ||||
| } | ||||
|  | ||||
| // When 'syntax' is set, load the syntax of that name | ||||
| static void do_syntax_autocmd(buf_T *buf, bool value_changed) | ||||
| { | ||||
|   static int syn_recursive = 0; | ||||
|  | ||||
|   syn_recursive++; | ||||
|   // Only pass true for "force" when the value changed or not used | ||||
|   // recursively, to avoid endless recurrence. | ||||
|   apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname, | ||||
|                  value_changed || syn_recursive == 1, buf); | ||||
|   buf->b_flags |= BF_SYN_SET; | ||||
|   syn_recursive--; | ||||
| } | ||||
|  | ||||
| static void do_spelllang_source(win_T *win) | ||||
| { | ||||
|   char fname[200]; | ||||
|   char *q = win->w_s->b_p_spl; | ||||
|  | ||||
|   // Skip the first name if it is "cjk". | ||||
|   if (strncmp(q, "cjk,", 4) == 0) { | ||||
|     q += 4; | ||||
|   } | ||||
|  | ||||
|   // Source the spell/LANG.{vim,lua} in 'runtimepath'. | ||||
|   // They could set 'spellcapcheck' depending on the language. | ||||
|   // Use the first name in 'spelllang' up to '_region' or | ||||
|   // '.encoding'. | ||||
|   char *p; | ||||
|   for (p = q; *p != NUL; p++) { | ||||
|     if (!ASCII_ISALNUM(*p) && *p != '-') { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   if (p > q) { | ||||
|     vim_snprintf(fname, sizeof(fname), "spell/%.*s.*", (int)(p - q), q); | ||||
|     source_runtime_vim_lua(fname, DIP_ALL); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// Handle string options that need some action to perform when changed. | ||||
| /// The new value must be allocated. | ||||
| /// | ||||
| /// @param opt_idx  index in options[] table | ||||
| /// @param varp  pointer to the option variable | ||||
| /// @param oldval  previous value of the option | ||||
| /// @param errbuf  buffer for errors, or NULL | ||||
| /// @param errbuflen  length of errors buffer | ||||
| /// @param opt_flags  OPT_LOCAL and/or OPT_GLOBAL | ||||
| /// @param op OP_ADDING/OP_PREPENDING/OP_REMOVING | ||||
| /// @param value_checked  value was checked to be safe, no need to set P_INSECURE | ||||
| /// | ||||
| /// @return  NULL for success, or an untranslated error message for an error | ||||
| const char *did_set_string_option(buf_T *buf, win_T *win, int opt_idx, char **varp, char *oldval, | ||||
|                                   char *errbuf, size_t errbuflen, int opt_flags, | ||||
|                                   bool *value_checked) | ||||
| { | ||||
|   const char *errmsg = NULL; | ||||
|   int restore_chartab = false; | ||||
|   vimoption_T *opt = get_option(opt_idx); | ||||
|   bool free_oldval = (opt->flags & P_ALLOCED); | ||||
|   opt_did_set_cb_T did_set_cb = get_option_did_set_cb(opt_idx); | ||||
|   bool value_changed = false; | ||||
|  | ||||
|   optset_T args = { | ||||
|     .os_varp = varp, | ||||
|     .os_idx = opt_idx, | ||||
|     .os_flags = opt_flags, | ||||
|     .os_oldval.string = cstr_as_string(oldval), | ||||
|     .os_newval.string = cstr_as_string(*varp), | ||||
|     .os_value_checked = false, | ||||
|     .os_value_changed = false, | ||||
|     .os_restore_chartab = false, | ||||
|     .os_errbuf = errbuf, | ||||
|     .os_errbuflen = errbuflen, | ||||
|     .os_win = win, | ||||
|     .os_buf = buf, | ||||
|   }; | ||||
|  | ||||
|   // Disallow changing some options from secure mode | ||||
|   if ((secure || sandbox != 0) && (opt->flags & P_SECURE)) { | ||||
|     errmsg = e_secure; | ||||
|     // Check for a "normal" directory or file name in some options. | ||||
|   } else if (check_illegal_path_names(*varp, opt->flags)) { | ||||
|     errmsg = e_invarg; | ||||
|   } else if (did_set_cb != NULL) { | ||||
|     // Invoke the option specific callback function to validate and apply | ||||
|     // the new option value. | ||||
|     errmsg = did_set_cb(&args); | ||||
|  | ||||
|     // The 'filetype' and 'syntax' option callback functions may change | ||||
|     // the os_value_changed field. | ||||
|     value_changed = args.os_value_changed; | ||||
|     // The 'keymap', 'filetype' and 'syntax' option callback functions | ||||
|     // may change the os_value_checked field. | ||||
|     *value_checked = args.os_value_checked; | ||||
|     // The 'isident', 'iskeyword', 'isprint' and 'isfname' options may | ||||
|     // change the character table.  On failure, this needs to be restored. | ||||
|     restore_chartab = args.os_restore_chartab; | ||||
|   } | ||||
|  | ||||
|   // If an error is detected, restore the previous value. | ||||
|   if (errmsg != NULL) { | ||||
|     free_string_option(*varp); | ||||
|     *varp = oldval; | ||||
|     // When resetting some values, need to act on it. | ||||
|     if (restore_chartab) { | ||||
|       (void)buf_init_chartab(buf, true); | ||||
|     } | ||||
|   } else { | ||||
|     // Remember where the option was set. | ||||
|     set_option_sctx_idx(opt_idx, opt_flags, current_sctx); | ||||
|     // Free string options that are in allocated memory. | ||||
|     // Use "free_oldval", because recursiveness may change the flags under | ||||
|     // our fingers (esp. init_highlight()). | ||||
|     if (free_oldval) { | ||||
|       free_string_option(oldval); | ||||
|     } | ||||
|     opt->flags |= P_ALLOCED; | ||||
|  | ||||
|     if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 | ||||
|         && (opt->indir & PV_BOTH)) { | ||||
|       // global option with local value set to use global value; free | ||||
|       // the local value and make it empty | ||||
|       char *p = get_varp_scope(opt, OPT_LOCAL); | ||||
|       free_string_option(*(char **)p); | ||||
|       *(char **)p = empty_string_option; | ||||
|     } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) { | ||||
|       // May set global value for local option. | ||||
|       set_string_option_global(opt, varp); | ||||
|     } | ||||
|  | ||||
|     // Trigger the autocommand only after setting the flags. | ||||
|     if (varp == &buf->b_p_syn) { | ||||
|       do_syntax_autocmd(buf, value_changed); | ||||
|     } else if (varp == &buf->b_p_ft) { | ||||
|       // 'filetype' is set, trigger the FileType autocommand | ||||
|       // Skip this when called from a modeline | ||||
|       // Force autocmd when the filetype was changed | ||||
|       if (!(opt_flags & OPT_MODELINE) || value_changed) { | ||||
|         do_filetype_autocmd(buf, value_changed); | ||||
|       } | ||||
|     } else if (varp == &win->w_s->b_p_spl) { | ||||
|       do_spelllang_source(win); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (varp == &p_mouse) { | ||||
|     setmouse();  // in case 'mouse' changed | ||||
|   } | ||||
|  | ||||
|   if ((varp == &p_flp || varp == &(buf->b_p_flp)) | ||||
|       && win->w_briopt_list) { | ||||
|     // Changing Formatlistpattern when briopt includes the list setting: | ||||
|     // redraw | ||||
|     redraw_all_later(UPD_NOT_VALID); | ||||
|   } else if (varp == &p_wbr || varp == &(win->w_p_wbr)) { | ||||
|     // add / remove window bars for 'winbar' | ||||
|     set_winbar(true); | ||||
|   } | ||||
|  | ||||
|   if (win->w_curswant != MAXCOL | ||||
|       && (opt->flags & (P_CURSWANT | P_RALL)) != 0) { | ||||
|     win->w_set_curswant = true; | ||||
|   } | ||||
|  | ||||
|   check_redraw_for(buf, win, opt->flags); | ||||
|  | ||||
|   return errmsg; | ||||
| } | ||||
|  | ||||
| /// Check an option that can be a range of string values. | ||||
| /// | ||||
| /// @param list  when true: accept a list of values | ||||
|   | ||||
| @@ -1536,14 +1536,18 @@ describe('API', function() | ||||
|  | ||||
|       -- Now try with options with a special "local is unset" value (e.g. 'undolevels') | ||||
|       nvim('set_option_value', 'undolevels', 1000, {}) | ||||
|       eq(1000, nvim('get_option_value', 'undolevels', {scope = 'local'})) | ||||
|       nvim('set_option_value', 'undolevels', 1200, {scope = 'local'}) | ||||
|       eq(1200, nvim('get_option_value', 'undolevels', {scope = 'local'})) | ||||
|       nvim('set_option_value', 'undolevels', NIL, {scope = 'local'}) | ||||
|       eq(-123456, nvim('get_option_value', 'undolevels', {scope = 'local'})) | ||||
|       eq(1000, nvim('get_option_value', 'undolevels', {})) | ||||
|  | ||||
|       nvim('set_option_value', 'autoread', true, {}) | ||||
|       eq(true, nvim('get_option_value', 'autoread', {scope = 'local'})) | ||||
|       nvim('set_option_value', 'autoread', false, {scope = 'local'}) | ||||
|       eq(false, nvim('get_option_value', 'autoread', {scope = 'local'})) | ||||
|       nvim('set_option_value', 'autoread', NIL, {scope = 'local'}) | ||||
|       eq(NIL, nvim('get_option_value', 'autoread', {scope = 'local'})) | ||||
|       eq(true, nvim('get_option_value', 'autoread', {})) | ||||
|     end) | ||||
|  | ||||
|     it('set window options', function() | ||||
|   | ||||
| @@ -260,7 +260,7 @@ describe('au OptionSet', function() | ||||
|       command('setlocal tags=tagpath2') | ||||
|       expected_combination({'tags', 'tagpath1', 'tagpath1', '', 'tagpath2', 'local', 'setlocal'}) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local string options | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('noa setglobal tags=tag_global') | ||||
|       command('noa setlocal tags=tag_local') | ||||
| @@ -269,12 +269,12 @@ describe('au OptionSet', function() | ||||
|         'tags', 'tag_global', 'tag_local', 'tag_global', 'tagpath', 'global', 'set' | ||||
|       }) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local string options | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('noa set tags=tag_global') | ||||
|       command('noa setlocal tags=') | ||||
|       command('set tags=tagpath') | ||||
|       expected_combination({'tags', 'tag_global', '', 'tag_global', 'tagpath', 'global', 'set'}) | ||||
|       expected_combination({'tags', 'tag_global', 'tag_global', 'tag_global', 'tagpath', 'global', 'set'}) | ||||
|     end) | ||||
|  | ||||
|     it('with string local (to buffer) option', function() | ||||
| @@ -295,7 +295,7 @@ describe('au OptionSet', function() | ||||
|       command('setlocal spelllang=klingon') | ||||
|       expected_combination({'spelllang', oldval, oldval, '', 'klingon', 'local', 'setlocal'}) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local string options | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('noa setglobal spelllang=spellglobal') | ||||
|       command('noa setlocal spelllang=spelllocal') | ||||
| @@ -311,7 +311,7 @@ describe('au OptionSet', function() | ||||
|       command('set statusline=foo') | ||||
|       expected_combination({'statusline', oldval, oldval, '', 'foo', 'global', 'set'}) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local string options | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('set statusline&') | ||||
|       expected_combination({'statusline', 'foo', 'foo', 'foo', oldval, 'global', 'set'}) | ||||
| @@ -323,7 +323,7 @@ describe('au OptionSet', function() | ||||
|       command('setlocal statusline=baz') | ||||
|       expected_combination({'statusline', oldval, oldval, '', 'baz', 'local', 'setlocal'}) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local string options | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('noa setglobal statusline=bar') | ||||
|       command('noa setlocal statusline=baz') | ||||
| @@ -364,11 +364,15 @@ describe('au OptionSet', function() | ||||
|       command('setlocal cmdheight=2') | ||||
|       expected_combination({'cmdheight', 1, 1, '', 2, 'local', 'setlocal'}) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('noa setglobal cmdheight=8') | ||||
|       command('noa setlocal cmdheight=1') -- Sets the global(!) value | ||||
|       command('set cmdheight=2') | ||||
|       expected_combination({'cmdheight', 1, 1, 1, 2, 'global', 'set'}) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('noa set cmdheight=8') | ||||
|       command('set cmdheight=2') | ||||
|       expected_combination({'cmdheight', 8, 8, 8, 2, 'global', 'set'}) | ||||
| @@ -385,11 +389,15 @@ describe('au OptionSet', function() | ||||
|       command('setlocal undolevels=2') | ||||
|       expected_combination({'undolevels', 1, 1, '', 2, 'local', 'setlocal'}) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('noa setglobal undolevels=8') | ||||
|       command('noa setlocal undolevels=1') | ||||
|       command('set undolevels=2') | ||||
|       expected_combination({'undolevels', 1, 1, 8, 2, 'global', 'set'}) | ||||
|       expected_combination({'undolevels', 8, 1, 8, 2, 'global', 'set'}) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('noa set undolevels=8') | ||||
|       command('set undolevels=2') | ||||
|       expected_combination({'undolevels', 8, 8, 8, 2, 'global', 'set'}) | ||||
| @@ -427,11 +435,15 @@ describe('au OptionSet', function() | ||||
|       command('setlocal scrolloff=2') | ||||
|       expected_combination({'scrolloff', 1, 1, '', 2, 'local', 'setlocal'}) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('noa setglobal scrolloff=8') | ||||
|       command('noa setlocal scrolloff=1') | ||||
|       command('set scrolloff=2') | ||||
|       expected_combination({'scrolloff', 1, 1, 8, 2, 'global', 'set'}) | ||||
|       expected_combination({'scrolloff', 8, 1, 8, 2, 'global', 'set'}) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('noa set scrolloff=8') | ||||
|       command('set scrolloff=2') | ||||
|       expected_combination({'scrolloff', 8, 8, 8, 2, 'global', 'set'}) | ||||
| @@ -490,11 +502,15 @@ describe('au OptionSet', function() | ||||
|       command('setlocal noautoread') | ||||
|       expected_combination({'autoread', true, true, '', false, 'local', 'setlocal'}) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('noa setglobal noautoread') | ||||
|       command('noa setlocal autoread') | ||||
|       command('set autoread') | ||||
|       expected_combination({'autoread', true, true, false, true, 'global', 'set'}) | ||||
|       expected_combination({'autoread', false, true, false, true, 'global', 'set'}) | ||||
|  | ||||
|       -- Note: v:option_old is the old global value for global-local options. | ||||
|       -- but the old local value for all other kinds of options. | ||||
|       command('noa set noautoread') | ||||
|       command('set autoread') | ||||
|       expected_combination({'autoread', false, false, false, true, 'global', 'set'}) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Famiu Haque
					Famiu Haque