mirror of
https://github.com/folke/snacks.nvim
synced 2025-08-05 11:18:26 +00:00
feat(picker): persistent history. Closes #528
This commit is contained in:
parent
8d9677fc47
commit
ea665ebad1
3 changed files with 103 additions and 15 deletions
|
@ -23,8 +23,7 @@ Async.BUDGET = 10
|
|||
---@field start_time number
|
||||
---@field title string
|
||||
---@field closed? boolean
|
||||
---@field hist_idx number
|
||||
---@field hist_cursor number
|
||||
---@field history snacks.picker.History
|
||||
---@field visual? snacks.picker.Visual
|
||||
local M = {}
|
||||
M.__index = M
|
||||
|
@ -46,8 +45,7 @@ M._active = {}
|
|||
---@type snacks.picker.Last?
|
||||
M.last = nil
|
||||
|
||||
---@type {pattern: string, search: string, live?: boolean}[]
|
||||
M.history = {}
|
||||
---@alias snacks.picker.history.Record {pattern: string, search: string, live?: boolean}
|
||||
|
||||
---@hide
|
||||
---@param opts? snacks.picker.Config
|
||||
|
@ -58,6 +56,16 @@ function M.new(opts)
|
|||
return M.resume()
|
||||
end
|
||||
|
||||
self.history = require("snacks.picker.util.history").new("picker", {
|
||||
---@param hist snacks.picker.history.Record
|
||||
filter = function(hist)
|
||||
if hist.pattern == "" and hist.search == "" then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end,
|
||||
})
|
||||
|
||||
local picker_count = vim.tbl_count(M._pickers)
|
||||
if picker_count > 0 then
|
||||
-- clear items from previous pickers for garbage collection
|
||||
|
@ -96,8 +104,6 @@ function M.new(opts)
|
|||
self.opts.win.input.actions = actions
|
||||
self.opts.win.list.actions = actions
|
||||
self.opts.win.preview.actions = actions
|
||||
self.hist_idx = #M.history + 1
|
||||
self.hist_cursor = self.hist_idx
|
||||
|
||||
local sort = self.opts.sort or require("snacks.picker.sort").default()
|
||||
sort = type(sort) == "table" and require("snacks.picker.sort").default(sort) or sort
|
||||
|
@ -451,6 +457,7 @@ function M:close()
|
|||
if self.closed then
|
||||
return
|
||||
end
|
||||
self:hist_record(true)
|
||||
self.closed = true
|
||||
M.last.selected = self:selected({ fallback = false })
|
||||
M.last.cursor = self.list.cursor
|
||||
|
@ -572,23 +579,31 @@ function M:find(opts)
|
|||
end
|
||||
|
||||
--- Add current filter to history
|
||||
---@param force? boolean
|
||||
---@private
|
||||
function M:hist_record()
|
||||
M.history[self.hist_idx] = {
|
||||
function M:hist_record(force)
|
||||
if not force and not self.history:is_current() then
|
||||
return
|
||||
end
|
||||
self.history:record({
|
||||
pattern = self.input.filter.pattern,
|
||||
search = self.input.filter.search,
|
||||
live = self.opts.live,
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
--- Move the history cursor
|
||||
---@param forward? boolean
|
||||
function M:hist(forward)
|
||||
self:hist_record()
|
||||
self.hist_cursor = self.hist_cursor + (forward and 1 or -1)
|
||||
self.hist_cursor = math.min(math.max(self.hist_cursor, 1), #M.history)
|
||||
self.opts.live = M.history[self.hist_cursor].live
|
||||
self.input:set(M.history[self.hist_cursor].pattern, M.history[self.hist_cursor].search)
|
||||
if forward then
|
||||
self.history:next()
|
||||
else
|
||||
self.history:prev()
|
||||
end
|
||||
local hist = self.history:get() --[[@as snacks.picker.history.Record]]
|
||||
self.opts.live = hist.live
|
||||
self.input:set(hist.pattern, hist.search)
|
||||
end
|
||||
|
||||
--- Run the matcher with the current pattern.
|
||||
|
|
73
lua/snacks/picker/util/history.lua
Normal file
73
lua/snacks/picker/util/history.lua
Normal file
|
@ -0,0 +1,73 @@
|
|||
---@class snacks.picker.History
|
||||
---@field path string
|
||||
---@field kv snacks.picker.KeyValue
|
||||
---@field idx number
|
||||
---@field cursor number
|
||||
local M = {}
|
||||
M.__index = M
|
||||
|
||||
---@type table<string, snacks.picker.KeyValue>
|
||||
M.stores = {}
|
||||
|
||||
-- Save the history on exit
|
||||
vim.api.nvim_create_autocmd("ExitPre", {
|
||||
group = vim.api.nvim_create_augroup("snacks_history", { clear = true }),
|
||||
callback = function()
|
||||
for n, kv in pairs(M.stores) do
|
||||
kv:close()
|
||||
M.stores[n] = nil
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
---@param name string
|
||||
---@param opts? {filter?: fun(value: string): boolean}
|
||||
function M.new(name, opts)
|
||||
opts = opts or {}
|
||||
local self = setmetatable({}, M)
|
||||
self.path = vim.fn.stdpath("data") .. "/snacks/" .. name .. ".history"
|
||||
if not M.stores[name] then
|
||||
M.stores[name] = require("snacks.picker.util.kv").new(self.path, {
|
||||
max_size = 1000,
|
||||
---@param a snacks.picker.KeyValue.entry
|
||||
---@param b snacks.picker.KeyValue.entry
|
||||
cmp = function(a, b)
|
||||
return a.key > b.key
|
||||
end,
|
||||
})
|
||||
end
|
||||
self.kv = M.stores[name]
|
||||
-- re-index the data
|
||||
self.kv.data = vim.tbl_values(self.kv.data)
|
||||
if opts.filter then
|
||||
self.kv.data = vim.tbl_filter(opts.filter, self.kv.data)
|
||||
end
|
||||
self.idx = #self.kv.data + 1
|
||||
self.cursor = self.idx
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function M:is_current()
|
||||
return self.cursor == self.idx
|
||||
end
|
||||
|
||||
function M:record(value)
|
||||
self.kv:set(self.idx, value)
|
||||
end
|
||||
|
||||
function M:next()
|
||||
self.cursor = math.min(self.cursor + 1, self.idx)
|
||||
return self:get()
|
||||
end
|
||||
|
||||
function M:prev()
|
||||
self.cursor = math.max(self.cursor - 1, 1)
|
||||
return self:get()
|
||||
end
|
||||
|
||||
function M:get()
|
||||
return self.kv:get(self.cursor)
|
||||
end
|
||||
|
||||
return M
|
|
@ -11,7 +11,7 @@ local uv = vim.uv or vim.loop
|
|||
---@alias snacks.picker.KeyValue.entry {key:string, value:number}
|
||||
|
||||
---@param path string
|
||||
---@param opts? {max_size?: number, cmp?: fun(a,b)}
|
||||
---@param opts? {max_size?: number, cmp?: fun(a:snacks.picker.KeyValue.entry, b:snacks.picker.KeyValue.entry): boolean}
|
||||
function M.new(path, opts)
|
||||
local self = setmetatable({}, M)
|
||||
self.data = {}
|
||||
|
@ -39,7 +39,7 @@ function M:set(key, value)
|
|||
end
|
||||
|
||||
function M:get(key)
|
||||
return self.data[key] or 0
|
||||
return self.data[key]
|
||||
end
|
||||
|
||||
function M:close()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue