本文へ移動
購読
GUIDE#devtools更新 2026.09.24

Neovim入門:init.luaの書き方とLSP・補完の設定

Neovim 0.12で動作確認したinit.luaの書き方。組み込みのvim.packでのプラグイン管理、vim.lsp.configとpyrightでの診断、autocompleteによる補完まで。

level
初級
time
約30分
stack
Neovim 0.12 / nvim-lspconfig / pyright / Ubuntu 24.04
author
@ryoma_t
// 前提条件
Vimの基本操作(挿入モードと保存・終了)ができること / Node.jsを入れられること(pyrightの実行に必要)
Neovimで2つのファイルを左右に開き、pyrightのLSP診断で未定義の変数と型の誤りが表示された画面
画像:nagoya.dev(実際の画面・実行結果をもとに作成)

Neovimとは、Vimから派生したテキストエディタで、設定をLuaで書け、LSP(Language Server Protocol)のクライアントやプラグインの管理機能を本体に持っているのが特徴です。この記事のNeovimの設定は、執筆時点の最新安定版Neovim 0.12.5で実際に動かして確かめたinit.lua 1枚で、外部プラグインはnvim-lspconfigだけです。

Ubuntu 24.04のaptで入るNeovimは0.9.5で、ここで使うvim.lsp.config(0.11で追加)とvim.pack(0.12で追加)がありません。GitHubのリリース版を入れるところから始め、Pythonのコードでpyrightの診断と補完が出る状態まで進めます。

この記事でわかること

  • Neovim 0.12をGitHubのリリースから入れる手順と、設定ファイルの場所
  • init.luaの基本オプションとキーマップ、Leaderキーの確かめ方
  • 0.12の組み込みプラグインマネージャーvim.packとロックファイルの扱い
  • vim.lsp.config/vim.lsp.enableでpyrightを動かし、診断と補完を出すまで
  • LSPが黙って動かないときの:checkhealth vim.lspの読み方

Neovimとは:Vimとの違いと0.11・0.12で変わった設定の書き方

NeovimはVimのキー操作とVim scriptの大半を引き継いだうえで、設定とプラグインをLuaで書けるようにしたエディタです。LSPクライアント、構文解析ライブラリのTreesitter、端末エミュレーター(:terminal)を本体に持ち、既定値もVimと違ってhlsearchやマウス操作が最初から有効になっています。

ここ2回のリリースで、設定の定番が大きく変わりました。0.11でLSPの設定を本体だけで書けるvim.lsp.configとvim.lsp.enable、LSP向けの既定キーマップ、LSPを使う補完(vim.lsp.completion)が入り、0.12でプラグインマネージャーのvim.packと、入力中に補完候補を出す'autocomplete'オプションが加わりました。検索で見つかる設定例の多くはそれ以前のもので、書き方が次のように違います。

やること0.12での書き方(この記事)以前の定番
プラグイン管理vim.pack.add(組み込み)lazy.nvimなどのプラグイン
LSPの設定vim.lsp.config+vim.lsp.enablerequire("lspconfig").pyright.setup({})
LSPサーバーの導入npm・mise・uvなど普段のパッケージ管理mason.nvim
補完'autocomplete'+vim.lsp.completionnvim-cmp
LSPのキーマップ既定のgrn・gra・grrなどon_attachで自分で定義

プラグインを減らすと、Neovimを更新したときに壊れる箇所が減ります。反対に、0.10以前のNeovimしか入れられないサーバーで使う設定なら、右列の書き方を選ぶしかありません。

GitHubのリリースからNeovim 0.12を入れる

Linuxは公式リリースのtarballを/optに展開し、/usr/local/binにリンクを張るのが確実です。vim.packはプラグインの取得にgitコマンドを使うので、gitも入れておきます。

terminal
sudo apt install -y git curl
curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz
sudo tar -C /opt -xzf nvim-linux-x86_64.tar.gz
sudo ln -sf /opt/nvim-linux-x86_64/bin/nvim /usr/local/bin/nvim
nvim --version | head -1

最後の行がNVIM v0.12.5のように0.12以上を表示すれば完了です。同じリリースページには、ARM機向けのnvim-linux-arm64.tar.gz、macOS向けのnvim-macos-arm64.tar.gz、Windows向けのインストーラーnvim-win64.msiも並んでいます(ファイル名は執筆時点)。aptの0.9.5が先に入っているとPATHの順で古い方が起動することがあるので、which -a nvimで確かめ、不要ならsudo apt remove neovimで消しておきます。

init.luaの書き方:Neovimの設定ファイルの場所と中身

設定ファイルは、LinuxとmacOSなら~/.config/nvim/init.lua、Windowsなら%LOCALAPPDATA%\nvim\init.luaです。環境変数XDG_CONFIG_HOMEやNVIM_APPNAMEで場所が変わるため、迷ったらNeovimの中で:echo stdpath("config")を実行して表示されたディレクトリを使います。この記事で使うinit.luaの全体は次のとおりです。

~/.config/nvim/init.lua
-- Neovim 0.12 以降が前提

-- Leader キーは、<leader> を使うキーマップより前に決める
vim.g.mapleader = " "
vim.g.maplocalleader = " "

-- 基本オプション
local opt = vim.opt
opt.number = true
opt.relativenumber = true
opt.expandtab = true
opt.shiftwidth = 4
opt.tabstop = 4
opt.ignorecase = true
opt.smartcase = true
opt.signcolumn = "yes"
opt.undofile = true
opt.splitright = true
opt.splitbelow = true
opt.scrolloff = 5
opt.updatetime = 250
opt.completeopt = { "menuone", "noselect", "popup" }

-- 補完:入力中に候補を出し、LSP(omnifunc)も候補元に加える
vim.o.autocomplete = true
vim.opt.complete:append("o")

-- キーマップ
vim.keymap.set("n", "<leader>w", "<cmd>write<cr>", { desc = "保存" })
vim.keymap.set("n", "<Esc>", "<cmd>nohlsearch<cr>")
vim.keymap.set("n", "<leader>e", vim.diagnostic.open_float, { desc = "診断を表示" })

-- プラグイン(vim.pack は 0.12 で入った組み込みのプラグインマネージャー)
vim.pack.add({
  "https://github.com/neovim/nvim-lspconfig",
})

-- 診断と LSP
vim.diagnostic.config({ virtual_text = true, severity_sort = true })
vim.lsp.enable({ "pyright" })

vim.api.nvim_create_autocmd("LspAttach", {
  callback = function(args)
    local client = assert(vim.lsp.get_client_by_id(args.data.client_id))
    if client:supports_method("textDocument/completion") then
      vim.lsp.completion.enable(true, client.id, args.buf, { autotrigger = true })
    end
  end,
})

オプションは、入れた理由を説明できるものだけにしています。signcolumn = "yes"は診断の印が出たり消えたりするたびに本文が左右へずれるのを防ぎ、undofileはファイルを閉じたあともuで過去の変更に戻れるようにします(履歴はstdpath("state")配下に保存)。ignorecaseとsmartcaseの組み合わせは「小文字だけなら大小を無視、大文字を含めば区別」という検索になり、updatetime = 250はカーソル停止を検知するまでの待ち時間を既定の4秒から縮めます。

Leaderキーはスペースにしています。vim.g.mapleaderは<leader>を含むキーマップを定義した時点の値が使われるため、ファイルの先頭で決めます。設定されているかはNeovimの中で:lua =vim.g.mapleaderを実行し、" "と表示されるかで確かめられます。

同じディレクトリにinit.vimが残っていると、起動のたびにE5422: Conflicting configs: "/root/.config/nvim/init.lua" "/root/.config/nvim/init.vim"と表示されます。検証ではinit.luaが読まれ、init.vimは無視されました。Vimの設定を引き継ぎたいときは、init.vimを消したうえでinit.luaからvim.cmd.source("~/.vimrc")のように読み込みます。

vim.packでプラグインを入れる

vim.pack.addにGitリポジトリのURLを並べると、初回の起動時に確認を求めたうえでgit cloneし、プラグインを読み込みます。取得先は~/.local/share/nvim/site/pack/core/opt/で、同時に~/.config/nvim/nvim-pack-lock.jsonにコミットのハッシュが記録されます。

~/.config/nvim/nvim-pack-lock.json
{
  "plugins": {
    "nvim-lspconfig": {
      "rev": "046b1a797af1effe8e0474b9af9657a3761ec58a",
      "src": "https://github.com/neovim/nvim-lspconfig"
    }
  }
}

このロックファイルはinit.luaと一緒にGitで管理します。別のマシンでinit.luaとロックファイルを取り込んで起動すると、同じハッシュのプラグインが入るため、「自宅では動くのに職場のPCでは壊れる」を防げます。更新は:lua vim.pack.update()で、差分を確認するバッファが開き、確定すると更新の記録がstdpath("log")のnvim-pack.logに残ります。消すときは、init.luaから行を削除して:restart(0.12で追加された再起動コマンド)したあと、:lua vim.pack.del({ "nvim-lspconfig" })を実行します。

執筆時点のvim.packは、ヘルプ上まだexperimental(実験的)扱いで、イベントやコマンドをきっかけにプラグインを読み込むような遅延読み込みの機能も持っていません。プラグインを数十本入れて起動時間を詰めたい場合や、LazyVimのような配布設定を使う場合は、lazy.nvimのほうが向いています。

LSPを設定する:vim.lsp.configとvim.lsp.enable

LSPでは、補完や診断を計算する「言語サーバー」は別プログラムで、Neovimはそれと標準入出力で話すクライアントです。まずPython用のpyrightを入れます。Node.js製なので、ここではmiseのnpmバックエンドで入れました。npm install -g pyrightでも構いません。

terminal
mise use -g node@22
mise use -g npm:pyright
pyright --version          # pyright 1.1.414(検証時)
which pyright-langserver   # Neovim が起動するのはこちら

init.luaのvim.lsp.enable({ "pyright" })は「pyrightという名前の設定を有効にする」という意味です。Pythonのバッファを開くと、Neovimはruntimepathのlsp/pyright.lua(nvim-lspconfigが提供)を読み、起動コマンドpyright-langserver --stdio、対象のファイルタイプ、プロジェクトのルートを探す目印(pyproject.tomlや.gitなど)を得て、サーバーを起動します。nvim-lspconfigは設定値の集まりにすぎないので、次のように自分で書けばプラグインなしでも動きます。

init.lua(nvim-lspconfigを使わない場合)
vim.lsp.config("pyright", {
  cmd = { "pyright-langserver", "--stdio" },
  filetypes = { "python" },
  root_markers = { "pyproject.toml", ".git" },
})
vim.lsp.enable("pyright")

nvim-lspconfigを入れたまま一部だけ変えたいときもvim.lsp.configを使います。たとえばvim.lsp.config("pyright", { settings = { python = { analysis = { typeCheckingMode = "strict" } } } })と書くと、既定の設定(autoSearchPathsなど)を残したまま、型チェックだけが厳しくなることを確認しました。

試すために、間違いを2つ含むPythonファイルを用意します。

~/demo/app.py
from util import slugify


def build_url(title: str) -> str:
    return "https://example.com/posts/" + slugify(titel)


print(build_url(2026))
~/demo/util.py
import re


def slugify(text: str) -> str:
    text = text.lower().strip()
    return re.sub(r"[^a-z0-9]+", "-", text).strip("-")
terminal
mkdir -p ~/demo && cd ~/demo && git init
nvim -O app.py util.py

-Oは複数のファイルを左右に並べて開くオプションです。検証では起動から10秒ほどで、左のapp.pyの5行目に"titel" is not defined、8行目にArgument of type "Literal[2026]" cannot be assigned to parameter "title" of type "str"という診断が、行末の文字(virtual text)と行頭のEの印で表示されました。0.11以降は診断のvirtual textが既定で無効なので、init.luaのvim.diagnostic.config({ virtual_text = true })がないと、印だけが出て内容は<leader>eで開くまで見えません。

LSPが接続されたバッファでは、次のキーマップが既定で使えます。自分で定義しなくてよいので、まずはこれを指に覚えさせるのが近道です。

キー(ノーマルモード)動作呼ばれる関数
Kカーソル位置の型・ドキュメントを表示vim.lsp.buf.hover()
grn名前を一括で変更vim.lsp.buf.rename()
graコードアクション(importの追加など)vim.lsp.buf.code_action()
grr参照している箇所の一覧vim.lsp.buf.references()
gri / grt実装へ移動/型定義へ移動(grtは0.12から)implementation() / type_definition()
gOファイル内のシンボル一覧vim.lsp.buf.document_symbol()
Ctrl-]定義へ移動(tagfuncがvim.lsp.tagfuncに設定される)vim.lsp.tagfunc()
[d / ]d前/次の診断へ移動(組み込み)

補完を設定する:autocompleteとvim.lsp.completion

init.luaでは補完を2段で有効にしています。vim.lsp.completion.enable(..., { autotrigger = true })は、LSPサーバーが指定した「トリガー文字」を打ったときに候補を出す設定です。pyrightのトリガー文字は.などなので、util.pyでre.と打つとA・ASCII・DEBUG…と定数の候補が出ますが、sluのような途中までの入力では出ません。

そこで0.12のvim.o.autocomplete = trueで入力中の自動補完をオンにし、completeオプションにo(omnifunc、つまりLSP)を加えています。検証ではapp.pyでx = sluまで打った時点でslugify Functionが候補に出ました。候補はCtrl-n/Ctrl-pで選び、Ctrl-yで確定、Ctrl-eで閉じます。0.11を使う場合は'autocomplete'がないため、Ctrl-x Ctrl-oで手動で呼び出します。

スニペットの展開や、LSP以外の候補元(パスやバッファ内の単語)を細かく並べ替えたい場合は、nvim-cmpやblink.cmpのような補完プラグインが必要になります。組み込み補完で困るまでは入れず、困った点を具体的に言えるようになってから選ぶと、設定が膨らみすぎません。

つまずきポイント

症状・エラーメッセージ原因対処
E5113: Error while calling lua chunk: vim/_options.lua:491: E474: Invalid argument0.9.5など古いNeovimがcompleteoptのpopupを知らない0.12以上に更新する。どの版が起動しているかはnvim --versionで確認
attempt to index field 'pack' (a nil value)0.11以前にはvim.packがない0.12以上に更新するか、lazy.nvimを使う
エラーは出ないのに診断も補完も出ない言語サーバーがPATHにない。この場合は何も表示せずに起動をあきらめる:checkhealth vim.lspで'pyright-langserver' is not executable. Configuration will not be used.を探し、サーバーを入れる
行頭にEは出るが、エラーの内容が表示されない0.11以降はvirtual textが既定で無効vim.diagnostic.config({ virtual_text = true })を書くか、<leader>eで開く

動作確認

端末で版を確かめたら、app.pyを開いた状態で:checkhealth vim.lspを実行し、Active Clientsにpyrightが出ているかを見ます。Root directoryがgit initした~/demoになっていればroot_markersの探索も期待どおりで、左右に開いた2ファイルに接続しているのでAttached buffersは1, 2になります。

terminal
$ nvim --version | head -1
NVIM v0.12.5
:checkhealth vim.lsp(抜粋)
vim.lsp: Active Clients ~
- pyright (id: 1)
  - Version: ? (no serverInfo.version response)
  - Root directory: ~/demo
  - Command: { "pyright-langserver", "--stdio" }
  - Settings: {
      pyright = {
        disableTaggedHints = true
      },
      python = {
        analysis = {
          autoSearchPaths = true,
          diagnosticMode = "openFilesOnly",
          useLibraryCodeForTypes = true
        }
      }
    }
  - Attached buffers: 1, 2

サーバーを再起動したいときは:lsp restart、止めたいときは:lsp stopが使えます(どちらも0.12の:lspコマンド)。設定を書き換えたあとは:restartでNeovimごと起動し直すと、開いていたファイルを保ったまま新しいinit.luaが読まれます。

よくある質問

Neovimの設定ファイルはどこにありますか?

LinuxとmacOSは~/.config/nvim/init.lua、Windowsは%LOCALAPPDATA%\nvim\init.luaです。環境変数XDG_CONFIG_HOMEやNVIM_APPNAMEで場所が変わるため、Neovimの中で:echo stdpath("config")を実行し、表示された場所を見るのが確実です。

Neovimとvimの違いは何ですか?

Vimから派生したNeovimは、Luaで設定やプラグインを書けることと、LSPクライアント・Treesitter・プラグインマネージャー(0.12以降のvim.pack)を本体に持つことが大きな違いです。既定値も異なり、hlsearchやマウス操作が最初から有効です。Vimのキー操作と大半のVim scriptはそのまま使えます。

Neovimのおすすめプラグインは?

最初はLSPの設定集であるnvim-lspconfigだけで足ります。不足を感じたら、ファイルを名前で探すtelescope.nvimやfzf-lua、Gitの変更を行ごとに表示するgitsigns.nvimのように、困りごとが1つ解決するものを1本ずつ足すと、どのプラグインが何をしているかを把握し続けられます。

プラグインを入れたいのですが、Neovimでの入れ方は?

0.12以降なら、init.luaにvim.pack.add({ "https://github.com/作者/リポジトリ" })を書いて再起動すると、初回に確認のうえでgit cloneされます。更新は:lua vim.pack.update()、削除はinit.luaから消したあとのvim.pack.del()です。0.11以前はvim.packがないので、lazy.nvimなどを使います。

まとめ

Ubuntuのaptで入る0.9.5ではこのinit.luaは動かないので、まずGitHubのリリースから0.12系を入れます。LSPはサーバーを普段のパッケージ管理(npmやmise)で入れてvim.lsp.enableで有効にし、反応がなければ:checkhealth vim.lspで「not executable」の警告を探します。補完はvim.lsp.completionと'autocomplete'で足り、nvim-cmpなどは具体的な不満が出てからで間に合います。

次に読む

$ subscribe --weekly
毎週月曜、企業ITとホームラボの要点を5分で。