🪴 a tiny, customizable statusline for neovim
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat!: solid defaults and better error handling

robin 0e0a110a 2d41df65

+182 -118
+10 -6
.nvim.lua
··· 1 1 R = function(m, ...) 2 - require("plenary.reload").reload_module(m, ...) 3 - return require(m) 2 + require("plenary.reload").reload_module(m, ...) 3 + return require(m) 4 4 end 5 5 6 - vim.opt.rtp:prepend "." 6 + vim.opt.rtp:prepend(".") 7 7 8 8 -- 9 9 10 - pcall(function() 11 - R("lylla").init() 12 - end) 10 + vim.api.nvim_create_autocmd("UIEnter", { 11 + callback = function() 12 + pcall(function() 13 + R("lylla") 14 + end) 15 + end, 16 + })
+55 -88
lua/lylla/config.lua
··· 1 1 ---@module 'lylla.config' 2 2 3 + ---@class lylla.item 4 + ---@field fn fun(): any 5 + ---@field opts? { events?: string[] } 6 + 7 + ---@alias lylla.item.tuple {[1]: string, [2]?: string} 8 + 3 9 ---@class lylla.config 4 10 ---@field refresh_rate integer 5 11 ---@field events string[] 6 - ---@field prefix string 7 12 ---@field hls table<'normal'|'visual'|'command'|'insert', vim.api.keyset.highlight> 8 - ---@field modules any[] 13 + ---@field modules (lylla.item|lylla.item.tuple|string)[] 9 14 ---@field winbar any[] 10 15 11 - local utils = require("lylla.utils") 12 - 13 16 local M = {} 14 17 15 - ---@param fn fun(): string[] 16 - ---@param opts? { events: string[] } 17 - ---@return table 18 - local function component(fn, opts) 19 - local t = { _type = "component" } 20 - t.fn = fn 21 - t.opts = opts 22 - return t 23 - end 24 - 25 18 ---@type lylla.config 26 19 ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section) 27 20 ---@text # Default ~ ··· 40 33 "ModeChanged", 41 34 "CmdlineEnter", 42 35 }, 43 - prefix = "▌", 44 36 hls = {}, 45 37 modules = { 46 - component(function() 47 - local prefix = require("lylla.config").get().prefix 48 - local modehl = utils.get_modehl() 49 - return { 50 - { prefix, modehl }, 51 - { "[" .. vim.api.nvim_get_mode().mode .. "]", modehl }, 52 - } 53 - end, { 54 - events = { "ModeChanged", "CmdlineEnter" }, 55 - }), 38 + "%<%f %h%w%m%r", 39 + "%=", 40 + { 41 + fn = function() 42 + if vim.o.showcmdloc == "statusline" then 43 + return "%-10.S" 44 + end 45 + return "" 46 + end, 47 + }, 56 48 { " " }, 57 - component(function() 58 - return { 59 - utils.getfilepath(), 60 - utils.getfilename(), 61 - { " " }, 62 - } 63 - end, { 64 - events = { 65 - "WinEnter", 66 - "BufEnter", 67 - "BufWritePost", 68 - "FileChangedShellPost", 69 - "Filetype", 70 - }, 71 - }), 49 + { 50 + fn = function() 51 + if not vim.b.keymap_name then 52 + return "" 53 + end 54 + return "<" .. vim.b.keymap_name .. ">" 55 + end, 56 + }, 72 57 { " " }, 73 - component(function() 74 - return { utils.get_searchcount() } 75 - end), 76 - { "%=" }, 77 - {}, 78 - { "%=" }, 79 - component(function() 80 - return { 81 - { { "lsp :: " }, { utils.get_client() or "none" } }, 82 - { " | ", "NonText" }, 83 - { { "fmt :: " }, { utils.get_fmt() or "none" } }, 84 - { " | ", "NonText" }, 85 - } 86 - end, { events = { "FileType" } }), 87 - { "%p%%" }, 88 - { " | ", "NonText" }, 89 - { "%L lines" }, 90 - { " | ", "NonText" }, 91 - { "%l:%c" }, 58 + { 59 + fn = function() 60 + if vim.bo.busy > 0 then 61 + return "◐ " 62 + end 63 + return "" 64 + end, 65 + }, 92 66 { " " }, 93 - }, 94 - winbar = { 95 - component(function() 96 - local prefix = require("lylla.config").get().prefix 97 - local modehl = utils.get_modehl() 98 - return { 99 - { prefix, modehl }, 100 - } 101 - end, { 102 - events = { "ModeChanged", "CmdlineEnter" }, 103 - }), 104 - { " " }, 105 - component(function() 106 - return { 107 - utils.getfilepath(), 108 - utils.getfilename(), 109 - { " " }, 110 - } 111 - end, { 112 - events = { 113 - "WinEnter", 114 - "BufEnter", 115 - "BufWritePost", 116 - "FileChangedShellPost", 117 - "Filetype", 67 + { 68 + fn = function() 69 + if not package.loaded["vim.diagnostic"] then 70 + return "" 71 + end 72 + return vim.diagnostic.status() 73 + end, 74 + opts = { 75 + events = { "DiagnosticChanged" }, 118 76 }, 119 - }), 77 + }, 120 78 { " " }, 121 - component(function() 122 - return { utils.get_searchcount() } 123 - end), 79 + { 80 + fn = function() 81 + if not vim.o.ruler then 82 + return "" 83 + end 84 + if vim.o.rulerformat == "" then 85 + return "%-14.(%l,%c%V%) %P" 86 + end 87 + return vim.o.rulerformat 88 + end, 89 + }, 124 90 }, 91 + winbar = {}, 125 92 } 126 93 127 94 ---@type lylla.config
+7
lua/lylla/init.lua
··· 8 8 insert = { link = "MiniIconsGrey" }, 9 9 } 10 10 11 + ---@param cfg? lylla.config 12 + function M.setup(cfg) 13 + cfg = cfg or {} 14 + local config = require("lylla.config") 15 + config.set(config.override(cfg)) 16 + end 17 + 11 18 function M.inithls() 12 19 local utils = require("lylla.utils") 13 20 vim.iter(pairs(default_hls)):each(function(mode, defaulthl)
+70
lua/lylla/log.lua
··· 1 + ---@module 'lylla.log' 2 + 3 + local log = {} 4 + 5 + log.stack = {} 6 + 7 + local function getdebuginfo() 8 + local i = 3 9 + local info = debug.getinfo(i, "nSf") 10 + local nextinfo = debug.getinfo(i + 1, "n") 11 + while nextinfo and info.name == nil do 12 + info = nextinfo 13 + i = i + 1 14 + nextinfo = debug.getinfo(i + 1, "n") 15 + end 16 + return info 17 + end 18 + 19 + --- any message will not be displayed unless `'debug'` is set to `msg`. 20 + --- this is a purposeful design decisison made to avoid flooding the user with errors. 21 + --- this design decision also means that modules can silently fail. 22 + ---@param msg string 23 + ---@param level integer 24 + function log.notify(msg, level, debuginfo) 25 + debuginfo = debuginfo or getdebuginfo() 26 + local info = string.format( 27 + "%s %s at %s", 28 + #debuginfo.namewhat > 0 and debuginfo.namewhat or "chunk", 29 + debuginfo.name or "main", 30 + debuginfo.short_src or "main loop" 31 + ) 32 + log.stack[#log.stack + 1] = { level, info, msg } 33 + msg = string.format("in %s:\n\t%s", info, msg) 34 + if vim.o.debug == "" then 35 + return 36 + end 37 + if vim.o.debug == "throw" and level >= vim.log.levels.ERROR then 38 + error(msg, level) 39 + return 40 + end 41 + vim.notify_once(msg, level) 42 + return level 43 + end 44 + 45 + ---@param msg string 46 + function log.trace(msg) 47 + return log.notify(msg, vim.log.levels.TRACE, getdebuginfo()) 48 + end 49 + 50 + ---@param msg string 51 + function log.debug(msg) 52 + return log.notify(msg, vim.log.levels.DEBUG, getdebuginfo()) 53 + end 54 + 55 + ---@param msg string 56 + function log.info(msg) 57 + return log.notify(msg, vim.log.levels.INFO, getdebuginfo()) 58 + end 59 + 60 + ---@param msg string 61 + function log.warn(msg) 62 + return log.notify(msg, vim.log.levels.WARN, getdebuginfo()) 63 + end 64 + 65 + ---@param msg string 66 + function log.error(msg) 67 + return log.notify(msg, vim.log.levels.ERROR, getdebuginfo()) 68 + end 69 + 70 + return log
+40 -13
lua/lylla/statusline.lua
··· 1 + local log = require("lylla.log") 1 2 local utils = require("lylla.utils") 2 3 3 4 ---@class lylla.proto ··· 59 60 statusline.wins[self.win] = nil 60 61 end 61 62 63 + local function refreshcomponent(self, fn, ev) 64 + do 65 + local ok, result = pcall(fn, self, ev) 66 + if not ok then 67 + log.error("[lylla] error occured on refresh:\n\t" .. result) 68 + end 69 + end 70 + end 71 + 62 72 ---@class lylla.proto 63 73 ---@field refresh fun(self, ev?: vim.api.keyset.create_autocmd.callback_args) 64 74 function statusline:refresh(ev) ··· 67 77 return 68 78 end 69 79 70 - self:set(ev) 71 - self:setwinbar(ev) 80 + refreshcomponent(self, statusline.set, ev) 81 + refreshcomponent(self, statusline.setwinbar, ev) 72 82 end) 73 83 end 74 84 75 85 ---@class lylla.proto 76 86 ---@field fold fun(self, ev?: vim.api.keyset.create_autocmd.callback_args, modules: any[]): string 77 87 function statusline:fold(ev, modules) 88 + if type(modules) ~= "table" or modules == nil then 89 + return "" 90 + end 91 + 78 92 local lst = vim 79 93 .iter(ipairs(modules)) 80 94 :map(function(_, module) 81 - if type(module) == "table" and module._type == "component" then 95 + if type(module) == "table" and module.fn and type(module.fn) == "function" then 82 96 if module.opts and module.opts.events then 83 97 -- refresh from timer 84 98 if not ev and module.prev then ··· 89 103 return module.prev 90 104 end 91 105 end 92 - module.prev = module.fn() 106 + do 107 + local ok, result = pcall(module.fn) 108 + if not ok then 109 + error(result) 110 + end 111 + module.prev = result 112 + end 93 113 return module.prev 94 114 end 95 115 if type(module) == "function" then 96 - module = module() 116 + local ok, result = pcall(module) 117 + if not ok then 118 + error(result) 119 + end 120 + return result 97 121 end 98 122 return module 99 123 end) 100 124 :totable() 101 125 lst = utils.flatten(lst, 1) 102 126 return vim.iter(lst):fold("", function(str, module) 127 + if type(module) == "string" and #module > 0 then 128 + return str .. module 129 + end 103 130 if type(module) ~= "table" or #module == 0 then 104 131 return str 105 132 end 106 133 local text = module[1] 107 - if #module > 1 then 108 - return str .. "%#" .. module[2] .. "#" .. text .. "%*" 134 + if text == nil or type(text) ~= "string" or #text == 0 then 135 + return str 136 + end 137 + local hl = module[2] 138 + if hl and type(hl) == "string" and #hl > 0 then 139 + return str .. "%#" .. hl .. "#" .. text .. "%*" 109 140 end 110 141 return str .. "%*" .. text 111 142 end) ··· 134 165 local ok, result = pcall(vim.api.nvim_win_call, self.win, function() 135 166 return self:getwinbar(ev) 136 167 end) 137 - if not ok then 138 - return 139 - end 168 + assert(ok, string.format("error occured while trying to evaluate winbar:\n\t%s", result)) 140 169 141 170 vim.wo[self.win].winbar = result 142 171 end ··· 147 176 local ok, result = pcall(vim.api.nvim_win_call, self.win, function() 148 177 return self:get(ev) 149 178 end) 150 - if not ok then 151 - return 152 - end 179 + assert(ok, string.format("error occured while trying to evaluate statusline:\n\t%s", result)) 153 180 154 181 vim.wo[self.win].statusline = result 155 182 end
-11
lua/lylla/utils.lua
··· 111 111 return { { string.format("/%s", term), "IncSearch" }, { " " }, { display, "MsgSeparator" } } 112 112 end 113 113 114 - function utils.get_fmt() 115 - local formatters = require("mossy").get() 116 - return vim.iter(ipairs(formatters)):fold("", function(str, i, formatter) 117 - if i == 1 then 118 - return formatter.name 119 - end 120 - 121 - return str .. " 󰧟 " .. formatter.name 122 - end) 123 - end 124 - 125 114 ---@param mode string 126 115 ---@return string 127 116 function utils.get_modehl_name(mode)