local M = {} -- Safe require function to handle missing dependencies local function safe_require(module) local ok, result = pcall(require, module) return ok and result or nil end -- These will be initialized in M.setup() local heirline = nil local conditions = {} local utils = {} local colors = {} function M.setup() heirline = safe_require("heirline") if not heirline then return end -- Initialize conditions and utils after heirline is loaded conditions = require("heirline.conditions") or {} utils = require("heirline.utils") or {} -- Initialize colors after safe_fg is defined colors = { bg = "NONE", nobg = "NONE", white = "#f8f8f2", black = "#000000", darkgray = "#23232e", gray = "#2d2b3a", lightgray = "#d6d3ea", pink = "#f92672", green = "#50fa7b", blue = "#39BAE6", yellow = "#f1fa8c", orange = "#ffb86c", purple = "#BF40BF", violet = "#7F00FF", red = "#ff5555", cyan = "#66d9eC", --diag = { -- warn = safe_fg("DiagnosticSignWarn", "#ffb86c"), -- error = safe_fg("DiagnosticSignError", "#ff5555"), -- hint = safe_fg("DiagnosticSignHint", "#50fa7b"), -- info = safe_fg("DiagnosticSignInfo", "#66d9eC"), --}, diag = { warn = utils.get_highlight("DiagnosticSignWarn").fg, error = utils.get_highlight("DiagnosticSignError").fg, hint = utils.get_highlight("DiagnosticSignHint").fg, info = utils.get_highlight("DiagnosticSignInfo").fg, }, git = { active = "#f34f29", del = "#ff5555", add = "#50fa7b", change = "#ae81ff", }, } -- Only load colors if heirline is available if heirline.load_colors then local ok, err = pcall(heirline.load_colors, colors) if not ok then vim.notify("Failed to load Heirline colors: " .. tostring(err), vim.log.levels.ERROR) end end local function get_icon(icon, fallback) -- Check if we have Nerd Fonts available local has_nerd_fonts = vim.g.statusline_has_nerd_fonts if has_nerd_fonts == nil then -- Cache the result to avoid repeated checks if vim.fn.has('unix') == 1 and vim.fn.executable('fc-list') == 1 then local handle = io.popen('fc-list | grep -i nerd') local result = handle:read('*a') handle:close() has_nerd_fonts = result ~= "" else -- On non-Unix systems or if fc-list isn't available, assume no Nerd Fonts has_nerd_fonts = false end vim.g.statusline_has_nerd_fonts = has_nerd_fonts end -- Return the appropriate string based on font availability local result = has_nerd_fonts and icon or (fallback or '') -- Trim any whitespace to prevent layout issues return vim.trim(result) end -- Define all components after colors and utils are initialized --local Signs = { -- Error = "✘", -- Warn = "", -- Hint = "◉", -- Info = "", --} local Icons = { Signs = { Error = "✘", Warn = "", Hint = "◉", Info = "", LSP = get_icon("⚙️", "LSP"), }, ---- LSP/Debug Error = get_icon("✘", "E"), Warn = get_icon("", "W"), Hint = get_icon("◉", "H"),-- Info = get_icon("ℹ", "I"), --LSP = get_icon("⚙️", "LSP"), -- Diagnostic Diagnostic = { error = get_icon("✘", "E"), warn = get_icon("", "W"), hint = get_icon("", "H"), info = get_icon("ℹ", "I"), }, --local GitIcons = { -- added = "✚", -- plus in diff style -- modified = "", -- nf-oct-diff_modified -- removed = "", -- nf-oct-diff_removed --} --added = "", -- nf-oct-diff_added --modified = "", -- nf-oct-diff_modified --removed = "", -- nf-oct-diff_removed --local GitIcons = { -- added = "", -- nf-fa-plus_square -- modified = "", -- nf-fa-file_text_o -- removed = "", -- nf-fa-minus_square --} -- Git Git = { branch = get_icon(" ", "⎇ "), added = get_icon("+ ", "+"), removed = get_icon("- ", "-"), modified = get_icon("~ ", "~"), renamed = get_icon("", "r"), untracked = get_icon("", "?"), ignored = get_icon("", "."), }, -- UI Elements UI = { left_separator = get_icon("", ""), right_separator = get_icon("", ""), thin_separator = get_icon("▏", "|"), ellipsis = get_icon("…", "..."), arrow_left = get_icon("◀", "<"), arrow_right = get_icon("▶", ">"), close = get_icon("✕", "x"), big_close = get_icon(" ", "x "), modified = get_icon(" + ", "*"), readonly = get_icon("", "RO"), lock = get_icon("", "[L]"), clock = get_icon("🕒", "[TIME]"), buffer = get_icon("", "[BUF]"), tab = get_icon("", "[TAB]"), search = get_icon("🔍", "[SEARCH]"), spell = get_icon("暈", "[SPELL]"), whitespace = get_icon("␣", "[WS]"), newline = get_icon("↵", "[NL]"), indent = get_icon("▏", "|"), fold = get_icon("", ">"), fold_open = get_icon("", "v"), fold_closed = get_icon("", ">"), }, -- File types File = { default = get_icon("", "[F]"), directory = get_icon("", "[D]"), symlink = get_icon("", "[L]"), executable = get_icon("", "[X]"), image = get_icon("", "[IMG]"), archive = get_icon("", "[ARC]"), audio = get_icon("", "[AUD]"), video = get_icon("", "[VID]"), document = get_icon("", "[DOC]"), config = get_icon("", "[CFG]"), code = get_icon("", "[CODE]"), terminal = get_icon("", "[TERM]"), }, -- File format indicators Format = { unix = get_icon("", "[UNIX]"), dos = get_icon("", "[DOS]"), mac = get_icon("", "[MAC]"), }, -- Version control VCS = { branch = get_icon("", "[BR]"), git = get_icon("", "[GIT]"), github = get_icon("", "[GH]"), gitlab = get_icon("", "[GL]"), bitbucket = get_icon("", "[BB]"), }, -- Programming languages Lang = { lua = get_icon("", "[LUA]"), python = get_icon("", "[PY]"), javascript = get_icon("", "[JS]"), typescript = get_icon("", "[TS]"), html = get_icon("", "[HTML]"), css = get_icon("", "[CSS]"), json = get_icon("", "[JSON]"), markdown = get_icon("", "[MD]"), docker = get_icon("", "[DKR]"), rust = get_icon("", "[RS]"), go = get_icon("", "[GO]"), java = get_icon("", "[JAVA]"), c = get_icon("", "[C]"), cpp = get_icon("", "[C++]"), ruby = get_icon("", "[RB]"), php = get_icon("", "[PHP]"), haskell = get_icon("", "[HS]"), scala = get_icon("", "[SCALA]"), elixir = get_icon("", "[EXS]"), clojure = get_icon("", "[CLJ]"), }, -- UI Indicators Indicator = { error = get_icon("✘", "[E]"), warning = get_icon("⚠", "[W]"), info = get_icon("ℹ", "[I]"), hint = get_icon("", "[H]"), success = get_icon("✓", "[OK]"), question = get_icon("?", "[?]"), star = get_icon("★", "[*]"), heart = get_icon("❤", "<3"), lightning = get_icon("⚡", "[!]"), check = get_icon("✓", "[√]"), cross = get_icon("✗", "[x]"), plus = get_icon("+", "[+]"), recording = get_icon(" ", "q") }, -- File operations FileOp = { new = get_icon("", "[NEW]"), save = get_icon("💾", "[SAVE]"), open = get_icon("📂", "[OPEN]"), close = get_icon("✕", "[X]"), undo = get_icon("↩", "[UNDO]"), redo = get_icon("↪", "[REDO]"), cut = get_icon("✂", "[CUT]"), copy = get_icon("⎘", "[COPY]"), paste = get_icon("📋", "[PASTE]"), search = get_icon("🔍", "[FIND]"), replace = get_icon("🔄", "[REPLACE]"), }, -- Navigation Nav = { left = get_icon("←", "[<]"), right = get_icon("→", "[>]"), up = get_icon("↑", "[^]"), down = get_icon("↓", "[v]"), first = get_icon("⏮", "[<<]"), last = get_icon("⏭", "[>>]"), prev = get_icon("◀", "[<]"), next = get_icon("▶", "[>]"), back = get_icon("↩", "[B]"), forward = get_icon("↪", "[F]"), }, -- Editor states State = { insert = get_icon("", "[INS]"), normal = get_icon("🚀", "[NOR]"), visual = get_icon("👁", "[VIS]"), replace = get_icon("🔄", "[REP]"), command = get_icon("", "[CMD]"), terminal = get_icon("", "[TERM]"), select = get_icon("🔍", "[SEL]"), }, -- Common symbols Symbol = { dot = get_icon("•", "•"), bullet = get_icon("•", "•"), middle_dot = get_icon("·", "·"), ellipsis = get_icon("…", "..."), check = get_icon("✓", "[OK]"), cross = get_icon("✗", "[X]"), arrow_right = get_icon(" ", "->"), arrow_left = get_icon(" ", "<-"), double_arrow_right = get_icon("»", ">>"), double_arrow_left = get_icon("«", "<<"), chevron_right = get_icon("›", ">"), chevron_left = get_icon("‹", "<"), }, -- Document symbols DocSymbol = { class = get_icon("", "[C]"), function_icon = get_icon("", "[F]"), method = get_icon("", "[M]"), property = get_icon("", "[P]"), field = get_icon("ﰠ", "[F]"), constructor = get_icon("", "[C]"), enum = get_icon("", "[E]"), interface = get_icon("", "[I]"), variable = get_icon("", "[V]"), constant = get_icon("", "[C]"), string = get_icon("", "[S]"), number = get_icon("", "[N]"), boolean = get_icon("◩", "[B]"), array = get_icon("", "[A]"), object = get_icon("⦿", "[O]"), key = get_icon("🔑", "[K]"), null = get_icon("NULL", "Ø"), enum_member = get_icon("", "[E]"), struct = get_icon("פּ", "[S]"), event = get_icon("", "[E]"), operator = get_icon("", "[O]"), type_parameter = get_icon("", "[T]"), }, } local Align = { provider = "%=", hl = { bg = colors.bg } } local Space = { provider = " ", hl = { bg = colors.bg } } local Tab = { provider = " " } local LeftSpace = { provider = "" } local RightSpace = { provider = "" } local ViMode = { init = function(self) self.mode = vim.fn.mode(1) -- Store the initial mode self.prev_mode = self.mode -- Set up autocommand to force redraw on mode change vim.api.nvim_create_autocmd("ModeChanged", { pattern = "*:*", callback = function() -- Only redraw if the mode actually changed local current_mode = vim.fn.mode(1) if current_mode ~= self.prev_mode then self.prev_mode = current_mode vim.schedule(function() vim.cmd("redrawstatus") end) end end, }) end, static = { mode_names = { n = " NORMAL ", no = "PENDING ", nov = " N? ", noV = " N? ", ["no\22"] = " N? ", niI = " Ni ", niR = " Nr ", niV = " Nv ", nt = "TERMINAL", v = " VISUAL ", vs = " Vs ", V = " V·LINE ", ["\22"] = "V·BLOCK ", ["\22s"] = "V·BLOCK ", s = " SELECT ", S = " S·LINE ", ["\19"] = "S·BLOCK ", i = " INSERT ", ix = "insert x", ic = "insert c", R = "REPLACE ", Rc = " Rc ", Rx = " Rx ", Rv = "V·REPLACE ", Rvc = " Rv ", Rvx = " Rv ", c = "COMMAND ", cv = " VIM EX ", ce = " EX ", r = " PROMPT ", rm = " MORE ", ["r?"] = "CONFIRM ", ["!"] = " SHELL ", t = "TERMINAL", }, }, provider = function(self) return " %2(" .. self.mode_names[self.mode] .. "%) " end, hl = function(self) return { fg = "colors.black", bg = self.mode_color, bold = true } end, update = { "ModeChanged", "VimEnter", "BufEnter", "WinEnter", "TabEnter", pattern = "*:*", callback = vim.schedule_wrap(function() vim.cmd("redrawstatus") end), }, } -- LSP local LSPActive = { condition = function() local ok, _ = pcall(function() local buf = vim.api.nvim_get_current_buf() return #vim.lsp.get_clients({ bufnr = buf }) > 0 end) return ok or false end, update = { "LspAttach", "LspDetach", "BufEnter" }, provider = function() local ok, result = pcall(function() local buf = vim.api.nvim_get_current_buf() if not vim.api.nvim_buf_is_valid(buf) then return "" end local clients = vim.lsp.get_clients({ bufnr = buf }) if not clients or #clients == 0 then return "" end local client_names = {} for _, client in ipairs(clients) do if client and client.name and client.name ~= "null-ls" then table.insert(client_names, client.name) end end if #client_names > 0 then return Icons.Signs.LSP .. " " .. table.concat(client_names, "/") .. " " end return "" end) if not ok then vim.schedule(function() vim.notify_once("Error in LSPActive provider: " .. tostring(result), vim.log.levels.DEBUG) end) return "" end return result or "" end, hl = { fg = "lightgray", bold = false }, } local Navic = { condition = function() local ok, navic = pcall(require, "nvim-navic") return ok and navic.is_available() end, static = { type_hl = { File = "Directory", Module = "@include", Namespace = "@namespace", Package = "@include", Class = "@structure", Method = "@method", Property = "@property", Field = "@field", Constructor = "@constructor", Enum = "@field", Interface = "@type", Function = "@function", Variable = "@variable", Constant = "@constant", String = "@string", Number = "@number", Boolean = "@boolean", Array = "@field", Object = "@type", Key = "@keyword", Null = "@comment", EnumMember = "@field", Struct = "@structure", Event = "@keyword", Operator = "@operator", TypeParameter = "@type", }, enc = function(line, col, winnr) return bit.bor(bit.lshift(line, 16), bit.lshift(col, 6), winnr) end, dec = function(c) local line = bit.rshift(c, 16) local col = bit.band(bit.rshift(c, 6), 1023) local winnr = bit.band(c, 63) return line, col, winnr end, }, init = function(self) local data = require("nvim-navic").get_data() or {} local children = {} for i, d in ipairs(data) do local pos = self.enc(d.scope.start.line, d.scope.start.character, self.winnr) local child = { { provider = d.icon, hl = self.type_hl[d.type], }, { provider = d.name:gsub("%%", "%%%%"):gsub("%s*->%s*", ""), on_click = { minwid = pos, callback = function(_, minwid) local line, col, winnr = self.dec(minwid) vim.api.nvim_win_set_cursor(vim.fn.win_getid(winnr), { line, col }) end, name = "heirline_navic", }, }, } if #data > 1 and i < #data then table.insert(child, { provider = " > ", hl = { fg = "bright_fg" }, }) end table.insert(children, child) end self.child = self:new(children, 1) end, provider = function(self) return self.child:eval() end, hl = { fg = "gray" }, update = "CursorMoved", } -- Diagnostics local Diagnostics = { condition = conditions.has_diagnostics, static = { error_icon = Icons.Error, warn_icon = Icons.Warn, info_icon = Icons.Info, hint_icon = Icons.Hint, }, init = function(self) self.errors = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.ERROR }) self.warnings = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.WARN }) self.hints = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.HINT }) self.info = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.INFO }) end, update = { "DiagnosticChanged", "BufEnter" }, { provider = function(self) return self.errors > 0 and (self.error_icon .. " " .. self.errors .. " ") end, hl = { fg = colors.diag.error, bg = colors.bg }, }, { provider = function(self) return self.warnings > 0 and (self.warn_icon .. " " .. self.warnings .. " ") end, hl = { fg = colors.diag.warn, bg = colors.bg }, }, { provider = function(self) return self.info > 0 and (self.info_icon .. " " .. self.info .. " ") end, hl = { fg = colors.diag.info, bg = colors.bg }, }, { provider = function(self) return self.hints > 0 and (self.hint_icon .. " " .. self.hints .. " ") end, hl = { fg = colors.diag.hint, bg = colors.bg }, }, on_click = { callback = function() local ok, _ = pcall(require, "trouble") if ok then require("trouble").toggle({ mode = "document_diagnostics" }) else vim.diagnostic.setqflist() end end, name = "heirline_diagnostics", }, } -- Git local Git = { condition = conditions.is_git_repo, init = function(self) self.status_dict = vim.b.gitsigns_status_dict or {} self.has_changes = (self.status_dict.added or 0) ~= 0 or (self.status_dict.removed or 0) ~= 0 or (self.status_dict.changed or 0) ~= 0 end, { provider = function() return " " .. Icons.Git.branch .. " " end, hl = { fg = colors.git.active, bg = colors.bg }, }, { provider = function(self) return self.status_dict.head or "" end, hl = { fg = colors.white, bg = colors.bg }, }, { condition = function(self) return self.has_changes end, provider = "", }, { provider = function(self) local count = self.status_dict.added or 0 return count > 0 and (" " .. Icons.Git.added .. count) or "" end, hl = { fg = colors.git.add, bg = colors.bg }, }, { provider = function(self) local count = self.status_dict.removed or 0 return count > 0 and (" " .. Icons.Git.removed .. count) or "" end, hl = { fg = colors.git.del, bg = colors.bg }, }, { provider = function(self) local count = self.status_dict.changed or 0 return count > 0 and (" " .. Icons.Git.modified .. count) or "" end, hl = { fg = colors.git.change, bg = colors.bg }, }, on_click = { callback = function() vim.defer_fn(function() vim.cmd("Lazygit") end, 100) end, name = "heirline_git", }, } -- FileNameBlock: FileIcon, FileName and friends local FileNameBlock = { init = function(self) self.filename = vim.api.nvim_buf_get_name(0) end, hl = { bg = colors.bg }, } local FileIcon = { init = function(self) local filename = self.filename or vim.api.nvim_buf_get_name(0) local extension = vim.fn.fnamemodify(filename, ":e") local has_nerd_fonts = vim.g.statusline_has_nerd_fonts if has_nerd_fonts == nil then if vim.fn.has('unix') == 1 and vim.fn.executable('fc-list') == 1 then local handle = io.popen('fc-list | grep -i nerd') local result = handle:read('*a') handle:close() has_nerd_fonts = result ~= "" else has_nerd_fonts = false end vim.g.statusline_has_nerd_fonts = has_nerd_fonts end local icon, icon_color if has_nerd_fonts then icon, icon_color = require("nvim-web-devicons").get_icon_color(filename, extension, { default = true }) end if vim.fn.isdirectory(filename) == 1 then self.icon = has_nerd_fonts and Icons.File.directory or "[DIR]" self.icon_color = colors.blue else if has_nerd_fonts and icon then self.icon = icon .. " " self.icon_color = icon_color or colors.blue else if extension == "" then self.icon = Icons.File.default else local file_icon = Icons.File[extension:lower()] or Icons.File.default if type(file_icon) == "table" then self.icon = file_icon[1] or Icons.File.default else self.icon = file_icon end end self.icon_color = colors.blue end end end, provider = function(self) return self.icon end, hl = function(self) return { fg = self.icon_color, bold = true } end, } local FileName = { provider = function(self) local filename = vim.fn.fnamemodify(self.filename, ":.") if filename == "" then return "No Name" end if not conditions.width_percent_below(#filename, 0.25) then filename = vim.fn.pathshorten(filename) end return filename end, hl = { fg = colors.white, bold = false, bg = colors.bg }, } local FileFlags = { { provider = function() if vim.bo.modified then return " +" end end, hl = { fg = colors.green, bg = colors.bg }, }, { provider = function() if not vim.bo.modifiable or vim.bo.readonly then return " " .. Icons.UI.lock end end, hl = { fg = colors.orange, bold = true, bg = colors.bg }, }, } local FileNameModifier = { hl = function() if vim.bo.modified then return { fg = colors.green, bold = false, force = true } end end, } -- FileType, FileEncoding and FileFormat local FileType = { provider = function() return vim.bo.filetype end, hl = { fg = colors.white, bold = false, bg = colors.bg }, } local FileEncoding = { Space, provider = function() local enc = (vim.bo.fenc ~= "" and vim.bo.fenc) or vim.o.enc return enc:lower() end, hl = { bg = colors.bg, bold = false }, } local FileFormat = { provider = function() local fmt = vim.bo.fileformat return fmt ~= "unix" and fmt:lower() or "" end, hl = { fg = utils.get_highlight("Statusline").fg, bold = true, bg = colors.bg }, } local FileSize = { provider = function() local suffix = { "b", "k", "M", "G", "T", "P", "E" } local filename = vim.api.nvim_buf_get_name(0) local fsize = vim.fn.getfsize(filename) fsize = (fsize < 0 and 0) or fsize if fsize < 1024 then return fsize .. suffix[1] end local i = math.floor((math.log(fsize) / math.log(1024))) return string.format("%.2g%s", fsize / math.pow(1024, i), suffix[i + 1]) end, hl = { fg = utils.get_highlight("Statusline").fg, bold = true, bg = colors.bg }, } local FileLastModified = { provider = function() local filename = vim.api.nvim_buf_get_name(0) local ftime = vim.fn.getftime(filename) return (ftime > 0) and os.date("%c", ftime) or "" end, hl = { fg = utils.get_highlight("Statusline").fg, bold = true, bg = colors.bg }, } local Spell = { condition = function() return vim.wo.spell end, provider = function() return " " .. Icons.Indicator.spell .. " " end, hl = { bold = true, fg = colors.yellow }, } local HelpFileName = { condition = function() return vim.bo.filetype == "help" end, provider = function() local filename = vim.api.nvim_buf_get_name(0) return vim.fn.fnamemodify(filename, ":t") end, hl = { fg = colors.blue }, } local SearchCount = { condition = function() return vim.v.hlsearch ~= 0 and vim.o.cmdheight == 0 end, init = function(self) local ok, search = pcall(vim.fn.searchcount, { recompute = 1, maxcount = -1 }) if ok and search.total then self.search = search end end, provider = function(self) local search = self.search or { current = 0, total = 0, maxcount = 0 } return string.format("[%d/%d]", search.current, math.min(search.total, search.maxcount)) end, update = { "CursorMoved", "CursorMovedI", "SearchWrapped" }, } local MacroRec = { condition = function() return vim.fn.reg_recording() ~= "" and vim.o.cmdheight == 0 end, provider = function() return Icons.Indicator.recording .. " " end, hl = { fg = "orange", bold = true }, utils.surround({ "[", "]" }, nil, { provider = function() return vim.fn.reg_recording() end, hl = { fg = "green", bold = true }, }), update = { "RecordingEnter", "RecordingLeave", callback = vim.schedule_wrap(function() vim.cmd("redrawstatus") end), }, } local ShowCmd = { condition = function() return vim.o.cmdheight == 0 end, provider = ":%3.5(%S%)", update = { "CmdlineChanged" }, } local cursor_location = { { provider = "%1(%4l:%-3(%c%)%) %*", hl = { fg = colors.black, bold = true } }, } local Ruler = { cursor_location } local WordCount = { condition = function() return conditions.buffer_matches({ filetype = { "markdown", "txt", "vimwiki", }, }) end, Space, { provider = function() local ok, wordcount = pcall(vim.fn.wordcount) return ok and wordcount.words and ("W:%d"):format(wordcount.words) or "" end, update = { "CursorMoved", "CursorMovedI", "InsertEnter", "TextChanged", "TextChangedI" }, }, } local WorkDir = { init = function(self) local is_local = vim.fn.haslocaldir(0) == 1 self.icon = (is_local and "l" or "g") .. " " .. Icons.File.directory local cwd = vim.fn.getcwd(0) self.cwd = vim.fn.fnamemodify(cwd, ":~") end, hl = { fg = colors.blue, bold = true }, on_click = { callback = function() vim.cmd("Telescope find_files cwd=" .. vim.fn.getcwd(0)) end, name = "heirline_workdir", }, flexible = 1, { provider = function(self) local trail = self.cwd:sub(-1) == "/" and "" or "/" return self.icon .. " " .. self.cwd .. trail .. " " end, }, { provider = function(self) local cwd = vim.fn.pathshorten(self.cwd) local trail = self.cwd:sub(-1) == "/" and "" or "/" return self.icon .. " " .. cwd .. trail .. " " end, }, { provider = function(self) return self.icon .. " " end, }, } -- Build FileNameBlock FileNameBlock = utils.insert( FileNameBlock, FileIcon, utils.insert(FileNameModifier, FileName), unpack(FileFlags), { provider = "%<" } ) local FileInfoBlock = { init = function(self) self.filename = vim.api.nvim_buf_get_name(0) end, } FileInfoBlock = utils.insert( FileInfoBlock, Space, FileIcon, FileType, { provider = "%<" } ) -- Create surrounded components with proper mode color functions LeftSpace = utils.surround({ "", Icons.UI.right_separator }, function(self) return self:mode_color() end, { LeftSpace, hl = { fg = utils.get_highlight("statusline").bg, force = true } }) RightSpace = utils.surround({ Icons.UI.left_separator, "" }, function(self) return self:mode_color() end, { RightSpace, hl = { fg = utils.get_highlight("statusline").bg, force = true } }) LSPActive = utils.surround({ "", "" }, function(self) return self:mode_color() end, { Space, LSPActive, hl = { bg = colors.darkgray, force = true } }) FileInfoBlock = utils.surround({ "", "" }, function(self) return self:mode_color() end, { FileInfoBlock, Space, hl = { bg = colors.black, force = true } }) ViMode = utils.surround({ "", "" }, function(self) return self:mode_color() end, { ViMode, hl = { fg = colors.black, force = true } }) Ruler = utils.surround({ "", "" }, function(self) return self:mode_color() end, { Ruler, hl = { fg = colors.black, force = true } }) -- Statusline sections - FIXED: Removed duplicate LeftSpace from right section local left = { { RightSpace, hl = { bg = colors.nobg, force = true } }, { ViMode, hl = { bg = utils.get_highlight("statusline").bg, bold = false } }, { LeftSpace, hl = { bg = colors.nobg, force = true } }, { Space, hl = { bg = colors.nobg, force = true } }, { FileNameBlock, hl = { bg = colors.nobg, force = true } }, { Space, hl = { bg = colors.nobg, force = true } }, { Git, hl = { bg = colors.nobg, force = true } }, } local middle = { { Align, hl = { bg = colors.nobg, force = true } }, { Align, hl = { bg = colors.nobg, force = true } }, } -- FIXED: Right section now has proper sequence without duplicate LeftSpace local right = { { Diagnostics, hl = { bg = colors.nobg, force = true } }, { Space, hl = { bg = colors.nobg, force = true } }, { LSPActive, hl = { bg = colors.nobg, force = true } }, { Space, hl = { bg = colors.nobg, force = true } }, { FileInfoBlock, hl = { bg = colors.nobg, force = true } }, { RightSpace, hl = { bg = colors.nobg, force = true } }, { Ruler, hl = { fg = utils.get_highlight("statusline").bg, bold = false } }, { LeftSpace, hl = { bg = colors.nobg, force = true } }, } local sections = { left, middle, right } local DefaultStatusline = { sections } -- Special statuslines for inactive/special buffers local specialleft = { { RightSpace, hl = { bg = colors.nobg, force = true } }, { ViMode, hl = { bg = utils.get_highlight("statusline").bg, bold = false } }, { LeftSpace, hl = { bg = colors.nobg, force = true } }, } local specialmiddle = { { Align, hl = { bg = colors.nobg, force = true } }, { Align, hl = { bg = colors.nobg, force = true } }, } local specialright = { { RightSpace, hl = { bg = colors.nobg, force = true } }, { Ruler, hl = { fg = utils.get_highlight("statusline").bg, bold = false } }, { LeftSpace, hl = { bg = colors.nobg, force = true } }, } local specialsections = { specialleft, specialmiddle, specialright } local InactiveStatusline = { condition = conditions.is_not_active, specialsections, } local SpecialStatusline = { condition = function() return conditions.buffer_matches({ buftype = { "nofile", "prompt", "help", "quickfix" }, filetype = { "^git.*", "fugitive", "dashboard" }, }) end, specialsections, } local TerminalStatusline = { condition = function() return conditions.buffer_matches({ buftype = { "terminal" } }) end, specialsections, } -- FIXED: Main StatusLine with better mode handling local StatusLine = { static = { mode_colors = { n = colors.blue, no = colors.blue, nov = colors.blue, noV = colors.blue, ["no\22"] = colors.blue, niI = colors.blue, niR = colors.blue, niV = colors.blue, nt = colors.blue, v = colors.purple, vs = colors.purple, V = colors.purple, ["\22"] = colors.purple, ["\22s"] = colors.purple, s = colors.purple, S = colors.purple, ["\19"] = colors.purple, i = colors.green, ix = colors.green, ic = colors.green, R = colors.red, Rc = colors.red, Rx = colors.red, Rv = colors.red, Rvc = colors.red, Rvx = colors.red, c = colors.orange, cv = colors.orange, ce = colors.orange, r = colors.red, rm = colors.red, ["r?"] = colors.red, ["!"] = colors.orange, t = colors.orange, }, mode_color = function(self) -- FIXED: Always get current mode to ensure updates local mode = vim.fn.mode() return self.mode_colors[mode] or colors.blue end, }, -- FIXED: Add update triggers to ensure statusline refreshes properly update = { "ModeChanged", "BufEnter", "WinEnter", "WinLeave", "BufWinEnter", "CmdlineLeave", callback = vim.schedule_wrap(function() vim.cmd("redrawstatus") end), }, fallthrough = false, SpecialStatusline, TerminalStatusline, InactiveStatusline, DefaultStatusline, } -- WinBar components local WinbarFileNameBlock = { init = function(self) self.filename = vim.api.nvim_buf_get_name(0) end, hl = { bg = colors.bg }, } local WinbarFileName = { provider = function(self) local filename = vim.fn.fnamemodify(self.filename, ":.") if filename == "" then return "No Name" end if not conditions.width_percent_below(#filename, 0.25) then filename = vim.fn.pathshorten(filename) end return filename end, hl = { fg = colors.gray, bold = false, bg = colors.bg }, } WinbarFileNameBlock = utils.insert( WinbarFileNameBlock, FileIcon, utils.insert(WinbarFileName), unpack(FileFlags), { provider = "%<" } ) vim.api.nvim_create_autocmd("User", { pattern = "HeirlineInitWinbar", callback = function(args) local buf = args.buf local buftype = vim.tbl_contains({ "prompt", "nofile", "help", "quickfix" }, vim.bo[buf].buftype) local filetype = vim.tbl_contains({ "gitcommit", "fugitive" }, vim.bo[buf].filetype) if buftype or filetype then vim.opt_local.winbar = nil end end, }) local On_click = { minwid = function() return vim.api.nvim_get_current_win() end, callback = function(_, minwid) local winid = minwid local buf = vim.api.nvim_win_get_buf(winid) end, } local CloseButton = { condition = function(self) return not vim.bo.modified end, update = { "WinNew", "WinClosed", "BufEnter" }, { provider = " " }, { provider = Icons.UI.close, hl = { fg = "gray" }, On_click = { minwid = function() return vim.api.nvim_get_current_win() end, callback = function(_, minwid) vim.api.nvim_win_close(minwid, true) end, name = "heirline_winbar_close_button", }, }, } local Center = { fallthrough = false, { condition = function() return conditions.buffer_matches({ buftype = { "terminal", "nofile", "prompt", "help", "quickfix" }, filetype = { "dap-ui", "NvimTree", "^git.*", "fugitive", "dashboard" }, }) end, init = function() vim.opt_local.winbar = nil end, }, { condition = function() return conditions.buffer_matches({ buftype = { "terminal" } }) end, FileType, Space, }, { condition = function() return not conditions.is_active() end, utils.surround({ "", "" }, colors.nobg, { WinbarFileNameBlock }), }, utils.surround({ "", "" }, colors.nobg, { FileNameBlock }), } local WinBar = { Space, Center } -- Tabline components local TablineFileIcon = { init = function(self) local filename = self.filename local extension = vim.fn.fnamemodify(filename, ":e") local has_nerd_fonts = vim.g.statusline_has_nerd_fonts if has_nerd_fonts == nil then if vim.fn.has('unix') == 1 and vim.fn.executable('fc-list') == 1 then local handle = io.popen('fc-list | grep -i nerd') local result = handle:read('*a') handle:close() has_nerd_fonts = result ~= "" else has_nerd_fonts = false end vim.g.statusline_has_nerd_fonts = has_nerd_fonts end if has_nerd_fonts then self.icon, self.icon_color = require("nvim-web-devicons").get_icon_color(filename, extension, { default = true }) else self.icon = "" self.icon_color = colors.blue if vim.fn.isdirectory(filename) == 1 then self.icon = "[DIR]" else local file_icon = Icons.File[extension:lower()] or Icons.File.default if type(file_icon) == "table" then self.icon = file_icon[1] or Icons.File.default else self.icon = file_icon end end end if self.icon ~= "" then self.icon = self.icon .. " " end end, provider = function(self) return self.icon or "" end, hl = function(self) return { fg = self.icon_color or colors.blue } end, } local TablineFileName = { provider = function(self) local filename = vim.fn.fnamemodify(self.filename, ":t") if filename == "" then return "[No Name]" end return filename end, } local TablineFileFlags = { { condition = function(self) return vim.api.nvim_buf_get_option(self.bufnr, "modified") end, provider = "%X " .. Icons.Indicator.plus .. " %X", hl = { fg = "green" }, }, { condition = function(self) return not vim.api.nvim_buf_get_option(self.bufnr, "modifiable") or vim.api.nvim_buf_get_option(self.bufnr, "readonly") end, provider = function() if vim.bo.readonly then return " " .. Icons.UI.lock end return "" end, hl = { fg = "orange" }, }, } local TablineFileNameBlock = { init = function(self) self.filename = vim.api.nvim_buf_get_name(self.bufnr) end, hl = function(self) if self.is_active then return "TabLineSel" else return "TabLineFill" end end, on_click = { callback = function(_, minwid, _, button) if button == "m" then vim.api.nvim_buf_delete(minwid, { force = false }) else vim.api.nvim_win_set_buf(0, minwid) end end, minwid = function(self) return self.bufnr end, name = "heirline_tabline_buffer_callback", }, TablineFileIcon, TablineFileName, TablineFileFlags, } local TablineCloseButton = { condition = function(self) return not vim.api.nvim_buf_get_option(self.bufnr, "modified") end, { provider = " " }, { provider = "" .. Icons.UI.close .. " %X", hl = { fg = colors.red }, on_click = { callback = function(_, minwid) vim.api.nvim_buf_delete(minwid, { force = false }) end, minwid = function(self) return self.bufnr end, name = "heirline_tabline_close_buffer_callback", }, }, } local TablineBufferBlock = utils.surround({ "", "" }, function(self) if self.is_active then return utils.get_highlight("TabLineSel").bg else return utils.get_highlight("TabLineFill").bg end end, { Tab, TablineFileNameBlock, TablineCloseButton }) local BufferLine = utils.make_buflist( TablineBufferBlock, { provider = Icons.Symbol.arrow_left, hl = { fg = colors.gray } }, { provider = Icons.Symbol.arrow_right, hl = { fg = colors.gray } } ) local Tabpage = { provider = function(self) return "%" .. self.tabnr .. "T " .. self.tabnr .. " %T" end, hl = function(self) return self.is_active and "TabLineSel" or "TabLineFill" end, } local TabpageClose = { provider = "%999X " .. Icons.UI.close .. " %X", hl = { fg = colors.red, bg = colors.bg }, } local TabPages = { condition = function() return #vim.api.nvim_list_tabpages() >= 2 end, { provider = "%=" }, utils.make_tablist(Tabpage), TabpageClose, } local TabLineOffset = { condition = function(self) local win = vim.api.nvim_tabpage_list_wins(0)[1] local bufnr = vim.api.nvim_win_get_buf(win) self.winid = win if vim.api.nvim_buf_get_option(bufnr, "filetype") == "NvimTree" then self.title = "NvimTree" return true end end, provider = function(self) local title = self.title local width = vim.api.nvim_win_get_width(self.winid) local pad = math.ceil((width - #title) / 2) return string.rep(" ", pad) .. title .. string.rep(" ", pad) end, hl = { fg = colors.white, bg = "#333842", bold = true }, } local TabLine = { TabLineOffset, BufferLine, TabPages, } -- Buffer navigation functions local function get_bufs() return vim.tbl_filter(function(bufnr) return vim.api.nvim_buf_is_loaded(bufnr) and vim.bo[bufnr].buflisted end, vim.api.nvim_list_bufs()) end local function goto_buf(index) local bufs = get_bufs() if index > #bufs then index = #bufs end vim.api.nvim_win_set_buf(0, bufs[index]) end local function add_key(key, index) vim.keymap.set("n", "", function() goto_buf(index) end, { noremap = true, silent = true }) end for i = 1, 9 do add_key(i, i) end add_key("0", 10) vim.o.showtabline = 2 vim.cmd([[au FileType * if index(['wipe', 'delete', 'unload'], &bufhidden) >= 0 | set nobuflisted | endif]]) -- FIXED: Add proper autocmds for better statusline updates local augroup = vim.api.nvim_create_augroup("HeirlineStatusline", { clear = true }) -- Force statusline refresh on mode changes and buffer events vim.api.nvim_create_autocmd({ "ModeChanged", "BufEnter", "BufWinEnter", "WinEnter", "WinLeave", "CmdlineLeave", "TermEnter", "TermLeave" }, { group = augroup, callback = function() vim.schedule(function() if vim.o.laststatus > 0 then vim.cmd("redrawstatus!") end end) end, }) -- Final heirline setup heirline.setup({ statusline = StatusLine, winbar = WinBar, tabline = TabLine, opts = { disable_winbar_cb = function(args) local buf = args.buf if not vim.api.nvim_buf_is_valid(buf) then return true end local buftype = vim.tbl_contains( { "prompt", "nofile", "help", "quickfix" }, vim.bo[buf].buftype ) local filetype = vim.tbl_contains( { "gitcommit", "fugitive" }, vim.bo[buf].filetype ) return buftype or filetype end, } }) end return M