diff options
Diffstat (limited to 'lua/clever_f/motion_executor.lua')
| -rw-r--r-- | lua/clever_f/motion_executor.lua | 436 |
1 files changed, 0 insertions, 436 deletions
diff --git a/lua/clever_f/motion_executor.lua b/lua/clever_f/motion_executor.lua deleted file mode 100644 index 79cc2ab..0000000 --- a/lua/clever_f/motion_executor.lua +++ /dev/null @@ -1,436 +0,0 @@ -local destination_engine = require("clever_f.destination_engine") -local domain = require("clever_f.domain") -local sequence_state = require("clever_f.sequence_state") -local state_transitions = require("clever_f.state_transitions") -local text_topology = require("clever_f.text_topology") - -local M = {} -local MotionExecutor = {} -M.MotionExecutor = MotionExecutor - -M.ExecutionPath = { - VISUAL = "visual", - COMMAND = "command", -} - -local executor_records = setmetatable({}, { __mode = "k" }) - -local function fail(message, level) - error(message, (level or 1) + 1) -end - -function M.execution_path(context) - context = domain.ModeContext.from_full_mode(context) - if context.visual_kind ~= nil then - return M.ExecutionPath.VISUAL - end - return M.ExecutionPath.COMMAND -end - -function M.moved_forward(origin, destination) - origin = domain.Position.coerce(origin) - destination = domain.Position.coerce(destination) - return domain.Position.compare(destination, origin) > 0 -end - -function M.command_moved_forward(descriptor, origin, destination) - descriptor = domain.Descriptor.from_string(descriptor) - origin = domain.Position.coerce(origin) - destination = domain.Position.coerce(destination) - if descriptor.family == domain.Family.TILL - and domain.Position.stationary(origin, destination) - then - return false - end - return M.moved_forward(origin, destination) -end - -function M.create_dot_payload(plan) - if not domain.ResolvedMotionPlan.is(plan) then - fail("dot payload plan must be a ResolvedMotionPlan", 2) - end - return domain.DotPayload.new(plan.descriptor, plan.target_plan.target) -end - -function M.plan_for_dot_payload(plan, payload) - if not domain.ResolvedMotionPlan.is(plan) then - fail("dot replay plan must be a ResolvedMotionPlan", 2) - end - if not domain.DotPayload.is(payload) then - fail("dot replay payload must be a DotPayload", 2) - end - if payload.target ~= plan.target_plan.target then - fail("dot replay payload target must match its resolved target plan", 2) - end - return domain.ResolvedMotionPlan.new({ - target_plan = plan.target_plan, - descriptor = payload.descriptor, - search_scope = plan.search_scope, - endpoint_policy = plan.endpoint_policy, - }) -end - -local function copy_options(options) - local result = {} - for key, value in pairs(options or {}) do - result[key] = value - end - return result -end - -local function normalize_options(options, dependencies) - if MotionExecutor.is(options) and dependencies == nil then - return options - end - if type(options) ~= "table" then - fail("MotionExecutor options must be a table", 3) - end - if options.host ~= nil then - if dependencies ~= nil then - fail("MotionExecutor dependencies must be part of its options", 3) - end - return options - end - local result = copy_options(dependencies) - result.host = options - return result -end - -local function require_host(host) - if type(host) ~= "table" - or type(host.read_cursor) ~= "function" - or type(host.read_encoding) ~= "function" - or type(host.read_pending_operator) ~= "function" - or type(host.read_selection) ~= "function" - or type(host.read_text) ~= "function" - or type(host.apply_cursor) ~= "function" - or type(host.apply_selection) ~= "function" - or type(host.set_operator_inclusive) ~= "function" - or type(host.register_dot_repeat) ~= "function" - then - fail("MotionExecutor host must provide movement state", 3) - end - return host -end - -local function require_destination_engine(engine) - engine = engine or destination_engine.new() - if type(engine) ~= "table" or type(engine.calculate) ~= "function" then - fail("MotionExecutor destination engine must provide calculate", 3) - end - return engine -end - -local function require_feedback_service(service) - if service ~= nil and (type(service) ~= "table" - or type(service.migrate_command) ~= "function") - then - fail("MotionExecutor feedback service must provide migrate_command", 3) - end - return service -end - -local function require_state(state) - state = state or sequence_state.get() - if not sequence_state.is(state) then - fail("MotionExecutor state must be the plugin-global SequenceState", 3) - end - return state -end - -local function require_transitions(transitions, state) - transitions = transitions or state_transitions.new(state) - if type(transitions) ~= "table" - or type(transitions.CommitCommandSuccess) ~= "function" - or type(transitions.CommitVisualSuccess) ~= "function" - then - fail("MotionExecutor transitions must commit motion success", 3) - end - return transitions -end - -local executor_metatable = { - __index = MotionExecutor, - __newindex = function() - fail("MotionExecutor values are immutable", 2) - end, - __tostring = function() - return "motion-executor" - end, - __metatable = "clever_f.motion_executor.MotionExecutor", -} - -function MotionExecutor.new(options, dependencies) - options = normalize_options(options, dependencies) - if MotionExecutor.is(options) then - return options - end - - local executor = setmetatable({}, executor_metatable) - local state = require_state(options.state) - executor_records[executor] = { - host = require_host(options.host), - destination_engine = require_destination_engine( - options.destination_engine or options.engine - ), - feedback_service = require_feedback_service( - options.feedback_service or options.feedback - ), - state = state, - transitions = require_transitions( - options.transitions or options.state_transitions, - state - ), - } - return executor -end - -function MotionExecutor.is(value) - return type(value) == "table" and executor_records[value] ~= nil -end - -local function execution_request( - view, - context, - plan, - count, - first_move, - execution_options -) - if not text_topology.TextView.is(view) then - fail("motion execution view must be a TextView", 3) - end - context = domain.ModeContext.from_full_mode(context) - if not domain.ResolvedMotionPlan.is(plan) then - fail("motion execution plan must be a ResolvedMotionPlan", 3) - end - count = domain.Count.new(count) - if type(first_move) ~= "boolean" then - fail("motion execution first_move must be a Boolean", 3) - end - execution_options = execution_options or {} - if type(execution_options) ~= "table" then - fail("motion execution options must be a table", 3) - end - local dot_payload = execution_options.dot_payload - if dot_payload ~= nil and not domain.DotPayload.is(dot_payload) then - fail("motion execution dot_payload must be a DotPayload", 3) - end - local register_dot_repeat = execution_options.register_dot_repeat - if register_dot_repeat == nil then - register_dot_repeat = true - elseif type(register_dot_repeat) ~= "boolean" then - fail("motion execution register_dot_repeat must be a Boolean", 3) - end - return { - view = view, - context = context, - plan = plan, - count = count, - first_move = first_move, - dot_payload = dot_payload, - register_dot_repeat = register_dot_repeat, - } -end - -local function calculate(executor, request, origin) - return executor_records[executor].destination_engine:calculate( - request.view, - origin, - request.plan, - request.count, - request.first_move - ) -end - -local function command_action( - host, - outcome, - descriptor, - dot_payload, - use_current_position -) - return domain.ActionOutcome.new({ - kind = outcome.complete - and domain.ActionKind.MOVEMENT - or domain.ActionKind.FAILED_SEARCH, - position = use_current_position and host:read_cursor() or outcome.endpoint, - search_outcome = outcome, - effective_descriptor = descriptor, - dot_payload = dot_payload, - }) -end - -local function register_dot_replay(executor, request, payload) - local host = executor_records[executor].host - host:register_dot_repeat(payload, function(replayed_payload, replay_count) - return executor:execute_dot( - text_topology.from_host(host), - request.context, - request.plan, - replayed_payload, - replay_count - ) - end) -end - -local function migrate_command_feedback(executor, request, origin, outcome) - local record = executor_records[executor] - local feedback = record.feedback_service - if feedback == nil then - return - end - feedback:migrate_command({ - context = request.context, - origin = origin, - destination = outcome.endpoint, - plan = request.plan, - resolved_motion_plan = request.plan, - outcome = outcome, - count = request.count, - first_move = request.first_move, - moved_forward = request.moved_forward, - previous_moved_forward = record.state.moved_forward, - previous_moved_forward_initialized = record.state.moved_forward_initialized, - }) -end - -function MotionExecutor:_execute_command(request) - local host = executor_records[self].host - local origin = host:read_cursor() - local pending_operator = request.context.operator - and host:read_pending_operator() - or nil - local outcome = calculate(self, request, origin) - if outcome.successful_steps > 0 then - if request.context.operator - and request.plan.descriptor.direction == domain.Direction.FORWARD - then - host:set_operator_inclusive(true) - end - host:apply_cursor(outcome.endpoint, { - context = request.context, - descriptor = request.plan.descriptor, - origin = origin, - }) - end - if not outcome.complete then - return command_action( - host, - outcome, - request.plan.descriptor, - nil, - pending_operator ~= nil and pending_operator ~= "" - ) - end - request.moved_forward = M.command_moved_forward( - request.plan.descriptor, - origin, - outcome.endpoint - ) - migrate_command_feedback(self, request, origin, outcome) - executor_records[self].transitions:CommitCommandSuccess( - request.context, - outcome.endpoint, - request.moved_forward - ) - local dot_payload - if pending_operator ~= nil and pending_operator ~= "" then - dot_payload = request.dot_payload or M.create_dot_payload(request.plan) - if request.register_dot_repeat then - register_dot_replay(self, request, dot_payload) - end - end - return command_action( - host, - outcome, - request.plan.descriptor, - dot_payload, - pending_operator ~= nil and pending_operator ~= "" - ) -end - -function MotionExecutor:_execute_visual(request) - local host = executor_records[self].host - local selection = host:read_selection() - if not domain.Selection.is(selection) - or not selection.active - or selection.kind ~= request.context.visual_kind - then - fail("Visual motion execution requires its active selection kind", 2) - end - local origin = host:read_cursor() - local outcome = calculate(self, request, origin) - request.selection = selection - if outcome.successful_steps > 0 then - host:apply_selection(selection:with_focus(outcome.endpoint)) - end - if not outcome.complete then - return domain.ActionOutcome.from_search(outcome, request.plan.descriptor) - end - executor_records[self].transitions:CommitVisualSuccess( - request.context, - outcome.endpoint - ) - return domain.ActionOutcome.from_search(outcome, request.plan.descriptor) -end - -function MotionExecutor:execute( - view, - context, - plan, - count, - first_move, - execution_options -) - local request = execution_request( - view, - context, - plan, - count, - first_move, - execution_options - ) - if M.execution_path(request.context) == M.ExecutionPath.VISUAL then - return self:_execute_visual(request) - end - return self:_execute_command(request) -end - -function MotionExecutor:execute_dot(view, context, plan, payload, count) - return self:execute( - view, - context, - M.plan_for_dot_payload(plan, payload), - count, - false, - { - dot_payload = payload, - register_dot_repeat = false, - } - ) -end - -function M.new(options, dependencies) - return MotionExecutor.new(options, dependencies) -end - -function M.execute(host, view, context, plan, count, first_move, dependencies) - return MotionExecutor.new(host, dependencies):execute( - view, - context, - plan, - count, - first_move - ) -end - -M.run = M.execute - -setmetatable(M, { - __call = function(_, options, dependencies) - return MotionExecutor.new(options, dependencies) - end, -}) - -return M |
