cafegale(LeafCage備忘録)

LeafCage備忘録(はてなダイアリー)と統一しました。

lightline.vimをカスタマイズする

Vim Advent Calendar 2012 の325日目の記事です。
少し長めになりますので、お時間があるときにお読みください。

Vimのステータスラインを改造するプラグインが、Lokaltog/powerlinebling/vim-airlineに続いてitchyny/lightline.vimが登場しました。
私は今までステータスラインやタブラインは自前で改造していましたが、モダンなラインも経験してみたかったので、導入することにしました。

(当記事の内容)
続きを読む

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 7.3 の p1170 で追加された便利なアレについて - C++でゲームプログラミング

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.vimctrlp#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

f:id:leafcage:20130925124226p:plain
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']}}

という簡潔な記述で済ませることが出来るのです。

[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:いいえ。技術を磨くことは不可欠です。技術力がないから小細工に走り無駄に時間を浪費するのです。