diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
commit | d5f194ce780c95821a855aca3c19426576d28ae0 (patch) | |
tree | d45f461b19f9118ad2bb1f440a7a08973ad18832 /src/nvim/generators/gen_options.lua | |
parent | c5d770d311841ea5230426cc4c868e8db27300a8 (diff) | |
parent | 44740e561fc93afe3ebecfd3618bda2d2abeafb0 (diff) | |
download | rneovim-rahm.tar.gz rneovim-rahm.tar.bz2 rneovim-rahm.zip |
Diffstat (limited to 'src/nvim/generators/gen_options.lua')
-rw-r--r-- | src/nvim/generators/gen_options.lua | 589 |
1 files changed, 357 insertions, 232 deletions
diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 02f3ac3257..e5dba90925 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -1,143 +1,30 @@ -local options_file = arg[1] -local options_enum_file = arg[2] -local options_map_file = arg[3] - -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 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 - --- @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<string, vim.option_meta[]> -local scope_options = {} -for _, scope in ipairs(valid_scopes) do - scope_options[scope] = {} +--- @param o vim.option_meta +--- @return string +local function get_values_var(o) + return ('opt_%s_values'):format(o.abbreviation or o.full_name) end --- @param s string --- @return string -local lowercase_to_titlecase = function(s) - return s:sub(1, 1):upper() .. s:sub(2) +local function lowercase_to_titlecase(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<string, string> -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) +local function get_scope_option(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() - local redraw_flags = { ui_option = 'kOptFlagUIOption', tabline = 'kOptFlagRedrTabl', @@ -161,28 +48,29 @@ local list_flags = { --- @param o vim.option_meta --- @return string local function get_flags(o) - --- @type string - local flags = '0' + --- @type string[] + local flags = { '0' } --- @param f string - local add_flag = function(f) - flags = flags .. '|' .. f + local function add_flag(f) + table.insert(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 + + for _, r_flag in ipairs(o.redraw or {}) do + add_flag(redraw_flags[r_flag]) 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' }, @@ -197,13 +85,14 @@ local function get_flags(o) { 'modelineexpr', 'MLE' }, { 'func' }, }) do - local key_name = flag_desc[1] - local def_name = 'kOptFlag' .. (flag_desc[2] or lowercase_to_titlecase(key_name)) + local key_name, flag_suffix = flag_desc[1], flag_desc[2] if o[key_name] then + local def_name = 'kOptFlag' .. (flag_suffix or lowercase_to_titlecase(key_name)) add_flag(def_name) end end - return flags + + return table.concat(flags, '|') end --- @param opt_type vim.option_type @@ -220,21 +109,6 @@ 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' @@ -262,35 +136,15 @@ local function get_scope_idx(o) 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) +local function static_cstr_as_string(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) +local function get_opt_val(v) --- @type vim.option_type local v_type @@ -308,6 +162,7 @@ local get_opt_val = function(v) elseif v_type == 'number' then v = ('%iL'):format(v) elseif v_type == 'string' then + --- @cast v string v = static_cstr_as_string(cstr(v)) end end @@ -318,7 +173,7 @@ end --- @param d vim.option_value|function --- @param n string --- @return string -local get_defaults = function(d, n) +local function get_defaults(d, n) if d == nil then error("option '" .. n .. "' should have a default value") end @@ -327,84 +182,354 @@ 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)) +--- @param write fun(...: string) +local function dump_option(i, o, write) + write(' [', ('%u'):format(i - 1) .. ']={') + write(' .fullname=', cstr(o.full_name)) if o.abbreviation then - w(' .shortname=' .. cstr(o.abbreviation)) + write(' .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)) + write(' .type=', opt_type_enum(o.type)) + write(' .flags=', get_flags(o)) + write(' .scope_flags=', get_scope_flags(o)) + write(' .scope_idx=', get_scope_idx(o)) + write(' .values=', (o.values and get_values_var(o) or 'NULL')) + write(' .values_len=', (o.values and #o.values or '0')) + write(' .flags_var=', (o.flags_varname and ('&%s'):format(o.flags_varname) or 'NULL')) if o.enable_if then - w(get_cond(o.enable_if)) + write(('#if defined(%s)'):format(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 + if is_window_local then + write(' .var=NULL') + elseif o.varname then + write(' .var=&', o.varname) + elseif o.immutable then + -- Immutable options can directly point to the default value. + write((' .var=&options[%u].def_val.data'):format(i - 1)) 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) + error('Option must be immutable or have a variable.') end + + write(' .immutable=', (o.immutable and 'true' or 'false')) + write(' .opt_did_set_cb=', o.cb or 'NULL') + write(' .opt_expand_cb=', o.expand_cb or 'NULL') + if o.enable_if then - w('#else') + write('#else') -- Hidden option directly points to default value. - w((' .var=&options[%u].def_val.data'):format(i - 1)) + write((' .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') + write(' .immutable=true') + write('#endif') end - if o.defaults then - if o.defaults.condition then - w(get_cond(o.defaults.condition)) + + if not o.defaults then + write(' .def_val=NIL_OPTVAL') + elseif o.defaults.condition then + write(('#if defined(%s)'):format(o.defaults.condition)) + write(' .def_val=', get_defaults(o.defaults.if_true, o.full_name)) + if o.defaults.if_false then + write('#else') + write(' .def_val=', get_defaults(o.defaults.if_false, o.full_name)) 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') + write('#endif') + else + write(' .def_val=', get_defaults(o.defaults.if_true, o.full_name)) + end + + write(' },') +end + +--- @param prefix string +--- @param values vim.option_valid_values +local function preorder_traversal(prefix, values) + local out = {} --- @type string[] + + local function add(s) + table.insert(out, s) + end + + add('') + add(('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 + add((' "%s",'):format(value)) + else + assert(type(value) == 'table' and type(value[1]) == 'string' and type(value[2]) == 'table') + add((' "%s",'):format(value[1])) + table.insert(children, value) end + end + + add(' NULL') + add('});') + + for _, value in pairs(children) do + -- Remove trailing colon from the added prefix to prevent syntax errors. + add(preorder_traversal(prefix .. '_' .. value[1]:gsub(':$', ''), value[2])) + end + + return table.concat(out, '\n') +end + +--- @param o vim.option_meta +--- @return string +local function gen_opt_enum(o) + local out = {} --- @type string[] + + local function add(s) + table.insert(out, s) + end + + add('') + add('typedef enum {') + + local opt_name = lowercase_to_titlecase(o.abbreviation or o.full_name) + --- @type table<string,integer> + local enum_values + + if type(o.flags) == 'table' then + enum_values = o.flags --[[ @as table<string,integer> ]] else - w(' .def_val=NIL_OPTVAL') + enum_values = {} + for i, flag_name in ipairs(o.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 + add( + (' kOpt%sFlag%s = 0x%02x,'):format( + opt_name, + lowercase_to_titlecase(flag_name:gsub(':$', '')), + enum_values[flag_name] + ) + ) + end + + add(('} Opt%sFlags;'):format(opt_name)) + + return table.concat(out, '\n') +end + +--- @param output_file string +--- @return table<string,string> options_index Map of option name to option index +local function gen_enums(output_file) + --- Options for each scope. + --- @type table<string, vim.option_meta[]> + local scope_options = {} + for _, scope in ipairs(valid_scopes) do + scope_options[scope] = {} + end + + local fd = assert(io.open(output_file, 'w')) + + --- @param s string + local function write(s) + fd:write(s) + fd:write('\n') + end + + -- Generate options enum file + write('// IWYU pragma: private, include "nvim/option_defs.h"') + write('') + + --- Map of option name to option index + --- @type table<string, string> + local option_index = {} + + -- Generate option index enum and populate the `option_index` and `scope_option` dicts. + write('typedef enum {') + write(' kOptInvalid = -1,') + + for i, o in ipairs(options_meta) do + local enum_val_name = 'kOpt' .. lowercase_to_titlecase(o.full_name) + write((' %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 + + local alias = o.alias or {} --[[@as string[] ]] + for _, v in ipairs(alias) do + option_index[v] = enum_val_name + end + + for _, scope in ipairs(o.scope) do + table.insert(scope_options[scope], o) + end + end + + write(' // Option count') + write('#define kOptCount ' .. tostring(#options_meta)) + write('} OptIndex;') + + -- Generate option index enum for each scope + for _, scope in ipairs(valid_scopes) do + write('') + + local scope_name = lowercase_to_titlecase(scope) + write('typedef enum {') + write((' %s = -1,'):format(get_scope_option(scope, 'Invalid'))) + + for idx, option in ipairs(scope_options[scope]) do + write((' %s = %u,'):format(get_scope_option(scope, option.full_name), idx - 1)) + end + + write((' // %s option count'):format(scope_name)) + write(('#define %s %d'):format(get_scope_option(scope, 'Count'), #scope_options[scope])) + write(('} %sOptIndex;'):format(scope_name)) end - w(' },') + + -- Generate reverse lookup from option scope index to option index for each scope. + for _, scope in ipairs(valid_scopes) do + write('') + write(('EXTERN const OptIndex %s_opt_idx[] INIT( = {'):format(scope)) + for _, option in ipairs(scope_options[scope]) do + local idx = option_index[option.full_name] + write((' [%s] = %s,'):format(get_scope_option(scope, option.full_name), idx)) + end + write('});') + end + + fd:close() + + return option_index 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) +--- @param output_file string +--- @param option_index table<string,string> +local function gen_map(output_file, option_index) + -- 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 + ) + + local fd = assert(io.open(output_file, 'w')) + + --- @param s string + local function write(s) + fd:write(s) + fd:write('\n') + end + + write('static const struct { const char *name; OptIndex opt_idx; } option_hash_elems[] = {') + + for _, name in ipairs(neworder) do + assert(option_index[name] ~= nil) + write((' { .name = "%s", .opt_idx = %s },'):format(name, option_index[name])) + end + + write('};') + write('') + write('static ' .. hashfun) + + fd:close() end -w('};') + +--- @param output_file string +local function gen_vars(output_file) + local fd = assert(io.open(output_file, 'w')) + + --- @param s string + local function write(s) + fd:write(s) + fd:write('\n') + end + + write('// IWYU pragma: private, include "nvim/option_vars.h"') + + -- Generate enums for option flags. + for _, o in ipairs(options_meta) do + if o.flags and (type(o.flags) == 'table' or o.values) then + write(gen_opt_enum(o)) + end + end + + -- Generate valid values for each option. + for _, option in ipairs(options_meta) do + -- Since option values can be nested, we need to do preorder traversal to generate the values. + if option.values then + local values_var = ('opt_%s'):format(option.abbreviation or option.full_name) + write(preorder_traversal(values_var, option.values)) + end + end + + fd:close() +end + +--- @param output_file string +local function gen_options(output_file) + local fd = assert(io.open(output_file, 'w')) + + --- @param ... string + local function write(...) + local s = table.concat({ ... }, '') + fd:write(s) + if s:match('^ %.') then + fd:write(',') + end + fd:write('\n') + end + + -- Generate options[] array. + write([[ + #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_meta) do + dump_option(i, o, write) + end + + write('};') + + fd:close() +end + +local function main() + local options_file = arg[1] + local options_enum_file = arg[2] + local options_map_file = arg[3] + local option_vars_file = arg[4] + + local option_index = gen_enums(options_enum_file) + gen_map(options_map_file, option_index) + gen_vars(option_vars_file) + gen_options(options_file) +end + +main() |