🧋 a tiny plugin for smoother movement keybinds
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: `throttle()` fn

robin d609f9e1 71c73041

+97 -42
+97 -42
plugin/smoothie.lua
··· 6 6 7 7 local cfg = vim.g.smoothie_config or {} 8 8 local defaults = { 9 - interval = 5, 10 - maxtime = 100, 9 + interval = 20, 10 + maxtime = 200, 11 11 } 12 12 13 13 local opt = function(default, ...) ··· 18 18 end) 19 19 20 20 local M, H = {}, {} 21 + Smoothie = vim.defaulttable(function(k) 22 + return M[k] or H[k] 23 + end) 21 24 22 25 local active = false 23 26 27 + ---@param win integer 28 + ---@param step fun(win, ...): true? returns true if repeater should be stopped 29 + ---@param init fun(): any[] 24 30 function M.smooth(win, step, init) 25 - -- on resume, returns whether still active 26 - local repeater = coroutine.wrap(function(delta, ...) 27 - while true do 28 - if step(delta, ...) then 29 - break 30 - end 31 - coroutine.yield(true) 32 - end 33 - active = false 34 - end) 31 + if active then 32 + vim.wait(1000, function() 33 + return not active 34 + end) 35 + end 36 + active = true 35 37 36 - coroutine.wrap(function() 37 - if active then 38 - vim.wait(1000, function() 39 - return not active 40 - end) 41 - end 42 - 43 - active = true 44 - local args = init() 38 + local args = init() 45 39 46 - local now = vim.uv.now() 40 + H.throttle(function(t) 41 + if not active then 42 + return t.cancel() 43 + end 47 44 48 - while active do 49 - local rel = vim.uv.now() - now 50 - local done = -1 51 - vim.defer_fn(function() 52 - done = repeater(rel / opts.maxtime, unpack(args)) 53 - end, opts.interval) 54 - if not vim.wait(1000, function() 55 - return done ~= -1 56 - end, opts.interval) then 57 - error("timeout") 58 - end 59 - vim.api.nvim__redraw({ win = win, valid = true, cursor = true }) 60 - vim.uv.update_time() 45 + if step(t.total_ms / opts.maxtime, unpack(args)) then 46 + active = false 47 + t.cancel() 61 48 end 62 - end)() 49 + 50 + vim.api.nvim__redraw({ win = win, valid = true, cursor = true }) 51 + end, opts.interval) 63 52 end 64 53 65 54 function M.scroll(vcount) ··· 113 102 local s = math.max(1, math.ceil(H.coillerp(math.abs(diff), delta))) 114 103 start.row = start.row + (m * s) 115 104 116 - vim._with({ win = win }, function() 117 - for _ = 1, s do 118 - local key = vim.api.nvim_replace_termcodes(m > 0 and "<c-y>" or "<c-e>", true, false, true) 119 - vim.api.nvim_feedkeys(key, "n", false) 120 - end 105 + vim.schedule(function() 106 + vim._with({ win = win }, function() 107 + for _ = 1, s do 108 + local key = vim.api.nvim_replace_termcodes(m > 0 and "<c-y>" or "<c-e>", true, false, true) 109 + vim.api.nvim_feedkeys(key, "n", false) 110 + end 111 + end) 121 112 end) 122 113 end 123 114 ··· 132 123 if wlast < wfirst then 133 124 m = 0 134 125 end 135 - local dst = vim.pos.cursor(buf, { math.ceil(wfirst + (wlast - wfirst) / 2), col }) 126 + local wheight = vim.api.nvim_win_get_height(win) 127 + local wdst = math.min(wlast, math.floor(wfirst + wheight / 2)) 128 + local dst = vim.pos.cursor(buf, { wdst, col }) 136 129 local diff = dst.row - start.row 137 130 m = m or diff / math.abs(diff) 138 131 ··· 173 166 t = H.clamp(0, t, 1) 174 167 return (math.log(1 + t) * 1 + math.log(1 + t)) * n 175 168 end 169 + 170 + ---@param f fun(ref): true? 171 + ---@param interval integer 172 + function H.throttle(f, interval) 173 + vim.validate("f", f, "function") 174 + vim.validate("interval", interval, "number") 175 + 176 + local repeater = vim.uv.new_timer() 177 + if not repeater then 178 + return 179 + end 180 + 181 + local now = vim.uv.now() 182 + local timer = { 183 + now = now, 184 + --- last tick 185 + tick = now, 186 + ntick = nil, 187 + total_ms = 0, 188 + cancel = function() 189 + if repeater ~= nil and not repeater:is_closing() then 190 + repeater:stop() 191 + repeater:close() 192 + return 193 + end 194 + end, 195 + } 196 + 197 + local ready = true 198 + repeater:start( 199 + 0, 200 + interval, 201 + vim.schedule_wrap(function() 202 + vim.uv.update_time() 203 + 204 + local ntick = vim.uv.now() 205 + vim.wait(interval, function() 206 + return ready 207 + end, 5, true) 208 + local wait_time = vim.uv.now() - ntick 209 + 210 + --- next tick 211 + timer.ntick = ntick 212 + -- total time since timer start 213 + timer.total_ms = timer.ntick - timer.now 214 + -- delta since last tick 215 + timer.delta = timer.ntick - timer.tick 216 + 217 + ready = false 218 + local timeout = math.max(0, interval - wait_time) 219 + vim.defer_fn(function() 220 + if f(timer) then 221 + timer.cancel() 222 + end 223 + 224 + ready = true 225 + end, timeout) 226 + 227 + timer.tick = timer.ntick 228 + end) 229 + ) 230 + end