return { 'rebelot/heirline.nvim', dependencies = { 'folke/tokyonight.nvim', 'lewis6991/gitsigns.nvim', }, config = function() ---@diagnostic disable-next-line: missing-fields local colors = require('tokyonight.colors').setup({ style = 'night', }) local conditions = require('heirline.conditions') local utils = require('heirline.utils') vim.o.laststatus = 3 vim.o.showtabline = 2 local Align = { provider = '%=' } local Space = { provider = ' ' } local SmallSpace = { provider = ' ' } local ViMode = { -- get vim current mode, this information will be required by the provider -- and the highlight functions, so we compute it only once per component -- evaluation and store it as a component attribute init = function(self) self.mode = vim.fn.mode(1) -- :h mode() end, -- Now we define some dictionaries to map the output of mode() to the -- corresponding string and color. We can put these into `static` to compute -- them at initialisation time. static = { mode_names = { -- change the strings if you like it vvvvverbose! ['n'] = 'NORMAL ', ['no'] = 'N·OPERATOR PENDING ', ['v'] = 'VISUAL ', ['vs'] = 'VISUAL·S ', ['V'] = 'V·LINE ', ['Vs'] = 'V·LINE·S ', [''] = 'V·BLOCK ', ['s'] = 'SELECT ', ['S'] = 'S·LINE ', [''] = 'S·BLOCK ', ['i'] = 'INSERT ', ['ic'] = 'COMPLETION ', ['niI'] = 'INSERT ', ['niR'] = 'REPLACE ', ['niV'] = 'V·REPLACE ', ['R'] = 'REPLACE ', ['Rv'] = 'V·REPLACE ', ['c'] = 'COMMAND ', ['cv'] = 'VIM EX ', ['ce'] = 'EX ', ['r'] = 'PROMPT ', ['rm'] = 'MORE ', ['r?'] = 'CONFIRM ', ['!'] = 'SHELL ', ['t'] = 'TERMINAL ', }, mode_colors = { n = colors.green, i = colors.magenta, v = colors.blue, V = colors.blue, [''] = colors.blue, c = colors.red, s = colors.purple, S = colors.purple, [''] = colors.purple, R = colors.orange, r = colors.orange, ['!'] = colors.red, t = colors.red, }, }, -- We can now access the value of mode() that, by now, would have been -- computed by `init()` and use it to index our strings dictionary. -- note how `static` fields become just regular attributes once the -- component is instantiated. -- To be extra meticulous, we can also add some vim statusline syntax to -- control the padding and make sure our string is always at least 2 -- characters long. Plus a nice Icon. provider = function(self) if self.mode == nil then return '' end -- return ' %2(' .. self.mode .. '%)' return ' %2(' .. self.mode_names[self.mode] .. '%)' end, -- Same goes for the highlight. Now the foreground will change according to the current mode. hl = function(self) local mode = self.mode:sub(1, 1) -- get only the first mode character return { bg = self.mode_colors[mode], fg = colors.bg, bold = true } end, } local FileNameBlock = { -- let's first set up some attributes needed by this component and it's children init = function(self) self.filename = vim.api.nvim_buf_get_name(0) end, } local FileIcon = { init = function(self) local filename = self.filename local extension = vim.fn.fnamemodify(filename, ':e') self.icon, self.icon_color = require('nvim-web-devicons').get_icon_color(filename, extension, { default = true }) end, provider = function(self) return self.icon and (self.icon .. ' ') end, hl = function(self) return { fg = self.icon_color } end, } local FileName = { provider = function(self) -- first, trim the pattern relative to the current directory. For other -- options, see :h filename-modifers local filename = vim.fn.fnamemodify(self.filename, ':.') if filename == '' then return '[No Name]' end -- now, if the filename would occupy more than 1/4th of the available -- space, we trim the file path to its initials -- See Flexible Components section below for dynamic truncation if not conditions.width_percent_below(#filename, 0.25) then filename = vim.fn.pathshorten(filename) end return filename end, hl = { fg = utils.get_highlight('Directory').fg }, } local FileFlags = { { provider = function() if vim.bo.modified then return ' [+]' end end, hl = { fg = colors.green }, }, { provider = function() if (not vim.bo.modifiable) or vim.bo.readonly then return '' end end, hl = { fg = colors.orange }, }, } -- Change highlight when file has changes local FileNameModifer = { hl = function() if vim.bo.modified then -- use `force` because we need to override the child's hl foreground return { fg = colors.cyan, bold = true, force = true } end end, } local diag_signs = vim.diagnostic.config().signs.text or {} local Diagnostics = { condition = conditions.has_diagnostics, static = { error_icon = diag_signs[1], warn_icon = diag_signs[2], info_icon = diag_signs[3], hint_icon = diag_signs[4], }, 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, { provider = function(self) -- 0 is just another output, we can decide to print it or not! return self.errors > 0 and (self.error_icon .. self.errors .. ' ') end, hl = { fg = colors.error }, }, { provider = function(self) return self.warnings > 0 and (self.warn_icon .. self.warnings .. ' ') end, hl = { fg = colors.warning }, }, { provider = function(self) return self.info > 0 and (self.info_icon .. self.info .. ' ') end, hl = { fg = colors.info }, }, { provider = function(self) return self.hints > 0 and (self.hint_icon .. self.hints) end, hl = { fg = colors.hint }, }, } -- let's add the children to our FileNameBlock component FileNameBlock = utils.insert(FileNameBlock, FileIcon, utils.insert(FileNameModifer, FileName), unpack(FileFlags), { provider = '%<' }) local Git = { condition = conditions.is_git_repo, init = function(self) self.status_dict = vim.b.gitsigns_status_dict self.has_changes = self.status_dict.added ~= 0 or self.status_dict.removed ~= 0 or self.status_dict.changed ~= 0 end, hl = { fg = colors.orange, bg = colors.bg }, { provider = function(self) return ' ' .. self.status_dict.head end, hl = { bold = true }, }, { condition = function(self) return self.has_changes end, provider = ' ', }, { provider = function(self) local count = self.status_dict.added or 0 return count > 0 and ('  ' .. count) end, hl = { fg = colors.git.add }, }, { provider = function(self) local count = self.status_dict.removed or 0 return count > 0 and ('  ' .. count) end, hl = { fg = colors.git.delete }, }, { provider = function(self) local count = self.status_dict.changed or 0 return count > 0 and ('  ' .. count) end, hl = { fg = colors.git.change }, }, } local FileType = { provider = function() return vim.bo.filetype end, hl = { fg = utils.get_highlight('Statusline').fg, bold = true }, } local FileEncoding = { provider = function() local enc = (vim.bo.fenc ~= '' and vim.bo.fenc) or vim.o.enc -- :h 'enc' return enc:upper() end, } local FileInfoBlock = { init = function(self) self.filename = vim.api.nvim_buf_get_name(0) end, } FileInfoBlock = utils.insert( FileInfoBlock, FileEncoding, Space, FileIcon, FileType, { provider = '%<' } -- this means that the statusline is cut here when there's not enough space ) local FileNameShort = { provider = function(self) -- first, trim the pattern relative to the current directory. For other -- options, see :h filename-modifers local filename = vim.fn.fnamemodify(self.filename, ':t') if filename == '' then return '[No Name]' end return filename end, hl = { fg = colors.fg_dark }, } local FileNameShortBlock = { init = function(self) self.filename = vim.api.nvim_buf_get_name(0) end, } FileNameShortBlock = utils.insert( FileNameShortBlock, FileIcon, FileNameShort, { provider = '%<' } -- this means that the statusline is cut here when there's not enough space ) local TerminalName = { -- we could add a condition to check that buftype == 'terminal' -- or we could do that later (see #conditional-statuslines below) provider = function() local tname, _ = vim.api.nvim_buf_get_name(0):gsub('.*:', '') return ' ' .. tname end, hl = { bold = true }, } local Ruler = { -- %l = current line number -- %L = number of lines in the buffer -- %c = column number -- %P = percentage through file of displayed window provider = '%7 %p%% %l,%c', } local DefaultStatusline = { ViMode, Space, FileNameBlock, Space, Diagnostics, Align, Ruler, Space, FileInfoBlock, Space, Git, } local SpecialStatusline = { condition = function() return conditions.buffer_matches({ buftype = { 'nofile', 'prompt', 'help', 'quickfix' }, filetype = { '^git.*', 'fugitive' }, }) end, FileType, Space, Align, } local TerminalStatusline = { condition = function() return conditions.buffer_matches({ buftype = { 'terminal' } }) end, TerminalName, Align, } local StatusLines = { fallthrough = false, SpecialStatusline, TerminalStatusline, DefaultStatusline, } local WinBars = { fallthrough = false, { -- An inactive winbar for regular files condition = function() return conditions.buffer_matches({ buftype = { 'terminal' } }) and not conditions.is_active() end, { hl = { bg = colors.bg_dark, fg = 'gray', force = true }, SmallSpace, TerminalName, Align, }, }, { -- A special winbar for terminals condition = function() return conditions.buffer_matches({ buftype = { 'terminal' } }) end, { hl = { bg = colors.bg_dark1, force = true }, SmallSpace, TerminalName, Align, }, }, { -- An inactive winbar for regular files condition = function() return not conditions.is_active() end, { hl = { bg = colors.bg_dark, fg = 'gray', force = true }, SmallSpace, FileNameShortBlock, Align, }, }, -- A winbar for regular files { hl = { bg = colors.bg_dark1, force = true }, SmallSpace, FileNameShortBlock, Align, }, } 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 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.bo[bufnr].filetype == 'neo-tree' then self.title = ' NeoTree' return true end end, provider = function(self) local title = self.title local width = vim.api.nvim_win_get_width(self.winid) + 2 local padLeft = math.ceil((width - #title) / 2) local padRight = width - #title - padLeft return string.rep(' ', padLeft) .. title .. string.rep(' ', padRight) .. '▐' end, hl = function(self) if vim.api.nvim_get_current_win() == self.winid then return 'TablineSel' else return 'Tabline' end end, } local Tabpage = { provider = function(self) return '%' .. self.tabnr .. 'T ' .. self.tabnr .. ' %T' end, hl = function(self) if not self.is_active then return 'TabLine' else return 'TabLineSel' end end, } local TabpageClose = { provider = '%999X  %X', hl = 'TabLine', } local TabPages = { -- only show this component if there's 2 or more tabpages condition = function() return #vim.api.nvim_list_tabpages() >= 2 end, { provider = '%=' }, utils.make_tablist(Tabpage), TabpageClose, } local BufferLine = require('plugins.heirline-tabline') local Tabline = { TablineOffset, BufferLine, TabPages } require('heirline').setup({ statusline = StatusLines, winbar = WinBars, tabline = Tabline, opts = { disable_winbar_cb = 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', 'Trouble', 'packer', 'markdown' }, vim.bo[buf].filetype) return buftype or filetype end, }, }) local function goto_buf(index) local bufs = vim.tbl_filter(function(bufnr) return vim.api.nvim_buf_is_loaded(bufnr) and vim.bo[bufnr].buflisted end, vim.api.nvim_list_bufs()) if index > #bufs then index = #bufs end vim.api.nvim_win_set_buf(0, bufs[index]) end local function addKey(key, index) vim.keymap.set('', '', function() goto_buf(index) end, { noremap = true, silent = true }) end for i = 1, 9 do addKey(i, i) end addKey('0', 10) end, }