プラグイン無しでVimからClaude CodeとCodexを使う
Claude CodeもCodexもターミナルで動くCLIなので、エディタ側に専用の拡張を入れなくても使えます。日本語の解説記事はNeovim用プラグインとtmuxのペイン分割に集中していますが、素のVimでも:terminalとフィルタ機能だけで十分に組み込めます。この記事では3通りの組み込み方と、実際にやってみて引っかかった点を実機の出力付きで扱います。そのうえで、UIで勝てないVimからわざわざ使って何が嬉しいのかも整理します。
目次(10項目)
| コマンド・キー | これだけ覚える |
|---|---|
:term ++rows=15 claude | Vimの中でCLIを開く |
| Ctrl-w N | ターミナルの内容をVim流にスクロール・ヤンクする |
set autoread | CLIが書き換えたファイルを読み直す |
:r !claude -p "..." < /dev/null | 回答をカーソル位置に挿入する |
:%!claude -p "..." | バッファを丸ごと渡して書き換えさせる |
検証環境:macOS + Vim 9.1.1752 / Neovim 0.12.4、Claude Code 2.1.223、codex-cli 0.136.0、claudecode.nvim(2026-06-25時点)。コマンドの出力・画面表示はすべてこの環境の実物です。CLIの回答内容はモデルやバージョンで変わるので、そこは参考程度に見てください。
組み込み方は3通りある
やり方を整理すると、エディタとCLIの結びつきの強さで3段階に分かれます。上から順に手軽で、下に行くほど連携が密になります。
| 方法 | できること | 必要なもの |
|---|---|---|
| 同じ画面に並べる | CLIとVimを行き来しながら作業する | :terminalまたはtmux |
| フィルタとして呼ぶ | バッファの一部を渡して書き換えさせる | Vimの!と:r ! |
| IDE連携させる | 選択範囲の共有、変更をdiffで確認 | Neovim用プラグイン |
3つ目だけはNeovim限定です。CLI側が想定しているIDE連携はVS CodeとJetBrains向けに作られていて、そのプロトコルをLuaで再実装したプラグインがNeovimにはある、という構図になっています。素のVimで使えるのは上2つですが、実務ではこの2つで足りることのほうが多いというのが使ってみての感想です。
:terminalでVimの中に置く
Vim 8.1以降には端末エミュレータが内蔵されているので、Vimを終了せずにCLIを起動できます。ウインドウ分割と同じ感覚で、上下に割って使うのが素直です。
:term ++rows=15 claude " 上に15行のターミナルを開く
:vert term claude " 左右に分割して開く
:term ++close claude --version " 終了したらウインドウも閉じる
実際に:terminalでCLIを起動したときのVimの画面がこちらです。上半分がターミナルバッファ、下半分が編集中のファイルで、ステータス行にジョブの状態が出ます。
2.1.223 (Claude Code)
~
~
!claude --version [finished] 1,1 All
def add(a, b):
return a + b
~
sample.py 1,1 All
ターミナルバッファで覚えておくと便利なのがCtrl-w Nです。押すとTerminal-Normalモードになり、CLIの出力をVimの通常のバッファと同じようにkやggでスクロールしたり、yyでヤンクしたりできます。今どちらのモードにいるかはステータス行で判別できて、ジョブ実行中の[running]が、Ctrl-w Nを押すと[Terminal]に変わります。入力に戻るときはiです。CLIが出したコマンドやパスをコピーしたいときに、マウス選択に頼らずに済みます。
この方式の利点は、tmuxを使っていなくても成立することと、Vimのウインドウ操作がそのまま効くことです。逆に、Vimのウインドウ幅に引きずられるのでCLI側の表示が窮屈になりがちで、広い画面を確保したいならtmuxで別ペインに置くほうが快適でした。
CLIが書き換えたファイルをVimに反映する
ここが素のVimで使うときに最初に引っかかる部分です。Claude CodeもCodexも、指示に応じてディスク上のファイルを直接書き換えます。ところがVimは開いた時点の内容をバッファに持っているので、放っておくと画面の内容とディスクの内容がずれていきます。
Vim 9.1で、ファイルを開いた状態のまま外部からファイルを書き換え、:checktimeを実行して挙動を確かめました。結果は3通りに分かれます。
| 状況 | 結果 |
|---|---|
既定('autoread'オフ) | 「ファイルが変更されています」の警告が出て、読み込むかどうか聞かれる |
set autoread済み | 何も聞かれずバッファが新しい内容に置き換わる |
autoread+Vim側に未保存の変更 | 自動では読み込まれない。バッファは自分の編集のまま |
3行目が要注意です。'autoread'を入れていても、Vim側にも変更がある場合は自動リロードされません。このときVimは内部的に「衝突」として扱っていて、自作のautocmdで拾うとv:fcs_reasonの値がchangedではなくconflictになることを確認しました。Vimのヘルプにも警告メッセージの説明があります。
Warning: File "{filename}" has changed and the buffer was changed in Vim as well
ヘルプは「Vim側の版とディスク上の版のどちらを残すか決める必要があります」と続きます。つまりこの状態で何も考えずに:wすると、CLIがやった仕事のほうが消えます。CLIに作業を任せている間はVim側で同じファイルを編集しない、というのが結局いちばん安全でした。
反映の設定自体は~/.vimrcに数行足すだけです。'autoread'だけでは足りず、Vimが変更を確認しにいくきっかけ(:checktime)が要る点がポイントになります。
set autoread
set updatetime=300
autocmd CursorHold,CursorHoldI,FocusGained,BufEnter * silent! checktime
この設定で実際にVimを開いたまま外部からファイルを書き換えると、書き換えた直後の画面は古いままですが、何かキーを押して'updatetime'ぶん待った時点でCursorHoldが発火し、新しい内容に入れ替わりました。キーをまったく触らずに勝手に更新されるわけではないので、CLIの完了を待っている間はカーソルを1回動かすつもりでいると確実です。
バッファをフィルタとして渡す
もう1つの使い方が、Vimの!フィルタでバッファの内容をCLIの標準入力に流し、返ってきた結果で置き換えるやり方です。対話画面を開かずにその場で完結するので、短い書き換えには向いています。Claude Codeは-p(--print)を付けると非対話で答えだけを返します。
:%!claude -p "この関数にdocstringを追加して"
:'<,'>!claude -p "この範囲をリファクタして"
ただし素直に実行すると、実用にはなりませんでした。手元でdef add(a, b)の2行を渡したところ、返ってきたのは次のような内容で、そのままバッファに流し込まれます。
```python
def add(a, b):
"""Return the sum of two values."""
return a + b
```
Wait — you asked for no code fence. Here it is plain:
def add(a, b):
...
コードフェンスと説明文が混ざり、同じコードが2回書かれた状態になりました。フィルタは返ってきたものをそのままバッファに書くので、こうなるとファイルが壊れます。uで元に戻せるとはいえ、毎回そうなるようでは使えません。
解決策はプロンプト側ではなく、システムプロンプトの追加で指定することでした。--append-system-promptで「生のテキストだけを返せ」と指示すると、同じ入力に対してコードだけが返るようになります。
:%!claude -p "この関数にdocstringを追加して" \
--append-system-prompt "You are a code filter. Output ONLY the resulting file content as raw text. No markdown fences, no explanation."
この形で実行し直すと、フェンスも前置きも無いコードだけが返り、バッファがそのまま置き換わりました。毎回打つには長いので、~/.vimrcでコマンドにしておくと実用になります。
command! -range=% -nargs=1 AI <line1>,<line2>!claude -p <q-args>
\ --append-system-prompt "You are a code filter. Output ONLY the resulting file content as raw text. No markdown fences, no explanation."
もう1つ、:r !でコマンドの出力を挿入する使い方には別の落とし穴があります。バッファの内容を渡さずに質問だけしたつもりでも、Vimは標準入力を開いたまま渡すため、CLIが入力を待ってしまいます。手元では次の警告が挿入されました。
Warning: no stdin data received in 3s, proceeding without it.
If piping from a slow command, redirect stdin explicitly:
< /dev/null to skip, or wait longer.
警告文が指示しているとおり、< /dev/nullを付けて標準入力を閉じれば解決します。付けた場合は3秒の待ちも警告も消え、回答だけが挿入されました。
:r !claude -p "Pythonでファイルを1行ずつ読む定型を書いて" < /dev/null
Codexの場合
Codex CLIもcodex execで非対話実行ができますが、Vimから使う場合の作法が少し違います。まず、gitリポジトリの外では実行を拒否されました。
Not inside a trusted directory and --skip-git-repo-check was not specified.
もう1つの違いが出力の形です。codex execは標準出力に実行環境の情報(作業ディレクトリ、モデル名、承認モード、セッションID)と、送ったプロンプト、最後に消費トークン数まで出します。フィルタとして通すと、これが全部バッファに入ってしまいます。
回避策として用意されているのが-o(--output-last-message)です。最終メッセージだけを指定ファイルに書き出すので、Vimからは書き出したファイルを読み込む形にすると素直に扱えます。
:!codex exec -o /tmp/codex.txt "この関数にdocstringを付けて" < /dev/null
:r /tmp/codex.txt
ただし-oで書き出された内容にもコードフェンスは付いたままでした。Claude Codeの--append-system-promptに相当する調整をしない限り、そのまま置き換えるのではなく、いったん別バッファに出して必要な部分だけ取り込むほうが安全です。
Claude Code側をVimのキー操作にする
ここまではVimからCLIを呼ぶ話でしたが、逆にClaude Codeの入力欄をvimのキーバインドにもできます。~/.claude/settings.jsonに設定を書きます。
{
"editorMode": "vim",
"vimInsertModeRemaps": { "jj": "<Esc>" }
}
この設定でClaude Codeを起動して手元で確かめたところ、入力欄の下に-- INSERT --が表示され、Escを押すと消えてNORMALモードに入りました。NORMALモードで$を押してからxを押すと、入力していた文字列の末尾が1文字消えます。iでINSERTに戻り、文字を打ってからjjと続けて押すと、jが入力に残らずNORMALモードへ戻ることも確認しました。
使える操作は本家の一部ですが、日常的に使う範囲は揃っています。
| 種類 | 使えるキー |
|---|---|
| 移動 | h j k l w e b 0 $ ^ gg G |
| 検索移動 | f F t T と ; , |
| 挿入への遷移 | i I a A o O |
| 選択 | v V |
1つ注意点があります。この設定はユーザーの設定ファイル(と--settingsで明示したファイル)からしか読まれず、プロジェクト側の.claude/settings.jsonに書いても無視されます。チェックアウトしてきたリポジトリに自分のキー入力を書き換えられないようにするための仕様で、公式ドキュメントにも明記されています。jjのリマップはClaude Code v2.1.208以降で使えます。
Neovimならプラグインもある
Neovimを使っているなら、CLIとエディタをもう一段密に繋ぐ選択肢があります。coder/claudecode.nvimはClaude CodeのIDE連携プロトコルをLuaで実装したプラグインで、選択範囲の受け渡しや、提案された変更のdiff表示ができます。要件はNeovim 0.8以降とfolke/snacks.nvimです。
仕組みが気になったので、Neovim 0.12.4に入れて起動してみました。プラグインはNeovim側でWebSocketサーバーを立て、接続情報を~/.claude/ide/<ポート番号>.lockに書き出します。CLIはこのファイルを見てエディタを見つける仕掛けです。実際に生成されたファイルの中身がこちらで、認証トークンは伏せています。
{"workspaceFolders":["/path/to/project"],"ideName":"Neovim",
"transport":"ws","authToken":"(略)","pid":91145}
同じディレクトリには、VS Codeから接続したときのlockファイルも残っていました("ideName":"Visual Studio Code")。エディタ側が名乗り、CLIが拾う、という関係がファイルを見るだけで分かります。プラグインが提供するコマンドは:ClaudeCodeや:ClaudeCodeSendなど15個ありました。
素のVimでこの連携を再現するのは現実的ではありませんが、ここまで見てきたとおり:terminalとフィルタで実務は回ります。Neovimに乗り換える理由になるかどうかは、diff表示にどれだけ価値を感じるか次第です。
VS Codeと比べて何が残るのか
ここまで書いておいて何ですが、:terminalにCLIを置くこと自体には大した意味がありません。tmuxで隣のペインに置くのと変わりませんし、素のVimにVS Codeのような承認ボタンやインライン提案の可視化は用意できません。AIエージェントの働き(ファイルを読んで書き換えてテストを回す)はエディタと無関係に完結するので、Vimから使うと賢くなる、ということもありません。表現力の勝負なら負けています。
そのうえで、Vim側に残る差分は3つだと考えています。
1つ目が、AIの呼び出しが編集操作になることです。Vimの!はオペレータなので、!apや!ipのようにテキストオブジェクトで範囲を決めてフィルタを掛けられます。手元で6行のバッファに対して!ipからsortを実行したところ、カーソルのある段落だけが並び替わり、空行の下は手つかずのままでした。同じ要領でCLIに渡せるので、範囲指定から書き換えまでが1つの操作で終わります。VS Codeだと選択してチャットに貼って結果を適用する3手になる場面です。しかもVimの操作なので、.やマクロで繰り返せますし、頻出パターンはcommand!で自分用のコマンドに固定できます。
2つ目が、CLIの出力がただのバッファになることです。チャットUIの中に閉じている回答と違い、返ってきたテキストはレジスタにヤンクでき、置換もマクロもdiffも掛けられます。コマンドの実行結果をバッファに出すのと同じ扱いができるので、加工の自由度はこちらのほうが高くなります。
3つ目が、環境を選ばないことです。SSHで入った先のサーバーでも、GUIが入っていないマシンでも、操作は何も変わりません。踏み台越しの環境や他人のマシンに一時的に入るときなど、IDEのリモート接続が使えない場面でもAIエージェントを持ち込めるのは、この構成に固有の利点です。
「承認UIが無い」という弱点も、Vimのdiffである程度は埋められます。CLIが書き換えたあとの内容とHEADの内容を並べれば、変更点を目で確認してから受け入れられます。プラグインなしなら次のコマンドで足ります。
function! s:DiffGit() abort
let l:path = trim(system('git ls-files --full-name -- ' . shellescape(expand('%'))))
diffthis
vertical new
setlocal buftype=nofile bufhidden=wipe noswapfile
execute 'read !git show ' . shellescape('HEAD:' . l:path)
1delete _
diffthis
endfunction
command! DiffGit call s:DiffGit()
実際にCLIが1行足したファイルで:DiffGitを実行すると、左にHEADの内容、右に作業ツリーの内容が並び、両方のウインドウがdiffモードになりました。差分の行をdoやdpで戻したり反映したりできるので、承認ボタンの代わりとしては十分に使えます。バッファとディスクの差分を見たいだけなら、Vim同梱のdefaults.vimに:DiffOrigが定義済みです。
逆に、向いていない使い方もはっきりしています。
| やりたいこと | 向いている環境 |
|---|---|
| 選択範囲を渡してその場で書き換える | 素のVim(!フィルタ) |
| SSH越し・GUIなしの環境で使う | 素のVim |
| 提案を1件ずつ確認して受け入れる | VS Code / JetBrains |
| 複数ファイルにまたがる変更を俯瞰する | VS Code / JetBrains |
| 選択範囲やカーソル位置を自動で共有する | Neovim+プラグイン |
まとめると、Vimから使う構成が向いているのは「AIに全部任せて結果を眺める」使い方ではなく、「自分が編集している流れの中で、必要な範囲だけAIに投げる」使い方です。そこを外すと、素直にVS Codeを併用したほうが早いと思います。
最初はフィルタでの置き換えが便利そうに見えて、いきなり:%!を実行してファイルを壊しました。返ってきたのがコードフェンス付きの説明文で、バッファがまるごとMarkdownになった状態です。uで戻せたので実害はありませんでしたが、それ以来、置き換え系はコマンドとして~/.vimrcに登録し、システムプロンプトで出力形式を固定してからしか使っていません。日常的には:term ++rows=15 claudeで開いて、Vim側は'autoread'で追随させるだけの構成に落ち着いています。
まとめ
Claude CodeもCodexもターミナルで完結するので、Vimに専用プラグインを入れなくても:terminalで並べるだけで使えます。素のVimで押さえるべきなのは、CLIがディスクを書き換える前提でVim側の設定を用意しておくことです。set autoreadと:checktimeのautocmdを入れておけば表示のずれは防げますが、Vim側に未保存の変更があると自動リロードは止まるので、同じファイルを両方から触らない運用にするのが安全です。フィルタとして呼ぶ使い方は強力な反面、返ってくる形式を固定しないとバッファが壊れます。--append-system-promptで出力を縛り、:r !のときは< /dev/nullを付ける。この2つを知っているかどうかで実用性がかなり変わります。
そして、この構成の価値はUIではありません。VS Codeのような承認画面は作れませんし、作る必要もないと思います。!がオペレータであること、出力がただのバッファになること、SSHの先でも同じ操作が通ること。この3つのために素のVimを選ぶ、という整理がいちばん実態に近いはずです。