Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add optional debounce #55

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .luarc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
"type": {
"checkTableShape": true
},
"diagnostics.globals": [
"describe",
"it",
"before_each",
"after_each"
]
"diagnostics.globals": [
"describe",
"it",
"before_each",
"after_each"
]
}
83 changes: 59 additions & 24 deletions lua/precognition/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ local M = {}
---@field NextParagraph Precognition.HintOpts

---@class Precognition.Config
---@field debounceMs integer
---@field startVisible boolean
---@field showBlankVirtLine boolean
---@field highlightColor vim.api.keyset.highlight
Expand Down Expand Up @@ -73,6 +74,7 @@ local defaultHintConfig = {

---@type Precognition.Config
local default = {
debounceMs = 0,
startVisible = true,
showBlankVirtLine = true,
highlightColor = { link = "Comment" },
Expand Down Expand Up @@ -301,21 +303,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)
Expand All @@ -324,6 +311,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
Expand Down Expand Up @@ -388,29 +393,44 @@ function M.show()
end
visible = true

local prev_line
local draw = display_marks
if config.debounceMs > 0 then
local debounced = require("precognition.utils").debounce_trailing(display_marks, config.debounceMs)
draw = function(...)
local line = vim.api.nvim_win_get_cursor(0)[1]
if line == prev_line then
display_marks()
else
prev_line = line
end
debounced(...)
end
end

-- clear and redraw the hints when the cursor moves
vim.api.nvim_create_autocmd("CursorMoved", {
group = au,
callback = cursor_moved_handler(draw),
})

-- clear the extmark entirely when leaving a buffer (hints should only show in current buffer)
vim.api.nvim_create_autocmd("BufLeave", {
group = au,
callback = on_buf_leave,
})

-- clear the extmark when the cursor moves in insert mode
vim.api.nvim_create_autocmd("CursorMovedI", {
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,
})

vim.api.nvim_create_autocmd("InsertEnter", {
group = au,
callback = on_insert_enter,
})

display_marks()
draw()
end

--- Disable automatic showing of hints
Expand Down Expand Up @@ -479,7 +499,22 @@ local state = {
return build_gutter_hints
end,
on_cursor_moved = function()
return on_cursor_moved
local prev_line
local draw = display_marks
if config.debounceMs > 0 then
local debounced = require("precognition.utils").debounce_trailing(display_marks, config.debounceMs)
draw = function(...)
local line = vim.api.nvim_win_get_cursor(0)[1]
if line == prev_line then
display_marks()
else
prev_line = line
end
debounced(...)
end
end

return cursor_moved_handler(draw)
end,
extmark = function()
return extmark
Expand Down
38 changes: 38 additions & 0 deletions lua/precognition/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,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
43 changes: 43 additions & 0 deletions tests/precognition/e2e_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,49 @@ describe("e2e tests", function()
end
end)

it("supports debounce", function()
precognition.setup({
debounceMs = 200,
})

local buffer = vim.api.nvim_create_buf(true, false)
vim.api.nvim_set_current_buf(buffer)
vim.api.nvim_buf_set_lines(
buffer,
0,
-1,
false,
{ "Hello World this is a test", "line 2", "", "line 4", "", "line 6" }
)

precognition.on_cursor_moved()

---@diagnostic disable-next-line: undefined-field
assert.is_nil(precognition.extmark)

eq(
{},
vim.api.nvim_buf_get_extmarks(buffer, precognition.ns, 0, -1, {
details = true,
})
)

local co = coroutine.running()
coroutine.yield(vim.defer_fn(function()
coroutine.resume(co)
end, 210))

---@diagnostic disable-next-line: undefined-field
assert.not_nil(precognition.extmark)

local extmarks = vim.api.nvim_buf_get_extmark_by_id(buffer, precognition.ns, precognition.extmark, {
details = true,
})

eq(vim.api.nvim_win_get_cursor(0)[1] - 1, extmarks[1])
eq("^ e w $", extmarks[3].virt_lines[1][1][1])
end)

it("virtual line text color can be customised", function()
precognition.setup({ highlightColor = { link = "Function" } })
local buffer = vim.api.nvim_create_buf(true, false)
Expand Down
Loading