diff options
Diffstat (limited to 'lua/clever_f/repeat_resolver.lua')
| -rw-r--r-- | lua/clever_f/repeat_resolver.lua | 333 |
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 |
