diff options
| author | Jackson Moore <jacksonmoore@tuta.io> | 2026-09-04 10:47:53 +0200 |
|---|---|---|
| committer | Jackson Moore <jacksonmoore@tuta.io> | 2026-09-04 10:47:53 +0200 |
| commit | 3770a731f500e9253bbcf300c213276359a15686 (patch) | |
| tree | 07cbf7d10bfcdf0d146d2691343e0228ff7389e9 /lua/clever_f/motion_executor.lua | |
| parent | 768cb71841e9c2b0989d454e6d6804c0f2330eff (diff) | |
Save command origins
Diffstat (limited to 'lua/clever_f/motion_executor.lua')
| -rw-r--r-- | lua/clever_f/motion_executor.lua | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/lua/clever_f/motion_executor.lua b/lua/clever_f/motion_executor.lua index 212a362..5dba08f 100644 --- a/lua/clever_f/motion_executor.lua +++ b/lua/clever_f/motion_executor.lua @@ -1,12 +1,22 @@ +local destination_engine = require("clever_f.destination_engine") local domain = require("clever_f.domain") +local text_topology = require("clever_f.text_topology") local M = {} +local MotionExecutor = {} +M.MotionExecutor = MotionExecutor M.ExecutionPath = { VISUAL = "visual", COMMAND = "command", } +local executor_records = setmetatable({}, { __mode = "k" }) + +local function fail(message, level) + error(message, (level or 1) + 1) +end + function M.execution_path(context) context = domain.ModeContext.from_full_mode(context) if context.visual_kind ~= nil then @@ -15,4 +25,153 @@ function M.execution_path(context) return M.ExecutionPath.COMMAND end +local function copy_options(options) + local result = {} + for key, value in pairs(options or {}) do + result[key] = value + end + return result +end + +local function normalize_options(options, dependencies) + if MotionExecutor.is(options) and dependencies == nil then + return options + end + if type(options) ~= "table" then + fail("MotionExecutor options must be a table", 3) + end + if options.host ~= nil then + if dependencies ~= nil then + fail("MotionExecutor dependencies must be part of its options", 3) + end + return options + end + local result = copy_options(dependencies) + result.host = options + return result +end + +local function require_host(host) + if type(host) ~= "table" + or type(host.read_cursor) ~= "function" + then + fail("MotionExecutor host must provide read_cursor", 3) + end + return host +end + +local function require_destination_engine(engine) + engine = engine or destination_engine.new() + if type(engine) ~= "table" or type(engine.calculate) ~= "function" then + fail("MotionExecutor destination engine must provide calculate", 3) + end + return engine +end + +local executor_metatable = { + __index = MotionExecutor, + __newindex = function() + fail("MotionExecutor values are immutable", 2) + end, + __tostring = function() + return "motion-executor" + end, + __metatable = "clever_f.motion_executor.MotionExecutor", +} + +function MotionExecutor.new(options, dependencies) + options = normalize_options(options, dependencies) + if MotionExecutor.is(options) then + return options + end + + local executor = setmetatable({}, executor_metatable) + executor_records[executor] = { + host = require_host(options.host), + destination_engine = require_destination_engine( + options.destination_engine or options.engine + ), + } + return executor +end + +function MotionExecutor.is(value) + return type(value) == "table" and executor_records[value] ~= nil +end + +local function execution_request(view, context, plan, count, first_move) + if not text_topology.TextView.is(view) then + fail("motion execution view must be a TextView", 3) + end + context = domain.ModeContext.from_full_mode(context) + if not domain.ResolvedMotionPlan.is(plan) then + fail("motion execution plan must be a ResolvedMotionPlan", 3) + end + count = domain.Count.new(count) + if type(first_move) ~= "boolean" then + fail("motion execution first_move must be a Boolean", 3) + end + return { + view = view, + context = context, + plan = plan, + count = count, + first_move = first_move, + } +end + +local function calculate(executor, request, origin) + return executor_records[executor].destination_engine:calculate( + request.view, + origin, + request.plan, + request.count, + request.first_move + ) +end + +function MotionExecutor:_execute_command(request) + local host = executor_records[self].host + local origin = host:read_cursor() + local outcome = calculate(self, request, origin) + return domain.ActionOutcome.from_search(outcome, request.plan.descriptor) +end + +function MotionExecutor:_execute_visual(request) + local host = executor_records[self].host + local origin = host:read_cursor() + local outcome = calculate(self, request, origin) + return domain.ActionOutcome.from_search(outcome, request.plan.descriptor) +end + +function MotionExecutor:execute(view, context, plan, count, first_move) + local request = execution_request(view, context, plan, count, first_move) + if M.execution_path(request.context) == M.ExecutionPath.VISUAL then + return self:_execute_visual(request) + end + return self:_execute_command(request) +end + +function M.new(options, dependencies) + return MotionExecutor.new(options, dependencies) +end + +function M.execute(host, view, context, plan, count, first_move, dependencies) + return MotionExecutor.new(host, dependencies):execute( + view, + context, + plan, + count, + first_move + ) +end + +M.run = M.execute + +setmetatable(M, { + __call = function(_, options, dependencies) + return MotionExecutor.new(options, dependencies) + end, +}) + return M |
