Λlisue's blog

つれづれなるままに更新されないブログ

Vimconf 2018 で "Effective Modern Vim scripting" を喋ってきた

f:id:lambdalisue:20181224162724p:plain

どうも、サブタイトル通りご無沙汰しております、有末です。 この記事は Vim その2 Advent Calendar 2018 のクリスマスイブの記事となっております。

もうだいぶ旬が過ぎてしまいましたが、2018 年にアキバホールにて行われた Vimconf 2018 で "Effective Modern Vim scripting" というタイトルで発表させていただいたので、その報告をさせていただきます。

なお、昨年は謹製プラグインの発表をさせていただいたので、参考として貼らせていただきます。

lambdalisue.hatenablog.com

はじめに

例年通り感想記事を書こうかと思っていたんですが VimConf のスポンサーでもある Quipper さんの Vim初心者に贈る、Vimの各種モードを完全に理解するとっておきの方法 という参加記事が発表者にしかできない記事っぽくて惹かれたので、ちょっと真似して発表の補足記事を書きたいと思います。

発表資料

補足解説

個人的に補足が必要そうだなと思ったスライドの画像と補足を書いていきます。

f:id:lambdalisue:20181224163649p:plain

このスライドでは Vim plugin を作る上でおそらくもっとも重要な pluginautoload ディレクトリの使い分けを説明しています。

Vimruntimepath に登録されたパス(set runtimepath+=...)を起動時に調査します。 その際に各ディレクトリ配下の plugin ディレクトリ内の *.vim ファイルを再帰的にロードしていきます。 つまり、以下の様なディレクトリ構造では (*) が付いたファイル全てが Vim 起動時にロードされることになります。

+- amake.vim/ (runtimepath の一つと過程)
    +- autoload/
        +- amake/
            +- core.vim
        +- amake.vim
    +- plugin/
        +- amake/
            +- foo.vim (*)
        +- amake.vim (*)  

これとは対象的に autoload ディレクトリ内の *.vim ファイルは対応する autoload 関数が呼ばれた際にロードされます。 各ファイルの対応する autoload 関数は各ファイルの autoload ディレクトリからの相対パス# 区切りに直した接頭辞で始まります。 例えば autoload/foo/bar/hoge.vim の autoload 関数は foo#bar#hoge# から始まる関数(例:foo#bar#hoge#hello())となります。

上記の様に pluginautoload 内の Vim script はロードタイミングの違いがあるため、モダンな Vim plugin ではそれぞれを以下の様に使い分けます。

ファイル位置 目的
plugin マッピング・コマンド定義等、エントリーポイントの定義
autoload 上記エントリーポイントから呼び出される「機能」の本体

こうすることで、実際にその「機能」が必要になるまでロード処理を遅延できるので Vim の起動時間に影響を与えにくくなります。

f:id:lambdalisue:20181224174750p:plain

次に解説が必要なのはこのスライドですね。 ここではコマンド引数リストを受け取り、外部コマンドを実行して結果リストを返す関数を作成しています。

この関数内では map() 関数を利用していますが、この関数には以下の様な癖があるため注意が必要です。

  • 関数適用は破壊的に行われるため、渡したリストそのものが変化する
    • 防ぐためには copy() 関数かスライス構文で Shallow copy を作成する必要がある
  • コールバックの第一引数は添字のため { x -> x } を渡すと添字リストになる
    • 値が欲しい場合は { _, v -> v } のように第一引数を捨てる必要がある
    • 実は v:keyv:val が利用できるので { -> v:val } でもよい

この癖さえ知っていれば、そこまで難しいことはしていません。 ちなみに system() が要求する通りに文字列を受け取り文字列を返す関数にすれば、こんな面倒なことはしなくても済むのですが、あとあと Job 化することも踏まえて、ここではリストを主体に扱っています。

f:id:lambdalisue:20181224180048p:plain

次は割とシンプルなのですが、このスライドです。

ここでは与えられた filetype を利用して動的に autoload 関数を呼び出しています。 先にも述べた通り autoload 関数が呼ばれると対象の Vim script ファイルが自動的にロードされるため、プラグイン作成時に存在しなくても構いません。 そのため、この仕組みを使うことで第三者が amake.vim プラグイン拡張プラグインとして任意の Runner の作成等が可能です。 ちなみに、ここで使っている動的な autoload 関数呼び出しは Unite.vim などの有名なプラグインでも使われている Vim プラグインプラグイン機構の常套手段です。

f:id:lambdalisue:20181224204705p:plain

最後は非常に申し訳ないのですが、誤植です。

初版では options を渡していく形で作成していたのですが、途中から opener を直接渡す形に修正しました。 このスライドでは options を渡していた頃のコードが記載されてしまっています。正しくは以下です。

function! amake#run(opener) abort
  let runner = amake#runner#new(&filetype)
  let result = amake#runner#run(runner, expand('%:p'))
  let bufname = printf('amake://%s', join(result.args, ' '))
  let Open = { c -> amake#buffer#new(bufname, c, a:opener) }
  call result
        \.then({ v -> Open(v.stdout) })
        \.catch({ v -> Open(v.stdout + [''] + v.stderr) })
endfunction

ちなみに GitHubリポジトリの方は多分 options ベースの実装になっていると思います。 この辺りはコードとにらめっこしながら整合性を確かめていけばわかる・・・かな?

おわりに

とりあえず補足が必要そうだなと感じたところをコメントしました。 「ここもわからないんだけど!」みたいなのがあったらお気軽にコメント欄へ(対応するかは別として :p)