summaryrefslogtreecommitdiff
path: root/lua/clever_f/sequence_coordinator.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/clever_f/sequence_coordinator.lua')
-rw-r--r--lua/clever_f/sequence_coordinator.lua675
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