fix(util): refactor debouncer
This commit is contained in:
+2
-2
@@ -101,7 +101,7 @@ function Repo:start_watcher()
|
|||||||
if err or (filename ~= "index" and filename ~= "HEAD") then
|
if err or (filename ~= "index" and filename ~= "HEAD") then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
self.refresh:call()
|
self.refresh()
|
||||||
end))
|
end))
|
||||||
self.watcher = watcher
|
self.watcher = watcher
|
||||||
end
|
end
|
||||||
@@ -241,7 +241,7 @@ local function refresh(buf)
|
|||||||
vim.b[buf].git_status = nil
|
vim.b[buf].git_status = nil
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
repo.refresh:call()
|
repo.refresh()
|
||||||
end
|
end
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|||||||
+3
-6
@@ -435,14 +435,11 @@ function Linter.add(bufnr, config)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local debouncer = util.debounce(function()
|
|
||||||
linter:run()
|
|
||||||
end, config.debounce)
|
|
||||||
vim.api.nvim_create_autocmd(config.events, {
|
vim.api.nvim_create_autocmd(config.events, {
|
||||||
buffer = linter.bufnr,
|
buffer = linter.bufnr,
|
||||||
callback = function()
|
callback = util.debounce(function()
|
||||||
debouncer:call()
|
linter:run()
|
||||||
end,
|
end, config.debounce) --[[@as fun()]],
|
||||||
group = linter.augroup,
|
group = linter.augroup,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ end
|
|||||||
|
|
||||||
local session = Session.new()
|
local session = Session.new()
|
||||||
|
|
||||||
local dispatcher = util.debounce(function(_, trigger_kind, char)
|
local dispatcher = util.debounce(function(trigger_kind, char)
|
||||||
if vim.fn.mode() ~= "i" then
|
if vim.fn.mode() ~= "i" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
+6
-10
@@ -1,7 +1,7 @@
|
|||||||
local log = require("log")
|
local log = require("log")
|
||||||
local util = require("util")
|
local util = require("util")
|
||||||
|
|
||||||
local config_dir = vim.fn.stdpath("config")
|
local plugins_dir = vim.fs.joinpath(vim.fn.stdpath("config"), "plugins")
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return boolean success
|
---@return boolean success
|
||||||
@@ -51,9 +51,7 @@ local function plugin_config_path(name)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local path = vim.fs.joinpath(config_dir, "plugins", normalized .. ".lua")
|
return vim.fs.joinpath(plugins_dir, normalized .. ".lua")
|
||||||
|
|
||||||
return path
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param name string
|
---@param name string
|
||||||
@@ -152,7 +150,7 @@ end
|
|||||||
---@type uv.uv_fs_event_t?
|
---@type uv.uv_fs_event_t?
|
||||||
local watcher = nil
|
local watcher = nil
|
||||||
|
|
||||||
---@type ow.Util.Debouncer?
|
---@type (fun(filename: string) | ow.Util.KeyedDebouncer<string>)?
|
||||||
local reload = nil
|
local reload = nil
|
||||||
|
|
||||||
---@class ow.Pack
|
---@class ow.Pack
|
||||||
@@ -185,10 +183,8 @@ function M.watch()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local plugins_dir = vim.fs.joinpath(config_dir, "plugins")
|
|
||||||
|
|
||||||
watcher = assert(vim.uv.new_fs_event())
|
watcher = assert(vim.uv.new_fs_event())
|
||||||
reload = util.debounce(function(filename)
|
reload = util.keyed_debounce(function(filename)
|
||||||
local path = vim.fs.joinpath(plugins_dir, filename)
|
local path = vim.fs.joinpath(plugins_dir, filename)
|
||||||
if not vim.uv.fs_stat(path) then
|
if not vim.uv.fs_stat(path) then
|
||||||
return
|
return
|
||||||
@@ -209,7 +205,7 @@ function M.watch()
|
|||||||
if not filename or not filename:match("%.lua$") then
|
if not filename or not filename:match("%.lua$") then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
reload:call(filename)
|
reload(filename)
|
||||||
end))
|
end))
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -219,7 +215,7 @@ function M.unwatch()
|
|||||||
end
|
end
|
||||||
|
|
||||||
if reload then
|
if reload then
|
||||||
reload:cancel()
|
reload:close()
|
||||||
reload = nil
|
reload = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+144
-81
@@ -1,8 +1,8 @@
|
|||||||
local log = require("log")
|
local log = require("log")
|
||||||
|
|
||||||
local Util = {}
|
local M = {}
|
||||||
|
|
||||||
Util.os_name = vim.uv.os_uname().sysname
|
M.os_name = vim.uv.os_uname().sysname
|
||||||
|
|
||||||
---@alias OutputStream "stdout" | "stderr" | "in_place"
|
---@alias OutputStream "stdout" | "stderr" | "in_place"
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ Util.os_name = vim.uv.os_uname().sysname
|
|||||||
|
|
||||||
--- Format buffer
|
--- Format buffer
|
||||||
---@param opts ow.FormatOptions
|
---@param opts ow.FormatOptions
|
||||||
function Util.format(opts)
|
function M.format(opts)
|
||||||
opts = {
|
opts = {
|
||||||
buf = opts.buf or vim.api.nvim_get_current_buf(),
|
buf = opts.buf or vim.api.nvim_get_current_buf(),
|
||||||
cmd = opts.cmd,
|
cmd = opts.cmd,
|
||||||
@@ -241,7 +241,7 @@ end
|
|||||||
---@param kt type
|
---@param kt type
|
||||||
---@param vt type
|
---@param vt type
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function Util.is_map(val, kt, vt)
|
function M.is_map(val, kt, vt)
|
||||||
if type(val) ~= "table" then
|
if type(val) ~= "table" then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@@ -263,7 +263,7 @@ end
|
|||||||
---@param val any
|
---@param val any
|
||||||
---@param t? type
|
---@param t? type
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function Util.is_list(val, t)
|
function M.is_list(val, t)
|
||||||
if not vim.islist(val) then
|
if not vim.islist(val) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@@ -285,115 +285,178 @@ end
|
|||||||
---@param val? any
|
---@param val? any
|
||||||
---@param t? type
|
---@param t? type
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function Util.is_list_or_nil(val, t)
|
function M.is_list_or_nil(val, t)
|
||||||
if val == nil then
|
if val == nil then
|
||||||
return true
|
return true
|
||||||
else
|
else
|
||||||
return Util.is_list(val, t)
|
return M.is_list(val, t)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local NIL_KEY = {}
|
|
||||||
|
|
||||||
---@class ow.Util.Debouncer.Slot
|
|
||||||
---@field timer uv.uv_timer_t
|
|
||||||
---@field cb function
|
|
||||||
---@field id any
|
|
||||||
---@field args table
|
|
||||||
|
|
||||||
---@class ow.Util.Debouncer
|
---@class ow.Util.Debouncer
|
||||||
---@field private _fn fun(id: any, ...)
|
---@field package _fn fun(...)
|
||||||
---@field private _delay integer
|
---@field package _delay integer
|
||||||
---@field private _slots table<any, ow.Util.Debouncer.Slot>
|
---@field package _timer uv.uv_timer_t
|
||||||
|
---@field package _gen integer
|
||||||
|
---@field package _fired_gen integer
|
||||||
|
---@field package _args? table
|
||||||
|
---@field package _cb_main fun()
|
||||||
|
---@field package _cb_uv fun()
|
||||||
local Debouncer = {}
|
local Debouncer = {}
|
||||||
Debouncer.__index = Debouncer
|
Debouncer.__index = Debouncer
|
||||||
|
|
||||||
---@param fn fun(id: any, ...)
|
---@param fn fun(...)
|
||||||
---@param delay integer
|
---@param delay integer
|
||||||
---@return ow.Util.Debouncer
|
---@return ow.Util.Debouncer
|
||||||
function Debouncer.new(fn, delay)
|
function Debouncer.new(fn, delay)
|
||||||
return setmetatable({ _fn = fn, _delay = delay, _slots = {} }, Debouncer)
|
local self = setmetatable({
|
||||||
end
|
_fn = fn,
|
||||||
|
_delay = delay,
|
||||||
---@param slot ow.Util.Debouncer.Slot
|
_timer = assert(vim.uv.new_timer()),
|
||||||
local function dispose(slot)
|
_gen = 0,
|
||||||
if not slot.timer:is_closing() then
|
_fired_gen = 0,
|
||||||
slot.timer:stop()
|
_args = nil,
|
||||||
slot.timer:close()
|
}, Debouncer)
|
||||||
|
self._cb_main = vim.schedule_wrap(function()
|
||||||
|
-- Identity check: the libuv fire may have been superseded by a
|
||||||
|
-- re-arm or a cancel between the timer firing and this scheduled
|
||||||
|
-- callback running.
|
||||||
|
if self._fired_gen ~= self._gen or self._args == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local args = self._args
|
||||||
|
self._args = nil
|
||||||
|
self._fn(vim.F.unpack_len(args))
|
||||||
|
end)
|
||||||
|
self._cb_uv = function()
|
||||||
|
self._fired_gen = self._gen
|
||||||
|
self._cb_main()
|
||||||
end
|
end
|
||||||
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param id? any
|
function Debouncer:__call(...)
|
||||||
---@param ... any
|
self._args = vim.F.pack_len(...)
|
||||||
function Debouncer:call(id, ...)
|
self._gen = self._gen + 1
|
||||||
local key = id == nil and NIL_KEY or id
|
self._timer:start(self._delay, 0, self._cb_uv)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Debouncer:cancel()
|
||||||
|
self._timer:stop()
|
||||||
|
self._args = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Debouncer:flush()
|
||||||
|
if self._args == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self._timer:stop()
|
||||||
|
local args = self._args
|
||||||
|
self._args = nil
|
||||||
|
self._fn(vim.F.unpack_len(args))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return boolean
|
||||||
|
function Debouncer:pending()
|
||||||
|
return self._args ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function Debouncer:close()
|
||||||
|
self._timer:stop()
|
||||||
|
if not self._timer:is_closing() then
|
||||||
|
self._timer:close()
|
||||||
|
end
|
||||||
|
self._args = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---@generic F: fun(...)
|
||||||
|
---@param fn F
|
||||||
|
---@param delay integer
|
||||||
|
---@return F | ow.Util.Debouncer
|
||||||
|
function M.debounce(fn, delay)
|
||||||
|
return Debouncer.new(fn, delay)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class ow.Util.KeyedDebouncer<T>
|
||||||
|
---@field package _fn fun(key: T, ...)
|
||||||
|
---@field package _delay integer
|
||||||
|
---@field package _slots table<T, ow.Util.Debouncer>
|
||||||
|
local KeyedDebouncer = {}
|
||||||
|
KeyedDebouncer.__index = KeyedDebouncer
|
||||||
|
|
||||||
|
---@generic T
|
||||||
|
---@param fn fun(key: T, ...)
|
||||||
|
---@param delay integer
|
||||||
|
---@return ow.Util.KeyedDebouncer<T>
|
||||||
|
function KeyedDebouncer.new(fn, delay)
|
||||||
|
return setmetatable({
|
||||||
|
_fn = fn,
|
||||||
|
_delay = delay,
|
||||||
|
_slots = {},
|
||||||
|
}, KeyedDebouncer)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@generic T
|
||||||
|
---@param self ow.Util.KeyedDebouncer<T>
|
||||||
|
---@param key T
|
||||||
|
function KeyedDebouncer:__call(key, ...)
|
||||||
local slot = self._slots[key]
|
local slot = self._slots[key]
|
||||||
local args = vim.F.pack_len(...)
|
|
||||||
if not slot then
|
if not slot then
|
||||||
-- cb lives on the slot so restart (the else branch below) can hand
|
slot = Debouncer.new(function(...)
|
||||||
-- the same closure back to timer:start, avoiding a fresh
|
self._fn(key, ...)
|
||||||
-- schedule_wrap allocation per call. It reads id/args from the
|
end, self._delay)
|
||||||
-- slot (not upvalues) so restarts pick up the latest args.
|
self._slots[key] = slot
|
||||||
local timer = assert(vim.uv.new_timer())
|
|
||||||
---@type ow.Util.Debouncer.Slot
|
|
||||||
local new_slot
|
|
||||||
new_slot = {
|
|
||||||
timer = timer,
|
|
||||||
id = id,
|
|
||||||
args = args,
|
|
||||||
cb = vim.schedule_wrap(function()
|
|
||||||
-- Slot may have been cancelled between libuv firing and
|
|
||||||
-- this scheduled callback running. Identity-check before
|
|
||||||
-- firing.
|
|
||||||
if self._slots[key] ~= new_slot then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
self._slots[key] = nil
|
|
||||||
dispose(new_slot)
|
|
||||||
self._fn(new_slot.id, vim.F.unpack_len(new_slot.args))
|
|
||||||
end),
|
|
||||||
}
|
|
||||||
self._slots[key] = new_slot
|
|
||||||
slot = new_slot
|
|
||||||
else
|
|
||||||
slot.id = id
|
|
||||||
slot.args = args
|
|
||||||
end
|
end
|
||||||
-- uv_timer_start on an already-active timer restarts it with the new
|
slot(...)
|
||||||
-- timeout, reusing the same handle (no per-call allocation).
|
|
||||||
assert(slot.timer:start(self._delay, 0, slot.cb))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param id? any
|
---@generic T
|
||||||
function Debouncer:cancel(id)
|
---@param self ow.Util.KeyedDebouncer<T>
|
||||||
local key = id == nil and NIL_KEY or id
|
---@param key T
|
||||||
|
function KeyedDebouncer:cancel(key)
|
||||||
local slot = self._slots[key]
|
local slot = self._slots[key]
|
||||||
if slot then
|
if slot then
|
||||||
|
slot:close()
|
||||||
self._slots[key] = nil
|
self._slots[key] = nil
|
||||||
dispose(slot)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Debouncer:cancel_all()
|
---@generic T
|
||||||
|
---@param self ow.Util.KeyedDebouncer<T>
|
||||||
|
---@param key T
|
||||||
|
function KeyedDebouncer:flush(key)
|
||||||
|
local slot = self._slots[key]
|
||||||
|
if slot then
|
||||||
|
slot:flush()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@generic T
|
||||||
|
---@param self ow.Util.KeyedDebouncer<T>
|
||||||
|
---@param key T
|
||||||
|
---@return boolean
|
||||||
|
function KeyedDebouncer:pending(key)
|
||||||
|
local slot = self._slots[key]
|
||||||
|
return slot ~= nil and slot:pending()
|
||||||
|
end
|
||||||
|
|
||||||
|
function KeyedDebouncer:close()
|
||||||
for _, slot in pairs(self._slots) do
|
for _, slot in pairs(self._slots) do
|
||||||
dispose(slot)
|
slot:close()
|
||||||
end
|
end
|
||||||
self._slots = {}
|
self._slots = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
Util.Debouncer = Debouncer
|
---@diagnostic disable-next-line: undefined-doc-name
|
||||||
|
---@generic T, F: fun(key: T, ...)
|
||||||
--- Creates a debounced function that delays execution of `fn` until after `delay` milliseconds have
|
---@param fn F
|
||||||
--- elapsed since the last time it was invoked. Use `d:call(id, ...)` to debounce per-id, or
|
---@param delay integer
|
||||||
--- `d:call(...)` for a single shared slot.
|
---@return F | ow.Util.KeyedDebouncer<T>
|
||||||
---@param fn fun(id: any, ...) Function to be debounced
|
function M.keyed_debounce(fn, delay)
|
||||||
---@param delay integer Debounce delay in milliseconds
|
return KeyedDebouncer.new(fn, delay)
|
||||||
---@return ow.Util.Debouncer
|
|
||||||
function Util.debounce(fn, delay)
|
|
||||||
return Debouncer.new(fn, delay)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Util.get_hl_source(name)
|
function M.get_hl_source(name)
|
||||||
local hl = vim.api.nvim_get_hl(0, { name = name })
|
local hl = vim.api.nvim_get_hl(0, { name = name })
|
||||||
while hl.link do
|
while hl.link do
|
||||||
hl = vim.api.nvim_get_hl(0, { name = hl.link })
|
hl = vim.api.nvim_get_hl(0, { name = hl.link })
|
||||||
@@ -402,4 +465,4 @@ function Util.get_hl_source(name)
|
|||||||
return hl
|
return hl
|
||||||
end
|
end
|
||||||
|
|
||||||
return Util
|
return M
|
||||||
|
|||||||
Reference in New Issue
Block a user