summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 10:50:04 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 10:50:04 +0200
commit3b0769600b73db271c8cb694d1c8cf7a00493e1b (patch)
tree6f8ccabb34f3c5225e07c9b2e7a11fbea529dd40
parent26e79b1a99624c43c4149bd09b047af19c615a1c (diff)
Request command feedback migration
-rw-r--r--lua/clever_f/motion_executor.lua44
-rw-r--r--tests/run.lua42
2 files changed, 86 insertions, 0 deletions
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