馃 a tiny, customizable statusline for neovim
1local utils = {}
2
3--- flatten list so all children have level of depth
4---@param lst table
5---@param maxdepth integer
6function utils.flatten(lst, maxdepth)
7 vim.validate("lst", lst, "table")
8 vim.validate("maxdepth", maxdepth, "number")
9
10 ---@param _t any[]
11 ---@return integer
12 local function _depth(_t)
13 return vim.iter(ipairs(_t)):fold(1, function(maxd, _, v)
14 if type(v) == "table" and vim.islist(v) then
15 local d = 1 + _depth(v)
16 if d > maxd then
17 return d
18 end
19 end
20 return maxd
21 end)
22 end
23
24 local result = {}
25 ---@param _t any[]
26 local function _flatten(_t)
27 local n = #_t
28 for i = 1, n do
29 local v = _t[i]
30 if type(v) ~= "table" or (not vim.islist(v)) or _depth(v) <= maxdepth then
31 table.insert(result, v)
32 else
33 _flatten(v)
34 end
35 end
36 end
37 _flatten(lst)
38 return result
39end
40
41local sfmt = "%s%%*%s"
42local sfmt_inherit = "%s%s"
43-- str, hl_name, text
44local hlfmt = "%s%%#%s#%s"
45local hlfmt_inherit = "%s%%$%s$%s"
46
47---@param str string
48---@param text string
49---@param inherit boolean
50---@param hl? string
51---@return string
52local function strfmt(str, text, inherit, hl)
53 if hl then
54 return string.format(inherit and hlfmt_inherit or hlfmt, str, hl, text)
55 end
56 return string.format(inherit and sfmt_inherit or sfmt, str, text)
57end
58
59function utils.fold(lst)
60 vim.validate("lst", lst, "table")
61
62 lst = utils.flatten(lst, 1)
63 ---@type string|false
64 local section = false
65 return vim.iter(ipairs(lst)):fold("", function(str, _, module)
66 local inherit = not not section
67 if type(module) == "string" and #module > 0 then
68 return strfmt(str, module, inherit)
69 end
70 if type(module) ~= "table" then
71 return str
72 end
73 if module.section ~= nil and (type(module.section) == "string" or module.section == false) then
74 section = module.section
75 if section then
76 return string.format("%s%%#%s#", str, module.section)
77 end
78 return str
79 end
80 local text = module[1]
81 if text == nil or type(text) ~= "string" or #text == 0 then
82 return str
83 end
84 local hl = module[2]
85 if not hl then
86 return strfmt(str, text, inherit)
87 end
88 if type(hl) == "string" and #hl > 0 then
89 return strfmt(str, text, inherit, hl)
90 elseif type(hl) == "table" and (hl.fg or hl.bg or hl.link) then
91 inherit = inherit or hl.inherit
92 hl.inherit = nil
93 local hl_name = hl.link
94 if not hl_name then
95 hl_name = utils.create_hl(hl)
96 end
97 return strfmt(str, text, inherit, hl_name)
98 elseif type(hl) == "table" and hl.inherit then
99 return strfmt(str, text, true)
100 end
101
102 return strfmt(str, text, inherit)
103 end)
104end
105
106---@param hl_name string
107---@return vim.api.keyset.highlight
108function utils.reverse_hl(hl_name)
109 local hl = vim.api.nvim_get_hl(0, { name = hl_name })
110 if vim.tbl_isempty(hl) or (not hl.fg and not hl.bg and not hl.link) then
111 return {}
112 end
113 if hl.link then
114 return utils.reverse_hl(hl.link)
115 end
116 local rev = vim.deepcopy(hl)
117 rev.fg = hl.bg
118 rev.bg = hl.fg
119 ---@diagnostic disable-next-line: return-type-mismatch
120 return rev
121end
122
123---@param hl vim.api.keyset.highlight
124function utils.create_hl(hl)
125 local hl_name = string.format("@lylla.%s", vim.fn.sha256(vim.inspect(hl)))
126 vim.schedule(function()
127 vim.api.nvim_set_hl(0, hl_name, hl)
128 end)
129 return hl_name
130end
131
132function utils.getfilename()
133 local _, default_file_hl = require("mini.icons").get("default", "file")
134
135 local name = vim.fn.expand("%:t")
136
137 local file_icon_raw, file_icon_hl
138
139 if vim.bo.buftype ~= "" then
140 local filetype = vim.bo.filetype
141 file_icon_raw, file_icon_hl = require("mini.icons").get("filetype", filetype)
142 else
143 file_icon_raw, file_icon_hl = require("mini.icons").get("file", name)
144 end
145
146 return { { name, default_file_hl }, { " " }, { file_icon_raw, file_icon_hl } }
147end
148
149function utils.getfilepath()
150 local path = vim.fn.expand("%:p:~:.")
151
152 local file_path_list = {}
153 local _ = string.gsub(path, "[^/]+", function(w)
154 table.insert(file_path_list, w)
155 end)
156
157 local filepath = vim.iter(ipairs(file_path_list)):fold("", function(acc, i, fragment)
158 if i == #file_path_list then
159 return acc
160 end
161 acc = acc .. fragment .. "/"
162 return acc
163 end)
164
165 return { filepath, "Directory" }
166end
167
168function utils.get_searchcount()
169 local result = vim.fn.searchcount({ recompute = 1 })
170 if vim.v.hlsearch ~= 1 then
171 return ""
172 end
173 if vim.tbl_isempty(result) then
174 return ""
175 end
176 local term = vim.fn.getreg("/")
177 local display
178 if result.incomplete == 1 then
179 -- timed out
180 display = "[?/??]"
181 elseif result.incomplete == 2 then
182 -- max count exceeded
183 if result.total > result.maxcount and result.current > result.maxcount then
184 display = string.format("[>%d/>%d]", result.current, result.total)
185 elseif result.total > result.maxcount then
186 display = string.format("[%d/>%d]", result.current, result.total)
187 end
188 end
189 display = display or string.format("[%d/%d]", result.current, result.total)
190
191 return { { string.format("/%s", term), "IncSearch" }, { " " }, { display, "MsgSeparator" } }
192end
193
194---@param mode string
195---@return string
196---@return string
197function utils.get_modehl_name(mode)
198 return "@lylla." .. mode, string.format("@lylla.%s.rev", mode)
199end
200
201---@return string
202---@return string
203function utils.get_modehl()
204 local mode = vim.api.nvim_get_mode().mode
205 local modename = "normal"
206
207 if string.match(mode, "^[vVs]") then
208 modename = "visual"
209 elseif string.match(mode, "^c") then
210 modename = "command"
211 elseif string.match(mode, "^[it]") then
212 modename = "insert"
213 elseif string.match(mode, "^[rR]") then
214 modename = "replace"
215 elseif string.match(mode, "^%ao") then
216 modename = "operator"
217 end
218
219 return utils.get_modehl_name(modename)
220end
221
222return utils