From b33242f900b93f6fad3e17d401a01d7d4fc4f8e4 Mon Sep 17 00:00:00 2001 From: Masaaki Hirotsu Date: Tue, 10 Feb 2026 22:49:51 +0900 Subject: [PATCH 1/3] perf(events): scope CursorMoved tracking to popup buffers --- lua/peekstack/core/events.lua | 47 +++++++++++++++++++++++++---------- tests/events_spec.lua | 39 ++++++++++++++++++----------- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/lua/peekstack/core/events.lua b/lua/peekstack/core/events.lua index c64564f..c08e9d0 100644 --- a/lua/peekstack/core/events.lua +++ b/lua/peekstack/core/events.lua @@ -7,6 +7,8 @@ local M = {} ---@type uv.uv_timer_t? local reflow_timer = nil local REFLOW_DEBOUNCE_MS = 80 +---@type table +local popup_cursor_buffers = {} local function reset_reflow_timer() local store = timer_util.get_store() @@ -31,6 +33,27 @@ local function debounced_reflow() end) end +---@param group integer +---@param bufnr integer +local function ensure_popup_cursor_tracking(group, bufnr) + if popup_cursor_buffers[bufnr] then + return + end + popup_cursor_buffers[bufnr] = true + + vim.api.nvim_create_autocmd("CursorMoved", { + group = group, + buffer = bufnr, + callback = function() + local winid = vim.api.nvim_get_current_win() + if vim.w[winid].peekstack_popup_id == nil then + return + end + stack.touch(winid) + end, + }) +end + ---Close all ephemeral popups across all stacks and reflow local function close_ephemeral_popups() stack.close_ephemerals() @@ -46,6 +69,7 @@ end function M.setup() reset_reflow_timer() + popup_cursor_buffers = {} local group = vim.api.nvim_create_augroup("PeekstackEvents", { clear = true }) vim.api.nvim_create_autocmd("WinClosed", { @@ -63,6 +87,7 @@ function M.setup() callback = function(args) stack.handle_buf_wipeout(args.buf) stack.handle_origin_wipeout(args.buf) + popup_cursor_buffers[args.buf] = nil end, }) @@ -71,23 +96,14 @@ function M.setup() callback = debounced_reflow, }) - vim.api.nvim_create_autocmd("CursorMoved", { - group = group, - callback = function() - local winid = vim.api.nvim_get_current_win() - if vim.w[winid].peekstack_popup_id == nil then - return - end - if is_floating_window(winid) then - stack.touch(winid) - end - end, - }) - vim.api.nvim_create_autocmd("WinEnter", { group = group, callback = function() local winid = vim.api.nvim_get_current_win() + if vim.w[winid].peekstack_popup_id ~= nil then + local bufnr = vim.api.nvim_win_get_buf(winid) + ensure_popup_cursor_tracking(group, bufnr) + end if is_floating_window(winid) then stack.touch(winid) local layout = require("peekstack.core.layout") @@ -99,6 +115,11 @@ function M.setup() end, }) + local current_winid = vim.api.nvim_get_current_win() + if vim.w[current_winid].peekstack_popup_id ~= nil then + ensure_popup_cursor_tracking(group, vim.api.nvim_win_get_buf(current_winid)) + end + local cfg = config.get() local close_events = cfg.ui.quick_peek and cfg.ui.quick_peek.close_events or { "CursorMoved", "InsertEnter", "BufLeave", "WinLeave" } diff --git a/tests/events_spec.lua b/tests/events_spec.lua index 30b2665..471b72c 100644 --- a/tests/events_spec.lua +++ b/tests/events_spec.lua @@ -5,7 +5,6 @@ describe("peekstack.core.events", function() local original_touch local original_close_ephemerals - local original_win_get_config before_each(function() config.setup({ @@ -16,28 +15,21 @@ describe("peekstack.core.events", function() }) original_touch = stack.touch original_close_ephemerals = stack.close_ephemerals - original_win_get_config = vim.api.nvim_win_get_config end) after_each(function() stack.touch = original_touch stack.close_ephemerals = original_close_ephemerals - vim.api.nvim_win_get_config = original_win_get_config local winid = vim.api.nvim_get_current_win() vim.w[winid].peekstack_popup_id = nil end) it("skips non-peekstack windows on CursorMoved", function() local touch_calls = 0 - local config_calls = 0 stack.touch = function() touch_calls = touch_calls + 1 end stack.close_ephemerals = function() end - vim.api.nvim_win_get_config = function(winid) - config_calls = config_calls + 1 - return original_win_get_config(winid) - end local winid = vim.api.nvim_get_current_win() vim.w[winid].peekstack_popup_id = nil @@ -46,28 +38,45 @@ describe("peekstack.core.events", function() vim.api.nvim_exec_autocmds("CursorMoved", { modeline = false }) assert.equals(0, touch_calls) - assert.equals(0, config_calls) end) it("touches peekstack popup windows on CursorMoved", function() local touch_calls = 0 - local config_calls = 0 stack.touch = function() touch_calls = touch_calls + 1 end stack.close_ephemerals = function() end - vim.api.nvim_win_get_config = function() - config_calls = config_calls + 1 - return { relative = "editor" } - end local winid = vim.api.nvim_get_current_win() vim.w[winid].peekstack_popup_id = 999 events.setup() + vim.api.nvim_exec_autocmds("WinEnter", { modeline = false }) vim.api.nvim_exec_autocmds("CursorMoved", { modeline = false }) assert.equals(1, touch_calls) - assert.equals(1, config_calls) + end) + + it("registers CursorMoved autocmd only for popup buffers", function() + local winid = vim.api.nvim_get_current_win() + local bufnr = vim.api.nvim_get_current_buf() + + events.setup() + local before = vim.api.nvim_get_autocmds({ + group = "PeekstackEvents", + event = "CursorMoved", + buffer = bufnr, + }) + assert.equals(0, #before) + + vim.w[winid].peekstack_popup_id = 999 + vim.api.nvim_exec_autocmds("WinEnter", { modeline = false }) + + local after = vim.api.nvim_get_autocmds({ + group = "PeekstackEvents", + event = "CursorMoved", + buffer = bufnr, + }) + assert.equals(1, #after) end) end) From 157a638d9a818e0efa5c7bbeab62dd06a98e4b2e Mon Sep 17 00:00:00 2001 From: Masaaki Hirotsu Date: Tue, 10 Feb 2026 22:50:00 +0900 Subject: [PATCH 2/3] perf(ui): cache inline preview namespace id --- lua/peekstack/ui/inline_preview.lua | 20 +++++++++++++++++--- tests/inline_preview_spec.lua | 29 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/lua/peekstack/ui/inline_preview.lua b/lua/peekstack/ui/inline_preview.lua index 612031e..56f46e2 100644 --- a/lua/peekstack/ui/inline_preview.lua +++ b/lua/peekstack/ui/inline_preview.lua @@ -2,6 +2,8 @@ local config = require("peekstack.config") local fs = require("peekstack.util.fs") local NS_NAME = "PeekstackInlinePreviewNS" +---@type integer? +local namespace_id = nil ---@type PeekstackInlinePreviewState? local state = nil @@ -13,10 +15,14 @@ local M = {} ---Get or create the inline preview namespace ---@return integer local function get_namespace() - if not vim.api.nvim_get_namespaces()[NS_NAME] then - vim.api.nvim_create_namespace(NS_NAME) + if namespace_id then + return namespace_id end - return vim.api.nvim_get_namespaces()[NS_NAME] + namespace_id = vim.api.nvim_get_namespaces()[NS_NAME] + if not namespace_id then + namespace_id = vim.api.nvim_create_namespace(NS_NAME) + end + return namespace_id end ---Check if inline preview is currently open @@ -228,4 +234,12 @@ function M.setup_close_events() end end +---Reset inline preview internals (for testing). +function M._reset_for_test() + M.close() + state = nil + request_id = 0 + namespace_id = nil +end + return M diff --git a/tests/inline_preview_spec.lua b/tests/inline_preview_spec.lua index 1286d54..f91d8ec 100644 --- a/tests/inline_preview_spec.lua +++ b/tests/inline_preview_spec.lua @@ -4,6 +4,7 @@ describe("peekstack.ui.inline_preview", function() local temp_file = nil before_each(function() + inline_preview._reset_for_test() -- Setup config with inline preview enabled config.setup({ ui = { @@ -20,6 +21,7 @@ describe("peekstack.ui.inline_preview", function() end) after_each(function() + inline_preview._reset_for_test() if temp_file then vim.fn.delete(temp_file) end @@ -119,4 +121,31 @@ describe("peekstack.ui.inline_preview", function() -- Should not error when disabled inline_preview.open(location) end) + + it("should cache namespace id", function() + local location = { + uri = vim.uri_from_fname(temp_file), + range = { start = { line = 0, character = 0 }, ["end"] = { line = 0, character = 10 } }, + } + + local original_get_namespaces = vim.api.nvim_get_namespaces + local get_namespaces_calls = 0 + local ok, err = pcall(function() + vim.api.nvim_get_namespaces = function(...) + get_namespaces_calls = get_namespaces_calls + 1 + return original_get_namespaces(...) + end + + inline_preview.open(location) + inline_preview.close() + inline_preview.open(location) + inline_preview.close() + end) + vim.api.nvim_get_namespaces = original_get_namespaces + if not ok then + error(err) + end + + assert.equals(1, get_namespaces_calls) + end) end) From d881ae0696d87d547df958cedd9a627a9da1991c Mon Sep 17 00:00:00 2001 From: Masaaki Hirotsu Date: Tue, 10 Feb 2026 22:50:09 +0900 Subject: [PATCH 3/3] refactor(ui): register stack view tab cleanup in setup --- lua/peekstack/init.lua | 2 ++ lua/peekstack/ui/stack_view.lua | 13 ++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lua/peekstack/init.lua b/lua/peekstack/init.lua index 5faaa15..58b419f 100644 --- a/lua/peekstack/init.lua +++ b/lua/peekstack/init.lua @@ -262,6 +262,7 @@ function M.setup(opts) local config = require("peekstack.config") local events = require("peekstack.core.events") local commands = require("peekstack.commands") + local stack_view = require("peekstack.ui.stack_view") local persist_auto = require("peekstack.persist.auto") providers = {} @@ -271,6 +272,7 @@ function M.setup(opts) set_hl() events.setup() commands.setup() + stack_view.setup() local cfg = config.get() diff --git a/lua/peekstack/ui/stack_view.lua b/lua/peekstack/ui/stack_view.lua index e88b1a4..42bff50 100644 --- a/lua/peekstack/ui/stack_view.lua +++ b/lua/peekstack/ui/stack_view.lua @@ -52,6 +52,8 @@ local TITLE_HL_TO_SV = { ---@type table local states = {} +---@type integer? +local tab_cleanup_group = nil ---@param s PeekstackStackViewState local function cleanup_state(s) @@ -79,10 +81,14 @@ local function cleanup_invalid_states() end end -do - local group = vim.api.nvim_create_augroup("PeekstackStackViewTabCleanup", { clear = true }) +---Setup stack view autocmds. +function M.setup() + if tab_cleanup_group then + return + end + tab_cleanup_group = vim.api.nvim_create_augroup("PeekstackStackViewTabCleanup", { clear = true }) vim.api.nvim_create_autocmd("TabClosed", { - group = group, + group = tab_cleanup_group, callback = function() cleanup_invalid_states() end, @@ -1202,6 +1208,7 @@ end ---Open the stack view function M.open() + M.setup() local s = get_state() if is_open(s) then vim.api.nvim_set_current_win(s.winid)