local domain = require("clever_f.domain") local M = {} local State = {} M.State = State local MAP_FIELDS = { "previous_descriptor", "previous_landing", "first_move", "previous_target", } local MAP_FIELD_SET = {} for _, field in ipairs(MAP_FIELDS) do MAP_FIELD_SET[field] = true end local data = { previous_descriptor = {}, previous_landing = {}, first_move = {}, previous_target = {}, known_contexts = {}, last_input_context = nil, moved_forward = false, moved_forward_initialized = false, migemo_cache = {}, repeat_timestamp_ms = 0, highlight_timer = nil, target_overlays = {}, temporary_overlays = {}, finalizers = {}, } local function fail(message, level) error(message, (level or 1) + 1) end local function normalize_context(context, name) if domain.ModeContext.is(context) then return domain.ModeContext.from_full_mode(context.full_mode) end if type(context) ~= "string" or context == "" then fail((name or "context") .. " must be a ModeContext or full mode string", 2) end return domain.ModeContext.from_full_mode(context) end local function copy_map(source) local result = {} for key, value in pairs(source) do result[key] = value end return result end local function copy_resource(resource) local result = {} for key, value in pairs(resource) do result[key] = value end return result end local function copy_resources(resources) local result = {} for index, resource in ipairs(resources) do result[index] = copy_resource(resource) end return result end local function sorted_contexts() local result = {} for context in pairs(data.known_contexts) do result[#result + 1] = context end table.sort(result, function(left, right) return left.key < right.key end) return result end local function context_record(context) return { context = context, previous_descriptor = data.previous_descriptor[context], previous_landing = data.previous_landing[context], first_move = data.first_move[context], previous_target = data.previous_target[context], } end local function context_records() local result = {} for _, context in ipairs(sorted_contexts()) do result[context.key] = context_record(context) end return result end function State:get_previous_descriptor(context) context = normalize_context(context) return data.previous_descriptor[context] end function State:get_previous_landing(context) context = normalize_context(context) return data.previous_landing[context] end function State:get_first_move(context) context = normalize_context(context) return data.first_move[context] end function State:get_previous_target(context) context = normalize_context(context) return data.previous_target[context] end function State:get_context(context) context = normalize_context(context) return context_record(context) end State.context = State.get_context function State:has_previous_landing(context) return self:get_previous_landing(context) ~= nil end function State:get_migemo(encoding) if type(encoding) ~= "string" or encoding == "" then fail("encoding must be a nonempty string", 2) end return data.migemo_cache[encoding] end function State:target_overlay_identities() local result = {} for index, resource in ipairs(data.target_overlays) do result[index] = resource.identity end return result end function State:temporary_overlay_identities() local result = {} for index, resource in ipairs(data.temporary_overlays) do result[index] = resource.identity end return result end function State:finalizer_identities() local result = {} for index, resource in ipairs(data.finalizers) do result[index] = resource.identity end return result end function State:resources() return { highlight_timer = data.highlight_timer, target_overlays = copy_resources(data.target_overlays), temporary_overlays = copy_resources(data.temporary_overlays), finalizers = copy_resources(data.finalizers), } end function State:snapshot() local snapshot = { contexts = context_records(), last_input_context = data.last_input_context, moved_forward = data.moved_forward, moved_forward_initialized = data.moved_forward_initialized, migemo_cache = copy_map(data.migemo_cache), repeat_timestamp_ms = data.repeat_timestamp_ms, highlight_timer = data.highlight_timer, target_overlays = copy_resources(data.target_overlays), temporary_overlays = copy_resources(data.temporary_overlays), finalizers = copy_resources(data.finalizers), } for _, field in ipairs(MAP_FIELDS) do snapshot[field] = copy_map(data[field]) end return snapshot end local function target_to_table(target) return target and target:to_table() or nil end function State:to_table() local contexts = {} for _, context in ipairs(sorted_contexts()) do local record = context_record(context) contexts[context.key] = { previous_descriptor = record.previous_descriptor and record.previous_descriptor.value or nil, previous_landing = record.previous_landing and record.previous_landing:to_table() or nil, first_move = record.first_move, previous_target = target_to_table(record.previous_target), } end local cache_keys = {} for encoding in pairs(data.migemo_cache) do cache_keys[#cache_keys + 1] = encoding end table.sort(cache_keys) return { contexts = contexts, last_input_context = data.last_input_context and data.last_input_context.key or nil, moved_forward = data.moved_forward, moved_forward_initialized = data.moved_forward_initialized, migemo_cache = cache_keys, repeat_timestamp_ms = data.repeat_timestamp_ms, highlight_timer = data.highlight_timer, target_overlays = self:target_overlay_identities(), temporary_overlays = self:temporary_overlay_identities(), finalizers = self:finalizer_identities(), } end local state local state_metatable = { __index = function(_, key) local method = State[key] if method ~= nil then return method end if MAP_FIELD_SET[key] then return copy_map(data[key]) end if key == "contexts" then return context_records() end if key == "last_input_context" or key == "moved_forward" or key == "moved_forward_initialized" or key == "highlight_timer" then return data[key] end if key == "repeat_timestamp" or key == "repeat_timestamp_ms" then return data.repeat_timestamp_ms end if key == "migemo_cache" then return copy_map(data.migemo_cache) end if key == "target_overlays" or key == "temporary_overlays" or key == "finalizers" then return copy_resources(data[key]) end return nil end, __newindex = function() fail("SequenceState is read-only; use StateTransitions", 2) end, __metatable = "clever_f.sequence_state.State", } state = setmetatable({}, state_metatable) function M.get() return state end function M.new() return state end function M.is(value) return value == state end M.global = state function M._mutate(target, mutation) if target ~= state then fail("StateTransitions must use the plugin-global SequenceState", 2) end if type(mutation) ~= "function" then fail("state mutation must be a function", 2) end return mutation(data) end function M._normalize_context(context) return normalize_context(context) end return M