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("") end if type(runtime.api.nvim_replace_termcodes) == "function" then return runtime.api.nvim_replace_termcodes("", 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