Files
neovim/src/nvim/generators/gen_options.lua
Famiu Haque 6346987601 refactor(options): reduce findoption() usage
Problem: Many places in the code use `findoption()` to access an option using its name, even if the option index is available. This is very slow because it requires looping through the options array over and over.

Solution: Use option index instead of name wherever possible. Also introduce an `OptIndex` enum which contains the index for every option as enum constants, this eliminates the need to pass static option names as strings.
2023-12-09 17:54:43 +06:00

250 lines
5.8 KiB
Lua

local options_file = arg[1]
local options_enum_file = arg[2]
local opt_fd = assert(io.open(options_file, 'w'))
local w = function(s)
if s:match('^ %.') then
opt_fd:write(s .. ',\n')
else
opt_fd:write(s .. '\n')
end
end
--- @module 'nvim.options'
local options = require('options')
local cstr = options.cstr
local type_flags = {
bool = 'P_BOOL',
number = 'P_NUM',
string = 'P_STRING',
}
local redraw_flags = {
ui_option = 'P_UI_OPTION',
tabline = 'P_RTABL',
statuslines = 'P_RSTAT',
current_window = 'P_RWIN',
current_window_only = 'P_RWINONLY',
current_buffer = 'P_RBUF',
all_windows = 'P_RALL',
curswant = 'P_CURSWANT',
}
local list_flags = {
comma = 'P_COMMA',
onecomma = 'P_ONECOMMA',
commacolon = 'P_COMMA|P_COLON',
onecommacolon = 'P_ONECOMMA|P_COLON',
flags = 'P_FLAGLIST',
flagscomma = 'P_COMMA|P_FLAGLIST',
}
--- @param s string
--- @return string
local title_case = function(s)
return s:sub(1, 1):upper() .. s:sub(2):lower()
end
--- @param o vim.option_meta
--- @return string
local function get_flags(o)
--- @type string[]
local ret = { type_flags[o.type] }
local add_flag = function(f)
ret[1] = ret[1] .. '|' .. f
end
if o.list then
add_flag(list_flags[o.list])
end
if o.redraw then
for _, r_flag in ipairs(o.redraw) do
add_flag(redraw_flags[r_flag])
end
end
if o.expand then
add_flag('P_EXPAND')
if o.expand == 'nodefault' then
add_flag('P_NO_DEF_EXP')
end
end
for _, flag_desc in ipairs({
{ 'alloced' },
{ 'nodefault' },
{ 'no_mkrc' },
{ 'secure' },
{ 'gettext' },
{ 'noglob' },
{ 'normal_fname_chars', 'P_NFNAME' },
{ 'normal_dname_chars', 'P_NDNAME' },
{ 'pri_mkrc' },
{ 'deny_in_modelines', 'P_NO_ML' },
{ 'deny_duplicates', 'P_NODUP' },
{ 'modelineexpr', 'P_MLE' },
{ 'func' },
}) do
local key_name = flag_desc[1]
local def_name = flag_desc[2] or ('P_' .. key_name:upper())
if o[key_name] then
add_flag(def_name)
end
end
return ret[1]
end
--- @param c string|string[]
--- @param base_string? string
--- @return string
local function get_cond(c, base_string)
local cond_string = base_string or '#if '
if type(c) == 'table' then
cond_string = cond_string .. get_cond(c[1], '')
for i, subc in ipairs(c) do
if i > 1 then
cond_string = cond_string .. ' && ' .. get_cond(subc, '')
end
end
elseif c:sub(1, 1) == '!' then
cond_string = cond_string .. '!defined(' .. c:sub(2) .. ')'
else
cond_string = cond_string .. 'defined(' .. c .. ')'
end
return cond_string
end
local value_dumpers = {
['function'] = function(v)
return v()
end,
string = cstr,
boolean = function(v)
return v and 'true' or 'false'
end,
number = function(v)
return ('%iL'):format(v)
end,
['nil'] = function(_)
return '0'
end,
}
local get_value = function(v)
return '(void *) ' .. value_dumpers[type(v)](v)
end
local get_defaults = function(d, n)
if d == nil then
error("option '" .. n .. "' should have a default value")
end
return get_value(d)
end
--- @type {[1]:string,[2]:string}[]
local defines = {}
--- @param i integer
--- @param o vim.option_meta
local function dump_option(i, o)
w(' [' .. ('%u'):format(i - 1) .. ']={')
w(' .fullname=' .. cstr(o.full_name))
if o.abbreviation then
w(' .shortname=' .. cstr(o.abbreviation))
end
w(' .flags=' .. get_flags(o))
if o.enable_if then
w(get_cond(o.enable_if))
end
if o.varname then
w(' .var=&' .. o.varname)
-- Immutable options should directly point to the default value
elseif o.immutable then
w((' .var=&options[%u].def_val'):format(i - 1))
elseif #o.scope == 1 and o.scope[1] == 'window' then
w(' .var=VAR_WIN')
end
w(' .immutable=' .. (o.immutable and 'true' or 'false'))
if #o.scope == 1 and o.scope[1] == 'global' then
w(' .indir=PV_NONE')
else
assert(#o.scope == 1 or #o.scope == 2)
assert(#o.scope == 1 or o.scope[1] == 'global')
local min_scope = o.scope[#o.scope]
local varname = o.pv_name or o.varname or ('p_' .. (o.abbreviation or o.full_name))
local pv_name = (
'OPT_'
.. min_scope:sub(1, 3):upper()
.. '('
.. (min_scope:sub(1, 1):upper() .. 'V_' .. varname:sub(3):upper())
.. ')'
)
if #o.scope == 2 then
pv_name = 'OPT_BOTH(' .. pv_name .. ')'
end
table.insert(defines, { 'PV_' .. varname:sub(3):upper(), pv_name })
w(' .indir=' .. pv_name)
end
if o.cb then
w(' .opt_did_set_cb=' .. o.cb)
end
if o.expand_cb then
w(' .opt_expand_cb=' .. o.expand_cb)
end
if o.enable_if then
w('#else')
w(' .var=NULL')
w(' .indir=PV_NONE')
w('#endif')
end
if o.defaults then
if o.defaults.condition then
w(get_cond(o.defaults.condition))
end
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))
end
w('#endif')
end
end
w(' },')
end
w([[
#include "nvim/ex_getln.h"
#include "nvim/insexpand.h"
#include "nvim/mapping.h"
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/optionstr.h"
#include "nvim/quickfix.h"
#include "nvim/runtime.h"
#include "nvim/tag.h"
#include "nvim/window.h"
static vimoption_T options[] = {]])
for i, o in ipairs(options.options) do
dump_option(i, o)
end
w(' [' .. ('%u'):format(#options.options) .. ']={.fullname=NULL}')
w('};')
w('')
for _, v in ipairs(defines) do
w('#define ' .. v[1] .. ' ' .. v[2])
end
-- Generate options enum file
opt_fd = assert(io.open(options_enum_file, 'w'))
w('typedef enum {')
for i, o in ipairs(options.options) do
w((' kOpt%s = %u,'):format(title_case(o.full_name), i - 1))
end
w('} OptIndex;')
opt_fd:close()