aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/option.c
diff options
context:
space:
mode:
authorFamiu Haque <famiuhaque@proton.me>2023-06-07 06:05:16 +0600
committerGitHub <noreply@github.com>2023-06-07 08:05:16 +0800
commitb3d5138fd0066fda26ef7724a542ae45eb42fc84 (patch)
treef9893af238d838408c81c6d4e36655865fe465b0 /src/nvim/option.c
parent0370e4def0c0328f8cd09f02c1ca82ed491ecb9a (diff)
downloadrneovim-b3d5138fd0066fda26ef7724a542ae45eb42fc84.tar.gz
rneovim-b3d5138fd0066fda26ef7724a542ae45eb42fc84.tar.bz2
rneovim-b3d5138fd0066fda26ef7724a542ae45eb42fc84.zip
refactor(options): remove `getoption_T` and introduce `OptVal` (#23850)
Removes the `getoption_T` struct and also introduces the `OptVal` struct to unify the methods of getting/setting different option value types. This is the first of many PRs to reduce code duplication in the Vim option code as well as to make options easier to maintain. It also increases the flexibility and extensibility of options. Which opens the door for things like Array and Dictionary options.
Diffstat (limited to 'src/nvim/option.c')
-rw-r--r--src/nvim/option.c309
1 files changed, 227 insertions, 82 deletions
diff --git a/src/nvim/option.c b/src/nvim/option.c
index e69c017b08..bec0f12d68 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -394,7 +394,7 @@ void set_init_1(bool clean_arg)
// NOTE: mlterm's author is being asked to 'set' a variable
// instead of an environment variable due to inheritance.
if (os_env_exists("MLTERM")) {
- set_option_value_give_err("tbidi", 1L, NULL, 0);
+ set_option_value_give_err("tbidi", BOOLEAN_OPTVAL(true), 0);
}
didset_options2();
@@ -2424,7 +2424,7 @@ static const char *did_set_arabic(optset_T *args)
p_deco = true;
// Force-set the necessary keymap for arabic.
- errmsg = set_option_value("keymap", 0L, "arabic", OPT_LOCAL);
+ errmsg = set_option_value("keymap", STATIC_CSTR_AS_OPTVAL("arabic"), OPT_LOCAL);
} else {
// 'arabic' is reset, handle various sub-settings.
if (!p_tbidi) {
@@ -3339,7 +3339,7 @@ void set_tty_background(const char *value)
? "autocmd VimEnter * ++once ++nested :lua if not vim.api.nvim_get_option_info2('bg', {}).was_set then vim.o.bg = 'light' end"
: "autocmd VimEnter * ++once ++nested :lua if not vim.api.nvim_get_option_info2('bg', {}).was_set then vim.o.bg = 'dark' end");
} else {
- set_option_value_give_err("bg", 0L, value, 0);
+ set_option_value_give_err("bg", CSTR_AS_OPTVAL((char *)value), 0);
reset_option_was_set("bg");
}
}
@@ -3355,32 +3355,153 @@ int findoption(const char *const arg)
return findoption_len(arg, strlen(arg));
}
+void optval_free(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ case kOptValTypeBoolean:
+ case kOptValTypeNumber:
+ break;
+ case kOptValTypeString:
+ api_free_string(o.data.string);
+ break;
+ }
+}
+
+OptVal optval_copy(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ case kOptValTypeBoolean:
+ case kOptValTypeNumber:
+ return o;
+ case kOptValTypeString:
+ return STRING_OPTVAL(copy_string(o.data.string, NULL));
+ default:
+ abort();
+ }
+}
+
+// Match type of OptVal with the type of the target option. Returns true if the types match and
+// false otherwise.
+static bool optval_match_type(OptVal o, int opt_idx)
+{
+ assert(opt_idx >= 0);
+ uint32_t flags = options[opt_idx].flags;
+
+ switch (o.type) {
+ case kOptValTypeNil:
+ return false;
+ case kOptValTypeBoolean:
+ return flags & P_BOOL;
+ case kOptValTypeNumber:
+ return flags & P_NUM;
+ case kOptValTypeString:
+ return flags & P_STRING;
+ default:
+ abort();
+ }
+}
+
+// Return C-string representation of OptVal. Caller must free the returned C-string.
+static char *optval_to_cstr(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ return xstrdup("");
+ case kOptValTypeBoolean:
+ return xstrdup(o.data.boolean ? "true" : "false");
+ case kOptValTypeNumber: {
+ char *buf = xmalloc(NUMBUFLEN);
+ snprintf(buf, NUMBUFLEN, "%" PRId64, o.data.number);
+ return buf;
+ }
+ case kOptValTypeString: {
+ char *buf = xmalloc(o.data.string.size + 3);
+ snprintf(buf, o.data.string.size + 3, "\"%s\"", o.data.string.data);
+ return buf;
+ }
+ default:
+ abort();
+ }
+}
+
+// Get an allocated string containing a list of valid types for an option.
+// For options with a singular type, it returns the name of the type. For options with multiple
+// possible types, it returns a comma separated list of types. For example, if an option can be a
+// number, boolean or string, the function returns "Number, Boolean, String"
+static char *option_get_valid_types(int opt_idx)
+{
+ uint32_t flags = options[opt_idx].flags;
+ uint32_t type_count = 0;
+
+ StringBuilder str = KV_INITIAL_VALUE;
+ kv_resize(str, 32);
+
+#define OPTION_ADD_TYPE(typename) \
+ do { \
+ if (type_count == 0) { \
+ kv_concat(str, typename); \
+ } else { \
+ kv_printf(str, ", %s", typename); \
+ } \
+ type_count++; \
+ } while (0);
+
+ if (flags & P_NUM) {
+ OPTION_ADD_TYPE("Number");
+ }
+ if (flags & P_BOOL) {
+ OPTION_ADD_TYPE("Boolean");
+ }
+ if (flags & P_STRING) {
+ OPTION_ADD_TYPE("String");
+ }
+
+ if (type_count == 0) {
+ abort();
+ }
+
+ // Ensure that the string is NUL-terminated.
+ kv_push(str, NUL);
+ return str.items;
+
+#undef OPTION_ADD_TYPE
+}
+
/// Gets the value for an option.
///
-/// @param stringval NULL when only checking existence
-/// @param flagsp set to the option flags (P_xxxx) (if not NULL)
+/// @param[in] name Option name.
+/// @param[out] flagsp Set to the option flags (P_xxxx) (if not NULL).
+/// @param[in] scope Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination).
+/// @param[out] hidden Whether option is hidden.
///
-/// @returns:
-/// Number option: gov_number, *numval gets value.
-/// Tottle option: gov_bool, *numval gets value.
-/// String option: gov_string, *stringval gets allocated string.
-/// Hidden Number option: gov_hidden_number.
-/// Hidden Toggle option: gov_hidden_bool.
-/// Hidden String option: gov_hidden_string.
-/// Unknown option: gov_unknown.
-getoption_T get_option_value(const char *name, long *numval, char **stringval, uint32_t *flagsp,
- int scope)
+/// @return Option value. Returns NIL_OPTVAL for invalid options. Return value must be freed by
+/// caller.
+OptVal get_option_value(const char *name, uint32_t *flagsp, int scope, bool *hidden)
{
- if (get_tty_option(name, stringval)) {
- return gov_string;
+ // Make sure that hidden and flagsp are never returned uninitialized
+ if (hidden != NULL) {
+ *hidden = false;
+ }
+ if (flagsp != NULL) {
+ *flagsp = 0;
+ }
+
+ char *str;
+ if (get_tty_option(name, &str)) {
+ return CSTR_AS_OPTVAL(str);
}
int opt_idx = findoption(name);
if (opt_idx < 0) { // option not in the table
- return gov_unknown;
+ return NIL_OPTVAL;
}
char *varp = get_varp_scope(&(options[opt_idx]), scope);
+ if (hidden != NULL) {
+ *hidden = varp == NULL;
+ }
if (flagsp != NULL) {
// Return the P_xxxx option flags.
@@ -3388,30 +3509,23 @@ getoption_T get_option_value(const char *name, long *numval, char **stringval, u
}
if (options[opt_idx].flags & P_STRING) {
- if (varp == NULL) { // hidden option
- return gov_hidden_string;
- }
- if (stringval != NULL) {
- *stringval = xstrdup(*(char **)(varp));
- }
- return gov_string;
+ return varp == NULL ? STRING_OPTVAL(STRING_INIT) : CSTR_TO_OPTVAL(*(char **)(varp));
}
- if (varp == NULL) { // hidden option
- return (options[opt_idx].flags & P_NUM) ? gov_hidden_number : gov_hidden_bool;
- }
if (options[opt_idx].flags & P_NUM) {
- *numval = *(long *)varp;
+ return NUMBER_OPTVAL(varp == NULL ? 0 : (*(long *)varp));
} else {
// Special case: 'modified' is b_changed, but we also want to consider
// it set when 'ff' or 'fenc' changed.
- if ((int *)varp == &curbuf->b_changed) {
- *numval = curbufIsChanged();
+ if (varp == NULL) {
+ return BOOLEAN_OPTVAL(false);
+ } else if ((int *)varp == &curbuf->b_changed) {
+ return BOOLEAN_OPTVAL(curbufIsChanged());
} else {
- *numval = (long)(*(int *)varp);
+ int n = *(int *)varp;
+ return BOOLEAN_OPTVAL(n == 0 ? kFalse : (n >= 1 ? kTrue : kNone));
}
}
- return (options[opt_idx].flags & P_NUM) ? gov_number : gov_bool;
}
// Returns the option attributes and its value. Unlike the above function it
@@ -3544,20 +3658,25 @@ vimoption_T *get_option(int opt_idx)
/// Set the value of an option
///
-/// @param[in] name Option name.
-/// @param[in] number New value for the number or boolean option.
-/// @param[in] string New value for string option.
+/// @param[in] name Option name.
+/// @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).
/// If OPT_CLEAR is set, the value of the option
/// is cleared (the exact semantics of this depend
/// on the option).
///
/// @return NULL on success, an untranslated error message on error.
-const char *set_option_value(const char *const name, const long number, const char *const string,
- const int opt_flags)
+const char *set_option_value(const char *const name, const OptVal value, int opt_flags)
FUNC_ATTR_NONNULL_ARG(1)
{
- static char errbuf[80];
+ static const char *optval_type_names[] = {
+ [kOptValTypeNil] = "Nil",
+ [kOptValTypeBoolean] = "Boolean",
+ [kOptValTypeNumber] = "Number",
+ [kOptValTypeString] = "String"
+ };
+
+ static char errbuf[IOSIZE];
if (is_tty_option(name)) {
return NULL; // Fail silently; many old vimrcs set t_xx options.
@@ -3565,23 +3684,14 @@ const char *set_option_value(const char *const name, const long number, const ch
int opt_idx = findoption(name);
if (opt_idx < 0) {
- semsg(_("E355: Unknown option: %s"), name);
- return NULL;
+ snprintf(errbuf, IOSIZE, _(e_unknown_option2), name);
+ return errbuf;
}
uint32_t flags = options[opt_idx].flags;
// Disallow changing some options in the sandbox
if (sandbox > 0 && (flags & P_SECURE)) {
- emsg(_(e_sandbox));
- return NULL;
- }
-
- if (flags & P_STRING) {
- const char *s = string;
- if (s == NULL || opt_flags & OPT_CLEAR) {
- s = "";
- }
- return set_string_option(opt_idx, s, opt_flags, errbuf, sizeof(errbuf));
+ return _(e_sandbox);
}
char *varp = get_varp_scope(&(options[opt_idx]), opt_flags);
@@ -3590,46 +3700,81 @@ const char *set_option_value(const char *const name, const long number, const ch
return NULL;
}
- if (number == 0 && string != NULL) {
- int idx;
-
- // Either we are given a string or we are setting option
- // to zero.
- for (idx = 0; string[idx] == '0'; idx++) {}
- if (string[idx] != NUL || idx == 0) {
- // There's another character after zeros or the string
- // is empty. In both cases, we are trying to set a
- // num option using a string.
- semsg(_("E521: Number required: &%s = '%s'"),
- name, string);
- return NULL; // do nothing as we hit an error
- }
- }
- long numval = number;
- if (opt_flags & OPT_CLEAR) {
- if ((int *)varp == &curbuf->b_p_ar) {
- numval = -1;
- } else if ((long *)varp == &curbuf->b_p_ul) {
- numval = NO_LOCAL_UNDOLEVEL;
- } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) {
- numval = -1;
- } else {
- char *s = NULL;
- (void)get_option_value(name, &numval, &s, NULL, OPT_GLOBAL);
+ const char *errmsg = NULL;
+ // Copy the value so we can modify the copy.
+ OptVal v = optval_copy(value);
+
+ if (v.type == kOptValTypeNil) {
+ opt_flags |= OPT_CLEAR;
+
+ // 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;
+ } else if (flags & P_NUM) {
+ v.type = kOptValTypeNumber;
+ } else if (flags & P_STRING) {
+ v.type = kOptValTypeString;
+ }
+ } 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, _("E5383: Allowed types for option '%s': %s. Got %s value: %s"),
+ name, valid_types, optval_type_names[v.type], rep);
+ xfree(rep);
+ xfree(valid_types);
+ errmsg = errbuf;
+ goto end;
+ }
+
+ switch (v.type) {
+ case kOptValTypeNil:
+ abort(); // This will never happen.
+ case kOptValTypeBoolean: {
+ if (opt_flags & OPT_CLEAR) {
+ if ((int *)varp == &curbuf->b_p_ar) {
+ v.data.boolean = kNone;
+ } else {
+ v = get_option_value(name, NULL, OPT_GLOBAL, NULL);
+ }
}
+ errmsg = set_bool_option(opt_idx, varp, (int)v.data.boolean, opt_flags);
+ break;
+ }
+ case kOptValTypeNumber: {
+ if (opt_flags & OPT_CLEAR) {
+ if ((long *)varp == &curbuf->b_p_ul) {
+ v.data.number = NO_LOCAL_UNDOLEVEL;
+ } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) {
+ v.data.number = -1;
+ } else {
+ v = get_option_value(name, NULL, OPT_GLOBAL, NULL);
+ }
+ }
+ errmsg = set_num_option(opt_idx, varp, (long)v.data.number, errbuf, sizeof(errbuf), opt_flags);
+ break;
+ }
+ case kOptValTypeString: {
+ const char *s = v.data.string.data;
+ if (s == NULL || opt_flags & OPT_CLEAR) {
+ s = "";
+ }
+ errmsg = set_string_option(opt_idx, s, opt_flags, errbuf, sizeof(errbuf));
+ break;
}
- if (flags & P_NUM) {
- return set_num_option(opt_idx, varp, numval, errbuf, sizeof(errbuf), opt_flags);
}
- return set_bool_option(opt_idx, varp, (int)numval, opt_flags);
+
+end:
+ optval_free(v); // Free the copied OptVal.
+ return errmsg;
}
/// Call set_option_value() and when an error is returned report it.
///
/// @param opt_flags OPT_LOCAL or 0 (both)
-void set_option_value_give_err(const char *name, long number, const char *string, int opt_flags)
+void set_option_value_give_err(const char *name, OptVal value, int opt_flags)
{
- const char *errmsg = set_option_value(name, number, string, opt_flags);
+ const char *errmsg = set_option_value(name, value, opt_flags);
if (errmsg != NULL) {
emsg(_(errmsg));