summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 10:51:00 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 10:51:00 +0200
commit93e840d9f95c8500ccebcbfe6b09d5fa2a191040 (patch)
treea62e65e280990b07de9ea98c04c9a44f5925a06c
parent6fb1cc4e2840a359f628245d7fb81d4666246780 (diff)
Retain active Visual selections
-rw-r--r--lua/clever_f/motion_executor.lua11
-rw-r--r--tests/run.lua35
2 files changed, 45 insertions, 1 deletions
diff --git a/lua/clever_f/motion_executor.lua b/lua/clever_f/motion_executor.lua
index 72a66bd..afa3ee1 100644
--- a/lua/clever_f/motion_executor.lua
+++ b/lua/clever_f/motion_executor.lua
@@ -74,9 +74,10 @@ end
local function require_host(host)
if type(host) ~= "table"
or type(host.read_cursor) ~= "function"
+ or type(host.read_selection) ~= "function"
or type(host.apply_cursor) ~= "function"
then
- fail("MotionExecutor host must provide cursor movement", 3)
+ fail("MotionExecutor host must provide movement state", 3)
end
return host
end
@@ -234,8 +235,16 @@ end
function MotionExecutor:_execute_visual(request)
local host = executor_records[self].host
+ local selection = host:read_selection()
+ if not domain.Selection.is(selection)
+ or not selection.active
+ or selection.kind ~= request.context.visual_kind
+ then
+ fail("Visual motion execution requires its active selection kind", 2)
+ end
local origin = host:read_cursor()
local outcome = calculate(self, request, origin)
+ request.selection = selection
return domain.ActionOutcome.from_search(outcome, request.plan.descriptor)
end
diff --git a/tests/run.lua b/tests/run.lua
index 97fbacb..957a4bb 100644
--- a/tests/run.lua
+++ b/tests/run.lua
@@ -3340,6 +3340,41 @@ test("Complete command execution commits direction and landing", function()
truthy(state.moved_forward_initialized)
end)
+test("Visual calculation retains the active selection", function()
+ local origin = domain.Position.new(1, 1)
+ local active = domain.Selection.active(
+ domain.SelectionKind.CHARACTER,
+ origin,
+ origin,
+ domain.SelectionOption.INCLUSIVE
+ )
+ local host = MemoryHost.new({
+ buffer_lines = { "abc" },
+ cursor = origin,
+ mode = "v",
+ selection = active,
+ })
+ local observed_selection
+ local engine = {
+ calculate = function(_, _, calculation_origin)
+ observed_selection = host:read_selection()
+ return domain.SearchOutcome.boundary_before_any(calculation_origin)
+ end,
+ }
+ local plan = motion_plan.build(
+ target_plan.build(target("z"), matching_policy()),
+ "f"
+ )
+ local outcome = motion_executor.new({
+ host = host,
+ destination_engine = engine,
+ }):execute(text_topology.from_host(host), "v", plan, 1, true)
+
+ same(domain.ActionKind.FAILED_SEARCH, outcome.kind)
+ same(active, observed_selection)
+ same(active, host:read_selection())
+end)
+
for _, item in ipairs(tests) do
local ok, failure = xpcall(item.body, debug.traceback)
if not ok then