diff options
| -rw-r--r-- | lua/clever_f/motion_plan.lua | 122 | ||||
| -rw-r--r-- | tests/run.lua | 30 |
2 files changed, 152 insertions, 0 deletions
diff --git a/lua/clever_f/motion_plan.lua b/lua/clever_f/motion_plan.lua new file mode 100644 index 0000000..362b978 --- /dev/null +++ b/lua/clever_f/motion_plan.lua @@ -0,0 +1,122 @@ +local domain = require("clever_f.domain") + +local M = {} +local MotionPlanFactory = {} +M.MotionPlanFactory = MotionPlanFactory + +local factory_records = setmetatable({}, { __mode = "k" }) + +local function fail(message, level) + error(message, (level or 1) + 1) +end + +local function normalize_options(options) + if options == nil then + return {} + end + if type(options) == "table" and type(options.sample_search) == "function" then + return { policy = options } + end + if type(options) ~= "table" then + fail("MotionPlanFactory options must be a table", 3) + end + return options +end + +local function require_policy(service) + if service ~= nil and (type(service) ~= "table" + or type(service.sample_search) ~= "function") + then + fail("MotionPlanFactory policy must provide sample_search", 3) + end + return service +end + +local factory_metatable = { + __index = MotionPlanFactory, + __newindex = function() + fail("MotionPlanFactory values are immutable", 2) + end, + __tostring = function() + return "motion-plan-factory" + end, + __metatable = "clever_f.motion_plan.MotionPlanFactory", +} + +function MotionPlanFactory.new(options) + if MotionPlanFactory.is(options) then + return options + end + options = normalize_options(options) + local factory = setmetatable({}, factory_metatable) + factory_records[factory] = { + policy = require_policy(options.policy or options.policy_service), + } + return factory +end + +function MotionPlanFactory.is(value) + return type(value) == "table" and factory_records[value] ~= nil +end + +local function resolved_scope(factory, search_scope) + if search_scope == nil then + local policy = factory_records[factory].policy + if policy ~= nil then + search_scope = policy:sample_search().search_scope + else + search_scope = domain.SearchScope.BUFFER + end + elseif search_scope == "line" then + search_scope = domain.SearchScope.CURRENT_LINE + end + return domain.SearchScope.from_string(search_scope) +end + +function MotionPlanFactory:build( + target_plan, + effective_descriptor, + search_scope, + endpoint_policy +) + if not domain.TargetPlan.is(target_plan) then + fail("motion target plan must be a TargetPlan", 2) + end + + return domain.ResolvedMotionPlan.new({ + target_plan = target_plan, + descriptor = effective_descriptor, + search_scope = resolved_scope(self, search_scope), + endpoint_policy = endpoint_policy or domain.EndpointPolicy.REGULAR, + }) +end + +function M.new(options) + return MotionPlanFactory.new(options) +end + +function M.build( + target_plan, + effective_descriptor, + search_scope, + endpoint_policy, + options +) + return MotionPlanFactory.new(options):build( + target_plan, + effective_descriptor, + search_scope, + endpoint_policy + ) +end + +M.create = M.build +M.resolve = M.build + +setmetatable(M, { + __call = function(_, options) + return MotionPlanFactory.new(options) + end, +}) + +return M diff --git a/tests/run.lua b/tests/run.lua index 6ca2327..6d4ed3e 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -11,6 +11,7 @@ local capabilities = require("clever_f.capabilities") local case_policy = require("clever_f.case_policy") local policy = require("clever_f.policy") local migemo_catalog = require("clever_f.migemo_catalog") +local motion_plan = require("clever_f.motion_plan") local sequence_state = require("clever_f.sequence_state") local state_transitions = require("clever_f.state_transitions") local text_topology = require("clever_f.text_topology") @@ -2136,6 +2137,35 @@ test("Public Reset discards loaded Migemo dictionary objects", function() same(2, catalog:load_count("utf-8")) end) +test("MotionPlanFactory combines resolved motion values", function() + local target_match = target_plan.build(target("a"), matching_policy()) + local factory = motion_plan.new() + local descriptors = { "f", "F", "t", "T" } + + for _, descriptor in ipairs(descriptors) do + local plan = factory:build( + target_match, + descriptor, + domain.SearchScope.CURRENT_LINE, + domain.EndpointPolicy.VISUAL_EXCLUSIVE + ) + same(target_match, plan.target_plan) + same(domain.Descriptor.from_string(descriptor), plan.descriptor) + same(domain.SearchScope.CURRENT_LINE, plan.search_scope) + same(domain.EndpointPolicy.VISUAL_EXCLUSIVE, plan.endpoint_policy) + end + + local defaulted = motion_plan.build(target_match, "f") + same(domain.SearchScope.BUFFER, defaulted.search_scope) + same(domain.EndpointPolicy.REGULAR, defaulted.endpoint_policy) + fails(function() + defaulted.search_scope = domain.SearchScope.CURRENT_LINE + end, "immutable") + fails(function() + factory:build({}, "f", "buffer", "regular") + end, "TargetPlan") +end) + for _, item in ipairs(tests) do local ok, failure = xpcall(item.body, debug.traceback) if not ok then |
