neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

gen_api_ui_events.lua (6906B)


      1 local mpack = vim.mpack
      2 
      3 assert(#arg == 5)
      4 local input = io.open(arg[1], 'rb')
      5 local call_output = io.open(arg[2], 'wb')
      6 local remote_output = io.open(arg[3], 'wb')
      7 local metadata_output = io.open(arg[4], 'wb')
      8 local client_output = io.open(arg[5], 'wb')
      9 
     10 local c_grammar = require('gen.c_grammar')
     11 local events = c_grammar.grammar:match(input:read('*all'))
     12 
     13 local hashy = require 'gen.hashy'
     14 
     15 local function write_signature(output, ev, prefix, notype)
     16  output:write('(' .. prefix)
     17  if prefix == '' and #ev.parameters == 0 then
     18    output:write('void')
     19  end
     20  for j = 1, #ev.parameters do
     21    if j > 1 or prefix ~= '' then
     22      output:write(', ')
     23    end
     24    local param = ev.parameters[j]
     25    if not notype then
     26      output:write(param[1] .. ' ')
     27    end
     28    output:write(param[2])
     29  end
     30  output:write(')')
     31 end
     32 
     33 local function write_arglist(output, ev)
     34  if #ev.parameters == 0 then
     35    return
     36  end
     37  output:write('  MAXSIZE_TEMP_ARRAY(args, ' .. #ev.parameters .. ');\n')
     38  for j = 1, #ev.parameters do
     39    local param = ev.parameters[j]
     40    local kind = string.upper(param[1])
     41    output:write('  ADD_C(args, ')
     42    output:write(kind .. '_OBJ(' .. param[2] .. ')')
     43    output:write(');\n')
     44  end
     45 end
     46 
     47 local function call_ui_event_method(output, ev)
     48  output:write('void ui_client_event_' .. ev.name .. '(Array args)\n{\n')
     49 
     50  local hlattrs_args_count = 0
     51  if #ev.parameters > 0 then
     52    output:write('  if (args.size < ' .. #ev.parameters)
     53    for j = 1, #ev.parameters do
     54      local kind = ev.parameters[j][1]
     55      if kind ~= 'Object' then
     56        if kind == 'HlAttrs' then
     57          kind = 'Dict'
     58        end
     59        output:write('\n      || args.items[' .. (j - 1) .. '].type != kObjectType' .. kind .. '')
     60      end
     61    end
     62    output:write(') {\n')
     63    output:write('    ELOG("Error handling ui event \'' .. ev.name .. '\'");\n')
     64    output:write('    return;\n')
     65    output:write('  }\n')
     66  end
     67 
     68  for j = 1, #ev.parameters do
     69    local param = ev.parameters[j]
     70    local kind = param[1]
     71    output:write('  ' .. kind .. ' arg_' .. j .. ' = ')
     72    if kind == 'HlAttrs' then
     73      -- The first HlAttrs argument is rgb_attrs and second is cterm_attrs
     74      output:write(
     75        'ui_client_dict2hlattrs(args.items['
     76          .. (j - 1)
     77          .. '].data.dict, '
     78          .. (hlattrs_args_count == 0 and 'true' or 'false')
     79          .. ');\n'
     80      )
     81      hlattrs_args_count = hlattrs_args_count + 1
     82    elseif kind == 'Object' then
     83      output:write('args.items[' .. (j - 1) .. '];\n')
     84    elseif kind == 'Window' then
     85      output:write('(Window)args.items[' .. (j - 1) .. '].data.integer;\n')
     86    else
     87      output:write('args.items[' .. (j - 1) .. '].data.' .. string.lower(kind) .. ';\n')
     88    end
     89  end
     90 
     91  output:write('  tui_' .. ev.name .. '(tui')
     92  for j = 1, #ev.parameters do
     93    output:write(', arg_' .. j)
     94  end
     95  output:write(');\n')
     96 
     97  output:write('}\n\n')
     98 end
     99 
    100 events = vim.tbl_filter(function(ev)
    101  return ev[1] ~= 'empty' and ev[1] ~= 'preproc'
    102 end, events)
    103 
    104 for i = 1, #events do
    105  local ev = events[i]
    106  assert(ev.return_type == 'void')
    107 
    108  if ev.since == nil and not ev.noexport then
    109    print('Ui event ' .. ev.name .. ' lacks since field.\n')
    110    os.exit(1)
    111  end
    112  ev.since = tonumber(ev.since)
    113 
    114  local args = #ev.parameters > 0 and 'args' or 'noargs'
    115  if not ev.remote_only then
    116    if not ev.remote_impl and not ev.noexport then
    117      remote_output:write('void remote_ui_' .. ev.name)
    118      write_signature(remote_output, ev, 'RemoteUI *ui')
    119      remote_output:write('\n{\n')
    120      write_arglist(remote_output, ev)
    121      remote_output:write('  push_call(ui, "' .. ev.name .. '", ' .. args .. ');\n')
    122      remote_output:write('}\n\n')
    123    end
    124  end
    125 
    126  if not (ev.remote_only and ev.remote_impl) then
    127    call_output:write('void ui_call_' .. ev.name)
    128    write_signature(call_output, ev, '')
    129    call_output:write('\n{\n')
    130    if ev.remote_only then
    131      -- Lua callbacks may emit other events or the same event again. Avoid the latter
    132      -- by adding a recursion guard to each generated function that may call a Lua callback.
    133      call_output:write('  static bool entered = false;\n')
    134      call_output:write('  if (entered) {\n')
    135      call_output:write('    return;\n')
    136      call_output:write('  }\n')
    137      call_output:write('  entered = true;\n')
    138      write_arglist(call_output, ev)
    139      call_output:write(('  ui_call_event("%s", %s)'):format(ev.name, args))
    140      call_output:write(';\n  entered = false;\n')
    141    elseif ev.compositor_impl then
    142      call_output:write('  ui_comp_' .. ev.name)
    143      write_signature(call_output, ev, '', true)
    144      call_output:write(';\n')
    145      call_output:write('  UI_CALL')
    146      write_signature(call_output, ev, '!ui->composed, ' .. ev.name .. ', ui', true)
    147      call_output:write(';\n')
    148    else
    149      call_output:write('  UI_CALL')
    150      write_signature(call_output, ev, 'true, ' .. ev.name .. ', ui', true)
    151      call_output:write(';\n')
    152    end
    153    call_output:write('}\n\n')
    154  end
    155 
    156  if ev.compositor_impl then
    157    call_output:write('void ui_composed_call_' .. ev.name)
    158    write_signature(call_output, ev, '')
    159    call_output:write('\n{\n')
    160    call_output:write('  UI_CALL')
    161    write_signature(call_output, ev, 'ui->composed, ' .. ev.name .. ', ui', true)
    162    call_output:write(';\n')
    163    call_output:write('}\n\n')
    164  end
    165 
    166  if (not ev.remote_only) and not ev.noexport and not ev.client_impl and not ev.client_ignore then
    167    call_ui_event_method(client_output, ev)
    168  end
    169 end
    170 
    171 local client_events = {}
    172 for _, ev in ipairs(events) do
    173  if (not ev.noexport) and ((not ev.remote_only) or ev.client_impl) and not ev.client_ignore then
    174    client_events[ev.name] = ev
    175  end
    176 end
    177 
    178 local hashorder, hashfun = hashy.hashy_hash(
    179  'ui_client_handler',
    180  vim.tbl_keys(client_events),
    181  function(idx)
    182    return 'event_handlers[' .. idx .. '].name'
    183  end
    184 )
    185 
    186 client_output:write('static const UIClientHandler event_handlers[] = {\n')
    187 
    188 for _, name in ipairs(hashorder) do
    189  client_output:write('  { .name = "' .. name .. '", .fn = ui_client_event_' .. name .. '},\n')
    190 end
    191 
    192 client_output:write('\n};\n\n')
    193 client_output:write(hashfun)
    194 
    195 call_output:close()
    196 remote_output:close()
    197 client_output:close()
    198 
    199 -- don't expose internal attributes like "impl_name" in public metadata
    200 local exported_attributes = { 'name', 'parameters', 'since', 'deprecated_since' }
    201 local exported_events = {}
    202 for _, ev in ipairs(events) do
    203  local ev_exported = {}
    204  for _, attr in ipairs(exported_attributes) do
    205    ev_exported[attr] = ev[attr]
    206  end
    207  for _, p in ipairs(ev_exported.parameters) do
    208    if p[1] == 'HlAttrs' or p[1] == 'Dict' then
    209      -- TODO(justinmk): for back-compat, but do clients actually look at this?
    210      p[1] = 'Dictionary'
    211    end
    212  end
    213  if not ev.noexport then
    214    exported_events[#exported_events + 1] = ev_exported
    215  end
    216 end
    217 
    218 metadata_output:write(mpack.encode(exported_events))
    219 metadata_output:close()