diff --git a/.nvim.lua b/.nvim.lua index 6f6abd5..ff4124c 100644 --- a/.nvim.lua +++ b/.nvim.lua @@ -1,12 +1,16 @@ R = function(m, ...) - require("plenary.reload").reload_module(m, ...) - return require(m) + require("plenary.reload").reload_module(m, ...) + return require(m) end -vim.opt.rtp:prepend "." +vim.opt.rtp:prepend(".") -- -pcall(function() - R("lylla").init() -end) +vim.api.nvim_create_autocmd("UIEnter", { + callback = function() + pcall(function() + R("lylla") + end) + end, +}) diff --git a/lua/lylla/config.lua b/lua/lylla/config.lua index b8223b1..021d6e3 100644 --- a/lua/lylla/config.lua +++ b/lua/lylla/config.lua @@ -1,27 +1,20 @@ ---@module 'lylla.config' +---@class lylla.item +---@field fn fun(): any +---@field opts? { events?: string[] } + +---@alias lylla.item.tuple {[1]: string, [2]?: string} + ---@class lylla.config ---@field refresh_rate integer ---@field events string[] ----@field prefix string ---@field hls table<'normal'|'visual'|'command'|'insert', vim.api.keyset.highlight> ----@field modules any[] +---@field modules (lylla.item|lylla.item.tuple|string)[] ---@field winbar any[] -local utils = require("lylla.utils") - local M = {} ----@param fn fun(): string[] ----@param opts? { events: string[] } ----@return table -local function component(fn, opts) - local t = { _type = "component" } - t.fn = fn - t.opts = opts - return t -end - ---@type lylla.config ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section) ---@text # Default ~ @@ -40,88 +33,62 @@ M.default = { "ModeChanged", "CmdlineEnter", }, - prefix = "▌", hls = {}, modules = { - component(function() - local prefix = require("lylla.config").get().prefix - local modehl = utils.get_modehl() - return { - { prefix, modehl }, - { "[" .. vim.api.nvim_get_mode().mode .. "]", modehl }, - } - end, { - events = { "ModeChanged", "CmdlineEnter" }, - }), - { " " }, - component(function() - return { - utils.getfilepath(), - utils.getfilename(), - { " " }, - } - end, { - events = { - "WinEnter", - "BufEnter", - "BufWritePost", - "FileChangedShellPost", - "Filetype", - }, - }), + "%<%f %h%w%m%r", + "%=", + { + fn = function() + if vim.o.showcmdloc == "statusline" then + return "%-10.S" + end + return "" + end, + }, { " " }, - component(function() - return { utils.get_searchcount() } - end), - { "%=" }, - {}, - { "%=" }, - component(function() - return { - { { "lsp :: " }, { utils.get_client() or "none" } }, - { " | ", "NonText" }, - { { "fmt :: " }, { utils.get_fmt() or "none" } }, - { " | ", "NonText" }, - } - end, { events = { "FileType" } }), - { "%p%%" }, - { " | ", "NonText" }, - { "%L lines" }, - { " | ", "NonText" }, - { "%l:%c" }, + { + fn = function() + if not vim.b.keymap_name then + return "" + end + return "<" .. vim.b.keymap_name .. ">" + end, + }, { " " }, - }, - winbar = { - component(function() - local prefix = require("lylla.config").get().prefix - local modehl = utils.get_modehl() - return { - { prefix, modehl }, - } - end, { - events = { "ModeChanged", "CmdlineEnter" }, - }), + { + fn = function() + if vim.bo.busy > 0 then + return "◐ " + end + return "" + end, + }, { " " }, - component(function() - return { - utils.getfilepath(), - utils.getfilename(), - { " " }, - } - end, { - events = { - "WinEnter", - "BufEnter", - "BufWritePost", - "FileChangedShellPost", - "Filetype", + { + fn = function() + if not package.loaded["vim.diagnostic"] then + return "" + end + return vim.diagnostic.status() + end, + opts = { + events = { "DiagnosticChanged" }, }, - }), + }, { " " }, - component(function() - return { utils.get_searchcount() } - end), + { + fn = function() + if not vim.o.ruler then + return "" + end + if vim.o.rulerformat == "" then + return "%-14.(%l,%c%V%) %P" + end + return vim.o.rulerformat + end, + }, }, + winbar = {}, } ---@type lylla.config diff --git a/lua/lylla/init.lua b/lua/lylla/init.lua index 04dd857..04ecec6 100644 --- a/lua/lylla/init.lua +++ b/lua/lylla/init.lua @@ -8,6 +8,13 @@ local default_hls = { insert = { link = "MiniIconsGrey" }, } +---@param cfg? lylla.config +function M.setup(cfg) + cfg = cfg or {} + local config = require("lylla.config") + config.set(config.override(cfg)) +end + function M.inithls() local utils = require("lylla.utils") vim.iter(pairs(default_hls)):each(function(mode, defaulthl) diff --git a/lua/lylla/log.lua b/lua/lylla/log.lua new file mode 100644 index 0000000..0738654 --- /dev/null +++ b/lua/lylla/log.lua @@ -0,0 +1,70 @@ +---@module 'lylla.log' + +local log = {} + +log.stack = {} + +local function getdebuginfo() + local i = 3 + local info = debug.getinfo(i, "nSf") + local nextinfo = debug.getinfo(i + 1, "n") + while nextinfo and info.name == nil do + info = nextinfo + i = i + 1 + nextinfo = debug.getinfo(i + 1, "n") + end + return info +end + +--- any message will not be displayed unless `'debug'` is set to `msg`. +--- this is a purposeful design decisison made to avoid flooding the user with errors. +--- this design decision also means that modules can silently fail. +---@param msg string +---@param level integer +function log.notify(msg, level, debuginfo) + debuginfo = debuginfo or getdebuginfo() + local info = string.format( + "%s %s at %s", + #debuginfo.namewhat > 0 and debuginfo.namewhat or "chunk", + debuginfo.name or "main", + debuginfo.short_src or "main loop" + ) + log.stack[#log.stack + 1] = { level, info, msg } + msg = string.format("in %s:\n\t%s", info, msg) + if vim.o.debug == "" then + return + end + if vim.o.debug == "throw" and level >= vim.log.levels.ERROR then + error(msg, level) + return + end + vim.notify_once(msg, level) + return level +end + +---@param msg string +function log.trace(msg) + return log.notify(msg, vim.log.levels.TRACE, getdebuginfo()) +end + +---@param msg string +function log.debug(msg) + return log.notify(msg, vim.log.levels.DEBUG, getdebuginfo()) +end + +---@param msg string +function log.info(msg) + return log.notify(msg, vim.log.levels.INFO, getdebuginfo()) +end + +---@param msg string +function log.warn(msg) + return log.notify(msg, vim.log.levels.WARN, getdebuginfo()) +end + +---@param msg string +function log.error(msg) + return log.notify(msg, vim.log.levels.ERROR, getdebuginfo()) +end + +return log diff --git a/lua/lylla/statusline.lua b/lua/lylla/statusline.lua index 8c034ad..08930cc 100644 --- a/lua/lylla/statusline.lua +++ b/lua/lylla/statusline.lua @@ -1,3 +1,4 @@ +local log = require("lylla.log") local utils = require("lylla.utils") ---@class lylla.proto @@ -59,6 +60,15 @@ function statusline:close() statusline.wins[self.win] = nil 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 + end +end + ---@class lylla.proto ---@field refresh fun(self, ev?: vim.api.keyset.create_autocmd.callback_args) function statusline:refresh(ev) @@ -67,18 +77,22 @@ function statusline:refresh(ev) return end - self:set(ev) - self:setwinbar(ev) + refreshcomponent(self, statusline.set, ev) + refreshcomponent(self, statusline.setwinbar, ev) end) end ---@class lylla.proto ---@field fold fun(self, ev?: vim.api.keyset.create_autocmd.callback_args, modules: any[]): string function statusline:fold(ev, modules) + if type(modules) ~= "table" or modules == nil then + return "" + end + local lst = vim .iter(ipairs(modules)) :map(function(_, module) - if type(module) == "table" and module._type == "component" then + 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 @@ -89,23 +103,40 @@ function statusline:fold(ev, modules) return module.prev end end - module.prev = module.fn() + do + local ok, result = pcall(module.fn) + if not ok then + error(result) + end + module.prev = result + end return module.prev end if type(module) == "function" then - module = module() + local ok, result = pcall(module) + if not ok then + error(result) + end + return result end return module end) :totable() lst = utils.flatten(lst, 1) return vim.iter(lst):fold("", function(str, module) + if type(module) == "string" and #module > 0 then + return str .. module + end if type(module) ~= "table" or #module == 0 then return str end local text = module[1] - if #module > 1 then - return str .. "%#" .. module[2] .. "#" .. text .. "%*" + if text == nil or type(text) ~= "string" or #text == 0 then + return str + end + local hl = module[2] + if hl and type(hl) == "string" and #hl > 0 then + return str .. "%#" .. hl .. "#" .. text .. "%*" end return str .. "%*" .. text end) @@ -134,9 +165,7 @@ function statusline:setwinbar(ev) local ok, result = pcall(vim.api.nvim_win_call, self.win, function() return self:getwinbar(ev) end) - if not ok then - return - end + assert(ok, string.format("error occured while trying to evaluate winbar:\n\t%s", result)) vim.wo[self.win].winbar = result end @@ -147,9 +176,7 @@ function statusline:set(ev) local ok, result = pcall(vim.api.nvim_win_call, self.win, function() return self:get(ev) end) - if not ok then - return - end + assert(ok, string.format("error occured while trying to evaluate statusline:\n\t%s", result)) vim.wo[self.win].statusline = result end diff --git a/lua/lylla/utils.lua b/lua/lylla/utils.lua index 3b045dc..c5ad22d 100644 --- a/lua/lylla/utils.lua +++ b/lua/lylla/utils.lua @@ -111,17 +111,6 @@ function utils.get_searchcount() return { { string.format("/%s", term), "IncSearch" }, { " " }, { display, "MsgSeparator" } } end -function utils.get_fmt() - local formatters = require("mossy").get() - return vim.iter(ipairs(formatters)):fold("", function(str, i, formatter) - if i == 1 then - return formatter.name - end - - return str .. " 󰧟 " .. formatter.name - end) -end - ---@param mode string ---@return string function utils.get_modehl_name(mode)