diff options
Diffstat (limited to 'lua/clever_f/sequence_coordinator.lua')
| -rw-r--r-- | lua/clever_f/sequence_coordinator.lua | 675 |
1 files changed, 0 insertions, 675 deletions
diff --git a/lua/clever_f/sequence_coordinator.lua b/lua/clever_f/sequence_coordinator.lua deleted file mode 100644 index d8cf7f0..0000000 --- a/lua/clever_f/sequence_coordinator.lua +++ /dev/null @@ -1,675 +0,0 @@ -local acquisition_service_factory = require("clever_f.acquisition_service") -local case_policy = require("clever_f.case_policy") -local direct_preview_planner = require("clever_f.direct_preview_planner") -local domain = require("clever_f.domain") -local feedback_service_factory = require("clever_f.feedback_service") -local motion_executor_factory = require("clever_f.motion_executor") -local motion_plan_factory = require("clever_f.motion_plan") -local policy = require("clever_f.policy") -local repeat_resolver_factory = require("clever_f.repeat_resolver") -local sequence_state = require("clever_f.sequence_state") -local state_transitions = require("clever_f.state_transitions") -local target_plan_factory = require("clever_f.target_plan") -local text_topology = require("clever_f.text_topology") - -local M = {} -local SequenceCoordinator = {} -SequenceCoordinator.__index = SequenceCoordinator -M.SequenceCoordinator = SequenceCoordinator - -local coordinator_records = setmetatable({}, { __mode = "k" }) - -local function fail(message, level) - error(message, (level or 1) + 1) -end - -local function descriptor_text(value) - if domain.Descriptor.is(value) then - return value.value - end - return tostring(value) -end - -function M.validate_primary_descriptor(value) - local descriptor = domain.Descriptor.try_from_string(value) - if descriptor == nil then - error("clever-f: Invalid mapping '" .. descriptor_text(value) .. "'", 0) - end - return descriptor -end - -function SequenceCoordinator.new(options) - if SequenceCoordinator.is(options) then - return options - end - if type(options) ~= "table" then - fail("SequenceCoordinator options must be a table", 2) - end - local host = options.host or options - local state = options.state or sequence_state.get() - if not sequence_state.is(state) then - fail("SequenceCoordinator state must be the plugin-global SequenceState", 2) - end - local transitions = options.transitions - or options.state_transitions - or state_transitions.new(state) - local policy_service = options.policy - or options.policy_service - or policy.new(host) - local resolver = options.repeat_resolver - or options.resolver - or repeat_resolver_factory.new({ - state = state, - transitions = transitions, - policy = policy_service, - clock = host, - }) - if type(resolver) ~= "table" or type(resolver.decide) ~= "function" then - fail("SequenceCoordinator repeat resolver must provide decide", 2) - end - local feedback = options.feedback - or options.feedback_service - or feedback_service_factory.new({ - host = host, - state = state, - transitions = transitions, - policy = policy_service, - }) - local lowercase = options.lowercase - if lowercase == nil and type(host.lowercase) == "function" then - lowercase = function(value) - return host:lowercase(value) - end - end - local case_resolver = options.case_resolver - or case_policy.new({ lowercase = lowercase }) - local target_factory = options.target_factory - or options.target_plan_factory - or target_plan_factory.new({ - policy = policy_service, - case_resolver = case_resolver, - }) - if type(target_factory) ~= "table" or type(target_factory.build) ~= "function" then - fail("SequenceCoordinator target factory must provide build", 2) - end - local motion_factory = options.motion_factory - or options.motion_plan_factory - or motion_plan_factory.new({ policy = policy_service }) - if type(motion_factory) ~= "table" - or type(motion_factory.build_for_context) ~= "function" - then - fail("SequenceCoordinator motion factory must build contextual plans", 2) - end - local direct_planner = options.direct_planner - or options.direct_preview_planner - or direct_preview_planner.new({ case_resolver = case_resolver }) - local acquisition = options.acquisition - or options.acquisition_service - or acquisition_service_factory.new({ - host = host, - state = state, - transitions = transitions, - policy = policy_service, - feedback = feedback, - direct_planner = direct_planner, - target_factory = target_factory, - motion_factory = motion_factory, - }) - if type(acquisition) ~= "table" or type(acquisition.acquire) ~= "function" then - fail("SequenceCoordinator acquisition service must provide acquire", 2) - end - local executor = options.motion_executor - or options.executor - or motion_executor_factory.new({ - host = host, - state = state, - transitions = transitions, - feedback = feedback, - }) - if type(executor) ~= "table" or type(executor.execute) ~= "function" then - fail("SequenceCoordinator motion executor must provide execute", 2) - end - - local coordinator = setmetatable({}, SequenceCoordinator) - coordinator_records[coordinator] = { - host = host, - state = state, - transitions = transitions, - policy = policy_service, - repeat_resolver = resolver, - feedback = feedback, - target_factory = target_factory, - motion_factory = motion_factory, - acquisition = acquisition, - motion_executor = executor, - last_primary_resolution = nil, - last_explicit_resolution = nil, - } - return coordinator -end - -function SequenceCoordinator.is(value) - return type(value) == "table" and coordinator_records[value] ~= nil -end - -function SequenceCoordinator:validate_primary_descriptor(value) - return M.validate_primary_descriptor(value) -end - -local function require_primary_reader(host) - if type(host) ~= "table" - or type(host.read_mode) ~= "function" - or type(host.read_cursor) ~= "function" - or type(host.read_count) ~= "function" - or type(host.read_macro_state) ~= "function" - then - fail("SequenceCoordinator host must provide primary action state", 3) - end - return host -end - -function SequenceCoordinator:read_primary_invocation() - local host = require_primary_reader(coordinator_records[self].host) - local context = domain.ModeContext.from_full_mode(host:read_mode()) - local position = domain.Position.coerce(host:read_cursor()) - local count = domain.Count.new(host:read_count()) - local macro_state = domain.MacroState.new(host:read_macro_state()) - return { - context = context, - position = position, - origin = position, - count = count, - macro_state = macro_state, - } -end - -function SequenceCoordinator:inspect_fold_open_policy(invocation) - if type(invocation) ~= "table" or not domain.Position.is(invocation.position) then - fail("fold preflight requires primary invocation state", 2) - end - local host = coordinator_records[self].host - if type(host.read_fold_state) ~= "function" then - fail("SequenceCoordinator host must provide fold state", 2) - end - local fold_state = host:read_fold_state() - if not domain.FoldState.is(fold_state) then - fail("SequenceCoordinator host must return FoldState", 2) - end - return fold_state -end - -local function fold_open_enabled(fold_state) - return fold_state:opens("horizontal") or fold_state:opens("all") -end - -function SequenceCoordinator:open_enclosing_folds(invocation, fold_state) - if type(invocation) ~= "table" or not domain.Position.is(invocation.position) then - fail("fold opening requires primary invocation state", 2) - end - if not domain.FoldState.is(fold_state) then - fail("fold opening requires FoldState", 2) - end - if not fold_open_enabled(fold_state) then - return 0 - end - - local host = coordinator_records[self].host - if type(host.open_fold) ~= "function" then - fail("SequenceCoordinator host must open folds", 2) - end - local opened = 0 - while fold_state.closed_levels > 0 do - if host:open_fold(invocation.position) ~= true then - break - end - opened = opened + 1 - fold_state = self:inspect_fold_open_policy(invocation) - end - return opened -end - -function SequenceCoordinator:decide_primary(invocation) - if type(invocation) ~= "table" - or not domain.ModeContext.is(invocation.context) - or not domain.Position.is(invocation.position) - or not domain.MacroState.is(invocation.macro_state) - then - fail("primary decision requires invocation state", 2) - end - return coordinator_records[self].repeat_resolver:decide( - invocation.context, - invocation.position, - invocation.macro_state - ) -end - -function SequenceCoordinator:acquire_primary(descriptor, invocation) - descriptor = self:validate_primary_descriptor(descriptor) - if type(invocation) ~= "table" then - fail("primary acquisition requires invocation state", 2) - end - return coordinator_records[self].acquisition:acquire( - descriptor, - invocation.context, - invocation.position, - invocation.count, - invocation.macro_state - ) -end - -function SequenceCoordinator:fresh_primary_resolution( - initiating_descriptor, - acquisition_result, - invocation -) - initiating_descriptor = self:validate_primary_descriptor(initiating_descriptor) - if type(acquisition_result) ~= "table" - or acquisition_result.resolved ~= true - or not domain.TargetValue.is(acquisition_result.target) - or not domain.TargetPlan.is(acquisition_result.target_plan) - or not domain.ResolvedMotionPlan.is(acquisition_result.motion_plan) - then - fail("fresh primary resolution requires acquired motion plans", 2) - end - return { - kind = "fresh", - invocation = invocation, - acquisition_result = acquisition_result, - target = acquisition_result.target, - target_plan = acquisition_result.target_plan, - motion_plan = acquisition_result.motion_plan, - effective_descriptor = initiating_descriptor, - first_move = coordinator_records[self].state:get_first_move( - invocation.context - ) == true, - skip_destination = acquisition_result.target_plan.kind - == domain.TargetPlanKind.EMPTY, - } -end - -function SequenceCoordinator:resolve_acquisition(descriptor, invocation) - local result = self:acquire_primary(descriptor, invocation) - if type(result.has_outcome) ~= "function" then - fail("AcquisitionService must return an AcquisitionResult", 2) - end - if result:has_outcome() then - return result.outcome - end - return self:fresh_primary_resolution(descriptor, result, invocation) -end - -function SequenceCoordinator:evaluate_repeat_timeout(invocation) - if type(invocation) ~= "table" then - fail("repeat timeout requires primary invocation state", 2) - end - local record = coordinator_records[self] - if type(record.repeat_resolver.evaluate_timeout) ~= "function" then - fail("SequenceCoordinator repeat resolver must evaluate timeout", 2) - end - local window = record.host:read_window() - local decision, elapsed_ms, cleanup = - record.repeat_resolver:evaluate_timeout(window) - return { - decision = decision, - elapsed_ms = elapsed_ms, - cleanup = cleanup, - window = window, - } -end - -function SequenceCoordinator:build_live_target_plan(target, invocation) - if not domain.TargetValue.is(target) then - fail("primary target planning requires a TargetValue", 2) - end - if type(invocation) ~= "table" or not domain.Position.is(invocation.position) then - fail("primary target planning requires invocation state", 2) - end - local record = coordinator_records[self] - local view = text_topology.from_host(record.host) - local sampled_search = record.policy:sample_search() - local target_plan = record.target_factory:build(target, nil, { - text_view = view, - origin = invocation.position, - search_scope = sampled_search.search_scope, - effective_encoding = view.effective_encoding, - }) - if not domain.TargetPlan.is(target_plan) then - fail("TargetPlanFactory must return a TargetPlan", 2) - end - return target_plan, view, sampled_search.search_scope -end - -function SequenceCoordinator:build_movement_plan( - target_plan, - effective_descriptor, - invocation, - search_scope -) - if not domain.TargetPlan.is(target_plan) then - fail("movement planning requires a TargetPlan", 2) - end - if type(invocation) ~= "table" or not domain.ModeContext.is(invocation.context) then - fail("movement planning requires invocation state", 2) - end - local record = coordinator_records[self] - local selection = invocation.context.visual and record.host:read_selection() or nil - local motion_plan = record.motion_factory:build_for_context( - target_plan, - effective_descriptor, - invocation.context, - selection, - search_scope - ) - if not domain.ResolvedMotionPlan.is(motion_plan) then - fail("MotionPlanFactory must return a ResolvedMotionPlan", 2) - end - return motion_plan -end - -function SequenceCoordinator:restore_repeated_feedback(resolution) - if type(resolution) ~= "table" - or not domain.TargetPlan.is(resolution.target_plan) - or not domain.ResolvedMotionPlan.is(resolution.motion_plan) - then - fail("feedback restoration requires a repeated primary resolution", 2) - end - local feedback = coordinator_records[self].feedback - if type(feedback.restore_primary) ~= "function" then - fail("FeedbackService must restore primary feedback", 2) - end - return feedback:restore_primary({ - context = resolution.invocation.context, - anchor = resolution.invocation.position, - target_plan = resolution.target_plan, - motion_plan = resolution.motion_plan, - stored_descriptor = resolution.stored_descriptor, - endpoint_policy = resolution.motion_plan.endpoint_policy, - text_view = resolution.text_view, - window = resolution.timeout.window, - }) -end - -function SequenceCoordinator:stored_primary_resolution( - invocation, - pressed_descriptor, - timeout -) - if type(invocation) ~= "table" or not domain.ModeContext.is(invocation.context) then - fail("stored primary resolution requires invocation state", 2) - end - pressed_descriptor = self:validate_primary_descriptor(pressed_descriptor) - local state = coordinator_records[self].state - local stored_descriptor = state:get_previous_descriptor(invocation.context) - local stored_target = state:get_previous_target(invocation.context) - if stored_descriptor == nil or stored_target == nil then - fail("repeat-eligible primary state must contain descriptor and target", 2) - end - local resolver = coordinator_records[self].repeat_resolver - if type(resolver.resolve_primary_direction) ~= "function" then - fail("SequenceCoordinator repeat resolver must resolve primary direction", 2) - end - local effective_descriptor = domain.Descriptor.from_string( - resolver:resolve_primary_direction(stored_descriptor, pressed_descriptor) - ) - if effective_descriptor.family ~= stored_descriptor.family then - fail("primary repetition must preserve the stored motion family", 2) - end - local target_plan, text_view, search_scope = self:build_live_target_plan( - stored_target, - invocation - ) - local motion_plan = self:build_movement_plan( - target_plan, - effective_descriptor, - invocation, - search_scope - ) - local resolution = { - kind = "repeat", - invocation = invocation, - pressed_descriptor = pressed_descriptor, - timeout = timeout, - stored_descriptor = stored_descriptor, - target = stored_target, - target_plan = target_plan, - motion_plan = motion_plan, - text_view = text_view, - search_scope = search_scope, - effective_descriptor = effective_descriptor, - first_move = state:get_first_move(invocation.context) == true, - } - resolution.restored_feedback = self:restore_repeated_feedback(resolution) - return resolution -end - -function SequenceCoordinator:refresh_primary_feedback(resolution) - if type(resolution) ~= "table" or not domain.TargetValue.is(resolution.target) then - fail("primary feedback refresh requires a resolved target", 2) - end - local record = coordinator_records[self] - if type(record.feedback.refresh_primary) ~= "function" then - fail("FeedbackService must refresh primary feedback", 2) - end - local window = resolution.timeout and resolution.timeout.window - or record.host:read_window() - return record.feedback:refresh_primary(resolution.target, window) -end - -function SequenceCoordinator:execute_resolved_motion(resolution, execution_options) - if type(resolution) ~= "table" - or not domain.ModeContext.is(resolution.invocation.context) - or not domain.ResolvedMotionPlan.is(resolution.motion_plan) - then - fail("motion execution requires a resolved motion", 2) - end - if resolution.skip_destination then - return domain.ActionOutcome.empty(resolution.invocation.position) - end - local record = coordinator_records[self] - local view = resolution.text_view or text_topology.from_host(record.host) - local outcome = record.motion_executor:execute( - view, - resolution.invocation.context, - resolution.motion_plan, - resolution.invocation.count, - resolution.first_move, - execution_options - ) - if not domain.ActionOutcome.is(outcome) then - fail("MotionExecutor must return an ActionOutcome", 2) - end - return outcome -end - -function SequenceCoordinator:execute_primary_resolution(resolution) - local record = coordinator_records[self] - record.last_primary_resolution = resolution - local outcome = self:execute_resolved_motion(resolution) - resolution.highlight_timer = self:refresh_primary_feedback(resolution) - return outcome -end - -function SequenceCoordinator:last_primary_resolution() - return coordinator_records[self].last_primary_resolution -end - -function SequenceCoordinator:last_explicit_resolution() - return coordinator_records[self].last_explicit_resolution -end - -function SequenceCoordinator:reset() - local record = coordinator_records[self] - local position = domain.Position.coerce(record.host:read_cursor()) - local cleanup = record.transitions:PublicReset(record.host:read_window()) - if type(record.feedback.release_transition_cleanup) ~= "function" then - fail("FeedbackService must release reset cleanup", 2) - end - record.feedback:release_transition_cleanup(cleanup) - return domain.ActionOutcome.neutral(position) -end - -SequenceCoordinator.Reset = SequenceCoordinator.reset - -function SequenceCoordinator:diagnostic_full_reset() - local record = coordinator_records[self] - local position = domain.Position.coerce(record.host:read_cursor()) - local cleanup = record.transitions:DiagnosticFullReset( - record.host:read_window() - ) - if type(record.feedback.release_transition_cleanup) ~= "function" then - fail("FeedbackService must release diagnostic cleanup", 2) - end - record.feedback:release_transition_cleanup(cleanup) - return domain.ActionOutcome.neutral(position) -end - -SequenceCoordinator.DiagnosticFullReset = - SequenceCoordinator.diagnostic_full_reset - -function SequenceCoordinator:read_explicit_invocation() - local host = coordinator_records[self].host - if type(host) ~= "table" - or type(host.read_mode) ~= "function" - or type(host.read_cursor) ~= "function" - or type(host.read_count) ~= "function" - then - fail("SequenceCoordinator host must provide explicit action state", 2) - end - local position = domain.Position.coerce(host:read_cursor()) - return { - context = domain.ModeContext.from_full_mode(host:read_mode()), - position = position, - origin = position, - count = domain.Count.new(host:read_count()), - } -end - -function SequenceCoordinator:resolve_explicit(kind, resolver_method) - if type(kind) ~= "string" or kind == "" then - fail("explicit repeat kind must be a nonempty string", 2) - end - if type(resolver_method) ~= "string" or resolver_method == "" then - fail("explicit repeat resolver method must be a nonempty string", 2) - end - local invocation = self:read_explicit_invocation() - local resolver = coordinator_records[self].repeat_resolver - if type(resolver[resolver_method]) ~= "function" then - fail("RepeatResolver must build " .. kind .. " requests", 2) - end - local request = resolver[resolver_method](resolver, invocation.context) - local resolution = { - kind = kind, - invocation = invocation, - request = request, - } - if request.neutral then - return resolution - end - local target_plan, text_view, search_scope = self:build_live_target_plan( - request.target, - invocation - ) - local motion_plan = self:build_movement_plan( - target_plan, - request.descriptor, - invocation, - search_scope - ) - resolution.target = request.target - resolution.target_plan = target_plan - resolution.motion_plan = motion_plan - resolution.text_view = text_view - resolution.search_scope = search_scope - resolution.effective_descriptor = request.descriptor - resolution.first_move = coordinator_records[self].state:get_first_move( - invocation.context - ) == true - resolution.skip_destination = target_plan.kind == domain.TargetPlanKind.EMPTY - return resolution -end - -function SequenceCoordinator:resolve_explicit_same() - return self:resolve_explicit("explicit_same", "same_direction_request") -end - -function SequenceCoordinator:resolve_explicit_opposite() - return self:resolve_explicit( - "explicit_opposite", - "opposite_direction_request" - ) -end - -function SequenceCoordinator:primary(value) - local descriptor = self:validate_primary_descriptor(value) - local invocation = self:read_primary_invocation() - invocation.fold_state = self:inspect_fold_open_policy(invocation) - invocation.opened_folds = self:open_enclosing_folds( - invocation, - invocation.fold_state - ) - invocation.repeat_decision = self:decide_primary(invocation) - if invocation.repeat_decision == repeat_resolver_factory.Decision.ACQUIRE then - local acquired = self:resolve_acquisition(descriptor, invocation) - if domain.ActionOutcome.is(acquired) then - return acquired - end - return self:execute_primary_resolution(acquired) - end - local timeout = self:evaluate_repeat_timeout(invocation) - if timeout.decision == repeat_resolver_factory.Decision.ACQUIRE then - local record = coordinator_records[self] - if timeout.cleanup ~= nil then - if type(record.feedback.release_transition_cleanup) ~= "function" then - fail("FeedbackService must release reset cleanup", 2) - end - record.feedback:release_transition_cleanup(timeout.cleanup) - end - local acquired = self:resolve_acquisition(descriptor, invocation) - if domain.ActionOutcome.is(acquired) then - return acquired - end - return self:execute_primary_resolution(acquired) - end - return self:execute_primary_resolution( - self:stored_primary_resolution(invocation, descriptor, timeout) - ) -end - -function SequenceCoordinator:execute_explicit_resolution(resolution) - if type(resolution) ~= "table" - or not domain.ExplicitRepeatRequest.is(resolution.request) - then - fail("explicit execution requires a resolved repeat request", 2) - end - coordinator_records[self].last_explicit_resolution = resolution - if resolution.request.neutral then - return domain.ActionOutcome.empty(resolution.invocation.position) - end - return self:execute_resolved_motion(resolution) -end - -function SequenceCoordinator:repeat_same_direction() - return self:execute_explicit_resolution(self:resolve_explicit_same()) -end - -SequenceCoordinator.RepeatSameDirection = - SequenceCoordinator.repeat_same_direction - -function SequenceCoordinator:repeat_opposite_direction() - return self:execute_explicit_resolution(self:resolve_explicit_opposite()) -end - -SequenceCoordinator.RepeatOppositeDirection = - SequenceCoordinator.repeat_opposite_direction - -function M.new(options) - return SequenceCoordinator.new(options) -end - -setmetatable(M, { - __call = function(_, options) - return SequenceCoordinator.new(options) - end, -}) - -return M |
