snacks.nvim/lua/snacks/picker/source/recent.lua

122 lines
3.5 KiB
Lua

local M = {}
local uv = vim.uv or vim.loop
---@param filter snacks.picker.Filter
---@param extra? string[]
local function oldfiles(filter, extra)
local done = {} ---@type table<string, boolean>
local files = {} ---@type string[]
vim.list_extend(files, extra or {})
vim.list_extend(files, vim.v.oldfiles)
local i = 0
return function()
for f = i + 1, #files do
i = f
local file = files[f]
file = svim.fs.normalize(file, { _fast = true, expand_env = false })
local want = not done[file] and filter:match({ file = file, text = "" })
done[file] = true
if want and uv.fs_stat(file) then
return file
end
end
end
end
--- Get the most recent files, optionally filtered by the
--- current working directory or a custom directory.
---@param opts snacks.picker.recent.Config
---@type snacks.picker.finder
function M.files(opts, ctx)
local current_file = svim.fs.normalize(vim.api.nvim_buf_get_name(0), { _fast = true })
---@type number[]
local bufs = vim.tbl_filter(function(b)
return vim.api.nvim_buf_get_name(b) ~= "" and vim.bo[b].buftype == "" and vim.bo[b].buflisted
end, vim.api.nvim_list_bufs())
table.sort(bufs, function(a, b)
return vim.fn.getbufinfo(a)[1].lastused > vim.fn.getbufinfo(b)[1].lastused
end)
local extra = vim.tbl_map(function(b)
return vim.api.nvim_buf_get_name(b)
end, bufs)
---@async
---@param cb async fun(item: snacks.picker.finder.Item)
return function(cb)
for file in oldfiles(ctx.filter, extra) do
if file ~= current_file then
cb({ file = file, text = file, recent = true })
end
end
end
end
M.recent = M.files
--- Get the most recent projects based on git roots of recent files.
--- The default action will change the directory to the project root,
--- try to restore the session and open the picker if the session is not restored.
--- You can customize the behavior by providing a custom action.
---@param opts snacks.picker.projects.Config
---@type snacks.picker.finder
function M.projects(opts, ctx)
local args = {
"-H",
"-t",
"f",
"-t",
"s",
"-t",
"d",
"--max-depth",
"2",
"--follow",
"--absolute-path",
}
vim.list_extend(args, { "-g", "{" .. table.concat(opts.patterns or {}, ",") .. "}" })
local dev = type(opts.dev) == "string" and { opts.dev } or opts.dev or {}
---@cast dev string[]
vim.list_extend(args, vim.tbl_map(svim.fs.normalize, dev))
local fd = require("snacks.picker.source.files").get_fd()
if not fd then
Snacks.notify.warn("`fd` or `fdfind` is required for projects")
end
local proc = fd and require("snacks.picker.source.proc").proc({ cmd = fd, args = args, notify = false }, ctx)
---@async
---@param cb async fun(item: snacks.picker.finder.Item)
return function(cb)
local dirs = {} ---@type table<string, boolean>
---@async
local function add(dir)
if dir and not dirs[dir] then
dirs[dir] = true
cb({ file = dir, text = dir, dir = true })
end
end
if opts.recent then
for file in oldfiles(ctx.filter) do
local dir = Snacks.git.get_root(file)
add(dir)
end
end
vim.tbl_map(add, opts.projects or {})
if not proc then
return
end
---@async
proc(function(item)
local path = item.text
path = path:sub(-1) == "/" and path:sub(1, -2) or path
path = vim.fs.dirname(path)
if ctx.filter:match({ file = path, text = path }) then
add(path)
end
end)
end
end
return M