2023年 わたしの Neovim
はじめに
こんにちは、あろーです。
Neovim をメインのエディタとして使いはじめてから、ちょうど 1 年くらい が経っていました。
ひとつの区切りとして、なんだかちょうど良い感じがしたので現在のわたしの環境についてまとめてみたいと思います。
こんな感じ
設定の方針
厳密に守っているわけではありませんが、以下の方針で設定しています。
- 設定は基本 Lua で書きます
- プラグインの実装に使われている言語は問いません(Vim script で実装されているから使わないなどはしない)
- 常に表示される情報は最小限に留め、できるだけシンプルな画面を維持します
- 起動速度にはあまりこだわりません。気にならない程度の速度であればよしとしています(沼すぎるので…)
わたしの使い方
- OS は macOS、または Linux を使います
- ターミナルは Wezterm です
- コーディングからドキュメント作成、Git の操作など、だいたいを Neovim の中で行います
- タブはほとんど使いません
- ウィンドウサイズの変更やスクロール操作にはマウスを使うこともあります
設定
本体のオプション
Vim を初めて触ったときからそのままになっている箇所もあるので、そろそろ見直したほうがよさそうだな~という気がしています。
options.lua
local opt = vim.opt
-- 文字コード
vim.scriptencoding = 'utf-8'
opt.encoding = 'utf-8'
opt.fileencoding = 'utf-8'
-- 24bitカラー
opt.termguicolors = true
-- statusline を下部に固定
opt.laststatus = 3
-- intro を非表示
opt.shortmess = 'I'
-- 行
opt.number = false
opt.signcolumn = 'yes'
opt.cursorline = true
-- ヘルプの言語
opt.helplang = 'ja'
-- バックアップ, スワップファイル
opt.backup = false
opt.swapfile = false
-- buffer切り替え時の未保存警告をオフ
opt.hidden = true
-- 行末までカーソルを移動可能に
opt.virtualedit = 'onemore'
-- マウス操作有効化
opt.mouse = 'a'
-- 不可視文字可視化
opt.list = true
opt.listchars = { tab = '>>', trail = '-', nbsp = '+' }
-- タブ, インデント
opt.tabstop = 2
opt.expandtab = true
opt.shiftwidth = 2
opt.smartindent = true
-- 検索
opt.ignorecase = true
opt.smartcase = true
opt.incsearch = true
-- ヒストリの上限
opt.history = 512
-- 補完
opt.completeopt = 'menuone,noinsert'
-- LSPの警告フォーマット
-- ref: https://dev.classmethod.jp/articles/eetann-change-neovim-lsp-diagnostics-format/
vim.lsp.handlers['textDocument/publishDiagnostics'] = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
virtual_text = {
format = function(diagnostic)
return string.format('%s (%s: %s)', diagnostic.message, diagnostic.source, diagnostic.code)
end,
},
})
-- 自動フォーマットを無視して保存
vim.api.nvim_create_user_command('W', 'noautocmd w', {})
このなかで地味に重宝しているのが、不可視文字を見えるようにする設定です。
-- 不可視文字可視化
opt.list = true
opt.listchars = { tab = '>>', trail = '-', nbsp = '+' }
普段はあまりないですが、他の方のプロジェクトを触る際に「このインデントは…スペースじゃ…ない…!?」ってなる時があるので助かっています。
また、LSP の警告のフォーマットを変更して何が警告を出しているのかわかるようにしています。
こちらの設定は以下の記事を参考にさせていただきました。
キーマッピング
以下のようなヘルパー関数を作って、キーマップの設定を書きやすくしています。
local helper = {}
-- keymap
for _, mode in pairs({ 'n', 'v', 'i', 's', 'o', 'c', 't', 'x' }) do
helper[mode .. 'map'] = function(lhs, rhs, opts)
vim.keymap.set(mode, lhs, rhs, opts or { silent = true })
end
end
return helper
こちらは skanehira さんの dotfiles を参考にさせていただいています…!
プラグインに依存しないマッピングの設定をこのファイルにまとめています。
lua/config/keymaps.lua
local h = require('util.helper')
-- リーダーキー
vim.g.mapleader = ' '
-- 行移動
h.nmap('j', 'gj')
h.nmap('k', 'gk')
h.nmap('gj', 'j')
h.nmap('gk', 'k')
-- タブ
h.nmap(']t', '<CMD>tabnext<CR>', { desc = 'Switch to next tab' })
h.nmap('[t', '<CMD>tabprevious<CR>', { desc = 'Switch to previous tab' })
-- バッファ
h.nmap(']b', '<CMD>bnext<CR>', { desc = 'Switch to next buffer' })
h.nmap('[b', '<CMD>bprevious<CR>', { desc = 'Switch to previous buffer' })
-- ハイライト解除
h.nmap('<ESC><ESC>', '<CMD>nohlsearch<CR>', { desc = 'Unhighlight' })
-- ヒストリ選択
h.omap('<C-p>', '<Up>')
h.omap('<C-n>', '<Down>')
-- quickfix
h.nmap('[q', '<Cmd>cprevious<CR>')
h.nmap(']q', '<Cmd>cnext<CR>')
-- ESCでターミナルを抜ける
h.tmap('<ESC>', '<C-\\><C-n>')
デカくて押しやすいという理由で、リーダーキーはスペースに割り当てています。
-- リーダーキー
vim.g.mapleader = ' '
また、表示行単位で移動できるほうが好みなので j
k
を gj
gk
と入れ替えています。
-- 行移動
h.nmap('j', 'gj')
h.nmap('k', 'gk')
h.nmap('gj', 'j')
h.nmap('gk', 'k')
少し前までは <ESC>
を jj
に割り当てていましたが、Vim ではないところでも jj
を打ちまくる人間になってしまったのでやめました 🌝
Filetype 固有の設定
こちらの記事を参考に、Markdown のリストを編集する際に便利な設定を追加しています。
せっかくなので Lua で書きなおしました。
チェックボックスの切り替えは、後述する dial.vim でも実現できそうな気がしています…。
after/ftplugin/markdown.lua
local h = require('util.helper')
local optl = vim.opt_local
-- ref: Vimでmarkdownの箇条書き(kawarimidoll)
-- https://zenn.dev/vim_jp/articles/4564e6e5c2866d
-- 引用符
optl.comments = 'nb:>'
-- リスト(- / - [ ] / * / 1.)
optl.comments:append('b:- [ ],b:- [x],b:-')
optl.comments:append('b:*')
optl.comments:append('b:1.')
optl.formatoptions:remove('c')
optl.formatoptions:append('jro')
-- チェックボックス切替
local markdown_checkbox = function()
local line = vim.api.nvim_get_current_line()
local list_pattern = '\\v^\\s*([*+-]|\\d+\\.)\\s+'
if not vim.regex(list_pattern):match_str(line) then
-- not list -> nothing to do
return
elseif vim.regex(list_pattern .. '\\[ \\]\\s+'):match_str(line) then
-- blank box -> check
line, _ = line:gsub('%[ %]', '[x]', 1)
elseif vim.regex(list_pattern .. '\\[x\\]\\s+'):match_str(line) then
-- checked box -> uncheck
line, _ = line:gsub('%[x%]', '[ ]', 1)
end
vim.api.nvim_set_current_line(line)
end
vim.api.nvim_buf_create_user_command(
0,
'MarkdownCheckbox',
function()
markdown_checkbox()
end,
{}
)
for _, mode in pairs({ 'n', 'i', 'x' }) do
h[mode .. 'map'](
'<C-CR>',
'<CMD>MarkdownCheckbox<CR>',
{ buffer = true, desc = 'Toggle checkbox' }
)
end
プラグイン
以下、導入しているプラグインをジャンル別に紹介していきます。
プラグインマネージャー
lazy.nvim
モダンな感じの Lua 製プラグインマネージャーです。
以前は dein.vim を使っていましたが、設定ファイルを Lua へ移行する際にこちらへ乗り換えました。
簡単に扱えてリッチな UI が付いてくるところが魅力的ですが、遅延読み込み設定の柔軟度では dein.vim に軍配があがりそうです。
カラースキーム
iceberg.vim
暗めのブルーを基調とした、落ちついた雰囲気のカラースキームです。
定期的に他のカラースキームも試してみるのですが、結局これに帰ってきてしまいます。
ファジーファインダー
telescope.nvim
Neovim における定番とも言えそうなファジーファインダー(あいまい検索)のプラグインです。
ファイルの検索やブラウジング、リアルタイムな grep や LSP のエラー検索など、色々な操作をこれひとつで行っています。
ビルトインの機能に加えて、以下の拡張プラグインを導入しています。
telescope-file-browser.nvim
ファイラーの機能を追加するプラグインです。
telescope-kensaku.nvim
ローマ字での日本語検索を提供する kensaku.vim を利用して、全文検索をできるようにするプラグインです。
telescope-ui-select.nvim
vim.ui.select
を Telescope に置きかえるプラグインです。
LSP
Neovim 組込みの LSP クライアントを使うために次のプラグインを導入しています。
- LSP の設定:nvim-lspconfig
- 言語サーバーの管理:mason.nvim
- lspconfig との連携:mason-lspconfig.nvim
null-ls.nvim
LSP に対応していない Linter や Formatter(Textlint や Prettier など)を扱うために使っています。
mini.nvim
小さな Lua 製プラグインたちの集合体です。
わたしはまるごとインストールしていますが、個別にインストールもできます。
mini.comment
ファイルタイプに合わせて、いい感じにコメントアウトしてくれるプラグインです。
surround.vim のような感じです。
mini.indentscope
インデントの範囲を表示してくれるプラグインです。
インデント関係のテキストオブジェクトも提供してくれます。
mini.pairs
対応する括弧を自動で挿入してくれるプラグインです。
auto-pairs のような感じです。
mini.splitjoin
複数の引数を一発で分割・結合してくれるプラグインです。
地味な感じがしますが、かなり活躍しています。
mini.statusline
シンプルなステータスラインのプラグインです。
以下のような設定を書いています。
mini.lua
require('mini.statusline').setup({
content = {
active = function()
local mode, mode_hl = MiniStatusline.section_mode({ trunc_width = 9999 }) -- 常にShort表示
local git = MiniStatusline.section_git({ trunc_width = 75 })
local diagnostics = MiniStatusline.section_diagnostics({ trunc_width = 75 })
local filename = MiniStatusline.section_filename({ trunc_width = 140 })
local fileinfo = MiniStatusline.section_fileinfo({ trunc_width = 120 })
local location = MiniStatusline.section_location({ trunc_width = 9999 }) -- 常にShort表示
local get_lsp_progress = function()
local prog = vim.lsp.util.get_progress_messages()[1]
if not prog then return '' end
local title = prog.title or ''
local per = prog.percentage or 0
return string.format('%s (%s%%%%)', title, per)
end
return MiniStatusline.combine_groups({
{ hl = mode_hl, strings = { mode } },
{ hl = 'MiniStatuslineDevinfo', strings = { git, diagnostics } },
'%<', -- Mark general truncate point
{ hl = 'MiniStatuslineFilename', strings = { filename } },
'%=', -- End left alignment
{ hl = 'MiniStatuslineFilename', strings = { get_lsp_progress() } },
{ hl = 'MiniStatuslineFileinfo', strings = { fileinfo } },
{ hl = mode_hl, strings = { location } },
})
end,
inactive = nil,
},
use_icons = true,
set_vim_settings = false,
})
-- iceberg 風のカラーパレット
local mini_statusline_colors = {
MiniStatuslineModeNormal = { bg = '#818596', fg = '#17171b' },
MiniStatuslineModeInsert = { bg = '#84a0c6', fg = '#161821' },
MiniStatuslineModeVisual = { bg = '#b4be82', fg = '#161821' },
MiniStatuslineModeReplace = { bg = '#e2a478', fg = '#161821' },
MiniStatuslineModeCommand = { bg = '#818596', fg = '#17171b' },
MiniStatuslineModeOther = { bg = '#0f1117', fg = '#3e445e' },
MiniStatuslineDevinfo = { bg = '#2e313f', fg = '#6b7089' },
MiniStatuslineFileinfo = { bg = '#2e313f', fg = '#6b7089' },
MiniStatuslineInactive = { link = 'StatusLineNC' },
}
for group, conf in pairs(mini_statusline_colors) do
vim.api.nvim_set_hl(0, group, conf)
end
画面幅を見て表示内容を簡略化してくれる機能があるのですが、入力モードと位置の表示は常に簡略表示になるよう、大きな値を設定しています。
local mode, mode_hl = MiniStatusline.section_mode({ trunc_width = 9999 })
local location = MiniStatusline.section_location({ trunc_width = 9999 })
また、(なくてもいいのですが)LSP の進捗を表示するようにしています。
local get_lsp_progress = function()
local prog = vim.lsp.util.get_progress_messages()[1]
if not prog then return '' end
local title = prog.title or ''
local per = prog.percentage or 0
return string.format('%s (%s%%%%)', title, per)
end
return MiniStatusline.combine_groups({
-- ~省略~
{ hl = 'MiniStatuslineFilename', strings = { get_lsp_progress() } },
-- ~省略~
})
適用したいハイライトと表示したい文字列を渡してあげるだけなので、とてもカスタマイズしやすいです。
mini.surround
括弧・引用符の操作を提供してくれるプラグインです。
surround.vim のような感じです。
外観系
nvim-treesitter
主に構文ハイライト用に入れています 🎨
nvim-highlight-colors
カラーコードの色をプレビューしてくれるプラグインです。
Tailwind の色指定にも対応していてうれしい。
入力系
ddc.vim
Dark deno-powered な自動補完プラグインです。
Deno で Vim プラグインを作るためのエコシステム denops.vim を使って実装されています。
補完候補を出力する source
と絞り込みを行う filter
とが分離されており、拡張しやすいのが特徴かなと思います。
以下のプラグインと組み合わせて使っています。
UI
- 補完ウィンドウ:pum.vim
- pum.vim との連携:ddc-ui-pum
Source(補完候補)
- 現在のバッファ内の単語:ddc-source-around
- LSP:ddc-source-nvim-lsp
- ファイルパス:ddc-file
Filter(候補のフィルタリング)
- 先頭マッチ:ddc-filter-matcher_head
- 候補をマッチ順にソート:ddc-filter-sorter_rank
- 入力した文字列長以下の候補を除外:ddc-filter-matcher_length
- 重複する候補を除外:ddc-filter-converter_remove_overlap
プレビュー
- 選択候補のプレビュー:denops-popup-preview.vim
- シグネチャーのプレビュー:denops-signature_help
vim-vsnip
VSCode と同じフォーマットのスニペットを使えるようにするプラグインです。
vim-vsnip-integ
組込みの LSP クライアントや ddc.vim と連携するために入れています。
skkeleton
Vim 内で SKK による日本語入力を可能にするプラグインです。
わたしが最も依存しているプラグインでもあります。
OS 側の IME と辞書を共有するために、起動時に特定のディレクトリにある辞書を探すようにしています。
-- 辞書を探す
local dictionaries = {}
local handle = io.popen('ls $HOME/.skk/*') -- フルバスで取得
if handle then
for file in handle:lines() do
table.insert(dictionaries, file)
end
handle:close()
end
vim.api.nvim_create_autocmd('User', {
pattern = 'skkeleton-initialize-pre',
callback = function()
vim.fn['skkeleton#config']({
eggLikeNewline = true,
registerConvertResult = true,
globalDictionaries = dictionaries,
})
end
nvim-ts-autotag
Treesitter を使って HTML の閉じタグ補完・タグのリネームをしてくれるプラグインです。
dial.nvim
<C-a>
<C-x>
による値の増減を拡張してくれるプラグインです。
日付やバージョンをいい感じに増減してくれるほか、Bool 値の切り替えなどもできます。便利すぎる。
Git 系
neogit.nvim
実行されたコマンドの履歴を見てる様子
Emacs で動作する Git クライアント Magit のクローンです。
この記事を書いている最中に vim-fugitive から移行しました。
diffview.nvim
ファイルの変更履歴を表示してる様子
Git の変更差分をリッチな UI で見られるプラグインです。
まだ使ったことはありませんがマージツールも内蔵されているらしいです。
git-messenger.vim
カーソル行のコミット情報をポップアップしてくれるプラグインです。
遡って確認できるほか、その場で diff も見られるので重宝しています。
gitsigns.nvim
Git の変更状態を表示させるのに使っています。
こちらにも Git blame
を表示する機能がありますが使っていません。
移動系
chowcho.nvim
ウィンドウ間の移動をサポートしてくれるプラグインです。
こちらの記事を参考にして、<C-w><C-w>
に割りあてています。
fuzzy-motion.vim
画面内の文字を検索して、そこにカーソルを飛ばすことができるプラグインです。
ローマ字での日本語検索を提供する kensaku.vim と連携させて使っています。
その他
vim-qfreplace
QuickFix の置換を提供してくれるプラグインです。
- Telescope で置換したい文字列を grep 検索
- 検索結果を
<C-q>
で QuickFix に流し込む -
:Qfreplace
で置換
の流れで使っています。
denops-translate.vim
選択範囲を翻訳してポップアップしてくれるプラグインです。
プラグインのヘルプを読むときに重宝しています。
bufpreview.vim
Markdown のプレビューを提供してくれるプラグインです。
カーソル位置の同期もしてくれます。
vim-floaterm
Vim 内でターミナルをポップアップ表示してくれるプラグインです。
それ以外にも色々機能があるようですが、あんまり使いこなせていません…。
おわりに
現在のわたしの環境について、ざっくりですがまとめてみました。
あっという間に 1 年が経ちましたが、まだまだ知らないことがあってとても面白いです。
今年からは趣味の時間以外にも Neovim を触ることになりそうなので、もっと仲良くなっていきたいなと思っています。
なにか常用できるようなプラグインも自作してみたいですね…。
Discussion