local M = {} M.read_methods = { text = { "read_text" }, buffer = { "read_buffer" }, cursor = { "read_cursor" }, mode = { "read_mode", "read_pending_operator" }, selection = { "read_selection" }, count = { "read_count" }, configuration = { "read_configuration", "configuration_present" }, encoding = { "read_encoding" }, macro_state = { "read_macro_state" }, fold_state = { "read_fold_state" }, time = { "read_time_ms" }, highlight_groups = { "read_highlight_group" }, } M.effect_methods = { movement = { "apply_cursor", "apply_selection", "set_operator_inclusive" }, configuration = { "write_configuration" }, input = { "read_input" }, folds = { "open_fold" }, prompt = { "show_prompt" }, redraw = { "redraw" }, diagnostics = { "emit_diagnostic" }, highlights = { "define_highlight_group", "create_highlight", "remove_highlight", }, cursor_presentation = { "supports_cursor_presentation", "suppress_cursor_presentation", "restore_cursor_presentation", }, timers = { "supports_timers", "start_timer", "stop_timer" }, events = { "register_events", "remove_event_registration", "deliver_event", "begin_action_transition", "commit_action_transition", }, mappings = { "register_action", "register_mapping" }, dot_repeat = { "register_dot_repeat" }, } local function collect_methods(groups) local result = {} local group_names = {} for group_name in pairs(groups) do group_names[#group_names + 1] = group_name end table.sort(group_names) for _, group_name in ipairs(group_names) do for _, method_name in ipairs(groups[group_name]) do result[#result + 1] = method_name end end return result end local all_methods = collect_methods(M.read_methods) for _, method_name in ipairs(collect_methods(M.effect_methods)) do all_methods[#all_methods + 1] = method_name end table.sort(all_methods) function M.required_methods() local result = {} for index = 1, #all_methods do result[index] = all_methods[index] end return result end function M.missing_methods(host) local missing = {} for _, method_name in ipairs(all_methods) do if type(host) ~= "table" or type(host[method_name]) ~= "function" then missing[#missing + 1] = method_name end end return missing end function M.assert_implements(host) local missing = M.missing_methods(host) if #missing > 0 then error("host is missing semantic capabilities: " .. table.concat(missing, ", "), 2) end return host end local EventQueue = {} EventQueue.__index = EventQueue M.EventQueue = EventQueue function EventQueue.new(deliver) if type(deliver) ~= "function" then error("event delivery must be a function", 2) end return setmetatable({ _deliver = deliver, _active_token = nil, _pending = {}, _next_token = 1, }, EventQueue) end function EventQueue:begin_transition() if self._active_token ~= nil then error("an action transition is already active", 2) end local token = "action-transition-" .. tostring(self._next_token) self._next_token = self._next_token + 1 self._active_token = token self._pending = {} return token end function EventQueue:is_transition_active() return self._active_token ~= nil end function EventQueue:pending_count() return #self._pending end function EventQueue:emit(name, payload) if type(name) ~= "string" or name == "" then error("event name must be a nonempty string", 2) end if self._active_token ~= nil then self._pending[#self._pending + 1] = { name = name, payload = payload, } return false end self._deliver(name, payload) return true end function EventQueue:commit_transition(token) if self._active_token == nil then error("no action transition is active", 2) end if token ~= self._active_token then error("action transition token does not match", 2) end local pending = self._pending self._active_token = nil self._pending = {} for index = 1, #pending do local event = pending[index] self._deliver(event.name, event.payload) end end return M