はじめに
この記事はctrlpvim/ctrlp.vimのエクステンションの作り方についての記事であり,ctrlpの公式リポジトリのextensionsブランチに書かれているエクステンションの作り方,および以下の3つの記事の内容に個人的な知見を追加したものである.
CtrlPとは
ctrlpvim/ctrlp.vimは候補絞り込み検索型のファイラである.
Shougo/unite.vimと双璧を成すプラグインとして,とても有名である.
オリジナルはkien氏のものであるが,kien氏と連絡が取れないことから,それをforkしたctrlpvim/ctrlp.vimが誕生した.
ctrlp.vimは標準機能であるファイラだけではなく,候補絞り込み検索のインタフェースを利用して,ctagsのタグやundo履歴を候補として表示し,選択された場合にそれに応じたアクションが実行される拡張が本体に付属している.
このctrlp.vimの拡張は,第三者が作成することもできる.
unite.vimの様々な拡張(unite source)が多数の人により開発され公開されているように,ctrlp.vimの拡張(以後,エクステンションと呼ぶ)も多数の人により開発され公開されている.
ctrlp.vimのエクステンションは,unite.vimのsourceほど自由に作ることはできないが,シンプルかつ簡単に作ることが可能な点が魅力であると思っている.
2015 / 12 / 01 追記
kien氏と連絡が取れ,2015年11月30日にkien氏のリポジトリではメンテナンスされていないということがオリジナルの方のREADME.mdに明記された.
エクステンションの作り方
g:ctrlp_ext_vars
というリストを保持するグローバル変数があり,このグローバル変数に作成するエクステンションに関する辞書を追加するだけで,エクステンションの作成は終わりである.
そして,関数 ctrlp#init()
に適切なインデックスを渡すことで,作成したエクステンションを利用することができる.
ただし,作成するエクステンションは,1つのファイルにし, autoload/ctrlp/sample.vim
に置く.
リスト g:ctrlp_extensions
にエクステンションを登録してある場合,ctrlp.vim本体の読み込み時に,ctrlp.vim本体が runtime autoload/ctrlp/sample.vim
として読み込みを行うので,エクステンションのファイルは autoload/ctrlp/
以下に置かなければならない.
何はともあれ,実例を見てもらうとしよう.
以下は,カレントディレクトリのファイルを表示し,選択されたファイルの行数を echo
するというシンプル(かつ何の役にも立たない)なエクステンションのサンプルである.
if exists('g:loaded_sample')
finish
endif
let g:loaded_sample = 1
let s:save_cpo = &cpo
set cpo&vim
command! CtrlPSample call ctrlp#init(ctrlp#sample#id())
let &cpo = s:save_cpo
unlet s:save_cpo
autoload/ctrlp/sample.vim
if exists('g:loaded_ctrlp_sample') && g:loaded_ctrlp_sample
finish
endif
let g:loaded_ctrlp_sample = 1
let s:save_cpo = &cpo
set cpo&vim
let s:sample_var = {
\ 'init': 'ctrlp#sample#init()',
\ 'accept': 'ctrlp#sample#accept',
\ 'lname': 'sample',
\ 'sname': 'sample',
\ 'type': 'path',
\ 'nolim': 1
\}
if exists('g:ctrlp_ext_vars') && !empty(g:ctrlp_ext_vars)
let g:ctrlp_ext_vars = add(g:ctrlp_ext_vars, s:sample_var)
else
let g:ctrlp_ext_vars = [s:sample_var]
endif
let s:id = g:ctrlp_builtins + len(g:ctrlp_ext_vars)
function! ctrlp#sample#id() abort
return s:id
endfunction
function! ctrlp#sample#init() abort
return filter(split(globpath('.', '*', 1), "\n"), 'filereadable(v:val)')
endfunction
function! ctrlp#sample#accept(mode, str) abort
call ctrlp#exit()
echo len(readfile(a:str))
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
先に述べたように,ctrlp.vim本体が runtime autoload/ctrlp/sample.vim
として,作成したエクステンションを読みにいくので,autoloadファイルであっても,ファイルの先頭に二重読み込み防止の記述をしておく必要がある.
このサンプルは,koturn/ctrlp-sampleにあるので,インストールして動作を確認したり,改造したりして,ctrlp.vimのエクステンションの作り方の勉強に役立てていただきたい.
なお,masterブランチのものは,後述する僕の好みに合わせてコードを洗練してあるので,上記のコード例そのままのものが欲しいなら,not-refiedブランチのものを参照していただきたい.
g:ctrlp_ext_vars
に追加する辞書のそれぞれのキー
エクステンションの作成において,要となるのが, g:ctrlp_ext_vars
に追加する辞書である.
ここでは,その辞書が持つべきキーについて述べる.
必須
init
: 文字列(evalすると関数呼び出しになる文字列)
- 評価されるべき関数呼び出しの文字列(
'ctrlp#sample#init()'
など)
- 候補の取得に用い,関数から候補のリストを返却する
- unite.vimでいうところの
gather_candidates
- CtrlPのバッファに移動した後に呼び出される
accept
: 文字列(候補が選択されたときに呼び出される関数名)
- 選択した候補に対するアクションを定義した関数の名前を渡す
init
, enter
, exit
とは異なり, 関数呼び出しの括弧は不要
- ここで指定した関数のAPIは
ctrlp#sample#accept(mode, str)
といった形にすること(仮引数名は何でもよいが,本体付属のエクステンションに従った)
mode
: 候補の選択に入力したキーに応じた文字列が入っている.対応は以下の通り
<CR>
: 'mode' は 'e'
<C-v>
: 'mode' は 'v'
<C-t>
: 'mode' は 't'
<C-x>
: 'mode' は 'h'
str
: 選択した候補
- 通常,関数の先頭で
ctrlp#exit()
をコールするとよい
mode
に応じて処理を変更しているエクステンションは少ない
lname
: 文字列(省略無しのエクステンション名 - long name)
- ステータスラインに表示される省略無しのエクステンション名
sname
: 文字列(省略したエクステンション名 - short name)
- ステータスラインに表示される省略されたエクステンション名
type
: 文字列で 'line'
, 'path'
, 'tabs'
, 'tabe'
のいずれか
'line'
- それぞれの候補に対し,末尾まで入力文字列とのマッチを行う
'path'
- それぞれのファイルパスのような形式の候補に対し,末尾まで入力文字列とのマッチを行う
- それぞれの候補について,入力にマッチした部分がハイライト される
'tabs'
(tab start)
- 最初のTAB文字までマッチする
- 候補の文字列の最初のTAB以降を,ユーザに情報として提示したい場合に用いる(候補に関するヒントの提示)
'tabe'
(tab end)
- 最後のタブ文字までマッチする
- 用途は
'tabs'
と同じで,どのTABまでをマッチ対象にするかが異なる
任意
enter
: 文字列(evalすると関数呼び出しになる文字列)
- CtrlPが起動する前に呼び出される処理
- 元のバッファの
filetype
の取得することなどに用いる
exit
: 文字列(evalすると関数呼び出しになる文字列)
opts
: 文字列(evalすると関数呼び出しになる文字列)
- オプションの設定などに用いるらしい
enter
で指定した関数より前に呼び出される
sort
: 数値( 0
か 1
)
- 候補をソートするか否か
0
なら候補をソートしない(デフォルト), 1
なら候補をソートする
specinput
: 数値( 0
か 1
) - special input
..
や @cd
といった特殊な入力を可能にする
:h ctrlp-input-formats
を参照
0
なら特殊入力を不可に(デフォルト), 1
なら特殊入力を可能にする
..
は ../
と等価で,1つドットを増やす度にディレクトリ階層を1つ上にした入力として解釈され,リターンキーを押すと,ワーキングディレクトリを変更し,再度 init
に指定した関数が呼び出され,候補が更新される
...
なら ../../
,....
なら ../../../
に移動する
@cd
はワーキングディレクトリを変更する
- 候補選択時に
@cd ./plugin/
と入力して,リターンキーを押すと,ワーキングディレクトリを ./plugin/
に変更し,再度 init
に指定した関数が呼び出され,候補が更新される
- CtrlP起動中にワーキングディレクトリを変更したい場合に用いる
- 候補の絞り込みに用いるのではない
- ワーキングディレクトリが候補の取得に関わっているものに有効
ドキュメント化されていないもの(任意)
nolim
: 数値( 0
か 1
) - no limit
0
なら制限あり(デフォルト), 1
なら無制限(スクロール可能)に
- 日本語などの検索しづらい候補があるときに用いると便利かもしれない
- mattnさんのctrlpのエクステンションに多く用いられているのを見掛ける
opmul
: 数値( 0
か 1
) - open multiple
0
なら複数選択不可(デフォルト), 1
なら複数選択可能に
- 掛け算記号のことではない
- 複数選択は候補上で
<C-z>
と入力することで候補にマークを付け, <C-o>
を入力することで行うことができる
- ただし, 複数選択のアクションは "基本的に" 定義できず,ファイルを開くという動作のみ である
- 存在するファイルのパスに限定されるが,
autocmd
の BufReadCmd
を用いることで強引にアクションを定義することは可能(危険)
- 候補はファイルパスとして解釈され,存在しないファイルパスである候補は選択しても除外される
- 基本的に定義しない.本体付属のプラグインに用いられている
wipe
: 文字列(関数名)
<F7>
で候補を除去するときのアクションを指定する
opmul
が 1
のとき,<C-z>
でマークを付け,<F7>
でマークを付けた候補を指定した関数に渡す
- マークが1つも付いていない場合,
<F7>
で全ての候補を除去するかどうかのメッセージが表示される
o
を入力すると,指定した関数に空リストを渡してコールし,その後ctrlpバッファに戻る
c
を入力すると,候補を除去せず,ctrlpバッファに戻る
init
, enter
, exit
とは異なり, 関数呼び出しの括弧は不要
- ここで指定した関数のAPIは
ctrlp#sample#wipe(entries)
といった形にすること(仮引数名は何でもよいが,本体付属のエクステンションに従った)
- 新たな候補リスト (引数の除去対象が除去されたリストが想定されている)を関数から返却する
- 基本的に定義しない.本体付属のプラグインに用いられている
opmul
と wipe
を用い,指定した候補を除去したいのであれば,以下のようにするのがよいだろう.
function! ctrlp#sample#init() abort
let s:candidates = filter(split(globpath('.', '*', 1), "\n"), 'filereadable(v:val)')
return s:candidates
endfunction
function! ctrlp#sample#wipe(entries) abort
return empty(a:entries) ? [] : filter(s:candidates, 'index(a:entries, v:val) == -1')
endfunction
上記の ctrlp#sample#wipe()
が分かりにくいのであれば,以下のようにforを用いて書くとよい.
function! ctrlp#sample#wipe(entries) abort
if empty(a:entries)
return []
endif
for entry in a:entries
let idx = index(s:candidates, entry)
if idx != -1
call remove(s:candidates, idx)
endif
endfor
return s:candidates
endfunction
前者の filter()
を用いたものと比べると, a:entries
と s:candidates
の捉え方(ループの主体)が異なっているが,候補に重複が無ければ同一の結果が得られるはずだ.
(前者は除去候補にマッチしたものを候補から全て取り除き,後者は候補のうち,最初にマッチしたもののみを取り除く)
Vim scriptのパースは時間がかかる(ループ毎にもパースを行っているはず)ため, s:candidates
の数が多くないのであれば,前者の方が高速に動作すると思われる.
CtrlP起動時にワーキングディレクトリを変更する
ctrlp#init()
に作成したエクステンションのIDを渡すことで,CtrlPを起動するようになっていることは最初に述べた.
実は, ctrlp#init()
は,第二引数に辞書を取ることもでき,その辞書のキー dir
の値に,ワーキングディレクトリを指定することで,CtrlP起動時のディレクトリを変更することができる.
これは相対パスからファイルを簡単に辿りたい場合などに使用できる.
command! -nargs=1 -complete=dir CtrlPSample call ctrlp#init(ctrlp#sample#id(), {'dir': <q-args>})
本体付属のエクステンションやこの記事を参考にすると,CtrlPの候補にハイライトを適用する(すなわち,CtrlPバッファ内でハイライトを行う)には, ctrlp#sample#init()
内で,
function! ctrlp#sample#init() abort
call ctrlp#hicheck('CtrlPSampleTabExtra', 'Comment')
syntax match CtrlPSampleTabExtra '\zs\t.*$'
return filter(split(globpath('.', '*', 1), "\n"), 'filereadable(v:val)')
endfunction
などのようにするとよい.
本体付属のエクステンションでは,シンタックスハイライト用の設定を行う部分を関数として切り出し,
function! ctrlp#sample#init() abort
call s:syntax()
return filter(split(globpath('.', '*', 1), "\n"), 'filereadable(v:val)')
endfunction
function! s:syntax() abort
if ctrlp#nosy()
return
endif
call ctrlp#hicheck('CtrlPSampleTabExtra', 'Comment')
syntax match CtrlPSampleTabExtra '\zs\t.*$'
endfunction
としている( ctrlp#nosy()
については後述する).
ctrlp#hicheck()
はハイライトのリンクをチェックする関数であり,リンクが存在しないならば,リンクを行う.
実装は非常に単純で,
fu! ctrlp#hicheck(grp, defgrp)
if !hlexists(a:grp)
exe 'hi link' a:grp a:defgrp
en
endf
となっている.
また, ctrlp#nosy()
(no syntax) はシンタックスハイライトが利用できる環境であるかどうかを判別する関数である.
シンタックス機能が利用できないVimや, :syntax enable
をしていないVimならば 1
を返却し,シンタックスハイライトが可能ならば, 0
を返却する.
fu! ctrlp#nosy()
retu !( has('syntax') && exists('g:syntax_on') )
endf
グローバル変数 g:syntax_on
は, :syntax enable
とコマンドを実行する(大抵,.vimrcに書かれているだろう)ことで定義される変数で, :syntax off
とコマンドを実行すると unlet
される.
大抵のユーザはシンタックス機能をオンにしているので,わざわざチェックする必要も無いが,本体付属のプラグインに沿った形に記述しておくと,雰囲気は良いだろう.
シンタックスハイライトの用途としては, type
に tabs
を指定した場合,最初のタブ文字以降を目立たない色にするといったものが考えられる.
実は,先述のハイライトの例は,本体付属の type
が tabs
であるエクステンションにも書かれている「タブ文字以降のハイライトをコメントと同じものにして,目立たなくする」ものである.
改良案
ここまではctrlp.vimのエクステンションの作り方について述べたが,ここからは「エクステンションのコードはこう書くのが好きだ」という個人の意見を書く.
ctrlp.vimの拡張はプラグインとしてオプショナルであり,NeoBundle等で依存関係を明示したくない場合があるかもしれない(そんなユーザがいるのかは疑問であるが).
このことについて考えているときに,以下の記事を見て,なるほど,と感じた.
ctrlp.vimのExtensionを書くときに:NeoBundleLazy autoloadを考慮してg:ctrlp_builtinsをそのまま使わない - cafegale
ctrlp.vimの拡張はあくまでオプショナルとしてプラグインに付属させており,NeoBundleに依存関係が記述されておらず,ctrlp.vimが遅延読み込みされるように設定してある場合,先述の記事にあるように,作成したプラグインを読み込みしている段階で g:ctrlp_builtins
が定義されていないという問題が起こることもあるだろう.
従って,ctrlpが提供しているautoload関数 ctrlp#getvar()
を用いて,ctrlp.vim本体の読み込みを発生させ,ctrlpのグローバル変数が読み込まれるようにした方が望ましいはずだ.
そのことを踏まえ,作成するエクステンションのファイルの先頭に,
let s:ctrlp_builtins = ctrlp#getvar('g:ctrlp_builtins')
と記述し, g:ctrlp_builtins
の代わりに, s:ctrlp_builtins
を用いるようにするとよさそうだ.
ただ,プラグインマネージャである neobundle.vim のためだけの記述をするというのは,本質的ではなく,少し嫌な気持ちになる.
この g:ctrlp_builtins
の取得方法に関しては,個人の好みが大きいだろう.
正直,ユーザがNeoBundleの依存関係をキッチリ書けば問題の無い話でもある.
ちなみに, ctrlp#getvar()
は以下のような実装になっている.
fu! ctrlp#getvar(var)
retu {a:var}
endf
a:var
は文字列と想定されるので,{a:var}
は, eval(a:var)
と読み替えてよく, a:val
を評価した結果に置き換えられる.
すなわち, ctrlp#getvar()
は, autoload/ctrlp.vim
内で引数を評価した結果を返す関数であり,autoload/ctrlp.vim
のスクリプトローカル変数を取得することもできる.
g:ctrlp_ext_vars
へのエクステンションの辞書の追加
Vimプラグインの拡張機能プラグインを作ってVimをさらに使いやすくしよう - 29th Sta. という記事の例では,
let s:sample_var = {
\ 'init': 'ctrlp#sample#init()',
\ 'accept': 'ctrlp#sample#accept',
\ 'lname': 'sample extension',
\ 'sname': 'sample',
\ 'type': 'path',
\ 'nolim': 1
\}
if exists('g:ctrlp_ext_vars') && !empty(g:ctrlp_ext_vars)
let g:ctrlp_ext_vars = add(g:ctrlp_ext_vars, s:sample_var)
else
let g:ctrlp_ext_vars = [s:sample_var]
endif
としているが, add()
は第一引数のリストに対して破壊的変更を加えるので,返却値を再び第一引数のリストに代入する必要はない(どちらが読みやすいかは置いておくとして).
また,スクリプトローカル変数 s:sample_var
を保持する必要もない.
これらのことから,上記コードは以下のように記述することができる.
let s:sample_var = {
\ 'init': 'ctrlp#sample#init()',
\ 'accept': 'ctrlp#sample#accept',
\ 'lname': 'sample extension',
\ 'sname': 'sample',
\ 'type': 'path',
\ 'nolim': 1
\}
if exists('g:ctrlp_ext_vars') && !empty(g:ctrlp_ext_vars)
call add(g:ctrlp_ext_vars, s:sample_var)
else
let g:ctrlp_ext_vars = [s:sample_var]
endif
unlet s:sample_var
個人的にはif文を用いずにシンプルに書きたいので,以下のように記述することにしている.
let g:ctrlp_ext_vars = add(get(g:, 'ctrlp_ext_vars', []), {
\ 'init': 'ctrlp#sample#init()',
\ 'accept': 'ctrlp#sample#accept',
\ 'lname': 'sample extension',
\ 'sname': 'sample',
\ 'type': 'path',
\ 'nolim': 1
\})
add()
の第一引数が即値 []
となることもあるので,この場合は add()
の返却値を利用しなければならない.
可能な限り,グローバル関数を使用しないようにする
エクステンション側にグローバル関数を定義し,ctrlp.vimに渡す辞書にそのグローバル関数名を渡しているが,必ずしもそうする必要はない.
スクリプトローカル関数であっても,スクリプト番号付きであれば,グローバルに参照することが可能だからだ.
function! s:hoge() abort
echomsg 'Hello, World'
endfunction
このことを踏まえ, g:ctrlp_ext_vars
に辞書を追加する記述を,以下のような記述に変更した.
function! s:get_sid_prefix() abort
return matchstr(expand('<sfile>'), '\zs<SNR>\d\+_\zeget_sid_prefix$')
endfunction
let s:sid_prefix = s:get_sid_prefix()
delfunction s:get_sid_prefix
let g:ctrlp_ext_vars = add(get(g:, 'ctrlp_ext_vars', []), {
\ 'init': s:sid_prefix . 'init()',
\ 'accept': s:sid_prefix . 'accept',
\ 'lname': 'sample extension',
\ 'sname': 'sample',
\ 'type': 'path',
\ 'nolim': 1
\})
let s:id = s:ctrlp_builtins + len(g:ctrlp_ext_vars)
unlet s:sid_prefix
僕は,不特定多数の相手(プラグインであったり,.vimrcであったり)にAPIを提供する場面では,グローバル関数を用い,今回のように作成したプラグインから 能動的に 他のプラグインに自身の関数を渡す場合では,スクリプトローカル関数を用いるとよいだろうと考えている.
ただ,ここまでしてグローバル関数を嫌う必要があるのかと問われれば,どうなんだろうと思ってしまう.
また,スクリプト番号の取得は,コードとしてややごちゃごちゃしており(5行は必要になる),グローバル関数を用いる方がスマートという気もするが,この場では気にしないことにする.
ここまでのことを踏まえ,最初に紹介したカレントディレクトリのファイルを表示し,選択されたファイルの行数を表示するエクステンションを以下のように書き直した.
if exists('g:loaded_ctrlp_sample') && g:loaded_ctrlp_sample
finish
endif
let g:loaded_ctrlp_sample = 1
let s:save_cpo = &cpo
set cpo&vim
let s:ctrlp_builtins = ctrlp#getvar('g:ctrlp_builtins')
function! s:get_sid_prefix() abort
return matchstr(expand('<sfile>'), '\zs<SNR>\d\+_\zeget_sid_prefix$')
endfunction
let s:sid_prefix = s:get_sid_prefix()
delfunction s:get_sid_prefix
let g:ctrlp_ext_var = add(get(g:, 'ctrlp_ext_vars', []), {
\ 'init': s:sid_prefix . 'init()',
\ 'accept': s:sid_prefix . 'accept',
\ 'lname': 'sample',
\ 'sname': 'sample',
\ 'type': 'path',
\ 'nolim': 1
\})
let s:id = s:ctrlp_builtins + len(g:ctrlp_ext_vars)
unlet s:ctrlp_builtins s:sid_prefix
function! ctrlp#sample#id() abort
return s:id
endfunction
function! s:init() abort
return filter(split(globpath('.', '*', 1), "\n"), 'filereadable(v:val)')
endfunction
function! s:accept(mode, str) abort
call ctrlp#exit()
echomsg len(readfile(a:str))
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
ctrlp.vimのエクステンションのテンプレート
僕はctrlp.vimのエクステンションを作成する際に,以下のテンプレートを用いている.
<+FILEBASE+>
を適宜,エクステンションのファイル名からディレクトリ部と,拡張子を取り除いたものにすればよい( :%s/<+FILEBASE+>/\=fnamemodify(expand('%'), ':t:r')
).
これを thinca/vim-template などのテンプレートプラグインと組み合わせると,サクッとエクステンションを作ることができるはずだ.
なお,以下のテンプレートには,CtrlPバッファでハイライトするコードは含まれない.
僕の場合,ハイライトが必要なエクステンションを書くことがあまり無いので,必要に応じて追加するようにしているためだ.
if exists('g:loaded_ctrlp_<+FILEBASE+>') && g:loaded_ctrlp_<+FILEBASE+>
finish
endif
let g:loaded_ctrlp_<+FILEBASE+> = 1
let s:save_cpo = &cpo
set cpo&vim
let s:ctrlp_builtins = ctrlp#getvar('g:ctrlp_builtins')
function! s:get_sid_prefix() abort
return matchstr(expand('<sfile>'), '\zs<SNR>\d\+_\zeget_sid_prefix$')
endfunction
let s:sid_prefix = s:get_sid_prefix()
delfunction s:get_sid_prefix
let g:ctrlp_ext_var = add(get(g:, 'ctrlp_ext_vars', []), {
\ 'init': s:sid_prefix . 'init()',
\ 'accept': s:sid_prefix . 'accept',
\ 'lname': '<+FILEBASE+>',
\ 'sname': '<+FILEBASE+>',
\ 'type': 'line',
\ 'enter': s:sid_prefix . 'enter()',
\ 'exit': s:sid_prefix . 'exit()',
\ 'opts': s:sid_prefix . 'opts()',
\ 'sort': 0,
\ 'specinput': 0,
\ 'nolim': 1
\})
let s:id = s:ctrlp_builtins + len(g:ctrlp_ext_vars)
unlet s:ctrlp_builtins s:sid_prefix
function! ctrlp#<+FILEBASE+>#id() abort
return s:id
endfunction
function! s:init() abort
let candidates = []
return candidates
endfunction
function! s:accept(mode, str) abort
call ctrlp#exit()
endfunction
function! s:enter() abort
endfunction
function! s:exit() abort
endfunction
function! s:opts() abort
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
最小限の機能に絞ったテンプレートならば,以下のようになるだろう.
if exists('g:loaded_ctrlp_<+FILEBASE+>') && g:loaded_ctrlp_<+FILEBASE+>
finish
endif
let g:loaded_ctrlp_<+FILEBASE+> = 1
let s:save_cpo = &cpo
set cpo&vim
let s:ctrlp_builtins = ctrlp#getvar('g:ctrlp_builtins')
function! s:get_sid_prefix() abort
return matchstr(expand('<sfile>'), '\zs<SNR>\d\+_\zeget_sid_prefix$')
endfunction
let s:sid_prefix = s:get_sid_prefix()
delfunction s:get_sid_prefix
let g:ctrlp_ext_vars = add(get(g:, 'ctrlp_ext_vars', []), {
\ 'init': s:sid_prefix . 'init()',
\ 'accept': s:sid_prefix . 'accept',
\ 'lname': '<+FILEBASE+>',
\ 'sname': '<+FILEBASE+>',
\ 'type': 'line',
\ 'nolim': 1
\})
let s:id = s:ctrlp_builtins + len(g:ctrlp_ext_vars)
unlet s:ctrlp_builtins s:sid_prefix
function! ctrlp#<+FILEBASE+>#id() abort
return s:id
endfunction
function! s:init() abort
let candidates = []
return candidates
endfunction
function! s:accept(mode, str) abort
call ctrlp#exit()
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
基本的にこれらのテンプレートの s:init()
と s:accept()
を中心に実装するだけでよいはずだ.
最後に
ctrlp.vimのエクステンションを作るにあたって覚えなければならないことは,unite.vimより少なく,unite sourceより簡単に作ることができる.
日本人が作ったプラグインには,unite sourceが付属しているものが多いが,ctrlp.vimのエクステンションが付属しているプラグインは少ない.
unite sourceを付属させるのであれば,是非ともctrlp.vimのエクステンションも付属させてみてはどうだろうか?
追記
2015 / 12 / 01
何とあのmattnさんにこの記事を取り上げていただいた!
当初は割とパパッと書いた記事で,恥ずかしい間違いなどもあったのだが,まさかmattnさんに紹介していただけると思っていなかった.
mattnさんはブログ中で, g:ctrlp_ext_vars
に追加するときに if ~ else を用いるか,get() を用いるかについて堀下げて解説してくださっている.
特に,get()
の第一引数に g:
という一見変数っぽくないものを与えるあたりについて解説してくださっている.
let s:sample_var = {
\ 'init': s:sid_prefix . 'init()',
\ 'accept': s:sid_prefix . 'accept',
\ 'lname': 'sample extension',
\ 'sname': 'sample',
\ 'type': 'path',
\ 'nolim': 1
\}
if exists('g:ctrlp_ext_vars') && !empty(g:ctrlp_ext_vars)
call add(g:ctrlp_ext_vars, s:sample_var)
else
let g:ctrlp_ext_vars = [s:sample_var]
endif
unlet s:sample_var
とするより,
let g:ctrlp_ext_vars = get(g:, 'ctrlp_ext_vars', []) + [{
\ 'init': s:sid_prefix . 'init()',
\ 'accept': s:sid_prefix . 'accept',
\ 'lname': 'sample extension',
\ 'sname': 'sample',
\ 'type': 'path',
\ 'nolim': 1
\}]
とした方が上級Vimmerに見られるようなので,みなさんも要チェックだ.
ちなみに,Vim scriptはパースがネックである部分もあるので,前者より後者の方が高速に動作するはずだ.
カッコよさの他,マシンに優しくしたり,電力消費を僅かに抑えたいのであれば,後者を用いるとよいだろう.
(1度しか実行されない処理に何を言っているんだ,というツッコミは無しでお願いしたい)
参考