🐱

2023年 わたしの Neovim

2023/05/07に公開

はじめに

こんにちは、あろーです。

Neovim をメインのエディタとして使いはじめてから、ちょうど 1 年くらい が経っていました。

ひとつの区切りとして、なんだかちょうど良い感じがしたので現在のわたしの環境についてまとめてみたいと思います。

こんな感じ

neovim

https://github.com/arrow2nd/dotfiles

設定の方針

厳密に守っているわけではありませんが、以下の方針で設定しています。

  • 設定は基本 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 の警告のフォーマットを変更して何が警告を出しているのかわかるようにしています。

警告が表示されている様子

こちらの設定は以下の記事を参考にさせていただきました。

https://dev.classmethod.jp/articles/eetann-change-neovim-lsp-diagnostics-format/

キーマッピング

以下のようなヘルパー関数を作って、キーマップの設定を書きやすくしています。

lua/util/helper.lua
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 を参考にさせていただいています…!

https://github.com/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 kgj gk と入れ替えています。

-- 行移動
h.nmap('j', 'gj')
h.nmap('k', 'gk')
h.nmap('gj', 'j')
h.nmap('gk', 'k')

少し前までは <ESC>jj に割り当てていましたが、Vim ではないところでも jj を打ちまくる人間になってしまったのでやめました 🌝

Filetype 固有の設定

こちらの記事を参考に、Markdown のリストを編集する際に便利な設定を追加しています。

https://zenn.dev/vim_jp/articles/4564e6e5c2866d

せっかくなので 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

lazy.nvim

モダンな感じの Lua 製プラグインマネージャーです。

https://github.com/folke/lazy.nvim

以前は dein.vim を使っていましたが、設定ファイルを Lua へ移行する際にこちらへ乗り換えました。

簡単に扱えてリッチな UI が付いてくるところが魅力的ですが、遅延読み込み設定の柔軟度では dein.vim に軍配があがりそうです。

カラースキーム

iceberg.vim

暗めのブルーを基調とした、落ちついた雰囲気のカラースキームです。
定期的に他のカラースキームも試してみるのですが、結局これに帰ってきてしまいます。

https://github.com/cocopon/iceberg.vim

ファジーファインダー

telescope.nvim

telescope

Neovim における定番とも言えそうなファジーファインダー(あいまい検索)のプラグインです。

https://github.com/nvim-telescope/telescope.nvim

ファイルの検索やブラウジング、リアルタイムな grep や LSP のエラー検索など、色々な操作をこれひとつで行っています。

ビルトインの機能に加えて、以下の拡張プラグインを導入しています。

telescope-file-browser.nvim

ファイラーの機能を追加するプラグインです。

https://github.com/nvim-telescope/telescope-file-browser.nvim

telescope-kensaku.nvim

ローマ字での日本語検索を提供する kensaku.vim を利用して、全文検索をできるようにするプラグインです。

https://github.com/Allianaab2m/telescope-kensaku.nvim

telescope-ui-select.nvim

vim.ui.select を Telescope に置きかえるプラグインです。

https://github.com/nvim-telescope/telescope-ui-select.nvim

LSP

Neovim 組込みの LSP クライアントを使うために次のプラグインを導入しています。

null-ls.nvim

LSP に対応していない Linter や Formatter(Textlint や Prettier など)を扱うために使っています。

https://github.com/jose-elias-alvarez/null-ls.nvim

mini.nvim

小さな Lua 製プラグインたちの集合体です。
わたしはまるごとインストールしていますが、個別にインストールもできます。

https://github.com/echasnovski/mini.nvim

mini.comment

ファイルタイプに合わせて、いい感じにコメントアウトしてくれるプラグインです。
surround.vim のような感じです。

https://github.com/echasnovski/mini.comment

mini.indentscope

デモ

インデントの範囲を表示してくれるプラグインです。
インデント関係のテキストオブジェクトも提供してくれます。

https://github.com/echasnovski/mini.indentscope

mini.pairs

対応する括弧を自動で挿入してくれるプラグインです。
auto-pairs のような感じです。

https://github.com/echasnovski/mini.pairs

mini.splitjoin

デモ

複数の引数を一発で分割・結合してくれるプラグインです。
地味な感じがしますが、かなり活躍しています。

https://github.com/echasnovski/mini.splitjoin

mini.statusline

デモ

シンプルなステータスラインのプラグインです。

https://github.com/echasnovski/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 のような感じです。

https://github.com/echasnovski/mini.surround

外観系

nvim-treesitter

主に構文ハイライト用に入れています 🎨

https://github.com/tree-sitter/tree-sitter

nvim-highlight-colors

デモ

カラーコードの色をプレビューしてくれるプラグインです。
Tailwind の色指定にも対応していてうれしい。

https://github.com/brenoprata10/nvim-highlight-colors

入力系

ddc.vim

Dark deno-powered な自動補完プラグインです。
Deno で Vim プラグインを作るためのエコシステム denops.vim を使って実装されています。

補完候補を出力する source と絞り込みを行う filter とが分離されており、拡張しやすいのが特徴かなと思います。

https://github.com/Shougo/ddc.vim
https://zenn.dev/shougo/articles/ddc-vim-beta

以下のプラグインと組み合わせて使っています。

UI

Source(補完候補)

Filter(候補のフィルタリング)

プレビュー

vim-vsnip

VSCode と同じフォーマットのスニペットを使えるようにするプラグインです。

https://github.com/hrsh7th/vim-vsnip

vim-vsnip-integ

組込みの LSP クライアントや ddc.vim と連携するために入れています。

https://github.com/hrsh7th/vim-vsnip-integ

skkeleton

デモ

Vim 内で SKK による日本語入力を可能にするプラグインです。
わたしが最も依存しているプラグインでもあります。

https://github.com/vim-skk/skkeleton

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 の閉じタグ補完・タグのリネームをしてくれるプラグインです。

https://github.com/windwp/nvim-ts-autotag

dial.nvim

<C-a> <C-x> による値の増減を拡張してくれるプラグインです。
日付やバージョンをいい感じに増減してくれるほか、Bool 値の切り替えなどもできます。便利すぎる。

https://github.com/monaqa/dial.nvim

Git 系

neogit.nvim

デモ
実行されたコマンドの履歴を見てる様子

Emacs で動作する Git クライアント Magit のクローンです。
この記事を書いている最中に vim-fugitive から移行しました。

https://github.com/TimUntersberger/neogit

diffview.nvim

デモ
ファイルの変更履歴を表示してる様子

Git の変更差分をリッチな UI で見られるプラグインです。
まだ使ったことはありませんがマージツールも内蔵されているらしいです。

https://github.com/sindrets/diffview.nvim

git-messenger.vim

デモ

カーソル行のコミット情報をポップアップしてくれるプラグインです。
遡って確認できるほか、その場で diff も見られるので重宝しています。

https://github.com/rhysd/git-messenger.vim

gitsigns.nvim

Git の変更状態を表示させるのに使っています。
こちらにも Git blame を表示する機能がありますが使っていません。

https://github.com/lewis6991/gitsigns.nvim

移動系

chowcho.nvim

デモ

ウィンドウ間の移動をサポートしてくれるプラグインです。

https://github.com/tkmpypy/chowcho.nvim

こちらの記事を参考にして、<C-w><C-w> に割りあてています。

https://zenn.dev/kawarimidoll/articles/daa39da5838567

fuzzy-motion.vim

デモ

画面内の文字を検索して、そこにカーソルを飛ばすことができるプラグインです。
ローマ字での日本語検索を提供する kensaku.vim と連携させて使っています。

https://github.com/yuki-yano/fuzzy-motion.vim

その他

vim-qfreplace

QuickFix の置換を提供してくれるプラグインです。

https://github.com/thinca/vim-qfreplace

  1. Telescope で置換したい文字列を grep 検索
  2. 検索結果を<C-q> で QuickFix に流し込む
  3. :Qfreplace で置換

の流れで使っています。

denops-translate.vim

デモ

選択範囲を翻訳してポップアップしてくれるプラグインです。
プラグインのヘルプを読むときに重宝しています。

https://github.com/skanehira/denops-translate.vim

bufpreview.vim

Markdown のプレビューを提供してくれるプラグインです。
カーソル位置の同期もしてくれます。

https://github.com/kat0h/bufpreview.vim

vim-floaterm

デモ

Vim 内でターミナルをポップアップ表示してくれるプラグインです。
それ以外にも色々機能があるようですが、あんまり使いこなせていません…。

https://github.com/voldikss/vim-floaterm

おわりに

現在のわたしの環境について、ざっくりですがまとめてみました。

あっという間に 1 年が経ちましたが、まだまだ知らないことがあってとても面白いです。
今年からは趣味の時間以外にも Neovim を触ることになりそうなので、もっと仲良くなっていきたいなと思っています。

なにか常用できるようなプラグインも自作してみたいですね…。

Discussion