feat(fzf): match telescope recent files

This commit is contained in:
2026-06-10 17:32:36 +02:00
parent 0810939e24
commit 72dc07ba24
3 changed files with 232 additions and 52 deletions
+97
View File
@@ -0,0 +1,97 @@
local M = {}
---@param path string
---@return boolean
local function is_file(path)
local stat = vim.uv.fs_stat(path)
return stat ~= nil and stat.type == "file"
end
---@param line string
---@return integer?
local function buffer_number(line)
if line:match("line 0$") then
return nil
end
local bufnr = tonumber(line:match("^%s*(%d+)"))
---@cast bufnr integer?
return bufnr
end
---@return integer[]
local function session_buffers()
local buffers = {}
local output = vim.fn.execute(":buffers! t")
for _, line in ipairs(vim.split(output, "\n", { trimempty = true })) do
local bufnr = buffer_number(line)
if bufnr then
table.insert(buffers, bufnr)
end
end
return buffers
end
---@param results string[]
---@param seen table<string, boolean>
---@param file string
local function add(results, seen, file)
if file == "" or seen[file] then
return
end
seen[file] = true
table.insert(results, file)
end
---@param file string
---@param cwd string
---@return boolean
local function in_cwd(file, cwd)
if cwd:sub(-1) ~= "/" then
cwd = cwd .. "/"
end
return file:sub(1, #cwd) == cwd
end
---@class ow.RecentFiles.ListOpts
---@field cwd? string
---@field cwd_only? boolean
---@field include_current_session? boolean
---@param opts ow.RecentFiles.ListOpts?
---@return string[]
function M.list(opts)
opts = opts or {}
local current_buffer = vim.api.nvim_get_current_buf()
local current_file = vim.api.nvim_buf_get_name(current_buffer)
local results = {}
local seen = {}
local cwd = opts.cwd_only and assert(vim.uv.cwd()) or opts.cwd
local function should_stat(file)
return cwd == nil or in_cwd(file, cwd)
end
if opts.include_current_session ~= false then
for _, bufnr in ipairs(session_buffers()) do
local file = vim.api.nvim_buf_get_name(bufnr)
if
bufnr ~= current_buffer
and should_stat(file)
and is_file(file)
then
add(results, seen, file)
end
end
end
for _, file in ipairs(vim.v.oldfiles) do
if file ~= current_file and should_stat(file) and is_file(file) then
add(results, seen, file)
end
end
return results
end
return M
+3 -52
View File
@@ -1,4 +1,5 @@
local fzf = require("fzf-lua")
local recent = require("recent_files")
local wide = {
width = 160,
@@ -8,61 +9,11 @@ local wide = {
},
}
local function valid_file(file)
local stat = vim.uv.fs_stat(file)
return stat ~= nil
and not fzf.utils.path_is_directory(file, stat)
-- FIFO blocks `fs_open` indefinitely (#908)
and not fzf.utils.file_is_fifo(file, stat)
and fzf.utils.file_is_readable(file)
end
local function recent_files()
local cwd = vim.fn.getcwd()
local cwd = assert(vim.uv.cwd())
local current = vim.api.nvim_buf_get_name(0)
local has_current = current ~= ""
local oldfiles_count = #vim.v.oldfiles
local scores = {}
local function under_cwd(path)
local rel = vim.fs.relpath(cwd, path)
return rel ~= nil and not vim.startswith(rel, "..")
end
local function is_uri(path)
return path:match("^%w[%w+%-%.]*://") ~= nil
end
for i, f in ipairs(vim.v.oldfiles) do
if f ~= current and not is_uri(f) and under_cwd(f) then
scores[f] = oldfiles_count - i + 1
end
end
for _, b in ipairs(vim.api.nvim_list_bufs()) do
if vim.bo[b].buftype == "" then
local name = vim.api.nvim_buf_get_name(b)
if
name ~= ""
and not is_uri(name)
and name ~= current
and under_cwd(name)
and (scores[name] or valid_file(name))
then
local info = vim.fn.getbufinfo(b)[1]
---@cast info -nil
scores[name] = math.max(info.lastused, scores[name] or 0)
end
end
end
local list = {}
for f in pairs(scores) do
table.insert(list, f)
end
table.sort(list, function(a, b)
return scores[a] > scores[b]
end)
local list = recent.list({ cwd_only = true })
for i, f in ipairs(list) do
list[i] = vim.fs.relpath(cwd, f) or f
end
+132
View File
@@ -0,0 +1,132 @@
local recent = require("recent_files")
local t = require("test")
---@return string
local function temp_dir()
local dir = vim.fn.tempname()
vim.fn.mkdir(dir, "p")
t.defer(function()
vim.fn.delete(dir, "rf")
end)
return dir
end
---@param path string
local function edit(path)
vim.cmd.edit(vim.fn.fnameescape(path))
end
---@param dir string
---@param name string
---@return string
local function file(dir, name)
t.write(dir, name, name)
return vim.fs.joinpath(dir, name)
end
---@param fn fun()
local function with_clean_state(fn)
local cwd = vim.fn.getcwd()
local oldfiles = vim.deepcopy(vim.v.oldfiles)
local before = {}
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
before[bufnr] = vim.api.nvim_buf_get_name(bufnr)
end
local ok, err = pcall(fn)
t.defer(function()
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
local name = vim.api.nvim_buf_get_name(bufnr)
if before[bufnr] ~= name and vim.api.nvim_buf_is_valid(bufnr) then
pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
end
end
vim.v.oldfiles = oldfiles
pcall(vim.cmd.cd, vim.fn.fnameescape(cwd))
end)
if not ok then
error(err, 0)
end
end
t.test("list orders session buffers before oldfiles", function()
with_clean_state(function()
local dir = temp_dir()
vim.cmd.cd(vim.fn.fnameescape(dir))
local first = file(dir, "first.txt")
local second = file(dir, "second.txt")
local current = file(dir, "current.txt")
local old = file(dir, "old.txt")
local missing = vim.fs.joinpath(dir, "missing.txt")
edit(first)
edit(second)
edit(current)
vim.v.oldfiles = { old, second, current, missing }
t.eq(recent.list({ cwd_only = true }), { first, second, old })
end)
end)
t.test("list can omit current session buffers", function()
with_clean_state(function()
local dir = temp_dir()
vim.cmd.cd(vim.fn.fnameescape(dir))
local session_file = file(dir, "session.txt")
local current = file(dir, "current.txt")
local old = file(dir, "old.txt")
edit(session_file)
edit(current)
vim.v.oldfiles = { old }
t.eq(
recent.list({
cwd_only = true,
include_current_session = false,
}),
{ old }
)
end)
end)
t.test("list excludes directory session buffers", function()
with_clean_state(function()
local dir = temp_dir()
vim.cmd.cd(vim.fn.fnameescape(dir))
local current = file(dir, "current.txt")
local old = file(dir, "old.txt")
edit(dir)
edit(current)
vim.v.oldfiles = { old }
t.eq(recent.list({ cwd_only = true }), { old })
end)
end)
t.test("list filters cwd entries", function()
with_clean_state(function()
local dir = temp_dir()
local other_dir = temp_dir()
vim.cmd.cd(vim.fn.fnameescape(dir))
local inside_session = file(dir, "inside-session.txt")
local inside_old = file(dir, "inside-old.txt")
local outside_session = file(other_dir, "outside-session.txt")
local outside_old = file(other_dir, "outside-old.txt")
local current = file(dir, "current.txt")
edit(inside_session)
edit(outside_session)
edit(current)
vim.v.oldfiles = { inside_old, outside_old }
t.eq(recent.list({ cwd_only = true }), { inside_session, inside_old })
end)
end)