lightline.vimをカスタマイズする
Vim Advent Calendar 2012 の325日目の記事です。
少し長めになりますので、お時間があるときにお読みください。
Vimのステータスラインを改造するプラグインが、Lokaltog/powerline、bling/vim-airlineに続いてitchyny/lightline.vimが登場しました。
私は今までステータスラインやタブラインは自前で改造していましたが、モダンなラインも経験してみたかったので、導入することにしました。
(参考:VACのライン系の記事)
(当記事の内容)
- 設定方法
- コンポーネント作成方法
- カラースキーム作成方法
ctrlp.vimの拡張を作るときにVim7.3.1170以前ではスクリプトローカル関数をFuncrefで渡すことが出来ない
こういうのは無理。
スクリプトローカル関数のFuncrefはそのスクリプト内でのみしか有効でないから。
let s:ctrlp_ext_var let s:ctrlp_ext_var.accept = function('s:accept')
グローバル関数や、オートロード関数を使おう。
もしかしたらこういうことをしたら可能かもしれない。
function! s:SID() return matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$') endfun let s:FSID = '<SNR>'. s:SID(). '_' let s:ctrlp_ext_var let s:ctrlp_ext_var.accept = function(s:FSID. 'accept')
将来使うかも知れないVimの正規表現
- コマンドの引数を空白で分割(ただし「\」でエスケープされている空白を考慮)
for arg in split(a:args, '\%(\\\@<!\s\)\+') let arg = substitute(arg, '\\\( \)', '\1', 'g') endfor
ctrlp.vimのExtensionを書くときに:NeoBundleLazy autoloadを考慮してg:ctrlp_builtinsをそのまま使わない
ctrlp拡張を作ったら、最後にctrlpからid番号を取得してctrlpに登録することになるが、
このとき、ctrlpがNeoBundleLazy状態であるのなら、
command! CtrlPExtension call ctrlp#init(ctrlp#{extension-name}#id())
let s:id = g:ctrlp_builtins + len(g:ctrlp_ext_vars) function! ctrlp#{extension-name}#id() return s:id endfunction
は、g:ctrlp_builtins
が定義されていないためにエラーが出る。
ctrlp.vimはctrlp#init()
が呼び出されたときに初めてneobundle.vimによって読み込まれるために、それ以前にはg:ctrlp_builtins
が定義されていないからである。
エラーが出ないようにするためには、たとえば以下のように書き改める。
let s:id = ctrlp#getvar('g:ctrlp_builtins') + len(g:ctrlp_ext_vars) function! ctrlp#{extension-name}#id() return s:id endfunction
ctrlpの変数の値を返すctrlp#getvar()が呼ばれたときに、neobundle.vimがctrlp.vimを読み込むようになるために、エラーが出なくなる。
ctrlpのExtensionの作り方は以下の記事に詳しい。
ctrlp.vim起動時にステータスラインをlightline.vimのものに上書きされるのを防ぐ方法
以下のautocmdをvimrcに定義しておくと、lightline.vimがctrlp.vimのステータスラインを上書きするのを防ぐことが出来ます。
autocmd CursorMoved ControlP let w:lightline = 0
[注意]
このautocmdはplugin/lightline.vimが読まれるより先に定義されていなければいけません。
[解説]
ctrlpは専用バッファを:noautocmd
で開くため、lightline.vimの
autocmd WinEnter,BufWinEnter,FileType,ColorScheme * call lightline#update()
が発動せず、ctrlpのステータスラインが描写された後に、
autocmd CursorMoved,BufUnload * call lightline#update_once()
が発動して上書きしてしまうのです。CursorMovedのときにw:lightline
を定義することで上書きを防いでいます。
GVimのハイライトで使える色名を色見本付きで一覧するunite-source-gvimrgb
この記事は Vim Advent Calendar 2012 299日目の記事です。
GVimで:highlight
コマンドのguifg=
guibg=
引数に渡すことが出来る色名を一覧するunite-sourceを作りました。
$VIMRUNTIMEディレクトリの、rgb.txtの中にある色名リストを元にしています。:h rgb.txt
実行にはunite.vim
が必要です。
実行すると以下のように、色がuniteで一覧されます。
:Unite gvimrgb
GVimにはかなりの色名が用意されているのが分かります。(AntiqueWhite2とかPeachPuff1とかいうのが色名になります)
ハイライトの色を何にするかで困ったときに、ここからよく選んでいます。
例えば、Gray{数字}はGray0が黒でこの数を上げていけば明度が増してきてGray100で完全に白になります。灰色の微調整をするときに、Gray40,Gray30,...てな具合に数字を変更させて見た目を確認します。
後は、#rrggbb
で設定するよりも、後から見たときに色名だから分かり易くなる‥?かな?
itchyny/lightline.vimを導入したので、ステータスラインの色を調整するヒントとして活用しています。
似たunite-sourceに、Webカラー名を一覧表示するものがありますが、そのソースコードをパクっています。
Yuki(ぱせらん)さんありがとうございます。
半自動でNeoBundleLazy autoloadの設定をするプラグイン作りました
この記事は Vim Advent Calendar 2012 286日目の記事です。
:NeoBundleLazy に autoload機能が搭載されたのは、このAdventCalendarが始まって間もなくのことでした。
これはVimの鈍重な立ち上がりに喘いでいたプラグイン重層派が、Vim本来の軽量な立ち上がりに立ち返り、スパルタンに対する反攻の狼煙を上げるかのように見えました。
しかしLazy autoloadを手に沸き上がる重装派に、恐るべき困難が待ち構えていたのです。
[設定がすごく面倒くさい]
NeoBundleLazy設定の書式
NeoBundleLazy <リポジトリ名>, {'autoload': \ {'commands': ['Cmd1', 'Cmd2', {'name': 'Cmd3', 'complete': <利用する補完>}, ...]} \ {'mappings': ['<Plug>mapping1', ['i', '<Plug>mapping2'], ...]} \ }
neobundleのヘルプに載っていた例
NeoBundleLazy 'Shougo/vimshell',{ \ 'depends' : 'Shougo/vimproc', \ 'autoload' : { \ 'commands' : [{ 'name' : 'VimShell', \ 'complete' : 'customlist,vimshell#complete'}, \ 'VimShellExecute', 'VimShellInteractive', \ 'VimShellTerminal', 'VimShellPop'], \ 'mappings' : ['<Plug>(vimshell_'] \ }})
こんなのをいくつも書かないといけないなんて死んでしまいます。
そもそもVimの達人でしたならまだこういったJSONを苦痛なく記述するすごい技術をお持ちかも知れませんが、
私のようなしょっぱい編集能力では{ }括弧と[ ]括弧の入力の連続で指が疲れてしまいます。
そしてまた、こういう設定をするためには、当該プラグインのドキュメントを参照したり、ソースを直接読みに行って、
どういったインターフェイスが用意されているのかを確かめなければいけません。
やはり重装派は、プラグインの設定のためだけに無駄な時間と労力を費やして、スパルタンにせせら笑われる運命なのでしょうか?
そうして設定が面倒になって、lazyでない:NeoBundleを使い始めて、元の重たいVimへと戻っていくのでしょうか?
[重装派は解決もプラグインでやる]
そうです。重装派はいつだって最新鋭の装備でもって技術の未熟さを補うものです。*1
そんなわけで自動でNeoBundleLazy autoloadの設定をするプラグインを作りました。
開発のきっかけ
設定例
nnoremap <silent>,bl :<C-u>NebulaPutLazy<CR> nnoremap <silent>,bc :<C-u>NebulaPutConfig<CR> nnoremap <silent>,by :<C-u>NebulaYankOptions<CR> nnoremap <silent>,bp :<C-u>NebulaPutFromClipboard<CR>
使い方
カーソルをvimrcなどに書いた、NeoBundleの以下のような設定行へ持っていきます。
NeoBundle 'kana/vim-smartword'
:NebulaPutLazy
を呼びます。するとこの次の行に以下の行が出力されます。
NeoBundleLazy 'kana/vim-smartword', {'autoload': {'mappings': [['sxno', '<Plug>(smartword-']]}}
後はこの出力された行をお好みで手直しした後、元の行を消して完成。
lazyした場合マッピングなどの設定は自分でやるのを忘れないようにしましょう。
map w <Plug>(smartword-w) map b <Plug>(smartword-b) map e <Plug>(smartword-e) map ge <Plug>(smartword-ge)
他のコマンドですが、:NebulaPutCongig
は、NeoBundleLazyではなくneobundle#config()を出力します。
:NebulaYankOptions
はオプションをレジスタに入れます。
:NebulaPutFromClipboard
は、OSのクリップボードに入っている文字列を
NeoBundle 'クリップボード文字列'
の形にして出力します。新しいプラグインのインストールに便利です。
(注意)
複雑な構造を持つプラグインや、動的にコマンドやマッピングを生成するようなプラグインには対応していません。
不具合が出ると分かっているもの
- 'bkad/CamelCaseMotion' のマッピングは取得できない。
- 'Shougo/unite.vim' のsourceが一部取得できない。unite-source取得が不完全という例は他にもあると思います。
- 'scrooloose/nerdcommenter' のマッピングのモードに'x'が含まれていない。正しくは、以下のようにする。
{'mappings': [['inx', '<Plug>NERDCommenter']]}
- 'tpope/vim-fugitive'のコマンドが取得できない。
'kana/vim-textobj-user' を使って作られたtextobjectは g:textobj_{textobjname}_no_default_key_mappings が設定されているかのように振る舞います。つまり設定されるはずのキーマッピングが設定されません。
これに限らずlazy化されたプラグインはデフォルトマッピングが設定されません。vimrcなどで各自キー割り当てを設定してください。
'kana/vim-textobj-user' を使って作られたプラグインなど、依存関係があるものには、'depends'属性を設定して、先に親に当たるプラグインを読み込ませないと、lazy atoload で読み込めません。
NeoBundleLazy 'kana/vim-textobj-user' NeoBundleLazy 'osyo-manga/vim-textobj-multiblock', \ {'depends': 'kana/vim-textobj-user', \ 'autoload': {'mappings': ['<Plug>(textobj-multiblock']}}
['autoload'に autoload-functions を設定する必要はない]
foo.vimというプラグインの foo#bar()
とかfoo#baz#qux()
のような関数は、呼ばれたとき勝手に foo.vim を source してくれるので、'functions'に設定する必要はありません。また、制作者の努力により、'mappings'の設定は先頭マッチで見てくれるようになっています。
プラグインのマッピングは大抵はプレフィックスがつくのでプレフィクスだけを指定することで、多くの場合、'mappings'に指定する要素は1つで済みます。
例えば、NeoBundle 'deton/jasegment.vim' の設定を
NeoBundleLazy 'deton/jasegment.vim', {'autoload': {'mappings': [ \ ['sx', '<Plug>JaSegmentTextObjVA'], '<Plug>JaSegmentTextObjA', '<Plug>JaSegmentMoveOB', '<Plug>JaSegmentMoveOE', \ '<Plug>JaSegmentTextObjI', ['sx', '<Plug>JaSegmentMoveVE'], '<Plug>JaSegmentMoveOW', ['sx', '<Plug>JaSegmentMoveVW'], \ '<Plug>JaSegmentMoveNB', '<Plug>JaSegmentMoveNE', ['sx', '<Plug>JaSegmentMoveVB'], ['sx', '<Plug>JaSegmentTextObjVI'], \ '<Plug>JaSegmentMoveNW' \ ], 'commands': ['JaSegmentSplit']}}
のようなヤバいことをしなくても、
NeoBundleLazy 'deton/jasegment.vim', {'autoload': {'mappings': [['sxno', '<Plug>JaSegment']], \ 'commands': ['JaSegmentSplit']}}
という簡潔な記述で済ませることが出来るのです。
[NeoBundleについての詳細]
以下の記事が詳しいです。
[NeoBundleLazyしたプラグインがうまく動かないときの対処法]
大抵のプラグインはうまく動きますが、中にはうまく動かないものもあります。
この原因の一つがプラグインがlazy状態にある間は:autocmd
が働かないことにあります。
そんなときは bundle.hooks.on_post_source(bundle)を定義して、中で:doautocmd
してやるとうまくいきます。
例えば、'scrooloose/nerdcommenter' は、新しくバッファに入るとBufEnterイベントでそのバッファのファイル情報を取得します。
しかしlazyで無効になっている間はBufEnterイベントも働いていないので、lazy autoloadでsourceされたときに、そのときのバッファには本来ならある取得しているはずの情報がなくてエラーが発生します。
そこで、bundle.hooks.on_post_source(bundle)に:doautocmd
を設定して、sourceされたときにBufEnterイベントを起こしてやります。
let s:bundle = neobundle#get('nerdcommenter') function! s:bundle.hooks.on_post_source(bundle) doautocmd NERDCommenter BufEnter endfunction
また、'tpope/vim-fugitive' は、新しく編集を始めるときにBufNewFileイベントで、そのバッファがGitの管理下かどうかを調べているのですが、これもlazyで無効になっている間は働いていないので、呼び出されたとき本来Git管理下のファイルであってもGitの管理下にないかのように振る舞います。この場合は:doautoall
で全てのバッファにautocmdを発生させなければいけません。
let s:bundle = neobundle#get('vim-fugitive') function! s:bundle.hooks.on_post_source(bundle) doautoall fugitive BufNewFile endfunction
というわけで、基本はautocmdを疑って、bundle.hooks.on_post_source(bundle)の中で:doautocmd
, :doautoall
を呼びます。
これで解決しないならlazyとは相性が悪いプラグインということになります。諦めましょう。
[NeoBundleLocalにはあまりプラグインを置くべきでない]
NeoBundleLocal(pathogenみたいな管理)で設定しているディレクトリにスクリプトを置くと遅くなります。定期的に掃除して、NeoBundleで管理できそうなものはNeoBundleで管理するディレクトリに移して、lazy化するといいでしょう。
*1:いいえ。技術を磨くことは不可欠です。技術力がないから小細工に走り無駄に時間を浪費するのです。