summaryrefslogtreecommitdiff
path: root/lua
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 14:23:03 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 14:23:03 +0200
commit970db02504aeaf5c392a1bcad2f148734ba6ea48 (patch)
treeb384b7e5e28e45d658112a6a8e1dcc71c3dbe374 /lua
parent5199ca2e6527ce83b80898c57a09e984beb1fb67 (diff)
Translate action outcomes
Diffstat (limited to 'lua')
-rw-r--r--lua/clever_f/host_adapter.lua108
1 files changed, 108 insertions, 0 deletions
diff --git a/lua/clever_f/host_adapter.lua b/lua/clever_f/host_adapter.lua
new file mode 100644
index 0000000..a0eec66
--- /dev/null
+++ b/lua/clever_f/host_adapter.lua
@@ -0,0 +1,108 @@
+local domain = require("clever_f.domain")
+
+local M = {}
+local HostAdapter = {}
+HostAdapter.__index = HostAdapter
+M.HostAdapter = HostAdapter
+
+M.ActionEffect = {
+ NONE = "none",
+ ESCAPE = "escape",
+ ERROR = "error",
+}
+
+local adapter_records = setmetatable({}, { __mode = "k" })
+
+local function fail(message, level)
+ error(message, (level or 1) + 1)
+end
+
+local function current_runtime(options)
+ if type(options) == "table" and options.runtime ~= nil then
+ return options.runtime
+ end
+ if options ~= nil and options ~= HostAdapter then
+ return options
+ end
+ return rawget(_G, "vim")
+end
+
+local function require_runtime(runtime)
+ if type(runtime) ~= "table" or type(runtime.api) ~= "table" then
+ fail("HostAdapter requires the Nvim Lua runtime", 3)
+ end
+ return runtime
+end
+
+function HostAdapter.new(options)
+ if HostAdapter.is(options) then
+ return options
+ end
+ local adapter = setmetatable({}, HostAdapter)
+ adapter_records[adapter] = {
+ runtime = require_runtime(current_runtime(options)),
+ }
+ return adapter
+end
+
+function HostAdapter.is(value)
+ return type(value) == "table" and adapter_records[value] ~= nil
+end
+
+function HostAdapter:runtime()
+ return adapter_records[self].runtime
+end
+
+local function escape_key(runtime)
+ if type(runtime.keycode) == "function" then
+ return runtime.keycode("<Esc>")
+ end
+ if type(runtime.api.nvim_replace_termcodes) == "function" then
+ return runtime.api.nvim_replace_termcodes("<Esc>", true, false, true)
+ end
+ return string.char(27)
+end
+
+function HostAdapter:return_escape()
+ local runtime = self:runtime()
+ if type(runtime.api.nvim_feedkeys) ~= "function" then
+ fail("HostAdapter runtime must provide nvim_feedkeys", 2)
+ end
+ runtime.api.nvim_feedkeys(escape_key(runtime), "n", false)
+end
+
+function HostAdapter:emit_action_error(text)
+ local runtime = self:runtime()
+ if type(runtime.notify) ~= "function" then
+ fail("HostAdapter runtime must provide notify", 2)
+ end
+ local levels = type(runtime.log) == "table" and runtime.log.levels or nil
+ runtime.notify(text, levels and levels.ERROR or nil, { title = "clever-f" })
+end
+
+function HostAdapter:translate_action_outcome(outcome)
+ if not domain.ActionOutcome.is(outcome) then
+ fail("host action translation requires an ActionOutcome", 2)
+ end
+ if outcome.kind == domain.ActionKind.ESCAPE then
+ self:return_escape()
+ return M.ActionEffect.ESCAPE
+ end
+ if outcome.kind == domain.ActionKind.ERROR then
+ self:emit_action_error(outcome.diagnostic)
+ return M.ActionEffect.ERROR
+ end
+ return M.ActionEffect.NONE
+end
+
+function M.new(options)
+ return HostAdapter.new(options)
+end
+
+setmetatable(M, {
+ __call = function(_, options)
+ return HostAdapter.new(options)
+ end,
+})
+
+return M