summaryrefslogtreecommitdiff
path: root/lua/clever_f/host_adapter.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua/clever_f/host_adapter.lua')
-rw-r--r--lua/clever_f/host_adapter.lua1165
1 files changed, 0 insertions, 1165 deletions
diff --git a/lua/clever_f/host_adapter.lua b/lua/clever_f/host_adapter.lua
deleted file mode 100644
index a4ec77d..0000000
--- a/lua/clever_f/host_adapter.lua
+++ /dev/null
@@ -1,1165 +0,0 @@
-local capabilities = require("clever_f.capabilities")
-local domain = require("clever_f.domain")
-
-local M = {}
-local unpack_values = table.unpack or unpack
-local HostAdapter = {}
-HostAdapter.__index = HostAdapter
-M.HostAdapter = HostAdapter
-
-M.ActionEffect = {
- NONE = "none",
- ESCAPE = "escape",
- ERROR = "error",
-}
-M.CONFIGURATION_PREFIX = "clever_f_"
-M.CONFIGURATION_GLOBALS = {
- suppress_default_mappings = "clever_f_not_overwrites_standard_mappings",
-}
-
-local BOOLEAN_CONFIGURATION = {
- search_current_line_only = true,
- ignore_case = true,
- smart_case = true,
- use_migemo = true,
- fix_key_direction = true,
- show_prompt = true,
- mark_cursor = true,
- hide_cursor_on_cmdline = true,
- mark_char = true,
- mark_direct = true,
- clean_labels_eagerly = true,
-}
-
-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" then
- if options.runtime ~= nil then
- return options.runtime
- end
- if options.api ~= nil then
- return options
- end
- return rawget(_G, "vim")
- 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)
- local record = {
- runtime = require_runtime(current_runtime(options)),
- next_identity = 1,
- highlights = {},
- timers = {},
- cursor_presentations = {},
- events = {},
- actions = {},
- mappings = {},
- dot_repeat = nil,
- dot_bridge = nil,
- event_order = {},
- action_diagnostics = nil,
- augroup = nil,
- }
- record.event_queue = capabilities.EventQueue.new(function(_, delivery)
- delivery.callback(delivery.name, delivery.payload)
- end)
- adapter_records[adapter] = record
- 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
-
-function HostAdapter:read_text()
- local lines = self:runtime().api.nvim_buf_get_lines(0, 0, -1, true)
- if #lines == 0 then
- lines = { "" }
- end
- return domain.TextSnapshot.new(lines)
-end
-
-function HostAdapter:read_buffer()
- return self:runtime().api.nvim_get_current_buf()
-end
-
-function HostAdapter:read_window()
- return self:runtime().api.nvim_get_current_win()
-end
-
-function HostAdapter:read_cursor()
- local position = self:runtime().api.nvim_win_get_cursor(0)
- return domain.Position.new(position[1], position[2] + 1)
-end
-
-function HostAdapter:read_mode()
- return self:runtime().api.nvim_get_mode().mode
-end
-
-local function selection_option(runtime)
- local value = runtime.api.nvim_get_option_value(
- "selection",
- { scope = "global" }
- )
- if value == "exclusive" then
- return domain.SelectionOption.EXCLUSIVE
- end
- return domain.SelectionOption.INCLUSIVE
-end
-
-function HostAdapter:read_selection()
- local runtime = self:runtime()
- local context = domain.ModeContext.from_full_mode(self:read_mode())
- local kind = context.visual_kind or context.select_kind
- local option = selection_option(runtime)
- if kind == nil then
- return domain.Selection.inactive(option)
- end
- local raw_anchor = runtime.fn.getpos("v")
- local focus = self:read_cursor()
- local anchor
- if type(raw_anchor) == "table"
- and type(raw_anchor[2]) == "number"
- and raw_anchor[2] > 0
- and type(raw_anchor[3]) == "number"
- and raw_anchor[3] > 0
- then
- anchor = domain.Position.new(raw_anchor[2], raw_anchor[3])
- else
- anchor = focus
- end
- return domain.Selection.active(kind, anchor, focus, option)
-end
-
-function HostAdapter:read_count()
- local runtime = self:runtime()
- local count = runtime.v.count1
- if type(count) ~= "number" or count < 1 then
- count = 1
- end
- return domain.Count.new(count)
-end
-
-local function configuration_global(name)
- if type(name) ~= "string" or name == "" then
- fail("configuration name must be a nonempty string", 3)
- end
- return M.CONFIGURATION_GLOBALS[name] or M.CONFIGURATION_PREFIX .. name
-end
-
-function M.configuration_global(name)
- return configuration_global(name)
-end
-
-function HostAdapter:configuration_present(name)
- local global = configuration_global(name)
- return self:runtime().fn.exists("g:" .. global) == 1
-end
-
-local function normalize_configuration(name, value)
- if BOOLEAN_CONFIGURATION[name] and type(value) == "number" then
- return value ~= 0
- end
- return value
-end
-
-function HostAdapter:read_configuration(name)
- local global = configuration_global(name)
- return normalize_configuration(name, self:runtime().g[global])
-end
-
-function HostAdapter:write_configuration(name, value)
- local global = configuration_global(name)
- self:runtime().g[global] = value
-end
-
-function HostAdapter:read_encoding()
- local runtime = self:runtime()
- return runtime.api.nvim_get_option_value(
- "encoding",
- { scope = "global" }
- )
-end
-
-function HostAdapter:lowercase(value)
- if type(value) ~= "string" then
- fail("case conversion value must be a string", 2)
- end
- local result = self:runtime().fn.tolower(value)
- if type(result) ~= "string" then
- fail("Nvim case conversion must return a string", 2)
- end
- return result
-end
-
-function HostAdapter:read_macro_state()
- local register = self:runtime().fn.reg_executing()
- return domain.MacroState.new(register ~= "" and register or nil)
-end
-
-local function fold_open_policies(value)
- local result = {}
- for item in tostring(value):gmatch("[^,]+") do
- if item == "hor" then
- item = "horizontal"
- end
- result[#result + 1] = item
- end
- return result
-end
-
-function HostAdapter:read_fold_state()
- local runtime = self:runtime()
- local foldopen = runtime.api.nvim_get_option_value(
- "foldopen",
- { scope = "global" }
- )
- local line = self:read_cursor().line
- local closed_levels = runtime.fn.foldclosed(line) == -1 and 0 or 1
- return domain.FoldState.new(
- fold_open_policies(foldopen),
- closed_levels
- )
-end
-
-function HostAdapter:read_time_ms()
- local runtime = self:runtime()
- local uv = runtime.uv or runtime.loop
- if type(uv) ~= "table" or type(uv.hrtime) ~= "function" then
- fail("HostAdapter runtime must provide a monotonic clock", 2)
- end
- return uv.hrtime() / 1000000
-end
-
-function HostAdapter:read_pending_operator()
- local operator = self:runtime().v.operator
- if operator == nil then
- return ""
- end
- return operator
-end
-
-function HostAdapter:apply_cursor(position)
- position = domain.Position.coerce(position)
- self:runtime().api.nvim_win_set_cursor(
- 0,
- { position.line, position.byte_column - 1 }
- )
-end
-
-function HostAdapter:apply_selection(position)
- if domain.Selection.is(position) then
- if not position.active then
- fail("selection movement requires an active selection", 2)
- end
- position = position.focus
- end
- return self:apply_cursor(position)
-end
-
-function HostAdapter:set_operator_inclusive(enabled)
- if type(enabled) ~= "boolean" then
- fail("operator inclusivity must be a Boolean", 2)
- end
- if not enabled then
- return
- end
-
- local runtime = self:runtime()
- local api = runtime.api
- local selection
- if type(api.nvim_get_option_value) == "function" then
- selection = api.nvim_get_option_value("selection", { scope = "global" })
- end
- if selection ~= "exclusive" then
- api.nvim_cmd({
- cmd = "normal",
- bang = true,
- args = { "v" },
- }, {})
- return
- end
- if type(api.nvim_set_option_value) ~= "function"
- or type(runtime.schedule) ~= "function"
- then
- fail("HostAdapter cannot preserve exclusive selection during an operator", 2)
- end
-
- api.nvim_set_option_value("selection", "inclusive", { scope = "global" })
- local ok, command_error = pcall(api.nvim_cmd, {
- cmd = "normal",
- bang = true,
- args = { "v" },
- }, {})
- if not ok then
- api.nvim_set_option_value("selection", selection, { scope = "global" })
- error(command_error, 0)
- end
- runtime.schedule(function()
- api.nvim_set_option_value("selection", selection, { scope = "global" })
- end)
-end
-
-local function string_bytes(value)
- local bytes = {}
- for index = 1, #value do
- bytes[index] = string.byte(value, index)
- end
- return bytes
-end
-
-function HostAdapter:read_input()
- local runtime = self:runtime()
- local value = runtime.fn.getcharstr()
- if type(value) ~= "string" or value == "" then
- fail("Nvim target input must be a nonempty string", 2)
- end
- local bytes = string_bytes(value)
- if #bytes == 3
- and bytes[1] == 0x80
- and bytes[2] == 0xfd
- and bytes[3] == 0x60
- then
- return domain.InputPacket.raw_bytes(bytes)
- end
- if #bytes == 1 and bytes[1] == 27 then
- return domain.InputPacket.special_key("Escape", bytes)
- end
- if bytes[1] == 0x80 then
- local name = type(runtime.fn.keytrans) == "function"
- and runtime.fn.keytrans(value)
- or "Special"
- return domain.InputPacket.special_key(name, value)
- end
- return domain.InputPacket.text(value)
-end
-
-function HostAdapter:open_fold(position)
- position = position and domain.Position.coerce(position) or self:read_cursor()
- local runtime = self:runtime()
- if runtime.fn.foldclosed(position.line) == -1 then
- return false
- end
- runtime.api.nvim_cmd({
- cmd = "normal",
- bang = true,
- args = { "zo" },
- }, {})
- return true
-end
-
-function HostAdapter:show_prompt(text)
- if type(text) ~= "string" then
- fail("prompt must be a string", 2)
- end
- self:runtime().api.nvim_echo({ { text } }, false, {})
-end
-
-function HostAdapter:redraw(kind)
- if kind == "suppressed" then
- return false
- end
- if kind ~= "screen" and kind ~= "full" then
- fail("redraw kind must be screen, full, or suppressed", 2)
- end
- self:runtime().api.nvim_cmd({
- cmd = "redraw",
- bang = kind == "full",
- }, {})
- return true
-end
-
-local DIAGNOSTIC_LEVELS = {
- error = "ERROR",
- warning = "WARN",
- info = "INFO",
-}
-
-function HostAdapter:emit_diagnostic(level, text)
- local level_name = DIAGNOSTIC_LEVELS[level]
- if level_name == nil then
- fail("diagnostic level must be error, warning, or info", 2)
- end
- if type(text) ~= "string" or text == "" then
- fail("diagnostic text must be a nonempty string", 2)
- end
- local record = adapter_records[self]
- local runtime = record.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 {}
- runtime.notify(text, levels[level_name], { title = "clever-f" })
- if record.action_diagnostics ~= nil then
- record.action_diagnostics[level .. "\0" .. text] = true
- end
-end
-
-local install_dot_bridge
-
-function HostAdapter:register_dot_repeat(payload, callback)
- if not domain.DotPayload.is(payload) then
- fail("dot-repeat payload must be a DotPayload", 2)
- end
- if callback ~= nil and type(callback) ~= "function" then
- fail("dot-repeat callback must be a function", 2)
- end
- local record = adapter_records[self]
- record.dot_repeat = {
- payload = payload,
- callback = callback,
- operator = self:read_pending_operator(),
- }
- if install_dot_bridge ~= nil then
- install_dot_bridge(self)
- end
- if record.dot_bridge ~= nil then
- record.dot_bridge.awaiting_change = true
- end
- return payload
-end
-
-function HostAdapter:dot_repeat_payload()
- local registration = adapter_records[self].dot_repeat
- return registration and registration.payload or nil
-end
-
-function HostAdapter:replay_dot(count)
- local registration = adapter_records[self].dot_repeat
- if registration == nil or registration.callback == nil then
- fail("dot repeat is not executable", 2)
- end
- return registration.callback(
- registration.payload,
- domain.Count.new(count)
- )
-end
-
-local function next_identity(adapter, prefix)
- local record = adapter_records[adapter]
- local identity = prefix .. "-" .. tostring(record.next_identity)
- record.next_identity = record.next_identity + 1
- return identity
-end
-
-local function highlight_exists(runtime, name)
- if type(runtime.fn) == "table" and type(runtime.fn.hlexists) == "function" then
- return runtime.fn.hlexists(name) == 1
- end
- local definition = runtime.api.nvim_get_hl(0, {
- name = name,
- link = true,
- create = false,
- })
- return next(definition) ~= nil
-end
-
-function HostAdapter:read_highlight_group(name)
- if type(name) ~= "string" or name == "" then
- fail("highlight group name must be a nonempty string", 2)
- end
- local runtime = self:runtime()
- if not highlight_exists(runtime, name) then
- return nil
- end
- return runtime.api.nvim_get_hl(0, {
- name = name,
- link = true,
- create = false,
- })
-end
-
-local function native_highlight_definition(definition, options)
- if type(definition) ~= "table" then
- fail("highlight group definition must be a table", 3)
- end
- options = options or {}
- if type(options) ~= "table" then
- fail("highlight group options must be a table", 3)
- end
- local native = {}
- for key, value in pairs(definition) do
- if key ~= "guifg" and key ~= "guibg" and key ~= "gui" then
- native[key] = value
- end
- end
- if definition.guifg ~= nil then
- native.fg = definition.guifg
- end
- if definition.guibg ~= nil then
- native.bg = definition.guibg
- end
- for key, value in pairs(definition.gui or {}) do
- native[key] = value
- end
- if options.default ~= nil then
- native.default = options.default
- end
- if options.force ~= nil then
- native.force = options.force
- end
- return native
-end
-
-function HostAdapter:define_highlight_group(name, definition, options)
- if type(name) ~= "string" or name == "" then
- fail("highlight group name must be a nonempty string", 2)
- end
- options = options or {}
- local runtime = self:runtime()
- if options.default and highlight_exists(runtime, name) then
- return false
- end
- runtime.api.nvim_set_hl(
- 0,
- name,
- native_highlight_definition(definition, options)
- )
- return true
-end
-
-local function overlay_positions(specification)
- local positions = specification.positions
- if positions == nil and specification.position ~= nil then
- positions = { specification.position }
- end
- if type(positions) ~= "table" then
- fail("highlight positions must be a list", 3)
- end
- local native = {}
- for index, position in ipairs(positions) do
- position = domain.Position.coerce(position)
- native[index] = { position.line, position.byte_column }
- end
- if #native == 0 then
- native[1] = { 0 }
- end
- return native
-end
-
-local function overlay_priority(value)
- if value == "high" then
- return 100
- end
- if value == "ordinary" or value == nil then
- return 10
- end
- if type(value) == "number" then
- return value
- end
- fail("highlight priority must be high, ordinary, or numeric", 3)
-end
-
-function HostAdapter:create_highlight(specification)
- if type(specification) ~= "table" then
- fail("highlight specification must be a table", 2)
- end
- if type(specification.group) ~= "string" or specification.group == "" then
- fail("highlight group must be a nonempty string", 2)
- end
- if specification.window == nil then
- fail("highlight window must identify its Nvim window", 2)
- end
- local record = adapter_records[self]
- local identity = specification.identity or next_identity(self, "highlight")
- if record.highlights[identity] ~= nil then
- fail("highlight identity is already active", 2)
- end
- local match_id = record.runtime.fn.matchaddpos(
- specification.group,
- overlay_positions(specification),
- overlay_priority(specification.priority),
- -1,
- { window = specification.window }
- )
- if type(match_id) ~= "number" or match_id < 0 then
- fail("Nvim could not create the window-local highlight", 2)
- end
- record.highlights[identity] = {
- match_id = match_id,
- window = specification.window,
- }
- return identity
-end
-
-function HostAdapter:remove_highlight(identity)
- local record = adapter_records[self]
- local resource = record.highlights[identity]
- if resource == nil then
- return false
- end
- record.highlights[identity] = nil
- record.runtime.fn.matchdelete(resource.match_id, resource.window)
- return true
-end
-
-local function nonnegative_integer(value, name)
- if type(value) ~= "number"
- or value < 0
- or value ~= math.floor(value)
- or value == math.huge
- then
- fail((name or "value") .. " must be a nonnegative integer", 3)
- end
- return value
-end
-
-function HostAdapter:supports_timers()
- local fn = self:runtime().fn
- return type(fn) == "table"
- and type(fn.timer_start) == "function"
- and type(fn.timer_stop) == "function"
-end
-
-function HostAdapter:start_timer(delay_ms, callback)
- nonnegative_integer(delay_ms, "timer delay")
- if type(callback) ~= "function" then
- fail("timer callback must be a function", 2)
- end
- if not self:supports_timers() then
- return nil
- end
- local record = adapter_records[self]
- local identity = next_identity(self, "timer")
- local timer_id = record.runtime.fn.timer_start(delay_ms, function()
- local resource = record.timers[identity]
- if resource == nil or not resource.active then
- return
- end
- record.timers[identity] = nil
- callback(identity)
- end)
- if type(timer_id) ~= "number" or timer_id < 0 then
- fail("Nvim could not start the timer", 2)
- end
- record.timers[identity] = {
- timer_id = timer_id,
- active = true,
- }
- return identity
-end
-
-function HostAdapter:stop_timer(identity)
- local record = adapter_records[self]
- local resource = record.timers[identity]
- if resource == nil or not resource.active then
- return false
- end
- record.timers[identity] = nil
- record.runtime.fn.timer_stop(resource.timer_id)
- return true
-end
-
-local function event_names(value)
- if type(value) == "string" then
- value = { value }
- end
- if type(value) ~= "table" or #value == 0 then
- fail("event names must be a nonempty list", 3)
- end
- local names = {}
- local set = {}
- for index, name in ipairs(value) do
- if type(name) ~= "string" or name == "" then
- fail("event name must be a nonempty string", 3)
- end
- if not set[name] then
- names[#names + 1] = name
- set[name] = true
- end
- end
- return names, set
-end
-
-local function event_payload(adapter, event)
- local payload = {
- buffer = event.buf,
- file = event.file,
- match = event.match,
- data = event.data,
- }
- local api = adapter:runtime().api
- if type(api.nvim_get_current_win) == "function" then
- payload.window = api.nvim_get_current_win()
- end
- return payload
-end
-
-local function event_augroup(record)
- if record.augroup == nil then
- record.augroup = record.runtime.api.nvim_create_augroup(
- "clever_f",
- { clear = true }
- )
- end
- return record.augroup
-end
-
-local function queue_event(record, name, payload, callback)
- return record.event_queue:emit(name, {
- name = name,
- payload = payload,
- callback = callback,
- })
-end
-
-function HostAdapter:register_events(names, callback, options)
- local name_set
- names, name_set = event_names(names)
- if type(callback) ~= "function" then
- fail("event callback must be a function", 2)
- end
- options = options or {}
- if type(options) ~= "table" then
- fail("event registration options must be a table", 2)
- end
- local record = adapter_records[self]
- local identity = next_identity(self, "event-registration")
- local autocmd_options = {
- group = event_augroup(record),
- desc = "clever-f " .. table.concat(names, "/"),
- callback = function(event)
- local resource = record.events[identity]
- if resource ~= nil and resource.active then
- queue_event(
- record,
- event.event,
- event_payload(self, event),
- resource.callback
- )
- end
- end,
- }
- if options.buffer ~= nil then
- autocmd_options.buffer = options.buffer
- end
- local autocmd_id = record.runtime.api.nvim_create_autocmd(
- names,
- autocmd_options
- )
- record.events[identity] = {
- autocmd_id = autocmd_id,
- names = names,
- name_set = name_set,
- callback = callback,
- buffer = options.buffer,
- active = true,
- }
- record.event_order[#record.event_order + 1] = identity
- return identity
-end
-
-function HostAdapter:remove_event_registration(identity)
- local record = adapter_records[self]
- local resource = record.events[identity]
- if resource == nil or not resource.active then
- return false
- end
- resource.active = false
- record.runtime.api.nvim_del_autocmd(resource.autocmd_id)
- return true
-end
-
-local DOT_MOTION_MAPPING = "<Plug>(clever-f-dot-motion)"
-
-local function dot_bridge_supported(runtime)
- return type(runtime.keymap) == "table"
- and type(runtime.keymap.set) == "function"
- and type(runtime.keymap.del) == "function"
- and type(runtime.fn.maparg) == "function"
- and type(runtime.fn.mapset) == "function"
- and type(runtime.api.nvim_feedkeys) == "function"
-end
-
-local function restore_dot_mapping(record)
- local bridge = record.dot_bridge
- if bridge == nil or not bridge.active then
- return false
- end
- bridge.active = false
- pcall(record.runtime.keymap.del, "n", ".")
- if type(bridge.previous_mapping) == "table"
- and next(bridge.previous_mapping) ~= nil
- then
- record.runtime.fn.mapset("n", false, bridge.previous_mapping)
- end
- return true
-end
-
-local function dot_replay_keys(runtime, count, operator)
- local prefix = count > 0 and tostring(count) or ""
- local keys = prefix .. operator .. DOT_MOTION_MAPPING
- if type(runtime.keycode) == "function" then
- return runtime.keycode(keys)
- end
- return runtime.api.nvim_replace_termcodes(keys, true, false, true)
-end
-
-install_dot_bridge = function(adapter)
- local record = adapter_records[adapter]
- local runtime = record.runtime
- if not dot_bridge_supported(runtime) then
- return nil
- end
- local bridge = record.dot_bridge
- if bridge == nil then
- bridge = {
- active = false,
- awaiting_change = false,
- previous_mapping = nil,
- }
- record.dot_bridge = bridge
- runtime.keymap.set("o", DOT_MOTION_MAPPING, function()
- local registration = record.dot_repeat
- if registration == nil or registration.callback == nil then
- return
- end
- local outcome = registration.callback(
- registration.payload,
- adapter:read_count()
- )
- if domain.ActionOutcome.is(outcome) then
- adapter:translate_action_outcome(outcome)
- end
- end, {
- silent = true,
- remap = false,
- desc = "clever-f dot motion",
- })
- local has_cmd_atom = type(runtime.fn.exists) == "function"
- and runtime.fn.exists("##CmdAtom") == 1
- local ownership_events = has_cmd_atom
- and "CmdAtom"
- or { "TextChanged", "TextChangedI", "TextChangedP" }
- runtime.api.nvim_create_autocmd(ownership_events, {
- group = event_augroup(record),
- desc = "clever-f dot ownership",
- callback = function(event)
- if has_cmd_atom and not (event.data and event.data.changed) then
- return
- end
- if bridge.awaiting_change then
- bridge.awaiting_change = false
- return
- end
- restore_dot_mapping(record)
- end,
- })
- end
- if not bridge.active then
- bridge.previous_mapping = runtime.fn.maparg(".", "n", false, true)
- runtime.keymap.set("n", ".", function()
- local registration = record.dot_repeat
- if registration == nil or registration.operator == "" then
- restore_dot_mapping(record)
- runtime.api.nvim_feedkeys(".", "n", false)
- return
- end
- bridge.awaiting_change = true
- local count = runtime.v.count or 0
- runtime.api.nvim_feedkeys(
- dot_replay_keys(runtime, count, registration.operator),
- "n",
- false
- )
- end, {
- silent = true,
- remap = false,
- desc = "clever-f dot repeat",
- })
- bridge.active = true
- end
- return bridge
-end
-
-function HostAdapter:deliver_event(name, payload)
- if type(name) ~= "string" or name == "" then
- fail("event name must be a nonempty string", 2)
- end
- payload = payload or {}
- local record = adapter_records[self]
- local event_buffer = payload.buffer
- for _, identity in ipairs(record.event_order) do
- local resource = record.events[identity]
- if resource.active
- and resource.name_set[name]
- and (resource.buffer == nil
- or event_buffer == nil
- or resource.buffer == event_buffer)
- then
- queue_event(record, name, payload, resource.callback)
- end
- end
-end
-
-function HostAdapter:begin_action_transition()
- local record = adapter_records[self]
- record.action_diagnostics = {}
- return record.event_queue:begin_transition()
-end
-
-function HostAdapter:commit_action_transition(token)
- local record = adapter_records[self]
- local result = record.event_queue:commit_transition(token)
- record.action_diagnostics = nil
- return result
-end
-
-local function terminal_cursor_option(runtime)
- return runtime.fn.eval("&t_ve")
-end
-
-local function set_terminal_cursor_option(runtime, value)
- runtime.api.nvim_cmd({
- cmd = "let",
- args = { "&t_ve", "=", runtime.fn.string(value) },
- }, {})
-end
-
-function HostAdapter:supports_cursor_presentation()
- local runtime = self:runtime()
- return type(runtime.api.nvim_get_option_value) == "function"
- and type(runtime.api.nvim_set_option_value) == "function"
- and type(runtime.api.nvim_cmd) == "function"
- and type(runtime.fn) == "table"
- and type(runtime.fn.exists) == "function"
- and runtime.fn.exists("+t_ve") == 1
- and type(runtime.fn.eval) == "function"
- and type(runtime.fn.string) == "function"
-end
-
-function HostAdapter:suppress_cursor_presentation()
- if not self:supports_cursor_presentation() then
- return nil
- end
- local record = adapter_records[self]
- local runtime = record.runtime
- local identity = next_identity(self, "cursor-presentation")
- local saved = {
- guicursor = runtime.api.nvim_get_option_value(
- "guicursor",
- { scope = "global" }
- ),
- terminal_cursor = terminal_cursor_option(runtime),
- }
- runtime.api.nvim_set_option_value(
- "guicursor",
- "a:ver1",
- { scope = "global" }
- )
- local ok, failure = pcall(set_terminal_cursor_option, runtime, "")
- if not ok then
- runtime.api.nvim_set_option_value(
- "guicursor",
- saved.guicursor,
- { scope = "global" }
- )
- error(failure, 0)
- end
- record.cursor_presentations[identity] = saved
- return identity
-end
-
-function HostAdapter:restore_cursor_presentation(identity)
- local record = adapter_records[self]
- local saved = record.cursor_presentations[identity]
- if saved == nil then
- return false
- end
- record.cursor_presentations[identity] = nil
- record.runtime.api.nvim_set_option_value(
- "guicursor",
- saved.guicursor,
- { scope = "global" }
- )
- set_terminal_cursor_option(record.runtime, saved.terminal_cursor)
- return true
-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)
- return self:emit_diagnostic("error", text)
-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
- local diagnostics = adapter_records[self].action_diagnostics
- local key = "error\0" .. outcome.diagnostic
- if diagnostics == nil or not diagnostics[key] then
- self:emit_action_error(outcome.diagnostic)
- end
- return M.ActionEffect.ERROR
- end
- return M.ActionEffect.NONE
-end
-
-local function packed(...)
- return { n = select("#", ...), ... }
-end
-
-local function invoke_callback(adapter, callback, ...)
- local arguments = packed(...)
- local token = adapter:begin_action_transition()
- local results = packed(pcall(function()
- local values = packed(callback(unpack_values(arguments, 1, arguments.n)))
- if domain.ActionOutcome.is(values[1]) then
- adapter:translate_action_outcome(values[1])
- end
- return unpack_values(values, 1, values.n)
- end))
- local commit = packed(pcall(adapter.commit_action_transition, adapter, token))
- if not results[1] then
- error(results[2], 0)
- end
- if not commit[1] then
- error(commit[2], 0)
- end
- return unpack_values(results, 2, results.n)
-end
-
-function HostAdapter:register_action(name, callback)
- if type(name) ~= "string" or name == "" then
- fail("action name must be a nonempty string", 2)
- end
- if type(callback) ~= "function" then
- fail("action callback must be a function", 2)
- end
- local actions = adapter_records[self].actions
- if actions[name] ~= nil then
- fail("action is already registered", 2)
- end
- actions[name] = callback
- return name
-end
-
-function HostAdapter:invoke_action(name, ...)
- local callback = adapter_records[self].actions[name]
- if callback == nil then
- fail("action is not registered", 2)
- end
- return invoke_callback(self, callback, ...)
-end
-
-function HostAdapter:invoke_callback(callback, ...)
- if type(callback) ~= "function" then
- fail("action callback must be a function", 2)
- end
- return invoke_callback(self, callback, ...)
-end
-
-local function mapping_modes(value)
- if type(value) == "string" then
- value = { value }
- end
- if type(value) ~= "table" or #value == 0 then
- fail("mapping modes must be a nonempty list", 3)
- end
- local result = {}
- for index, mode in ipairs(value) do
- if type(mode) ~= "string" or mode == "" then
- fail("mapping mode must be a nonempty string", 3)
- end
- result[index] = mode
- end
- return result
-end
-
-function HostAdapter:register_mapping(modes, lhs, action, options)
- modes = mapping_modes(modes)
- if type(lhs) ~= "string" or lhs == "" then
- fail("mapping lhs must be a nonempty string", 2)
- end
- if type(action) ~= "string" and type(action) ~= "function" then
- fail("mapping action must be an action name or function", 2)
- end
- options = options or {}
- if type(options) ~= "table" then
- fail("mapping options must be a table", 2)
- end
- local callback
- if type(action) == "string" then
- callback = function()
- return self:invoke_action(action)
- end
- else
- callback = function(...)
- return invoke_callback(self, action, ...)
- end
- end
- local native_options = {
- silent = options.silent == true,
- remap = options.remap == true,
- desc = options.desc
- or ("clever-f " .. (type(action) == "string" and action or lhs)),
- }
- self:runtime().keymap.set(modes, lhs, callback, native_options)
- local identity = next_identity(self, "mapping")
- adapter_records[self].mappings[identity] = {
- modes = modes,
- lhs = lhs,
- action = action,
- options = options,
- callback = callback,
- }
- return identity
-end
-
-function M.new(options)
- return HostAdapter.new(options)
-end
-
-setmetatable(M, {
- __call = function(_, options)
- return HostAdapter.new(options)
- end,
-})
-
-return M