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