馃 a tiny, customizable statusline for neovim
at main 194 lines 5.3 kB view raw
1local log = require("lylla.log") 2local utils = require("lylla.utils") 3 4---@class lylla.proto 5---@field wins table<integer, table> 6---@field win integer 7---@field modules any[] 8---@field winbar any[] 9---@field timer uv.uv_timer_t 10---@field refreshau integer 11local statusline = {} 12 13statusline.wins = {} 14 15---@class lylla.proto 16---@field new fun(self, win): lylla.proto 17function statusline:new(win) 18 if statusline.wins[win] then 19 statusline.wins[win]:close() 20 end 21 local stl = setmetatable({ 22 win = win, 23 modules = vim.deepcopy(require("lylla.config").get().modules, true), 24 winbar = vim.deepcopy(require("lylla.config").get().winbar, true), 25 }, { __index = statusline }) 26 statusline.wins[win] = stl 27 return stl 28end 29 30---@class lylla.proto 31---@field init fun(self) 32function statusline:init() 33 local err, err_kind 34 ---@diagnostic disable-next-line: assign-type-mismatch 35 self.timer, err, err_kind = vim.uv.new_timer() 36 if not self.timer or err then 37 vim.api.nvim_echo({ { err_kind }, { "\n\t" }, { err } }, true, { err = true }) 38 return 39 end 40 41 local refresh = require("lylla.config").get().refresh_rate 42 self.timer:start(0, refresh, function() 43 self:refresh() 44 end) 45 46 self.refreshau = vim.api.nvim_create_augroup(("lylla:refresh[%d]"):format(self.win), { clear = true }) 47 48 local events = self:getevents() 49 50 for i = 1, #events do 51 local event = events[i] 52 local eventname, eventpattern = unpack(vim.split(event, " "), 1, 2) 53 vim.api.nvim_create_autocmd(eventname, { 54 group = self.refreshau, 55 pattern = eventpattern, 56 callback = function(ev) 57 self:refresh(ev) 58 end, 59 }) 60 end 61end 62 63---@class lylla.proto 64---@field close fun(self) 65function statusline:close() 66 self.timer:stop() 67 self.timer:close() 68 pcall(vim.api.nvim_del_augroup_by_id, self.refreshau) 69 statusline.wins[self.win] = nil 70end 71 72---@class lylla.proto 73---@field getevents fun(self): string[] 74function statusline:getevents() 75 local t = vim.iter(ipairs(self.modules)):fold({}, function(acc, _, module) 76 if type(module) == "table" and module.fn and type(module.fn) == "function" then 77 if module.opts and module.opts.events then 78 return vim.iter(module.opts.events):fold(acc, function(a, event) 79 a[event] = true 80 return a 81 end) 82 end 83 end 84 return acc 85 end) 86 87 return vim.tbl_keys(t) 88end 89 90local function refreshcomponent(self, fn, ev) 91 do 92 local ok, result = pcall(fn, self, ev) 93 if not ok then 94 log.error("[lylla] error occured on refresh:\n\t" .. result) 95 end 96 end 97end 98 99---@class lylla.proto 100---@field refresh fun(self, ev?: vim.api.keyset.create_autocmd.callback_args) 101function statusline:refresh(ev) 102 vim.schedule(function() 103 if not vim.api.nvim_win_is_valid(self.win) then 104 return 105 end 106 107 refreshcomponent(self, statusline.set, ev) 108 refreshcomponent(self, statusline.setwinbar, ev) 109 end) 110end 111 112---@class lylla.proto 113---@field fold fun(self, ev?: vim.api.keyset.create_autocmd.callback_args, modules: any[]): string 114function statusline:fold(ev, modules) 115 if type(modules) ~= "table" or modules == nil then 116 return "" 117 end 118 119 local lst = vim 120 .iter(ipairs(modules)) 121 :map(function(_, module) 122 if type(module) == "table" and module.fn and type(module.fn) == "function" then 123 if module.opts and module.opts.events then 124 -- refresh from timer 125 if not ev and module.prev then 126 return module.prev 127 end 128 -- refresh from non-match event 129 if ev and not vim.tbl_contains(module.opts.events, ev.event) and module.prev then 130 return module.prev 131 end 132 end 133 do 134 local ok, result = pcall(module.fn) 135 if not ok then 136 error(result) 137 end 138 module.prev = result 139 end 140 return module.prev 141 end 142 if type(module) == "function" then 143 local ok, result = pcall(module) 144 if not ok then 145 error(result) 146 end 147 return result 148 end 149 return module 150 end) 151 :totable() 152 return utils.fold(lst) 153end 154 155---@class lylla.proto 156---@field get fun(self, ev?: vim.api.keyset.create_autocmd.callback_args) 157function statusline:get(ev) 158 return self:fold(ev, self.modules) 159end 160 161---@class lylla.proto 162---@field getwinbar fun(self, ev?: vim.api.keyset.create_autocmd.callback_args) 163function statusline:getwinbar(ev) 164 return self:fold(ev, self.winbar) 165end 166 167---@class lylla.proto 168---@field setwinbar fun(self, ev?: vim.api.keyset.create_autocmd.callback_args) 169function statusline:setwinbar(ev) 170 local buf = vim.api.nvim_win_get_buf(self.win) 171 if vim.bo[buf].buftype ~= "" then 172 return 173 end 174 175 local ok, result = pcall(vim.api.nvim_win_call, self.win, function() 176 return self:getwinbar(ev) 177 end) 178 assert(ok, string.format("error occured while trying to evaluate winbar:\n\t%s", result)) 179 180 vim.wo[self.win].winbar = result 181end 182 183---@class lylla.proto 184---@field set fun(self, ev?: vim.api.keyset.create_autocmd.callback_args) 185function statusline:set(ev) 186 local ok, result = pcall(vim.api.nvim_win_call, self.win, function() 187 return self:get(ev) 188 end) 189 assert(ok, string.format("error occured while trying to evaluate statusline:\n\t%s", result)) 190 191 vim.wo[self.win].statusline = result 192end 193 194return statusline