🤖

Vimで使っている簡単キーマッピングたちを共有

2024/09/13に公開

この記事はVim駅伝の2024-09-13の記事です。
前回の記事はryoppippiさんのNeovimで記録したマクロを後から編集するです。
次回の記事はyuys13さんのNeovimのコメンティングプラグインの選び方です。


筆者がVimの設定ファイルに定義しているキーマッピングの中から、簡単に書けるものをいくつか紹介します。
1行書けばVimが便利になるようなものを集めました。
どれも外部プラグインや複雑な関数は必要ありません。

Yで行末までコピー

有名なので既に使っている方も多いとは思いますが紹介。
デフォルトのマッピングでは、以下の機能が割り当てられています。

  • cc:行全体を編集
  • C:行末まで編集
  • dd:行全体をカット
  • D:行末までカット
  • yy:行全体をヤンク
  • Y:行全体をヤンク

Yだけちょっとずれているんですよね。そもそもyyと機能が被ってしまっています。
「行末までヤンク」にマッピングすると便利です。

nnoremap Y y$

デフォルトの設定には歴史的経緯があるらしいです。

https://vim-jp.org/vim-users-jp/2010/02/05/Hack-122.html

なお、Neovimでは自動でこのマッピングが適用されます(:h default-mappings)。

xで削除

こちらも有名だと思いますが紹介。
デフォルトのxは「レジスタに入れずに1文字削除」なのですが、これを「レジスタに入れずにカット(削除)」にします。もともとの処理はxlで実現できます。

nnoremap x "_d
nnoremap X "_D
xnoremap x "_x
onoremap x d

前述のYのマッピングと揃え、Xで行末まで削除するようにしています。
"_はブラックホールレジスタです(:h quote_)。

i<space>でWORD選択

Vimの強力な機能といえばテキストオブジェクトですね。標準では、WがWORD単位での選択に割り当てられています。連続した文字を対象とするテキストオブジェクトです。
ところで、これって「連続した文字」ですが、逆に考えると「スペースに挟まれた範囲」と考えることができますよね。ということで以下のマッピングです。

onoremap i<space> iW
xnoremap i<space> iW

簡単なマッピングですが、かなり編集体験が変わる可能性があります。
筆者はこれを定義してからWORD単位の選択を多用するようになりました。iWと比べてi<space>のほうが格段に押しやすいので。

Uでリドゥ

標準ではuでアンドゥ、<c-r>でリドゥです。このリドゥをUに割り当てます。
f/t/nなど、「大文字にすると逆方向になる」というマッピングは多くあるので、「Uuの逆」というのもわかりやすいのではないでしょうか。
デフォルトのUの機能を使っていますか?筆者は使っていなかったのでこちらにマッピングしてしまいました。

nnoremap U <c-r>

このアイデアはhelixのマッピングから拝借しました。

https://docs.helix-editor.com/keymap.html#changes

Mで括弧ジャンプ

Mが押しやすいのに余り使わず、逆に%が押しにくいのに便利な機能です。ということでM%に割り当てています。

noremap M %

さらに、筆者はmatchitを使うため以下のようにしています。

packadd matchit
map <expr> M expand('<cword>') =~# 'end' ? '%' : 'g%'

noremapではなくmapなのは%にマッピングされているmatchitの機能を使用するため、分岐を入れているのはmatchitで端から端まで一気にジャンプできるようにするためです。
Vim scriptのように関数やif文がキーワードで区切られる場合、%ifelseifelseendifのようなジャンプ順に、g%はその逆順になります。
個人的には途中のキーワードを経由せず端から端まで一気に飛んでくれたほうが好みなので、endを含んでいるかどうかでジャンプを切り替えています。

qをプレフィックスにする

qはマクロを起動・終了する重要なキーです。直後に押したキーに対応するレジスタにマクロを記録します。…が、アルファベット26字をすべて記録用に使うでしょうか。少なくとも筆者はそんなに要りません。
ということで、qレジスタのみをマクロとして使用することにしました。
マクロ記録中はq一発で終了できるよう、reg_recording()で分岐をしておきます。

nnoremap <script><expr> q empty(reg_recording()) ? '<sid>(q)' : 'q'
nnoremap <sid>(q)q qq

これはthincaさんのブログから輸入しました。

https://thinca.hatenablog.com/entry/q-as-prefix-key-in-vim

これでqをマッピングのプレフィックスに使えるようになります。マクロ実行中に行わない操作に割り当てましょう。
たとえば、ウィンドウ操作やバッファ移動などがおすすめです。

nnoremap <sid>(q)o <Cmd>only!<CR>
nnoremap <sid>(q)t <C-^>

さらに、マクロ用レジスタを固定したことにより、実行時にレジスタを選択する必要がなくなります。qレジスタ決め打ちで実行すれば良いということです。
筆者はQでマクロを実行するようにしています。
なお、マクロの入れ子を防ぐため、記録中であれば、いったんqを入力して記録を終了する処理を挟んでいます。

nnoremap <script><expr> Q empty(reg_recording()) ? '@q' : 'q@q'

global-normalコマンドを呼び出す

「マッチする行に対し操作を実行するglobal」と、「キーストロークをエミュレートするnormal」を組み合わせると、マクロのような操作をできます。一定範囲に対し同じ操作を繰り返せて便利です。
筆者は以下の記事で知りました。

https://qiita.com/mityu/items/c4495712296c2d40e463

この記事を読んだ当時、かなり便利だと思ったのですが、globalnormalも親しいコマンドではなかったので忘れてしまいがちでした。そこで導入したのが以下のマッピングです。qgでコマンドラインに途中まで入力されるので、あとは実行したいnormalコマンドを入力するだけです。

nnoremap <sid>(q)g :<C-u>global/^/normal<Space>
xnoremap qg :global/^/normal<Space>

<sid>(q)は前述のとおり、qをプレフィックス化したものです。

Visual コピー時にカーソル位置を保存

デフォルトでは、Visual modeで選択してからyでコピーすると、カーソルがコピー範囲の始点に飛んでしまうんですよね。あまり直感的ではないと感じたので、コピー前後にmarkを使い、カーソルが動かないようにしています。

xnoremap y mzy`z

なお、筆者はzマークを「マッピングなどで一時的に使うmark」として使っています。

Visual ペースト時にレジスタの変更を防止

デフォルトでは、Visual modeで選択してからpでペーストすると、無名レジスタの中身が入れ替わる挙動になっています。つまり連続でペーストできません。
ここは普通に連続でペーストしたいと思ったので、Pの挙動に置き換えています。

xnoremap p P

shougo-s-githubを参考にしました。

https://github.com/Shougo/shougo-s-github/blob/21a3f500cdc2b37c8d184edbf640d9e17458358a/vim/rc/mappings.rc.vim#L190-L191

このVisual Pの実装もShougoさんなのですが。

https://github.com/vim/vim/pull/9591

この機能が実装される前は、ペースト前後にレジスタを保存・復旧する処理を自前で記述する必要がありました。

Visual <, >で連続してインデントを操作

Visual modeで<または>することでインデントを増減させることができますが、デフォルトでは一回で選択が解除されてしまいます。
以下のマッピングを設定することで、直後に同範囲で選択し直し、連続でインデントを操作できます。

xnoremap < <gv
xnoremap > >gv

こちらもshougo-s-githubを参考にしました。

https://github.com/Shougo/shougo-s-github/blob/21a3f500cdc2b37c8d184edbf640d9e17458358a/vim/rc/mappings.rc.vim#L24-L25

最近はフォーマッタを使うことが増えたので、インデントを手で調整することはあまりなくなりました。

https://zenn.dev/vim_jp/articles/f480fbad7572b7

行を上下に移動

行を左右に移動させるマッピングを紹介したので、上下移動も紹介しましょう。
Normal modeでは現在の行を、Visual modeでは選択中の複数行をまとめて上下移動させます。直後に=を実行するので、自動でインデントも揃えられます。

nnoremap <expr> <C-k> $'<Cmd>move-1-{v:count1}<CR>=l'
nnoremap <expr> <C-j> $'<Cmd>move+{v:count1}<CR>=l'

xnoremap <silent><C-k> :move'<-2<CR>gv=gv
xnoremap <silent><C-j> :move'>+1<CR>gv=gv

<c-j><c-k>にマッピングしていますが、かなり押しやすいキーなので他の機能と取り合いになるかもしれませんね。
筆者は「2行下にずらす」のような場合はカウントを使わず2連打することが多いので、連打しやすい<c-j><c-k>にマッピングしています。

行を上下に複製

前掲のものは移動だったので、コピーも紹介しましょう。<space>gで下に、<space>Gで上に複製します。
こちらではインデントを抜けたり入ったりすることは想定していないため=は使っていません。
実は、移動と異なり、上に複製しても下に複製してもバッファの結果は変わりません。複製後のカーソル位置・選択範囲をどうするかがポイントですね。

nnoremap <Space>g <Cmd>copy.<CR>
nnoremap <Space>G <Cmd>copy-1<CR>

xnoremap <Space>g :copy'<-1<CR>gv
xnoremap <Space>G :copy'>+0<CR>gv

空行での編集開始時に自動でインデント

空行で編集を開始したとき、前後の行がインデントされていたらそのインデントに揃えて編集開始したいと思ったことはありませんか。実はccだとその動きをするのですが、通常のiでは対応していません。
ということで、空行ならccに、そうでなければ通常のiにフォールバックするようなマッピングを追加します。レジスタを汚さないよう、ブラックホールを使いましょう。

nnoremap <expr> i empty(getline('.')) ? '"_cc' : 'i'
nnoremap <expr> A empty(getline('.')) ? '"_cc' : 'A'

yuki-yanoさんのdotfilesを参考にしました。

https://github.com/yuki-yano/dotfiles/blob/c56b219116de1693c45c20b03b109497896d5b16/.vimrc#L602-L605

引用箇所で使われているKeymapは以下の記事のものですね。

https://zenn.dev/kawarimidoll/articles/513d603681ece9

入力してから大文字小文字を切り替え

UPPER_SNAKE_CASEのように大文字が連続するとき、どのように入力しているでしょうか。Shiftキーを押しっぱなしでしょうか、Caps Lockを使うでしょうか?
以下の設定を使うと、直前のwordを<c-g><c-u>で大文字に、<c-g><c-l>で小文字に変換できます。また、<c-g><c-k>で最初の文字だけを大文字にします。
つまり、とりあえず小文字で入力して、後から大文字に変換できるということです。後から小文字にする需要はあまりありませんが。

inoremap <C-g><C-u> <esc>gUiwgi
inoremap <C-g><C-l> <esc>guiwgi
inoremap <C-g><C-k> <esc>bgUlgi

thincaさんのvimrcで紹介されていたものです。gi便利。

https://gist.github.com/thinca/1518874#file-vimrc-L784-L786

<esc>しているのでドットリピートが途切れる点には注意してください。

very magicフラグを入れた状態で検索開始

検索をvery magic(:h \v)で行いたい場合が多いので、/で起動したら即\vを入力するようにしています。

nnoremap / /\v
nnoremap ? /\V

?でvery no magic検索になるようにしていますが、実はこちらはあまり使っていません。
はじめから正規表現オフで検索スタートするのではなく、何かを入力した後で「あ、これは正規表現を切った方が良いな」と気付くことが多いんですよね。
これに関しては自作のmagic.vimで後から切り替えられるようにしています。複雑になってしまうので本記事での詳細説明は控えます。

https://zenn.dev/kawarimidoll/articles/4d31576fb9f67b

カーソル下のキーワードをサクッと置換

デフォルトのSccと機能が被っているので潰して、置換を起動するようにしています。

Normalではcwordを置換し、自動でvery nomagicフラグと単語境界を挿入します。
Visualではzレジスタを介して選択範囲を置換しますが、スラッシュはエスケープします。

nnoremap S :%s/\V\<<C-r><C-w>\>//g<Left><Left>
xnoremap S "zy:%s/\V<C-r><C-r>=escape(@z,'/\')<CR>//gce<Left><Left><Left><Left>

全く同じではないですが、monaqaさんのdotfilesを参考にした記憶があります。

https://github.com/monaqa/dotfiles/blob/f214e9628cbac0803ad9d9bc2ad1582c696c4e6b/.config/nvim/scripts/keymap.vim#L63-L69

Sも押しやすいキーなので他のマッピングと取り合いになるかもしれません。あと今ならzレジスタではなくgetregion()を使えるかな…?

ペースト結果のインデントを自動で揃える

ペーストした際に、インデントを整え直したことはないですか。実は]pで自動でインデントを揃えてくれます。
ついでにマークを使ってカーソルをペースト範囲末尾に置くようにしています。こっちのほうが直感的なので。

nnoremap p ]p`]
nnoremap P ]P`]

直前のペースト範囲を選択する

デフォルトでは、gvで直前の選択範囲を再選択できます。
この発想で、gVで直前のペースト範囲を選択できるようにしました。

noremap gV `[v`]

まあgVってちょっと押しづらくて余り使っていないのですが。

直前・直後の空行に飛ぶ

f, Fは直後のキーを検索する機能だからマッピングはできない…と思っていませんか。
確かに単体で潰すのはおすすめしませんが、プレフィックスとしてなら使用できる可能性があります。
f<cr>は押したことありますか?空いてますよ!

fの後に制御文字が来る組み合わせはデフォルトの機能が割り当てられていません。
ということで、fで改行へ飛ぶイメージで、直前・直後の空行(正確には、paragraphの途切れ)に飛ぶマッピングはいかがでしょうか。

nnoremap F<cr> {
nnoremap f<cr> }

f<bs>f<c-f>も押しやすそうですね。

現在のバッファのgit diffを確認する

c, d, yはカットやコピーといった基本的な機能だからマッピングはできない…と思っていませんか。
確かに単体で潰すのはおすすめしませんが、プレフィックスとしてなら使用できる可能性があります。
dsは押したことありますか?空いてますよ!

オペレータの後にテキストオブジェクトではないキーが来る組み合わせはデフォルトの機能が割り当てられていません。
ということで、dsで現在のバッファの更新内容を見る(diff start)マッピングはいかがでしょうか。

nnoremap ds <cmd>new <bar> read !git diff #<cr>gg

これ↑だけだとちょっと見にくいので、以下の記事のコマンドを呼び出すようにすると、より見やすくなると思います。

https://zenn.dev/vim_jp/articles/b15bbd5b682cd8

" GitDiffは記事を参照
" https://zenn.dev/vim_jp/articles/b15bbd5b682cd8
nnoremap ds <cmd>GitDiff<cr>

ypcdも押しやすそうですね。

おわりに

本記事を書き始めた時点では「Uでリドゥ」を紹介するつもりだったのですが、それだけだと味気ないな…と思って追加していったらけっこうな量になってしまいました。
お役に立てると幸いです。

マッピングに使えるキーについてはスパルタンVim5.1が詳細に解説しているので読みましょう。

https://files.kaoriya.net/docs/SpartanVim/SpartanVim-5.1-online.pdf

なお、スパルタンVimでは、Normalのc, d, f, yにはマッピングできないと書かれていますが…前述の通り、プレフィックスとしてならマッピングできる可能性があります。

以下の記事も参考になります。

https://zenn.dev/vim_jp/articles/2023-05-19-vim-keybind-philosophy

Discussion