local api = vim.api
local state = {
buf = nil,
win = nil,
lastused = nil,
parent = nil,
map = {},
buffers = {},
}
local function buf_create()
local opts = {
bufhidden = "wipe",
buflisted = false,
buftype = "nofile",
filetype = "buffy",
modifiable = false,
swapfile = false,
undolevels = -1,
}
local buf = api.nvim_create_buf(false, true)
for key, value in pairs(opts) do
api.nvim_set_option_value(key, value, { buf=buf })
end
return buf
end
local function win_create(buf, buffers)
local width = 40
local padding = (
3
+ 6
+ 2
)
for _, buffer in ipairs(buffers) do
local test = #buffer.path + padding
if test > width then width = test end
end
local height = #buffers
if height == 0 then height = 1 end
local win = api.nvim_open_win(buf, true, {
style = "minimal",
relative = "editor",
row = math.floor(vim.o.lines / 3),
col = math.floor(vim.o.columns / 2) - math.floor(width / 2),
anchor = "NW",
width = width,
height = height,
border = "single",
})
api.nvim_set_option_value("cursorline", true, { win=win })
api.nvim_set_option_value("cursorlineopt", "both", { win=win })
return win
end
local function filetype(path)
local stat = vim.uv.fs_stat(path)
return stat and stat.type
end
local function get_buffers()
state.buffers = {}
state.map = {}
local buffers = vim.iter(vim.fn.getbufinfo({ buflisted=1 }))
:filter(function(buffer)
return (buffer.name ~= "") and (buffer.listed == 1)
end)
:map(function(buffer)
return {
alternate = vim.fn.bufnr('#') == buffer.bufnr,
filetype = filetype(buffer.name),
lastused = buffer.lastused,
number = buffer.bufnr,
path = buffer.name:gsub(assert(os.getenv("HOME")), "~", 1),
visible = #buffer.windows > 0,
windows = buffer.windows,
}
end):totable()
table.sort(buffers, function(m, n)
return m.lastused > n.lastused
end)
return buffers
end
local function sign_column()
local ns = api.nvim_create_namespace("buffy")
for row, buffer in pairs(state.map) do
local sign
if buffer.visible then
sign = "●"
elseif buffer.alternate then
sign = "○"
else
sign = ""
end
vim.api.nvim_buf_set_extmark(state.buf, ns, row-1, 4, {
strict = false,
sign_text = sign
})
end
end
local function format_lines(buffers)
state.map = {}
local lines = {}
for _, buffer in ipairs(buffers) do
table.insert(lines, (" %03d %s%s"):format(
buffer.number,
buffer.path,
(buffer.filetype == "directory") and "/" or ""
))
state.map[#lines] = buffer
end
return lines
end
local function redraw(lines)
api.nvim_set_option_value("modifiable", true, { buf=state.buf })
api.nvim_buf_set_lines(state.buf, 0, -1, false, lines)
api.nvim_win_set_height(state.win, #lines)
api.nvim_set_option_value("modifiable", false, { buf=state.buf })
sign_column()
end
local function reload()
local buffers = get_buffers()
state.buffers = buffers
redraw(format_lines(buffers))
end
local function filter()
vim.ui.input({
prompt = "filter> "
}, function(input)
if not input then return end
local matches = vim.fn.matchfuzzy(state.buffers, input, {
text_cb = function(buffer) return buffer.path end
})
redraw(format_lines(matches))
end)
end
local function open()
if state.win and api.nvim_win_is_valid(state.win) then
return
end
state.parent = api.nvim_get_current_win()
local buffers = get_buffers()
local buf = buf_create()
local win = win_create(buf, buffers)
state.buf = buf
state.win = win
state.buffers = buffers
redraw(format_lines(buffers))
end
local function close()
if state.win and api.nvim_win_is_valid(state.win) then
api.nvim_win_close(state.win, true)
state.win = nil
end
end
local function select()
local cursor = api.nvim_win_get_cursor(state.win)
local buffer = state.map[cursor[1]]
api.nvim_win_set_buf(state.parent, buffer.number)
close()
end
local function delete()
local cursor = api.nvim_win_get_cursor(state.win)
local row = cursor[1]
local buffer = state.map[row]
if not buffer then return end
if api.nvim_get_option_value("modified", { buf=buffer.number }) then
vim.notify("No write since last change", vim.log.levels.ERROR)
return
end
local safe_buffer = vim.iter(api.nvim_list_bufs())
:filter(function(b) return b ~= buffer.number end):next()
for _, winnr in pairs(buffer.windows) do
api.nvim_win_set_buf(winnr, safe_buffer)
end
if pcall(api.nvim_buf_delete, buffer.number, { force = false }) then
table.remove(state.map, row)
table.remove(state.buffers, buffer.number)
local buffers = get_buffers()
state.buffers = buffers
redraw(format_lines(buffers))
end
end
return {
close = close,
delete = delete,
filter = filter,
open = open,
reload = reload,
select = select,
}