summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 13:41:10 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 13:41:10 +0200
commit15ed87626e8774ea2a54a82ff3bf9ac399ba4fc0 (patch)
tree26bd73d45a47978998f7ff112437f2fbbf98dcbd
parent38d3c7480a5db02793dec95250a69b707f6af9d9 (diff)
Build repeated movement plan
-rw-r--r--lua/clever_f/sequence_coordinator.lua45
-rw-r--r--tests/run.lua30
2 files changed, 75 insertions, 0 deletions
diff --git a/lua/clever_f/sequence_coordinator.lua b/lua/clever_f/sequence_coordinator.lua
index 51f74d5..c5ee808 100644
--- a/lua/clever_f/sequence_coordinator.lua
+++ b/lua/clever_f/sequence_coordinator.lua
@@ -1,6 +1,7 @@
local acquisition_service_factory = require("clever_f.acquisition_service")
local domain = require("clever_f.domain")
local feedback_service_factory = require("clever_f.feedback_service")
+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")
@@ -77,6 +78,14 @@ function SequenceCoordinator.new(options)
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 acquisition = options.acquisition
or options.acquisition_service
or acquisition_service_factory.new({
@@ -86,6 +95,7 @@ function SequenceCoordinator.new(options)
policy = policy_service,
feedback = feedback,
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)
@@ -100,6 +110,7 @@ function SequenceCoordinator.new(options)
repeat_resolver = resolver,
feedback = feedback,
target_factory = target_factory,
+ motion_factory = motion_factory,
acquisition = acquisition,
}
return coordinator
@@ -293,6 +304,33 @@ function SequenceCoordinator:build_live_target_plan(target, invocation)
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:stored_primary_resolution(
invocation,
pressed_descriptor,
@@ -322,6 +360,12 @@ function SequenceCoordinator:stored_primary_resolution(
stored_target,
invocation
)
+ local motion_plan = self:build_movement_plan(
+ target_plan,
+ effective_descriptor,
+ invocation,
+ search_scope
+ )
return {
kind = "repeat",
invocation = invocation,
@@ -330,6 +374,7 @@ function SequenceCoordinator:stored_primary_resolution(
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,
diff --git a/tests/run.lua b/tests/run.lua
index da13a3b..eb82d0b 100644
--- a/tests/run.lua
+++ b/tests/run.lua
@@ -7478,6 +7478,36 @@ test("Primary repetition builds one target plan from live matching policy", func
truthy(insensitive.target_plan:matches("A"))
end)
+test("Primary repetition builds its effective contextual motion plan", function()
+ local _, transitions = fresh_sequence_state()
+ local landing = domain.Position.new(1, 3)
+ transitions:BeginAcquisition("v", "t")
+ transitions:CommitAcquiredTarget("v", target("a"))
+ transitions:CommitVisualSuccess("v", landing)
+ local host = MemoryHost.new({
+ buffer_lines = { "ababa" },
+ cursor = landing,
+ mode = "v",
+ selection = domain.Selection.active(
+ domain.SelectionKind.CHARACTER,
+ domain.Position.new(1, 1),
+ landing,
+ domain.SelectionOption.EXCLUSIVE
+ ),
+ })
+
+ local resolution = sequence_coordinator.new({ host = host }):primary("F")
+
+ same(resolution.target_plan, resolution.motion_plan.target_plan)
+ same(domain.Descriptor.TILL_BACKWARD, resolution.effective_descriptor)
+ same(resolution.effective_descriptor, resolution.motion_plan.descriptor)
+ same(domain.SearchScope.BUFFER, resolution.motion_plan.search_scope)
+ same(
+ domain.EndpointPolicy.VISUAL_EXCLUSIVE,
+ resolution.motion_plan.endpoint_policy
+ )
+end)
+
for _, item in ipairs(tests) do
local ok, failure = xpcall(item.body, debug.traceback)
if not ok then