-- Credit: https://gist.github.com/runiq/31aa5c4bf00f8e0843cd267880117201 -- local M = {} ---Validates args for `throttle()` and `debounce()`. local function td_validate(fn, ms) vim.validate{ fn = { fn, 'f' }, ms = { ms, function(ms) return type(ms) == 'number' and ms > 0 end, "number > 0", }, } end --- Throttles a function on the leading edge. Automatically `schedule_wrap()`s. --- --@param fn (function) Function to throttle --@param timeout (number) Timeout in ms --@returns (function, timer) throttled function and timer. Remember to call ---`timer:close()` at the end or you will leak memory! function M.throttle_leading(fn, ms) td_validate(fn, ms) local timer = vim.loop.new_timer() local running = false local function wrapped_fn(...) if not running then timer:start(ms, 0, function() running = false end) running = true pcall(vim.schedule_wrap(fn), select(1, ...)) end end return wrapped_fn, timer end --- Throttles a function on the trailing edge. Automatically --- `schedule_wrap()`s. --- --@param fn (function) Function to throttle --@param timeout (number) Timeout in ms --@param last (boolean, optional) Whether to use the arguments of the last ---call to `fn` within the timeframe. Default: Use arguments of the first call. --@returns (function, timer) Throttled function and timer. Remember to call ---`timer:close()` at the end or you will leak memory! function M.throttle_trailing(fn, ms, last) td_validate(fn, ms) local timer = vim.loop.new_timer() local running = false local wrapped_fn if not last then function wrapped_fn(...) if not running then local argv = {...} local argc = select('#', ...) timer:start(ms, 0, function() running = false pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc)) end) running = true end end else local argv, argc function wrapped_fn(...) argv = {...} argc = select('#', ...) if not running then timer:start(ms, 0, function() running = false pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc)) end) running = true end end end return wrapped_fn, timer end --- Debounces a function on the leading edge. Automatically `schedule_wrap()`s. --- --@param fn (function) Function to debounce --@param timeout (number) Timeout in ms --@returns (function, timer) Debounced function and timer. Remember to call ---`timer:close()` at the end or you will leak memory! function M.debounce_leading(fn, ms) td_validate(fn, ms) local timer = vim.loop.new_timer() local running = false local function wrapped_fn(...) timer:start(ms, 0, function() running = false end) if not running then running = true pcall(vim.schedule_wrap(fn), select(1, ...)) end end return wrapped_fn, timer end --- Debounces a function on the trailing edge. Automatically --- `schedule_wrap()`s. --- --@param fn (function) Function to debounce --@param timeout (number) Timeout in ms --@param first (boolean, optional) Whether to use the arguments of the first ---call to `fn` within the timeframe. Default: Use arguments of the last call. --@returns (function, timer) Debounced function and timer. Remember to call ---`timer:close()` at the end or you will leak memory! function M.debounce_trailing(fn, ms, first) td_validate(fn, ms) local timer = vim.loop.new_timer() local wrapped_fn if not first then function wrapped_fn(...) local argv = {...} local argc = select('#', ...) timer:start(ms, 0, function() pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc)) end) end else local argv, argc function wrapped_fn(...) argv = argv or {...} argc = argc or select('#', ...) timer:start(ms, 0, function() pcall(vim.schedule_wrap(fn), unpack(argv, 1, argc)) end) end end return wrapped_fn, timer end --- Test deferment methods (`{throttle,debounce}_{leading,trailing}()`). --- --@param bouncer (string) Bouncer function to test --@param ms (number, optional) Timeout in ms, default 2000. --@param firstlast (bool, optional) Whether to use the 'other' fn call ---strategy. function M.test_defer(bouncer, ms, firstlast) local bouncers = { tl = M.throttle_leading, tt = M.throttle_trailing, dl = M.debounce_leading, dt = M.debounce_trailing, } local timeout = ms or 2000 local bounced = bouncers[bouncer]( function(i) vim.cmd('echom "' .. bouncer .. ': ' .. i .. '"') end, timeout, firstlast ) for i, _ in ipairs{1,2,3,4,5} do bounced(i) vim.schedule(function () vim.cmd('echom ' .. i) end) vim.fn.call("wait", {1000, "v:false"}) end end return M