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()