From 3b0769600b73db271c8cb694d1c8cf7a00493e1b Mon Sep 17 00:00:00 2001 From: Jackson Moore Date: Fri, 4 Sep 2026 10:50:04 +0200 Subject: Request command feedback migration --- lua/clever_f/motion_executor.lua | 44 ++++++++++++++++++++++++++++++++++++++++ tests/run.lua | 42 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/lua/clever_f/motion_executor.lua b/lua/clever_f/motion_executor.lua index b66f0b7..cc3cc56 100644 --- a/lua/clever_f/motion_executor.lua +++ b/lua/clever_f/motion_executor.lua @@ -1,5 +1,6 @@ local destination_engine = require("clever_f.destination_engine") local domain = require("clever_f.domain") +local sequence_state = require("clever_f.sequence_state") local text_topology = require("clever_f.text_topology") local M = {} @@ -87,6 +88,23 @@ local function require_destination_engine(engine) 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 executor_metatable = { __index = MotionExecutor, __newindex = function() @@ -110,6 +128,10 @@ function MotionExecutor.new(options, dependencies) destination_engine = require_destination_engine( options.destination_engine or options.engine ), + feedback_service = require_feedback_service( + options.feedback_service or options.feedback + ), + state = require_state(options.state), } return executor end @@ -149,6 +171,27 @@ local function calculate(executor, request, origin) ) 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() @@ -164,6 +207,7 @@ function MotionExecutor:_execute_command(request) origin, outcome.endpoint ) + migrate_command_feedback(self, request, origin, outcome) return domain.ActionOutcome.from_search(outcome, request.plan.descriptor) end diff --git a/tests/run.lua b/tests/run.lua index 2b1613c..09a0375 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -3269,6 +3269,48 @@ test("A stationary first TILL completion is not forward movement", function() same(1, outcome.successful_steps) end) +test("Command success requests feedback migration before state commit", function() + local state, transitions = fresh_sequence_state() + local old_landing = domain.Position.new(1, 1) + transitions:BeginAcquisition("n", "f") + transitions:CommitAcquiredTarget("n", target("a")) + transitions:CommitCommandSuccess("n", old_landing, false) + + local host = MemoryHost.new({ + buffer_lines = { "aba" }, + cursor = old_landing, + }) + local observed + local feedback = { + migrate_command = function(_, request) + observed = { + request = request, + cursor = host:read_cursor(), + landing = state:get_previous_landing("n"), + moved_forward = state.moved_forward, + } + end, + } + local plan = motion_plan.build( + target_plan.build(target("a"), matching_policy()), + "f" + ) + local outcome = motion_executor.new({ + host = host, + feedback_service = feedback, + }):execute(text_topology.from_host(host), "n", plan, 1, false) + + same(domain.ActionKind.MOVEMENT, outcome.kind) + same(old_landing, observed.request.origin) + same(domain.Position.new(1, 3), observed.request.destination) + same(plan, observed.request.resolved_motion_plan) + truthy(observed.request.moved_forward) + falsy(observed.request.previous_moved_forward) + same(domain.Position.new(1, 3), observed.cursor) + same(old_landing, observed.landing) + falsy(observed.moved_forward) +end) + for _, item in ipairs(tests) do local ok, failure = xpcall(item.body, debug.traceback) if not ok then -- cgit v1.2.3