馃 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 name = string.format("(%s)", vim.bo.filetype)
143 else
144 file_icon_raw, file_icon_hl = require("mini.icons").get("file", name)
145 end
146
147 return { { name, default_file_hl }, { " " }, { file_icon_raw, file_icon_hl } }
148end
149
150function utils.getfilepath()
151 if vim.bo.buftype ~= "" then
152 return {}
153 end
154
155 local path = vim.fn.expand("%:p:~:.")
156
157 local file_path_list = {}
158 local _ = string.gsub(path, "[^/]+", function(w)
159 table.insert(file_path_list, w)
160 end)
161
162 local filepath = vim.iter(ipairs(file_path_list)):fold("", function(acc, i, fragment)
163 if i == #file_path_list then
164 return acc
165 end
166 acc = acc .. fragment .. "/"
167 return acc
168 end)
169
170 return { filepath, "Directory" }
171end
172
173function utils.get_searchcount()
174 local result = vim.fn.searchcount({ recompute = 1 })
175 if vim.v.hlsearch ~= 1 then
176 return ""
177 end
178 if vim.tbl_isempty(result) then
179 return ""
180 end
181 local term = vim.fn.getreg("/")
182 local display
183 if result.incomplete == 1 then
184 -- timed out
185 display = "[?/??]"
186 elseif result.incomplete == 2 then
187 -- max count exceeded
188 if result.total > result.maxcount and result.current > result.maxcount then
189 display = string.format("[>%d/>%d]", result.current, result.total)
190 elseif result.total > result.maxcount then
191 display = string.format("[%d/>%d]", result.current, result.total)
192 end
193 end
194 display = display or string.format("[%d/%d]", result.current, result.total)
195
196 return { { string.format("/%s", term), "IncSearch" }, { " " }, { display, "MsgSeparator" } }
197end
198
199---@param mode string
200---@return string
201---@return string
202function utils.get_modehl_name(mode)
203 return "@lylla." .. mode, string.format("@lylla.%s.rev", mode)
204end
205
206---@return string
207---@return string
208function utils.get_modehl()
209 local mode = vim.api.nvim_get_mode().mode
210 local modename = "normal"
211
212 if string.match(mode, "^[vVs]") then
213 modename = "visual"
214 elseif string.match(mode, "^c") then
215 modename = "command"
216 elseif string.match(mode, "^[it]") then
217 modename = "insert"
218 elseif string.match(mode, "^[rR]") then
219 modename = "replace"
220 elseif string.match(mode, "^%ao") then
221 modename = "operator"
222 end
223
224 return utils.get_modehl_name(modename)
225end
226
227return utils