From 56cb2612fd436ac3fd0c11c857301e5ad0dc107b Mon Sep 17 00:00:00 2001 From: robin Date: Sun, 3 May 2026 16:06:35 +0200 Subject: [PATCH] refactor: move timer to global scope - fix issue with first arg win not being init - fix issue with rev highlights not changing after colorscheme event --- lua/lylla/config.lua | 1 + lua/lylla/init.lua | 123 +++++++++++++++++++++++++--- lua/lylla/statusline.lua | 167 +++++++++++++-------------------------- lua/lylla/tabline.lua | 2 +- lua/lylla/utils.lua | 40 +++++++--- plugin/lylla.lua | 62 +++++++++------ stylua.toml | 2 +- 7 files changed, 239 insertions(+), 158 deletions(-) diff --git a/lua/lylla/config.lua b/lua/lylla/config.lua index 1cab23b..80bd048 100644 --- a/lua/lylla/config.lua +++ b/lua/lylla/config.lua @@ -22,6 +22,7 @@ M.default = { refresh_rate = 300, hls = {}, modules = { + "meow", "%<%f %h%w%m%r", "%=", { diff --git a/lua/lylla/init.lua b/lua/lylla/init.lua index f859d86..85ebcd8 100644 --- a/lua/lylla/init.lua +++ b/lua/lylla/init.lua @@ -1,4 +1,9 @@ -local M = {} +local logger = vim.log.new({ + name = "lylla", + current_level = vim.log.levels.DEBUG, +}) + +local lylla = {} local lzrq = function(modname) return vim.defaulttable(function(k) @@ -7,24 +12,124 @@ local lzrq = function(modname) end local config = lzrq("lylla.config") +local statusline = lzrq("lylla.statusline") + +-- setup ====================================================================== + +---@param cfg lylla.config +local function getevents(cfg) + local modules = cfg.modules + + local t = vim.iter(ipairs(modules)):fold({}, function(acc, _, module) + if + type(module) == "table" + and module.fn + and type(module.fn) == "function" + then + if module.opts and module.opts.events then + return vim.iter(module.opts.events):fold(acc, function(a, event) + a[event] = true + return a + end) + end + end + return acc + end) + + return vim.tbl_keys(t) +end + +lylla.timer = nil +lylla.group = nil ---@param cfg? lylla.config -function M.setup(cfg) +function lylla.setup(cfg) ---@diagnostic disable-next-line: assign-type-mismatch cfg = cfg or {} config.set(config.override(cfg)) + + lylla.group = vim.api.nvim_create_augroup("@lylla.refresh", { clear = true }) + local events = getevents(config.get()) + + for i = 1, #events do + local event = events[i] + local eventname, eventpattern = unpack(vim.split(event, " "), 1, 2) + ---@cast eventname vim.api.keyset.events? + if eventname then + vim.api.nvim_create_autocmd(eventname, { + group = lylla.group, + pattern = eventpattern, + callback = function(ev) + lylla.refresh(ev) + end, + }) + end + end +end + +lylla._init = false + +function lylla.init() + if lylla._init then + vim.notify("lylla has already been initialized", vim.log.levels.ERROR) + return + end + + local err, err_kind + ---@diagnostic disable-next-line: assign-type-mismatch + lylla.timer, err, err_kind = vim.uv.new_timer() + if not lylla.timer or err then + vim.notify(string.format("%s\n\t%s", err_kind, err), vim.log.levels.ERROR) + return + end + + -- TODO: only refresh visible windows + local refresh = config.get().refresh_rate --[[@as integer]] + lylla.timer:start(0, refresh, function() + lylla.refresh() + end) + + lylla._init = true +end + +---@param self lylla.proto +---@param fn fun(self: lylla.proto, ev?: vim.api.keyset.create_autocmd.callback_args|true) +---@param ev? vim.api.keyset.create_autocmd.callback_args|true +local function refreshcomponent(self, fn, ev) + do + local ok, result = pcall(fn, self, ev) + if not ok then + logger.error("error occured on refresh:\n\t" .. result) + end + end +end + +---@param ev? vim.api.keyset.create_autocmd.callback_args|true +function lylla.refresh(ev) + if not lylla._init then + return + end + + vim.schedule(function() + local wins = statusline.wins + for win, st in pairs(wins) do + if not vim.api.nvim_win_is_valid(win) then + st:close() + else + refreshcomponent(st, statusline.set, ev) + refreshcomponent(st, statusline.setwinbar, ev) + end + end + end) end --- helpers +-- helpers ==================================================================== ---@param fn fun(): string|any[] ---@param opts? { events: string[] } ---@return table -function M.component(fn, opts) - local t = {} - t.fn = fn - t.opts = opts - return t +function lylla.component(fn, opts) + return { fn = fn, opts = opts } end -return M +return lylla diff --git a/lua/lylla/statusline.lua b/lua/lylla/statusline.lua index 283abed..b8fc6ec 100644 --- a/lua/lylla/statusline.lua +++ b/lua/lylla/statusline.lua @@ -1,4 +1,3 @@ -local log = require("lylla.log") local utils = require("lylla.utils") ---@class (partial) lylla.proto @@ -6,9 +5,6 @@ local utils = require("lylla.utils") ---@field win integer ---@field modules any[] ---@field winbar any[] ----@field initialized boolean? ----@field timer uv.uv_timer_t ----@field refreshau integer local statusline = {} statusline.wins = {} @@ -39,99 +35,32 @@ function statusline.try_new(win) return statusline.new(win) end ----@class (partial) lylla.proto ----@field init fun(self) -function statusline:init() - if self.initialized then - return - end - - local err, err_kind - ---@diagnostic disable-next-line: assign-type-mismatch - self.timer, err, err_kind = vim.uv.new_timer() - if not self.timer or err then - vim.notify(string.format("%s\n\t%s", err_kind, err), vim.log.levels.ERROR) - return - end - - local refresh = require("lylla.config").get().refresh_rate - self.timer:start(0, refresh, function() - self:refresh() - end) - - self.refreshau = vim.api.nvim_create_augroup(("@lylla.refresh.%d"):format(self.win), { clear = true }) - - local events = self:getevents() - - for i = 1, #events do - local event = events[i] - local eventname, eventpattern = unpack(vim.split(event, " "), 1, 2) - ---@cast eventname vim.api.keyset.events? - if eventname then - vim.api.nvim_create_autocmd(eventname, { - group = self.refreshau, - pattern = eventpattern, - callback = function(ev) - self:refresh(ev) - end, - }) - end - end - - self.initialized = true -end - ---@class (partial) lylla.proto ---@field close fun(self) function statusline:close() - self.timer:stop() - self.timer:close() - pcall(vim.api.nvim_del_augroup_by_id, self.refreshau) statusline.wins[self.win] = nil end ----@class (partial) lylla.proto ----@field getevents fun(self): string[] -function statusline:getevents() - local t = vim.iter(ipairs(self.modules)):fold({}, function(acc, _, module) - if type(module) == "table" and module.fn and type(module.fn) == "function" then - if module.opts and module.opts.events then - return vim.iter(module.opts.events):fold(acc, function(a, event) - a[event] = true - return a - end) - end - end - return acc - end) - - return vim.tbl_keys(t) -end - -local function refreshcomponent(self, fn, ev) - do - local ok, result = pcall(fn, self, ev) - if not ok then - log.error("[lylla] error occured on refresh:\n\t" .. result) - end +---@param v any +---@return string? +local function display(v) + if type(v) == "table" then + return utils.fold(v) end -end - ----@class (partial) lylla.proto ----@field refresh fun(self, ev?: vim.api.keyset.create_autocmd.callback_args) -function statusline:refresh(ev) - vim.schedule(function() - if not vim.api.nvim_win_is_valid(self.win) then + if type(v) == "function" then + local ok, result = pcall(v) + if not ok then return end - refreshcomponent(self, statusline.set, ev) - refreshcomponent(self, statusline.setwinbar, ev) - end) + return display(result) + end + + return tostring(v) end ---@class (partial) lylla.proto ----@field fold fun(ev?: vim.api.keyset.create_autocmd.callback_args, modules: any[]): string +---@field fold fun(ev?: vim.api.keyset.create_autocmd.callback_args|true, modules: any[]): string function statusline.fold(ev, modules) if type(modules) ~= "table" or modules == nil then return "" @@ -140,37 +69,39 @@ function statusline.fold(ev, modules) local lst = vim .iter(ipairs(modules)) :map(function(_, module) - if type(module) == "table" and module.fn and type(module.fn) == "function" then - if module.opts and module.opts.events then - -- refresh from timer - if not ev and module.prev then - return module.prev - end - -- refresh from non-match event - if ev and not vim.tbl_contains(module.opts.events, ev.event) and module.prev then - return module.prev - end - end - do - local ok, result = pcall(module.fn) - if not ok then - error(result) - end - module.prev = result - end - return module.prev + if type(module) ~= "table" then + return display(module) + end + + if not module.fn or type(module.fn) ~= "function" then + return display(module) end - if type(module) == "function" then - local ok, result = pcall(module) - if not ok then - error(result) + + if + not (type(ev) == "boolean" and ev == true) + and module.opts + and module.opts.events + then + -- refresh from timer + if not ev and module.prev then + return module.prev + end + -- refresh from non-match event + if + ev + and not vim.tbl_contains(module.opts.events, ev.event) + and module.prev + then + return module.prev end - return result end - return module + + module.prev = display(module.fn) + + return module.prev end) :totable() - return utils.fold(lst) + return table.concat(lst) end ---@class (partial) lylla.proto @@ -196,7 +127,13 @@ function statusline:setwinbar(ev) local ok, result = pcall(vim.api.nvim_win_call, self.win, function() return self:getwinbar(ev) end) - assert(ok, string.format("error occured while trying to evaluate winbar:\n\t%s", result)) + assert( + ok, + string.format( + "error occured while trying to evaluate winbar:\n\t%s", + result + ) + ) ---@cast result string vim.wo[self.win].winbar = result @@ -208,7 +145,13 @@ function statusline:set(ev) local ok, result = pcall(vim.api.nvim_win_call, self.win, function() return self:get(ev) end) - assert(ok, string.format("error occured while trying to evaluate statusline:\n\t%s", result)) + assert( + ok, + string.format( + "error occured while trying to evaluate statusline:\n\t%s", + result + ) + ) ---@cast result string vim.wo[self.win].statusline = result diff --git a/lua/lylla/tabline.lua b/lua/lylla/tabline.lua index fb95cfe..46e1542 100644 --- a/lua/lylla/tabline.lua +++ b/lua/lylla/tabline.lua @@ -11,7 +11,7 @@ end local M, H = {}, {} -M.setup = function() +M.init = function() vim.o.tabline = "%!v:lua.vim.g.tablinefn()" end diff --git a/lua/lylla/utils.lua b/lua/lylla/utils.lua index 77e53b9..12e1b02 100644 --- a/lua/lylla/utils.lua +++ b/lua/lylla/utils.lua @@ -51,11 +51,19 @@ local hlfmt_inherit = "%s%%$%s$%s%%#%s#" ---@return string local function strfmt(str, text, inherit, hl) if hl then - return string.format(inherit and hlfmt_inherit or hlfmt, str, hl, text, inherit or nil) + return string.format( + inherit and hlfmt_inherit or hlfmt, + str, + hl, + text, + inherit or nil + ) end return string.format(inherit and sfmt_inherit or sfmt, str, text) end +---@param lst any[] +---@return string function utils.fold(lst) vim.validate("lst", lst, "table") @@ -69,7 +77,10 @@ function utils.fold(lst) if type(module) ~= "table" then return str end - if module.section ~= nil and (type(module.section) == "string" or module.section == false) then + if + module.section ~= nil + and (type(module.section) == "string" or module.section == false) + then section = module.section if section then return string.format("%s%%#%s#", str, module.section) @@ -99,7 +110,7 @@ function utils.fold(lst) end) end ----@param hl string|vim.api.keyset.highlight name or keyset +---@param hl string|vim.api.keyset.highlight|vim.api.keyset.get_hl_info name or keyset ---@return vim.api.keyset.highlight function utils.reverse_hl(hl) if type(hl) == "string" then @@ -144,7 +155,8 @@ function utils.getfilename() file_icon_raw, file_icon_hl = require("mini.icons").get(type_, name) elseif buftype == "nofile" or buftype == "acwrite" then local filetype = vim.bo.filetype - file_icon_raw, file_icon_hl = require("mini.icons").get("filetype", filetype) + file_icon_raw, file_icon_hl = + require("mini.icons").get("filetype", filetype) name = string.format("(%s)", filetype) else name = string.format("(%s)", buftype) @@ -167,13 +179,15 @@ function utils.getfilepath() table.insert(file_path_list, w) end) - local filepath = vim.iter(ipairs(file_path_list)):fold("", function(acc, i, fragment) - if i == #file_path_list then + local filepath = vim + .iter(ipairs(file_path_list)) + :fold("", function(acc, i, fragment) + if i == #file_path_list then + return acc + end + acc = acc .. fragment .. "/" return acc - end - acc = acc .. fragment .. "/" - return acc - end) + end) return { filepath, "Directory" } end @@ -201,7 +215,11 @@ function utils.get_searchcount() end display = display or string.format("[%d/%d]", result.current, result.total) - return { { string.format("/%s", term), "IncSearch" }, { " " }, { display, "MsgSeparator" } } + return { + { string.format("/%s", term), "IncSearch" }, + { " " }, + { display, "MsgSeparator" }, + } end ---@param mode string diff --git a/plugin/lylla.lua b/plugin/lylla.lua index 7fb404b..9c93665 100644 --- a/plugin/lylla.lua +++ b/plugin/lylla.lua @@ -45,35 +45,49 @@ end -- init ======================================================================= +local group = vim.api.nvim_create_augroup("@lylla", { clear = true }) + +vim.api.nvim_create_autocmd({ "WinNew", "WinEnter" }, { + group = group, + callback = function() + require("lylla.statusline").try_new() + end, +}) + +vim.api.nvim_create_autocmd("WinClosed", { + group = group, + callback = function(ev) + ---@cast ev +{match: integer} + local stl = require("lylla.statusline").wins[ev.match] + if stl then + stl:close() + end + end, +}) + +vim.api.nvim_create_autocmd({ "UIEnter", "ColorScheme" }, { + group = group, + callback = function() + inithls() + + require("lylla").refresh(true) + end, +}) + local function init() - vim.api.nvim_create_autocmd({ "UIEnter", "WinNew", "WinEnter" }, { - group = vim.api.nvim_create_augroup("@lylla.win", { clear = true }), - callback = function() - require("lylla.statusline").try_new():init() - end, - }) + require("lylla").init() - vim.api.nvim_create_autocmd("WinClosed", { - group = vim.api.nvim_create_augroup("@lylla.close", { clear = true }), - callback = function(ev) - ---@cast ev +{match: integer} - local stl = require("lylla.statusline").wins[ev.match] - if stl then - stl:close() - end - end, - }) + local wins = vim.api.nvim_list_wins() + for _, win in ipairs(wins) do + if vim.api.nvim_win_is_valid(win) then + require("lylla.statusline").try_new(win) + end + end if config.get().tabline ~= vim.NIL then - require("lylla.tabline").setup() + ---@diagnostic disable-next-line: undefined-field + require("lylla.tabline").init() end - - vim.api.nvim_create_autocmd({ "UIEnter", "ColorScheme" }, { - group = vim.api.nvim_create_augroup("@lylla.hls", { clear = true }), - callback = function() - inithls() - end, - }) end if vim.v.vim_did_enter > 0 then diff --git a/stylua.toml b/stylua.toml index 5957975..32d4fb8 100644 --- a/stylua.toml +++ b/stylua.toml @@ -1,6 +1,6 @@ indent_type = "Spaces" indent_width = 2 -column_width = 120 +column_width = 80 quote_style = "AutoPreferDouble" call_parentheses = "Always" line_endings = "Unix" -- 2.51.2