VimからNeovimへの移行とプラグインの棚卸し

2019年ごろにVim/Neovimで開発できる環境を整えてから3年経過しました。

Neovim関連のプラグインが充実しているようで、最近はGitHub copilot XのようなGPT4ベースのツールもNeovimに対応しています。AstroNvimとか、もはやVSCodeと見間違いそうなカッコいい設定ツールも出てきました。

VimとNeovim両方で動くように.vimrcを設定していましたが、そろそろNeovimへ寄せようと思い、プラグインなどの棚卸しを行いました。

プラグインマネージャーの変更

packer.nvimへ移行しました。

READMEを参考に設定後、nvimで下記コマンドを実行することで、設定ファイルに記載したプラグインがインストールされます。

:PackerUpdate

LSP設定用のプラグイン

nvim-lspconfigに移行しました。goplsなどのLanguage Server Protocolを設定することで、コーディング時にVSCodeのような静的解析が可能になります。

vim-lspはLSPの設定を自動化してくれるので、使い勝手はnvim-lspconfigより良かったかもしれません。nvim-lspconfigを採用した理由は、Neovimネイティブの機能であるNvim LSP client用のコンフィグだからです。

関数や変数の定義ジャンプなどの設定はREADMEに書かれていた内容をほぼそのまま利用しました。定義元へジャンプしたいときはgd、参照先へ飛びたいときはgrでジャンプできます。omnifuncによる補完はデフォルトのキーバインド<C-x><C-o>だと使いにくかったので、<C-n>で補完されるようにしました(「補完用のプラグイン」の項を参照)。

require'lspconfig'.gopls.setup{}
require'lspconfig'.golangci_lint_ls.setup{}

-- Global mappings.
-- See `:help vim.diagnostic.*` for documentation on any of the below functions
vim.keymap.set('n', '<space>e', vim.diagnostic.open_float)
vim.keymap.set('n', '[d', vim.diagnostic.goto_prev)
vim.keymap.set('n', ']d', vim.diagnostic.goto_next)
vim.keymap.set('n', '<space>q', vim.diagnostic.setloclist)

-- Use LspAttach autocommand to only map the following keys
-- after the language server attaches to the current buffer
vim.api.nvim_create_autocmd('LspAttach', {
  group = vim.api.nvim_create_augroup('UserLspConfig', {}),
  callback = function(ev)
    -- Enable completion triggered by <c-x><c-o>
    vim.bo[ev.buf].omnifunc = 'v:lua.vim.lsp.omnifunc'

    -- Buffer local mappings.
    -- See `:help vim.lsp.*` for documentation on any of the below functions
    local opts = { buffer = ev.buf }
    vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts)
    vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
    vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
    vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts)
    vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, opts)
    vim.keymap.set('n', '<space>wa', vim.lsp.buf.add_workspace_folder, opts)
    vim.keymap.set('n', '<space>wr', vim.lsp.buf.remove_workspace_folder, opts)
    vim.keymap.set('n', '<space>wl', function()
      print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
    end, opts)
    vim.keymap.set('n', '<space>D', vim.lsp.buf.type_definition, opts)
    vim.keymap.set('n', '<space>rn', vim.lsp.buf.rename, opts)
    vim.keymap.set('n', '<space>ca', vim.lsp.buf.code_action, opts)
    vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts)
    vim.keymap.set('n', '<space>f', function()
      vim.lsp.buf.format { async = true }
    end, opts)
  end,
})

フォーマッターとリンター用のプラグイン

aleからnull-ls.nvimに移行しました。非常に動作が軽くなった気がします。

nvim-lspconfigでもLSPに対応しているリンターを設定できるみたいですが、LSPに対応していないリンターやフォーマッター用に使います。

local null_ls = require("null-ls")

-- ------------------------------
-- Autoformat settings.
-- https://github.com/jose-elias-alvarez/null-ls.nvim/wiki/Formatting-on-save
-- https://github.com/jose-elias-alvarez/null-ls.nvim/wiki/Avoiding-LSP-formatting-conflicts
-- ------------------------------
local augroup = vim.api.nvim_create_augroup("LspFormatting", {})
local lsp_formatting = function(bufnr)
    vim.lsp.buf.format({
        filter = function(client)
            return client.name == "null-ls"
        end,
        bufnr = bufnr,
    })
end

-- ------------------------------
-- null_ls setup.
-- ------------------------------
null_ls.setup({
    sources = {
        null_ls.builtins.formatting.shfmt,
        null_ls.builtins.formatting.stylua,
        null_ls.builtins.diagnostics.eslint,
        -- null_ls.builtins.completion.spell,
        -- null_ls.builtins.diagnostics.golangci_lint,
        null_ls.builtins.formatting.goimports,
        null_ls.builtins.diagnostics.markdownlint_cli2,
        null_ls.builtins.formatting.markdownlint,
    },
    on_attach = function(client, bufnr)
        if client.supports_method("textDocument/formatting") then
            vim.api.nvim_clear_autocmds({ group = augroup, buffer = bufnr })
            vim.api.nvim_create_autocmd("BufWritePre", {
                group = augroup,
                buffer = bufnr,
                callback = function()
                  lsp_formatting(bufnr)
                end,
            })
        end
    end,
})

補完用のプラグイン

変数や関数、メソッドの入力時に自動で補完するためのプラグインnvim-cmpに移行しました。以前はasyncomplete.vimを利用していました(vim-lspを開発したエンジニアが公開しているプラグインです)。

nvim-cmpの利用に際し下記の依存ツールが必要だったので、合わせて導入しました。

  • vim-vsnip: スニペット用プラグイン
  • cmp-nvim-lsp: nvim-cmpでLSPを利用できるようにするためのプラグイン

以下、luaの設定内容。 omnifunkを<C-n><C-p>で実行できるようにしました。それ以外はREADMEに記載されている内容と同じです。

local cmp = require'cmp'

cmp.setup({
  snippet = {
    -- REQUIRED - you must specify a snippet engine
    expand = function(args)
      vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
    end,
  },
  window = {
    -- completion = cmp.config.window.bordered(),
    -- documentation = cmp.config.window.bordered(),
  },
  mapping = cmp.mapping.preset.insert({
    ["<C-p>"] = cmp.mapping.select_prev_item(),
    ["<C-n>"] = cmp.mapping.select_next_item(),
    ['<CR>'] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
  }),
  sources = cmp.config.sources({
    { name = 'nvim_lsp' },
  }, {
    { name = 'buffer' },
  })
})

その他

下記のプラグインは引き続き利用することにしました。

  • lambdalisue/fern.vim: ファイルマネージャー
  • Morhetz/gruvbox: カラースキーマ
  • nathanaelkane/vim-indent-guides: インデントを見やすくする
  • airblade/vim-gitgutter: git diffがわかるようにする
  • vim-airline/vim-airline: 見た目を整える
  • vim-airline/vim-airline-themes: 見た目を整える
  • sebdah/vim-delve: Goのデバッガ支援

まとめ

nvim-dapなど気になるプラグインはまだあるのですが、取り急ぎ業務でも問題なく使える状態にアップデートしました。

今はGoのコーディングとMarkdownに特化した設定ですが、少しずつ他の言語の開発も行えるように整えていきたいです。

Neovimの設定置き場