diff options
Diffstat (limited to 'common/nvim/lua/plugins/heirline.lua')
| -rwxr-xr-x | common/nvim/lua/plugins/heirline.lua | 1497 |
1 files changed, 1497 insertions, 0 deletions
diff --git a/common/nvim/lua/plugins/heirline.lua b/common/nvim/lua/plugins/heirline.lua new file mode 100755 index 0000000..a4c2fc3 --- /dev/null +++ b/common/nvim/lua/plugins/heirline.lua @@ -0,0 +1,1497 @@ +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", "<A-" .. key .. ">", 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 |
