diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 234370c..37d8f9b 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -26,6 +26,7 @@ local M = {} ---@field NextParagraph Precognition.HintOpts ---@class Precognition.Config +---@field debounce integer ---@field startVisible boolean ---@field showBlankVirtLine boolean ---@field highlightColor vim.api.keyset.highlight @@ -74,6 +75,7 @@ local defaultHintConfig = { ---@type Precognition.Config local default = { + debounce = 0, startVisible = true, showBlankVirtLine = true, highlightColor = { link = "Comment" }, @@ -264,21 +266,6 @@ local function display_marks() dirty = false end -local function on_cursor_moved(ev) - local buf = ev and ev.buf or vim.api.nvim_get_current_buf() - if extmark then - local ext = vim.api.nvim_buf_get_extmark_by_id(buf, ns, extmark, { - details = true, - }) - if ext and ext[1] ~= vim.api.nvim_win_get_cursor(0)[1] - 1 then - vim.api.nvim_buf_del_extmark(0, ns, extmark) - extmark = nil - end - end - dirty = true - display_marks() -end - local function on_insert_enter(ev) if extmark then vim.api.nvim_buf_del_extmark(ev.buf, ns, extmark) @@ -287,6 +274,24 @@ local function on_insert_enter(ev) dirty = true end +---@param draw fun() +local function cursor_moved_handler(draw) + return function(ev) + local buf = ev and ev.buf or vim.api.nvim_get_current_buf() + if extmark then + local ext = vim.api.nvim_buf_get_extmark_by_id(buf, ns, extmark, { + details = true, + }) + if ext and ext[1] ~= vim.api.nvim_win_get_cursor(0)[1] - 1 then + vim.api.nvim_buf_del_extmark(0, ns, extmark) + extmark = nil + end + end + dirty = true + draw() + end +end + local function on_buf_edit() apply_gutter_hints(build_gutter_hints()) end @@ -318,6 +323,11 @@ function M.show() end visible = true + local draw = display_marks + if config.debounce > 0 then + draw = utils.debounce_trailing(display_marks, config.debounce) + end + -- clear the extmark entirely when leaving a buffer (hints should only show in current buffer) vim.api.nvim_create_autocmd("BufLeave", { group = au, @@ -328,11 +338,13 @@ function M.show() group = au, callback = on_buf_edit, }) + -- clear the extmark when the cursor moves, or when insert mode is entered -- vim.api.nvim_create_autocmd("CursorMoved", { group = au, - callback = on_cursor_moved, + -- callback = on_cursor_moved, + callback = cursor_moved_handler(draw), }) vim.api.nvim_create_autocmd("InsertEnter", { @@ -340,7 +352,7 @@ function M.show() callback = on_insert_enter, }) - display_marks() + draw() end --- Disable automatic showing of hints @@ -396,7 +408,7 @@ local state = { return build_gutter_hints end, on_cursor_moved = function() - return on_cursor_moved + return cursor_moved_handler(display_marks) end, extmark = function() return extmark diff --git a/lua/precognition/utils.lua b/lua/precognition/utils.lua index c01d5fc..2b10e0d 100644 --- a/lua/precognition/utils.lua +++ b/lua/precognition/utils.lua @@ -65,4 +65,42 @@ function M.add_multibyte_padding(cur_line, extra_padding, line_len) end end +---Debounces calls to a function, and ensures it only runs once per delay +---even if called repeatedly. +---@param fn fun(...: any) +---@param delay integer +function M.debounce_trailing(fn, delay) + local running = false + local timer = assert(vim.uv.new_timer()) + + -- Ugly hack to ensure timer is closed when the function is garbage collected + -- unfortunate but necessary to avoid creating a new timer for each call. + -- + -- In LuaJIT, only userdata can have finalizers. `newproxy` creates an opaque userdata + -- which we can attach a finalizer to and use as a "canary." + local proxy = newproxy(true) + getmetatable(proxy).__gc = function() + if not timer:is_closing() then + timer:close() + end + end + + return function(...) + local _ = proxy + if running then + return + end + running = true + local args = { ... } + timer:start( + delay, + 0, + vim.schedule_wrap(function() + fn(unpack(args)) + running = false + end) + ) + end +end + return M