REFACTOR: Changed status bar significantly to improve performance and be

a little smaller
This commit is contained in:
2026-10-02 23:59:54 -04:00
parent f8578e46c8
commit f242ace8a6
+209 -202
View File
@@ -30,8 +30,14 @@ local function setup_statusline_highlights()
derive_mode_hl('StatusModeReplace', 'WarningMsg') derive_mode_hl('StatusModeReplace', 'WarningMsg')
derive_mode_hl('StatusModeTerminal', 'Special') derive_mode_hl('StatusModeTerminal', 'Special')
derive_hl('StatusGit', { base = 'CursorLine', fg = 'LineNr' }) derive_hl('StatusGit', { base = 'CursorLine', fg = 'LineNr' })
derive_hl('StatusLsp', { base = 'StatusLine', fg = 'DiagnosticInfo' }) -- derive_hl('StatusLsp', { base = 'StatusLine', fg = 'DiagnosticInfo' })
derive_hl('StatusLspSpinner', { base = 'StatusLine', fg = 'Constant' }) -- derive_hl('StatusLspSpinner', { base = 'StatusLine', fg = 'Constant' })
derive_hl(
'StatusLspWorking',
{ base = 'StatusLine', fg = 'DiagnosticInfo' }
)
derive_hl('StatusLspReady', { base = 'StatusLine', fg = 'Constant' })
derive_hl('StatusLspNone', { base = 'StatusLine', fg = 'NonText' })
derive_hl( derive_hl(
'StatusDiagError', 'StatusDiagError',
{ base = 'StatusLine', fg = 'DiagnosticError' } { base = 'StatusLine', fg = 'DiagnosticError' }
@@ -39,7 +45,6 @@ local function setup_statusline_highlights()
derive_hl('StatusDiagWarn', { base = 'StatusLine', fg = 'DiagnosticWarn' }) derive_hl('StatusDiagWarn', { base = 'StatusLine', fg = 'DiagnosticWarn' })
derive_hl('StatusDiagHint', { base = 'StatusLine', fg = 'DiagnosticHint' }) derive_hl('StatusDiagHint', { base = 'StatusLine', fg = 'DiagnosticHint' })
derive_hl('StatusCursor', { base = 'CursorLine', fg = 'Normal' }) derive_hl('StatusCursor', { base = 'CursorLine', fg = 'Normal' })
-- derive_hl('StatusGitAdded', { base = 'StatusLine', fg = 'OkMsg' })
derive_hl('StatusGitAdded', { base = 'StatusLine', fg = 'MoreMsg' }) derive_hl('StatusGitAdded', { base = 'StatusLine', fg = 'MoreMsg' })
derive_hl( derive_hl(
'StatusGitRemoved', 'StatusGitRemoved',
@@ -54,183 +59,142 @@ setup_statusline_highlights()
--- Mode text mapping. --- Mode text mapping.
local mode_map = { local mode_map = {
['n'] = ' NORM ', n = ' NORM ',
['i'] = ' INS ', i = ' INS ',
['v'] = ' VIS ', v = ' VIS ',
['V'] = ' V-LINE ', V = ' V-LINE ',
[''] = ' V-BLK ', ['^V'] = ' V-BLK ',
['R'] = ' REPL ', s = ' VIS ',
['c'] = ' CMD ', S = ' V-LINE ',
['t'] = ' TERM ', ['^S'] = ' V-BLK ',
R = ' REPL ',
c = ' CMD ',
r = ' PROMPT ',
t = ' TERM ',
['!'] = ' SHELL ',
} }
local mode_hl_map = { local mode_hl_map = {
['n'] = 'StatusModeNormal', n = 'StatusModeNormal',
['i'] = 'StatusModeInsert', i = 'StatusModeInsert',
['v'] = 'StatusModeVisual', v = 'StatusModeVisual',
['V'] = 'StatusModeVisual', V = 'StatusModeVisual',
[''] = 'StatusModeVisual', ['^V'] = 'StatusModeVisual',
['R'] = 'StatusModeReplace', s = 'StatusModeVisual',
['c'] = 'StatusModeCommand', S = 'StatusModeVisual',
['t'] = 'StatusModeTerminal', ['^S'] = 'StatusModeVisual',
R = 'StatusModeReplace',
c = 'StatusModeCommand',
r = 'StatusModeCommand',
['!'] = 'StatusModeCommand',
t = 'StatusModeTerminal',
} }
--- Git branch (async, cached per buffer, refreshed every 5s) --- Git branch (async, cached per buffer, refreshed every 5s)
local function update_git_branch() local function refresh_status(buf)
if vim.bo.buftype ~= '' then local b = vim.b[buf]
local left, right = {}, {}
if b.git_branch and b.git_branch ~= '' then
left[#left + 1] = '%#StatusGit# ' .. b.git_branch .. ' %*'
end
local diff = b.git_diff
if diff then
if diff.added > 0 then
left[#left + 1] = '%#StatusGitAdded# +' .. diff.added .. ' %*'
end
if diff.removed > 0 then
left[#left + 1] = '%#StatusGitRemoved# -' .. diff.removed .. ' %*'
end
end
if b.lsp_icon then
left[#left + 1] = '%#' .. b.lsp_hl .. '#' .. b.lsp_icon .. '%*'
end
if b.recording then
right[#right + 1] = '%#StatusRecording# ' .. b.recording .. ' %*'
end
if b.diag_str then
right[#right + 1] = b.diag_str
end
b.status_left = #left > 0 and table.concat(left) or ''
b.status_right = #right > 0 and table.concat(right) or ''
end
--- LSP: one icon, three states.
local lsp_icon = {
none = ' <U+F0374> ',
working = ' <U+F012C> ',
ready = ' <U+F012D> ',
}
local lsp_hl = {
none = 'StatusLspNone',
working = 'StatusLspWorking',
ready = 'StatusLspReady',
}
local active_progress = {}
local function refresh_lsp(buf)
local state = 'ready'
local clients = vim.lsp.get_clients({ bufnr = buf })
if #clients == 0 then
state = 'none'
else
for _, client in ipairs(clients) do
if active_progress[client.id] then
state = 'working'
break
end
end
vim.b[buf].lsp_icon = lsp_icon[state]
vim.b[buf].lsp_hl = lsp_hl[state]
refresh_status(buf)
end
end
vim.api.nvim_create_autocmd({ 'LspAttach', 'LspDetach' }, {
callback = function(ev)
refresh_lsp(ev.buf)
end,
})
vim.api.nvim_create_autocmd('LspProgress', {
callback = function(ev)
if ev.match == 'begin' or ev.match == 'report' then
active_progress[ev.data.client_id] = true
elseif ev.match == 'end' then
active_progress[ev.data.client_id] = nil
else
return return
end end
local client = vim.lsp.get_client_by_id(ev.data.client_id)
if client then
for buf in pairs(client.attached_buffers) do
refresh_lsp(buf)
end
end
end,
})
--- Git: resolved once per buffer, refreshed on save.
local function refresh_branch(buf)
vim.system( vim.system(
{ 'git', 'branch', '--show-current' }, { 'git', 'branch', '--show-current' },
{ text = true }, { text = true },
function(out) function(out)
vim.b.git_branch = (out.code == 0 and out.stdout or ''):gsub( if not vim.api.nvim_buf_is_valid(buf) then
return
end
vim.b[buf].git_branch = (out.code == 0 and out.stdout or ''):gsub(
'%s+', '%s+',
'' ''
) )
refresh_status(buf)
end end
) )
end end
vim.uv.new_timer():start(5000, 5000, vim.schedule_wrap(update_git_branch))
vim.api.nvim_create_autocmd({ 'BufEnter', 'FocusGained' }, {
callback = function()
update_git_branch()
end,
})
--- LSP progress (Neovim 0.10+ built-in handler) local function refresh_diff(buf)
local progress = {} local file = vim.fn.fnamemodify(vim.fn.bufname(buf), ':p')
local spinner_frames =
{ '⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷' }
local frame = 0
--- Clean stale progress messages periodically
local progress_timer = vim.uv.new_timer()
local function start_progress_timer()
if not progress_timer:is_active() then
progress_timer:start(0, 200, function()
local now = vim.loop.now()
for k, v in pairs(progress) do
local ttl = v.done and 2000 or 10000
if now - v.t > ttl then
progress[k] = nil
end
end
if next(progress) then
frame = (frame % #spinner_frames) + 1
vim.schedule(function()
vim.cmd('redrawstatus')
end)
else
progress_timer:stop()
vim.schedule(function()
vim.cmd('redrawstatus')
end)
end
end)
end
end
vim.api.nvim_create_autocmd('LspProgress', {
callback = function(ev)
local d = ev.data
if not d then
return
end
local params = d.params
if not params or not params.token then
return
end
local key = d.client_id .. ':' .. tostring(params.token)
local value = params.value or {}
if value.kind == 'begin' then
progress[key] = {
msg = (value.message or 'working...'):gsub('%%', '%%%%'),
t = vim.loop.now(),
}
start_progress_timer()
elseif value.kind == 'report' then
local msg_parts = {}
if value.message then
msg_parts[#msg_parts + 1] = value.message
end
if value.percentage ~= nil then
msg_parts[#msg_parts + 1] = '(' .. value.percentage .. '%%)'
end
progress[key] = {
msg = table.concat(msg_parts, ' '),
t = vim.loop.now(),
}
elseif value.kind == 'end' then
if progress[key] then
progress[key].done = true
end
vim.cmd('redrawstatus')
end
end,
})
vim.api.nvim_create_autocmd({ 'LspAttach', 'LspDetach' }, {
callback = function(args)
local names = {}
for _, c in ipairs(vim.lsp.get_clients({ bufnr = args.buf })) do
table.insert(names, c.name)
end
vim.b[args.buf].lsp_names = #names > 0
and (' ● ' .. table.concat(names, ', ') .. ' ')
or ''
end,
})
local function lsp_section()
local client_ids = {}
for _, c in
ipairs(vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() }))
do
client_ids[c.id] = true
end
local latest = nil
for key, p in pairs(progress) do
local cid = tonumber(key:match('^(%d+):'))
if cid and client_ids[cid] then
if not latest or p.t > latest.t then
latest = p
end
end
end
if latest and (not latest.done or (vim.loop.now() - latest.t < 2000)) then
return '%#StatusLspSpinner# ○ ' .. latest.msg .. ' %*'
end
return '%#StatusLsp#' .. (vim.b.lsp_names or '') .. '%*'
end
local diag_cache = {}
local function build_diag_string(counts)
local parts = {}
if counts[1] and counts[1] > 0 then
table.insert(parts, '%#StatusDiagError# E' .. counts[1] .. ' %*')
end
if counts[2] and counts[2] > 0 then
table.insert(parts, '%#StatusDiagWarn# W' .. counts[2] .. ' %*')
end
if counts[4] and counts[4] > 0 then
table.insert(parts, '%#StatusDiagHint# H' .. counts[4] .. ' %*')
end
return table.concat(parts)
end
local function update_diag_cache(ev)
diag_cache[ev.buf] = build_diag_string(vim.diagnostic.count(ev.buf))
end
vim.api.nvim_create_autocmd(
'DiagnosticChanged',
{ callback = update_diag_cache }
)
local function update_git_diff()
if vim.bo.buftype ~= '' then
return
end
local file = vim.fn.expand('%:p')
if file == '' then if file == '' then
return return
end end
@@ -238,59 +202,102 @@ local function update_git_diff()
{ 'git', 'diff', '--numstat', 'HEAD', '--', file }, { 'git', 'diff', '--numstat', 'HEAD', '--', file },
{ text = true }, { text = true },
function(out) function(out)
if out.code ~= 0 or out.stdout == '' then if not vim.api.nvim_buf_is_valid(buf) then
vim.b.git_diff = nil
return return
end end
if out.code ~= 0 or out.stdout == '' then
vim.b[buf].git_diff = nil
else
local added, removed = out.stdout:match('(%d+)%s+(%d+)') local added, removed = out.stdout:match('(%d+)%s+(%d+)')
vim.b.git_diff = { vim.b[buf].git_diff = {
added = tonumber(added) or 0, added = tonumber(added) or 0,
removed = tonumber(removed) or 0, removed = tonumber(removed) or 0,
} }
end end
refresh_status(buf)
end
) )
end end
vim.api.nvim_create_autocmd({ 'BufEnter', 'FocusGained', 'BufWritePost' }, {
callback = update_git_diff, vim.api.nvim_create_autocmd({ 'BufEnter', 'BufNewFile' }, {
callback = function(ev)
if vim.bo[ev.buf].buftype == '' then
refresh_branch(ev.buf)
refresh_diff(ev.buf)
end
refresh_lsp(ev.buf)
refresh_diag(ev.buf)
end,
})
vim.api.nvim_create_autocmd('FocusGained', {
callback = function()
local buf = vim.api.nvim_get_current_buf()
if vim.bo[buf].buftype == '' then
refresh_branch(buf)
end
end,
})
vim.api.nvim_create_autocmd('BufWritePost', {
callback = function(ev)
refresh_diff(ev.buf)
end,
})
--- Diagnostics: counted on change, never during a redraw.
local function refresh_diag(buf)
local count = vim.diagnostic.count(buf)
local parts = {}
if count.error > 0 then
parts[#parts + 1] = '%#StatusDiagError# E' .. count.error .. ' %*'
end
if count.warn > 0 then
parts[#parts + 1] = '%#StatusDiagWarn# W' .. count.warn .. ' %*'
end
if count.hint > 0 then
parts[#parts + 1] = '%#StatusDiagHint# H' .. count.hint .. ' %*'
end
vim.b[buf].diag_str = #parts > 0 and table.concat(parts) or ''
refresh_status(buf)
end
vim.api.nvim_create_autocmd('DiagnosticChanged', {
callback = function(ev)
refresh_diag(ev.buf)
end,
})
--- Macro: read once when recording starts, not on every redraw.
vim.api.nvim_create_autocmd('RecordingEnter', {
pattern = '*',
callback = function(ev)
vim.b[ev.buf].recording = '● Rec @' .. vim.fn.reg_recording()
refresh_status(ev.buf)
end,
})
vim.api.nvim_create_autocmd('RecordingLeave', {
pattern = '*',
callback = function(ev)
vim.b[ev.buf].recording = nil
refresh_status(ev.buf)
end,
}) })
function _G.__statusline() function _G.__statusline()
local m = vim.fn.mode() local key = vim.fn.mode():sub(1, 2)
local mode_hl = mode_hl_map[m] or 'StatusModeNormal' if key ~= '^V' and key ~= '^S' then
local git = vim.b.git_branch or '' key = key:sub(1, 1)
local cur_buf = vim.api.nvim_get_current_buf()
local parts = {
'%#' .. mode_hl .. '#' .. (mode_map[m] or ' ?? ') .. '%*',
git ~= '' and ('%#StatusGit#  ' .. git .. ' %*') or '',
}
local diff = vim.b.git_diff
if diff and (diff.added > 0 or diff.removed > 0) then
if diff.added > 0 then
table.insert(parts, '%#StatusGitAdded# +' .. diff.added .. ' %*')
end end
if diff.removed > 0 then local hl = mode_hl_map[key] or 'StatusModeNormal'
table.insert( local b = vim.b
parts, return table.concat({
'%#StatusGitRemoved# -' .. diff.removed .. ' %*' '%#' .. hl .. '#' .. (mode_map[key] or ' ?? ') .. '%*',
) b.status_left or '',
end '%=',
end b.status_right or '',
table.insert(parts, lsp_section()) '%#StatusCursor# %l:%-2c %*',
table.insert(parts, '%=') '%#' .. hl .. '# %p%% %*',
local recording = vim.fn.reg_recording() })
if recording ~= '' then
table.insert(
parts,
'%#StatusRecording# ● Rec @' .. recording .. ' %*'
)
end
table.insert(
parts,
diag_cache[cur_buf] or build_diag_string(vim.diagnostic.count(cur_buf))
)
table.insert(parts, '%#StatusCursor# %l:%-2c %*')
table.insert(parts, '%#' .. mode_hl .. '# %p%% %*')
return table.concat(parts)
end end
vim.o.statusline = '%!v:lua.__statusline()' vim.o.statusline = '%!v:lua.__statusline()'