summaryrefslogtreecommitdiff
path: root/lua/clever_f/repeat_resolver.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/clever_f/repeat_resolver.lua')
-rw-r--r--lua/clever_f/repeat_resolver.lua333
1 files changed, 0 insertions, 333 deletions
diff --git a/lua/clever_f/repeat_resolver.lua b/lua/clever_f/repeat_resolver.lua
deleted file mode 100644
index a5374d6..0000000
--- a/lua/clever_f/repeat_resolver.lua
+++ /dev/null
@@ -1,333 +0,0 @@
-local domain = require("clever_f.domain")
-local sequence_state = require("clever_f.sequence_state")
-local state_transitions = require("clever_f.state_transitions")
-
-local M = {}
-local RepeatResolver = {}
-M.RepeatResolver = RepeatResolver
-
-M.Decision = {
- ACQUIRE = "acquire",
- REPEAT = "repeat",
-}
-M.ACQUIRE = M.Decision.ACQUIRE
-M.REPEAT = M.Decision.REPEAT
-
-local resolver_records = setmetatable({}, { __mode = "k" })
-
-local function fail(message, level)
- error(message, (level or 1) + 1)
-end
-
-local function is_nonnegative_integer(value)
- return type(value) == "number"
- and value >= 0
- and value < math.huge
- and value == math.floor(value)
-end
-
-local function require_policy(service)
- if service ~= nil and (type(service) ~= "table"
- or type(service.sample_timeouts) ~= "function"
- or type(service.sample_direction) ~= "function")
- then
- fail("RepeatResolver policy must sample timeouts and direction", 3)
- end
- return service
-end
-
-local function require_clock(clock)
- if clock ~= nil and (type(clock) ~= "table"
- or type(clock.read_time_ms) ~= "function")
- then
- fail("RepeatResolver clock must provide read_time_ms", 3)
- end
- return clock
-end
-
-local function require_transitions(transitions, state)
- transitions = transitions or state_transitions.new(state)
- if type(transitions) ~= "table"
- or type(transitions.SetRepeatTimestamp) ~= "function"
- or type(transitions.PublicReset) ~= "function"
- then
- fail("RepeatResolver transitions must set repeat time and apply Public Reset", 3)
- end
- return transitions
-end
-
-local function finite_time(value)
- return type(value) == "number"
- and value == value
- and value > -math.huge
- and value < math.huge
-end
-
-function M.truncate_elapsed_ms(elapsed_ms)
- if not finite_time(elapsed_ms) then
- fail("elapsed milliseconds must be finite", 2)
- end
- local integer_part = math.modf(elapsed_ms)
- return integer_part
-end
-
-M.truncate_milliseconds = M.truncate_elapsed_ms
-
-local resolver_metatable = {
- __index = RepeatResolver,
- __newindex = function()
- fail("RepeatResolver values are immutable", 2)
- end,
- __tostring = function()
- return "repeat-resolver"
- end,
- __metatable = "clever_f.repeat_resolver.RepeatResolver",
-}
-
-function RepeatResolver.new(options)
- if RepeatResolver.is(options) then
- return options
- end
- if options == nil then
- options = {}
- elseif sequence_state.is(options) then
- options = { state = options }
- elseif type(options) ~= "table" then
- fail("RepeatResolver options must be a table", 2)
- end
-
- local state = options.state or sequence_state.get()
- if not sequence_state.is(state) then
- fail("RepeatResolver state must be the plugin-global SequenceState", 2)
- end
-
- local resolver = setmetatable({}, resolver_metatable)
- resolver_records[resolver] = {
- clock = require_clock(options.clock or options.time_provider or options.host),
- policy = require_policy(options.policy or options.policy_service),
- state = state,
- transitions = require_transitions(
- options.transitions or options.state_transitions,
- state
- ),
- }
- return resolver
-end
-
-function RepeatResolver.is(value)
- return type(value) == "table" and resolver_records[value] ~= nil
-end
-
-function RepeatResolver:previous_landing(context)
- context = domain.ModeContext.from_full_mode(context)
- return resolver_records[self].state:get_previous_landing(context)
-end
-
-function RepeatResolver:decide(context, current_position, macro_state)
- current_position = domain.Position.coerce(current_position)
- local landing = self:previous_landing(context)
- if landing == nil or not domain.Position.equal(landing, current_position) then
- return M.Decision.ACQUIRE
- end
- if domain.MacroState.new(macro_state).executing then
- return M.Decision.ACQUIRE
- end
- return M.Decision.REPEAT
-end
-
-RepeatResolver.eligibility = RepeatResolver.decide
-RepeatResolver.resolve_eligibility = RepeatResolver.decide
-
-function RepeatResolver:sample_repeat_timeout_ms()
- local service = resolver_records[self].policy
- if service == nil then
- fail("RepeatResolver requires a policy to sample repeat timeout", 2)
- end
- local sampled = service:sample_timeouts()
- local timeout = type(sampled) == "table" and sampled.repeat_timeout_ms or nil
- if not is_nonnegative_integer(timeout) then
- fail("repeat_timeout_ms sample must be a nonnegative integer", 2)
- end
- return timeout
-end
-
-RepeatResolver.sample_repeat_timeout = RepeatResolver.sample_repeat_timeout_ms
-
-function RepeatResolver:evaluate_timeout(current_window)
- local timeout = self:sample_repeat_timeout_ms()
- if timeout == 0 then
- return M.Decision.REPEAT, nil
- end
-
- local clock = resolver_records[self].clock
- if clock == nil then
- fail("RepeatResolver requires a clock for positive repeat timeout", 2)
- end
- local current_time = clock:read_time_ms()
- if not finite_time(current_time) then
- fail("repeat clock must return finite milliseconds", 2)
- end
- local record = resolver_records[self]
- local elapsed_ms = M.truncate_elapsed_ms(
- current_time - record.state.repeat_timestamp_ms
- )
- record.transitions:SetRepeatTimestamp(current_time)
- if elapsed_ms <= timeout then
- return M.Decision.REPEAT, elapsed_ms
- end
- local cleanup = record.transitions:PublicReset(current_window)
- return M.Decision.ACQUIRE, elapsed_ms, cleanup
-end
-
-RepeatResolver.check_timeout = RepeatResolver.evaluate_timeout
-RepeatResolver.resolve_timeout = RepeatResolver.evaluate_timeout
-
-local function sampled_fixed_direction(resolver)
- local service = resolver_records[resolver].policy
- if service == nil then
- fail("RepeatResolver requires a policy to resolve primary direction", 3)
- end
- local sampled = service:sample_direction()
- local fixed
- if type(sampled) == "table" then
- fixed = sampled.fix_key_direction
- end
- if type(fixed) ~= "boolean" then
- fail("fix_key_direction sample must be a Boolean", 3)
- end
- return fixed
-end
-
-function M.reverse_request(stored_descriptor, pressed_key, fix_key_direction)
- local stored = domain.Descriptor.from_string(stored_descriptor)
- local pressed = domain.Descriptor.from_string(pressed_key)
- if type(fix_key_direction) ~= "boolean" then
- fail("fix_key_direction must be a Boolean", 2)
- end
-
- local reverse = domain.Descriptor.is_uppercase(pressed)
- if fix_key_direction and domain.Descriptor.is_uppercase(stored) then
- reverse = not reverse
- end
- return reverse
-end
-
-function M.primary_direction(stored_descriptor, pressed_key, fix_key_direction)
- local stored = domain.Descriptor.from_string(stored_descriptor)
- if M.reverse_request(stored, pressed_key, fix_key_direction) then
- return domain.Descriptor.swap(stored)
- end
- return stored
-end
-
-M.resolve_primary_direction = M.primary_direction
-M.effective_primary_descriptor = M.primary_direction
-
-function RepeatResolver:resolve_primary_direction(stored_descriptor, pressed_key)
- return M.primary_direction(
- stored_descriptor,
- pressed_key,
- sampled_fixed_direction(self)
- )
-end
-
-RepeatResolver.primary_direction = RepeatResolver.resolve_primary_direction
-RepeatResolver.effective_primary_descriptor =
- RepeatResolver.resolve_primary_direction
-
-function M.explicit_target(stored_target)
- if stored_target == nil then
- return domain.TargetValue.code_fallback(0)
- end
- return stored_target
-end
-
-local function build_explicit_request(descriptor, stored_target)
- if descriptor == nil then
- return domain.ExplicitRepeatRequest.neutral()
- end
- local target = M.explicit_target(stored_target)
- if target.first_code == 0x80 then
- return domain.ExplicitRepeatRequest.neutral()
- end
- return domain.ExplicitRepeatRequest.new(descriptor, target)
-end
-
-function M.build_same_direction_request(stored_descriptor, stored_target)
- return build_explicit_request(stored_descriptor, stored_target)
-end
-
-M.explicit_same_direction = M.build_same_direction_request
-M.same_direction_request = M.build_same_direction_request
-
-function RepeatResolver:same_direction_request(context)
- context = domain.ModeContext.from_full_mode(context)
- local state = resolver_records[self].state
- return M.build_same_direction_request(
- state:get_previous_descriptor(context),
- state:get_previous_target(context)
- )
-end
-
-RepeatResolver.resolve_explicit_same = RepeatResolver.same_direction_request
-RepeatResolver.explicit_same = RepeatResolver.same_direction_request
-
-function M.build_opposite_direction_request(stored_descriptor, stored_target)
- if stored_descriptor == nil then
- return domain.ExplicitRepeatRequest.neutral()
- end
- return build_explicit_request(
- domain.Descriptor.swap(stored_descriptor),
- stored_target
- )
-end
-
-M.explicit_opposite_direction = M.build_opposite_direction_request
-M.opposite_direction_request = M.build_opposite_direction_request
-
-function RepeatResolver:opposite_direction_request(context)
- context = domain.ModeContext.from_full_mode(context)
- local state = resolver_records[self].state
- return M.build_opposite_direction_request(
- state:get_previous_descriptor(context),
- state:get_previous_target(context)
- )
-end
-
-RepeatResolver.resolve_explicit_opposite =
- RepeatResolver.opposite_direction_request
-RepeatResolver.explicit_opposite = RepeatResolver.opposite_direction_request
-
-function M.new(options)
- return RepeatResolver.new(options)
-end
-
-M.landing = function(context, options)
- return RepeatResolver.new(options):previous_landing(context)
-end
-
-function M.decide(context, current_position, macro_state, options)
- return RepeatResolver.new(options):decide(
- context,
- current_position,
- macro_state
- )
-end
-
-M.eligibility = M.decide
-
-function M.sample_repeat_timeout_ms(options)
- return RepeatResolver.new(options):sample_repeat_timeout_ms()
-end
-
-function M.evaluate_timeout(options, current_window)
- return RepeatResolver.new(options):evaluate_timeout(current_window)
-end
-
-setmetatable(M, {
- __call = function(_, options)
- return RepeatResolver.new(options)
- end,
-})
-
-return M