Vimはホントに素敵なエディターでして、mapをゴソゴソ設定するだけでかなり自分好みに拡張することができます。ちょっとした機能なら、わざわざプラグインをインストールしなくてもmapだけで実現できます。今回は私的にはずせない&&ちょっとマニアックなmap設定を書いてみたいと思います。Vim初心者の方も考慮して説明も付けてます。
1.カーソル下の単語をハイライトする
nnoremap <silent> <Space><Space> "zyiw:let @/ = '\<' . @z . '\>'<CR>:set hlsearch<CR>
ノーマルモードでスペースを2回押すと、カーソル下の単語がハイライトされます。
Eclipseのカーソル下の単語を光らせる機能が欲しくて作りました。なにげにすごく便利で気に入ってます。Vimデフォルトの*
キーで近いことは実現できるのですが、カーソルがジャンプしてしまうのが嫌なので独自にmapしました。
ではmapについて説明していきます。mapコマンドの書式はmap [左辺] [右辺]
となっています(参考:h map
)。バラしてみると次のようになります。
nnoremap ・・・ ノーマルモードのmapを定義(再マップなし)
[左辺] <silent> <Space><Space>
[右辺] "zyiw:let @/ = '\<' . @z . '\>'<CR>:set hlsearch<CR>
左辺ですが、<silent>
はとりあえず無視してください。<Space><Space>
とありますので、スペースキーを2回連続で押した場合に[右辺]の内容が発動するようにmapを定義しているわけです。
右辺ですが、とてもややこしく見えます。しかし先頭からじっくり読んでいくと、実はたいして難しいことはありません。処理の塊にバラして見てみます。
1| "z ・・・ 今からzレジスタ使いますよ
2| y ・・・ ヤンクするのは、、、
3| iw ・・・ カーソル下の単語(inner word)です
4| :let @/ = '\<' . @z . '\>'<CR> ・・・ /レジスタに代入して
5| :set hlsearch<CR> ・・・ hlsearch(ハイライト検索)ONだ
まず1行目の"
コマンドは使用するレジスタを指定するコマンドです。(参考:h "
)
レジスタとはヤンクした内容などを保存しておく記憶領域のことで、普段みんなよく使っているyy
なんかの内容もこのレジスタに保存されています。レジスタは色んな種類があるので、詳しくは この記事とか読んでみてください。
このケースでは"
のあとにz
が続いているので、これからzレジスタを使用するぞってことです。つまり、次に行うヤンクの結果をzレジスタに放り込むことになります。
2行目のy
でヤンク、3行目がパラメータiw
なので、カーソル下の単語をヤンクすることになります。ここまで「カーソル下の単語をzレジスタにヤンク」することができました。
ちょっと試してみたいなって人は、適当な単語の上にカーソルを乗せて、"zyiw
と入力してから、:reg
を実行してみましょう。レジスタの一覧が表示されて、zレジスタにカーソル下の単語がヤンクされているはずです。(実際にすぐ試せるのがVimのいいところ)
続いて4行目ですが、ややプログラムチックです。(まあプログラムなんですが)
4| :let @/ = '\<' . @z . '\>'<CR>
先に@z
についてですが、こいつは「zレジスタの内容」という意味です。Vimスクリプト内では@
の後ろにレジスタ名を付けると、そのレジスタを参照することができます。
次にlet
ですが、これは変数に値を代入する際に必要なキーワードです。Javascriptのvar
に近いのかな。let @/ = ...
ということは、「/レジスタに...を代入する」ということになりますね。ちなみに<CR>
はエンターキーの入力と等価です。
じゃあその「/レジスタ」とはなんぞや?ということになりますが、こいつは最後に/
コマンドで検索した際の検索文字列を格納する特殊レジスタなのです。つまり/レジスタに文字列を代入するということは、あたかも「ついさっき/
コマンドで検索しましたよ〜ん」と嘘ぶくことなのです。
ちょっと試してみましょう。おもむろに適当なファイルを開いて、:let @/ = 'a'
と入力します。続いて5行目のように:set hlsearch
と入力してみましょう。検索したこともないのにa
の文字がハイライトされるはずです。
最後に'\<'
, '\>'
という奇妙な文字列の説明ですが、これらは単語の境界を表す特殊文字列になります。つまり、これらを連結しておくと単語一致モードでハイライトさせることができます。例えば次のようなファイルを編集していて、
1| A barking dog seldom bites.
2| Every dog has his day.
3| Let sleeping dogs lie.
:let @/ = '\<dog\>'
と入力してn
を押して検索すると、1行目と2行目のdog
にはヒットしますが、3行目のdogs
の部分にはヒットしません。一方、:let @/ = 'dog'
としてからn
で検索すると、3行目のdogs
のdogにもヒットします。
以上で "zyiw:let @/ = '\<' . @z . '\>'<CR>:set hlsearch<CR>
の説明は終わりです。前から順番に見ていくと、意外と簡単に理解できると思います。
<silent>について
<silent>
は左辺のオプションです。これは処理結果をステータスラインに表示させないためのオプションです。試しに<silent>
を取り除いてmapを定義し、単語の上で<Space>
を2回叩いてみましょう。ハイライト結果は変わりませんが、ステータスラインに:set hlsearch
という表示が残ったままになっていると思います。<silent>
を付ければこれを抑制することができます。
なぜzレジスタを使うの?
別にzレジスタじゃなくても問題ありません。ただ何かしらレジスタを指定しておかないと、無名レジスタ"
(普段のy
やd
で使用されるレジスタ)が汚れることになるので、私はこういった場合はz
を使用するように習慣づけています。
2.カーソル下の単語をハイライトしてから置換する
nmap # <Space><Space>:%s/<C-r>///g<Left><Left>
#
キーを押すと、カーソル下の単語をハイライトしてから置換後文字列を入力する状態にします。
ステータスバーを見てください。ポイントはカーソルの位置が置換後文字列を入力する場所に来てるところ。五郎さん風に言うと「こういうの嬉しい」。1.のハイライトmapを利用しています。
定義を見ていきます。今回もバラして見ていきましょう。
nmap ・・・ ノーマルモードmap(再マップあり)
左辺: #
右辺:
<Space><Space> ・・・ 1.のハイライトmap発動
:%s/<C-r>///g<Left><Left> ・・・ 置換
最初にnmap
についてですが、こいつはnnoremap
と違い、右辺の再マップを行います。つまり右辺最初の<Space><Space>
によって1.のハイライトmapを発動させるということです。通常mapはnore
を付けて再マップ無しでmapすることが一般的ですが、きちんと理解した上で再マップを利用するのはアリです。
次に:%s/<C-r>///g
の部分ですが、基本的には:%s
と置換コマンドを呼び出しているだけです。置換対象文字列の箇所が<C-r>/
となっていますが、これは/レジスタの内容をペーストするという意味になります。:h <C-r>
でヘルプを読めばよくわかると思います。<Space><Space>
でハイライトする際に/レジスタにカーソル下の単語を放り込んでいるので、そいつを利用するというわけです。
最後に<Left><Left>
でカーソルを置換後文字列を入力する箇所に移動させています。もう一度言いますが、こういうの嬉しい。
ちなみに置換後文字列を入力する際、置換対象文字列をペーストしてからちょこっと修正して置換したい場合が結構あります。そういう場合は、<C-r>z
を入力すると置換対象文字列がピタッとペーストできますよ。(1.でハイライトするときにzレジスタに格納しているので)
3.ビジュアルモードでもハイライト・置換
しつこくハイライト・置換ネタですが、1. 2. でやったことをビジュアルモードでもできるように設定します。
xnoremap <silent> <Space> mz:call <SID>set_vsearch()<CR>:set hlsearch<CR>`z
xnoremap * :<C-u>call <SID>set_vsearch()<CR>/<C-r>/<CR>
xmap # <Space>:%s/<C-r>///g<Left><Left>
function! s:set_vsearch()
silent normal gv"zy
let @/ = '\V' . substitute(escape(@z, '/\'), '\n', '\\n', 'g')
endfunction
ビジュアルモードで選択中に<Space>
を押すと、選択範囲の文字列をハイライトします。*
を押すと後方検索、#
を押すと2.と同様にハイライトしてから置換入力状態にします。
まず一つ目のmapから見ていきます。
xnoremap ・・・ ビジュアルモードでのマップ(再マップ無し)
左辺: <silent> <Space>
右辺:
1| mz ・・・ マークをzに付ける
2| :call <SID>set_vsearch()<CR> ・・・ スクリプトローカル関数set_vsearchを呼び出す
3| :set hlsearch<CR> ・・・ ハイライト実行
4| `z ・・・ マークzに戻る
えらく物々しくなってしまいましたが、ここでやっていることは大したことはありません。現在のカーソル位置にマークを付けてzに格納します。その後後述する関数を呼び出してからハイライトさせ、最後に最初にマークした位置まで戻します。
肝になっている関数を見てみます。
function! s:set_vsearch()
silent normal gv"zy
let @/ = '\V' . substitute(escape(@z, '/\'), '\n', '\\n', 'g')
endfunction
vimスクリプトの世界です。まずfunction!
ですが、関数を定義しますよーって意味です。!
が付いているのは、もし定義されてても再定義しろッ!という意味です。.vimrc
ってリロードされることがよくあるので、基本的には付けておくべきでしょう。
続いて関数名に付いているs:
という接頭子ですが、これが付いている関数のスコープはスクリプトローカルで定義されます。要するに(基本的には)このファイル内からしか呼び出せない関数を定義するということです。(こういうのも:h s:
でヘルプが読めるのがVimのいいところ。ぜひヘルプを読んでみてください)
関数内に移ります。最初のsilent normal gv"zy
の行から見ていきましょう。silent
はそれに続くコマンドを静かに実行するコマンドです。normal
はノーマルコマンドを実行します。ノーマルコマンドっていうのは、ノーマルモードで普段実行してるyy
とかdd
とかのアレです。gv
は最後にビジュアルモードで選択した範囲をもいちど選択するコマンド。そしておなじみの"zy
でzレジスタにヤンク。要するに「最後の選択範囲を静かにzレジスタにヤンク」しているわけです。
続いて次の行let @/ = '\V' . substitute(escape(@z, '/\'), '\n', '\\n', 'g')
ですが、先ほどヤンクしたzレジスタの内容をゴソゴソしてから/レジスタに格納しています。何をゴソゴソしているかというと、検索文字列のエスケープ処理を施しています。例えば/
が検索文字列に含まれていた場合、そいつをエスケープして\/
にしてあげてたりします。escape
もsubstitute
もvimの標準関数ですので、詳しくはヘルプを見てみてください。
先頭にくっつけている\V
は"very nomagic"
といい、very magicモードを使わないという意味になります。こちらも:h \V
でヘルプを読めば分かりますが、very nomagicでは正規表現で使用する特殊文字が単なる文字として扱われるようになります。ビジュアルモードで選択した文字列で検索する場合に、正規表現を使うことなんてありえないので\V
を付けてます。
これで一つ目のmapが理解できるかと思います。続く2つのmap
xnoremap * :<C-u>call <SID>set_vsearch()<CR>/<C-r>/<CR>
xmap # <Space>:%s/<C-r>///g<Left><Left>
もこれまでの知識で理解できると思います。
4.上下に空行を挿入する
Shift + Enter
で下に、Shift + Ctrl + Enter
で上に空行を挿入します。
imap <S-CR> <End><CR>
imap <C-S-CR> <Up><End><CR>
nnoremap <S-CR> mzo<ESC>`z
nnoremap <C-S-CR> mzO<ESC>`z
Eclipseにもあるような空行を上下に挿入するやつです。挿入モード用とノーマルモード用に分かれています。imap
使っているのは<CR>
を再マップさせたいからです。<CR>
ってneocompleteとかでよくmapしてますので。
追記
なお<C-o>o
でも同じことできるじゃんと思うかもしれませんが、このmapの方が優秀です。ひとつは入力しやすいということ。さらにもうひとつ重要なのは、実際に<End>
で行の末尾に移動し<CR>
を入力するので、他のプラグインなどの自動挿入が働くということです。(例えばif
に対するend
の自動挿入とか)
ノーマルモード用のmapでmz -> `z
してるのは、元いた場所にカーソルを戻すためです。使い勝手を良くするために、こういうのすごく大事だと思います。
CUI版のVimで実行するには(追記)
書くの忘れてました。。。上記map<S-CR>
,<C-S-CR>
はMacVimのようなGUI版のVimでしか動作しません。なぜかというと、ターミナルから起動したCUI版のVimでShift+Enter
を押しても、Vimに送られるコードはEnter
のみだからです。
以下の手順でこの問題を回避できます。(Macでしか検証してません)
- iTermなどのターミナルアプリの設定で、Shift+Enter/Ctrl+Shift+Enterが押されたら、ある特定の文字を出力するように設定する
- その文字に対するmap設定を
.vimrc
に施す
私は次のようにmap設定してます。
if !has('gui_running')
" CUIで入力された<S-CR>,<C-S-CR>が拾えないので
" iTerm2のキー設定を利用して特定の文字入力をmapする
map ✠ <S-CR>
imap ✠ <S-CR>
map ✢ <C-S-CR>
imap ✢ <C-S-CR>
endif
iTerm2の設定画面
↓これ↓を参考にしました。
http://stackoverflow.com/questions/5388562/cant-map-s-cr-in-vim
5.コマンドラインはemacsバインディングで
vimのコマンドラインのカーソル移動にemacsキーバインドを使用するようにします。
cnoremap <C-p> <Up>
cnoremap <C-n> <Down>
cnoremap <C-b> <Left>
cnoremap <C-f> <Right>
cnoremap <C-a> <Home>
cnoremap <C-e> <End>
cnoremap <C-d> <Del>
標準のバインディングだとカーソルキー使わなきゃなんないのが嫌なのでこれを設定してます。ちなみにMacのテキスト入力時なんかもemacsバインディングが使用できるので、Vimmerであってもemacsの基本的なカーソル移動はできたほうが良いと思いますよ。
6.行を移動する
行ごと移動します。
" 行を移動
nnoremap <C-Up> "zdd<Up>"zP
nnoremap <C-Down> "zdd"zp
" 複数行を移動
vnoremap <C-Up> "zx<Up>"zP`[V`]
vnoremap <C-Down> "zx"zp`[V`]
グリグリ行を移動させられるので、メソッド丸ごと上の方に持って行きたいなあ、ってな場合に便利です。Vimcastにあった例を少しいじっています。
" 行を移動
のmapの方は特筆すべきことは無いです。俺的お約束のzレジスタを使って行を削除・ペーストしているだけです。
" 複数行を移動
の方ですが、目新しいのは`[
と`]
ぐらいです。これはそれぞれ「直前にヤンクした開始場所に移動」「直前にヤンクした終了場所に移動」という意味になります(これも:h `[
でヘルプが読めます)。つまり、ビジュアルモードで選択した範囲をx
でzレジスタにカットして、それをp
,P
でペースト、その後さっきヤンクした開始位置にジャンプしてV
で行選択モードにして、さっきヤンクした終了位置にジャンプして終わる・・という流れになります。マクロを組んでるみたいで楽しくありませんか?
7.Ctrl+tでタイポ修正
Macに備わってるアレです。Ctrl+tでteh
をthe
に直したりできます。
inoremap <C-t> <Esc><Left>"zx"zpa
英語を入力しているとthe
って打つつもりがteh
と打ってしまう場合とかよくると思います。そういった入力順間違いを入れ替えます。(|
がカーソルだとして)teh|
の状態で<C-t>
を入力するとthe|
という状態になります。
8.ハイライトを消去する
ハイライトを消去しつつ画面も再描画します。
nnoremap <silent> <C-l> :<C-u>nohlsearch<CR><C-l>
こだわりのmap・・・って感じじゃないですが笑。もともとは
nnoremap <silent> <Esc><Esc><Esc> :<C-u>nohlsearch<CR>
ってやってたんですが、もともと用意されている再描画キー<C-l>
を使用する例を見かけて、こっちのほうがスジがいいなあと思い、こっちにしました。
9.Delete, Backspace
挿入モードでのDeleteとBackspaceです。
inoremap <C-d> <Del>
imap <C-h> <BS>
入力中って文字DeleteしたりBackspaceすること多いじゃないですか。標準の<C-d>
のマッピングは「インデントを減らす」ですが、これはあまり使用しないので<Del>
に当ててます。標準の<C-h>
は「カーソルを行頭へ」(?)なのですが、これも<BS>
の方が有用なので変えました。
追記
わざわざimap
にしているのは、neocompleteの設定用に<BS>
にmapしている定義があるからです。
inoremap <expr><BS> neocomplete#smart_close_popup().\<BS>
<C-h>
と<BS>
を完全に等価にしたかったのであえてimap
にしています。
なお、挿入モード中にインデントを正したい場合は<C-o>==
押せばいいですし、行頭へ移動したい場合はちょっと面倒だけど<C-o>^
もしくはちょい邪道でCMD + ←
しちゃってます。
10.x
やs
ではヤンクしない
好みによりますが、x
やs
で削除した内容でレジスタを汚したくないので、これらは闇に葬ってます。
nnoremap x "_x
nnoremap s "_s
最後は小ネタです。"_
は「_(アンダースコア)レジスタを使用する」という意味ですが、_レジスタは消去用レジスタといいブラックホールのように吸い込んだものを闇に葬り去ります。
以上
俺的こだわりmapでした。お気に召すのがあればぜひ設定してみてください。