From ab2fca82cf676583efc9a5897de0dd7f07c053ef Mon Sep 17 00:00:00 2001 From: Jackson Moore Date: Fri, 4 Sep 2026 13:45:27 +0200 Subject: Return outcomes through ActionFacade --- lua/clever_f/action_facade.lua | 67 ++++++++++++++++++++++++++++++++++++++++++ tests/run.lua | 24 +++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 lua/clever_f/action_facade.lua diff --git a/lua/clever_f/action_facade.lua b/lua/clever_f/action_facade.lua new file mode 100644 index 0000000..5d8da67 --- /dev/null +++ b/lua/clever_f/action_facade.lua @@ -0,0 +1,67 @@ +local domain = require("clever_f.domain") +local sequence_coordinator = require("clever_f.sequence_coordinator") + +local M = {} +local ActionFacade = {} +ActionFacade.__index = ActionFacade +M.ActionFacade = ActionFacade + +local facade_records = setmetatable({}, { __mode = "k" }) + +local function fail(message, level) + error(message, (level or 1) + 1) +end + +function ActionFacade.new(options) + if ActionFacade.is(options) then + return options + end + local coordinator + if sequence_coordinator.SequenceCoordinator.is(options) then + coordinator = options + elseif type(options) == "table" then + coordinator = options.coordinator + or options.sequence_coordinator + or sequence_coordinator.new(options) + else + fail("ActionFacade options must be a table", 2) + end + if not sequence_coordinator.SequenceCoordinator.is(coordinator) then + fail("ActionFacade requires a SequenceCoordinator", 2) + end + + local facade = setmetatable({}, ActionFacade) + facade_records[facade] = { coordinator = coordinator } + return facade +end + +function ActionFacade.is(value) + return type(value) == "table" and facade_records[value] ~= nil +end + +function ActionFacade:coordinator() + return facade_records[self].coordinator +end + +function ActionFacade:primary(descriptor) + local outcome = self:coordinator():primary(descriptor) + if not domain.ActionOutcome.is(outcome) then + fail("SequenceCoordinator must return an ActionOutcome", 2) + end + return outcome +end + +ActionFacade.invoke_primary = ActionFacade.primary +ActionFacade.start = ActionFacade.primary + +function M.new(options) + return ActionFacade.new(options) +end + +setmetatable(M, { + __call = function(_, options) + return ActionFacade.new(options) + end, +}) + +return M diff --git a/tests/run.lua b/tests/run.lua index 97a9a6d..7e49e62 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -7,6 +7,7 @@ package.path = table.concat({ }, ";") local domain = require("clever_f.domain") +local action_facade = require("clever_f.action_facade") local acquisition_service = require("clever_f.acquisition_service") local capabilities = require("clever_f.capabilities") local destination_engine = require("clever_f.destination_engine") @@ -7682,6 +7683,29 @@ test("Failed resolved primary search still refreshes highlight timeout", functio truthy(host:timers()[state.highlight_timer].active) end) +test("ActionFacade returns typed primary action outcomes", function() + fresh_sequence_state() + local host = MemoryHost.new({ + buffer_lines = { "aba" }, + input_packets = { { kind = "text", text = "b" } }, + configuration = { + mark_cursor = false, + mark_char = false, + }, + emit_movement_events = false, + }) + local facade = action_facade.new({ host = host }) + + local outcome = facade:primary("f") + + truthy(domain.ActionOutcome.is(outcome)) + same(domain.ActionKind.MOVEMENT, outcome.kind) + same(domain.Position.new(1, 2), outcome.position) + fails(function() + facade:primary("x") + end, "clever-f: Invalid mapping 'x'") +end) + for _, item in ipairs(tests) do local ok, failure = xpcall(item.body, debug.traceback) if not ok then -- cgit v1.2.3