スタートアップCTOによるITエンジニアのためのブログ

【Neovim】nvim-cmpによるコード補完

Neovimでコーディングの際、全てのコードを手動入力するのは面倒と感じたことはないでしょうか。

この記事では、そのような悩みを解決してくれるコード補完プラグイン nvim-cmp とその関連プラグインのインストール・設定方法を解説します。また、nvim-cmpによって可能になる様々な種類の補完機能を紹介します。

コード補完とnvim-cmpの概要

コード補完とは、コーディング中に次の入力候補を提案してくれる機能です。

関数名やファイル名など、既にソースコードやファイルシステム上にある情報を元に入力候補を提案してくれるため、手動入力の手間を省き、タイポ(打ち間違い)の防止や作業の効率化に繋がります。

nvim-cmp は、Neovimでコード補完を実現するための強力なプラグインです。

nvim-cmp自体は「補完エンジン(枠組み)」としての役割を担うため、単体では動作しません。バッファ補完やファイルパス補完など、表示したい機能に合わせた「ソース(拡張プラグイン)」と組み合わせて使用するのが特徴です。

Tips: ビルトイン機能との違い 実はNeovimには標準でコード補完(Ctrl+nでバッファ補完、Ctrl+xとCtrl+fでファイルパス補完など)が備わっていますが、一度に一つのソースからしか候補を出せません。nvim-cmpを使えば、「LSP、バッファ、ファイルパス」といった異なる種類の候補を一つのリストにまとめて表示できます。

nvim-cmpのインストールと設定

今回は、プラグインマネージャに lazy.nvim を使用した設定例を紹介します。

lazy.nvimの設定やプラグインのインストール方法は「【Neovim】 lazy.nvimのインストール・設定方法」で解説しています。

インストールするプラグイン

  • hrsh7th/nvim-cmp: 本体
  • hrsh7th/cmp-buffer: バッファ補完(開いているファイル内の文字列)
  • hrsh7th/cmp-path: ファイルパス補完
  • hrsh7th/cmp-cmdline: コマンドライン補完
  • hrsh7th/cmp-nvim-lsp: LSP補完
  • saadparwaiz1/cmp_luasnip & L3MON4D3/LuaSnip: スニペット補完

設定ファイル例

-- ~/.config/nvim/lua/plugins/cmp.lua
return {
    "hrsh7th/nvim-cmp",
    dependencies = {
      "hrsh7th/cmp-buffer",
      "hrsh7th/cmp-path",
      "hrsh7th/cmp-cmdline",
      "hrsh7th/cmp-nvim-lsp",
      "saadparwaiz1/cmp_luasnip",
      {
        "L3MON4D3/LuaSnip",
        dependencies = { "rafamadriz/friendly-snippets" },
        config = function()
          require("luasnip.loaders.from_vscode").lazy_load()
        end,
      },
    },
    config = function()
      local cmp = require("cmp")
      local luasnip = require("luasnip")

      -- 1. 通常の基本設定 (Insertモード用)
      cmp.setup({
        mapping = cmp.mapping.preset.insert({
          ["<C-k>"] = cmp.mapping.select_prev_item(),
          ["<C-j>"] = cmp.mapping.select_next_item(),
          ['<C-b>'] = cmp.mapping.scroll_docs(-4),
          ['<C-f>'] = cmp.mapping.scroll_docs(4),
          ['<C-Space>'] = cmp.mapping.complete(),
          ['<C-e>'] = cmp.mapping.abort(),
          ['<CR>'] = cmp.mapping.confirm({ select = true }),
          ["<Tab>"] = cmp.mapping(function(fallback)
            if cmp.visible() then
              cmp.select_next_item()
            elseif luasnip.expand_or_jumpable() then
              luasnip.expand_or_jump()
            else
              fallback()
            end
          end, {
            "i",
            "s",
          }),
          ["<S-Tab>"] = cmp.mapping(function(fallback)
            if cmp.visible() then
              cmp.select_prev_item()
            elseif luasnip.jumpable(-1) then
              luasnip.jump(-1)
            else
              fallback()
            end
          end, {
            "i",
            "s",
          }),
        }),
        snippet = {
          expand = function(args)
            require'luasnip'.lsp_expand(args.body)
          end
        },
        formatting = {
          fields = { "abbr", "menu" },
          format = function(entry, vim_item)
            vim_item.menu = ({
              nvim_lsp = "[LSP]",
              buffer = "[Buffer]",
              path = "[Path]",
              luasnip = "[Snippet]",
            })[entry.source.name]
            return vim_item
          end,
        },
        sources = cmp.config.sources({
          { name = 'nvim_lsp' },
          { name = "buffer" },
          { name = "path" },
          { name = 'luasnip' },
        })
      })

      -- 2. 検索モード ('/' や '?') 用の設定
      cmp.setup.cmdline({ "/", "?" }, {
        mapping = cmp.mapping.preset.cmdline(),
        sources = {
          { name = "buffer" },
        },
      })

      -- 3. コマンドラインモード (':') 用の設定
      cmp.setup.cmdline(":", {
        mapping = cmp.mapping.preset.cmdline(),
        sources = cmp.config.sources({
          { name = "path" },
        }, {
          { name = "cmdline" },
        }),
      })
    end,
}

バッファ補完(hrsh7th/cmp-buffer)

cmp-buffer は、Neovimで開いているバッファに含まれる文字列を候補として表示します。

同じファイル内で定義した変数名やコメント内の単語を再利用したい場合に非常に便利です。デフォルトでは現在のファイルが対象ですが、設定により他の開いているファイルから候補を持ってくることも可能です。

Neovim cmp-bufferによるバッファ補完

ファイルパス補完(hrsh7th/cmp-path)

cmp-path は、ファイルシステム上のディレクトリやファイル名を補完します。

相対パス(./, ../)や絶対パス(/, ~/)を入力し始めると、自動的に候補が表示されます。単にファイル名を入力するだけでは反応しないため、パスの起点となる記号を入力するのがコツです。

Neovim cmp-pathによるファイルパス補完

コマンドライン補完(hrsh7th/cmp-cmdline)

cmp-cmdline を導入すると、Neovim下部のコマンドライン上でも補完が効くようになります。

  • 検索時 (/): バッファ内の単語を対象に検索ワードを補完できます。
  • コマンド時 (:): :edit:split などの入力時にファイルパスを補完したり、コマンドそのものを補完したりできます。

文字列検索でバッファ補完を表示:

Neovim文字列検索でcmp-cmdlineのバッファ補完使用

exコマンド入力でファイルパス補完を表示:

Neovimのexコマンドでcmp-cmdlineのパス名補完使用

LSP補完(hrsh7th/cmp-nvim-lsp)

LSPサーバと連携して、クラス名やメソッド名を補完します。

RubyのLSP補完例:

LSPの設定が別途必要です。「Neovim LSP」の記事で解説しています。

コードスニペット補完(saadparwaiz1/cmp_luasnip)

スニペット補完は、短いキーワードから定型文(テンプレート)を呼び出す機能です。

例えば、JavaScriptで clg と打てば console.log() に展開されるといった具合です。

  • LuaSnip: スニペットを管理・展開するエンジン。
  • friendly-snippets: 膨大な言語別のスニペット集(これを読み込むことで、自分で定義しなくてもすぐに使えます)。

使い方:

  1. 候補の中からスニペット(アイコンや [Snippet] 表記があるもの)を選択し、Enterで確定。
  2. 展開された後、Tabキーなどで引数や変数名の箇所へジャンプして入力を進められます。

例:Javascriptファイルで”fu”とタイプすると、functionのスニペットが表示される。

Neovim cmp_luasnipによるスニペット補完

スニペット展開時の関数名入力

Neovimでのコードスニペット入力

まとめ

nvim-cmpを導入することで、Neovimの操作感はIDE(統合開発環境)に大きく近づきます。

  • バッファ補完 でタイポ防止
  • ファイルパス補完 でパス入力のストレス解消
  • コマンドライン補完 で操作を高速化
  • LSP補完 でLSPと連携
  • スニペット補完 で定型文作成を爆速に

ここで紹介したプラグインはあくまで基本です。他にもGitのブランチ名を補完したり、絵文字を補完したりと、ニーズに合わせてカスタマイズできるのがnvim-cmpの真の魅力です。ぜひ自分好みの環境を構築してみてください。