Telescope.nvimの実装を読みながら自分の設定をカスタマイズする
ふと思い立ったので、自分が使っているプラグインのカスタマイズを雑に紹介したいと思います。 今回は Telescope.nvim のハイライトのカスタマイズを紹介します。
Telescope.nvim
Telescope.nvim はNeovimのファジーファインダープラグインです。 多くの組み込みソースを提供し幅広い操作をサポートしているので、このプラグインを導入するだけでも多くのことができるようになります。 この記事では、インストール手順や基本的な使い方は説明しませんが、実際に行ったカスタマイズや、そのためにコードリーディングした箇所を簡単に紹介します。 今回行ったカスタマイズはdotfilesの一部として公開しています。
用語の定義
カスタマイズを紹介する前に、用語の定義をしておきます。 基本的にはREADMEやWIKIなどのドキュメントおよびソースコード内のコメントにおける説明で用いられているものを使用します。 このセクションで明示していない場合でも、説明が難しい場合はソース内で用いられている変数および関数の名前をそのまま使用することもあります。
Finder
Fuzzy search の対象となるオブジェクトの集合を返すものです。 ファイルやdiagnosticsの一覧(を返すもの)などが挙げられます。
Picker
検索結果が表示される部分です。 Finderから表示内容を受け取り、ユーザからの入力に応じて表示内容の絞り込みを行います。
Entry
Pickerに表示される各アイテムです。 Finderから返されたものを絞り込み、Highlightなどを適用したものがEntryの集合になります。
Previewer
Picker内で選択されたEntryのプレビューを表示します。 設定でon/offが切り替えられるほか、ハイライトも適用できます。
Previewer
Telescope.nvim
にはカーソルのある検索結果のプレビューを表示することができるのですが、このプレビューにはシンタックスハイライトを適用することもできます。
視認性が向上するため是非とも適用しておきたい一方、仮にサイズの大きいファイルが検索結果に出現した場合に動作が詰まってしまうことも考えられます。
これに対しては wiki に記載されているように、特定のサイズを超えるファイルを表示しないようにしたり、最初から特定の位置までファイルを読み込むことが可能です。
Picker
ファイル名が検索対象となるとき、検索結果に表示されるのはもちろんファイル名なのですが、全て同じ文字色だと見づらい場合があります。 特にディレクトリ構成が複雑であり、深い位置にファイルが位置している場合などは顕著だと思います。
そのため、ディレクトリ名とファイル名に別のハイライトを当てるようにしたいと思いました。
Telescope.nvim
では各pickerに渡すオプションにentry_maker
を指定することができ、Entryの内容を自由にカスタマイズできます。
ソース上のコメントによるとEntryMaker
には最低限以下の3つの要素が必要になります。
[object Object]
今回は見た目をカスタマイズしたいので、display
をカスタマイズします。
display
の正体はentry
を受け取り、実際に表示するテキストとハイライト情報を返す関数(もしくは文字列)です。
ここで返されるハイライト情報は{ { start_col, end_col }, hl_group }
のような構造になっています。
したがって、各Entryには部分的に別々のハイライトを適用できます。
これを利用して、ディレクトリとファイル名に別のハイライトを適用するようにします。
Telescope.nvimではPicker内の各Entryにおけるdisplay
を作成するための関数が提供されています。
これを用いてカスタムハイライトを適用できるdisplay
を作成します。
local displayer = require("telescope.pickers.entry_display").create({
separator = "",
items = { ... },
})
Entryは見かけ上は文字列になります。
文字列のjoin関数をイメージしてもらえるとわかりやすいでしょうか。
items
にEntryに表示する内容を順に指定します。
この指定を別々のitemとすることで、別のハイライトを指定することができるようになります。
separator
は各items
を結合する際の区切り文字です。
各item
は表示する文字の幅を指定することができます。
数値を指定することで、渡された内容がその文字数になるようにトリミングされます。
nil
を指定することで、入力された文字列をそのまま表示することができます。
残りの部分すべてを使用するように設定することもでき、その場合はremaining=true
を指定します。
今回はパスのディレクトリ部分とファイル名部分に別々のハイライトを指定するのが目的です。
したがってディレクトリ名とファイル名部分は別々のitems
として定義します。
その際に見た目が不自然にならないようにseparator
には空文字を指定します。
こうして生成されたdisplay
を用いて各Entryを生成します。
Finderから渡された情報、実質的には素のEntryですが、これをよしなに変換し先程生成したdisplay
を介することでカスタマイズしたEntryを出力します。
Telescope.nvimではFinderの結果からEntryを生成する関数も提供されています。
カスタマイズしたいPickerが使用しているFinderの種類に応じてこれらの関数を使い分けます。
ファイルの一覧をソースとしているならgen_from_file
を、vimgrepの結果をソースとしているならgen_from_vimgrep
をといった具合です。
これらの関数から生成されたEntryのdisplay
を上書きすることで、ハイライトをカスタマイズするのが簡単で良いと思います。
以下に一例を挙げますが、これ以外にもgitやLSPなど様々なソースに対応しています。
- gen_from_string
- gen_from_file
- gen_from_vimgrep
- gen_from_quickfix
- etc...
これらの関数を使用することで、デフォルトの状態のEntryが作成されます。
ソースから渡された入力からファイルパスを受け取り、ディレクトリ名とファイル名に分け先程のdisplay
に渡します。
参考までに、vimgrepの結果を表示するPickerをカスタマイズする際のコードを載せます。
return function(line)
-- vimgrepの結果からデフォルト状態のEntryを生成する関数を取得
local entry_maker = make_entry.gen_from_vimgrep(opts or {})
-- Finderの出力1行分からデフォルト状態のEntryを生成
local entry = entry_maker(line)
-- entryのdisplayをカスタマイズしたハイライトを適用したもので上書きする
entry.display = function(et)
-- vimgrepの結果を分割
local filepath, row, col, text = get_path_and_pos(et.value, ":")
-- ファイル名とディレクトリ名を分割
local filename, directory_path = get_path_and_tail(filepath)
-- ファイルタイプからアイコンを取得
local icon, iconhl = utils.get_devicons(filename)
return displayer({
{ icon, iconhl },
spacer,
{ directory_path .. "/", "Directory" },
{ filename, nil },
separator,
{ row, "Number" },
separator,
{ col, "Number" },
spacer,
{ text, "Comment" },
})
end
-- entryを返す
return entry
end
Spacer や Separator は明示的にwidth=1
としても良いですし、好みに応じてそれ以外の値を指定してもいいです。
スクリーンショット
このような見た目になります。
この記事で紹介したもの以外にも、ファイル名を切り出して表示することで目的のファイルを見つけやすくするカスタマイズなども適用しています。
特にindex
or init
ファイルと細かく別れたモジュールが配置されているような場合には少し見やすくなると思います。
アイディアは
issue から拝借しました。
詳細なコードも紹介されているので、気になった方は参考にしてみてください。