-- 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