summaryrefslogtreecommitdiff
path: root/lua/clever_f/testing
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 18:44:32 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 18:44:32 +0200
commitc7b14ffb6d14969c9c41864e827f33f8e80fc24e (patch)
treec7c0ac23dc93a4a8b75449d2be4d0e05c4f05f9e /lua/clever_f/testing
parent9013636a57144e1f57c9339e7888d588430aae6a (diff)
Rename plugin to clever-tee
Diffstat (limited to 'lua/clever_f/testing')
-rw-r--r--lua/clever_f/testing/memory_host.lua1075
1 files changed, 0 insertions, 1075 deletions
diff --git a/lua/clever_f/testing/memory_host.lua b/lua/clever_f/testing/memory_host.lua
deleted file mode 100644
index 6147d2c..0000000
--- a/lua/clever_f/testing/memory_host.lua
+++ /dev/null
@@ -1,1075 +0,0 @@
-local capabilities = require("clever_f.capabilities")
-local domain = require("clever_f.domain")
-local text_topology = require("clever_f.text_topology")
-
-local M = {}
-local MemoryHost = {}
-MemoryHost.__index = MemoryHost
-M.MemoryHost = MemoryHost
-local unpack_values = table.unpack or unpack
-
-local function is_integer(value)
- return type(value) == "number"
- and value > -math.huge
- and value < math.huge
- and value == math.floor(value)
-end
-
-local function copy(value, seen)
- if type(value) ~= "table" or domain.type_of(value) ~= nil then
- return value
- end
- seen = seen or {}
- if seen[value] ~= nil then
- return seen[value]
- end
- local result = {}
- seen[value] = result
- for key, item in pairs(value) do
- result[copy(key, seen)] = copy(item, seen)
- end
- return result
-end
-
-local function list_copy(values)
- local result = {}
- for index = 1, #values do
- result[index] = values[index]
- end
- return result
-end
-
-local function text_snapshot(value)
- if domain.TextSnapshot.is(value) then
- return value
- end
- if type(value) == "table" and value.lines ~= nil then
- value = value.lines
- end
- return domain.TextSnapshot.new(value)
-end
-
-local function selection_value(value)
- if value == nil then
- return domain.Selection.inactive()
- end
- return domain.Selection.new(value)
-end
-
-local function macro_state(value)
- if type(value) == "table" and not domain.MacroState.is(value) then
- value = value.register
- end
- return domain.MacroState.new(value)
-end
-
-local function fold_state(options)
- if domain.FoldState.is(options.fold_state) then
- return options.fold_state
- end
- return domain.FoldState.new(
- options.fold_open_policy or {},
- options.closed_fold_levels or 0
- )
-end
-
-local function input_packet(value)
- return domain.InputPacket.from_table(value)
-end
-
-local function normalize_event_names(event_names)
- if type(event_names) == "string" then
- event_names = { event_names }
- end
- if type(event_names) ~= "table" or #event_names < 1 then
- error("event names must be a nonempty list", 3)
- end
- local result = {}
- local seen = {}
- for index = 1, #event_names do
- local name = event_names[index]
- if type(name) ~= "string" or name == "" then
- error("event name must be a nonempty string", 3)
- end
- if not seen[name] then
- seen[name] = true
- result[#result + 1] = name
- end
- end
- return result, seen
-end
-
-local function normalize_modes(modes)
- if type(modes) == "string" then
- modes = { modes }
- end
- if type(modes) ~= "table" or #modes < 1 then
- error("mapping modes must be a nonempty list", 3)
- end
- local result = {}
- for index = 1, #modes do
- if type(modes[index]) ~= "string" or modes[index] == "" then
- error("mapping mode must be a nonempty string", 3)
- end
- result[index] = modes[index]
- end
- return result
-end
-
-function MemoryHost.new(options)
- options = options or {}
- if type(options) ~= "table" then
- error("memory host options must be a table", 2)
- end
-
- local cursor_presentation_support = options.cursor_presentation_support
- if cursor_presentation_support == nil then
- cursor_presentation_support = options.cmdline_cursor_support
- end
- if cursor_presentation_support == nil then
- cursor_presentation_support = true
- end
-
- local raw_mode = options.mode or "n"
- if domain.ModeContext.is(raw_mode) then
- raw_mode = raw_mode.full_mode
- end
- domain.ModeContext.from_full_mode(raw_mode)
-
- local self = setmetatable({
- _text = text_snapshot(options.text or options.buffer_lines or { "" }),
- _buffer = options.buffer or "buffer-1",
- _cursor = domain.Position.coerce(options.cursor or { line = 1, byte_column = 1 }),
- _window = options.window or "window-1",
- _mode = raw_mode,
- _selection = selection_value(options.selection),
- _count = domain.Count.new(options.count),
- _configuration = copy(options.configuration or {}),
- _encoding = options.encoding or options.effective_encoding or "utf-8",
- _lowercase = options.lowercase or vim.fn.tolower,
- _macro_state = macro_state(options.macro_state or options.macro_register),
- _fold_state = fold_state(options),
- _pending_operator = options.pending_operator,
- _time_values = list_copy(options.time_values_ms or {}),
- _time_index = 1,
- _current_time = options.time_ms or 0,
- _input_packets = {},
- _input_index = 1,
- _timer_support = options.timer_support ~= false,
- _cursor_presentation_support = cursor_presentation_support,
- _cursor_presentation = copy(options.cursor_presentation or {
- hidden = false,
- }),
- _emit_movement_events = options.emit_movement_events ~= false,
- _operator_inclusive = false,
- _operations = {},
- _prompts = {},
- _redraws = {},
- _diagnostics = {},
- _highlight_groups = copy(options.highlight_groups or {}),
- _highlights = {},
- _timers = {},
- _event_registrations = {},
- _event_registration_order = {},
- _actions = {},
- _mappings = {},
- _cursor_leases = {},
- _dot_repeat = nil,
- _identity_counters = {},
- }, MemoryHost)
-
- for index, packet in ipairs(options.input_packets or {}) do
- self._input_packets[index] = input_packet(packet)
- end
-
- self._event_queue = capabilities.EventQueue.new(function(name, payload)
- self:_deliver_event_now(name, payload)
- end)
-
- return capabilities.assert_implements(self)
-end
-
-function M.new(options)
- return MemoryHost.new(options)
-end
-
-setmetatable(M, {
- __call = function(_, options)
- return MemoryHost.new(options)
- end,
-})
-
-function MemoryHost:_next_identity(prefix)
- local next_value = (self._identity_counters[prefix] or 0) + 1
- self._identity_counters[prefix] = next_value
- return prefix .. "-" .. tostring(next_value)
-end
-
-function MemoryHost:_record(operation, details)
- local entry = { operation = operation }
- for key, value in pairs(details or {}) do
- entry[key] = copy(value)
- end
- self._operations[#self._operations + 1] = entry
-end
-
-function MemoryHost:operations()
- return copy(self._operations)
-end
-
-function MemoryHost:clear_operations()
- self._operations = {}
-end
-
-function MemoryHost:read_text()
- self:_record("read_text")
- return self._text
-end
-
-function MemoryHost:read_cursor()
- self:_record("read_cursor")
- return self._cursor
-end
-
-function MemoryHost:read_buffer()
- self:_record("read_buffer", { buffer = self._buffer })
- return self._buffer
-end
-
-function MemoryHost:read_window()
- self:_record("read_window", { window = self._window })
- return self._window
-end
-
-function MemoryHost:read_mode()
- self:_record("read_mode", { mode = self._mode })
- return self._mode
-end
-
-function MemoryHost:read_mode_context()
- return domain.ModeContext.from_full_mode(self:read_mode())
-end
-
-function MemoryHost:read_pending_operator()
- self:_record("read_pending_operator", { operator = self._pending_operator })
- return self._pending_operator
-end
-
-function MemoryHost:read_selection()
- self:_record("read_selection")
- return self._selection
-end
-
-function MemoryHost:read_count()
- self:_record("read_count", { count = self._count.value })
- return self._count
-end
-
-function MemoryHost:configuration_present(name)
- if type(name) ~= "string" or name == "" then
- error("configuration name must be a nonempty string", 2)
- end
- local present = self._configuration[name] ~= nil
- self:_record("configuration_present", { name = name, present = present })
- return present
-end
-
-function MemoryHost:read_configuration(name)
- if type(name) ~= "string" or name == "" then
- error("configuration name must be a nonempty string", 2)
- end
- local value = copy(self._configuration[name])
- self:_record("read_configuration", { name = name, value = value })
- return value
-end
-
-function MemoryHost:write_configuration(name, value)
- if type(name) ~= "string" or name == "" then
- error("configuration name must be a nonempty string", 2)
- end
- self._configuration[name] = copy(value)
- self:_record("write_configuration", { name = name, value = value })
-end
-
-function MemoryHost:read_encoding()
- self:_record("read_encoding", { encoding = self._encoding })
- return self._encoding
-end
-
-function MemoryHost:lowercase(value)
- if type(value) ~= "string" then
- error("case conversion value must be a string", 2)
- end
- local result = self._lowercase(value)
- if type(result) ~= "string" then
- error("case converter must return a string", 2)
- end
- self:_record("lowercase", { value = value, result = result })
- return result
-end
-
-function MemoryHost:read_macro_state()
- self:_record("read_macro_state", { executing = self._macro_state.executing })
- return self._macro_state
-end
-
-function MemoryHost:read_fold_state()
- self:_record("read_fold_state", { closed_levels = self._fold_state.closed_levels })
- return self._fold_state
-end
-
-function MemoryHost:read_time_ms()
- local value = self._time_values[self._time_index]
- if value ~= nil then
- self._time_index = self._time_index + 1
- self._current_time = value
- else
- value = self._current_time
- end
- if type(value) ~= "number" then
- error("time value must be a number", 2)
- end
- self:_record("read_time_ms", { value = value })
- return value
-end
-
-function MemoryHost:set_text(value)
- self._text = text_snapshot(value)
-end
-
-function MemoryHost:set_cursor(position)
- self._cursor = domain.Position.coerce(position)
-end
-
-function MemoryHost:set_buffer(buffer)
- if buffer == nil then
- error("buffer identity must be present", 2)
- end
- self._buffer = buffer
-end
-
-function MemoryHost:set_window(window)
- if window == nil then
- error("window identity must be present", 2)
- end
- self._window = window
-end
-
-function MemoryHost:set_mode(full_mode)
- if domain.ModeContext.is(full_mode) then
- full_mode = full_mode.full_mode
- end
- domain.ModeContext.from_full_mode(full_mode)
- self._mode = full_mode
-end
-
-function MemoryHost:set_selection(selection)
- self._selection = selection_value(selection)
-end
-
-function MemoryHost:set_count(count)
- self._count = domain.Count.new(count)
-end
-
-function MemoryHost:set_configuration(name, value)
- if type(name) ~= "string" or name == "" then
- error("configuration name must be a nonempty string", 2)
- end
- self._configuration[name] = copy(value)
-end
-
-function MemoryHost:unset_configuration(name)
- self._configuration[name] = nil
-end
-
-function MemoryHost:set_encoding(encoding)
- if type(encoding) ~= "string" or encoding == "" then
- error("encoding must be a nonempty string", 2)
- end
- self._encoding = encoding
-end
-
-function MemoryHost:set_macro_state(state)
- self._macro_state = macro_state(state)
-end
-
-function MemoryHost:set_fold_state(state, closed_levels)
- if domain.FoldState.is(state) then
- self._fold_state = state
- else
- self._fold_state = domain.FoldState.new(state, closed_levels)
- end
-end
-
-function MemoryHost:set_pending_operator(operator)
- self._pending_operator = operator
-end
-
-function MemoryHost:push_time_ms(value)
- if type(value) ~= "number" then
- error("time value must be a number", 2)
- end
- self._time_values[#self._time_values + 1] = value
-end
-
-function MemoryHost:push_input(packet)
- self._input_packets[#self._input_packets + 1] = input_packet(packet)
-end
-
-function MemoryHost:_emit_movement_event(previous)
- if self._emit_movement_events and not domain.Position.equal(previous, self._cursor) then
- self:deliver_event("CursorMoved", {
- cursor = self._cursor,
- })
- end
-end
-
-local function motion_descriptor(motion)
- if domain.Descriptor.is(motion) then
- return motion
- end
- if type(motion) == "table" and motion.descriptor ~= nil then
- return domain.Descriptor.from_string(motion.descriptor)
- end
- return nil
-end
-
-local function character_boundary(view, position)
- if view:line_is_empty(position.line) then
- return 1
- end
- return view:character_index_for_position(position)
-end
-
-local function character_lines(snapshot)
- local result = {}
- for line_number, line in ipairs(snapshot:lines()) do
- result[line_number] = text_topology.split_editor_characters(line)
- end
- return result
-end
-
-local function joined_range(characters, first, last)
- local result = {}
- for index = first, last do
- result[#result + 1] = characters[index]
- end
- return table.concat(result)
-end
-
-local function delete_character_range(
- snapshot,
- start_line,
- start_index,
- finish_line,
- finish_index
-)
- local source = character_lines(snapshot)
- local lines = snapshot:lines()
- local result = {}
-
- for line_number = 1, start_line - 1 do
- result[#result + 1] = lines[line_number]
- end
-
- local prefix = joined_range(source[start_line], 1, start_index - 1)
- if start_line == finish_line then
- result[#result + 1] = prefix
- .. joined_range(
- source[start_line],
- finish_index,
- #source[start_line]
- )
- else
- result[#result + 1] = prefix
- .. joined_range(
- source[finish_line],
- finish_index,
- #source[finish_line]
- )
- end
-
- for line_number = finish_line + 1, #lines do
- result[#result + 1] = lines[line_number]
- end
- return domain.TextSnapshot.new(result)
-end
-
-local function normalized_cursor(snapshot, encoding, position)
- local line_number = math.min(position.line, snapshot.line_count)
- local view = text_topology.new(snapshot, encoding)
- return view:normalize_endpoint(line_number, position.byte_column)
-end
-
-function MemoryHost:_apply_pending_delete(origin, destination, descriptor)
- if self._pending_operator ~= "delete" and self._pending_operator ~= "d" then
- return false
- end
-
- local view = text_topology.new(self._text, self._encoding)
- local origin_index = character_boundary(view, origin)
- local destination_index = character_boundary(view, destination)
- local start_line
- local start_index
- local finish_line
- local finish_index
- local final_cursor
-
- if descriptor.direction == domain.Direction.FORWARD then
- start_line = origin.line
- start_index = origin_index
- finish_line = destination.line
- finish_index = destination_index + (self._operator_inclusive and 1 or 0)
- final_cursor = origin
- elseif descriptor.family == domain.Family.FIND then
- start_line = destination.line
- start_index = destination_index + 1
- finish_line = origin.line
- finish_index = origin_index + 1
- final_cursor = destination
- else
- start_line = destination.line
- start_index = destination_index
- finish_line = origin.line
- finish_index = origin_index
- final_cursor = view:predecessor(destination) or destination
- end
-
- self._text = delete_character_range(
- self._text,
- start_line,
- start_index,
- finish_line,
- finish_index
- )
- self._cursor = normalized_cursor(self._text, self._encoding, final_cursor)
- self:_record("apply_operator", {
- operator = self._pending_operator,
- descriptor = descriptor.value,
- origin = origin,
- endpoint = destination,
- position = self._cursor,
- })
- return true
-end
-
-function MemoryHost:apply_cursor(position, motion)
- position = domain.Position.coerce(position)
- local previous = self._cursor
- local descriptor = motion_descriptor(motion)
- self._cursor = position
- self:_record("apply_cursor", {
- position = position,
- descriptor = descriptor and descriptor.value or nil,
- })
- if descriptor ~= nil then
- local origin = type(motion) == "table" and motion.origin or previous
- self:_apply_pending_delete(domain.Position.coerce(origin), position, descriptor)
- end
- self:_emit_movement_event(previous)
-end
-
-function MemoryHost:apply_selection(position, kind)
- local previous = self._cursor
- local next_selection
- if domain.Selection.is(position) then
- next_selection = position
- position = next_selection.focus
- else
- position = domain.Position.coerce(position)
- if kind == nil then
- kind = self._selection.kind
- end
- kind = domain.SelectionKind.from_string(kind)
- if kind == domain.SelectionKind.NONE then
- error("selection movement requires a Visual selection kind", 2)
- end
- local anchor = self._selection.active and self._selection.anchor or previous
- next_selection = domain.Selection.active(
- kind,
- anchor,
- position,
- self._selection.option
- )
- end
-
- self._selection = next_selection
- self._cursor = position
- self:_record("apply_selection", {
- position = position,
- kind = next_selection.kind.value,
- })
- self:_emit_movement_event(previous)
-end
-
-function MemoryHost:set_operator_inclusive(enabled)
- if type(enabled) ~= "boolean" then
- error("operator inclusivity must be a Boolean", 2)
- end
- self._operator_inclusive = enabled
- self:_record("set_operator_inclusive", { enabled = enabled })
-end
-
-function MemoryHost:operator_inclusive()
- return self._operator_inclusive
-end
-
-function MemoryHost:read_input()
- local packet = self._input_packets[self._input_index]
- if packet == nil then
- error("in-memory input queue is empty", 2)
- end
- self._input_index = self._input_index + 1
- self:_record("read_input", { packet = packet:to_table() })
- if packet.kind == domain.InputPacketKind.ERROR then
- error(packet.message, 0)
- end
- return packet
-end
-
-function MemoryHost:open_fold(position)
- position = position and domain.Position.coerce(position) or self._cursor
- local closed_levels = self._fold_state.closed_levels
- if closed_levels == 0 then
- self:_record("open_fold", { position = position, opened = false })
- return false
- end
- self:_record("open_fold", {
- position = position,
- fold_level = closed_levels,
- opened = true,
- })
- self._fold_state = domain.FoldState.new(
- self._fold_state:policies(),
- closed_levels - 1
- )
- return true
-end
-
-function MemoryHost:show_prompt(text)
- if type(text) ~= "string" then
- error("prompt must be a string", 2)
- end
- self._prompts[#self._prompts + 1] = text
- self:_record("show_prompt", { text = text })
-end
-
-function MemoryHost:prompts()
- return list_copy(self._prompts)
-end
-
-function MemoryHost:redraw(kind)
- if kind ~= "screen" and kind ~= "full" and kind ~= "suppressed" then
- error("redraw kind must be screen, full, or suppressed", 2)
- end
- self._redraws[#self._redraws + 1] = kind
- self:_record("redraw", { kind = kind })
-end
-
-function MemoryHost:redraws()
- return list_copy(self._redraws)
-end
-
-function MemoryHost:emit_diagnostic(level, text)
- if level ~= "error" and level ~= "warning" and level ~= "info" then
- error("diagnostic level must be error, warning, or info", 2)
- end
- if type(text) ~= "string" or text == "" then
- error("diagnostic text must be a nonempty string", 2)
- end
- local diagnostic = { level = level, text = text }
- self._diagnostics[#self._diagnostics + 1] = diagnostic
- self:_record("emit_diagnostic", diagnostic)
-end
-
-function MemoryHost:diagnostics()
- return copy(self._diagnostics)
-end
-
-function MemoryHost:read_highlight_group(name)
- if type(name) ~= "string" or name == "" then
- error("highlight group name must be a nonempty string", 2)
- end
- local definition = self._highlight_groups[name]
- self:_record("read_highlight_group", {
- name = name,
- defined = definition ~= nil,
- })
- return copy(definition)
-end
-
-function MemoryHost:highlight_groups()
- return copy(self._highlight_groups)
-end
-
-function MemoryHost:define_highlight_group(name, definition, options)
- if type(name) ~= "string" or name == "" then
- error("highlight group name must be a nonempty string", 2)
- end
- if type(definition) ~= "table" then
- error("highlight group definition must be a table", 2)
- end
- options = options or {}
- if type(options) ~= "table" then
- error("highlight group options must be a table", 2)
- end
- if options.default ~= nil and type(options.default) ~= "boolean" then
- error("highlight group default option must be a Boolean", 2)
- end
- if options.force ~= nil and type(options.force) ~= "boolean" then
- error("highlight group force option must be a Boolean", 2)
- end
-
- local exists = self._highlight_groups[name] ~= nil
- local applied = not (exists and options.default)
- if applied then
- self._highlight_groups[name] = copy(definition)
- end
- self:_record("define_highlight_group", {
- name = name,
- definition = definition,
- options = options,
- applied = applied,
- })
- return applied
-end
-
-function MemoryHost:create_highlight(specification)
- if type(specification) ~= "table" then
- error("highlight specification must be a table", 2)
- end
- if type(specification.group) ~= "string" or specification.group == "" then
- error("highlight group must be a nonempty string", 2)
- end
- local identity = specification.identity or self:_next_identity("highlight")
- if self._highlights[identity] ~= nil then
- error("highlight identity is already active", 2)
- end
- local stored = copy(specification)
- stored.identity = identity
- self._highlights[identity] = stored
- self:_record("create_highlight", stored)
- return identity
-end
-
-function MemoryHost:remove_highlight(identity)
- if type(identity) ~= "string" or identity == "" then
- error("highlight identity must be a nonempty string", 2)
- end
- local removed = self._highlights[identity] ~= nil
- self._highlights[identity] = nil
- self:_record("remove_highlight", { identity = identity, removed = removed })
- return removed
-end
-
-function MemoryHost:highlights()
- return copy(self._highlights)
-end
-
-function MemoryHost:supports_cursor_presentation()
- self:_record("supports_cursor_presentation", {
- supported = self._cursor_presentation_support,
- })
- return self._cursor_presentation_support
-end
-
-function MemoryHost:suppress_cursor_presentation()
- if not self._cursor_presentation_support then
- self:_record("suppress_cursor_presentation", { supported = false })
- return nil
- end
- local identity = self:_next_identity("cursor-presentation")
- self._cursor_leases[identity] = copy(self._cursor_presentation)
- local suppressed = copy(self._cursor_presentation)
- suppressed.hidden = true
- self._cursor_presentation = suppressed
- self:_record("suppress_cursor_presentation", {
- identity = identity,
- supported = true,
- })
- return identity
-end
-
-function MemoryHost:restore_cursor_presentation(identity)
- if identity == nil then
- self:_record("restore_cursor_presentation", { restored = false })
- return false
- end
- local saved = self._cursor_leases[identity]
- if saved == nil then
- error("cursor presentation lease is inactive", 2)
- end
- self._cursor_presentation = saved
- self._cursor_leases[identity] = nil
- self:_record("restore_cursor_presentation", {
- identity = identity,
- restored = true,
- })
- return true
-end
-
-function MemoryHost:cursor_presentation()
- return copy(self._cursor_presentation)
-end
-
-function MemoryHost:supports_timers()
- self:_record("supports_timers", { supported = self._timer_support })
- return self._timer_support
-end
-
-function MemoryHost:start_timer(delay_ms, callback)
- if not is_integer(delay_ms) or delay_ms < 0 then
- error("timer delay must be a nonnegative integer", 2)
- end
- if type(callback) ~= "function" then
- error("timer callback must be a function", 2)
- end
- if not self._timer_support then
- self:_record("start_timer", { delay_ms = delay_ms, supported = false })
- return nil
- end
- local identity = self:_next_identity("timer")
- self._timers[identity] = {
- identity = identity,
- delay_ms = delay_ms,
- callback = callback,
- active = true,
- }
- self:_record("start_timer", {
- identity = identity,
- delay_ms = delay_ms,
- supported = true,
- })
- return identity
-end
-
-function MemoryHost:stop_timer(identity)
- if type(identity) ~= "string" or identity == "" then
- error("timer identity must be a nonempty string", 2)
- end
- local timer = self._timers[identity]
- local stopped = timer ~= nil and timer.active
- if timer ~= nil then
- timer.active = false
- end
- self:_record("stop_timer", { identity = identity, stopped = stopped })
- return stopped
-end
-
-function MemoryHost:fire_timer(identity)
- local timer = self._timers[identity]
- if timer == nil then
- error("timer identity is unknown", 2)
- end
- if not timer.active then
- self:_record("ignore_timer", { identity = identity })
- return false
- end
- timer.active = false
- self:_record("fire_timer", { identity = identity })
- timer.callback(identity)
- return true
-end
-
-function MemoryHost:timers()
- local result = {}
- for identity, timer in pairs(self._timers) do
- result[identity] = {
- identity = identity,
- delay_ms = timer.delay_ms,
- active = timer.active,
- }
- end
- return result
-end
-
-function MemoryHost:register_events(event_names, callback, options)
- local names, name_set = normalize_event_names(event_names)
- if type(callback) ~= "function" then
- error("event callback must be a function", 2)
- end
- local identity = self:_next_identity("event-registration")
- self._event_registrations[identity] = {
- identity = identity,
- names = names,
- name_set = name_set,
- callback = callback,
- options = copy(options or {}),
- active = true,
- }
- self._event_registration_order[#self._event_registration_order + 1] = identity
- self:_record("register_events", {
- identity = identity,
- names = names,
- options = options or {},
- })
- return identity
-end
-
-function MemoryHost:remove_event_registration(identity)
- local registration = self._event_registrations[identity]
- local removed = registration ~= nil and registration.active
- if registration ~= nil then
- registration.active = false
- end
- self:_record("remove_event_registration", {
- identity = identity,
- removed = removed,
- })
- return removed
-end
-
-function MemoryHost:_deliver_event_now(name, payload)
- self:_record("event", { name = name, payload = payload })
- local order = list_copy(self._event_registration_order)
- local event_buffer = payload.buffer or self._buffer
- for _, identity in ipairs(order) do
- local registration = self._event_registrations[identity]
- local registration_buffer = registration.options.buffer
- if registration.active
- and registration.name_set[name]
- and (registration_buffer == nil or registration_buffer == event_buffer)
- then
- registration.callback(name, payload)
- end
- end
-end
-
-function MemoryHost:deliver_event(name, payload)
- if type(name) ~= "string" or name == "" then
- error("event name must be a nonempty string", 2)
- end
- payload = copy(payload or {})
- local queued = self._event_queue:is_transition_active()
- self:_record(queued and "queue_event" or "deliver_event", {
- name = name,
- payload = payload,
- })
- return self._event_queue:emit(name, payload)
-end
-
-function MemoryHost:begin_action_transition()
- local token = self._event_queue:begin_transition()
- self:_record("begin_action_transition", { identity = token })
- return token
-end
-
-function MemoryHost:commit_action_transition(token)
- self:_record("commit_action_transition", { identity = token })
- self._event_queue:commit_transition(token)
-end
-
-function MemoryHost:pending_event_count()
- return self._event_queue:pending_count()
-end
-
-function MemoryHost:event_registrations()
- local result = {}
- for identity, registration in pairs(self._event_registrations) do
- result[identity] = {
- identity = identity,
- names = list_copy(registration.names),
- options = copy(registration.options),
- active = registration.active,
- }
- end
- return result
-end
-
-function MemoryHost:register_action(name, callback)
- if type(name) ~= "string" or name == "" then
- error("action name must be a nonempty string", 2)
- end
- if type(callback) ~= "function" then
- error("action callback must be a function", 2)
- end
- if self._actions[name] ~= nil then
- error("action is already registered", 2)
- end
- self._actions[name] = callback
- self:_record("register_action", { name = name })
- return name
-end
-
-function MemoryHost:invoke_action(name, ...)
- local callback = self._actions[name]
- if callback == nil then
- error("action is not registered", 2)
- end
- local arguments = { ... }
- local argument_count = select("#", ...)
- local token = self:begin_action_transition()
- local results = {
- pcall(function()
- return callback(unpack_values(arguments, 1, argument_count))
- end),
- }
- self:commit_action_transition(token)
- local succeeded = table.remove(results, 1)
- if not succeeded then
- error(results[1], 0)
- end
- return unpack_values(results)
-end
-
-function MemoryHost:register_mapping(modes, lhs, action, options)
- modes = normalize_modes(modes)
- if type(lhs) ~= "string" or lhs == "" then
- error("mapping lhs must be a nonempty string", 2)
- end
- if type(action) ~= "string" and type(action) ~= "function" then
- error("mapping action must be an action name or function", 2)
- end
- local identity = self:_next_identity("mapping")
- self._mappings[identity] = {
- identity = identity,
- modes = modes,
- lhs = lhs,
- action = action,
- options = copy(options or {}),
- }
- self:_record("register_mapping", {
- identity = identity,
- modes = modes,
- lhs = lhs,
- action = type(action) == "string" and action or "<function>",
- options = options or {},
- })
- return identity
-end
-
-function MemoryHost:mappings()
- return copy(self._mappings)
-end
-
-function MemoryHost:register_dot_repeat(payload, callback)
- if not domain.DotPayload.is(payload) then
- error("dot-repeat payload must be a DotPayload", 2)
- end
- if callback ~= nil and type(callback) ~= "function" then
- error("dot-repeat callback must be a function", 2)
- end
- self._dot_repeat = {
- payload = payload,
- callback = callback,
- operator = self._pending_operator,
- mode = self._mode,
- }
- self:_record("register_dot_repeat", { payload = payload:to_table() })
- return payload
-end
-
-function MemoryHost:dot_repeat_payload()
- return self._dot_repeat and self._dot_repeat.payload or nil
-end
-
-function MemoryHost:replay_dot(count)
- if self._dot_repeat == nil or self._dot_repeat.callback == nil then
- error("dot repeat is not executable", 2)
- end
- self._pending_operator = self._dot_repeat.operator
- self._mode = self._dot_repeat.mode
- return self._dot_repeat.callback(self._dot_repeat.payload, domain.Count.new(count))
-end
-
-return M