koturnの日記

普通の人です.ブログ上のコードはコピペ自由です.

NeoBundleのインサートモード時遅延読み込みの注意点

この前、Gistに投稿した記事だが、どうせなので、はてなの方にも投稿しておこうと思う。
元はこちら:https://gist.github.com/koturn/5929673


NeoBundleでインサートモード時に遅延読み込みすると、インサートモード時にステータスラインの色を変更するというよくある設定が うまくいかないということをTwitterで呟いたところ、暗黒美夢王さんから返答がきたので、そのことについて書いてみようかと思う。

挿入モードでステータスラインの色を変更する


まず、当初、私が.vimrcに書いていたのは、以下のような設定である。
例として、neocompleteをインサートモード時に遅延読み込みするようにしてある。

let $DOTVIM = $HOME . '/.vim'
if has('vim_starting')
  set runtimepath+=$DOTVIM/bundle/neobundle.vim
endif
call neobundle#rc(expand('$DOTVIM/bundle/'))
NeoBundle 'Shougo/neobundle.vim'
NeoBundleLazy 'Shougo/neocomplete.vim',
      \ {'autoload' : {'insert' : 1}
      \}


" ====================================================================
" インサートモードでステータスバーの色を変更する
" NeoBundleでインサートモード時に読み込むプラグインが1つでもあると、
" うまく動作しなくなる。
" ====================================================================
set laststatus=2
set statusline=%<%f\ %m\ %r%h%w%{'[fenc='.(&fenc!=#''?&fenc:&enc).']\ [ff='.&ff.']\ [ft='.(&ft==#''?'null':&ft).']\ [ascii=0x'}%B]%=\ (%v,%l)/%L%8P

let g:hi_insert = 'highlight StatusLine guifg=darkblue guibg=darkyellow gui=none ctermfg=blue ctermbg=yellow cterm=none'

if has('syntax')
  augroup InsertHook
    autocmd!
    autocmd InsertEnter * call s:StatusLine('Enter')
    autocmd InsertLeave * call s:StatusLine('Leave')
  augroup END
endif

let s:slhlcmd = ''
function! s:StatusLine(mode)
  if a:mode ==# 'Enter'
    silent! let s:slhlcmd = 'highlight ' . s:GetHighlight('StatusLine')
    silent exec g:hi_insert
  else
    highlight clear StatusLine
    silent exec s:slhlcmd
  endif
endfunction

function! s:GetHighlight(hi)
  redir => hl
  exec 'highlight ' . a:hi
  redir END
  let hl = substitute(hl, '[\r\n]', '', 'g')
  let hl = substitute(hl, 'xxx', '', '')
  return hl
endfunction

colorscheme default

これは最小構成であるが、うまくいかないということが確認できると思う。
暗黒美夢王さんによると、NeoBundleはインサートモード時に遅延読み込みを行うときに、InsertEnterのautocmdを発行しているらしい。
つまり、最初にインサートモードに入ったときに、InsertEnterが2回呼ばれているというになり、上記コードのs:slhlcmdという変数が、ハイライトコマンドをうまく保存できないということになる。

これは、NeoBundleの仕様であり、複数回InsertEnterが発行されても大丈夫なように、autocmdを書くべきとのこと。

上記のインサートモード時にステータスラインの色を変更するという設定は、InsertEnterの後にInsertLeaveが発行されると仮定しているから起こる問題なので、それを解消すればよい。
あと、個人的に気に入らないところ(関数や変数のスコープ、命名規則など)があるので、その部分も修正したコードを、以下に記す。

let $DOTVIM = $HOME . '/.vim'
if has('vim_starting')
  set runtimepath+=$DOTVIM/bundle/neobundle.vim
endif
call neobundle#rc(expand('$DOTVIM/bundle/'))
NeoBundle 'Shougo/neobundle.vim'
NeoBundleLazy 'Shougo/neocomplete.vim',
      \ {'autoload' : {'insert' : 1}
      \}


" Show status line at the second line from the last line.
set laststatus=2
set statusline=%<%f\ %m\ %r%h%w%{'[fenc='.(&fenc!=#''?&fenc:&enc).']\ [ff='.&ff.']\ [ft='.(&ft==#''?'null':&ft).']\ [ascii=0x'}%B]%=\ (%v,%l)/%L%8P

" Change color of status line depending on mode.
if has('syntax')
  augroup InsertHook
    autocmd!
    au InsertEnter * call s:hi_status_line(1)
    au InsertLeave * call s:hi_status_line(0)
    au ColorScheme * silent! let s:slhlcmd = 'highlight ' . s:get_highlight('StatusLine')
  augroup END
endif

function! s:hi_status_line(mode)
  if a:mode == 1
    highlight StatusLine guifg=white guibg=MediumOrchid gui=none ctermfg=white ctermbg=DarkRed cterm=none
  else
    highlight clear StatusLine
    silent exec s:slhlcmd
  endif
endfunction

function! s:get_highlight(hi)
  let l:hl = ''
  redir => l:hl
  exec 'highlight ' . a:hi
  redir END
  let l:hl = substitute(l:hl, '[\r\n]', '', 'g')
  let l:hl = substitute(l:hl, 'xxx', '', '')
  return l:hl
endfunction

colorscheme default

ステータスラインの色は、カラースキームの変更時に取得するようにしておけば、ステータスラインの色を変更するだけの処理をしない限りは問題ないだろう。


このような初心者的な問題に引っかからないように、Vim力を向上させていきたいものである。