馃 a tiny, customizable statusline for neovim
1local utils = require("lylla.utils")
2
3---@class lylla.proto
4---@field wins table<integer, table>
5---@field win integer
6---@field modules any[]
7---@field winbar any[]
8---@field timer uv.uv_timer_t
9---@field refreshau integer
10local statusline = {}
11
12statusline.wins = {}
13
14---@class lylla.proto
15---@field new fun(self, win): lylla.proto
16function statusline:new(win)
17 if statusline.wins[win] then
18 statusline.wins[win]:close()
19 end
20 local stl = setmetatable({
21 win = win,
22 modules = vim.deepcopy(require("lylla.config").get().modules, true),
23 winbar = vim.deepcopy(require("lylla.config").get().winbar, true),
24 }, { __index = statusline })
25 statusline.wins[win] = stl
26 return stl
27end
28
29---@class lylla.proto
30---@field init fun(self)
31function statusline:init()
32 local err, err_kind
33 ---@diagnostic disable-next-line: assign-type-mismatch
34 self.timer, err, err_kind = vim.uv.new_timer()
35 if not self.timer or err then
36 vim.api.nvim_echo({ { err_kind }, { "\n\t" }, { err } }, true, { err = true })
37 return
38 end
39
40 local refresh = require("lylla.config").get().refresh_rate
41 self.timer:start(0, refresh, function()
42 self:refresh()
43 end)
44
45 self.refreshau = vim.api.nvim_create_autocmd(require("lylla.config").get().events, {
46 group = vim.api.nvim_create_augroup("lylla:refresh", { clear = false }),
47 callback = function(ev)
48 self:refresh(ev)
49 end,
50 })
51end
52
53---@class lylla.proto
54---@field close fun(self)
55function statusline:close()
56 self.timer:stop()
57 self.timer:close()
58 vim.api.nvim_del_autocmd(self.refreshau)
59 statusline.wins[self.win] = nil
60end
61
62---@class lylla.proto
63---@field refresh fun(self, ev?: vim.api.keyset.create_autocmd.callback_args)
64function statusline:refresh(ev)
65 vim.schedule(function()
66 if not vim.api.nvim_win_is_valid(self.win) then
67 return
68 end
69
70 self:set(ev)
71 self:setwinbar(ev)
72 end)
73end
74
75---@class lylla.proto
76---@field fold fun(self, ev?: vim.api.keyset.create_autocmd.callback_args, modules: any[]): string
77function statusline:fold(ev, modules)
78 local lst = vim
79 .iter(ipairs(modules))
80 :map(function(_, module)
81 if type(module) == "table" and module._type == "component" then
82 if module.opts and module.opts.events then
83 -- refresh from timer
84 if not ev and module.prev then
85 return module.prev
86 end
87 -- refresh from non-match event
88 if ev and not vim.tbl_contains(module.opts.events, ev.event) and module.prev then
89 return module.prev
90 end
91 end
92 module.prev = module.fn()
93 return module.prev
94 end
95 if type(module) == "function" then
96 module = module()
97 end
98 return module
99 end)
100 :totable()
101 lst = utils.flatten(lst, 1)
102 return vim.iter(lst):fold("", function(str, module)
103 if type(module) ~= "table" or #module == 0 then
104 return str
105 end
106 local text = module[1]
107 if #module > 1 then
108 return str .. "%#" .. module[2] .. "#" .. text .. "%*"
109 end
110 return str .. "%*" .. text
111 end)
112end
113
114---@class lylla.proto
115---@field get fun(self, ev?: vim.api.keyset.create_autocmd.callback_args)
116function statusline:get(ev)
117 return self:fold(ev, self.modules)
118end
119
120---@class lylla.proto
121---@field getwinbar fun(self, ev?: vim.api.keyset.create_autocmd.callback_args)
122function statusline:getwinbar(ev)
123 return self:fold(ev, self.winbar)
124end
125
126---@class lylla.proto
127---@field setwinbar fun(self, ev?: vim.api.keyset.create_autocmd.callback_args)
128function statusline:setwinbar(ev)
129 local buf = vim.api.nvim_win_get_buf(self.win)
130 if vim.bo[buf].buftype ~= "" then
131 return
132 end
133
134 local ok, result = pcall(vim.api.nvim_win_call, self.win, function()
135 return self:getwinbar(ev)
136 end)
137 if not ok then
138 return
139 end
140
141 vim.wo[self.win].winbar = result
142end
143
144---@class lylla.proto
145---@field set fun(self, ev?: vim.api.keyset.create_autocmd.callback_args)
146function statusline:set(ev)
147 local ok, result = pcall(vim.api.nvim_win_call, self.win, function()
148 return self:get(ev)
149 end)
150 if not ok then
151 return
152 end
153
154 vim.wo[self.win].statusline = result
155end
156
157return statusline