🤹

なぜVimmerの僕はマルチカーソルを必要としないか

2024/04/19に公開8

はじめに

VSCode などのテキストエディタには、マルチカーソルという機能があります。
これは、エディタ上に複数のカーソルを出現させ、一度に複数の場所に同じ操作を行うことができる機能です。

0.gif
VSCode上でのマルチカーソル

自分はVSCodeをメインとしていた時にはこの機能を多用していたのですが、Neovimに移行してから一切使っていません。

一応Vim/Neovimにもマルチカーソルを実現するプラグインがいくつか存在します。
ですが、一度は入れてみるものの結局使わないままアンインストールしてしまいました。

https://github.com/terryma/vim-multiple-cursors
https://github.com/mg979/vim-visual-multi
https://github.com/brenton-leighton/multiple-cursors.nvim

ではなぜ、マルチカーソルが必要なくなったのか。
それはVim/Neovimの操作体系/機能が十分に強力であるので、マルチカーソルを使わなくても同じことができるからです。

この記事では、自分がVSCodeのマルチカーソルで行っていた操作をVim/Neovimの操作体系/機能でどのように行っているかを紹介します。

行の先頭に文字を追加する

例えば以下のようなテキストがあるとします。

foo
bar
baz

例えばこのテキストの行頭に1. を追加したいとします。

1. foo
1. bar
1. baz

この時、VimではVisual Blockモードを使うことで簡単に行うことができます。

  1. 0または^で行頭に移動する
  2. Ctrl-vでVisual Blockモードに入る
  3. jkで追加したい行を選択する
  4. Iで挿入モードに入る
  5. 追加したい文字列を入力する
  6. Escで挿入モードを抜ける

1.gif
Visual Blockモードを使って行の先頭に文字を追加する

行の末尾に文字を追加する

行の末尾に文字を追加する場合もVisual Blockモードを使うことで簡単に行うことができます。

  1. Ctrl-vでVisual Blockモードに入る
  2. jkで追加したい行を選択する
  3. $で行末に移動する
  4. Aで挿入モードに入る
  5. 追加したい文字列を入力する

2.gif
Visual Blockモードを使って行の末尾に文字を追加する

追記: Visual Blockモードを使って数字をインクリメントする

Visual Blockモードを使うと、数字をインクリメントすることも簡単に行うことができます。
例えば

1. foo
1. bar
1. baz

この時、Visual Blockモードで数字を選択し、 <Ctrl-a>/<Ctrl-x>を押すことで、数字を一気にインクリメント/デクリメントすることができます。

2. foo
2. bar
2. baz

もちろん一気に増やしたい時は、20<Ctrl-a>のように数字を指定することもできます。

さらに、g<Ctrl-a>/g<Ctrl-x>を使うことで、連番でインクリメント/デクリメントすることもできます。

この場合、数字を選択してからg<Ctrl-a>を押すことで、

1. foo
2. bar
3. baz

のように数字をインクリメントすることができます。

dial.nvimについて

さらに、Neovimのプラグインであるdial.nvimを使うことで、色々な形式の数字をインクリメント/デクリメントすることができます。

2024-12-29 hello
2024-12-29 hello
2024-12-29 hello
2024-12-29 hello
2024-12-29 hello

この日付をVisual Blockモードで選択して、`g<Ctrl-a>`を押すと

2024-12-29 hello
2024-12-30 hello
2024-12-31 hello
2025-01-01 hello
2025-01-02 hello

こうなる

https://github.com/monaqa/dial.nvim

カーソル下の単語の編集

Vimでは*を用いるとカーソル下の単語を前方検索することができます。
また、gnを用いると前方検索&ビジュアル選択を行うことができます。
さらに、cgnを用いると前方検索&ビジュアル選択&変更を行うことができます。

ところで、vimにはドットリピートという機能があり、直前の変更を繰り返すことができます[1]
これを使うと、直前の変更を繰り返すことができるので、繰り返し置換が簡単に行えます。

これらを組み合わせると、以下の操作でカーソル下の単語を繰り返し置換することができます。

  1. *でカーソル下の単語を検索
  2. ''で元の位置に戻る[2]
  3. cgnで単語を編集
  4. nで次の検索結果へ移動
  5. .で単語の置換を繰り返す(いわゆるドットリピート)

7.gif
カーソル下の単語を繰り返し置換

このような操作を毎回行ってもいいのですが、いい感じにkeymapを設定してしまうと便利です。

vim.keymap.set("n", "<leader>*", "*''cgn")
*の挙動についての補足

標準の*#では、現在のカーソルの位置から前方検索を行って次の検索結果にジャンプします。
そのため本文中にある通り*で検索を行った後に''で元の位置に戻る必要があります。
もしカーソルを移動したくない場合はvim-asteriskのようなプラグインを使うと便利です。

https://github.com/haya14busa/vim-asterisk

LSPを用いて変数を一括変更する

Neovimの組み込みのLanguage Server Protocol(LSP)クライアントを用いることで、変数名の一括変更を行うことができます。

:lua vim.lsp.buf.rename()

3.gif
LSPを用いて変数を一括変更する

また inc-rename.nvimを用いると、変更中にプレビューが表示されるようになります。
https://github.com/smjonas/inc-rename.nvim

Buffer全体で単語を一括変換する

Vimには便利な置換コマンドが用意されています。
例えば、:s/foo/bar/g でBuffer全体でfoobarに置換することができます。

これを利用して、自分は選択した範囲から置換コマンドを一発で呼び出せるkeymapを設定しています。

vim.keymap.set("x", "<leader>r", 'y:%s/<C-r><C-r>"//g<Left><Left>')
vim.keymap.set("n", "<leader>r", 'yiw:%s/<C-r><C-r>"//g<Left><Left>')

このコマンドのやっていることは以下の通りです。

  1. yで選択範囲をヤンク
  2. :%s//gでBuffer全体に対して置換コマンドを開始
  3. <C-r><C-r>"でヤンクした内容をペースト[3]
  4. <Left><Left>で最初の//の間にカーソルを移動

4.gif
選択範囲から置換コマンドを一発で呼び出す

より複雑な操作

ではもっと複雑な操作を考えてみましょう。
例えば、以下のようなテキストがあるとします。

foo
bar
baz
hoge
fuga

このテキストを以下のように変更したいとします。

[foo](https://example.com/foo)
[bar](https://example.com/bar)
[baz](https://example.com/baz)
[hoge](https://example.com/hoge)
[fuga](https://example.com/fuga)

このような操作はVisual Blockで一発で行うことは難しいです。
移動が伴うために、挿入モードで一発で行うことができず、ドットリピートも難しいです。

このような場合に使えるのがマクロです。
マクロは、複数の操作を記録して再生することができる機能です。

この場合だと以下のようなマクロを記録して再生することで簡単に変換することができます。

  1. 最初の行に移動
  2. qqqレジスタへのマクロの記録を開始
  3. 最初の行の編集を行う[4]
  4. qでマクロの記録を終了
  5. 4@qでマクロを4回繰り返す(Neovimなら4QでもOK)

5.gif
マクロを使って複雑な操作を行う


先の場合では、繰り返し回数を指定することで置換を行いましたが、他にもマクロの適用方法はあります。

例えば、gコマンドを使うことで、特定のパターンにマッチする行に対してマクロを適用することができます。

:g/foo/normal @q

このコマンドは、fooという文字列を含む行全てに対してマクロqを適用します。

検索結果に対して個別にマクロを適用したい時もあるかもしれませんね。
その場合は、先に述べた*&gnを使って一個ずつ@qでマクロを適用していくことができます。
(もちろん適用したい行までjkで移動してもOK)

また行単位に限らず、Visualモードに入って

:'<,'>normal @q

を実行することで任意の選択範囲に対してマクロを適用することができます。

AIを使う

「Vimの操作覚えるのとかめんどくせえよ、そんなことちまちまやってられっかよ」という方にはAIを使う方法がおすすめです。

https://github.com/github/copilot.vim
https://github.com/zbirenbaum/copilot.lua

6.gif
GitHub Copilotを使ってコードを生成する

まとめ

  • VSCodeなどのマルチカーソル機能は便利だが、Vim/Neovimの操作体系/機能で代替できる
  • Visual Blockモード/マクロ/LSP/置換コマンドなどを使うことでマルチカーソルで行っていた編集をカバーできる
  • AIは正義
おまけ

Neovim向けにEmacsでお馴染みのdmacroが実装されつつあるらしい...?
https://github.com/tani/dmacro.nvim

脚注
  1. ここここ参照 ↩︎

  2. ''もしくは``で直前のジャンプ位置に戻る ↩︎

  3. <C-r><C-r> ↩︎

  4. この場合だと0yiwI[<esc>A](https://example.com/<C-o>p)<esc>j ↩︎

Discussion

ピン留めされたアイテム
alminum_popalminum_pop

あくまで上の例についてなら、正規表現でおてがるにいけますよ。というつもりで示しておきます。

:'<'>s#\v(\w+)#[\1](https://example.com/\1)#

時間ができたのでもう少し丁寧に書いておきます。

  1. fooの行でV(行選択)
  2. G(最終行まで移動)
  3. : s (右になる :'<'>s)
  4. あとは上のように入力してenter。neovimなら、確定前にプレビューできます。
    (上記コマンドの\vは、very magicを指定しています。詳細はhelp参照のこと)
    vimでも、traces.vimを使えば、プレビューできます。
ryoppippiryoppippi

ありがとうございます!
そうなんですよね、この指摘は他の方からもいただいておりました。

自分は正規表現を書くのをサボりがち(まだ成熟してない)のでマクロに頼りがちです。
もう少し複雑な例を示せばよかったかなとも思っております。

とても有用なので先頭にピン留めさせていただきますね

alminum_popalminum_pop

おお、参考になれば幸いです。

反対に、私はマクロもドットリピートも、まだうまく使えないですし、他にもcopilotも動作例があって、とても参考になりました。

調子に乗ってもう一つだけ書いておくと、置換で式が評価できる機能が非常に強力です。
下は先の例を置き換えただけなのであまり旨味はないですが、数値をどうこうしたい時には有用です。

:'<'>s#\v(\w+)#\='[' . submatch(1) . '](https://example.com/' . submatch(1) . ')'#

\=以降が、式として評価されます

ryoppippiryoppippi

Vim script使えるのめちゃくちゃ強力ですよね〜
自分はNeovimから入った人なので、すぐにlua使っちゃうんですが、少しずつ慣れていきたいです!