
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.enable | require("lspconfig").pyright.setup({}) |
| LSPサーバーの導入 | npm・mise・uvなど普段のパッケージ管理 | mason.nvim |
| 補完 | 'autocomplete'+vim.lsp.completion | nvim-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も入れておきます。
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の全体は次のとおりです。
-- 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にコミットのハッシュが記録されます。
{
"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でも構いません。
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は設定値の集まりにすぎないので、次のように自分で書けばプラグインなしでも動きます。
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ファイルを用意します。
from util import slugify
def build_url(title: str) -> str:
return "https://example.com/posts/" + slugify(titel)
print(build_url(2026))import re
def slugify(text: str) -> str:
text = text.lower().strip()
return re.sub(r"[^a-z0-9]+", "-", text).strip("-")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 argument | 0.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になります。
$ nvim --version | head -1
NVIM v0.12.5vim.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などは具体的な不満が出てからで間に合います。
次に読む
- chezmoiでdotfilesを管理する:init.luaとnvim-pack-lock.jsonを複数のマシンへ同じ状態で配る
- tmuxの使い方:SSH先のサーバーでNeovimを開いたまま作業を中断・再開する
- VS CodeのRemote SSHとDev Containers:サーバー上の開発をGUIのエディタで行う場合の選択肢