summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lua/clever_f/destination_engine.lua83
-rw-r--r--tests/run.lua35
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