summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 10:25:07 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 10:25:07 +0200
commitf04282c17fa96e88e939e17d823489dbf10594e7 (patch)
treecd74d9f6a6fbc9e96471119d6b7ce0a2efb1b099
parentde9b718b8642fa554ac1533a0e0d064bad2979d3 (diff)
Enforce strict FIND destinations
-rw-r--r--lua/clever_f/destination_engine.lua58
-rw-r--r--tests/run.lua49
2 files changed, 88 insertions, 19 deletions
diff --git a/lua/clever_f/destination_engine.lua b/lua/clever_f/destination_engine.lua
index 836d190..0ae2314 100644
--- a/lua/clever_f/destination_engine.lua
+++ b/lua/clever_f/destination_engine.lua
@@ -68,20 +68,6 @@ local function candidate_starts(request, origin)
)
end
-local function next_matching_start(request, origin)
- local candidates = candidate_starts(request, origin)
-
- 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
-
local function regular_destination(request, target_position)
local descriptor = request.plan.descriptor
if descriptor.family == domain.Family.FIND then
@@ -106,13 +92,47 @@ local function target_destination(request, target_position)
return regular_destination(request, target_position)
end
+local function strict_destination(descriptor, destination, origin)
+ local comparison = domain.Position.compare(destination, origin)
+ if descriptor.direction == domain.Direction.FORWARD then
+ return comparison > 0
+ end
+ return comparison < 0
+end
+
+local function acceptable_destination(request, destination, origin)
+ if request.plan.descriptor.family == domain.Family.FIND then
+ return strict_destination(request.plan.descriptor, destination, origin)
+ end
+ return true
+end
+
+local function next_destination(request, origin)
+ local candidates = candidate_starts(request, origin)
+
+ while true do
+ local target_position, character = candidates()
+ if target_position == nil then
+ return nil
+ end
+ if request.plan.target_plan:matches(
+ character,
+ target_position,
+ request.view
+ ) then
+ local destination = target_destination(request, target_position)
+ if destination ~= nil
+ and acceptable_destination(request, destination, origin)
+ then
+ return destination
+ end
+ end
+ end
+end
+
function DestinationEngine:calculate(view, origin, plan, count, first_move)
local request = calculation_inputs(view, origin, plan, count, first_move)
- local target_position = next_matching_start(request, request.origin)
- if target_position == nil then
- return domain.SearchOutcome.boundary_before_any(request.origin)
- end
- local destination = target_destination(request, target_position)
+ local destination = next_destination(request, request.origin)
if destination == nil then
return domain.SearchOutcome.boundary_before_any(request.origin)
end
diff --git a/tests/run.lua b/tests/run.lua
index 60d6b5b..1f9b9da 100644
--- a/tests/run.lua
+++ b/tests/run.lua
@@ -2569,6 +2569,55 @@ test("MotionPlanFactory limits exclusive policy to character and line Visual", f
)
end)
+test("FIND requires destinations on the strict motion side", function()
+ local view = text_topology.new({ "axa" }, "utf-8")
+ local engine = destination_engine.new()
+ local target_match = target_plan.build(target("a"), matching_policy())
+ local forward = motion_plan.build(target_match, "f")
+ local backward = motion_plan.build(target_match, "F")
+
+ same(
+ domain.Position.new(1, 3),
+ engine:calculate(
+ view,
+ domain.Position.new(1, 1),
+ forward,
+ 1,
+ true
+ ).endpoint
+ )
+ same(
+ domain.Position.new(1, 1),
+ engine:calculate(
+ view,
+ domain.Position.new(1, 3),
+ backward,
+ 1,
+ true
+ ).endpoint
+ )
+ same(
+ domain.SearchStatus.BOUNDARY_BEFORE_ANY,
+ engine:calculate(
+ view,
+ domain.Position.new(1, 3),
+ forward,
+ 1,
+ false
+ ).status
+ )
+ same(
+ domain.SearchStatus.BOUNDARY_BEFORE_ANY,
+ engine:calculate(
+ view,
+ domain.Position.new(1, 1),
+ backward,
+ 1,
+ false
+ ).status
+ )
+end)
+
for _, item in ipairs(tests) do
local ok, failure = xpcall(item.body, debug.traceback)
if not ok then