ゴリラこんにちは、ゴリラです。ゴリラと学ぶVim講座の連載の最終回となりました。これまでみなさんにVimの基本的な操作から筆者のおすすめプラグインまで、色々と解説してきました。最終回はみなさんのお待ちかねのプラグインの作成について解説していきます。scriptを使って作られていることを解説しました。script構文と作成方法について解説していきます。長いのですが、ぜひ読みながらご自身でコードを書いてプラグインを完成させていきましょう。scriptはExコマンド(:から始まるコマンド)の集まりです。{file}で読み込むことで実行できます。ファイルの拡張子は.vimとつけるのが一般的です。sample.vimを実行するとコマンドラインにgorillaが出力されます。scriptでは次のように「“」から行末までがコメントとして解釈されます。複数行をコメントにする場合はそれぞれの行に「“」が必要になります。scriptで主に扱うことができるデータ型は次になります。scriptで「‘」と「“」で囲ったテキストは文字列として解釈されます。「‘」はテキストをそのまま文字列として扱いますが、「“」はタブを表す「\t」といった特殊な文字列を扱うことができます。‘hello\tgorilla’はそのままhello\tgorillaが出力されます。「.」で文字列同士を結合できます。gorillaになります。変数の宣言と代入は次のようにletを使用します。name宣言ではなく変数に値を代入するときもletを使用する必要があります。変数名はアルファベット、数字、アンダースコアを使用できます。数字で開始することができません。OKとNGの例はそれぞれ次になります。gori+llaスコープ変数や後述する関数にはスコープがあり、接頭子によってスコープが変わります。主なスコープは次になります。プラグインの節で掲示するソースでは関数内で接頭子をつけていないのですが、関数内でl:を省略した場合は暗黙的に関数ローカル変数にアクセスします。internal-variablesを参照ください。scriptにおける辞書はいわゆる連想配列で、1要素はkeyとvalueからになります。keyは文字列でなければいけません。次は辞書の例になります。{value}があります。{key})関数を使います。scriptでは次にように定義できます。型がないため、異なるデータ型を保持できます。{default})を使用します。{target}に存在しないインデックスを{idx}に指定した場合は、{default}で指定した値を返します。{default})は辞書にも使えます。その場合は{target}は辞書、{idx}はキーを指定します。キーが存在しない場合は{default}が返されます。{sep})を使用することで{list}の要素を{sep}を使って結合し1つの文字列として返します。')if文if文の基本形は次になります。{expr}は式で結果が1の場合はtrue、0の場合はfalseになります。scriptでの論理積は「&&」、論理和は「||」で表します。scriptにおける主な比較演算子は次になります。isnot#比較演算子に「#」を付けずに使用した場合は、ignorecaseの設定次第で動きが変わります。ignorecaseは、大文字小文字の区別を無視するオプションです。デフォルトでは無効になっているので、大文字小文字を区別して比較します。ユーザの設定によって比較処理の動きが変わらないように、基本的に「#」で大文字小文字を区別するようにしておくと良いです。バッファについてバッファはメモリ上にロードされたファイルのことです。バッファには名前と番号があり、名前はファイル名で、番号は作成された順で割り当てられます。バッファは:bwipeoutで明示的に削除するかVimを終了しない限り、メモリに残ります。バッファの存在チェックbufexists({expr})で{expr}のバッファがあるかを確認できます。ある場合はtrue、なければfalseが返ります。{end})を使用します。{end}を指定しない場合は{lnum}で指定した行だけを取得します。{text})を使用します。{text}はリストの場合は、{lnum}行目とそれ以降の行に要素が挿入されます。'name'])ウィンドウについてウィンドウはバッファを表示するための領域です。ウィンドウにはIDが割り当てられます。複数のウィンドウで複数のバッファを表示できます。:qといったコマンドではウィンドウを閉じるだけなのでバッファは残ります。ウィンドウIDを取得win_getid()で現在のウィンドウIDを取得できます。win_getid()を参照してください。ウィンドウに移動win_gotoid({expr})で{expr}のIDのウィンドウに移動します。バッファが表示されているウィンドウのIDを取得そのバッファが表示されているウィンドウのIDを取得にはbufwinid({expr})を使います。ウィンドウIDがある場合はIDを返し、なければ-1が返ります。バッファが表示されているウィンドウに移動するときはbufwinid()とwin_gotoid()の組み合わせを使います。scriptでは関数を使用して処理をまとめることができます。関数の定義は次のようになります。a:msgendfunction関数はfunctionとendfunctionで囲います。処理はその間に記述します。!とabort!は同名の関数がある場合は上書きします。abortは関数内でエラーが発生した場合、そこで処理を終了します。scriptはデフォルトでエラーがあっても処理が継続されるため基本的にabortをつけると良いです。引数を使用するときはa:スコープ接頭子を付ける必要があります。{expr}で{expr}の評価結果を返すことができます。..で{expr}の評価結果の文字列をExコマンドとして実行できます。複数の引数がある場合、それらはスペースで結合されます。{input})です。{epxr}は実行したいコマンドの文字列、{input}は省略可能で指定した場合は文字列をそのままコマンドの標準入力として渡します。いう形でLambdaを書くことができます。sort関数を用いた数値のソートです。sort()は第2引数にLambdaを受け取ることができて、Lambdaで書いた自前のソート処理を使用できます。F)プラグイン基本的な構文を解説したので、いよいよプラグインを作っていきましょう。プラグインを作るときに、まずプラグインの名前を決める必要がありますが、ここで1点注意です。つけようとしている名前のプラグインがすでにあるかどうかを調べておきましょう。名前が被ってしまうとプラグインがバッティングしてしまい、動かなくなる可能性があります。プラグインを検索するときはVimAwesomeをまず使いましょう。そこで見つからなければGoogleで検索しましょう。今回作成するプラグインはsession.vimという名前にしますが、もしすでに同じ名前のプラグインをインストールされているなら、thinca/vimというdockerのイメージを使って、プラグインが入っていない環境で作業を進めてください。セッション管理プラグイン今回作成するプラグインはVimのセッション機能を少し便利にするプラグインで、仕様は以下になります。{name}で{name}の名前でセッションファイルを保存できる:SessionListでセッション一覧をバッファに表示し、Enterを押下するとカーソル上にあるセッションをロードできるディレクトリ構成プラグインの基本的なディレクトリ構成は次のようになります。*.vimはスクリプトファイルと呼びます。session.vimそれぞれのディレクトリについて解説していきます。pluginディレクトリについてplugin配下はプラグインが提供するExコマンドやオプションを記述したスクリプトファイルを置きます。メインの処理はここではなく後述のautoloadに記述します。スクリプトファイル名はプラグイン名と同じにします。autoloadディレクトリについてautoload配下はメインの処理を記述したスクリプトファイルを置きます。配下のスクリプトファイルはVim起動時ではなく、コマンド実行時に一度だけ読み込まれます。スクリプトファイル名はプラグイン名と同じにします。plugin配下から呼ぶことができる関数をautoload配下に定義する時、ファイル名#関数名()という命名規則に従う必要があります。これはpluginで定義したコマンドを実行する時にautoload配下のどのファイルのどの関数を呼べば良いのかを知る必要があるからです。そのため、プラグイン名が被るとautoload配下のスクリプトファイル名も被り、最悪違うプラグインの関数で上書きされる可能性があります。これがプラグイン名が被らないようにする必要がある理由です。docディレクトリについてdoc配下はヘルプファイルを置きます。SessionListというようにコマンドのヘルプを引けるようにするためです。基本的にヘルプに書かれているものは公式、書かれていないものは非公式の機能になります。プラグインを公開する時はREADME.mdだけでなくヘルプを書きましょう。開発の大まかな流れ少し難しい説明が続きましたが、ここから先はプラグインを実際に作っていきます。作りながらゆっくり理解していきましょう。大まかには以下の流れになります。{packpath}については前回の記事を参照ください。ヘルプを記述では、作っていきましょう。autoload/session.vimの実装セッション保存処理まずg:session_pathにセッションファイルを作成する関数を実装します。g:session_pathを予めてvimrcに記述してvimrcを再ロードしておいてください。現在のパスからパスセパレータを取得しています。ここはそれほど重要ではないので、おまじないと考えておいてください。fnamemodify()`を参照してください。SessionCreateの引数をfileで受け取れるようにします。join()でセッションファイル保存先へのフルパスを生成し、mksession!でセッションファイルを作成します。redrawで画面を再描画してメッセージを出力します。session#create_session(‘test’)を実行してください。g:session_path配下にtestという名前のファイルが作られていたらOKです。セッションロード処理セッション一覧でEnterを押下したときにセッションをロードする関数を用意します。`:source`で渡されるセッションファイルをロードします。session#load_session(‘test’)を実行してみてください。ウィンドウなどの状態が戻ったらOKです。セッション一覧取得処理セッション一覧を取得する処理は大きく分けて2ステップになります。セッションファイルのリストを取得リストを表示するバッファを作成し(すでにあれば表示)、バッファ破棄とセッションをロードするキーマップを設定するでは、それぞれのステップを実装していきましょう。{expr})を使用します。具体的な説明はコメントを参照してください。コマンドラインの文字列をハイライトできます。Filter)endfunctionリストを表示するバッファを作成(すでにあれば表示)s:files()で取得できたファイル一覧を一時バッファに書き出し、ユーザが選択できるようにします。2つのキーマッピングを定義します。session#sessions()を実行してみてください。セッションファイル一覧がSESSIONSというバッファに表示され、Enterを押下するとカーソル下のセッションがロードされたらOKです。plugin/session.vimの実装plugin/session.vimではまずプラグインを無効化する手段を用意します。g:loaded_{plugin_name}というグローバル変数を用意して、それが存在しているならfinishでスクリプトの読み取りを停止します。1をvimrcに記述するだけでプラグインを無効化できます。session#create_session(<q-args>)ヘルプの記述autoload/session.vimとplugin/sesison.vimの実装が終わったのでこれでプラグインは動くようになりました。最後にプラグインを公開する前にヘルプを書きましょう。ヘルプを書くにあたり、テンプレート生成プラグインのLeafCage/vimhelpgeneratorを使います。筆者は普段このプラグインを使ってヘルプのテンプレートを生成してから、細かい説明を書いています。使い方はシンプルで、:VimHelpGeneratorを実行してeを押すだけです。するとdoc/session.vimが生成されます。これがヘルプのテンプレートで、そこに今回のプラグインで作成したコマンド、変数、キーマップの説明を追記していきます。追記する箇所は次になります。*session-key-mappings*ここにユーザが使用できるキーマップの説明を記述ヘルプに公式標準のレイアウトはないので、基本的にユーザがわかりやすいように心がけてヘルプを記述すれば良いですが、「*」で囲っている部分は実際ヘルプを引く際に検索される部分なのでそこは必ず記述しましょう。筆者の場合は次のように記述しています。:SessionCreateなら*:SessionCreate*のところにジャンプします。*g:session_path*セッションを保存するファイルパスを設定します。セッション一覧を開きます。Enterでカーソル上にあるセッションをロードします。セッションを{name}で保存します。カーソル下のセッションをロードします。セッションリストのバッファを閉じます。ヘルプを記述したら、これでプラグインは出来上がりです。あとはGitHubに公開するなり宣伝するなりしていきましょう。docを実行しておきしょう。tagsファイルがないとヘルプを引けないので、package機能を使ってプラグイン管理する方は忘れずにやりましょう。scriptを使ったプラグイン作成の方法になります。皆さんへの宿題というわけではないのですが、作成したプラグインにぜひ機能(例えばセッションファイル削除)を追加してみてください。usr_41を読んでみてください。不明点などありましたらTwitter(@gorilla0513)のDMもしくはリプをいただければ回答しますので遠慮なく聞いてください。最後に本記事で連載は終了になります。8ヶ月間という短い連載でしたが、筆者にとってとても良い経験になりました。みなさんにとってどんな連載でしたか?少しでも「Vimって良さそうだな」と感じていただけたら嬉しいなと思っています。最後に、連載の間レビューや校正などをしてくださった法林さん、ありがとうございました。おかげさまでとても良い経験をさせていただきました。