From 7d6bcccef6e2626b332c5df764c268874a7bd012 Mon Sep 17 00:00:00 2001 From: Will Hopkins Date: Sat, 1 Jun 2024 10:51:04 -0700 Subject: [PATCH 1/3] feat: add optional debounce --- lua/precognition/init.lua | 83 +++++++++++++++++++++++---------- lua/precognition/utils.lua | 38 +++++++++++++++ tests/precognition/e2e_spec.lua | 41 ++++++++++++++++ 3 files changed, 138 insertions(+), 24 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 7b74bf7..84bb6d3 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -24,6 +24,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 @@ -73,6 +74,7 @@ local defaultHintConfig = { ---@type Precognition.Config local default = { + debounce = 0, startVisible = true, showBlankVirtLine = true, highlightColor = { link = "Comment" }, @@ -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) @@ -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 @@ -388,29 +393,44 @@ function M.show() end visible = true + local prev_line + local draw = display_marks + if config.debounce > 0 then + local debounced = utils.debounce_trailing(display_marks, config.debounce) + 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 @@ -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.debounce > 0 then + local debounced = utils.debounce_trailing(display_marks, config.debounce) + 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 diff --git a/lua/precognition/utils.lua b/lua/precognition/utils.lua index 5c10bb1..9013c09 100644 --- a/lua/precognition/utils.lua +++ b/lua/precognition/utils.lua @@ -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 diff --git a/tests/precognition/e2e_spec.lua b/tests/precognition/e2e_spec.lua index 6917d7d..29fe04c 100644 --- a/tests/precognition/e2e_spec.lua +++ b/tests/precognition/e2e_spec.lua @@ -122,6 +122,47 @@ describe("e2e tests", function() end end) + it("supports debounce", function() + precognition.setup({ + debounce = 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() + + 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)) + + 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) From 28bc1d742e566c3454fade1740f3b75eca2e179d Mon Sep 17 00:00:00 2001 From: Will Hopkins Date: Sat, 1 Jun 2024 12:19:45 -0700 Subject: [PATCH 2/3] rename debounce -> debounceMs --- lua/precognition/init.lua | 12 ++++++------ tests/precognition/e2e_spec.lua | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index 84bb6d3..f76da0b 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -24,7 +24,7 @@ local M = {} ---@field NextParagraph Precognition.HintOpts ---@class Precognition.Config ----@field debounce integer +---@field debounceMs integer ---@field startVisible boolean ---@field showBlankVirtLine boolean ---@field highlightColor vim.api.keyset.highlight @@ -74,7 +74,7 @@ local defaultHintConfig = { ---@type Precognition.Config local default = { - debounce = 0, + debounceMs = 0, startVisible = true, showBlankVirtLine = true, highlightColor = { link = "Comment" }, @@ -395,8 +395,8 @@ function M.show() local prev_line local draw = display_marks - if config.debounce > 0 then - local debounced = utils.debounce_trailing(display_marks, config.debounce) + if config.debounceMs > 0 then + local debounced = utils.debounce_trailing(display_marks, config.debounceMs) draw = function(...) local line = vim.api.nvim_win_get_cursor(0)[1] if line == prev_line then @@ -501,8 +501,8 @@ local state = { on_cursor_moved = function() local prev_line local draw = display_marks - if config.debounce > 0 then - local debounced = utils.debounce_trailing(display_marks, config.debounce) + if config.debounceMs > 0 then + local debounced = utils.debounce_trailing(display_marks, config.debounceMs) draw = function(...) local line = vim.api.nvim_win_get_cursor(0)[1] if line == prev_line then diff --git a/tests/precognition/e2e_spec.lua b/tests/precognition/e2e_spec.lua index 29fe04c..56f3eb3 100644 --- a/tests/precognition/e2e_spec.lua +++ b/tests/precognition/e2e_spec.lua @@ -124,7 +124,7 @@ describe("e2e tests", function() it("supports debounce", function() precognition.setup({ - debounce = 200, + debounceMs = 200, }) local buffer = vim.api.nvim_create_buf(true, false) From 959a55d47a962ac7c0b3ca3441ed9f98301f4c15 Mon Sep 17 00:00:00 2001 From: tris203 Date: Thu, 5 Dec 2024 22:23:45 +0000 Subject: [PATCH 3/3] refactor: minor fixups --- .luarc.json | 12 ++++++------ lua/precognition/init.lua | 4 ++-- tests/precognition/e2e_spec.lua | 2 ++ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.luarc.json b/.luarc.json index 3826102..4fee847 100644 --- a/.luarc.json +++ b/.luarc.json @@ -6,10 +6,10 @@ "type": { "checkTableShape": true }, - "diagnostics.globals": [ - "describe", - "it", - "before_each", - "after_each" - ] + "diagnostics.globals": [ + "describe", + "it", + "before_each", + "after_each" + ] } diff --git a/lua/precognition/init.lua b/lua/precognition/init.lua index f76da0b..bff495e 100644 --- a/lua/precognition/init.lua +++ b/lua/precognition/init.lua @@ -396,7 +396,7 @@ function M.show() local prev_line local draw = display_marks if config.debounceMs > 0 then - local debounced = utils.debounce_trailing(display_marks, config.debounceMs) + 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 @@ -502,7 +502,7 @@ local state = { local prev_line local draw = display_marks if config.debounceMs > 0 then - local debounced = utils.debounce_trailing(display_marks, config.debounceMs) + 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 diff --git a/tests/precognition/e2e_spec.lua b/tests/precognition/e2e_spec.lua index 56f3eb3..c13650a 100644 --- a/tests/precognition/e2e_spec.lua +++ b/tests/precognition/e2e_spec.lua @@ -139,6 +139,7 @@ describe("e2e tests", function() precognition.on_cursor_moved() + ---@diagnostic disable-next-line: undefined-field assert.is_nil(precognition.extmark) eq( @@ -153,6 +154,7 @@ describe("e2e tests", 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, {