🪴 a tiny, customizable statusline for neovim
1---@module 'lylla.config'
2
3---@class lylla.item
4---@field fn fun(): any
5---@field opts? { events?: string[] }
6
7---@alias lylla.item.tuple {[1]: string, [2]?: string}
8
9---@class lylla.config
10---@field refresh_rate integer
11---@field hls table<'normal'|'visual'|'command'|'insert'|'replace'|'operator', vim.api.keyset.highlight>
12---@field modules (lylla.item|lylla.item.tuple|string)[]
13---@field winbar any[]
14---@field tabline (fun(): (lylla.item|lylla.item.tuple|string)[])|vim.NIL
15
16local M, H = {}, {}
17
18---@type lylla.config
19---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section)
20---@text # Default ~
21M.default = {
22 refresh_rate = 300,
23 hls = {},
24 modules = {
25 "%<%f %h%w%m%r",
26 "%=",
27 {
28 fn = function()
29 if vim.o.showcmdloc == "statusline" then
30 return "%-10.S"
31 end
32 return ""
33 end,
34 },
35 { " " },
36 {
37 fn = function()
38 if not vim.b.keymap_name then
39 return ""
40 end
41 return "<" .. vim.b.keymap_name .. ">"
42 end,
43 },
44 { " " },
45 {
46 fn = function()
47 if vim.bo.busy > 0 then
48 return "◐ "
49 end
50 return ""
51 end,
52 },
53 { " " },
54 {
55 fn = function()
56 if not package.loaded["vim.diagnostic"] then
57 return ""
58 end
59 return vim.diagnostic.status()
60 end,
61 opts = {
62 events = { "DiagnosticChanged" },
63 },
64 },
65 { " " },
66 {
67 fn = function()
68 if not vim.o.ruler then
69 return ""
70 end
71 if vim.o.rulerformat == "" then
72 return "%-14.(%l,%c%V%) %P"
73 end
74 return vim.o.rulerformat
75 end,
76 },
77 },
78 winbar = {},
79 tabline = vim.NIL,
80}
81
82---@type lylla.config
83---@diagnostic disable-next-line: missing-fields
84M.config = {}
85
86---@return lylla.config
87function M.get()
88 return H.merge(M.default, M.config)
89end
90
91---@param cfg lylla.config
92---@return lylla.config
93function M.override(cfg)
94 return H.merge(M.default, cfg)
95end
96
97---@param cfg lylla.config
98function M.set(cfg)
99 M.config = cfg
100end
101
102-- helpers ====================================================================
103
104---@param tdefault lylla.config
105---@param toverride lylla.config
106---@return lylla.config
107function H.merge(tdefault, toverride)
108 return H.tmerge(tdefault, toverride)
109end
110
111---@private
112---@generic T: table|any[]
113---@param tdefault T
114---@param toverride T
115---@return T
116H.tmerge = function(tdefault, toverride)
117 if toverride == nil then
118 return tdefault
119 end
120
121 -- do not merge lists
122 if H.islist(tdefault) then
123 return toverride
124 end
125 if vim.tbl_isempty(tdefault) then
126 return toverride
127 end
128
129 return vim.iter(pairs(tdefault)):fold({}, function(tnew, k, v)
130 if toverride[k] == nil then
131 tnew[k] = v
132 return tnew
133 end
134 if type(v) ~= type(toverride[k]) then
135 tnew[k] = toverride[k]
136 return tnew
137 end
138 if type(v) == "table" then
139 tnew[k] = H.tmerge(v, toverride[k])
140 return tnew
141 end
142
143 tnew[k] = toverride[k]
144 return tnew
145 end)
146end
147
148---@param t table
149---@return boolean
150H.islist = function(t)
151 if type(t) ~= "table" then
152 return false
153 end
154
155 for k, _ in ipairs(t) do
156 return type(k) == "number"
157 end
158
159 -- non-numeric keys
160 for _, _ in pairs(t) do
161 return false
162 end
163
164 -- empty table
165 return true
166end
167
168return M, H