From 38d3c7480a5db02793dec95250a69b707f6af9d9 Mon Sep 17 00:00:00 2001 From: Jackson Moore Date: Fri, 4 Sep 2026 13:40:39 +0200 Subject: Build live repeated target plan --- lua/clever_f/sequence_coordinator.lua | 39 +++++++++++++++++++++++++++++++++++ tests/run.lua | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/lua/clever_f/sequence_coordinator.lua b/lua/clever_f/sequence_coordinator.lua index 0be5494..51f74d5 100644 --- a/lua/clever_f/sequence_coordinator.lua +++ b/lua/clever_f/sequence_coordinator.lua @@ -5,6 +5,8 @@ local policy = require("clever_f.policy") local repeat_resolver_factory = require("clever_f.repeat_resolver") local sequence_state = require("clever_f.sequence_state") local state_transitions = require("clever_f.state_transitions") +local target_plan_factory = require("clever_f.target_plan") +local text_topology = require("clever_f.text_topology") local M = {} local SequenceCoordinator = {} @@ -69,6 +71,12 @@ function SequenceCoordinator.new(options) transitions = transitions, policy = policy_service, }) + local target_factory = options.target_factory + or options.target_plan_factory + or target_plan_factory.new({ policy = policy_service }) + if type(target_factory) ~= "table" or type(target_factory.build) ~= "function" then + fail("SequenceCoordinator target factory must provide build", 2) + end local acquisition = options.acquisition or options.acquisition_service or acquisition_service_factory.new({ @@ -77,6 +85,7 @@ function SequenceCoordinator.new(options) transitions = transitions, policy = policy_service, feedback = feedback, + target_factory = target_factory, }) if type(acquisition) ~= "table" or type(acquisition.acquire) ~= "function" then fail("SequenceCoordinator acquisition service must provide acquire", 2) @@ -90,6 +99,7 @@ function SequenceCoordinator.new(options) policy = policy_service, repeat_resolver = resolver, feedback = feedback, + target_factory = target_factory, acquisition = acquisition, } return coordinator @@ -261,6 +271,28 @@ function SequenceCoordinator:evaluate_repeat_timeout(invocation) } end +function SequenceCoordinator:build_live_target_plan(target, invocation) + if not domain.TargetValue.is(target) then + fail("primary target planning requires a TargetValue", 2) + end + if type(invocation) ~= "table" or not domain.Position.is(invocation.position) then + fail("primary target planning requires invocation state", 2) + end + local record = coordinator_records[self] + local view = text_topology.from_host(record.host) + local sampled_search = record.policy:sample_search() + local target_plan = record.target_factory:build(target, nil, { + text_view = view, + origin = invocation.position, + search_scope = sampled_search.search_scope, + effective_encoding = view.effective_encoding, + }) + if not domain.TargetPlan.is(target_plan) then + fail("TargetPlanFactory must return a TargetPlan", 2) + end + return target_plan, view, sampled_search.search_scope +end + function SequenceCoordinator:stored_primary_resolution( invocation, pressed_descriptor, @@ -286,6 +318,10 @@ function SequenceCoordinator:stored_primary_resolution( if effective_descriptor.family ~= stored_descriptor.family then fail("primary repetition must preserve the stored motion family", 2) end + local target_plan, text_view, search_scope = self:build_live_target_plan( + stored_target, + invocation + ) return { kind = "repeat", invocation = invocation, @@ -293,6 +329,9 @@ function SequenceCoordinator:stored_primary_resolution( timeout = timeout, stored_descriptor = stored_descriptor, target = stored_target, + target_plan = target_plan, + text_view = text_view, + search_scope = search_scope, effective_descriptor = effective_descriptor, } end diff --git a/tests/run.lua b/tests/run.lua index 3537f0a..da13a3b 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -7442,6 +7442,42 @@ test("Primary repetition preserves the stored motion family", function() end end) +test("Primary repetition builds one target plan from live matching policy", function() + local _, transitions = fresh_sequence_state() + local landing = domain.Position.new(1, 2) + local stored_target = target("a") + transitions:BeginAcquisition("n", "f") + transitions:CommitAcquiredTarget("n", stored_target) + transitions:CommitCommandSuccess("n", landing, true) + local host = MemoryHost.new({ + buffer_lines = { "xAx" }, + cursor = landing, + configuration = { ignore_case = false }, + }) + local underlying = target_plan.new({ policy = policy.new(host) }) + local build_count = 0 + local factory = { + build = function(_, ...) + build_count = build_count + 1 + return underlying:build(...) + end, + } + local coordinator = sequence_coordinator.new({ + host = host, + target_factory = factory, + }) + + local sensitive = coordinator:primary("f") + same(1, build_count) + same(domain.CaseMode.SENSITIVE, sensitive.target_plan.case_mode) + falsy(sensitive.target_plan:matches("A")) + host:set_configuration("ignore_case", true) + local insensitive = coordinator:primary("f") + same(2, build_count) + same(domain.CaseMode.INSENSITIVE, insensitive.target_plan.case_mode) + truthy(insensitive.target_plan:matches("A")) +end) + for _, item in ipairs(tests) do local ok, failure = xpcall(item.body, debug.traceback) if not ok then -- cgit v1.2.3