From 272c9cef5d40c9100968e1250036d74fa9868f71 Mon Sep 17 00:00:00 2001 From: Jackson Moore Date: Fri, 4 Sep 2026 10:19:02 +0200 Subject: Enumerate matching target starts --- lua/clever_f/destination_engine.lua | 24 +++++++++++++++++++++++- tests/run.lua | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/lua/clever_f/destination_engine.lua b/lua/clever_f/destination_engine.lua index 9b528d0..c17dc83 100644 --- a/lua/clever_f/destination_engine.lua +++ b/lua/clever_f/destination_engine.lua @@ -60,9 +60,31 @@ local function calculation_inputs(view, origin, plan, count, first_move) } end +local function next_matching_start(request, origin) + local candidates = request.view:iter_strict( + origin, + request.plan.descriptor.direction, + request.bounds + ) + + while true do + local position, character = candidates() + if position == nil then + return nil + end + if request.plan.target_plan:matches(character, position, request.view) then + return position + end + end +end + function DestinationEngine:calculate(view, origin, plan, count, first_move) local request = calculation_inputs(view, origin, plan, count, first_move) - return domain.SearchOutcome.boundary_before_any(request.origin) + local target_position = next_matching_start(request, request.origin) + if target_position == nil then + return domain.SearchOutcome.boundary_before_any(request.origin) + end + return domain.SearchOutcome.complete(target_position, 1) end function M.new() diff --git a/tests/run.lua b/tests/run.lua index f684d65..e84c12a 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -2201,6 +2201,43 @@ test("DestinationEngine accepts pure calculation inputs", function() end, "valid cursor") end) +test("DestinationEngine enumerates target starts in motion order", function() + local view = text_topology.new({ "abaca" }, "utf-8") + local origin = domain.Position.new(1, 3) + local visits = {} + local target_match = domain.TargetPlan.new({ + target = target("a"), + kind = domain.TargetPlanKind.LITERAL, + case_mode = domain.CaseMode.SENSITIVE, + matcher = function(character, position) + visits[#visits + 1] = character .. ":" .. tostring(position) + return character == "a" + end, + }) + local engine = destination_engine.new() + + local forward = engine:calculate( + view, + origin, + motion_plan.build(target_match, "f"), + 1, + true + ) + same(domain.Position.new(1, 5), forward.endpoint) + list_same({ "c:(1,4)", "a:(1,5)" }, visits) + + visits = {} + local backward = engine:calculate( + view, + origin, + motion_plan.build(target_match, "F"), + 1, + true + ) + same(domain.Position.new(1, 1), backward.endpoint) + list_same({ "b:(1,2)", "a:(1,1)" }, visits) +end) + for _, item in ipairs(tests) do local ok, failure = xpcall(item.body, debug.traceback) if not ok then -- cgit v1.2.3