Vimの:messagesなどコマンド結果をバッファに出力してコピーする方法
エラーの原因を人に聞きたくて:messagesの内容をコピーしようとしたら、Vimの出力パネルは普通のビジュアル選択でコピーできないことに気づきました。マウスで無理やり選択してもターミナルの表示がズレて崩れます。結局、出力をバッファに書き出してからヤンクするのが一番確実でした。
検証環境:macOS + Vim 9.1.1752。execute()の戻り値の形、:putで入る空行、split()を挟んだ場合の差、:redir版の結果は、この環境で実際に実行して確かめています。
目次(8項目)
| キー | これだけ覚える |
|---|---|
:put =execute('{コマンド}') | コマンド結果をバッファに書き出す |
let msgs = execute('{コマンド}') | 結果を変数に代入する |
:let @+ = execute('{コマンド}') | クリップボードへ直接送る |
結論から:execute()をバッファに書き出す
:new
:put =execute('messages')
:newで新しいバッファを開き、そこに:put =execute('messages')を実行します。execute()はVim scriptの関数で、公式ヘルプでは次のように定義されています。
Execute an Ex command or commands and return the output as a string.
つまり渡したExコマンドを実行し、その出力を文字列として受け取れます。それを:put =で現在のバッファに書き出すことで、普通のテキストとして選択・ヤンク・保存ができるようになります。この関数はVim 8.0で追加されたものなので、それ以前のVimでは後述の:redirを使うことになります。
先頭に空行が1行入る
実際に試すと気づくのが、書き出した先頭に空行が1行入ることです。execute()の戻り値が改行から始まるためで、手元のVim 9.1で戻り値を確認すると、たしかに1文字目が改行文字でした。
:echo string(execute('messages'))
'\nMessages maintainer: The Vim Project\nテスト1\nテスト2'
1行くらい気にならない場面がほとんどですが、書き出した内容をそのまま別のツールに渡したいときや、何度も書き出して継ぎ足すときには邪魔になります。split()で行のリストに変換してから渡すと、空行が入らなくなります。
:put =split(execute('messages'), '\n')
手元で同じバッファに両方の書き方を試したところ、:put =execute(...)では既存行の下に空行が1つ挟まり、split()を通した場合は挟まりませんでした。:put =はリストを渡すと各要素を1行として扱うので、行単位で加工したいときにもこちらの形が扱いやすくなります。
messages以外にも応用できる
この方法はmessagesに限らず、任意のExコマンドの出力に使えます。
:put =execute('scriptnames') " 読み込まれたスクリプト一覧
:put =execute('set') " 現在のオプション一覧
:put =execute('version') " バージョン情報
:put =execute('map') " 定義済みのキーマッピング一覧
scriptnamesは設定ファイルが読み込まれないトラブルの切り分けにもよく使うコマンドで、こちらもテキスト化しておくとgrepで絞り込みやすくなります。
環境依存の不具合を報告するときにversionの出力が必要になることは多く、この方法を知っていると毎回スクリーンショットを撮らずに済みます。バグ報告用のIssueにそのままテキストで貼り付けられるのも地味に助かります。
put の代わりに変数へ入れることもできる
バッファに出さず、そのまま変数として使いたい場合は次のように書けます。
let msgs = execute('messages')
echo msgs
どちらの形を使うかは目的次第です。
| やりたいこと | 使う形 |
|---|---|
| 手元でバッファを開いて読みたい | :put =execute('...') |
| スクリプト内でログ加工・条件分岐したい | let msgs = execute('...') |
要点::put =execute('{コマンド}')という型を覚えておけば、通常はコピーできないVimの内部出力をほぼ何でもテキスト化できます。:puと略しても動きます。
古い書き方:redirで受け取る
execute()が無かった時代は、出力先を一時的に変数やレジスタへ切り替える:redirを使っていました。今でも動きますし、既存の記事や設定ファイルではこちらの形をよく見かけます。
:redir @a
:silent messages
:redir END
これでレジスタaに出力が入るので、"apで貼り付けられます。手元で試したところ、execute()で取ったものと同じ内容が入りました。関係については公式ヘルプが直接説明しています。
This is more or less equivalent to: redir => var / {command} / redir END
つまりexecute()は:redirの定型処理を1つの関数にまとめたものです。:redirは開始と終了が対になっていて、途中でエラーが起きると出力先が戻らないまま残る事故が起きます。今から書くならexecute()のほうが安全です。
プラグインの読み込み順序がおかしいのではと疑ったとき、:put =execute('scriptnames')で一覧をバッファに出してからgrepで絞り込んで原因を特定したことがあります。ターミナルの表示をそのまま読もうとするより、一度テキスト化してエディタの検索機能に頼ったほうが圧倒的に早かったです。
クリップボードへそのまま送りたい場合
バッファを経由せず、システムクリップボードに直接送ってしまいたいこともあります。クリップボード連携が有効な環境(:echo has('clipboard')が1を返す環境)であれば、次のようにレジスタを指定して1行で済ませられます。
:let @+ = execute('messages')
@+はシステムクリップボードと連動する選択レジスタで、これにexecute()の結果を代入すれば、Vimの外のアプリにそのまま貼り付けられます。バッファを汚したくない、後始末が面倒という場合はこちらのほうが手早いです。ただしクリップボード連携が無効なビルドでは何も起きないので、まずhas('clipboard')で確認してから使うと安心です。
まとめ
Vimの内部出力は、execute()を経由すればどんなコマンドでも普通のテキストとしてバッファに書き出せます。コピーできずに困ったら、まずこの型を思い出すと解決します。