From 5a8fa5fc7a4b8d35a3ecd3852fffa7a7d6c0e565 Mon Sep 17 00:00:00 2001 From: korigamik Date: Fri, 19 Jun 2026 02:51:16 +0530 Subject: [PATCH 1/2] fix(nvim): preserve list cursor position on picker resume Resuming a picker wrote the saved query into the input buffer, whose on_lines callback scheduled on_input_change. That re-ran the search and reset the cursor to the first result, discarding the restored position. Suppress that single on_input_change during restore so the snapshot's items and cursor are kept verbatim, rendering once instead of relying on a second scheduled re-assert. --- lua/fff/picker_ui/picker_ui.lua | 7 +++++-- lua/fff/picker_ui/picker_ui_state.lua | 3 +++ lua/fff/picker_ui/search_manager.lua | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lua/fff/picker_ui/picker_ui.lua b/lua/fff/picker_ui/picker_ui.lua index d9c3d625..458aea9b 100644 --- a/lua/fff/picker_ui/picker_ui.lua +++ b/lua/fff/picker_ui/picker_ui.lua @@ -169,8 +169,11 @@ local function restore_from_state(state, source_label) M.state.suggestion_items = state.suggestion_items M.state.suggestion_source = state.suggestion_source - -- Set the query text in the input buffer + -- Writing the query below triggers on_lines -> on_input_change, which would + -- re-run the search and reset the cursor to the first result. Suppress it so + -- the restored items and cursor are kept as-is. if state.query and state.query ~= '' then + M.state.suppress_input_change = true vim.api.nvim_buf_set_lines(M.state.input_buf, 0, -1, false, { M.state.config.prompt .. state.query }) end @@ -186,7 +189,7 @@ local function restore_from_state(state, source_label) if M.state.active and M.state.input_win and vim.api.nvim_win_is_valid(M.state.input_win) then local prompt_len = #M.state.config.prompt vim.api.nvim_win_set_cursor(M.state.input_win, { 1, prompt_len + #state.query }) - vim.cmd('startinsert!') + vim.cmd('stopinsert') end end) diff --git a/lua/fff/picker_ui/picker_ui_state.lua b/lua/fff/picker_ui/picker_ui_state.lua index 5c4e0f5f..a1614581 100644 --- a/lua/fff/picker_ui/picker_ui_state.lua +++ b/lua/fff/picker_ui/picker_ui_state.lua @@ -30,6 +30,9 @@ M.state = { last_render_ctx = nil, location = nil, + -- Skip the next on_input_change (set during resume's buffer write) + suppress_input_change = false, + -- History cycling state history_offset = nil, next_search_force_combo_boost = false, diff --git a/lua/fff/picker_ui/search_manager.lua b/lua/fff/picker_ui/search_manager.lua index b6907c93..bcbc3ea6 100644 --- a/lua/fff/picker_ui/search_manager.lua +++ b/lua/fff/picker_ui/search_manager.lua @@ -219,6 +219,13 @@ end function M.on_input_change() if not P.state.active then return end + -- Restore writes the query into the input buffer, which triggers this via + -- on_lines. Skip the resulting search so the restored cursor/items survive. + if S.suppress_input_change then + S.suppress_input_change = false + return + end + local lines = vim.api.nvim_buf_get_lines(S.input_buf, 0, -1, false) local prompt_len = #S.config.prompt local query = '' From d9167cce5d00ef4e5be0e1dc825f522668266ff2 Mon Sep 17 00:00:00 2001 From: korigamik Date: Fri, 19 Jun 2026 10:55:27 +0530 Subject: [PATCH 2/2] fix(nvim): preserve list cursor position on picker resume Resuming a picker reset the selected entry to the first result. The saved cursor was restored, but writing the query into the input buffer triggers on_input_change, which re-runs the search and sets S.cursor = 1. Re-running the search on resume is intentional (results may have changed since close), so instead of suppressing it, thread the saved cursor through: restore_from_state stashes it in pending_restore_cursor and the re-search restores that position, clamped to the fresh result count. --- lua/fff/picker_ui/picker_ui.lua | 8 ++++---- lua/fff/picker_ui/picker_ui_state.lua | 6 ++++-- lua/fff/picker_ui/search_manager.lua | 16 ++++++++-------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/lua/fff/picker_ui/picker_ui.lua b/lua/fff/picker_ui/picker_ui.lua index 458aea9b..49ceeba0 100644 --- a/lua/fff/picker_ui/picker_ui.lua +++ b/lua/fff/picker_ui/picker_ui.lua @@ -169,11 +169,11 @@ local function restore_from_state(state, source_label) M.state.suggestion_items = state.suggestion_items M.state.suggestion_source = state.suggestion_source - -- Writing the query below triggers on_lines -> on_input_change, which would - -- re-run the search and reset the cursor to the first result. Suppress it so - -- the restored items and cursor are kept as-is. + -- Writing the query below triggers on_lines -> on_input_change, which re-runs + -- the search (results may have changed since close). Stash the saved cursor so + -- that re-search restores the position instead of resetting it to the top. if state.query and state.query ~= '' then - M.state.suppress_input_change = true + M.state.pending_restore_cursor = M.state.cursor vim.api.nvim_buf_set_lines(M.state.input_buf, 0, -1, false, { M.state.config.prompt .. state.query }) end diff --git a/lua/fff/picker_ui/picker_ui_state.lua b/lua/fff/picker_ui/picker_ui_state.lua index a1614581..dec4a47f 100644 --- a/lua/fff/picker_ui/picker_ui_state.lua +++ b/lua/fff/picker_ui/picker_ui_state.lua @@ -30,8 +30,9 @@ M.state = { last_render_ctx = nil, location = nil, - -- Skip the next on_input_change (set during resume's buffer write) - suppress_input_change = false, + -- Cursor index to restore after the next search completes (set on resume). + -- Lets the re-search run for fresh results while keeping the saved position. + pending_restore_cursor = nil, -- History cycling state history_offset = nil, @@ -120,6 +121,7 @@ function M.reset_state() M.state.item_to_lines = {} M.state.last_render_ctx = nil M.state.location = nil + M.state.pending_restore_cursor = nil M.reset_history_state() diff --git a/lua/fff/picker_ui/search_manager.lua b/lua/fff/picker_ui/search_manager.lua index bcbc3ea6..544bfdb3 100644 --- a/lua/fff/picker_ui/search_manager.lua +++ b/lua/fff/picker_ui/search_manager.lua @@ -101,7 +101,14 @@ function M.update_results_sync() if S.suggestion_items and #S.suggestion_items > 0 then S.filtered_items = S.suggestion_items end - S.cursor = 1 + -- On resume, restore the saved cursor onto the fresh results (clamped, since + -- the result set may have changed since close). Otherwise reset to the top. + if S.pending_restore_cursor then + S.cursor = math.max(1, math.min(S.pending_restore_cursor, #S.filtered_items)) + S.pending_restore_cursor = nil + else + S.cursor = 1 + end P.render_debounced() end @@ -219,13 +226,6 @@ end function M.on_input_change() if not P.state.active then return end - -- Restore writes the query into the input buffer, which triggers this via - -- on_lines. Skip the resulting search so the restored cursor/items survive. - if S.suppress_input_change then - S.suppress_input_change = false - return - end - local lines = vim.api.nvim_buf_get_lines(S.input_buf, 0, -1, false) local prompt_len = #S.config.prompt local query = ''