Skip to content

Commit

Permalink
refactor(options)!: use OptVal for option defaults neovim#26691
Browse files Browse the repository at this point in the history
Problem: We use `void *` for option default values, which is confusing and can cause problems with type-correctness. It also doesn't accomodate for multitype options. On top of that, it also leads to default boolean option values not behaving correctly on big endian systems.

Solution: Use `OptVal` for option default values.

BREAKING CHANGE:
- `:set {option}<` removes the local value for all global-local options instead of just string global-local options.
- `:setlocal {option}<` copies the global value to the local value for number and boolean global-local options instead of removing the local value.
  • Loading branch information
famiu authored Oct 25, 2024
1 parent 01739d4 commit b922b7d
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 327 deletions.
4 changes: 4 additions & 0 deletions runtime/doc/news.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ OPTIONS
changes according to related options. It takes care of alignment, 'number',
'relativenumber' and 'signcolumn' set to "number". The now redundant `%r` item
is no longer treated specially for 'statuscolumn'.
• `:set {option}<` removes the local value for all |global-local| options instead
of just string |global-local| options.
• `:setlocal {option}<` copies the global value to the local value for number
and boolean |global-local| options instead of removing the local value.

PLUGINS

Expand Down
12 changes: 3 additions & 9 deletions runtime/doc/options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -306,19 +306,13 @@ created, thus they behave slightly differently:

:se[t] {option}< Set the effective value of {option} to its global
value.
For string |global-local| options, the local value is
removed, so that the global value will be used.
For |global-local| options, the local value is removed,
so that the global value will be used.
For all other options, the global value is copied to
the local value.

:setl[ocal] {option}< Set the effective value of {option} to its global
value.
For number and boolean |global-local| options, the
local value is removed, so that the global value will
be used.
For all other options, including string |global-local|
options, the global value is copied to the local
value.
value by copying the global value to the local value.

Note that the behaviour for |global-local| options is slightly different
between string and number-based options.
Expand Down
6 changes: 2 additions & 4 deletions runtime/doc/vim_diff.txt
Original file line number Diff line number Diff line change
Expand Up @@ -339,10 +339,8 @@ Normal commands:

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.

- `:set {option}<` removes local value for all |global-local| options.
- `:setlocal {option}<` copies global value to local value for all options.
- 'autoread' works in the terminal (if it supports "focus" events)
- 'cpoptions' flags: |cpo-_|
- 'diffopt' "linematch" feature
Expand Down
71 changes: 50 additions & 21 deletions src/nvim/generators/gen_options.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ local function get_flags(o)
return flags
end

--- @param opt_type vim.option_type
--- @return string
local function opt_type_enum(opt_type)
return ('kOptValType%s'):format(lowercase_to_titlecase(opt_type))
end

--- @param o vim.option_meta
--- @return string
local function get_type_flags(o)
Expand All @@ -99,7 +105,7 @@ local function get_type_flags(o)

for _, opt_type in ipairs(opt_types) do
assert(type(opt_type) == 'string')
type_flags = ('%s | (1 << kOptValType%s)'):format(type_flags, lowercase_to_titlecase(opt_type))
type_flags = ('%s | (1 << %s)'):format(type_flags, opt_type_enum(opt_type))
end

return type_flags
Expand All @@ -125,27 +131,48 @@ local function get_cond(c, base_string)
return cond_string
end

--- @param s string
--- @return string
local static_cstr_as_string = function(s)
return ('{ .data = %s, .size = sizeof(%s) - 1 }'):format(s, s)
end

--- @param v vim.option_value|function
--- @return string
local get_opt_val = function(v)
--- @type vim.option_type
local v_type

if type(v) == 'function' then
v, v_type = v() --[[ @as string, vim.option_type ]]

if v_type == 'string' then
v = static_cstr_as_string(v)
end
else
v_type = type(v) --[[ @as vim.option_type ]]

if v_type == 'boolean' then
v = v and 'true' or 'false'
elseif v_type == 'number' then
v = ('%iL'):format(v)
elseif v_type == 'string' then
v = static_cstr_as_string(cstr(v))
end
end

return ('{ .type = %s, .data.%s = %s }'):format(opt_type_enum(v_type), v_type, v)
end

--- @param d vim.option_value|function
--- @param n string
--- @return string

local get_defaults = function(d, n)
if d == nil then
error("option '" .. n .. "' should have a default value")
end

local value_dumpers = {
['function'] = function(v)
return v()
end,
string = function(v)
return '.string=' .. cstr(v)
end,
boolean = function(v)
return '.boolean=' .. (v and 'true' or 'false')
end,
number = function(v)
return ('.number=%iL'):format(v)
end,
}

return value_dumpers[type(d)](d)
return get_opt_val(d)
end

--- @type [string,string][]
Expand Down Expand Up @@ -173,7 +200,7 @@ local function dump_option(i, o)
w(' .var=&' .. o.varname)
elseif o.hidden or o.immutable then
-- Hidden and immutable options can directly point to the default value.
w((' .var=&options[%u].def_val'):format(i - 1))
w((' .var=&options[%u].def_val.data'):format(i - 1))
elseif #o.scope == 1 and o.scope[1] == 'window' then
w(' .var=VAR_WIN')
else
Expand Down Expand Up @@ -219,14 +246,16 @@ local function dump_option(i, o)
if o.defaults.condition then
w(get_cond(o.defaults.condition))
end
w(' .def_val' .. get_defaults(o.defaults.if_true, o.full_name))
w(' .def_val=' .. get_defaults(o.defaults.if_true, o.full_name))
if o.defaults.condition then
if o.defaults.if_false then
w('#else')
w(' .def_val' .. get_defaults(o.defaults.if_false, o.full_name))
w(' .def_val=' .. get_defaults(o.defaults.if_false, o.full_name))
end
w('#endif')
end
else
w(' .def_val=NIL_OPTVAL')
end
w(' },')
end
Expand Down
Loading

0 comments on commit b922b7d

Please sign in to comment.