local options_file = arg[1] local options_enum_file = arg[2] local options_map_file = arg[3] local option_vars_file = arg[4] local opt_fd = assert(io.open(options_file, 'w')) local opt_enum_fd = assert(io.open(options_enum_file, 'w')) local opt_map_fd = assert(io.open(options_map_file, 'w')) local opt_vars_fd = assert(io.open(option_vars_file, 'w')) local w = function(s) if s:match('^ %.') then opt_fd:write(s .. ',\n') else opt_fd:write(s .. '\n') end end --- @param s string local function enum_w(s) opt_enum_fd:write(s .. '\n') end --- @param s string local function map_w(s) opt_map_fd:write(s .. '\n') end local function vars_w(s) opt_vars_fd:write(s .. '\n') end --- @module 'nvim.options' local options = require('options') local options_meta = options.options local cstr = options.cstr local valid_scopes = options.valid_scopes --- Options for each scope. --- @type table local scope_options = {} for _, scope in ipairs(valid_scopes) do scope_options[scope] = {} end --- @param s string --- @return string local lowercase_to_titlecase = function(s) return table.concat(vim.tbl_map(function(word) --- @param word string return word:sub(1, 1):upper() .. word:sub(2) end, vim.split(s, '[-_]'))) end -- Generate options enum file enum_w('// IWYU pragma: private, include "nvim/option_defs.h"') enum_w('') --- Map of option name to option index --- @type table local option_index = {} -- Generate option index enum and populate the `option_index` and `scope_option` dicts. enum_w('typedef enum {') enum_w(' kOptInvalid = -1,') for i, o in ipairs(options_meta) do local enum_val_name = 'kOpt' .. lowercase_to_titlecase(o.full_name) enum_w((' %s = %u,'):format(enum_val_name, i - 1)) option_index[o.full_name] = enum_val_name if o.abbreviation then option_index[o.abbreviation] = enum_val_name end if o.alias then o.alias = type(o.alias) == 'string' and { o.alias } or o.alias for _, v in ipairs(o.alias) do option_index[v] = enum_val_name end end for _, scope in ipairs(o.scope) do table.insert(scope_options[scope], o) end end enum_w(' // Option count') enum_w('#define kOptCount ' .. tostring(#options_meta)) enum_w('} OptIndex;') --- @param scope string --- @param option_name string --- @return string local get_scope_option = function(scope, option_name) return ('k%sOpt%s'):format(lowercase_to_titlecase(scope), lowercase_to_titlecase(option_name)) end -- Generate option index enum for each scope for _, scope in ipairs(valid_scopes) do enum_w('') local scope_name = lowercase_to_titlecase(scope) enum_w('typedef enum {') enum_w((' %s = -1,'):format(get_scope_option(scope, 'Invalid'))) for idx, option in ipairs(scope_options[scope]) do enum_w((' %s = %u,'):format(get_scope_option(scope, option.full_name), idx - 1)) end enum_w((' // %s option count'):format(scope_name)) enum_w(('#define %s %d'):format(get_scope_option(scope, 'Count'), #scope_options[scope])) enum_w(('} %sOptIndex;'):format(scope_name)) end -- Generate reverse lookup from option scope index to option index for each scope. for _, scope in ipairs(valid_scopes) do enum_w('') enum_w(('EXTERN const OptIndex %s_opt_idx[] INIT( = {'):format(scope)) for _, option in ipairs(scope_options[scope]) do local idx = option_index[option.full_name] enum_w((' [%s] = %s,'):format(get_scope_option(scope, option.full_name), idx)) end enum_w('});') end opt_enum_fd:close() -- Generate option index map. local hashy = require('generators.hashy') local neworder, hashfun = hashy.hashy_hash('find_option', vim.tbl_keys(option_index), function(idx) return ('option_hash_elems[%s].name'):format(idx) end) map_w('static const struct { const char *name; OptIndex opt_idx; } option_hash_elems[] = {') for _, name in ipairs(neworder) do assert(option_index[name] ~= nil) map_w((' { .name = "%s", .opt_idx = %s },'):format(name, option_index[name])) end map_w('};\n') map_w('static ' .. hashfun) opt_map_fd:close() vars_w('// IWYU pragma: private, include "nvim/option_vars.h"') -- Generate enums for option flags. for _, option in ipairs(options_meta) do if option.flags and (type(option.flags) == 'table' or option.values) then vars_w('') vars_w('typedef enum {') local opt_name = lowercase_to_titlecase(option.abbreviation or option.full_name) --- @type table local enum_values if type(option.flags) == 'table' then enum_values = option.flags --[[ @as table ]] else enum_values = {} for i, flag_name in ipairs(option.values) do assert(type(flag_name) == 'string') enum_values[flag_name] = math.pow(2, i - 1) end end -- Sort the keys by the flag value so that the enum can be generated in order. --- @type string[] local flag_names = vim.tbl_keys(enum_values) table.sort(flag_names, function(a, b) return enum_values[a] < enum_values[b] end) for _, flag_name in pairs(flag_names) do vars_w( (' kOpt%sFlag%s = 0x%02x,'):format( opt_name, lowercase_to_titlecase(flag_name:gsub(':$', '')), enum_values[flag_name] ) ) end vars_w(('} Opt%sFlags;'):format(opt_name)) end end -- Generate valid values for each option. for _, option in ipairs(options_meta) do --- @type function local preorder_traversal --- @param prefix string --- @param values vim.option_valid_values preorder_traversal = function(prefix, values) vars_w('') vars_w( ('EXTERN const char *(%s_values[%s]) INIT( = {'):format(prefix, #vim.tbl_keys(values) + 1) ) --- @type [string,vim.option_valid_values][] local children = {} for _, value in ipairs(values) do if type(value) == 'string' then vars_w((' "%s",'):format(value)) else assert(type(value) == 'table' and type(value[1]) == 'string' and type(value[2]) == 'table') vars_w((' "%s",'):format(value[1])) table.insert(children, value) end end vars_w(' NULL') vars_w('});') for _, value in pairs(children) do -- Remove trailing colon from the added prefix to prevent syntax errors. preorder_traversal(prefix .. '_' .. value[1]:gsub(':$', ''), value[2]) end end -- Since option values can be nested, we need to do preorder traversal to generate the values. if option.values then preorder_traversal(('opt_%s'):format(option.abbreviation or option.full_name), option.values) end end opt_vars_fd:close() local redraw_flags = { ui_option = 'kOptFlagUIOption', tabline = 'kOptFlagRedrTabl', statuslines = 'kOptFlagRedrStat', current_window = 'kOptFlagRedrWin', current_buffer = 'kOptFlagRedrBuf', all_windows = 'kOptFlagRedrAll', curswant = 'kOptFlagCurswant', highlight_only = 'kOptFlagHLOnly', } local list_flags = { comma = 'kOptFlagComma', onecomma = 'kOptFlagOneComma', commacolon = 'kOptFlagComma|kOptFlagColon', onecommacolon = 'kOptFlagOneComma|kOptFlagColon', flags = 'kOptFlagFlagList', flagscomma = 'kOptFlagComma|kOptFlagFlagList', } --- @param o vim.option_meta --- @return string local function get_flags(o) --- @type string local flags = '0' --- @param f string local add_flag = function(f) flags = flags .. '|' .. 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('kOptFlagExpand') if o.expand == 'nodefault' then add_flag('kOptFlagNoDefExp') end end for _, flag_desc in ipairs({ { 'nodefault', 'NoDefault' }, { 'no_mkrc', 'NoMkrc' }, { 'secure' }, { 'gettext' }, { 'noglob', 'NoGlob' }, { 'normal_fname_chars', 'NFname' }, { 'normal_dname_chars', 'NDname' }, { 'pri_mkrc', 'PriMkrc' }, { 'deny_in_modelines', 'NoML' }, { 'deny_duplicates', 'NoDup' }, { 'modelineexpr', 'MLE' }, { 'func' }, }) do local key_name = flag_desc[1] local def_name = 'kOptFlag' .. (flag_desc[2] or lowercase_to_titlecase(key_name)) if o[key_name] then add_flag(def_name) end end 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 scope vim.option_scope --- @return string local function opt_scope_enum(scope) return ('kOptScope%s'):format(lowercase_to_titlecase(scope)) end --- @param o vim.option_meta --- @return string local function get_type_flags(o) local opt_types = (type(o.type) == 'table') and o.type or { o.type } local type_flags = '0' assert(type(opt_types) == 'table') for _, opt_type in ipairs(opt_types) do assert(type(opt_type) == 'string') type_flags = ('%s | (1 << %s)'):format(type_flags, opt_type_enum(opt_type)) end return type_flags end --- @param o vim.option_meta --- @return string local function get_scope_flags(o) local scope_flags = '0' for _, scope in ipairs(o.scope) do scope_flags = ('%s | (1 << %s)'):format(scope_flags, opt_scope_enum(scope)) end return scope_flags end --- @param o vim.option_meta --- @return string local function get_scope_idx(o) --- @type string[] local strs = {} for _, scope in pairs(valid_scopes) do local has_scope = vim.tbl_contains(o.scope, scope) strs[#strs + 1] = (' [%s] = %s'):format( opt_scope_enum(scope), get_scope_option(scope, has_scope and o.full_name or 'Invalid') ) end return ('{\n%s\n }'):format(table.concat(strs, ',\n')) 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 --- @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 return get_opt_val(d) end --- @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)) w(' .type_flags=' .. get_type_flags(o)) w(' .scope_flags=' .. get_scope_flags(o)) w(' .scope_idx=' .. get_scope_idx(o)) if o.enable_if then w(get_cond(o.enable_if)) end local is_window_local = #o.scope == 1 and o.scope[1] == 'win' if not is_window_local then if o.varname then w(' .var=&' .. o.varname) elseif o.immutable then -- Immutable options can directly point to the default value. w((' .var=&options[%u].def_val.data'):format(i - 1)) else -- Option must be immutable or have a variable. assert(false) end else w(' .var=NULL') end w(' .immutable=' .. (o.immutable and 'true' or 'false')) 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') -- Hidden option directly points to default value. w((' .var=&options[%u].def_val.data'):format(i - 1)) -- Option is always immutable on the false branch of `enable_if`. w(' .immutable=true') 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 else w(' .def_val=NIL_OPTVAL') end w(' },') end -- Generate options[] array. w([[ #include "nvim/ex_docmd.h" #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('};')