···11+local M = {}
22+33+---@type table<'normal'|'visual'|'command'|'insert', vim.api.keyset.highlight>
44+local default_hls = {
55+ normal = { link = "MiniIconsAzure" },
66+ visual = { link = "MiniIconsPurple" },
77+ command = { link = "MiniIconsOrange" },
88+ insert = { link = "MiniIconsGrey" },
99+}
1010+1111+function M.inithls()
1212+ local utils = require("lylla.utils")
1313+ vim.iter(pairs(default_hls)):each(function(mode, defaulthl)
1414+ local name = utils.get_modehl_name(mode)
1515+1616+ local hl = require("lylla.config").get().hls[mode]
1717+ if hl then
1818+ vim.api.nvim_set_hl(0, name, hl)
1919+ return
2020+ end
2121+2222+ if vim.tbl_isempty(vim.api.nvim_get_hl(0, { name = name })) then
2323+ vim.api.nvim_set_hl(0, name, defaulthl)
2424+ end
2525+ end)
2626+end
2727+2828+function M.resethl()
2929+ local utils = require("lylla.utils")
3030+ vim.iter(pairs(default_hls)):each(function(mode, _)
3131+ local name = utils.get_modehl_name(mode)
3232+ vim.api.nvim_set_hl(0, name, {})
3333+ end)
3434+end
3535+3636+function M.init()
3737+ vim.api.nvim_create_autocmd("WinNew", {
3838+ group = vim.api.nvim_create_augroup("lylla:statusline:new", { clear = true }),
3939+ callback = function()
4040+ local win = vim.api.nvim_get_current_win()
4141+ require("lylla.statusline"):new(win):init()
4242+ end,
4343+ })
4444+ local win = vim.api.nvim_get_current_win()
4545+ require("lylla.statusline"):new(win):init()
4646+4747+ vim.api.nvim_create_autocmd("WinClosed", {
4848+ group = vim.api.nvim_create_augroup("lylla:close", { clear = true }),
4949+ callback = function(ev)
5050+ local stl = require("lylla.statusline").wins[ev.match]
5151+ if stl then
5252+ stl:close()
5353+ end
5454+ end,
5555+ })
5656+5757+ vim.api.nvim_create_autocmd("ColorSchemePre", {
5858+ group = vim.api.nvim_create_augroup("lylla:resethl", { clear = true }),
5959+ callback = function()
6060+ M.resethl()
6161+ end,
6262+ })
6363+ vim.api.nvim_create_autocmd("ColorScheme", {
6464+ group = vim.api.nvim_create_augroup("lylla:inithls", { clear = true }),
6565+ callback = function()
6666+ M.inithls()
6767+ end,
6868+ })
6969+ M.inithls()
7070+end
7171+7272+return M
+128
lua/lylla/statusline.lua
···11+local utils = require("lylla.utils")
22+33+---@class lylla.proto
44+---@field wins table<integer, table>
55+---@field win integer
66+---@field modules any[]
77+---@field timer uv.uv_timer_t
88+---@field refreshau integer
99+local statusline = {}
1010+1111+statusline.wins = {}
1212+1313+---@class lylla.proto
1414+---@field new fun(self, win): lylla.proto
1515+function statusline:new(win)
1616+ if statusline.wins[win] then
1717+ statusline.wins[win]:close()
1818+ end
1919+ local stl = setmetatable({
2020+ win = win,
2121+ modules = vim.deepcopy(require("lylla.config").get().modules, true),
2222+ }, { __index = statusline })
2323+ statusline.wins[win] = stl
2424+ return stl
2525+end
2626+2727+---@class lylla.proto
2828+---@field init fun(self)
2929+function statusline:init()
3030+ local err, err_kind
3131+ ---@diagnostic disable-next-line: assign-type-mismatch
3232+ self.timer, err, err_kind = vim.uv.new_timer()
3333+ if not self.timer or err then
3434+ vim.api.nvim_echo({ { err_kind }, { "\n\t" }, { err } }, true, { err = true })
3535+ return
3636+ end
3737+3838+ local refresh = require("lylla.config").get().refresh_rate
3939+ self.timer:start(0, refresh, function()
4040+ self:refresh()
4141+ end)
4242+4343+ self.refreshau = vim.api.nvim_create_autocmd(require("lylla.config").get().events, {
4444+ group = vim.api.nvim_create_augroup("lylla:refresh", { clear = false }),
4545+ callback = function(ev)
4646+ self:refresh(ev)
4747+ end,
4848+ })
4949+end
5050+5151+---@class lylla.proto
5252+---@field close fun(self)
5353+function statusline:close()
5454+ self.timer:stop()
5555+ self.timer:close()
5656+ vim.api.nvim_del_autocmd(self.refreshau)
5757+ statusline.wins[self.win] = nil
5858+end
5959+6060+---@class lylla.proto
6161+---@field refresh fun(self, ev?: vim.api.keyset.create_autocmd.callback_args)
6262+function statusline:refresh(ev)
6363+ vim.schedule(function()
6464+ if vim.o.cmdheight == 0 and vim.api.nvim_get_mode().mode == "c" then
6565+ return
6666+ end
6767+6868+ if not vim.api.nvim_win_is_valid(self.win) then
6969+ return
7070+ end
7171+7272+ local ok, result = pcall(vim.api.nvim_win_call, self.win, function()
7373+ return self:get(ev)
7474+ end)
7575+ if not ok then
7676+ return
7777+ end
7878+7979+ vim.wo[self.win].statusline = result
8080+ end)
8181+end
8282+8383+---@class lylla.proto
8484+---@field fold fun(self, ev?: vim.api.keyset.create_autocmd.callback_args): string
8585+function statusline:fold(ev)
8686+ local modules = vim
8787+ .iter(ipairs(self.modules))
8888+ :map(function(i, module)
8989+ if type(module) == "table" and module._type == "component" then
9090+ if module.opts and module.opts.events then
9191+ -- refresh from timer
9292+ if not ev and module.prev then
9393+ return module.prev
9494+ end
9595+ -- refresh from non-match event
9696+ if ev and not vim.tbl_contains(module.opts.events, ev.event) and module.prev then
9797+ return module.prev
9898+ end
9999+ end
100100+ module.prev = module.fn()
101101+ return module.prev
102102+ end
103103+ if type(module) == "function" then
104104+ module = module()
105105+ end
106106+ return module
107107+ end)
108108+ :totable()
109109+ modules = utils.flatten(modules, 1)
110110+ return vim.iter(modules):fold("", function(str, module)
111111+ if type(module) ~= "table" or #module == 0 then
112112+ return str
113113+ end
114114+ local text = module[1]
115115+ if #module > 1 then
116116+ return str .. "%#" .. module[2] .. "#" .. text .. "%*"
117117+ end
118118+ return str .. "%*" .. text
119119+ end)
120120+end
121121+122122+---@class lylla.proto
123123+---@field get fun(self, ev?: vim.api.keyset.create_autocmd.callback_args)
124124+function statusline:get(ev)
125125+ return self:fold(ev)
126126+end
127127+128128+return statusline
+166
lua/lylla/utils.lua
···11+local utils = {}
22+33+function utils.get_client()
44+ local buf_ft = vim.api.nvim_get_option_value("filetype", { buf = 0 })
55+ local result = vim.iter(vim.lsp.get_clients({ bufnr = 0 })):find(function(
66+ client --[[@as vim.lsp.Client]]
77+ )
88+ return vim.iter(ipairs(client.config.filetypes)):any(function(_, ft)
99+ return ft == buf_ft
1010+ end)
1111+ end)
1212+ if result then
1313+ return result.config.name
1414+ end
1515+end
1616+1717+--- flatten list so all children have level of depth
1818+---@param lst table
1919+---@param maxdepth integer
2020+function utils.flatten(lst, maxdepth)
2121+ ---@param _t any[]
2222+ ---@return integer
2323+ local function _depth(_t)
2424+ return vim.iter(_t):fold(1, function(maxd, v)
2525+ if type(v) == "table" then
2626+ local d = 1 + _depth(v)
2727+ if d > maxd then
2828+ return d
2929+ end
3030+ end
3131+ return maxd
3232+ end)
3333+ end
3434+3535+ local result = {}
3636+ ---@param _t any[]
3737+ local function _flatten(_t)
3838+ local n = #_t
3939+ for i = 1, n do
4040+ local v = _t[i]
4141+ if type(v) ~= "table" or _depth(v) <= maxdepth then
4242+ table.insert(result, v)
4343+ else
4444+ _flatten(v)
4545+ end
4646+ end
4747+ end
4848+ _flatten(lst)
4949+ return result
5050+end
5151+5252+function utils.getfilename()
5353+ local _, default_file_hl = require("mini.icons").get("default", "file")
5454+5555+ local name = vim.fn.expand("%:t")
5656+5757+ local file_icon_raw, file_icon_hl
5858+5959+ if vim.bo.buftype ~= "" then
6060+ local filetype = vim.bo.filetype
6161+ file_icon_raw, file_icon_hl = require("mini.icons").get("filetype", filetype)
6262+ else
6363+ file_icon_raw, file_icon_hl = require("mini.icons").get("file", name)
6464+ end
6565+6666+ return { { name, default_file_hl }, { " " }, { file_icon_raw, file_icon_hl } }
6767+end
6868+6969+function utils.getfilepath()
7070+ local path = vim.fn.expand("%:p:~:.")
7171+7272+ local file_path_list = {}
7373+ local _ = string.gsub(path, "[^/]+", function(w)
7474+ table.insert(file_path_list, w)
7575+ end)
7676+7777+ local filepath = vim.iter(ipairs(file_path_list)):fold("", function(acc, i, fragment)
7878+ if i == #file_path_list then
7979+ return acc
8080+ end
8181+ acc = acc .. fragment .. "/"
8282+ return acc
8383+ end)
8484+8585+ return { filepath, "Directory" }
8686+end
8787+8888+function utils.get_searchcount()
8989+ local result = vim.fn.searchcount({ recompute = 1 })
9090+ if vim.v.hlsearch ~= 1 then
9191+ return ""
9292+ end
9393+ if vim.tbl_isempty(result) then
9494+ return ""
9595+ end
9696+ local term = vim.fn.getreg("/")
9797+ local display
9898+ if result.incomplete == 1 then
9999+ -- timed out
100100+ display = "[?/??]"
101101+ elseif result.incomplete == 2 then
102102+ -- max count exceeded
103103+ if result.total > result.maxcount and result.current > result.maxcount then
104104+ display = string.format("[>%d/>%d]", result.current, result.total)
105105+ elseif result.total > result.maxcount then
106106+ display = string.format("[%d/>%d]", result.current, result.total)
107107+ end
108108+ end
109109+ display = display or string.format("[%d/%d]", result.current, result.total)
110110+111111+ return { { string.format("/%s", term), "IncSearch" }, { " " }, { display, "MsgSeparator" } }
112112+end
113113+114114+function utils.get_fmt()
115115+ local filetype = vim.bo.filetype
116116+ if not filetype then
117117+ return
118118+ end
119119+ local formatters = require("mossy.filetype").get(filetype, "formatting")
120120+ if #formatters == 0 then
121121+ return
122122+ end
123123+124124+ local fmt = vim.iter(formatters):find(function(formatter)
125125+ if formatter.cond and not formatter.cond({ buf = 0 }) then
126126+ return false
127127+ end
128128+129129+ if formatter.cmd and vim.fn.executable(formatter.cmd) == 0 then
130130+ return false
131131+ end
132132+ return true
133133+ end)
134134+ return fmt and fmt.name or nil
135135+end
136136+137137+---@param mode string
138138+---@return string
139139+function utils.get_modehl_name(mode)
140140+ return "@lylla." .. mode
141141+end
142142+143143+---@return string
144144+function utils.get_modehl()
145145+ local mode = vim.api.nvim_get_mode().mode
146146+147147+ if string.match(mode, "n") then
148148+ return utils.get_modehl_name("normal")
149149+ end
150150+151151+ if string.match(mode, "[vVs]") then
152152+ return utils.get_modehl_name("visual")
153153+ end
154154+155155+ if string.match(mode, "c") then
156156+ return utils.get_modehl_name("command")
157157+ end
158158+159159+ if string.match(mode, "[irRt]") then
160160+ return utils.get_modehl_name("insert")
161161+ end
162162+163163+ return mode
164164+end
165165+166166+return utils