この記事は、諸般の事情でVimを触り始めた結果、Emacsを投げ捨ててVimばかし使うようになった自分が、初めて自作VimScriptを書いた顛末を書いたものです。
最終的に自分が作ったものは、「ヤンクしたテキストを標準入力として外部コマンドを走らせる関数」です。また、そこに至るまでに得た知識である、VimのRanges機能とビジュアルモードの関係、VimのRegistersについて、外部コマンド実行のexecuteの落とし穴についても記載します。
各々独立しているので、目次機能にて全部読まずとも必要な部分だけお読みいただければ幸いです。
きっかけ
まずはVimScriptをあえて書こうと思ったきっかけについてお話します。現在SQLのフォーマッタを書いてまして1、テストコードを書く際、SQL文中の要素の開始位置をカウントする必要がありました。
例えば"select col1 from tbl1;"のcol1は何文字目から始まるか?ですね。自分もVimを使い始めて暫く経つので、「ああ、こういうときはこうだな・・・」とおもむろに次のコマンドを打ちました。
" visual mode で"select c"までを選択後:'<,'>w!wc
すると次のような結果が帰ってきました。
:'<,'>w!wc11580
えっ!?15単語80文字!?どう見てもそんな文字数多くねーよ!!!
これをきっかけに、Vimの機能を色々調べることになりました。その結果、自分のやりたいことを満たす機能は自分で作るしかないのかな・・・という結論にいたり、VimScript自作しました。ともあれ、まずはなぜwc
での文字数カウントがこんなに多かったのか、説明します。
VimのRanges機能
結論から言うと、'<,'>
はVimのRanges機能で使われる特殊文字で、visualモードで選択した範囲の、先頭行から最終行を表します。ここで注意しなければならなかったのは、visualモードで選択した文字列ではなく、選択した文字列を含む全ての行全体を'<,'>
は指定するということです2。これはVimのvisualモードとRanges機能の連携を行うためのものと思って間違いなさそうです。
また、Ranges機能はBuffer中の行単位で範囲指定を行う機能なので、文字単位での処理は不可能です3。
その他、Ranges機能の基本的な使い方を紹介しますと:
" 10行目から20行目までを置換します。:10,20s/before/after/g" カーソル現在位置の行から5行目までを置換します。:.,+5s/before/after/g" NOTE: 先程の'<,'>は、'<から'>まで、という指定で、" '< はvisualモードで範囲指定した最初の行" '> はvisualモードで範囲指定した最後の行" という意味になります。なので次のような使い方もできますね。:'<,+5s/before/after/g
閑話休題:Vimの文字数カウント機能g ctrl-g
ちなみにですが、visualモードで指定した選択範囲の文字数をカウントする機能は、Vim標準であります。visualモード中に、g ctrl-g
とコマンド打つだけです。
ただこの表示、ほんの1,2秒で消えちゃうのでほんと見づらいです4。
この表示時間どうにかなりませんかね?そういうこともあって、自作のVimScript作る気持ちは強まりました。標準入力でいろんなコマンドに渡せたほうが汎用的ですしね。
Vim版コピペ、YankとRegisters
次に説明するのはVimのRegistersです。Registersとは、VimでYank(コピー)したときにテキストが格納される領域です5。なぜこれについて調べたかといいますと、visualモード中に選択した範囲のテキストを取得するVimScriptが、いくら探しても見つからなかったためです。そこでいっそ一旦ヤンクして、Regisitersにつっこんだテキストを渡してコマンド実行すればよいのでは?と考えました。
Registersは色々ありますが、今回使うのはふつーにvisualモードで選択した範囲をYankしたときに使われる、"
Registerのみ使います6。VimScript内では、@"
として扱うことができ、例えば:echo @"
とVim内でコマンド実行すれば、現在Yankされてるテキストが表示されます。
ちなみに:reg
を実行すれば、現在Registersに保存されてるテキスト一覧を見ることができますので、挙動や状態を確認できます。VimScriptのテストしてるときにちょっと便利でした。
executeの罠~system()だけでええやん…~
さて、ここまで来て書きたいVimScriptがようやく見えてきました。処理内容の手順はだいたいこんな感じです。
" 事前につっこみたいテキストをYankしておくfunction! ProcessRegisteredText() " 実行したい外部コマンド文字列を取得(ex. wc -m)let input=ユーザに文字列入力を促す。
" 作成文字列イメージはこんな感じ "!echo -n @" | wc -l"let executeCmd=@"とinputから、実行文字列を作成。
" 外部コマンドを実行!めでたしめでたし。
exe executeCmd
endfunction
まずはユーザ入力を受け取る関数ですね。ちょっと調べると出てきました7。
call inputsave()let userInput=input("Enter command: ")call inputrestore()
inputsave()
→input("Explanation Text")
→inputrestore()
と3ステップ必ず必要みたいです。ちょっとめんどくさいですが、まあいいでしょう。
次にVimの標準機能execute
に渡す文字列の作成です8。execute
はVimのコマンドラインで使えるコマンド文字列を評価し、実行します。具体的には次のように使います。
" Vimコマンドラインで外部コマンドを実行する際の記法:!date
" executeを介して実行:execute "!date"
そのため、次のVimScript関数で完成!終わり!と思ったのですが…
function! ProcessRegisteredText() " ユーザの入力を受け取る call inputsave()let userInput=input("Enter command: ")call inputrestore() " 外部コマンド実行(ちなみに文字列連結は.です。ex: "a"."b")let executeCmd="echo -n '" . @" . "' |" . userInput
execute executeCmd
endfunction
ただ、executeの仕様にはこの機能を実装するにあたり重大な問題が有りまして、@"
に複数行にまたがるテキストが入ってた場合、想定通りに動きません。execute
はどうも、途中に改行がある場合、別コマンドとして実行してしまうようです。具体的には次のように処理されます。
:execute "!echo test\n!date"" これは、次の2回の命令に分けられて実行してしまいます。" execute !echo test" execute !date
どうしたもんか...と引き続き調べてましたら、system()
という関数でも外部コマンド実行できるという情報を見つけることができました。そちらでは、改行含む場合でもターミナルで実行したときと同様に外部実行してくれました。
最後に
最終的に、以下のVimScriptを.vimrcに追加しています。(シングルクオートがYankしたテキスト中に存在する場合、エスケープを加える処理も追加してます。)
" execute command with feeding registered(yanked) text" registered text will be passed by pipe. " In short,'echo -n <@"> | <your desired command>' runs. function! ProcessRegisteredText()call inputsave() " enter your desiered external command let userInput=input("Enter command: ")call inputrestore()
echo "\n" " escape single quotelet registeredText=substitute(@", "'", "\\'", "g")let executeCmd="echo -n '".registeredText."' | ".userInput
echo executeCmd."\n"
echo system(executeCmd)endfunctionnoremap<Plug>(process_registered) :<C-u>call ProcessRegisteredText()<CR>
nmap <leader>pr<Plug>(process_registered)
次の手順で外部コマンド実行結果を表示できます。
1. 対象の文字列をYank
2. <Leader> p r
3. 実行したい外部コマンドを入力(wc -m等)
以上、お読みいただきありがとうございました。Vimについてまだまだ知らない機能が多いですし、VimScriptコーディングのお作法、keymappingのお作法などまで完璧にすることはできませんでしたが、おいおい学んでゆきたい所存です。
Vim内で
:help :marks
を実行すれば、'<,'>
の説明が見れます。 ↩vim wikiの解説です。Vim wikiは分かりやすいと思うので、Vim機能の調べ物する際"wiki"も加えて検索する事が多いです。 ↩
このスクリーンショット撮るのもほんと難儀しました。 ↩
Vimは(Emacsも)コピペにOSのクリップボードを利用せず、コピーしたいテキストをYankという機能でエディタ内の領域に一時保存するのが基本的な仕組みとなっています。 ↩
このstackoverflow記事が詳しいです。 ↩
ちなみにVimの標準機能は省略形を許すものが多く
execute
はexe
でも大丈夫です。 ↩