From de9b718b8642fa554ac1533a0e0d064bad2979d3 Mon Sep 17 00:00:00 2001 From: Jackson Moore Date: Fri, 4 Sep 2026 10:24:05 +0200 Subject: Select regular endpoint policies --- lua/clever_f/motion_plan.lua | 66 +++++++++++++++++++++++++++++++++++++++ tests/run.lua | 74 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/lua/clever_f/motion_plan.lua b/lua/clever_f/motion_plan.lua index 362b978..1d0b4e3 100644 --- a/lua/clever_f/motion_plan.lua +++ b/lua/clever_f/motion_plan.lua @@ -91,6 +91,54 @@ function MotionPlanFactory:build( }) end +local function selection_option(selection) + if selection == nil then + return domain.SelectionOption.INCLUSIVE + end + if domain.Selection.is(selection) then + return selection.option + end + if domain.SelectionOption.is(selection) then + return selection + end + if type(selection) == "table" and selection.option ~= nil then + return domain.SelectionOption.from_string(selection.option) + end + return domain.SelectionOption.from_string(selection) +end + +function M.endpoint_policy(context, selection) + context = domain.ModeContext.from_full_mode(context) + local option = selection_option(selection) + local visual_kind = context.visual_kind + if option == domain.SelectionOption.EXCLUSIVE + and (visual_kind == domain.SelectionKind.CHARACTER + or visual_kind == domain.SelectionKind.LINE) + then + return domain.EndpointPolicy.VISUAL_EXCLUSIVE + end + return domain.EndpointPolicy.REGULAR +end + +function MotionPlanFactory:endpoint_policy(context, selection) + return M.endpoint_policy(context, selection) +end + +function MotionPlanFactory:build_for_context( + target_plan, + effective_descriptor, + context, + selection, + search_scope +) + return self:build( + target_plan, + effective_descriptor, + search_scope, + self:endpoint_policy(context, selection) + ) +end + function M.new(options) return MotionPlanFactory.new(options) end @@ -110,8 +158,26 @@ function M.build( ) end +function M.build_for_context( + target_plan, + effective_descriptor, + context, + selection, + search_scope, + options +) + return MotionPlanFactory.new(options):build_for_context( + target_plan, + effective_descriptor, + context, + selection, + search_scope + ) +end + M.create = M.build M.resolve = M.build +M.for_context = M.build_for_context setmetatable(M, { __call = function(_, options) diff --git a/tests/run.lua b/tests/run.lua index f9fbeb8..60d6b5b 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -2495,6 +2495,80 @@ test("Forward Visual-exclusive TILL uses target positions", function() ) end) +test("MotionPlanFactory limits exclusive policy to character and line Visual", function() + local view = text_topology.new({ "axz" }, "utf-8") + local origin = domain.Position.new(1, 1) + local target_match = target_plan.build(target("x"), matching_policy()) + local factory = motion_plan.new() + local engine = destination_engine.new() + local cases = { + { "v", domain.EndpointPolicy.VISUAL_EXCLUSIVE }, + { "V", domain.EndpointPolicy.VISUAL_EXCLUSIVE }, + { string.char(0x16), domain.EndpointPolicy.REGULAR }, + { "s", domain.EndpointPolicy.REGULAR }, + { "S", domain.EndpointPolicy.REGULAR }, + { string.char(0x13), domain.EndpointPolicy.REGULAR }, + { "n", domain.EndpointPolicy.REGULAR }, + { "no", domain.EndpointPolicy.REGULAR }, + } + + for _, case in ipairs(cases) do + local context = domain.ModeContext.from_full_mode(case[1]) + local kind = context.visual_kind or context.select_kind + local selection = kind ~= nil + and domain.Selection.active( + kind, + origin, + origin, + domain.SelectionOption.EXCLUSIVE + ) + or domain.Selection.inactive(domain.SelectionOption.EXCLUSIVE) + local plan = factory:build_for_context( + target_match, + "f", + context, + selection, + domain.SearchScope.BUFFER + ) + same(case[2], plan.endpoint_policy, case[1]) + local expected_column = case[2] == domain.EndpointPolicy.VISUAL_EXCLUSIVE + and 3 + or 2 + same( + domain.Position.new(1, expected_column), + engine:calculate(view, origin, plan, 1, true).endpoint, + case[1] + ) + end + + local inclusive_visual = factory:build_for_context( + target_match, + "f", + "v", + domain.SelectionOption.INCLUSIVE, + "buffer" + ) + same(domain.EndpointPolicy.REGULAR, inclusive_visual.endpoint_policy) + + local exclusive_backward = factory:build_for_context( + target_match, + "F", + "v", + domain.SelectionOption.EXCLUSIVE, + "buffer" + ) + same( + domain.Position.new(1, 2), + engine:calculate( + view, + domain.Position.new(1, 3), + exclusive_backward, + 1, + true + ).endpoint + ) +end) + for _, item in ipairs(tests) do local ok, failure = xpcall(item.body, debug.traceback) if not ok then -- cgit v1.2.3