diff --git a/plugin/smoothie.lua b/plugin/smoothie.lua --- a/plugin/smoothie.lua +++ b/plugin/smoothie.lua @@ -6,8 +6,8 @@ local cfg = vim.g.smoothie_config or {} local defaults = { - interval = 5, - maxtime = 100, + interval = 20, + maxtime = 200, } local opt = function(default, ...) @@ -18,48 +18,37 @@ end) local M, H = {}, {} +Smoothie = vim.defaulttable(function(k) + return M[k] or H[k] +end) local active = false +---@param win integer +---@param step fun(win, ...): true? returns true if repeater should be stopped +---@param init fun(): any[] function M.smooth(win, step, init) - -- on resume, returns whether still active - local repeater = coroutine.wrap(function(delta, ...) - while true do - if step(delta, ...) then - break - end - coroutine.yield(true) - end - active = false - end) + if active then + vim.wait(1000, function() + return not active + end) + end + active = true - coroutine.wrap(function() - if active then - vim.wait(1000, function() - return not active - end) + local args = init() + + H.throttle(function(t) + if not active then + return t.cancel() end - active = true - local args = init() - - local now = vim.uv.now() - - while active do - local rel = vim.uv.now() - now - local done = -1 - vim.defer_fn(function() - done = repeater(rel / opts.maxtime, unpack(args)) - end, opts.interval) - if not vim.wait(1000, function() - return done ~= -1 - end, opts.interval) then - error("timeout") - end - vim.api.nvim__redraw({ win = win, valid = true, cursor = true }) - vim.uv.update_time() + if step(t.total_ms / opts.maxtime, unpack(args)) then + active = false + t.cancel() end - end)() + + vim.api.nvim__redraw({ win = win, valid = true, cursor = true }) + end, opts.interval) end function M.scroll(vcount) @@ -113,11 +102,13 @@ local s = math.max(1, math.ceil(H.coillerp(math.abs(diff), delta))) start.row = start.row + (m * s) - vim._with({ win = win }, function() - for _ = 1, s do - local key = vim.api.nvim_replace_termcodes(m > 0 and "" or "", true, false, true) - vim.api.nvim_feedkeys(key, "n", false) - end + vim.schedule(function() + vim._with({ win = win }, function() + for _ = 1, s do + local key = vim.api.nvim_replace_termcodes(m > 0 and "" or "", true, false, true) + vim.api.nvim_feedkeys(key, "n", false) + end + end) end) end @@ -132,7 +123,9 @@ if wlast < wfirst then m = 0 end - local dst = vim.pos.cursor(buf, { math.ceil(wfirst + (wlast - wfirst) / 2), col }) + local wheight = vim.api.nvim_win_get_height(win) + local wdst = math.min(wlast, math.floor(wfirst + wheight / 2)) + local dst = vim.pos.cursor(buf, { wdst, col }) local diff = dst.row - start.row m = m or diff / math.abs(diff) @@ -172,4 +165,66 @@ function H.coillerp(n, t) t = H.clamp(0, t, 1) return (math.log(1 + t) * 1 + math.log(1 + t)) * n +end + +---@param f fun(ref): true? +---@param interval integer +function H.throttle(f, interval) + vim.validate("f", f, "function") + vim.validate("interval", interval, "number") + + local repeater = vim.uv.new_timer() + if not repeater then + return + end + + local now = vim.uv.now() + local timer = { + now = now, + --- last tick + tick = now, + ntick = nil, + total_ms = 0, + cancel = function() + if repeater ~= nil and not repeater:is_closing() then + repeater:stop() + repeater:close() + return + end + end, + } + + local ready = true + repeater:start( + 0, + interval, + vim.schedule_wrap(function() + vim.uv.update_time() + + local ntick = vim.uv.now() + vim.wait(interval, function() + return ready + end, 5, true) + local wait_time = vim.uv.now() - ntick + + --- next tick + timer.ntick = ntick + -- total time since timer start + timer.total_ms = timer.ntick - timer.now + -- delta since last tick + timer.delta = timer.ntick - timer.tick + + ready = false + local timeout = math.max(0, interval - wait_time) + vim.defer_fn(function() + if f(timer) then + timer.cancel() + end + + ready = true + end, timeout) + + timer.tick = timer.ntick + end) + ) end