REFACTOR: Changed status bar significantly to improve performance and be
a little smaller
This commit is contained in:
@@ -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]
|
||||||
return
|
local left, right = {}, {}
|
||||||
|
|
||||||
|
if b.git_branch and b.git_branch ~= '' then
|
||||||
|
left[#left + 1] = '%#StatusGit# ' .. b.git_branch .. ' %*'
|
||||||
end
|
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
|
||||||
|
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
|
||||||
local added, removed = out.stdout:match('(%d+)%s+(%d+)')
|
if out.code ~= 0 or out.stdout == '' then
|
||||||
vim.b.git_diff = {
|
vim.b[buf].git_diff = nil
|
||||||
added = tonumber(added) or 0,
|
else
|
||||||
removed = tonumber(removed) or 0,
|
local added, removed = out.stdout:match('(%d+)%s+(%d+)')
|
||||||
}
|
vim.b[buf].git_diff = {
|
||||||
|
added = tonumber(added) or 0,
|
||||||
|
removed = tonumber(removed) or 0,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
refresh_status(buf)
|
||||||
end
|
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
|
|
||||||
if diff.removed > 0 then
|
|
||||||
table.insert(
|
|
||||||
parts,
|
|
||||||
'%#StatusGitRemoved# -' .. diff.removed .. ' %*'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
table.insert(parts, lsp_section())
|
local hl = mode_hl_map[key] or 'StatusModeNormal'
|
||||||
table.insert(parts, '%=')
|
local b = vim.b
|
||||||
local recording = vim.fn.reg_recording()
|
return table.concat({
|
||||||
if recording ~= '' then
|
'%#' .. hl .. '#' .. (mode_map[key] or ' ?? ') .. '%*',
|
||||||
table.insert(
|
b.status_left or '',
|
||||||
parts,
|
'%=',
|
||||||
'%#StatusRecording# ● Rec @' .. recording .. ' %*'
|
b.status_right or '',
|
||||||
)
|
'%#StatusCursor# %l:%-2c %*',
|
||||||
end
|
'%#' .. hl .. '# %p%% %*',
|
||||||
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()'
|
||||||
|
|||||||
Reference in New Issue
Block a user