leap.nvimを拡張して検索対象にラベルをつけて飛べるleap-search.nvimを作った

by
カテゴリ:
タグ:

本記事はVim駅伝の5/24の記事です。

leap.nvimについて

easymotion系のNeovimプラグインとしてメジャーどころにはhop.nvimleap.nvimがあります。

leap.nvimはいわゆるeasymotion系のプラグインで、入力した文字にマッチする箇所にラベル(a, b, c, …)をつけ、ラベルを入力するとその位置にカーソルを移動します。

デフォルトの挙動はeasymotion2-character search motionに近いもので、2文字にマッチする箇所にラベルをつけます。

2-character search motionとして見た時のeasymotionleap.nvimの大きな違いは2つ。

  • ラベルをつけるタイミング
    • easymotionは2文字入力し終えてから
    • leap.nvimは1文字目の段階から。ジャンプ先の最初の2文字目くらいは読まずとも頭に入っているだろうから、2文字目を入力する時間でラベルを読み、3文字目としてラベルを入力すれば思考のスピードでジャンプできる、という考え方のよう。
  • ラベルをつける位置
    • easymotionはジャンプするまさしくその位置

    • leap.nvimはジャンプ先の2文字後。

      leap
      ^に飛びたい場合、「le」と左から文字を読みながら入力すると、3文字目の「a」の位置にラベルがつくので、そのまま目の移動に合わせてラベルを入力できる

合う合わないはありそうですが、個人的にはなかなか合理的に思います。

ところでleap.nvimは単純なモーションプラグインではなく、指定した箇所にラベルをつけて、選んだラベルに対して任意のアクションを実行するためのフレームワークでもあります。

targets引数を指定すれば、1、2、3行目の1文字目にラベルをつけられますし、action引数を指定すれば、選択したラベルやターゲットに関する情報を元に任意の操作を実行できます。

require("leap").leap({
  targets = {
    { pos = {1, 1} },
    { pos = {2, 1} },
    { pos = {3, 1} },
  },
  action = function(x) {
    vim.print(x.label) -- 選択したラベルの表示
    vim.print(x.pos) -- 選択したターゲットの位置
  }
})

簡単に拡張できそうな気配がしますね!

leap-search.nvimについて

というわけで指定したパターンを元に、Window内の文字列を検索してラベルをつけ、ジャンプするleap-search.nvimを作ってみました。

正規表現による検索結果へのジャンプ

たとえば以下のように leap.{-}nvim などと、Vimの正規表現を利用できます。

require("leap-search").leap("leap.{-}nvim")

応用すると直前の検索パターンでハイライトされた箇所にジャンプなんてこともできますね。

/leap.{-}nvim
:lua require("leap-search").leap(vim.fn.getreg("/"))

とても便利なので、私は以下のようにマッピングしています。

vim.keymap.set("n", "gn", function() require("leap-search").leap(vim.fn.getreg("/")) end)
vim.keymap.set("n", "gN", function() require("leap-search").leap(vim.fn.getreg("/"), {}, { backward = true }) end)

ドキュメントがまだ皆無ですが、第二引数がleap-search.nvim独自のオプションのテーブル、第三引数がleap.nvim本体のオプションのテーブルになってます。

様々な検索エンジンの利用

第二引数を弄ると、以下の検索エンジンの組み合わせも可能です。

  • Vimの正規表現を用いるvim.regex
  • Lua expressionまたは部分一致を用いるstring.find
  • migemoで日本語をローマジ検索するkensaku.query
    • エンジンが依存するkensaku.vimが必要
    • 全角文字へのラベルに関するバグを補正するleap-wide.nvimを推奨

たとえば string.find で部分一致を、 kensaku.query でローマ時検索を実行するなら以下。

-- 「kensaku」というパターンで「kensaku」にも「検索」にもラベルをつけられる
require("leap-search").leap("kensaku", {
  engines = {
    { name = "string.find", plain = true },
    { name = "kensaku.query" },
  },
})

対話的な検索

更に、第一引数に検索パターンにnilを与えると対話的な検索モードに入ります。あいまい検索機能こそありませんが、fuzzy-motion.vimに近い挙動です。

require("leap-search").leap(
  nil,
  {
    engines = {
      { name = "string.find", plain = true },
      { name = "kensaku.query" },
    }
  },
  {
    -- 現在のWindow全体を検索対象にする
    target_windows = { vim.api.nvim_get_current_win() },
  },
)

ENJOY!!