@ryoppippi

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

19 Apr 2024 ・ 12 min read


Note

この​記事はVim 駅伝の​ 4/19 の​記事です。

はじめに

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モードを​使って​数字を​インクリメントする

Note

この​章は@taniさんに​リマインドしていただき、​追記しました。

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には​ドットリピートと​いう​機能が​あり、​直前の​変更を​繰り返すことができます^[ここ(https://qiita.com/Kta-M/items/d8fbded37ad3140cfeb0)やここ参照]。 これを​使うと、​直前の​変更を​繰り返すことができるので、​繰り返し置換が​簡単に​行えます。

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

  1. *で​カーソル下の​単語を​検索
  2. ''で​元の​位置に​戻る​^[''も​しくは​``で​直前の​ジャンプ位置に​戻る(https://vim-jp.org/vimdoc-ja/motion.html#‘’)]
  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>"で​ヤンクした​内容を​ペースト^[<C-r><C-r>(https://vim-jp.org/vimdoc-ja/insert.html#i_CTRL-R_CTRL-R)]
  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. 最初の​行の​編集を​行う​^この​場合だと​`0yiwI[<esc>A(https://example.com/p)j` ]
  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

comment on bluesky / twitter
CC BY-NC-SA 4.0 2022-PRESENT © ryoppippi