nvim-frameline/frameline.lua

225 lines
5.5 KiB
Lua

-- Very minimal framework to write a status/tabline
-- Based on a (literally) stripped-down express_line.
-- License: MIT
-- Copyright © 2022 TJ DeVries, Michele Guerini Rocco
local fl = {}
-- Misc utilities
fl.utils = {
-- Built-in segments
line_number = '%l',
column_number = '%c',
percent = "%p%%",
filename = '%t',
split = '%=',
-- Highlights a string
highlight = function(group, content)
return ('%%#%s#'):format(group) .. content .. '%*'
end,
-- Creates a subsection {highlight, items, separator, stop}
subsection = function(cfg)
local segments = {}
if cfg.user ~= nil then
table.insert(segments, ('%%%s*'):format(cfg.user))
end
if cfg.highlight ~= nil then
table.insert(segments, ('%%#%s#'):format(cfg.highlight))
end
table.insert(segments, function(win, buf)
local res, last_active = "", false
for i, item in pairs(cfg.items) do
local part = type(item) == 'string' and item or item(win, buf)
if part and last_active then
res = res .. (cfg.separator or ' ')
end
if part then
res = res .. part
last_active = true
end
end
return res
end)
table.insert(segments, cfg.stop or ' ')
if highlight ~= nil or user ~= nil then
table.insert(segments, '%*')
end
return segments
end
}
-- Neovim Buffer class
local Buffer = {}
local buf_props = {
name = function(buf) return vim.api.nvim_buf_get_name(buf.bufnr) end,
is_active = function(buf) return buf.bufnr == vim.api.nvim_get_current_buf() end,
}
function Buffer:new(bufnr)
if bufnr == 0 then
bufnr = vim.api.nvim_buf_get_number(0)
end
return setmetatable({ bufnr = bufnr, }, { __index=function(t, k)
if Buffer[k] ~= nil then
t[k] = Buffer[k]
elseif buf_props[k] ~= nil then
t[k] = buf_props[k](t)
else
t[k] = vim.api.nvim_buf_get_option(t.bufnr, k)
end
return t[k]
end
})
end
-- Neovim Window class
local Window = {}
local win_props = {
width = function(win) return vim.api.nvim_win_get_width(win.winid) end,
height = function(win) return vim.api.nvim_win_get_height(win.winid) end,
is_active = function(win) return win.winid == vim.api.nvim_get_current_win() end
}
function Window:new(winid)
return setmetatable({winid=winid}, { __index=function(t, k)
local result
if Window[k] ~= nil then
result = Window[k]
elseif win_props[k] ~= nil then
result = win_props[k](t)
end
return result
end
})
end
-- Evaluates the statusline segments
local processor = function(items, window, buffer)
local winid = window.winid
return function()
if not vim.api.nvim_win_is_valid(winid) then return end
buffer = Buffer:new(buffer.bufnr)
local waiting = {}
local statusline = {}
local effects = {}
for k, v in ipairs(items) do
local ok, result, effect
if type(v) == 'string' then
ok, result = true, v
elseif type(v) == 'function' then
ok, result, effect = pcall(v, window, buffer)
else
ok = false
end
if not ok then
statusline[k] = ''
else
if type(result) == 'thread' then
table.insert(waiting, {index=k, thread=result, effect=effect})
else
statusline[k], effects[k] = result, effect
end
end
end
local remaining = table.getn(waiting)
local completed = 0
local start = os.time()
while start + 2 > os.time() do
if remaining == completed then
break
end
for i = 1, remaining do
local wait_val = waiting[i]
if wait_val ~= nil then
local index, thread = wait_val.index, wait_val.thread
local _, res = coroutine.resume(thread, window, buffer)
if coroutine.status(thread) == 'dead' then
statusline[index] = res
-- Remove
completed = completed + 1
waiting[i] = nil
end
end
end
end
-- Filter nils and concat
local final = {}
for k, v in ipairs(statusline) do
if effects[k] then v = effects[k](v) end
if v then table.insert(final, v) end
end
return table.concat(final, "")
end
end
-- Creates a table of all window/buffers
local get_new_windows_table = function()
return setmetatable({}, {__index = function(self, winid)
local val = setmetatable({}, {
__index = function(win_table, bufnr)
if not fl.generator then return function() return '' end end
local window = Window:new(winid)
local buffer = Buffer:new(bufnr)
local items = vim.tbl_flatten(fl.generator(window, buffer))
local p = processor(items, window, buffer)
rawset(win_table, bufnr, p)
return p
end,
})
rawset(self, winid, val)
return val
end,
})
end
-- Draws the status/tabline
function draw_statusline(winid)
local bufnr = vim.api.nvim_win_get_buf(winid)
return fl._window_statuslines[winid][bufnr]()
end
function draw_tabline()
return fl._tabline()
end
-- Set up a statusline per window
fl.setup_statusline = function(generator)
fl._window_statuslines = get_new_windows_table()
fl.generator = generator
vim.api.nvim_create_autocmd(
{'BufWinEnter','WinEnter'},
{pattern='*', callback=function()
vim.wo.statusline = "%!luaeval('draw_statusline("..vim.api.nvim_get_current_win()..")')"
end
})
end
-- Set up a global tabline
fl.setup_tabline = function(generator)
fl._tabline = generator
vim.cmd('set tabline=%!v:lua.draw_tabline()')
end
return fl