Skip to content

Instantly share code, notes, and snippets.

@MunifTanjim
Last active December 27, 2022 17:56
Show Gist options
  • Save MunifTanjim/6f5fcdc9649d52f28403250b26b4d867 to your computer and use it in GitHub Desktop.
Save MunifTanjim/6f5fcdc9649d52f28403250b26b4d867 to your computer and use it in GitHub Desktop.
Neovim Statusline
local mod = {}
local result = {
statusline = {},
tabline = {},
winbar = {},
}
local heirline = require("heirline")
local heirline_eval_statusline = heirline.eval_statusline
local function heirline_eval_statusline_profiler()
local start_time = vim.loop.hrtime()
local ret = heirline_eval_statusline()
local end_time = vim.loop.hrtime()
if not result['statusline'][1] then
result['statusline'][1] = {}
end
table.insert(result['statusline'][1], end_time - start_time)
return ret
end
function mod.start()
heirline.eval_statusline = heirline_eval_statusline_profiler
result.statusline = {}
result.tabline = {}
result.winbar = {}
end
function mod.stop()
heirline.eval_statusline = heirline_eval_statusline_profiler
end
function mod.result()
for _, bar_type in ipairs({ "statusline", "tabline", "winbar" }) do
for id, bar in pairs(result[bar_type]) do
local redraw = #bar
local total_time_ns = 0
for _, time_ns in ipairs(bar) do
total_time_ns = total_time_ns + time_ns
end
local total_time_ms = total_time_ns / 1e6
print(
string.format(
"%s(id: %2s) redraw(total: %5s per_ms: %9.6f) time(total: %12.6f per_redraw: %8.6f)",
bar_type,
id,
redraw,
redraw / total_time_ms,
total_time_ms,
total_time_ms / redraw
)
)
end
end
end
return mod
local conditions = require("heirline.conditions")
local utils = require("heirline.utils")
local colors = {
bright_bg = utils.get_highlight("Folded").bg,
bright_fg = utils.get_highlight("Folded").fg,
red = utils.get_highlight("DiagnosticError").fg,
dark_red = utils.get_highlight("DiffDelete").bg,
green = utils.get_highlight("String").fg,
blue = utils.get_highlight("Function").fg,
gray = utils.get_highlight("NonText").fg,
orange = utils.get_highlight("Constant").fg,
purple = utils.get_highlight("Statement").fg,
cyan = utils.get_highlight("Special").fg,
diag_warn = utils.get_highlight("DiagnosticWarn").fg,
diag_error = utils.get_highlight("DiagnosticError").fg,
diag_hint = utils.get_highlight("DiagnosticHint").fg,
diag_info = utils.get_highlight("DiagnosticInfo").fg,
-- git_del = utils.get_highlight("diffDeleted").fg,
-- git_add = utils.get_highlight("diffAdded").fg,
-- git_change = utils.get_highlight("diffChanged").fg,
}
require("heirline").load_colors(colors)
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()
-- execute this only once, this is required if you want the ViMode
-- component to be updated on operator pending mode
if not self.once then
vim.api.nvim_create_autocmd("ModeChanged", {
pattern = "*:*o",
command = "redrawstatus",
})
self.once = true
end
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 = "N",
no = "N?",
nov = "N?",
noV = "N?",
["no\22"] = "N?",
niI = "Ni",
niR = "Nr",
niV = "Nv",
nt = "Nt",
v = "V",
vs = "Vs",
V = "V_",
Vs = "Vs",
["\22"] = "^V",
["\22s"] = "^V",
s = "S",
S = "S_",
["\19"] = "^S",
i = "I",
ic = "Ic",
ix = "Ix",
R = "R",
Rc = "Rc",
Rx = "Rx",
Rv = "Rv",
Rvc = "Rv",
Rvx = "Rv",
c = "C",
cv = "Ex",
r = "...",
rm = "M",
["r?"] = "?",
["!"] = "!",
t = "T",
},
mode_colors = {
n = "red",
i = "green",
v = "cyan",
V = "cyan",
["\22"] = "cyan",
c = "orange",
s = "purple",
S = "purple",
["\19"] = "purple",
R = "orange",
r = "orange",
["!"] = "red",
t = "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)
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 { fg = self.mode_colors[mode], bold = true }
end,
-- Re-evaluate the component only on ModeChanged event!
-- This is not required in any way, but it's there, and it's a small
-- performance improvement.
update = {
"ModeChanged",
},
}
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,
}
-- We can now define some children separately and add them later
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 = {
{
condition = function()
return vim.bo.modified
end,
provider = "[+]",
hl = { fg = "green" },
},
{
condition = function()
return not vim.bo.modifiable or vim.bo.readonly
end,
provider = "",
hl = { fg = "orange" },
},
}
-- Now, let's say that we want the filename color to change if the buffer is
-- modified. Of course, we could do that directly using the FileName.hl field,
-- but we'll see how easy it is to alter existing components using a "modifier"
-- component
local FileNameModifer = {
hl = function()
if vim.bo.modified then
-- use `force` because we need to override the child's hl foreground
return { fg = "cyan", bold = true, force = true }
end
end,
}
-- let's add the children to our FileNameBlock component
FileNameBlock = utils.insert(
FileNameBlock,
FileIcon,
utils.insert(FileNameModifer, FileName), -- a new table where FileName is a child of FileNameModifier
FileFlags,
{ provider = "%<" } -- this means that the statusline is cut here when there's not enough space
)
local FileType = {
provider = function()
return string.upper(vim.bo.filetype)
end,
hl = { fg = utils.get_highlight("Type").fg, bold = true },
}
local Ruler = {
provider = "%7(%l/%3L%):%2c %P",
}
ViMode = utils.surround({ "", "" }, "bright_bg", { ViMode })
local Align = { provider = "%=" }
local Space = { provider = " " }
local DefaultStatusline = {
ViMode,
Space,
FileNameBlock,
Space,
Space,
Align,
Space,
Space,
Space,
FileType,
Space,
Ruler,
Space,
}
local StatusLines = {
hl = function()
if conditions.is_active() then
return "StatusLine"
else
return "StatusLineNC"
end
end,
-- the first statusline with no condition, or which condition returns true is used.
-- think of it as a switch case with breaks to stop fallthrough.
fallthrough = false,
DefaultStatusline,
}
require("heirline").setup(StatusLines)
local color = require("config.color")
local core = require("nui.bar.core")
local Bar = require("nougat.bar")
local bar_util = require("nougat.bar.util")
local Item = require("nougat.item")
local sep = require("nougat.separator")
local nut = {
buf = {
diagnostic_count = require("nougat.nut.buf.diagnostic_count").create,
fileencoding = require("nougat.nut.buf.fileencoding").create,
fileformat = require("nougat.nut.buf.fileformat").create,
filename = require("nougat.nut.buf.filename").create,
filestatus = require("nougat.nut.buf.filestatus").create,
filetype = require("nougat.nut.buf.filetype").create,
wordcount = require("nougat.nut.buf.wordcount").create,
},
git = {
branch = require("nougat.nut.git.branch").create,
},
tab = {
tablist = {
tabs = require("nougat.nut.tab.tablist").create,
close = require("nougat.nut.tab.tablist.close").create,
icon = require("nougat.nut.tab.tablist.icon").create,
label = require("nougat.nut.tab.tablist.label").create,
modified = require("nougat.nut.tab.tablist.modified").create,
},
},
mode = require("nougat.nut.mode").create,
ruler = require("nougat.nut.ruler").create,
spacer = require("nougat.nut.spacer").create,
}
vim.o.rulerformat = table.concat({
core.code("p"),
"%% L:",
core.code("l"),
"/",
core.code("L"),
" C:",
core.code("v"),
})
local breakpoint = { l = 1, m = 2, s = 3 }
local breakpoints = { [breakpoint.l] = math.huge, [breakpoint.m] = 128, [breakpoint.s] = 80 }
local stl = Bar("statusline", { breakpoints = breakpoints })
local mode = nut.mode({
prefix = " ",
suffix = " ",
config = {
highlight = {
normal = {
fg = color.dark.bg,
},
visual = {
bg = color.dark.orange,
fg = color.dark.bg,
},
insert = {
bg = color.dark.blue,
fg = color.dark.bg,
},
replace = {
bg = color.dark.purple,
fg = color.dark.bg,
},
commandline = {
bg = color.dark.green,
fg = color.dark.bg,
},
terminal = {
bg = color.dark.accent.green,
fg = color.dark.bg,
},
inactive = {},
},
},
})
stl:add_item(mode)
stl:add_item(core.truncation_point())
stl:add_item(nut.git.branch({
hl = { bg = color.dark.bg3, fg = color.dark.fg1 },
prefix = { "  ", " " },
suffix = " ",
}))
local filestatus = nut.buf.filestatus({
prefix = " ",
config = {
modified = "",
nomodifiable = "",
readonly = "",
sep = " ",
},
})
stl:add_item(filestatus)
stl:add_item(nut.buf.filename({
prefix = " ",
suffix = " ",
config = {
modifier = ":.",
[breakpoint.m] = {
format = function(name)
return table.concat({ vim.fn.pathshorten(vim.fn.fnamemodify(name, ":h")), "/", vim.fn.fnamemodify(name, ":t") })
end,
},
[breakpoint.s] = { modifier = ":t", format = false },
},
}))
stl:add_item(core.truncation_point())
stl:add_item(nut.spacer())
stl:add_item(nut.buf.filetype({
prefix = " ",
suffix = " ",
}))
stl:add_item(nut.buf.diagnostic_count({
hidden = function(item, ctx)
return item.cache[ctx.bufnr][item:config(ctx).severity] == 0
end,
hl = { bg = color.dark.bg3 },
prefix = " ",
suffix = " ",
config = {
error = { prefix = " ", fg = color.dark.red },
warn = { prefix = " ", fg = color.dark.yellow },
info = { prefix = " ", fg = color.dark.blue },
hint = { prefix = " ", fg = color.dark.green },
},
}))
stl:add_item(nut.buf.fileencoding({
hidden = function(_, ctx)
return vim.bo[ctx.bufnr].fileencoding == "utf-8"
end,
prefix = " ",
suffix = " ",
}))
stl:add_item(nut.buf.fileformat({
hidden = function(_, ctx)
return vim.bo[ctx.bufnr].fileformat == "unix"
end,
hl = { bg = color.dark.bg3, fg = "fg" },
prefix = " ",
suffix = " ",
config = {
text = {
dos = "",
unix = "",
mac = "",
},
},
}))
local wordcount_enabled = {
markdown = true,
}
stl:add_item(nut.buf.wordcount({
hidden = function(_, ctx)
return not wordcount_enabled[vim.bo[ctx.bufnr].filetype]
end,
hl = mode,
sep_left = sep.space(),
config = {
format = function(count)
return string.format("%d Word%s", count, count > 1 and "s" or "")
end,
},
}))
stl:add_item(nut.ruler({
hl = mode,
sep_left = sep.space(),
suffix = " ",
}))
local stl_inactive = Bar("statusline")
stl_inactive:add_item(mode)
stl_inactive:add_item(core.truncation_point())
stl_inactive:add_item(filestatus)
stl_inactive:add_item(nut.buf.filename({
prefix = " ",
suffix = " ",
}))
bar_util.set_statusline(function(ctx)
return ctx.is_focused and stl or stl_inactive
end)
@MunifTanjim
Copy link
Author

image

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment