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