summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lua/clever_f/motion_plan.lua122
-rw-r--r--tests/run.lua30
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