Neovim 0.11+ 刷新ガイド:プラグインとの相互作用から理解する、モダンな LSP & Tree-sitter 設定術

1. はじめに

ウェブサイトのフレームワークを Gatsby から Astro.js へ移行することに決めた(移行作業は現在進行中で、詳細は後日執筆予定)。その準備として、長年放置していた Neovim 環境を全面的に見直すことにした。

数年ぶりに最新動向を調査したところ、Neovim 周辺の環境は驚くほど変化していた。かつては外部プラグインが担っていた機能の多くが Neovim 本体(Built-in)へ統合され、設定の作法が根本から変わっている。この大きなギャップと、ネット上に散見される古い情報に翻弄され、刷新作業は想像以上に困難を極めた。

今回の刷新では、この「変化」に真っ向から向き合い、プラグインへの依存を最小限に抑えた構成を目指した。本記事では、Neovim 0.11+ 時代における LSP (Language Server Protocol)Tree-sitter のモダンな設定方法を徹底解説する。

本記事のポイント

  • 2026年最新仕様への追従: 0.11+ で非推奨(Deprecated)となった古い書き方(setup_handlers 等)を完全に排除。
  • ネイティブ API の徹底活用: nvim-cmp 等の定番プラグインに頼らず、Neovim 本体の API で自動補完や設定管理を行う。
  • LSP のメカニズムを解剖: 本記事の山場として、「プラグインが支える Neovim の LSP 機能:仕組みを解き明かす」 セクションを用意。nvim-lspconfig, mason, mason-lspconfig が内部でどのように Neovim と相互作用しているのか、ソースコードレベルでその正体を明らかにする。

対象読者

  • すでに Neovim を使用しており、最新の LSP/Tree-sitter 設定に移行したい方。
  • nvim-lspconfig 等の破壊的変更に直面し、これまでの「お決まりの設定」が動かなくなって困っている方。
  • プラグインを減らし、Neovim 本体の機能を活かしたミニマルかつブラックボックスのない設定に興味がある方。

2. プラグイン構成

刷新にあたり採用したプラグイン構成を示す。また、参考までに刷新前の構成と、LSP へ一本化した背景についても触れる。

刷新後の構成

「無理のない範囲で、最小限のプラグインで十分な機能性を確保する」ことを重視した。すべてのプラグインが Lua で実装されており、2026 年現在のモダンで実用的な構成となっている。

  • UI
  • Editor
  • LSP
    • nvim-lspconfig: LSP サーバーのデフォルト設定集。
    • mason.nvim: LSP サーバー等のパッケージマネージャー。
    • mason-lspconfig.nvim: LSP サーバーの自動インストールと有効化を担う司令塔。
  • Formatter
    • conform.nvim: フォーマッタの一括管理。LSP 経由、または単体バイナリでの整形を柔軟に切り替えるために採用。

Note: 本構成では、従来必須とされていた自動補完プラグイン(nvim-cmp 等)を採用せず、Neovim 0.11+ の ネイティブ補完機能 を活用している。

プラグインマネージャーには lazy.nvim を利用。次節からは lazy.nvim の設定方法を確認したのち、特に仕様変更の大きかった nvim-treesitter と LSP の設定方法を深掘りしていく。

補足: 以前の構成との比較

刷新前は、Vim script で実装された定番プラグインを中心とした「Vim 時代」の構成をそのまま Neovim で動かしていた。今回の刷新でこれらを廃止し、Lua ベースのモダンな環境へ完全移行した。

  • vim-airlinelualine.nvim
  • ALELSP (Native)
  • lexima.vimnvim-autopairs

補足: ALE から LSP への移行の背景

かつての ALE (Asynchronous Lint Engine) は、非同期でのリンター・フォーマッター実行において唯一無二の存在だった。しかし、現在の LSP (Language Server Protocol) は「補完」「定義ジャンプ」「リファクタリング」までを含む統合的な開発体験を、単一のプロトコルで提供する世界標準となっている。

Neovim 本体に高機能な LSP クライアントが内蔵された今、外部エンジンである ALE を介在させる必要性は薄れた。LSP に一本化することで、動作の軽量化と高度なインテリジェンスを両立させている。

参照:News-0.11 - Neovim docs

3. lazy.nvim プラグインマネージャ

プラグインマネージャ lazy.nvim における設定(Plugin Spec)の要点を整理する。

参照:🔌 Plugin Spec | lazy.nvim

依存関係とオプション

dependencies プロパティは、そのプラグインが動作するために必要な他のプラグインを定義する。

opts プロパティは、プラグインの設定を table 型で定義する。opts で指定された値はデフォルト値とマージされ、プラグインの setup() 関数(より正確には次に説明するconfig)へ渡される。

プラグイン側の要請で、単に setup() 関数を呼び出すだけで良い場合は、opts = {} と記述すればよい。

例えば lualine.nvim の設定を見てみよう。

{
  "nvim-lualine/lualine.nvim",
  dependencies = { "nvim-tree/nvim-web-devicons", "rebelot/kanagawa.nvim" },
  opts = {
    options = { theme = "kanagawa" },
  },
},

lualine.nvim はアイコン表示のために nvim-web-devicons に依存している。また、テーマとして kanagawa.nvim が提供する kanagawa を指定している。

補足: kanagawa.nvim のドキュメントには lualine.nvim との連携についての直接の記載はないが、lualine.nvimWriting a theme · nvim-lualine/lualine.nvim Wiki によれば、lua/lualine/themes 以下のファイルがテーマとして利用可能とされている。実際に kanagawa.nvim の構成ファイルを確認すると lua/lualine/themes/kanagawa.lua が存在することがわかる。

optsconfig

プラグインの設定では config プロパティも頻繁に利用される。両者の主な違いは以下の通り。

  • opts: table 型で記述する(宣言的)。
  • config: 自由な Lua コードを記述できる(手続き的)。

例えば、カラーテーマの設定で vim.cmd.colorscheme("kanagawa") のような API を呼び出したいときは、opts ではなく config が必要になる。

注意: optsconfig を併用することも可能だが、その場合は config 内で setup() を明示的に呼ばない限り、setup() は実行されないopts 単体で指定した場合に限り、lazy.nvim が背後で自動的に setup(opts) を呼び出す仕組みになっている。

config指定なし config指定あり
opts指定なし setup()実行されない setup()実行されない
opts指定あり setup(opts)実行される setup()実行されない

補足: opts指定なし、config = true と書いた場合も、setup({}) が呼ばれるという隠れた仕様がある。

併用のベストプラクティス

kanagawa.nvim を例に、正しい併用方法を見てみよう。

{
  "rebelot/kanagawa.nvim",
  opts = {
    theme = "wave",
    transparent = true,
  },
  config = function(_, opts)
    -- 1. opts の内容を反映させて setup() を実行
    require("kanagawa").setup(opts)
    -- 2. setup() の後に API を呼び出してカラースキームを適用
    vim.cmd.colorscheme("kanagawa")
  end,
},

ここでは opts で基本設定(テーマや背景透過)を行い、config で Neovim API を呼び出している。

optsconfigを併用しているので、configで明示的にsetup()を呼ぶ必要がある。ポイントは、config 関数の第二引数として opts を受け取れる点だ。これにより、opts プロパティで定義した値を setup() 関数へ確実に渡すことができる。

補足: config = function(_, opts) の第一引数にはプラグインのスペック自体が渡されるが、通常は使わないため _ で受け流すのが慣習。


アンチパターン

optsを使わずに、すべて config 内に setup() を含めた実装も考えられる。では、optsconfigのどちらを使うべきか?

{
  "rebelot/kanagawa.nvim",
  config = function()
    require("kanagawa").setup({
      theme = "wave",
      transparent = true,
    })
    -- setup()の後にカラースキームを適用
    vim.cmd.colorscheme("kanagawa")
  end,
},

lazy.nvim のドキュメントでは可能な限り opts の使用を推奨 している。

Always use opts instead of config when possible. config is almost never needed.

参照:🔌 Plugin Spec | lazy.nvim

これは、opts を使うことで複数の場所(プラグイン定義や他のファイル)からの設定変更をマージしやすくなり、設定の柔軟性と再利用性が高まるためだと考えられる。

まとめ

  • 基本的には opts を推奨。
  • API 呼び出しなどの手続き的な処理が必要な場合のみ config を併用する。
  • 併用する際は、config 内で require("プラグイン名").setup(opts) を呼び出すのを忘れないこと!

4. nvim-treesitter: シンタックスハイライト

さて、シンタックスハイライトを可能にするプラグイン nvim-treesitter の設定を説明していく。Neovim には正規表現ベースのシンタックスハイライトが標準備わっているが、AST(抽象構文木)ベースの Tree-sitter を利用することで、より高精度な色分けが可能になる。

最初に 2026 年現在の最新版に対応した設定方法と動作確認方法を示し、その背景にある破壊的変更と、現在の設定へ至るまでの道のりを解説する。

設定方法: パーサーのインストールとハイライトの有効化

lazy.nvimのPlugin Spec (lua/plugins/editor.lua)

{
  "nvim-treesitter/nvim-treesitter",
  lazy = false,
  build = ":TSUpdate",
  config = function()
    require("config.treesitter")
  end,
},

nvim-treesitter は lazy-loading(遅延読み込み)に原則非対応である。また、特定のバージョンの言語パーサー(lockfile.json で指定)でのみ動作することが保証されているため、プラグインのアップグレード時に :TSUpdate でパーサーを最新に更新するようにしている。

参照:Installation - README | nvim-treesitter/nvim-treesitter

設定ファイル (lua/config/treesitter.lua)

-- 1. パーサーのインストール
require("nvim-treesitter").install({
  "lua",
  "python",
  "rust",
})

-- 2. ハイライトの有効化
vim.api.nvim_create_autocmd("FileType", {
  callback = function(args)
    -- 2-1. FileType から Tree-sitter 用の言語名を取得
    local lang = vim.treesitter.language.get_lang(args.match)
    if not lang then return end  -- 対応言語がなければ早期リターン

    -- 2-2. その言語のパーサーが「実際に利用可能か」を確認
    -- pcall を使うことで、パーサーがない場合にエラーで止まるのを防ぐ
    local has_parser = pcall(vim.treesitter.get_parser, args.buf, lang)
    if has_parser then
      vim.treesitter.start(args.buf, lang)
    end
  end,
})

前半でインストールしたいパーサーを指定している。指定可能なパーサーは SUPPORTED_LANGUAGES.md | nvim-treesitter/nvim-treesitter で確認可能だ。

動作確認

Tree-sitter の稼働確認

設定した言語のファイルを開き、:InspectTree コマンドを実行する。右側に AST(構文木)が表示されれば、Tree-sitter は無事に動いている。

参照:inspect_tree() - Treesitter - Neovim docs

treesitter_syntax-tree.png

ハイライトのソース確認

Neovim には標準のハイライトがあるため、一見しただけでは Tree-sitter が有効か判別しにくい。確実な方法は、適当な単語の上で :Inspect を実行することだ。Treesitter セクション が表示されていれば、Tree-sitter によるハイライトが有効になっている。

参照:vim.inspect_pos() - Treesitter - Neovim docs

highlight_w_treesitter.png

もし、無効な場合は Syntax セクションが表示される。

highlight_wo_treesitter.png

2025 年 5 月のアップデート: main ブランチへの移行

2025 年 5 月、nvim-treesitter は大規模なリファクタリングを実施した。従来の master ブランチ に代わり、現在の main ブランチ が標準となっている。この変更には多くの破壊的変更が含まれているが、公式の移行ガイドが乏しいため、主な変更点を整理したい。

最大の変更点は、プラグインの役割が「パーサーとクエリのパッケージマネージャー」に絞られ、機能の実行自体は Neovim 本体が担うという分担になったことだ。

  • nvim-treesitter.configs モジュールの廃止
  • パーサーのインストール: ensure_installed の廃止 → install() 関数呼び出しへ
  • ハイライト: highlight.enable の廃止 → Neovim 本体の API 直接利用へ
  • インデント: indent.enable の廃止 → Neovim 本体の API 直接利用へ

つまり、次のような従来の master ブランチ向けの設定は、現在の main ブランチでは動作しない。

require("nvim-treesitter.configs").setup({ -- 廃止
  ensure_installed = { "lua", "python", "rust", "query" }, -- 廃止
  auto_install = true, -- 廃止
  highlight = { enable = true }, -- 廃止
  indent = { enable = true }, -- 廃止
})

補足: 以前はクエリファイルの解析のために query パーサーを明示的にインストールする慣習があったが、現在の main ブランチでは不要となっている。

この背景には、Neovim 0.10 以降で Tree-sitter 関連の機能が本体へ着実に取り込まれ、成熟したことがある。プラグイン独自の複雑なモジュールシステムを捨て、本体 API を活用する設計へと進化したのだ。

参照:News-0.9 - Neovim docs

参照:News-0.10 - Neovim docs

参照:Roadmap: Nvim-treesitter 1.0 · Issue #4767 · nvim-treesitter/nvim-treesitter

パーサーのインストール

明示的に install() 関数を呼び出す必要がある。

master(旧):

ensure_installed = { "lua", "python", "rust" },

参照:Modules - README | nvim-treesitter/nvim-treesitter at master

main(新):

require("nvim-treesitter").install({ "lua", "python", "rust" })

参照:Setup - README | nvim-treesitter/nvim-treesitter

ハイライト

Neovim 本体の API vim.treesitter.start() を直接利用してハイライトを有効化する。

master(旧):

highlight = { enable = true },

参照:Highlight - README | nvim-treesitter/nvim-treesitter at master

main(新):

vim.api.nvim_create_autocmd("FileType", {
  pattern = { "lua" },
  callback = function()
    vim.treesitter.start()
  end,
})

参照:Highlighting - README | nvim-treesitter/nvim-treesitter

参照:nvim_create_autocmd() - API - Neovim docs

参照:vim.treesitter.start() - Treesitter - Neovim docs

あくまでも 言語毎 に設定する必要があり、masterブランチのように一括で有効にするには工夫が必要。それについては、次の節で説明する。

インデント

インデントもハイライトと同様に、Neovim 本体の vim.bo.indentexpr を直接利用する。

master(旧):

indent = { enable = true },

参照:Indentation - README | nvim-treesitter/nvim-treesitter at master

main(新):

vim.api.nvim_create_autocmd("FileType", {
  pattern = { "lua", "python" },
  callback = function()
    vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()"
  end,
})

参照:Indentation - README | nvim-treesitter/nvim-treesitter

ちなみに、conform.nvim でフォーマッタを導入しているので、nvim-treesitterのインデント機能は有効にしない。

解説: 言語一括設定までの道のり

最新の main ブランチでは、ハイライトは言語ごとに vim.treesitter.start() を呼ぶ必要がある。これをどう汎用化するかが設定の鍵となる。

Step 1: 共通リスト方式

言語リスト langs を定義し、pattern = langs としてオートコマンドを登録する方法。

-- 共通の言語リスト
local langs = { "lua", "python", "rust" }

-- 1. パーサーのインストール
require("nvim-treesitter").install(langs)

-- 2. ハイライトの有効化
vim.api.nvim_create_autocmd("FileType", {
  pattern = langs, -- 言語を指定
  callback = function()
    vim.treesitter.start()
  end,
})

一見良さそうだが、.tsx ファイルのように「Neovimでのファイルタイプ名 (typescriptreacttsx)」と「Tree-sitterでのパーサー名 (tsx)」が異なる場合にハイライトが始まらないという問題がある。

Step 2: 汎用的な autocmd 方式

発想の転換で、「FileType が何であれ、パーサーがあるなら開始する」という汎用的な autocmd を定義する。

-- 2. ハイライトの有効化
vim.api.nvim_create_autocmd("FileType", {
  callback = function(args)
    -- 2-1. FileTypeからTree-sitter用の言語名を取得
    local lang = vim.treesitter.language.get_lang(args.match)

    -- 2-2. 開いたファイルの言語に対応するパーサーがあるか確認
    if lang then
      vim.treesitter.start(args.buf, lang)
    end
  end,
})

patternは取り除いて、FileTypeに無関係にコールバックを呼び出す。vim.treesitter.language.get_lang(args.match) を使うことで、ファイルタイプ名から正しいパーサー名を導き出せる。もし対応するパーサーがあれば、vim.treesitter.start() 関数でハイライトを有効化する。

参照:vim.treesitter.language.get_lang() - Treesitter - Neovim docs

Error detected while processing FileType Autocommands for "*":
Error executing lua callback: .../neovim/0.11.6/share/nvim/runtime/lua/vim/treesitter.lua:431: Parser could not be created for buffer 6 and language "TelescopePrompt"

FileTypeからTree-sitter用の言語名へ変換しているので上手くいきそうだが、このままだと Telescope 等の特殊バッファでエラー「Parser could not be created」が頻発する。

Step 3: パーサーの有無を pcall で厳密にチェック

最終的に行き着いたのが、冒頭の設定だ。

vim.treesitter.get_parserpcall(保護付き呼び出し)で実行し、パーサーが実際に存在する場合のみ start() を呼ぶ。これにより、未インストールの言語や特殊なバッファでもエラーを出さず、必要な場所でだけ確実にハイライトを効かせることが可能になった。

-- 2. ハイライトの有効化
vim.api.nvim_create_autocmd("FileType", {
  callback = function(args)
    -- 2-1. FileTypeからTree-sitter用の言語名を取得
    local lang = vim.treesitter.language.get_lang(args.match)
    if not lang then return end  -- 対応言語がなければ早期リターン

    -- 2-2. その言語のパーサーが「実際に利用可能か」を確認
    -- pcallを使うことで、パーサーがない場合にエラーで止まるのを防ぐ
    local has_parser = pcall(vim.treesitter.get_parser, args.buf, lang)
    if has_parser then
      vim.treesitter.start(args.buf, lang)
    end
  end,
})

参照:pcall() - Luaref - Neovim docs

5. LSP (Language Server Protocol)

LSP は、Neovim 本体の機能と 3 つのプラグイン(nvim-lspconfig, mason.nvim, mason-lspconfig.nvim)が組み合わさって動作する。

まず Neovim とこれらプラグインの関係を整理したのち、設定方法と使い方(ショートカット)を解説する。その上で、プラグインごとの詳細設定や 2025 年 5 月の大規模な仕様変更、さらにはネイティブ API を使った自動補完までを網羅する。最後に、各プラグインがどのように Neovim をサポートしているのか、その内部構造まで踏み込んでみたい。

Neovim と 3 つのプラグインの関係

Neovim 本体は LSP クライアントの実装を持っている。サーバーと通信して補完・定義ジャンプ・診断などを処理する仕組み自体は Neovim に組み込まれている。

一方で、「どのファイルでどのサーバーをどう起動するか」の 設定 や、LSP サーバー自体のインストール は自分で行う必要がある。そこを補うのが以下のプラグインだ。

参照:LSP - Neovim docs

  • nvim-lspconfig: LSP サーバーの設定集
    • 「どのファイルで、どのサーバーを、どう起動するか」のプリセットを定義。
  • mason.nvim: LSPサーバーのパッケージマネージャー
    • LSP サーバー等のバイナリを管理。brew や npm に相当するが、パッケージリストによる一括自動インストール機能(brew bundle 的なもの)は持たない。
  • mason-lspconfig.nvim:LSPサーバーの自動インストールと有効化を行う「司令塔」
    • mason.nvim を使ってサーバーを自動インストールし、それらを vim.lsp.enable() を介して Neovim 上で有効化する。

設定方法

ここでは Lua, Python 向けの設定例を示す。

lazy.nvimのPlugin Spec (lua/plugins/lsp.lua)

mason-lspconfig.nvim は LSP サーバーの自動インストールと有効化の責務を負う「司令塔」と言えるので、mason-lspconfig.nvim を中心に据える。ensure_installed で必要なサーバーを指定する。

{
  "mason-org/mason-lspconfig.nvim",
  dependencies = {
    {
      "mason-org/mason.nvim",
      opts = {}, -- setup()が必要なためoptsを指定
    },
    "neovim/nvim-lspconfig",
  },
  opts = {
    ensure_installed = {
      "lua_ls",
      "stylua",
      "ruff",
      "pyright",
    },
  },
  config = function(_, opts)
    require("mason-lspconfig").setup(opts)
    require("config.lsp") -- 言語個別設定や自動補完の設定を読み込む
  end,
},

最後のrequire("config.lsp")で、追加の設定ファイル (lua/config/lsp.lua)を読み込む。そこでは言語個別のカスタマイズや自動補完の設定を行う。前者はnvim-lspconfigの節で説明する。後者の自動補完については、この章の後半で説明するので一旦保留。

Neovim の診断設定 (init.lua など)

vim.diagnostic.config で、LSP によるエラー表示(Diagnostics)をカスタマイズする。

vim.diagnostic.config({
  -- エラー箇所に下線を引く(default: true)
  underline = true,
  -- 行末にエラーメッセージを表示(default: false)
  virtual_text = true,
  -- 行番号の横にアイコンを表示(default: true)
  signs = true,
  -- 入力中は表示を更新しない(default: true)
  update_in_insert = false,
  -- 重大なエラーを優先表示(default: false)
  severity_sort = true,
})

参照:vim.diagnostic.config() - Diagnostic - Neovim docs

参照:vim.diagnostic.Opts - Diagnostic - Neovim docs

使い方: コマンドとキーボードショートカット

動作確認

ファイルを開いて:LspInfoコマンドを実行し、vim.lsp: Active Clients にサーバー名が表示されれば有効だ。下はPythonのコードを開いた場合の例。

vim.lsp: Active Clients
- pyright (id: 1)
- ruff (id: 2)

標準の LSP クライアント機能

Neovim 0.11+ では、以下の主要機能がデフォルトでマッピングされている。

  • K: ホバー表示(型や説明の確認)
  • gd: 定義ジャンプ(関数定義)
  • gD: 定義のプレビュー / 型定義ジャンプ
  • grn: リネーム (Rename)
  • gra: コードアクション (Code Action)
  • grr: 参照一覧 (References)

参照:Defaults - LSP - Neovim docs

例: Kで関数定義をホバー表示

lsp_K.png

エラーと警告(Diagnostics)

  • [d: 前のエラー/警告へ移動
  • ]d: 次のエラー/警告へ移動
  • <C-w>d: 浮遊ウィンドウでエラーの詳細を表示

参照:Defaults - Diagnostic - Neovim docs

コードにエラーがあると、エラーの箇所が下線が引かれ、右側にエラーが表示される。エラーがポップアップでの内容表示もできる。

lsp_diagnostics.png

nvim-lspconfig の設定

nvim-lspconfig

各 LSP サーバーのデフォルト設定の内容は、LSP configs | neovim/nvim-lspconfig で確認可能だ。

LSPサーバ設定のカスタマイズ

特定のサーバー設定を上書きしたい場合は、Neovim 本体の vim.lsp.config({name}, {cfg}) 関数を使用する。この関数は、lspconfig が提供するデフォルト設定に、ユーザーが指定した値をマージしてくれる

参照:vim.lsp.config() - LSP - Neovim docs

参照:vim.lsp.Config - LSP - Neovim docs

参照:vim.lsp.ClientConfig - LSP - Neovim docs

例えば、デフォルトの Lua サーバー設定では vim というグローバル変数が認識されず、警告が表示されてしまう。

lsp_global-vim-warning.png

これを解消するには、以下のように設定を追加する。

vim.lsp.config("lua_ls", {
  settings = {
    Lua = {
      runtime = {
        version = "LuaJIT",
      },
      diagnostics = {
        globals = {
          "vim",
        },
      },
    },
  },
})

diagnostics.globalsvimを既知のグローバル変数として登録している。

Lua Language Server | Wiki: diagnostics.globals

補足: 2025 年のアップデートと設計思想の変化

Neovim 0.5 でLSPクライアントが組み込まれた当初は、設定のための高レベルAPIが不足していたため、nvim-lspconfig がサーバーの有効化や設定のカスタマイズといった幅広い機能を担っていた。しかし Neovim 0.11 で vim.lsp.configvim.lsp.enable という高レベルなAPIが追加されたことで、nvim-lspconfig は「データ(設定集)を提供するリポジトリ」という役割に専念するようになった。LSPサーバーの有効化などは Neovim 本体の API が受け持つ形となった。

  • 非推奨(Deprecated): require('lspconfig')
  • 現在(Modern):
    • 設定のカスタマイズ:vim.lsp.config('lua_ls', { ... })
    • 有効化:vim.lsp.enable('lua_ls')

参照:Important - README | neovim/nvim-lspconfig

従来の require('lspconfig').<lang>.setup({}) 方式は利用できなくなっているため、注意が必要だ。

この設計変更の最大の恩恵は、「お決まりの設定(ボイラープレート)」が消滅したこと にある。かつては補完のために capabilities を自前で用意したり、キーマップのために巨大な on_attach 関数を定義して各サーバーに渡していたが、今はもう不要だ。vim.lsp.config がそれらを裏側で適切にマージ・処理してくれるため、ユーザーは「自分が行いたいカスタマイズ」だけに集中できるようになった。

mason.nvim の設定

mason.nvim

インストールと利用

lazy.nvim で管理する場合、setup() を実行させるために必ず opts = {}(または詳細設定)を記述する。これがないと PATH が通らず、サーバーが起動しない。

{
    "mason-org/mason.nvim",
    opts = {}
}

参照:Installation & Usage - README | mason-org/mason.nvim

コマンド

:MasonコマンドでMasonのウィンドウが開く。ここでLSPサーバーのインストール、更新、アンインストールなどができる。

lsp_mason.png

mason-lspconfig.nvim の設定

mason-lspconfig.nvim

設定

もう一度、mason-lspconfig.nvim の設定ファイル (Plugin Spec lua/plugins/lsp.lua) を振り返ってみる。mason-lspconfig.nvim は LSP サーバーの自動インストールと有効化の責務を負う「司令塔」だ。optsensure_installedプロパティで、インストールするLSPサーバーを指定する。これらはnvim-lspconfigのLSPサーバ名で指定する必要があり、そのリストはLSP configs | neovim/nvim-lspconfigを参照。

{
  "mason-org/mason-lspconfig.nvim",
  dependencies = {
    {
      "mason-org/mason.nvim",
      opts = {},
    },
    "neovim/nvim-lspconfig",
  },
  opts = {
    ensure_installed = {
      "lua_ls",
      "stylua",
      "ruff",
      "pyright",
    },
  },
  config = function(_, opts)
    require("mason-lspconfig").setup(opts)
    require("config.lsp") -- ここで前述の vim.lsp.config などを読み込む
  end,
},

ここで config プロパティを使い、setup(opts) の後に独自の config.lsp を読み込んでいる。これは lazy.nvim の節で解説した「optsconfig の併用パターン」の実践例である。

補足: v2.0.0 2025年のアップデートと設計思想の変化

Neovim 0.11 の登場に合わせ、mason-lspconfig.nvim も v2.0.0(2025年5月)で大幅に改められた。

  • 廃止: handlers 設定、.setup_handlers() 関数。
  • 新機能: automatic_enable(デフォルト true)。
    • mason でインストールされたサーバーを自動的に vim.lsp.enable() へ登録してくれる。

ユーザーが本体 API(vim.lsp.config)を直接叩くことを邪魔せず、裏側で便利にインストールと有効化をサポートする、という極めてミニマルな設計に進化している。

参照:Release v2.0.0 | mason-org/mason-lspconfig.nvim

自動補完:プラグインなしのネイティブ構成

自動補完機能は従来、nvim-cmp を始めとするプラグインによって実現されてきたが、2026 年現在は状況が変わりつつある。Neovim 0.11 で自動補完を直接制御する vim.lsp.completion.enable() が導入され、LSP ソースによるオプトイン式の自動補完が標準で提供されるようになった。

lsp_completion.png

基本設定

ruff (Python) での設定例を示す。先ほどの追加設定ファイル (lua/config/lsp.lua)に、下のコードを追加する。

まず、補完メニューの挙動を調整する。

-- menuone: 候補が1つでもメニューを出す
-- noselect: メニューが出た際に自動で最初の候補を選択しない
-- noinsert: 候補を選んだだけでバッファに挿入しない
vim.opt.completeopt = { "menuone", "noselect", "noinsert" }

次に、LSP がアタッチした際に自動補完を有効化する設定を記述する。

vim.lsp.config("ruff", {
  on_attach = function(client, bufnr)
    vim.lsp.completion.enable(true, client.id, bufnr, { autotrigger = true })
  end,
})

参照:lsp.completion - LSP - Neovim docs

すべての言語で一括設定

言語ごとに設定するのは手間がかかるので、LspAttach オートコマンドを使い、すべての言語で一括設定する方法を採用する。Tree-sitterのときの vim.api.nvim_create_autocmd() が、ここでも使える。

上のvim.lsp.config(...)の部分を下記のものに入れ替える。

-- 全てのLSPがアタッチした際に実行される共通設定
vim.api.nvim_create_autocmd("LspAttach", {
  callback = function(args)
    local client = vim.lsp.get_client_by_id(args.data.client_id)
    if not client then return end

    -- LSPが補完機能を持っているか確認
    if client:supports_method("textDocument/completion") then
      -- ネイティブの自動補完を有効化
      vim.lsp.completion.enable(true, client.id, args.buf, { autotrigger = true })
    end
  end,
})

参照:CONFIGURE ON ATTACH - LSP - Neovim docs

ネイティブ補完の操作方法

プラグイン(nvim-cmp)と異なり、Neovim 標準の補完(ins-completion)の作法に従う点に注意。

  • 選択: <C-n> (Next) / <C-p> (Prev)
  • 確定: <C-y> (Yes)
  • 中断: <C-e> (Exit)

Tips: すべての文字で自動補完をトリガーする

デフォルトでは . (ドット) など特定の文字を入力したときのみ補完メニューが表示される。これを「あらゆる文字入力」で反応させたい場合は、triggerCharacters を拡張する以下のハックが有効だ。

vim.api.nvim_create_autocmd("LspAttach", {
  callback = function(args)
    local client = vim.lsp.get_client_by_id(args.data.client_id)
    if not client or not client:supports_method("textDocument/completion") then
      return
    end

    -- triggerCharactersを拡大し、英数字を含むすべてのキー入力でメニューを出す
    local chars = {}
    for i = 32, 126 do
      table.insert(chars, string.char(i))
    end
    client.server_capabilities.completionProvider.triggerCharacters = chars

    vim.lsp.completion.enable(true, client.id, args.buf, { autotrigger = true })
  end,
})

補足: すべてのキー入力をトリガーにすると、LSP サーバー側の負荷が高まる可能性がある。動作が重いと感じる場合は、特定の文字(:/ など)に絞って登録するなどの調整を行ってほしい。

プラグインが支える Neovim の LSP 機能:仕組みを解き明かす

プラグインが Neovim の LSP 機能をどのようにサポートしているのか、そのメカニズムを一歩踏み込んで解き明かしてみたい。あえて「プラグインを使わない」最小構成と比較することで、各プラグインの役割を明確にする。

Neovim だけで LSP を使う(最小構成)

プラグインを使わない場合、以下の 3 ステップが必要になる。

  1. LSP サーバーの準備: 手動でバイナリをインストールし、PATH を通す。
  2. vim.lsp.config() で設定: サーバーの起動コマンドや対象ファイルを定義。
  3. vim.lsp.enable() で有効化: 自動起動のスイッチを入れる。

参照:QUICKSTART - LSP - Neovim docs

1. LSP サーバーの準備

まずLSPサーバーのバイナリをシステムにインストールし、コマンドとして実行できる状態にする。たとえば lua-language-server であれば、インストール後に lua-language-server コマンドが PATH 上で使えることを確認する。

2. vim.lsp.config() で設定

LSPサーバーをどう起動するかを Neovim に伝える。

vim.lsp.config('lua_ls', {
  cmd = { 'lua-language-server' },    -- 起動コマンド
  filetypes = { 'lua' },              -- 対象のファイルタイプ
  root_markers = { '.luarc.json', '.git' }, -- プロジェクトルートの目印
  settings = {
    Lua = { runtime = { version = 'LuaJIT' } }
  },
})

lua_ls という名前で LSP サーバを定義している。filetypes で「どのファイル」に対して、cmd で「どのコマンド」で LSP サーバを起動するか設定している。

この時点ではまだサーバーは起動しない。あくまで「設定」だけである。

3. vim.lsp.enable() で有効化
vim.lsp.enable('lua_ls')

これにより、filetypes に指定したファイルを開いたとき、Neovim が自動的にサーバーを起動してアタッチするようになる。

LSP 設定の定義方法と優先度

LSP設定は vim.lsp.config() のように コード として定義以外にも、ファイル として定義する方法もある。

  • <rtp>lsp/<name>.lua (プラグイン等)
  • <rtp>/after/lsp/<name>.lua (ユーザー)

複数の場所で設定が定義された場合、以下の順序でマージされ、右側(後から呼ばれるもの)が優先される。

低 ─────────────────────────────────────────────── 高
lsp/<name>.lua (プラグイン等) → after/lsp/<name>.lua (ユーザー) → vim.lsp.config() (コード定義)

参照:CONFIG - LSP - Neovim docs

優先度とconfigのマージについて

参照:HOW CONFIGS ARE MERGED - LSP - Neovim docs

参照:Config priority - README | neovim/nvim-lspconfig

nvim-lspconfig の仕組み:lsp/ ディレクトリ

nvim-lspconfig がやっていることは、実は非常にシンプルだ。Neovim 0.11 の「<rtp>/lsp/ 以下にある Lua ファイルを LSP 設定として読み込む」という仕様をフル活用している。

nvim-lspconfig/
└── lsp/
    ├── lua_ls.lua       ← lua-language-serverの設定
    ├── pyright.lua      ← pyrightの設定
    └── ts_ls.lua        ← TypeScript Language Serverの設定

たとえば lsp/lua_ls.lua の実体はこのようなテーブルを返すファイルである。

-- nvim-lspconfig/lsp/lua_ls.lua(抜粋・簡略化)
return {
  cmd = { 'lua-language-server' },
  filetypes = { 'lua' },
  root_markers = { '.luarc.json', '.luarc.jsonc', '.git' },
  settings = {
    Lua = { runtime = { version = 'LuaJIT' } }
  },
}

nvim-lspconfig/lsp/lua_ls.lua | neovim/nvim-lspconfig

このプラグインを導入するだけで、Neovim は数百種類の LSP 設定を「知っている」状態になる。

補足:デフォルト設定の上書き

先ほどの節「nvim-lspconfig の設定」では、vim.lsp.config()を使って nvim-lspconfigなどのプラグインが提供するデフォルト設定 lsp/*.lua を上書きしてきた。

一方、「LSP 設定の定義方法と優先度」で説明したように、after/ ディレクトリを利用する方法もある。nvim-lspconfig などのプラグインが lsp/lua_ls.lua でデフォルト設定を提供していても、ユーザーが after/lsp/lua_ls.lua に設定を書けばそちらが優先される。

mason.nvim の仕組み:PATH の動的書き換え

mason.nvim の核心は、バイナリを管理するだけでなく、Neovim 内部の環境変数 PATH を操作することにある。

プラグインの実装を少し覗いてみよう。

require('mason').setup()

これを呼び出すと、Neovim のパス vim.env.PATH の先頭に、self:bin() (= "~/.local/share/nvim/mason/bin") が追加される。これは mason.nvim がバイナリをインストールする専用ディレクトリだ。

-- mason.nvim/lua/mason-core/installer/InstallLocation.lua (概略)
function InstallLocation:set_env(opts)
    -- インストール先 bin ディレクトリを PATH の先頭に追加
    if opts.PATH == "prepend" then
        vim.env.PATH = self:bin() .. platform.path_sep .. vim.env.PATH
    end
end

mason.nvim/lua/mason-core/installer/InstallLocation.lua | mason-org/mason.nvim

これにより、lspconfig 側の設定が cmd = { 'lua-language-server' } とだけ書いてあっても、Neovim は mason.nvim が持っているバイナリを正しく見つけることができる。

mason-lspconfig.nvim の仕組み:LSP サーバーのインストールと自動有効化

このプラグインの最大の仕事は「LSP サーバーのインストール」と「自動有効化」だ。また、そのために lspconfig 名(lua_ls)と mason.nvim パッケージ名(lua-language-server)の「名前変換」が必要不可欠となっている。

require('mason-lspconfig').setup({
  ensure_installed = { 'lua_ls' },  -- lspconfig名で指定
})

これを例に setup() の処理を追いかける。

名前変換

mason-lspconfig は内部で、lspconfig 名(lua_ls)と mason.nvim パッケージ名(lua-language-server)の巨大な マッピングテーブル を保持する。

lspconfig_to_package = {
    lua_ls            = "lua-language-server",
    pyright           = "pyright",
    ts_ls             = "typescript-language-server",
    rust_analyzer     = "rust-analyzer",
    -- ...(正引きテーブル)
},
package_to_lspconfig = {
    ["lua-language-server"]        = "lua_ls",
    ["pyright"]                    = "pyright",
    -- ...(逆引きテーブル)
},

サーバーのマッピング情報は mason-lspconfig 本体にバンドルされるのではなく、mason-registry 経由でホストされる方式になっている。

-- mason-lspconfig.nvim/lua/mason-lspconfig/mappings.lua
-- mason-registryからパッケージ仕様を取得
local cached_specs = _.lazy(registry.get_all_package_specs)
registry:on("update:success", function()
    cached_specs = _.lazy(registry.get_all_package_specs)
end)

function M.get_mason_map()
    ---@type table<string, string>
    local package_to_lspconfig = {}
    for _, pkg_spec in ipairs(cached_specs()) do
        -- lspconfig名を取得
        local lspconfig = vim.tbl_get(pkg_spec, "neovim", "lspconfig")
        if lspconfig then
            -- 逆引き: Masonパッケージ → lspconfig名のテーブルを作成
            package_to_lspconfig[pkg_spec.name] = lspconfig
        end
    end

    return {
        package_to_lspconfig = package_to_lspconfig,
        -- 正引き: lspconfig名 → Masonパッケージのテーブルを逆変換で作成
        lspconfig_to_package = _.invert(package_to_lspconfig),
    }
end

mason-lspconfig.nvim/lua/mason-lspconfig/mappings.lua | mason-org/mason-lspconfig.nvim

LSPサーバーのインストール

ensure_installed = { 'lua_ls' } と指定されている。get_mason_map() を利用して lspconfig 名(lua_ls)から mason.nvim パッケージ名(lua-language-server)に名前変換しながら、lua-language-server のインストールを mason.nvim に対して依頼する。

-- mason-lspconfig.nvim/lua/mason-lspconfig/features/ensure_installed.lua
for _, server_identifier in ipairs(settings.current.ensure_installed) do
    local Package = require "mason-core.package"

    local server_name, version = Package.Parse(server_identifier)
    resolve_package(server_name) -- get_mason_map()による名前変換が行われる
        :if_present(
            ---@param pkg Package
            function(pkg)
                if not pkg:is_installed() and not pkg:is_installing() then
                    -- mason へ pkg のインストールを依頼
                    require("mason-lspconfig.install").install(pkg, version)
                end
            end
        )

mason-lspconfig.nvim/lua/mason-lspconfig/features/ensure_installed.lua | mason-org/mason-lspconfig.nvim

自動有効化

mason.nvim でインストール済みのパッケージをスキャンし、それに対応する lspconfig 名を特定して(名前の逆変換)、自動的に vim.lsp.enable() を呼び出す。

-- mason-lspconfig.nvim/lua/mason-lspconfig/features/automatic_enable.lua
-- インストールされた全てのパッケージをenable
enable_all = function()
    _.each(enable_server, registry.get_installed_package_names())
end,

---@param mason_pkg string | Package
local function enable_server(mason_pkg)
	-- 中略

	-- get_mason_map()でMasonパッケージ名からlspconfig名に逆変換
    local lspconfig_name = mappings.get_mason_map().package_to_lspconfig[mason_pkg]

	-- 中略

	-- LSP を有効化
    vim.lsp.enable(lspconfig_name)
    enabled_servers[lspconfig_name] = true
end

mason-lspconfig.nvim/lua/mason-lspconfig/features/automatic_enable.lua | mason-org/mason-lspconfig.nvim

コメント: こうやって見てみると、mason.nvimmason-lspconfig.nvim は密で、もう一体化しても良さそうな気がする。

全体の流れのまとめ

  • nvim-lspconfig(runtimepath 経由)
    • lsp/lua_ls.lua を提供
      • cmd / filetypes / root_markers などのデフォルト設定
  • mason.nvim setup()
    • PATHmason/bin/ を追加
  • mason-lspconfig setup()
    • lua_lslua-language-server に名前変換
    • lua-language-server をインストール
    • lua_ls を自動有効化
  • Neovim 本体
    • lua ファイルを開く
    • lua-language-server を起動・アタッチ
    • 補完・定義ジャンプ・診断などが使えるようになる

これで、Neovim とプラグインが内部でどのように相互作用して、LSP機能を実現しているのかが明確になった。

6. おわりに:今後の展望

本記事では、2026年現在の最新仕様に基づいた「ミニマルかつ強力な設定」を構築してきた。しかし、Neovimの進化は止まらない。これからのカスタマイズに向けた展望をいくつか挙げて締めくくりたい。

  • Lua 学習の重要性
    • もはやプラグインのオプションをコピペするだけの時代は終わった。vim.apivim.lsp を直接叩くための Lua の基礎体力があれば、Neovim はさらに自由自在な道具へと進化する。
  • さらなる機能拡張
    • 今回紹介した基盤の上に、neo-tree.nvim(ファイラー)や gitsigns.nvim(Git 連携)などを加えれば、IDE と遜色ない開発環境が完成するだろう。
  • LSP 周りの再考
    • パッケージ管理: mason.nvim に頼らず、Homebrew でシステム全体を管理するか、あるいは uvnpm を用いてプロジェクト単位で LSP サーバーを管理する手法も検討の余地がある。
    • 自動補完: 今回はネイティブ機能を採用したが、より高度な UI やスニペット連携を求めるなら、nvim-cmp や新興の blink.cmp などの導入も一つの選択肢だ。まずは標準機能で使い込み、自分に何が足りないかを見極めていきたい。
  • 情報の鮮度を保つために
    • Neovim の世界では「昨日の常識が今日の非推奨」になることも珍しくない。日頃から :checkhealth を実行し、公式リリースの Changelog を追う習慣が、安定した環境を保つ唯一の道だ。

Neovim の設定に「完成」はない。この記事が、あなたの Neovim ライフを支える確かな一歩となれば幸いである。