summaryrefslogtreecommitdiff
path: root/lua
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 12:32:44 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 12:32:44 +0200
commitc6f2c9941742c84a43faee9e6426786bc1e8f13c (patch)
treecd14010eb2d37aed874e2395f7b707acfbe4cbd4 /lua
parenta32ac60fad13db062f23478e8da74d5dbba0edc8 (diff)
Apply persistent match behavior
Diffstat (limited to 'lua')
-rw-r--r--lua/clever_f/feedback_service.lua100
1 files changed, 93 insertions, 7 deletions
diff --git a/lua/clever_f/feedback_service.lua b/lua/clever_f/feedback_service.lua
index 9703502..037c7fd 100644
--- a/lua/clever_f/feedback_service.lua
+++ b/lua/clever_f/feedback_service.lua
@@ -1,6 +1,7 @@
local domain = require("clever_f.domain")
local policy = require("clever_f.policy")
local state_transitions = require("clever_f.state_transitions")
+local text_topology = require("clever_f.text_topology")
local M = {}
local FeedbackService = {}
@@ -307,6 +308,78 @@ function M.persistent_context_eligible(context)
or context.key == "cvr"
end
+function M.persistent_destination(
+ view,
+ target_position,
+ descriptor,
+ endpoint_policy
+)
+ if not text_topology.TextView.is(view) then
+ fail("persistent feedback requires a TextView", 2)
+ end
+ target_position = domain.Position.coerce(target_position)
+ descriptor = domain.Descriptor.from_string(descriptor)
+ endpoint_policy = domain.EndpointPolicy.from_string(endpoint_policy)
+
+ if endpoint_policy == domain.EndpointPolicy.VISUAL_EXCLUSIVE
+ and descriptor.direction == domain.Direction.FORWARD
+ then
+ if descriptor.family == domain.Family.FIND then
+ return view:successor(target_position)
+ end
+ return target_position
+ end
+ if descriptor.family == domain.Family.FIND then
+ return target_position
+ end
+ if descriptor.direction == domain.Direction.FORWARD then
+ return view:predecessor(target_position)
+ end
+ return view:successor(target_position)
+end
+
+function M.persistent_match_positions(
+ view,
+ match_start_line,
+ target_plan,
+ descriptor,
+ endpoint_policy
+)
+ if not text_topology.TextView.is(view) then
+ fail("persistent feedback requires a TextView", 2)
+ end
+ if not domain.TargetPlan.is(target_plan) then
+ fail("persistent feedback requires a TargetPlan", 2)
+ end
+ local positions = {}
+ local seen = {}
+ local candidates = view:iter_line_forward(match_start_line)
+ while true do
+ local position, character = candidates()
+ if position == nil then
+ break
+ end
+ if target_plan:matches(character, position, view) then
+ local destination = M.persistent_destination(
+ view,
+ position,
+ descriptor,
+ endpoint_policy
+ )
+ if destination ~= nil then
+ local key = tostring(destination.line)
+ .. ":"
+ .. tostring(destination.byte_column)
+ if not seen[key] then
+ seen[key] = true
+ positions[#positions + 1] = destination
+ end
+ end
+ end
+ end
+ return positions
+end
+
function FeedbackService:build_persistent(specification)
if type(specification) ~= "table" then
fail("persistent feedback request must be a table", 2)
@@ -324,17 +397,30 @@ function FeedbackService:build_persistent(specification)
if specification.motion_plan.target_plan ~= specification.target_plan then
fail("persistent feedback must reuse the movement TargetPlan", 2)
end
- local descriptor = specification.descriptor
- or specification.motion_plan.descriptor
- local endpoint_policy = specification.endpoint_policy
- or specification.motion_plan.endpoint_policy
+ local descriptor = domain.Descriptor.from_string(
+ specification.descriptor or specification.motion_plan.descriptor
+ )
+ local endpoint_policy = domain.EndpointPolicy.from_string(
+ specification.endpoint_policy or specification.motion_plan.endpoint_policy
+ )
+ local anchor = domain.Position.coerce(specification.anchor)
+ local view = specification.text_view
+ or text_topology.from_host(service_records[self].host)
return {
context = context,
- anchor = domain.Position.coerce(specification.anchor),
+ anchor = anchor,
target_plan = specification.target_plan,
motion_plan = specification.motion_plan,
- descriptor = domain.Descriptor.from_string(descriptor),
- endpoint_policy = domain.EndpointPolicy.from_string(endpoint_policy),
+ descriptor = descriptor,
+ endpoint_policy = endpoint_policy,
+ positions = M.persistent_match_positions(
+ view,
+ anchor.line,
+ specification.target_plan,
+ descriptor,
+ endpoint_policy
+ ),
+ text_view = view,
window = specification.window,
}
end