golangの日記

Go言語を中心にプログラミングについてのブログ

ステータスラインをカスタマイズしたい (neovim,lua)

vim.png


neovim のパッケージ管理ツール packer の作者の dotfiles を眺めていると nvim 以下に statusline.lua がある。これを参考に(丸パクリ)しつつ自分好みのステータスラインを書きたい。





目次



事前知識


  • lualine を使えば何も問題ない。

  • API
    API のドキュメントはここ https://neovim.io/doc/user/api.html
    Diagnostic はここ https://neovim.io/doc/user/diagnostic.html

  • ステータスラインに関する知識
    コマンド :h statusline でヘルプを読む。 %f でファイル名を表示。 %#HIGHLIGHT_ID# でハイライトの ID を指定して色を変更する %* がその終端。 %= でセクションを区切る。%< で省略箇所の指定

  • 中身の確認方法
    適当な lua ファイルに print(vim.o.fenc) とか for k, v in pairs(vim.diagnostic) do print(k, v) end を書いて :source % すれば中身が見れる



やりたいこと


  • LSP の error と warning の数を表示したい。

  • 分割したウィンドウそれぞれのステータスラインを表示したい。 statusline.lua では local statuslines = {} で、各ウィンドウのステータスライン(文字列)を保持しておいて、ウィンドウがアクティブになったら vim.o.statusline にセットしている。これはこれで合理的で自作する理由の一つなんだろうけど、ウィンドウを分割した場合にアクティブなウィンドウのステータスラインのみが表示されるので、どのウィンドウがアクティブなのか分かりづらい(カラースキームで非アクティブなウィンドウが暗くなるとかしてれば別だろうけど)



コード


~/.config/nvim/lua/statusline.lua

(function()
  -- このファイルが最初に require されたときのみ実行される
  local set_hl = vim.api.nvim_set_hl
  local bg_color = '#262626'
  local fg_color = '#999999'
  -- ハイライトの設定。Statusline はアクティブ StatuslineNC は非アクティブ
  set_hl(0, 'Statusline', { fg = fg_color, bg = bg_color })
  set_hl(0, 'StatuslineNC', { fg = '#444444', bg = '#181818' })
  set_hl(0, 'StatuslineNormalAccent', { fg = fg_color, bold = true, bg = '#333333' })
  set_hl(0, 'StatuslineInsertAccent', { fg = fg_color, bold = true, bg = '#555555' })
  set_hl(0, 'StatuslineReplaceAccent', { fg = fg_color, bold = true, bg = '#666b10' })
  set_hl(0, 'StatuslineConfirmAccent', { fg = fg_color, bold = true, bg = '#83adad' })
  set_hl(0, 'StatuslineTerminalAccent', { fg = fg_color, bold = true, bg = '#555555' })
  set_hl(0, 'StatuslineMiscAccent', { fg = fg_color, bold = true, bg = '#666b10' })
  set_hl(0, 'StatuslineDiagnosticERROR', { fg = '#FF0000', bold = true, bg = bg_color })
  set_hl(0, 'StatuslineDiagnosticWARN', { fg = '#FFAF00', bold = true, bg = bg_color })
end)()

local mode_name_table = {
  n = 'Normal',
  no = 'N·Operator Pending',
  v = 'Visual',
  V = 'V·Line',
  ['^V'] = 'V·Block',
  s = 'Select',
  S = 'S·Line',
  ['^S'] = 'S·Block',
  i = 'Insert',
  ic = 'Insert',
  R = 'Replace',
  Rv = 'V·Replace',
  c = 'Command',
  cv = 'Vim Ex',
  ce = 'Ex',
  r = 'Prompt',
  rm = 'More',
  ['r?'] = 'Confirm',
  ['!'] = 'Shell',
  t = 'Terminal',
}
local mode_color_table = {
  ['n'] = 'StatuslineNormalAccent',
  ['i'] = 'StatuslineInsertAccent',
  ['ic'] = 'StatuslineInsertAccent',
  ['R'] = 'StatuslineReplaceAccent',
  ['t'] = 'StatuslineTerminalAccent',
}
-- モードに応じたハイライトIDとモード名を返す。ノーマルモードなら '%#StatuslineNormalAccent# NORMAL %*'
local function get_mode()
  local mode = vim.api.nvim_get_mode().mode
  local color = mode_color_table[mode] or 'StatuslineMiscAccent'
  local name = string.upper(mode_name_table[mode] or 'V-Block')
  return '%#' .. color .. '# ' .. name .. ' %*'
end

local function get_paste()
  return vim.o.paste and ' PASTE ' or ''
end

local function get_readonly_space()
  return ((vim.o.paste and vim.bo.readonly) and ' ' or '') and '%r' .. (vim.bo.readonly and ' ' or '')
end

local function get_encoding()
  return vim.o.fenc or vim.o.enc
end

local function get_fileformat()
  return vim.o.ff
end

local ft_table = {
  ['javascript'] = 'js',
  ['typescript'] = 'ts'
}
local function get_filetype()
  local ft = vim.o.ft
  return (not ft or ft == '') and '' or (ft_table[ft] or ft) .. '|'
end

local function get_filename()
  return '%f %m' .. get_paste() .. get_readonly_space()
end

-- エラーかワーニングがあったら '%#StatuslineDiagnosticERROR#1%*' の様に返す。
local function get_diagnostic()
  local d = {}
  for _, v in pairs({ 'ERROR', 'WARN' }) do
    local t = vim.diagnostic.get(0, { severity = vim.diagnostic.severity[v] })
    if t ~= nil and #t > 0 then
      table.insert(d, '%#StatuslineDiagnostic' .. v .. '#' .. tostring(#t) .. '%*')
    end
  end
  return table.concat(d, ' ')
end
-- アクティブウィンドウのステータスライン
local function get_active()
  return string.format([[%s %s %s %%= %%< %s|%s|%s%%l/%%L(%%P) ]],
    get_mode(),
    get_diagnostic(),
    get_filename(),
    get_encoding(),
    get_fileformat(),
    get_filetype()
  )
end
-- 非アクティブウィンドウのステータスライン
local function get_inactive()
  return ' %f%=%l/%L'
end

return { get_active = get_active, get_inactive = get_inactive }



~/.config/nvim/init.lua

local function draw()
  statusline = require('statusline')
  for _, win_id in pairs(vim.api.nvim_list_wins()) do
    if vim.api.nvim_get_current_win() == win_id then
      vim.wo[win_id].statusline = '%!v:lua.statusline.get_active()'
    elseif vim.api.nvim_buf_get_name(0) ~= '' then
      vim.wo[win_id].statusline = '%!v:lua.statusline.get_inactive()'
    end
  end
end

-- イベントは過不足あるかもしれない
vim.api.nvim_create_autocmd({ 'BufEnter', 'WinEnter', 'TabEnter' }, {
  group = vim.api.nvim_create_augroup('draw_statusline', {}),
  pattern = '*',
  callback = draw,
})



できたやつ


できたやつ


lualine を使ってるときと起動速度(nvim --startuptime bench.txt で確認)は大差ないし軽くもなってないと思われる