🔢

dial.nvim の新機能紹介:ノーマルモード g<C-a>

2023/04/12に公開

はじめに

monaqa です。つい先日、私が開発している dial.nvim というプラグインに連番作成のための新機能が追加されました。今日はその紹介をしたいと思います。

dial.nvim とは

Neovim の <C-a> / <C-x> を拡張する Lua 製プラグインです。

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

標準の Vim/Neovim では、数字の上で <C-a><C-x> を押下することでその数字を増減させることができます。この時点でも便利なのですが、dial.nvim を導入すると日付や boolean 値など、数値以外の様々なものを増減・トグルできるようになります。増減対象のルールを柔軟に設定できるだけでなく、本家と同様にドットリピートにも対応しており、自分の好みに合わせて直感的に操作できる点が特徴です。


dial.nvim のデモ(READMEより)

詳しい機能紹介は 日本語版 README をご覧ください。また、一昨年のゴリラ.vim で発表したスライドもあります[1]

https://www.docswell.com/s/monaqa/ZJLLDG-2021-08-06-gorillavim-dial_nvim_introduction

連番の作成

Vim には連番の自動生成機能が標準で用意されています。たとえば

変換前
1.
1.
1.
1.

といった4行からなる文字列を

変換後
1.
2.
3.
4.

に変換したいとき、標準の Vim で最も楽な編集手順はおそらくビジュアルモードの g<C-a> を使う方法です (:h v_g_CTRL-A)。変換前のテキストの2行目から4行目までを行ビジュアルモードまたはブロックビジュアルモードで選択し、g<C-a> を押すだけで変換後の状態になります。番号付きの箇条書きなどを作成するときに重宝しますね。

同様の操作は dial.nvim でもサポートされており、日付等の連番も作成できるより強力なものとなっています。たとえば

変換前
* xx定例会議 議事録 (2023/02/15)
* xx定例会議 議事録 (2023/02/15)
* xx定例会議 議事録 (2023/02/15)
* xx定例会議 議事録 (2023/02/15)
* xx定例会議 議事録 (2023/02/15)

といった文書があるとき、2行目から5行目を行選択して 7g<C-a> を押せば簡単に

変換後
* xx定例会議 議事録 (2023/02/15)
* xx定例会議 議事録 (2023/02/22)
* xx定例会議 議事録 (2023/03/01)
* xx定例会議 議事録 (2023/03/08)
* xx定例会議 議事録 (2023/03/15)

とすることができます(設定で %Y/%m/%d の日付の増減ルールが有効になっているものとします)。この機能があれば、週毎に開かれる定例会議の議事録も簡単に作成できますね。

しかし、これはあくまで行が連続している時の話です。実際には以下の例のように、日付が飛び飛びになっているケースも少なくありません。

## xx定例会議 議事録 (2023/02/15)

### 出席者

- monaqa

### 議題

### 決定事項

## xx定例会議 議事録 (2023/02/15)

### 出席者

- monaqa

### 議題

### 決定事項

## xx定例会議 議事録 (2023/02/15)

### 出席者

- monaqa

### 議題

### 決定事項

ビジュアルモードでの連番生成は連続した行に対しては有効ですが、離散的な行に対しては不得手です[2]。 このようなケースでスムーズに連番を作るにはどうすればよいでしょうか。

dial.nvim の新機能:ノーマルモードの g<C-a>

それを解決するのがノーマルモードg<C-a> です。以下のような設定を入れることで有効化することができます。

Vim script で記述する場合
nmap g<C-a> g<Plug>(dial-increment)
nmap g<C-x> g<Plug>(dial-decrement)
Lua で記述する場合
vim.keymap.set("n", "g<C-a>", function()
    require("dial.map").manipulate("increment", "gnormal")
end)
vim.keymap.set("n", "g<C-x>", function()
    require("dial.map").manipulate("decrement", "gnormal")
end)

g<C-a> は基本的に <C-a> と同一の機能を持つものの、ドットリピートでの操作時のみ加数が[count]ずつ増加する という特殊効果があります。

論より証拠、先程の例で実際に試してみましょう。

  1. 以下のようなテンプレートを作成して、必要なぶんだけ複製する。

    ## xx定例会議 議事録 (2023/02/15)
    
    ### 出席者
    
    - monaqa
    
    ### 議題
    
    ### 決定事項
    
    
  2. 定例会議 で検索し、日付をインクリメントしたい行に飛んで 7g<C-a> を実行すると、日付が7日インクリメントされる。

    • 日付を狙える位置にカーソルを持っていくことさえできれば、検索クエリは何でも構いません。もちろん正規表現等を使って日付をピンポイントに狙うこともできます。
  3. n で次の日付の手前にジャンプし、 . でドットリピートを実行する。すると、今度は日付が14日インクリメントされる。

  4. 以下、n. を必要な数だけ繰り返す。インクリメントされる日付が21日、28日、…と増えていく。

ポイントは「ドットリピートでの操作時のみ加数が増えていく」という点で、再度 <C-a>g<C-a> を押し直せば加数はリセットされます。あくまで一時的なボーナス、ということですね。

ちなみに 2. のステップを「2023/02/1502 に飛んでから g<C-a> を実行する」に変えれば、月をインクリメントすることができます。この場合「月をインクリメントすべきである」ことを dial.nvim は記憶しているため、 3. 以降のステップでいちいち手動で月にジャンプする必要はありません。日付手前でドットリピートを実行するだけで、自動的に月がインクリメントされます。

その他のユースケース

定例議事録のテンプレートの複製というユースケースを例に紹介しましたが、他にも以下のような用途で効果を発揮すると考えています。

  • 複数行にわたるダミーデータを作成する

    [
      {
        "id": 1,
        "name": foo,
      },
      {
        "id": 2,
        "name": foo,
      },
      {
        "id": 3,
        "name": foo,
      },
      {
        "id": 4,
        "name": foo,
      },
    ]
    
  • 名前の重複が許されない関数定義を適当に作成する

    def test_foo_1():
        assert 100 + 100 == 200
    
    def test_foo_2():
        assert 100 + 100 == 200
    
    def test_foo_3():
        assert 100 + 100 == 200
    
    def test_foo_4():
        assert 100 + 100 == 200
    

余談

この機能が dial.nvim に入ったのはつい先週ですが、発想自体はもっと前からありました。それどころか1年ほど前に denops.vim を用いて開発した dps-dial.vim というプラグインでは、すでに似たような機能が使えていたのです。
しかし開発当時はg<C-a> に割り当てる発想に至らず、レジスタを用いて切り替える扱いづらいインターフェースにしてしまっていたため普段の操作で定着することはありませんでした。 「ノーマルモードの g<C-a> は空いているし、ここに割り当てればよかったのでは」 と閃いたのが先週のことであり、それが dial.nvim への導入の決め手となった、という経緯があります。

おわりに

皆さんもぜひ、dial.nvim で快適なインクリメント/デクリメント生活を送ってください!

脚注
  1. ただし、1年以上前のスライドのため若干内容が古いことに注意してください。たとえば今後の展望で「ドットリピートのサポート」が挙げられていますが、こちらはすでに対応済みです。 ↩︎

  2. 上の例であれば無理やり全体を選択して増減することも不可能ではないものの、途中の行に増減したくない日付があった場合、うまく動きません。 ↩︎

Discussion