summaryrefslogtreecommitdiff
path: root/lua/clever_f
diff options
context:
space:
mode:
authorJackson Moore <jacksonmoore@tuta.io>2026-09-04 14:26:06 +0200
committerJackson Moore <jacksonmoore@tuta.io>2026-09-04 14:26:06 +0200
commitae03a565105416f52a09bc0a171a7fdbb9ced5a0 (patch)
treebb4f92e9934e759e1a327e60b34e8ccd34260a86 /lua/clever_f
parent4cb3fb0f1532de3465962903a1697339ad3d6159 (diff)
Translate timers and events
Diffstat (limited to 'lua/clever_f')
-rw-r--r--lua/clever_f/host_adapter.lua206
1 files changed, 205 insertions, 1 deletions
diff --git a/lua/clever_f/host_adapter.lua b/lua/clever_f/host_adapter.lua
index 747d9b8..ab3467c 100644
--- a/lua/clever_f/host_adapter.lua
+++ b/lua/clever_f/host_adapter.lua
@@ -1,3 +1,4 @@
+local capabilities = require("clever_f.capabilities")
local domain = require("clever_f.domain")
local M = {}
@@ -39,11 +40,19 @@ function HostAdapter.new(options)
return options
end
local adapter = setmetatable({}, HostAdapter)
- adapter_records[adapter] = {
+ local record = {
runtime = require_runtime(current_runtime(options)),
next_identity = 1,
highlights = {},
+ timers = {},
+ events = {},
+ event_order = {},
+ augroup = nil,
}
+ record.event_queue = capabilities.EventQueue.new(function(_, delivery)
+ delivery.callback(delivery.name, delivery.payload)
+ end)
+ adapter_records[adapter] = record
return adapter
end
@@ -213,6 +222,201 @@ function HostAdapter:remove_highlight(identity)
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
+ resource.active = false
+ 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
+ resource.active = false
+ 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
+
+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()
+ return adapter_records[self].event_queue:begin_transition()
+end
+
+function HostAdapter:commit_action_transition(token)
+ return adapter_records[self].event_queue:commit_transition(token)
+end
+
local function escape_key(runtime)
if type(runtime.keycode) == "function" then
return runtime.keycode("<Esc>")