From 8516c2dc1f301c439695629fff771227dbe00d30 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Sat, 23 Nov 2024 14:22:06 +0600 Subject: refactor(options): autogenerate valid values and flag enums for options (#31089) Problem: Option metadata like list of valid values for an option and option flags are not listed in the `options.lua` file and are instead manually defined in C, which means option metadata is split between several places. Solution: Put metadata such as list of valid values for an option and option flags in `options.lua`, and autogenerate the corresponding C variables and enums. Supersedes #28659 Co-authored-by: glepnir --- src/nvim/generators/gen_options.lua | 92 +++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) (limited to 'src/nvim/generators') diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 02f3ac3257..779b31e7a0 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -1,10 +1,12 @@ 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 @@ -24,6 +26,10 @@ 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 @@ -138,6 +144,92 @@ 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), + 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', -- cgit From 8d55cc218cfed54136677398ca76c45987b15f29 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 24 Nov 2024 10:40:56 +0000 Subject: feat(keysets): teach Union and LuaRefOf --- src/nvim/generators/c_grammar.lua | 36 +++++++++++++++++++++++++++++--- src/nvim/generators/gen_api_dispatch.lua | 10 ++++++--- 2 files changed, 40 insertions(+), 6 deletions(-) (limited to 'src/nvim/generators') diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index ed6e30ea10..a0a9c86789 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -3,7 +3,7 @@ local lpeg = vim.lpeg -- lpeg grammar for building api metadata from a set of header files. It -- ignores comments and preprocessor commands and parses a very small subset -- of C prototypes with a limited set of types -local P, R, S = lpeg.P, lpeg.R, lpeg.S +local P, R, S, V = lpeg.P, lpeg.R, lpeg.S, lpeg.V local C, Ct, Cc, Cg = lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.Cg --- @param pat vim.lpeg.Pattern @@ -35,9 +35,39 @@ local cdoc_comment = P('///') * opt(Ct(Cg(rep(space) * rep(not_nl), 'comment'))) local c_preproc = P('#') * rep(not_nl) local dllexport = P('DLLEXPORT') * rep1(ws) -local typed_container = ((P('ArrayOf(') + P('DictOf(') + P('Dict(')) * rep1(any - P(')')) * P(')')) +--- @param x vim.lpeg.Pattern +local function comma1(x) + return x * rep(fill * P(',') * fill * x) +end + +-- Define the grammar + +local basic_id = letter * rep(alpha) + +local str = P('"') * rep(any - P('"')) * P('"') + +--- @param x vim.lpeg.Pattern +local function paren(x) + return P('(') * fill * x * fill * P(')') +end + +-- stylua: ignore start +local typed_container = P({ + 'S', + ID = V('S') + basic_id, + S = ( + (P('Union') * paren(comma1(V('ID')))) + + (P('ArrayOf') * paren(basic_id * opt(P(',') * fill * rep1(num)))) + + (P('DictOf') * paren(basic_id)) + + (P('LuaRefOf') * paren( + paren(comma1((V('ID') + str) * rep1(ws) * opt(P('*')) * basic_id)) + * opt(P(',') * fill * opt(P('*')) * V('ID')) + )) + + (P('Dict') * paren(basic_id))), +}) +-- stylua: ignore end -local c_id = (typed_container + (letter * rep(alpha))) +local c_id = typed_container + basic_id local c_void = P('void') local c_param_type = ( diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index a78f746fee..402382acd2 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -347,12 +347,16 @@ for _, k in ipairs(keysets) do local function typename(type) if type == 'HLGroupID' then return 'kObjectTypeInteger' + elseif not type or vim.startswith(type, 'Union') then + return 'kObjectTypeNil' + elseif vim.startswith(type, 'LuaRefOf') then + return 'kObjectTypeLuaRef' elseif type == 'StringArray' then return 'kUnpackTypeStringArray' - elseif type ~= nil then - return 'kObjectType' .. type + elseif vim.startswith(type, 'ArrayOf') then + return 'kObjectTypeArray' else - return 'kObjectTypeNil' + return 'kObjectType' .. type end end -- cgit From 3056115785a435f89d11551e1c8bb4c89c90f610 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 26 Nov 2024 12:26:46 +0000 Subject: refactor: gen_declarations.lua Problem: gen_declarations.lua is complex and has duplicate logic with c_grammar.lua Solution: Move all lpeg logic to c_grammar.lua and refactor gen_declarations.lua. --- src/nvim/generators/c_grammar.lua | 308 +++++++++++++++----- src/nvim/generators/gen_api_ui_events.lua | 2 +- src/nvim/generators/gen_declarations.lua | 468 ++++++++++-------------------- 3 files changed, 385 insertions(+), 393 deletions(-) (limited to 'src/nvim/generators') diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index a0a9c86789..9b1c284c1e 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -1,10 +1,37 @@ -local lpeg = vim.lpeg - -- lpeg grammar for building api metadata from a set of header files. It -- ignores comments and preprocessor commands and parses a very small subset -- of C prototypes with a limited set of types + +--- @class nvim.c_grammar.Proto +--- @field [1] 'proto' +--- @field pos integer +--- @field endpos integer +--- @field fast boolean +--- @field name string +--- @field return_type string +--- @field parameters [string, string][] +--- @field static true? +--- @field inline true? + +--- @class nvim.c_grammar.Preproc +--- @field [1] 'preproc' +--- @field content string + +--- @class nvim.c_grammar.Empty +--- @field [1] 'empty' + +--- @alias nvim.c_grammar.result +--- | nvim.c_grammar.Proto +--- | nvim.c_grammar.Preproc +--- | nvim.c_grammar.Empty + +--- @class nvim.c_grammar +--- @field match fun(self, input: string): nvim.c_grammar.result[] + +local lpeg = vim.lpeg + local P, R, S, V = lpeg.P, lpeg.R, lpeg.S, lpeg.V -local C, Ct, Cc, Cg = lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.Cg +local C, Ct, Cc, Cg, Cp = lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.Cg, lpeg.Cp --- @param pat vim.lpeg.Pattern local function rep(pat) @@ -21,125 +48,248 @@ local function opt(pat) return pat ^ -1 end -local any = P(1) -- (consume one character) +local any = P(1) local letter = R('az', 'AZ') + S('_$') local num = R('09') local alpha = letter + num local nl = P('\r\n') + P('\n') -local not_nl = any - nl local space = S(' \t') +local str = P('"') * rep((P('\\') * any) + (1 - P('"'))) * P('"') +local char = P("'") * (any - P("'")) * P("'") local ws = space + nl -local fill = rep(ws) -local c_comment = P('//') * rep(not_nl) -local cdoc_comment = P('///') * opt(Ct(Cg(rep(space) * rep(not_nl), 'comment'))) -local c_preproc = P('#') * rep(not_nl) -local dllexport = P('DLLEXPORT') * rep1(ws) +local wb = #-alpha -- word boundary +local id = letter * rep(alpha) + +local comment_inline = P('/*') * rep(1 - P('*/')) * P('*/') +local comment = P('//') * rep(1 - nl) * nl +local preproc = Ct(Cc('preproc') * P('#') * Cg(rep(1 - nl) * nl, 'content')) + +local fill = rep(ws + comment_inline + comment + preproc) + +--- @param s string +--- @return vim.lpeg.Pattern +local function word(s) + return fill * P(s) * wb * fill +end --- @param x vim.lpeg.Pattern local function comma1(x) return x * rep(fill * P(',') * fill * x) end --- Define the grammar - -local basic_id = letter * rep(alpha) - -local str = P('"') * rep(any - P('"')) * P('"') +--- @param v string +local function Pf(v) + return fill * P(v) * fill +end --- @param x vim.lpeg.Pattern local function paren(x) return P('(') * fill * x * fill * P(')') end +local cdoc_comment = P('///') * opt(Ct(Cg(rep(space) * rep(1 - nl), 'comment'))) + +local braces = P({ + 'S', + A = comment_inline + comment + preproc + str + char + (any - S('{}')), + S = P('{') * rep(V('A')) * rep(V('S') + V('A')) * P('}'), +}) + -- stylua: ignore start local typed_container = P({ 'S', - ID = V('S') + basic_id, S = ( (P('Union') * paren(comma1(V('ID')))) - + (P('ArrayOf') * paren(basic_id * opt(P(',') * fill * rep1(num)))) - + (P('DictOf') * paren(basic_id)) + + (P('ArrayOf') * paren(id * opt(P(',') * fill * rep1(num)))) + + (P('DictOf') * paren(id)) + (P('LuaRefOf') * paren( - paren(comma1((V('ID') + str) * rep1(ws) * opt(P('*')) * basic_id)) + paren(comma1((V('ID') + str) * rep1(ws) * opt(P('*')) * id)) * opt(P(',') * fill * opt(P('*')) * V('ID')) )) - + (P('Dict') * paren(basic_id))), + + (P('Dict') * paren(id))), + ID = V('S') + id, }) -- stylua: ignore end -local c_id = typed_container + basic_id -local c_void = P('void') +local ptr_mod = word('restrict') + word('__restrict') + word('const') +local opt_ptr = rep(Pf('*') * opt(ptr_mod)) + +--- @param name string +--- @param var string +--- @return vim.lpeg.Pattern +local function attr(name, var) + return Cg((P(name) * Cc(true)), var) +end + +--- @param name string +--- @param var string +--- @return vim.lpeg.Pattern +local function attr_num(name, var) + return Cg((P(name) * paren(C(rep1(num)))), var) +end + +local fattr = ( + attr_num('FUNC_API_SINCE', 'since') + + attr_num('FUNC_API_DEPRECATED_SINCE', 'deprecated_since') + + attr('FUNC_API_FAST', 'fast') + + attr('FUNC_API_RET_ALLOC', 'ret_alloc') + + attr('FUNC_API_NOEXPORT', 'noexport') + + attr('FUNC_API_REMOTE_ONLY', 'remote_only') + + attr('FUNC_API_LUA_ONLY', 'lua_only') + + attr('FUNC_API_TEXTLOCK_ALLOW_CMDWIN', 'textlock_allow_cmdwin') + + attr('FUNC_API_TEXTLOCK', 'textlock') + + attr('FUNC_API_REMOTE_IMPL', 'remote_impl') + + attr('FUNC_API_COMPOSITOR_IMPL', 'compositor_impl') + + attr('FUNC_API_CLIENT_IMPL', 'client_impl') + + attr('FUNC_API_CLIENT_IGNORE', 'client_ignore') + + (P('FUNC_') * rep(alpha) * opt(fill * paren(rep(1 - P(')') * any)))) +) + +local void = P('void') * wb -local c_param_type = ( - ((P('Error') * fill * P('*') * fill) * Cc('error')) - + ((P('Arena') * fill * P('*') * fill) * Cc('arena')) - + ((P('lua_State') * fill * P('*') * fill) * Cc('lstate')) - + C(opt(P('const ')) * c_id * rep1(ws) * rep1(P('*'))) - + (C(c_id) * rep1(ws)) +local api_param_type = ( + (word('Error') * opt_ptr * Cc('error')) + + (word('Arena') * opt_ptr * Cc('arena')) + + (word('lua_State') * opt_ptr * Cc('lstate')) ) -local c_type = (C(c_void) * (ws ^ 1)) + c_param_type -local c_param = Ct(c_param_type * C(c_id)) -local c_param_list = c_param * (fill * (P(',') * fill * c_param) ^ 0) -local c_params = Ct(c_void + c_param_list) +local ctype = C( + opt(word('const')) + * ( + typed_container + -- 'unsigned' is a type modifier, and a type itself + + (word('unsigned char') + word('unsigned')) + + (word('struct') * fill * id) + + id + ) + * opt(word('const')) + * opt_ptr +) -local impl_line = (any - P('}')) * opt(rep(not_nl)) * nl +local return_type = (C(void) * fill) + ctype -local ignore_line = rep1(not_nl) * nl +-- stylua: ignore start +local params = Ct( + (void * #P(')')) + + comma1(Ct( + (api_param_type + ctype) + * fill + * C(id) + * rep(Pf('[') * rep(alpha) * Pf(']')) + * rep(fill * fattr) + )) + * opt(Pf(',') * P('...')) +) +-- stylua: ignore end +local ignore_line = rep1(1 - nl) * nl local empty_line = Ct(Cc('empty') * nl * nl) -local c_proto = Ct( - Cc('proto') - * opt(dllexport) - * opt(Cg(P('static') * fill * Cc(true), 'static')) - * Cg(c_type, 'return_type') - * Cg(c_id, 'name') - * fill - * (P('(') * fill * Cg(c_params, 'parameters') * fill * P(')')) - * Cg(Cc(false), 'fast') - * (fill * Cg((P('FUNC_API_SINCE(') * C(rep1(num))) * P(')'), 'since') ^ -1) - * (fill * Cg((P('FUNC_API_DEPRECATED_SINCE(') * C(rep1(num))) * P(')'), 'deprecated_since') ^ -1) - * (fill * Cg((P('FUNC_API_FAST') * Cc(true)), 'fast') ^ -1) - * (fill * Cg((P('FUNC_API_RET_ALLOC') * Cc(true)), 'ret_alloc') ^ -1) - * (fill * Cg((P('FUNC_API_NOEXPORT') * Cc(true)), 'noexport') ^ -1) - * (fill * Cg((P('FUNC_API_REMOTE_ONLY') * Cc(true)), 'remote_only') ^ -1) - * (fill * Cg((P('FUNC_API_LUA_ONLY') * Cc(true)), 'lua_only') ^ -1) - * (fill * (Cg(P('FUNC_API_TEXTLOCK_ALLOW_CMDWIN') * Cc(true), 'textlock_allow_cmdwin') + Cg( - P('FUNC_API_TEXTLOCK') * Cc(true), - 'textlock' - )) ^ -1) - * (fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) - * (fill * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1) - * (fill * Cg((P('FUNC_API_CLIENT_IMPL') * Cc(true)), 'client_impl') ^ -1) - * (fill * Cg((P('FUNC_API_CLIENT_IGNORE') * Cc(true)), 'client_ignore') ^ -1) - * fill - * (P(';') + (P('{') * nl + (impl_line ^ 0) * P('}'))) +local proto_name = opt_ptr * fill * id + +-- __inline is used in MSVC +local decl_mod = ( + Cg(word('static') * Cc(true), 'static') + + Cg((word('inline') + word('__inline')) * Cc(true), 'inline') ) -local dict_key = P('DictKey(') * Cg(rep1(any - P(')')), 'dict_key') * P(')') -local keyset_field = - Ct(Cg(c_id, 'type') * ws * Cg(c_id, 'name') * fill * (dict_key ^ -1) * fill * P(';') * fill) -local c_keyset = Ct( - P('typedef') - * ws - * P('struct') +local proto = Ct( + Cg(Cp(), 'pos') + * Cc('proto') + * -#P('typedef') + * #alpha + * opt(P('DLLEXPORT') * rep1(ws)) + * rep(decl_mod) + * Cg(return_type, 'return_type') * fill - * P('{') + * Cg(proto_name, 'name') * fill - * Cg(Ct(keyset_field ^ 1), 'fields') - * P('}') - * fill - * P('Dict') + * paren(Cg(params, 'parameters')) + * Cg(Cc(false), 'fast') + * rep(fill * fattr) + * Cg(Cp(), 'endpos') + * (fill * (S(';') + braces)) +) + +local keyset_field = Ct( + Cg(ctype, 'type') * fill - * P('(') - * Cg(c_id, 'keyset_name') + * Cg(id, 'name') * fill - * P(')') - * P(';') + * opt(P('DictKey') * paren(Cg(rep1(1 - P(')')), 'dict_key'))) + * Pf(';') ) -local grammar = Ct( - rep1(empty_line + c_proto + cdoc_comment + c_comment + c_preproc + ws + c_keyset + ignore_line) +local keyset = Ct( + P('typedef') + * word('struct') + * Pf('{') + * Cg(Ct(rep1(keyset_field)), 'fields') + * Pf('}') + * P('Dict') + * paren(Cg(id, 'keyset_name')) + * Pf(';') ) -return { grammar = grammar, typed_container = typed_container } + +local grammar = + Ct(rep1(empty_line + proto + cdoc_comment + comment + preproc + ws + keyset + ignore_line)) + +if arg[1] == '--test' then + for i, t in ipairs({ + 'void multiqueue_put_event(MultiQueue *self, Event event) {} ', + 'void *xmalloc(size_t size) {} ', + { + 'struct tm *os_localtime_r(const time_t *restrict clock,', + ' struct tm *restrict result) FUNC_ATTR_NONNULL_ALL {}', + }, + { + '_Bool', + '# 163 "src/nvim/event/multiqueue.c"', + ' multiqueue_empty(MultiQueue *self)', + '{}', + }, + 'const char *find_option_end(const char *arg, OptIndex *opt_idxp) {}', + 'bool semsg(const char *const fmt, ...) {}', + 'int32_t utf_ptr2CharInfo_impl(uint8_t const *p, uintptr_t const len) {}', + 'void ex_argdedupe(exarg_T *eap FUNC_ATTR_UNUSED) {}', + 'static TermKeySym register_c0(TermKey *tk, TermKeySym sym, unsigned char ctrl, const char *name) {}', + 'unsigned get_bkc_flags(buf_T *buf) {}', + 'char *xstpcpy(char *restrict dst, const char *restrict src) {}', + 'bool try_leave(const TryState *const tstate, Error *const err) {}', + 'void api_set_error(ErrorType errType) {}', + + -- Do not consume leading preproc statements + { + '#line 1 "D:/a/neovim/neovim/src\\nvim/mark.h"', + 'static __inline int mark_global_index(const char name)', + ' FUNC_ATTR_CONST', + '{}', + }, + { + '', + '#line 1 "D:/a/neovim/neovim/src\\nvim/mark.h"', + 'static __inline int mark_global_index(const char name)', + '{}', + }, + { + 'size_t xstrlcpy(char *__restrict dst, const char *__restrict src, size_t dsize)', + ' FUNC_ATTR_NONNULL_ALL', + ' {}', + }, + }) do + if type(t) == 'table' then + t = table.concat(t, '\n') .. '\n' + end + t = t:gsub(' +', ' ') + local r = grammar:match(t) + if not r then + print('Test ' .. i .. ' failed') + print(' |' .. table.concat(vim.split(t, '\n'), '\n |')) + end + end +end + +return { + grammar = grammar --[[@as nvim.c_grammar]], + typed_container = typed_container, +} diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index 30a83330eb..a3bb76cb91 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -98,7 +98,7 @@ local function call_ui_event_method(output, ev) end events = vim.tbl_filter(function(ev) - return ev[1] ~= 'empty' + return ev[1] ~= 'empty' and ev[1] ~= 'preproc' end, events) for i = 1, #events do diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua index 2ec0e9ab68..6e1ea92572 100644 --- a/src/nvim/generators/gen_declarations.lua +++ b/src/nvim/generators/gen_declarations.lua @@ -1,136 +1,105 @@ -local fname = arg[1] -local static_fname = arg[2] -local non_static_fname = arg[3] -local preproc_fname = arg[4] -local static_basename = arg[5] - -local lpeg = vim.lpeg - -local fold = function(func, ...) - local result = nil - for _, v in ipairs({ ... }) do - if result == nil then - result = v - else - result = func(result, v) +local grammar = require('generators.c_grammar').grammar + +--- @param fname string +--- @return string? +local function read_file(fname) + local f = io.open(fname, 'r') + if not f then + return + end + local contents = f:read('*a') + f:close() + return contents +end + +--- @param fname string +--- @param contents string[] +local function write_file(fname, contents) + local contents_s = table.concat(contents, '\n') .. '\n' + local fcontents = read_file(fname) + if fcontents == contents_s then + return + end + local f = assert(io.open(fname, 'w')) + f:write(contents_s) + f:close() +end + +--- @param fname string +--- @param non_static_fname string +--- @return string? non_static +local function add_iwyu_non_static(fname, non_static_fname) + if fname:find('.*/src/nvim/.*%.c$') then + -- Add an IWYU pragma comment if the corresponding .h file exists. + local header_fname = fname:sub(1, -3) .. '.h' + local header_f = io.open(header_fname, 'r') + if header_f then + header_f:close() + return (header_fname:gsub('.*/src/nvim/', 'nvim/')) end + elseif non_static_fname:find('/include/api/private/dispatch_wrappers%.h%.generated%.h$') then + return 'nvim/api/private/dispatch.h' + elseif non_static_fname:find('/include/ui_events_call%.h%.generated%.h$') then + return 'nvim/ui.h' + elseif non_static_fname:find('/include/ui_events_client%.h%.generated%.h$') then + return 'nvim/ui_client.h' + elseif non_static_fname:find('/include/ui_events_remote%.h%.generated%.h$') then + return 'nvim/api/ui.h' end - return result end -local folder = function(func) - return function(...) - return fold(func, ...) +--- @param d string +local function process_decl(d) + -- Comments are really handled by preprocessor, so the following is not + -- needed + d = d:gsub('/%*.-%*/', '') + d = d:gsub('//.-\n', '\n') + d = d:gsub('# .-\n', '') + d = d:gsub('\n', ' ') + d = d:gsub('%s+', ' ') + d = d:gsub(' ?%( ?', '(') + d = d:gsub(' ?, ?', ', ') + d = d:gsub(' ?(%*+) ?', ' %1') + d = d:gsub(' ?(FUNC_ATTR_)', ' %1') + d = d:gsub(' $', '') + d = d:gsub('^ ', '') + return d .. ';' +end + +--- @param fname string +--- @param text string +--- @return string[] static +--- @return string[] non_static +--- @return boolean any_static +local function gen_declarations(fname, text) + local non_static = {} --- @type string[] + local static = {} --- @type string[] + + local neededfile = fname:match('[^/]+$') + local curfile = nil + local any_static = false + for _, node in ipairs(grammar:match(text)) do + if node[1] == 'preproc' then + curfile = node.content:match('^%a* %d+ "[^"]-/?([^"/]+)"') or curfile + elseif node[1] == 'proto' and curfile == neededfile then + local node_text = text:sub(node.pos, node.endpos - 1) + local declaration = process_decl(node_text) + + if node.static then + if not any_static and declaration:find('FUNC_ATTR_') then + any_static = true + end + static[#static + 1] = declaration + else + non_static[#non_static + 1] = 'DLLEXPORT ' .. declaration + end + end end -end -local lit = lpeg.P -local set = function(...) - return lpeg.S(fold(function(a, b) - return a .. b - end, ...)) -end -local any_character = lpeg.P(1) -local rng = function(s, e) - return lpeg.R(s .. e) -end -local concat = folder(function(a, b) - return a * b -end) -local branch = folder(function(a, b) - return a + b -end) -local one_or_more = function(v) - return v ^ 1 -end -local two_or_more = function(v) - return v ^ 2 -end -local any_amount = function(v) - return v ^ 0 -end -local one_or_no = function(v) - return v ^ -1 -end -local look_behind = lpeg.B -local look_ahead = function(v) - return #v -end -local neg_look_ahead = function(v) - return -v -end -local neg_look_behind = function(v) - return -look_behind(v) + return static, non_static, any_static end -local w = branch(rng('a', 'z'), rng('A', 'Z'), lit('_')) -local aw = branch(w, rng('0', '9')) -local s = set(' ', '\n', '\t') -local raw_word = concat(w, any_amount(aw)) -local right_word = concat(raw_word, neg_look_ahead(aw)) -local word = branch( - concat( - branch(lit('ArrayOf('), lit('DictOf('), lit('Dict(')), -- typed container macro - one_or_more(any_character - lit(')')), - lit(')') - ), - concat(neg_look_behind(aw), right_word) -) -local inline_comment = - concat(lit('/*'), any_amount(concat(neg_look_ahead(lit('*/')), any_character)), lit('*/')) -local spaces = any_amount(branch( - s, - -- Comments are really handled by preprocessor, so the following is not needed - inline_comment, - concat(lit('//'), any_amount(concat(neg_look_ahead(lit('\n')), any_character)), lit('\n')), - -- Linemarker inserted by preprocessor - concat(lit('# '), any_amount(concat(neg_look_ahead(lit('\n')), any_character)), lit('\n')) -)) -local typ_part = concat(word, any_amount(concat(spaces, lit('*'))), spaces) - -local typ_id = two_or_more(typ_part) -local arg = typ_id -- argument name is swallowed by typ -local pattern = concat( - any_amount(branch(set(' ', '\t'), inline_comment)), - typ_id, -- return type with function name - spaces, - lit('('), - spaces, - one_or_no(branch( -- function arguments - concat( - arg, -- first argument, does not require comma - any_amount(concat( -- following arguments, start with a comma - spaces, - lit(','), - spaces, - arg, - any_amount(concat(lit('['), spaces, any_amount(aw), spaces, lit(']'))) - )), - one_or_no(concat(spaces, lit(','), spaces, lit('...'))) - ), - lit('void') -- also accepts just void - )), - spaces, - lit(')'), - any_amount(concat( -- optional attributes - spaces, - lit('FUNC_'), - any_amount(aw), - one_or_no(concat( -- attribute argument - spaces, - lit('('), - any_amount(concat(neg_look_ahead(lit(')')), any_character)), - lit(')') - )) - )), - look_ahead(concat( -- definition must be followed by "{" - spaces, - lit('{') - )) -) - -if fname == '--help' then - print([[ +local usage = [[ Usage: gen_declarations.lua definitions.c static.h non-static.h definitions.i @@ -141,204 +110,77 @@ non-static.h. File `definitions.i' should contain an already preprocessed version of definitions.c and it is the only one which is actually parsed, definitions.c is needed only to determine functions from which file out of all functions found in definitions.i are needed and to generate an IWYU comment. - -Additionally uses the following environment variables: - - NVIM_GEN_DECLARATIONS_LINE_NUMBERS: - If set to 1 then all generated declarations receive a comment with file - name and line number after the declaration. This may be useful for - debugging gen_declarations script, but not much beyond that with - configured development environment (i.e. with with clang/etc). - - WARNING: setting this to 1 will cause extensive rebuilds: declarations - generator script will not regenerate non-static.h file if its - contents did not change, but including line numbers will make - contents actually change. - - With contents changed timestamp of the file is regenerated even - when no real changes were made (e.g. a few lines were added to - a function which is not at the bottom of the file). - - With changed timestamp build system will assume that header - changed, triggering rebuilds of all C files which depend on the - "changed" header. -]]) - os.exit() -end - -local preproc_f = io.open(preproc_fname) -local text = preproc_f:read('*all') -preproc_f:close() - -local non_static = [[ -#define DEFINE_FUNC_ATTRIBUTES -#include "nvim/func_attr.h" -#undef DEFINE_FUNC_ATTRIBUTES -#ifndef DLLEXPORT -# ifdef MSWIN -# define DLLEXPORT __declspec(dllexport) -# else -# define DLLEXPORT -# endif -#endif -]] - -local static = [[ -#define DEFINE_FUNC_ATTRIBUTES -#include "nvim/func_attr.h" -#undef DEFINE_FUNC_ATTRIBUTES ]] -local non_static_footer = [[ -#include "nvim/func_attr.h" -]] +local function main() + local fname = arg[1] + local static_fname = arg[2] + local non_static_fname = arg[3] + local preproc_fname = arg[4] + local static_basename = arg[5] -local static_footer = [[ -#define DEFINE_EMPTY_ATTRIBUTES -#include "nvim/func_attr.h" // IWYU pragma: export -]] - -if fname:find('.*/src/nvim/.*%.c$') then - -- Add an IWYU pragma comment if the corresponding .h file exists. - local header_fname = fname:sub(1, -3) .. '.h' - local header_f = io.open(header_fname, 'r') - if header_f ~= nil then - header_f:close() - non_static = ([[ -// IWYU pragma: private, include "%s" -]]):format(header_fname:gsub('.*/src/nvim/', 'nvim/')) .. non_static + if fname == '--help' or #arg < 5 then + print(usage) + os.exit() end -elseif fname:find('.*/src/nvim/.*%.h$') then - static = ([[ -// IWYU pragma: private, include "%s" -]]):format(fname:gsub('.*/src/nvim/', 'nvim/')) .. static -elseif non_static_fname:find('/include/api/private/dispatch_wrappers%.h%.generated%.h$') then - non_static = [[ -// IWYU pragma: private, include "nvim/api/private/dispatch.h" -]] .. non_static -elseif non_static_fname:find('/include/ui_events_call%.h%.generated%.h$') then - non_static = [[ -// IWYU pragma: private, include "nvim/ui.h" -]] .. non_static -elseif non_static_fname:find('/include/ui_events_client%.h%.generated%.h$') then - non_static = [[ -// IWYU pragma: private, include "nvim/ui_client.h" -]] .. non_static -elseif non_static_fname:find('/include/ui_events_remote%.h%.generated%.h$') then - non_static = [[ -// IWYU pragma: private, include "nvim/api/ui.h" -]] .. non_static -end -local filepattern = '^#%a* (%d+) "([^"]-)/?([^"/]+)"' + local text = assert(read_file(preproc_fname)) -local init = 1 -local curfile = nil -local neededfile = fname:match('[^/]+$') -local declline = 0 -local declendpos = 0 -local curdir = nil -local is_needed_file = false -local init_is_nl = true -local any_static = false -while init ~= nil do - if init_is_nl and text:sub(init, init) == '#' then - local line, dir, file = text:match(filepattern, init) - if file ~= nil then - curfile = file - is_needed_file = (curfile == neededfile) - declline = tonumber(line) - 1 - curdir = dir:gsub('.*/src/nvim/', '') - else - declline = declline - 1 - end - elseif init < declendpos then -- luacheck: ignore 542 - -- Skipping over declaration - elseif is_needed_file then - s = init - local e = pattern:match(text, init) - if e ~= nil then - local declaration = text:sub(s, e - 1) - -- Comments are really handled by preprocessor, so the following is not - -- needed - declaration = declaration:gsub('/%*.-%*/', '') - declaration = declaration:gsub('//.-\n', '\n') - - declaration = declaration:gsub('# .-\n', '') + local static_decls, non_static_decls, any_static = gen_declarations(fname, text) - declaration = declaration:gsub('\n', ' ') - declaration = declaration:gsub('%s+', ' ') - declaration = declaration:gsub(' ?%( ?', '(') - -- declaration = declaration:gsub(' ?%) ?', ')') - declaration = declaration:gsub(' ?, ?', ', ') - declaration = declaration:gsub(' ?(%*+) ?', ' %1') - declaration = declaration:gsub(' ?(FUNC_ATTR_)', ' %1') - declaration = declaration:gsub(' $', '') - declaration = declaration:gsub('^ ', '') - declaration = declaration .. ';' - - if os.getenv('NVIM_GEN_DECLARATIONS_LINE_NUMBERS') == '1' then - declaration = declaration .. (' // %s/%s:%u'):format(curdir, curfile, declline) - end - declaration = declaration .. '\n' - if declaration:sub(1, 6) == 'static' then - if declaration:find('FUNC_ATTR_') then - any_static = true - end - static = static .. declaration - else - declaration = 'DLLEXPORT ' .. declaration - non_static = non_static .. declaration - end - declendpos = e - end - end - init = text:find('[\n;}]', init) - if init == nil then - break - end - init_is_nl = text:sub(init, init) == '\n' - init = init + 1 - if init_is_nl and is_needed_file then - declline = declline + 1 + local static = {} --- @type string[] + if fname:find('.*/src/nvim/.*%.h$') then + static[#static + 1] = ('// IWYU pragma: private, include "%s"'):format( + fname:gsub('.*/src/nvim/', 'nvim/') + ) end -end - -non_static = non_static .. non_static_footer -static = static .. static_footer - -local F -F = io.open(static_fname, 'w') -F:write(static) -F:close() - -if any_static then - F = io.open(fname, 'r') - local orig_text = F:read('*a') - local pat = '\n#%s?include%s+"' .. static_basename .. '"\n' - local pat_comment = '\n#%s?include%s+"' .. static_basename .. '"%s*//' - if not string.find(orig_text, pat) and not string.find(orig_text, pat_comment) then - error('fail: missing include for ' .. static_basename .. ' in ' .. fname) + vim.list_extend(static, { + '#define DEFINE_FUNC_ATTRIBUTES', + '#include "nvim/func_attr.h"', + '#undef DEFINE_FUNC_ATTRIBUTES', + }) + vim.list_extend(static, static_decls) + vim.list_extend(static, { + '#define DEFINE_EMPTY_ATTRIBUTES', + '#include "nvim/func_attr.h" // IWYU pragma: export', + '', + }) + + write_file(static_fname, static) + + if any_static then + local orig_text = assert(read_file(fname)) + local pat = '\n#%s?include%s+"' .. static_basename .. '"\n' + local pat_comment = '\n#%s?include%s+"' .. static_basename .. '"%s*//' + if not orig_text:find(pat) and not orig_text:find(pat_comment) then + error(('fail: missing include for %s in %s'):format(static_basename, fname)) + end end - F:close() -end -if non_static_fname == 'SKIP' then - return -- only want static declarations -end - --- Before generating the non-static headers, check if the current file (if --- exists) is different from the new one. If they are the same, we won't touch --- the current version to avoid triggering an unnecessary rebuilds of modules --- that depend on this one -F = io.open(non_static_fname, 'r') -if F ~= nil then - if F:read('*a') == non_static then - os.exit(0) + if non_static_fname ~= 'SKIP' then + local non_static = {} --- @type string[] + local iwyu_non_static = add_iwyu_non_static(fname, non_static_fname) + if iwyu_non_static then + non_static[#non_static + 1] = ('// IWYU pragma: private, include "%s"'):format( + iwyu_non_static + ) + end + vim.list_extend(non_static, { + '#define DEFINE_FUNC_ATTRIBUTES', + '#include "nvim/func_attr.h"', + '#undef DEFINE_FUNC_ATTRIBUTES', + '#ifndef DLLEXPORT', + '# ifdef MSWIN', + '# define DLLEXPORT __declspec(dllexport)', + '# else', + '# define DLLEXPORT', + '# endif', + '#endif', + }) + vim.list_extend(non_static, non_static_decls) + non_static[#non_static + 1] = '#include "nvim/func_attr.h"' + write_file(non_static_fname, non_static) end - F:close() end -F = io.open(non_static_fname, 'w') -F:write(non_static) -F:close() +return main() -- cgit From 716adbcc4563f5b4d1b7bc0301530296c538a33c Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 2 Dec 2024 04:16:44 -0800 Subject: fix(api): deprecate nvim_subscribe, nvim_unsubscribe #30456 Problem: - nvim_subscribe, nvim_unsubscribe were deprecated in aec4938a21a02d279d13a9eb64ef3b7cc592c374 but this wasn't set in the API metadata. - The function annotations ``` FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY FUNC_API_DEPRECATED_SINCE(13) ``` cause this test to fail: ``` RUN T3 api metadata functions are compatible with old metadata or have new level: 3.00 ms ERR test/functional/api/version_spec.lua:135: function vim_subscribe was removed but exists in level 0 which nvim should be compatible with stack traceback: test/functional/api/version_spec.lua:135: in function ``` Solution: - Set the API metadata. - Rearrange the annotations so that FUNC_API_DEPRECATED_SINCE is 2nd: ``` FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) FUNC_API_REMOTE_ONLY ``` --- src/nvim/generators/c_grammar.lua | 5 +++++ src/nvim/generators/gen_api_dispatch.lua | 16 ++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) (limited to 'src/nvim/generators') diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index 9b1c284c1e..890c260843 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -257,6 +257,11 @@ if arg[1] == '--test' then 'char *xstpcpy(char *restrict dst, const char *restrict src) {}', 'bool try_leave(const TryState *const tstate, Error *const err) {}', 'void api_set_error(ErrorType errType) {}', + { + 'void nvim_subscribe(uint64_t channel_id, String event)', + 'FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) FUNC_API_REMOTE_ONLY', + '{}', + }, -- Do not consume leading preproc statements { diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 402382acd2..c987037324 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -1,3 +1,10 @@ +-- Example (manual) invocation: +-- +-- make +-- cp build/nvim_version.lua src/nvim/ +-- cd src/nvim +-- nvim -l generators/gen_api_dispatch.lua "../../build/src/nvim/auto/api/private/dispatch_wrappers.generated.h" "../../build/src/nvim/auto/api/private/api_metadata.generated.h" "../../build/funcs_metadata.mpack" "../../build/src/nvim/auto/lua_api_c_bindings.generated.h" "../../build/src/nvim/auto/keysets_defs.generated.h" "../../build/ui_metadata.mpack" "../../build/cmake.config/auto/versiondef_git.h" "./api/autocmd.h" "./api/buffer.h" "./api/command.h" "./api/deprecated.h" "./api/extmark.h" "./api/keysets_defs.h" "./api/options.h" "./api/tabpage.h" "./api/ui.h" "./api/vim.h" "./api/vimscript.h" "./api/win_config.h" "./api/window.h" "../../build/include/api/autocmd.h.generated.h" "../../build/include/api/buffer.h.generated.h" "../../build/include/api/command.h.generated.h" "../../build/include/api/deprecated.h.generated.h" "../../build/include/api/extmark.h.generated.h" "../../build/include/api/options.h.generated.h" "../../build/include/api/tabpage.h.generated.h" "../../build/include/api/ui.h.generated.h" "../../build/include/api/vim.h.generated.h" "../../build/include/api/vimscript.h.generated.h" "../../build/include/api/win_config.h.generated.h" "../../build/include/api/window.h.generated.h" + local mpack = vim.mpack local hashy = require 'generators.hashy' @@ -8,7 +15,7 @@ assert(#arg >= pre_args) local dispatch_outputf = arg[1] -- output h file with packed metadata (api_metadata.generated.h) local api_metadata_outputf = arg[2] --- output metadata mpack file, for use by other build scripts (api_metadata.mpack) +-- output metadata mpack file, for use by other build scripts (funcs_metadata.mpack) local mpack_outputf = arg[3] local lua_c_bindings_outputf = arg[4] -- lua_api_c_bindings.generated.c local keysets_outputf = arg[5] -- keysets_defs.generated.h @@ -235,7 +242,7 @@ for x in string.gmatch(ui_options_text, '"([a-z][a-z_]+)"') do table.insert(ui_options, x) end -local version = require 'nvim_version' +local version = require 'nvim_version' -- `build/nvim_version.lua` file. local git_version = io.open(git_version_inputf):read '*a' local version_build = string.match(git_version, '#define NVIM_VERSION_BUILD "([^"]+)"') or vim.NIL @@ -266,10 +273,7 @@ fixdict(1 + #version) for _, item in ipairs(version) do -- NB: all items are mandatory. But any error will be less confusing -- with placeholder vim.NIL (than invalid mpack data) - local val = item[2] - if val == nil then - val = vim.NIL - end + local val = item[2] == nil and vim.NIL or item[2] put(item[1], val) end put('build', version_build) -- cgit From ec94c2704f5059794923777ed51412d80bd26b5b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 7 Dec 2024 10:17:36 +0800 Subject: vim-patch:9.1.0908: not possible to configure :messages (#31492) Problem: not possible to configure :messages Solution: add the 'messagesopt' option (Shougo Matsushita) closes: vim/vim#16068 https://github.com/vim/vim/commit/51d4d84d6a7159c6ce9e04b36f8edc105ca3794b Co-authored-by: Shougo Matsushita Co-authored-by: h_east --- src/nvim/generators/gen_options.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/nvim/generators') diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 779b31e7a0..c79683dc00 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -47,7 +47,9 @@ end --- @param s string --- @return string local lowercase_to_titlecase = function(s) - return s:sub(1, 1):upper() .. s:sub(2) + 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 @@ -177,7 +179,7 @@ for _, option in ipairs(options_meta) do vars_w( (' kOpt%sFlag%s = 0x%02x,'):format( opt_name, - lowercase_to_titlecase(flag_name), + lowercase_to_titlecase(flag_name:gsub(':$', '')), enum_values[flag_name] ) ) -- cgit From 8ef41f590224dfeea2e51d9fec150e363fd72ee0 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 19 Dec 2024 07:07:04 -0800 Subject: feat(jobs): jobstart(…,{term=true}), deprecate termopen() #31343 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: `termopen` has long been a superficial wrapper around `jobstart`, and has no real purpose. Also, `vim.system` and `nvim_open_term` presumably will replace all features of `jobstart` and `termopen`, so centralizing the logic will help with that. Solution: - Introduce `eval/deprecated.c`, where all deprecated eval funcs will live. - Introduce "term" flag of `jobstart`. - Deprecate `termopen`. --- src/nvim/generators/gen_eval.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'src/nvim/generators') diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index 443c68e008..0b6ee6cb24 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -19,6 +19,7 @@ hashpipe:write([[ #include "nvim/digraph.h" #include "nvim/eval.h" #include "nvim/eval/buffer.h" +#include "nvim/eval/deprecated.h" #include "nvim/eval/fs.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" -- cgit From 0d469b697ed48a1cb1b624d65b6cd22a47195707 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 9 Jan 2025 08:40:50 +0800 Subject: vim-patch:1718e7d: runtime(vim): Update base-syntax, improve ex-bang matching (#31922) Always match ex-bang explicitly rather than incidentally as the ! operator. fixes: vim/vim#16221 closes: vim/vim#16410 https://github.com/vim/vim/commit/1718e7d07e391571ac81c507a746b3bc7a7e2024 Co-authored-by: Doug Kearns --- src/nvim/generators/gen_vimvim.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/nvim/generators') diff --git a/src/nvim/generators/gen_vimvim.lua b/src/nvim/generators/gen_vimvim.lua index 0675f04b73..d8053822bf 100644 --- a/src/nvim/generators/gen_vimvim.lua +++ b/src/nvim/generators/gen_vimvim.lua @@ -52,11 +52,13 @@ local function is_special_cased_cmd(cmd) end local vimcmd_start = 'syn keyword vimCommand contained ' +local vimcmd_end = ' nextgroup=vimBang' w(vimcmd_start) + local prev_cmd = nil for _, cmd_desc in ipairs(ex_cmds.cmds) do if lld.line_length > 850 then - w('\n' .. vimcmd_start) + w(vimcmd_end .. '\n' .. vimcmd_start) end local cmd = cmd_desc.command if cmd:match('%w') and cmd ~= 'z' and not is_special_cased_cmd(cmd) then @@ -79,9 +81,11 @@ for _, cmd_desc in ipairs(ex_cmds.cmds) do prev_cmd = cmd end +w(vimcmd_end .. '\n') + local vimopt_start = 'syn keyword vimOption contained ' local vimopt_end = ' skipwhite nextgroup=vimSetEqual,vimSetMod' -w('\n\n' .. vimopt_start) +w('\n' .. vimopt_start) for _, opt_desc in ipairs(options.options) do if not opt_desc.immutable then -- cgit From c5f93d7ab04f93db1470d58ca1f70e947e716c2b Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Sat, 28 Dec 2024 14:55:22 +0600 Subject: refactor(options): remove code for multitype options Problem: It was decided on Matrix chat that multitype options won't be necessary for Neovim options, and that options should only have a single canonical type. Therefore the code for supporting multitype options is unnecessary. Solution: Remove the additional code that's used to provide multitype option support. --- src/nvim/generators/gen_options.lua | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) (limited to 'src/nvim/generators') diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index c79683dc00..0298381ece 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -312,21 +312,6 @@ 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) @@ -427,8 +412,8 @@ local function dump_option(i, o) if o.abbreviation then w(' .shortname=' .. cstr(o.abbreviation)) end + w(' .type=' .. opt_type_enum(o.type)) 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 -- cgit From fb564ddff0b4ec9dad5afa7548777af1c3044273 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 16 Jan 2025 20:53:17 +0000 Subject: refactor(options): generic expand and did_set callbacks (#32011) * refactor(options): generic expand and did_set callbacks Problem: Many options have similar callbacks to check the values are valid. Solution: Generalize these callbacks into a single function that reads the option table. * refactor: gen_options.lua refactor: gen_options.lua - inline get_cond * refactor(options): use a simpler format for the common default --- src/nvim/generators/gen_options.lua | 662 +++++++++++++++++++----------------- src/nvim/generators/hashy.lua | 2 +- 2 files changed, 355 insertions(+), 309 deletions(-) (limited to 'src/nvim/generators') diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 0298381ece..e5dba90925 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -1,237 +1,30 @@ -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] = {} +--- @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) +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 -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() - -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', @@ -255,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' }, @@ -291,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 @@ -341,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 @@ -387,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 @@ -397,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 @@ -406,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(' .type=' .. opt_type_enum(o.type)) - w(' .flags=' .. get_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 + local enum_values + + if type(o.flags) == 'table' then + enum_values = o.flags --[[ @as table ]] 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 options_index Map of option name to option index +local function gen_enums(output_file) + --- Options for each scope. + --- @type table + 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 + 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 + + -- 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 + +--- @param output_file string +--- @param option_index table +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 + +--- @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 - w(' },') + + 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 --- 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) +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 -w('};') + +main() diff --git a/src/nvim/generators/hashy.lua b/src/nvim/generators/hashy.lua index ea35064962..74b7655324 100644 --- a/src/nvim/generators/hashy.lua +++ b/src/nvim/generators/hashy.lua @@ -55,7 +55,7 @@ function M.build_pos_hash(strings) end function M.switcher(put, tab, maxlen, worst_buck_size) - local neworder = {} + local neworder = {} --- @type string[] put ' switch (len) {\n' local bucky = worst_buck_size > 1 for len = 1, maxlen do -- cgit