diff options
| -rw-r--r-- | lua/clever_f/destination_engine.lua | 83 | ||||
| -rw-r--r-- | tests/run.lua | 35 |
2 files changed, 118 insertions, 0 deletions
diff --git a/lua/clever_f/destination_engine.lua b/lua/clever_f/destination_engine.lua new file mode 100644 index 0000000..c7345d5 --- /dev/null +++ b/lua/clever_f/destination_engine.lua @@ -0,0 +1,83 @@ +local domain = require("clever_f.domain") +local text_topology = require("clever_f.text_topology") + +local M = {} +local DestinationEngine = {} +M.DestinationEngine = DestinationEngine + +local engines = setmetatable({}, { __mode = "k" }) + +local function fail(message, level) + error(message, (level or 1) + 1) +end + +local engine_metatable = { + __index = DestinationEngine, + __newindex = function() + fail("DestinationEngine values are immutable", 2) + end, + __tostring = function() + return "destination-engine" + end, + __metatable = "clever_f.destination_engine.DestinationEngine", +} + +function DestinationEngine.new() + local engine = setmetatable({}, engine_metatable) + engines[engine] = true + return engine +end + +function DestinationEngine.is(value) + return type(value) == "table" and engines[value] == true +end + +local function calculation_inputs(view, origin, plan, count, first_move) + if not text_topology.TextView.is(view) then + fail("destination calculation view must be a TextView", 3) + end + + origin = domain.Position.coerce(origin) + if not view:is_valid_cursor_position(origin) then + fail("destination calculation origin must be a valid cursor position", 3) + end + if not domain.ResolvedMotionPlan.is(plan) then + fail("destination calculation plan must be a ResolvedMotionPlan", 3) + end + count = domain.Count.new(count) + if type(first_move) ~= "boolean" then + fail("destination calculation first_move must be a Boolean", 3) + end + + return { + view = view, + origin = origin, + plan = plan, + count = count, + first_move = first_move, + } +end + +function DestinationEngine:calculate(view, origin, plan, count, first_move) + local request = calculation_inputs(view, origin, plan, count, first_move) + return domain.SearchOutcome.boundary_before_any(request.origin) +end + +function M.new() + return DestinationEngine.new() +end + +function M.calculate(view, origin, plan, count, first_move) + return DestinationEngine.new():calculate(view, origin, plan, count, first_move) +end + +M.resolve = M.calculate +M.search = M.calculate + +setmetatable(M, { + __call = function() + return DestinationEngine.new() + end, +}) + +return M diff --git a/tests/run.lua b/tests/run.lua index 6d4ed3e..f684d65 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -8,6 +8,7 @@ package.path = table.concat({ local domain = require("clever_f.domain") local capabilities = require("clever_f.capabilities") +local destination_engine = require("clever_f.destination_engine") local case_policy = require("clever_f.case_policy") local policy = require("clever_f.policy") local migemo_catalog = require("clever_f.migemo_catalog") @@ -2166,6 +2167,40 @@ test("MotionPlanFactory combines resolved motion values", function() end, "TargetPlan") end) +test("DestinationEngine accepts pure calculation inputs", function() + local view = text_topology.new({ "abc" }, "utf-8") + local plan = motion_plan.build( + target_plan.build(target("z"), matching_policy()), + "f" + ) + local engine = destination_engine.new() + local origin = domain.Position.new(1, 1) + local outcome = engine:calculate(view, origin, plan, nil, true) + + same(domain.SearchStatus.BOUNDARY_BEFORE_ANY, outcome.status) + same(origin, outcome.endpoint) + same(0, outcome.successful_steps) + truthy(destination_engine.DestinationEngine.is(engine)) + fails(function() + engine.mutable = true + end, "immutable") + fails(function() + engine:calculate({}, origin, plan, 1, true) + end, "TextView") + fails(function() + engine:calculate(view, origin, {}, 1, true) + end, "ResolvedMotionPlan") + fails(function() + engine:calculate(view, origin, plan, 0, true) + end, "positive") + fails(function() + engine:calculate(view, origin, plan, 1, nil) + end, "Boolean") + fails(function() + engine:calculate(view, domain.Position.new(1, 4), plan, 1, true) + end, "valid cursor") +end) + for _, item in ipairs(tests) do local ok, failure = xpcall(item.body, debug.traceback) if not ok then |
