🚀

Vimプラグインマネージャー『dpp.vim』への移行と設定方針

2024/01/15に公開

始めに

年末年始の休みでNeovimプラグインマネージャをdein.vimからdpp.vimに移行しました。
dein.vim自体も難しいプラグインマネージャではありますが、dpp.vimはさらに上を行く難易度です。
また使用者によって構成や設定方法は違う可能性があり、応用力が求められるプラグインマネージャです。
今回の移行作業に際して、私の構成や設定方針を記事として残しておきます。

基本的な設定や使い方に関しては、Shougoさんの記事をご覧ください。

もくもく会はいいぞ

年末年始の2023-12-28にvim-jpで開催された 『新宿もくもく会』 では、このdpp.vimの移行作業というテーマで参加しました。
当日以内での移行とは行きませんでしたが、もくもく会という集中できる空間もあり、よい刺激を受けながら作業ができました。
当日は悩んでいる様子しか見せることができませんでしたが、無事dpp.vimへの移行ができたという報告も兼ねさせていただきます。

もくもく会はいいぞ!

構成

過去の構成・設定について

dpp.vimの拡張について

現在使用しているdpp.vimの拡張は次のとおりです。

  • dpp-ext-lazy
    • プラグインの遅延起動処理を追加する拡張
  • dpp-ext-toml
    • toml読み込みを使用できるようになる拡張
  • dpp-ext-installer
    • プラグインのインストール処理が可能になる拡張
  • dpp-protocol-git
    • Gitのcloneによるプラグインインストールを可能とする拡張

この拡張の構成をdein.vimと比較した場合、ローカルプラグインの読み込み機能を使用しない構成になります。
ローカルプラグインの読み込み機能はdein.vimの頃から使ってなかった機能だったので、このように使う機能を選択できるのがdpp.vimの良さですね。

まだ試せてはいないですが、この他にも拡張が作成されているようなので、設定のしがいがありそうですね。

ディレクトリ構造

ディレクトリ構成は次のようにしています。

directories
 ~/.config/nvim
 ├── after
 ├── denops
+├── dpp # dpp.vimの設定をまとめたディレクトリ
+├── hooks # hooks_fileをまとめたディレクトリ
 ├── lua
+├── rc # inlineVimrcsに追加される全体設定をまとめたディレクトリ :h dpp-option-inlineVimrcs
+├── snippets # 個人で定義したスニペット用のディレクトリ(今回は関係ない…)
+└── toml # dpp-ext-tomlで読み込むtoml用のディレクトリ

通常のruntimepathdenops.vimを使用した場合を除いて、自動で探索されないディレクトリを追加しています。
対象のディレクトリにはマークを付けていますが、ディレクトリの名前のとおりの意味合いになっております。
このとおりに各種設定ファイルを配置し、init.luadpp.vim内での設定で各種ディレクトリを探索して、
すべての設定はdppのキャッシュとしてまとめられるようになっています。

設定の基本構成としては、次の流れで読み込まれていきます。

  1. init.lua
  2. lua/user/rc.lua
  3. dpp/config.ts
    1. rc/*.lua
    2. toml/*.toml
    3. hooks/* ※各種プラグイン毎に読み込み

今回紹介する設定では、主にlua/user/rc.luadpp/config.tsを中心に紹介したいと思います。
tomlによる設定に関しては、dein.vimと構造は変わらないため説明を省きます。

設定

lua側の設定

https://github.com/yasunori0418/dotfiles/blob/d24f946e808782290093091616fb77b81e8372fb/config/nvim/lua/user/rc.lua

init.luaからlua/user/rc.luasetup関数を呼び出すことで、
各種ディレクトリのマークと必須プラグインのインストールまでをセットアップするようにしています。

setup

最初はNeovimで用意されているNVIM_APPNAMEへの対応と、各種設定を配置したディレクトリをマークするために、グローバル変数や環境変数にセットしています。
後半の方は、dpp.vimを使用するための設定郡で、別の関数に切り出しています。

plugin_add

一番大事な関数です。
初回起動からdpp.vimが使えるようになるためには、自前の極小プラグインマネージャを構築する必要があります。
私の場合色々とリッチにしていることもあって、他のGitホスティングサービスからのプラグイン追加をできるようにしていますが、
現状ではGitHubからのインストールだけで十分かと思います。

プラグインはruntimepathに追加されることで、プラグインとして使用可能になるため、必ずruntimepathへの追加を行なっています。
prependを使用することでruntimepathの先頭にdpp.vimとして必須のプラグインが追加されるようにしています。
気持ちの問題かもしれませんが、先頭に追加するのは「そっちの方が処理は速くなるのかな…」という迷信や思いを込めています。
ここに関しては、もうすこしよい書き方があるかもしれないと模索している最中です。

dpp_setup

lua側の最後設定です。
ここでは大量にdpp.vim用のautocmdを定義したり、自動セットアップのために涙ぐましい努力を重ねています。
自動セットアップとして初回起動時には、すべてのプラグインがインストールされてNeovimが安定して終了するように制御しています。

make_stateを実行するタイミングはdenops.vimがプラグインとして使える状態になっていないといけません。
そのため、Shougoさんの記事ではDenopsReadyというイベントにフックする形で、make_stateを実行しています。
しかし、autocmdで書かなくてもdenops.vimで用意されているdenops#server#wait_asyncという関数が便利です。

                                              *denops#server#wait_async()*
denops#server#wait_async({callback})
    Wait asynchronously until a |DenopsReady| autocmd is fired and invoke
    a {callback}. It invokes the {callback} immediately when the autocmd
    is already fired. If this function is called multiple times, callbacks
    registered are called in order of registration.

    DenopsReadyになるまで非同期で待機します。
    autocmdが起動され、{callback} が呼び出されます。
    autocmdがすでに起動されたら、すぐに {callback} を呼び出します。
    この関数が複数回呼び出された場合、登録されたコールバックが登録順に呼び出されます。

また、設定の編集・保存後にフックする形でcheck_filesという処理を実行しています。
これが実行されると、dpp.vimで作成されるキャッシュの更新として、make_stateを実行してくれます。
そのためNeovimの設定ファイルだけを探索しておいて、対象のファイルを変更したときだけcheck_filesを実行するようにしています。

TypeScript側の設定

https://github.com/yasunori0418/dotfiles/blob/d24f946e808782290093091616fb77b81e8372fb/config/nvim/dpp/config.ts

make_stateによって呼び出すTypeScriptの設定です。

基本方針として主要な処理はこのconfig.tsファイルで実行していますが、ファイルを集めたり、型を定義するのはhelper.tsにまとめています。
この基本方針は、ddc.vimddu.vimでも同じような方針で設定を分割しています。

ファイル探索系

各種設定ファイルを配置したディレクトリ内を探索する関数として、helper.tsには次の関数をそれぞれ定義しています。

  • gatherVimrcs
  • gatherTomls
  • gatherCheckFiles

これらの関数では、dpp.vimの設定として返して欲しい型にしてデータを返すように処理をまとめて、config.tsでは探索場所と追加の条件があれば、適宜定義しています。

これらの関数を使用するための探索場所をlua側の設定でグローバル変数や環境変数という形で定義しておくことで、denops_stdを使えば呼び出せるということです。
たとえばgatherVimrcsの場合、~/.config/nvim/rcというディレクトリを探索しやすくするために、vim.g.rc_dirというグローバル変数にパスを格納しています。
これをdenops_stdで取得する場合は次のようにすると呼びだせます。

const inlineVimrcs: string[] = gatherVimrcs(
  await vars.g.get(denops, "rc_dir"),
  vimrcSkipRules,
);

このvarsというものを使えば、vim側で設定した変数へのアクセスが可能になります。

私の場合、dpp.vimの依存としてまとめているdeps.tsからインポートするようにしています。

https://deno.land/x/dpp_vim/deps.ts

その後、対象のディレクトリの中のファイルリストを作成するのは、deno本体のAPIのDeno.readDirSyncというメソッドを使用しています。

https://deno.land/api?s=Deno.readDirSync

この探索方式はtomlファイルを探索するためにも使用していますが、gatherCheckFilesだけは違う方法で探索しています。

checkFilesでは設定ファイルのリストを渡す必要がありますが、設定ファイルはさまざまなディレクトリに配置されている関係で、Deno.readDirSyncを使うのは少し大変です。
そのため設定の基礎ディレクトリとしてbase_dirというグローバル変数を定義しているので、そこから再帰的にファイルを探索する方法として、globpathというvimの関数をdenops_stdから呼び出しています。

export async function gatherCheckFiles(
  denops: Denops,
  path: string,
  globs: string[],
): Promise<string[]> {
  const checkFiles: string[] = [];
  for (const glob of globs) {
    checkFiles.push(await fn.globpath(denops, path, glob, true, true));
  }

  return checkFiles.flat();
}

denops_stdでvimに組み込まれた関数を使用する場合はfnから使用できます。
また、globpathという関数に関しては、:h globpath()でご確認ください。

パーシャルクローン

dpp-protocol-gitによって、プラグインインストール時にはgit cloneを使用しますが、このときに大きなプラグインを落すときに時間が掛ってしまいます。
これを短縮する方法として、「パーシャルクローン」という方式を使ってプラグインをcloneしてくれるオプションを有効にしています。
パーシャルクローンについては次の記事が参考になります。

https://github.blog/jp/2021-01-13-get-up-to-speed-with-partial-clone-and-shallow-clone/

移行してみての感想

dpp.vim移行は楽しかったのと同時に、使用できるまで思うような処理の制御が難しく、
dein.vim以上においそれとお勧めできないプラグインマネージャだと思いました。
それ以上にプログラマーが使うツールということを前提にしてみれば、この難易度に妥当さを感じました。

そんな高難易度プラグインマネージャですが、高難易度という言葉にワクワクしたり、設定するのが大好きっていう人は是非ともチャレンジしてみてください!

GitHubで編集を提案

Discussion