nebula.vimを使ってneobundle#tap()をより高速に書く

Shougo/neobundle.vimももんが流NeoBundle管理術 | かなりすごいブログ から機能が取り込まれneobundle#tap() neobundle#untap()が使えるようになりました。

これを利用するとだいたいこんな感じで非常にすっきりとプラグインの設定が書けます。

ここでは LeafCage/nebula.vim を使って、より楽に高速にneobundle#tap()を記述する方法を紹介します。

vimrcのNeoBundle 'repository/pluginname'のある行で:NebulaYankTapを呼び出すと、

if neobundle#tap('pluginname')
endif

レジスタにセットされます。

:NebulaYankTap!のように、!付きで呼ぶと

if neobundle#tap('pluginname') "{{{
endif
"}}}

のようにfoldingmarker付きでヤンクされます。
これを記述したい場所にカーソルを持ってきて貼り付ければよいのです。

中身のneobundle#config()を書くときにはShougo/neosnippet.vimなどで

snippet   neobundleconfig
alias   nbc
  call neobundle#config(${1})

みたいなsnippetを定義しておいて、オプションを:NebulaYankOptionsで生成すると捗ります。

コマンドラインからシームレスにover.vimの全体置き換えを使う

私は今まで:sについて以下の設定をしていた。

cnoreabb <expr>s getcmdtype()==':' && getcmdline()=~'^s' ? '%s/<C-r>=Eat_whitespace(''\s\\|;\\|:'')<CR>' : 's'
function! Eat_whitespace(pat) "{{{
  let c = nr2char(getchar(0))
  if c=~a:pat
    return ''
  elseif c=~'\r'
    return ''
  end
  return c
endfunction
"}}}

こうしておくと、コマンドラインでsと打ってから<Space>キーで %s/ という具合に展開されていたので、全体置き換えをするときに打ちにくい % を打たなくて済むという利点があった。
そんな折、osyo-manga/vim-over がリリースされた。
結果をプレビューする機能はCoolだがぶっちゃけそんなに要らなかったが、置き換え実行後にカーソルが動くことがないというのに魅力を感じたので、上記の操作を over.vim で置き換えることにした。

以下のように設定することでコマンドラインで s を打ってから<Space>を打つと、そのままover.vim%s/ が入力された状態になる。

cnoreabb <silent><expr>s getcmdtype()==':' && getcmdline()=~'^s' ? 'OverCommandLine<CR><C-u>%s/<C-r>=get([], getchar(0), '')<CR>' : 's'

Vimのabbreviate(短縮入力)機能によって、行頭でのsOverCommandLine<CR><C-u>%s/に置き換えているわけである。
その際、トリガーに使った<Space>は不要なのでgetchar(0)に食わせている。
<C-r>=get([], getchar(0), '')<CR>の部分はトリガーに使った文字の処分である。
別にトリガーは短縮入力を展開できる文字なら何でも良いので<Space>でなくて<CR>でも良い。

'nobuflisted' なバッファの作り方

Vim Advent Calendar 2012 356日目の記事です。

バッファをバッファリストに登録したくないときがあります。
バッファリストに登録されていないバッファは:lsでは表示されず、:bnext :bpreviousでのバッファ切換の対象になりません。
また、Vim終了時に次回起動時のための情報を書き込むviminfoファイルのバッファリスト保存・復元から除外されます。つまりVim終了時にはそのバッファは破棄され次回起動に持ち越しません。
このオプションは通常、その場限りの情報を表示するだけで、書き込む予定のない、使い捨てのバッファに設定します。

しかし、せっかく'buflisted'オプションをオフに設定しても、バッファの内容を変更すると勝手に'buflisted'がオンに設定し直されてしまいます。
正しくは「バッファの編集を始めると」オンになるのでした。:edit:split :vsplit :newなどで切り替えるとオンにされるみたいです。つまり:editなどを使わず:bufferでバッファを切り替えるのなら:setlocal nobuflistedを設定するだけでよいです。

ここではいかにして、何らかの形で意図せず'buflisted'オンになっても'buflisted'をオフに保つようにするバッファを作るのかを解説します。また、特別なバッファに設定するべき他のオプションも併せて説明します。

変更の予定のないバッファの場合

バッファを変更する予定がなければ、ただ単に'nobuflisted'オプションを設定すればいいです。
その際、ユーザにバッファの内容を変更を禁止させるように'nomodifiable'オプション、読み込み専用だということをユーザに示すために'readonly'オプションもセットしましょう。

edit examplebuf
setlocal nobuflisted nomodifiable readonly

※これらはバッファにローカルなオプションなので:setlocalではなく通常の:setで設定しても問題ありません。
:edit+cmdオプションを与えればこのコマンドを1行で表せます。

edit +setlocal\ nobuflisted\ nomodifiable\ readonly   examplebuf

変更の予定があるバッファの場合

編集が禁止されたバッファでスクリプトがバッファの内容を変更するのなら、そのときに一時的にこれらのオプションを変更し、内容変更後に元に戻せばいいでしょう。

setlocal buflisted modifiable noreadonly
call setline(1, '==example==')
setlocal nobuflisted nomodifiable readonly

ユーザが自由に編集できるバッファをバッファリストに登録させたくない場合は、そのバッファを抜けるときに'nobuflisted'に設定しなおすバッファローカルなautocmdをそのバッファに設定しておきます。
また、たまたまそのバッファにいるときにVimを終了させようとしたときにもバッファリストから除外するようにVimLeavePreイベントのautocmdも設定します。

edit examplebuf
augroup example_local
  autocmd!
  autocmd BufLeave <buffer>   setlocal nobuflisted
  autocmd VimLeavePre <buffer>    setlocal nobuflisted
augroup END

需要があるか分かりませんが、現在Vimではbuflistedにしておきたいが、Vim終了時にはnobuflistedにして次回起動時のバッファに残させないときの設定です。この場合そのバッファのバッファ番号を何らかの形で記憶しておき、autocmd VimLeavePre \*でそれらのバッファにnobuflistedオプションを設定します。

augroup example_local
  autocmd!
  autocmd VimLeavePre *    call <SID>make_bufs_nobuflisted()
augroup END

let s:bufnrs = []
function! s:make_bufs_nobuflisted()
  for bufnr in s:bufnrs
    if buflisted(bufnr)
      call setbufvar(bufnr, "&bl", 0)
    endif
  endfor
endfunction
edit examplebuf
call add(s:bufnrs, bufnr('%'))

特別なバッファに設定するべき他のオプション

readonly, nomodifiable

説明済み

buftype

ファイルと関連がなく、書き込まれる予定のない仮想的なバッファにはset buftype=nofileを設定します。
buftype=nofileを設定すると:writeが禁止されます。しかし、バッファの編集変更自体は出来るので、それを禁止させたければ前述のnomodifiableも設定しましょう。

bufhidden

バッファの使い捨てという正確を強めたいときに利用します。 バッファがウィンドウに表示されなくなったときの挙動を指定するオプションですが、そのバッファを閉じたらバッファの内容を破棄させたいのならbufhidden=unload、削除や完全削除をしたいのならbufhidden=delete(←これはまだ:bでバッファ指定するなどしたらアクセスできる)やbufhidden=wipe(←完全に削除される)を使います。
例えばkien/ctrlp.vimbufhidden=unloadが利用されています。ctrlp.vimの作るバッファは刹那的な性格が強いですし、無駄なメモリも解放されるからでしょう。

活用例

例えばShougo/vimfiler.vimの作るバッファをバッファリストに残したくない時には.vimrcに次の設定を書きます。

autocmd FileType vimfiler  setlocal nobuflisted
  \ | autocmd BufLeave    <buffer>  setlocal nobuflisted
  \ | autocmd VimLeavePre <buffer>  setlocal nobuflisted

これでバッファを閉じたときにはバッファリストのサイクルから外れますし、次回起動時にvimfilerのバッファが残っているということもありません。

README.mdをVimのヘルプファイルから生成する

この記事はVim Advent Calendar 2012の341日目の記事です。

前回(84日前)Vimプラグインのスクリプトファイルからヘルプファイルを生成するというのをやりました。

しかしGitHubではREADME.mdファイルがリポジトリの窓口であり、説明であり、これによってそのプラグインの概要を知り、それを導入するかどうかが判断されます。
README.mdを置いていないとプラグインの詳細を知るためには、リポジトリトップから2クリックもかかるdoc/xxx.txtを見に行かなければいけませんし、リポジトリトップにはGitHubがREADME.mdを置けと勧めてきます。
つまり我々はREADME.mdを書くことを暗に強いられているのです。

しかし、すでにhelpに概要含め色々書いているのにさらに別の場所で二重に概要を書かなければいけないなんて苦痛です。
なぜhelpに書いたことを改めて書かなければいけないのでしょうか。
そこでLeafCage/vimhelpgenerator:HelpIntoMarkdownというコマンドを追加しました。*1

ヘルプファイル内でREADME.mdに含めたいところをビジュアル行選択(“概要”や“使い方”などを選択すると良いでしょう)して、

f:id:leafcage:20131106074914p:plain

:HelpIntoMarkdownを実行すると、

f:id:leafcage:20131106074933p:plain

そのヘルプファイルの上にあるREADME.mdファイルが開かれます。*2

f:id:leafcage:20131106074944p:plain

ここでPすると、先ほど行選択されたテキストがmarkdown形式に変換されてputされます。(README.mdを初めて作成するときにはプラグイン名と説明もhelpファイルの第1行目から生成されます。)
手直しした後、:writeしましょう。
:HelpIntoMarkdownはアンダースコア(_)のエスケープには対応していません*3。自力で対応させて下さい。

f:id:leafcage:20131106075002p:plain

もしもヘルプファイルを改修したときには改修した箇所をビジュアル行選択して:HelpIntoMarkdownして下さい。その部分の変換結果がレジスタにセットされるのでREADME.mdにて貼り付けて手直しして下さい。

これでヘルプファイルとREADME.mdを管理する手間を大幅に減らすことが出来ました。

:VimHelpGeneratorVirtualでヘルプを仮想バッファに出力する

vimhelpgeneratorには、:VimHelpGeneratorVirtualというコマンドも加わっています。
:VimHelpGeneratorが生成したバッファをそのまま書き込むのに対し、:VimHelpGeneratorVirtualは仮想バッファに出力します。

これにより、vimhelpgeneratorの、ヘルプファイルを初めて作るときにしか使えなかったという欠点を解消しました。
プラグインに機能を追加するなどしてヘルプの改修の必要性が生じたとき、:VimHelpGeneratorVirtualを実行して、出力された仮想バッファ内で必要な部分だけを、既存のヘルプファイルにコピーペースト(Vim風に言うとヤンクプット)すれば良いのです。
単純にVimHelpGeneratorの出力を確認したいときにも利用できます。

:VimHelpGeneratorを実行したときにすでにヘルプファイルが存在している場合もこのモードで出力されます。

*1:これに伴い、:VimHelpGenerator実行時に吐き出していた、クソの役にも立たないREADME.mdを生成させる機能はオミットされました。

*2:ファイルが存在しなくてもOK

*3:技術的に難しかったので

副作用の少ないYankRing.vimみたいなのができました

Vimmerにハロウィンがアドベントしましたね。
Vim Advent Calendar 2012 335日目の記事です。


Vimレジスタの履歴を取って再利用するプラグインにYankRing.vimというものがあります。間違えてp(テキストを貼り付け)してしまっても<C-p>で即座に履歴を遡(さかのぼ)ってテキストを置き換えられます。とてもお手軽で、優れたインターフェイスです。
しかしながら副作用が多く、他のプラグインや設定と干渉してしまうという問題がありました。(重要なキーマッピングを軒並み置き換えてしまうのは勘弁してほしいです。)
それを見かねたShougoさんはunite-source-history/yankというものを作ってくださいました。レジスタの履歴がunite.vimのインターフェイスで閲覧でき、操作できます。便利でしたが、YankRing.vimと比べると、お手軽さで劣りました。

それでしばらくはレジスタ履歴を使わず、必要になったらその都度ヤンクし直すという原始的なことをしていましたが、ふとYankRing.vimの載っている記事を見て*1あの操作感が羨ましくなったので、それっぽい挙動をするプラグインを作ってみました。

NeoBundle 'LeafCage/yankround.vim'

副作用が(たぶん)起こらないように注意しましたので、ヘビーユーザーにも堪える作りになっています。

YankRing.vimっぽく使う

もしYankRing.vimっぽく利用するのであれば、以下のキーマッピングをして下さい。

nmap p <Plug>(yankround-p)
nmap P <Plug>(yankround-P)
nmap gp <Plug>(yankround-gp)
nmap gP <Plug>(yankround-gP)
nmap <C-p> <Plug>(yankround-prev)
nmap <C-n> <Plug>(yankround-next)

これで貼り付けを行った後に<C-p><C-n>レジスタ履歴を遡ってテキストを置き換えることが出来ます。
カーソルを動かすと候補が確定されます。


デフォルトの履歴取得数は30までです。

let g:yankround_max_history = 35

で変更できます。
Vim終了時に履歴のキャッシュを専用ファイルに保存しますがそのディレクトリはデフォルトで以下の場所になります。

let g:yankround_dir = '~/.cache/yankround'

<C-p><C-n>は貼り付け直後にしか有効ではありません。
もしもそれが勿体ないと感じるのでしたら、<expr>を使ったマッピングでyankround#is_active()を使うことで、普段は別の役割を持たせることが出来ます。
例えば、yankroundが有効でないときの<C-p>で、:CtrlPを呼び出すなど。

nnoremap <silent><SID>(ctrlp) :<C-u>CtrlP<CR>
nmap <expr><C-p> yankround#is_active() ? "\<Plug>(yankround-prev)" : "<SID>(ctrlp)"

CtrlPでレジスタ履歴を利用する

yankround.vimにはkien/ctrlp.vimの拡張も付属しています。
:CtrlPYankRoundコマンドで利用することが出来ます。

nnoremap <silent>g<C-p> :<C-u>CtrlPYankRound<CR>

レジスタ履歴が一覧表示されます。履歴を選択後、

  • <CR>で、その履歴をカーソル位置に挿入します。
  • <C-x>(<C-s>)で、無名レジスタ " にその履歴をセットします。
  • <C-t>で、その履歴を履歴から削除します。

※unite-sourceは時間がなかったのと面倒くさかったので作りませんでした。作りました。

貼り付けた部分をハイライトする機能も付けました。

こうして私に再びレジスタ履歴がアドベントしました。
もしも私と同じく、本当はYankRing使いたかったんだけど、キーマップを乗っ取られるのが嫌で利用を諦めていたのなら、ぜひyankround.vimをお試し下さい。

*1:実はunite-source-history/yankっぽいのをctrlp.vimで実装して、それをVACの記事にしようとしてすでにその記事まで書いていたのだが、YankRing.vimとの比較という項を書くためにYankRing.vimについて調べていたら、やっぱりYankRing.vimのインターフェイスの方が便利じゃねーかと思えてきて急遽YankRingっぽいのを作ったというのが真相。

ユーザに入力をさせるinput()のインターフェイスが不満すぎて仕方がないならctrlp.vim風の入力インターフェイスalti.vimを使おう

Vim Advent Calendar 2012 333日目の記事です。

alti.vimctrlp.vim風の操作感を持つ入力インターフェイスです。

もしもユーザに多少複雑めの入力を要求したい場合は、Vimの組み込み関数input()でそれを実現するのが苦しいことがあります。
なぜならinput()には

という弱点があります。他にも操作性の悪さなどが弱点に挙げられます。

input()やコマンドライン補完で複雑な入力をユーザに負担なくさせるのは難しいと気付き、unite.vimctrlp.viminput()みたいなことをするのも恐らく不可能だ*1と考えた私は、新しい入力インターフェイスを開発しました。ちょうどctrlp.vimを本格的に使い始めて、そのインターフェイスを気に入ったので、その影響をかなり受けています。補完の候補が常時、窓に表示されて、入力と共に絞り込まれていくような感じで。

基本の仕組みはctrlp.vimのものを踏襲しましたので、設定変数もctrlp.vimに似せています。
ヘルプに詳しく記しましたのでご参照ください。
操作法を簡単に説明しますと、<C-j><C-k>で補完候補を選択、<Tab>で補完候補を挿入、<C-f><C-b>でページ送り/戻し(補完候補が多すぎるときのみ)、<C-h><C-l>で左右移動、他はコマンドラインの標準の操作とだいたい同じです。<CR>で入力したコマンドを実行します。*2
alti.vimは拡張あって初めて動作するので、これから拡張の作り方を解説していきます。

*1:もしかするとunite.vimでは私がやり方を知らないだけで不可能ではないのかも知れませんが、向いてはいないでしょう。

*2:標準キーマッピングは予告なく変更する可能性があります。

続きを読む

俺が見たいのはメッセージの最後なんだ!:messagesを便利にするVimプラグイン

Vim Advent Calendar 2012 330日目の記事です。

Vimでエラーが発生した時にメッセージが表示されます。
そのメッセージを後から確認したい場合は:messagesでメッセージ履歴を表示します。
しかし:messagesにはVimが起動してからのメッセージが蓄積されているので、長時間起動しているとメッセージが長くなってしまい、最後の方にある目当てのメッセージを表示するためにスクロールさせるのが苦痛になってきます。
知りたいのはエラーが発生してからのメッセージ数行だけでいいのに、200のメッセージをスクロールしなければいけないかもしれないのです。

見ることのできるメッセージ数は、tiny バージョンでは20に、それ以外のバージョン
では200に固定されている。
:help :messagesより引用)

このメッセージは蓄積される数を変更することも出来ないし、消去することも出来ません*1。非常に不便です。

前々からフラストレーションを感じていたので、こんなのを作りました。

NeoBundle 'LeafCage/lastmess.vim'

これを使えば、

:LastMess

で、デフォルトで最後の10行のメッセージが表示されます。

:LastMess 12

または

:12LastMess

というように数値を与えてやると、最後の与えられた数字行のメッセージ(この場合は最後の12行)が表示されます。

表示される行のデフォルト値は10ですが、g:lastmess_default_countで変更することが可能です。

let g:lastmess_default_count = 15

<Plug>(lastmess)はキーマップ版です。例えばこれを

nmap mz <Plug>(lastmess)

というキーに充てておきますと、mzでデフォルトの10行のメッセージが表示されます。
15mzのようにカウントを与えると、その行数分のメッセージが表示されます。

カウントに999などの大きな数値を与えた場合は、メッセージが最初から表示されます(:messagesと同じですね。)。
これで:messagesを見るストレスが軽減され、特にプラグイン制作者は捗ることでしょう。

*1:for i in range(200)| echom ''| endfor みたいなことをすればメッセージをクリア出来るとおしょーさんに教えていただきました。 http://lingr.com/room/vim/archives/2013/10/26#message-17184693