Hatena::ブログ(Diary)

Alone Like a Rhinoceros Horn

2012-02-21

unite-outline の近況: 次期バージョンの開発

昨年の秋頃から目立った変化のない unite-outline ですが、開発が停滞しているとか、作者のやる気がなくなった*1とかではなく、「今年の抱負 - Vimプラギン編」で述べた通り、次期バージョンの開発(準備)を少しずつ進めています。

ある程度形になったら別ブランチを切って push しようと思ってますが、まだ仕様をあれこれ考えたり非同期実行のやり方を模索している段階で、ものは全然できておりません(汗 まあ、上半期のうちに最初のバージョンを push できたらいいな、ぐらいの見込みです。*2

大きな変更が必要になるついでに、「もうこの際、たまった負債を全部返済してやる!」みたいな意気込みでして、プラグインの構成上前から気になっていた点や、作者がまずいと思っている outline info の仕様など、全面的に見直してドカンと刷新する予定です。(というか、そうせざるを得ないのです…… 詳細は以下を参照)

で、その次期バージョンですが、青写真は以下の通りです。

NuOutline(仮)

アウトライン解析部を独立したプラグイン(NuOutline(仮))として切り出し、unite-outline をそのフロントエンドとして添付するという構成をとります。

プラグインの構成上一番大きな変更点がこれです。

「unite.vim の source として unite-outline は大き過ぎる」、「アウトライン解析を行うプラグインに unite-outline を添付すべきでは?*3」という指摘は以前から何度かあり、自分でも気になっていました。また、

  • autoload/unite/sources/outline/ 以下のディレクトリ階層の深い位置にファイルがごちゃごちゃと多過ぎる。
  • その結果、オートロード関数の名前がやたら長くなる。
  • また、動作が複雑になってオプション変数も増えたが、unite.vim の source のそれはやはり名前が長くなる。
  • unite.vim の source とアウトライン解析部が渾然一体、なんだかなー

といったこともあり、アウトライン解析を行う部分を独立したプラグインとして切り出し、プラグイン全体の構成を仕切り直すことにしました。図で示すと以下のようになります。

f:id:h1mesuke:20120221221020p:image

きれいに 3層に分離しました。3者の役割分担は Webアプリケーションなどでおなじみの MVCパターンでとらえると理解しやすいです。アウトライン表示のインターフェースを提供してくれる unite.vim がビュー、見出し抽出のロジックを持っている NuOutline がモデル、両者の間に入ってあれこれと調停をし、見出しの自動更新のための面倒をみたりする unite-outline がコントローラというわけですね。

V, C は交換可能(にしたい)

NuOutline は(頑張って)unite.vim, unite-outline と密に結合させないで作ろうと思っています。unite-outline はあくまでフロントエンドのデフォルト実装として NuOutline に添付されているもの、という位置付けにして、第三者が別のフロントエンドを提供できる余地を残しておきます。(そんな人が現れるかは別にして ;-) )

が、リポジトリは unite-outline のままで!

どちらかというと unite-outline の方が NuOutline のオマケ的な存在なのですが、Vundle や neobundle.vim 全盛(?)の昨今、リポジトリURL が変わるというのは致命的だと思うので、開発は引き続き、現行の unite-outline のリポジトリで行います。まあ、使うだけなら NuOutline は unite-outline の影に隠れて見えませんし、ユーザーが使うのはあくまで unite.vim を通してですからね。

というわけでなんと、unite-outline という unite.vim の source に、プラグインがまるまる1個付いてくるということになります(笑)

オプション変数名が変化

「unite.vim上でどう表示するか」に関する設定を除き、オプション変数の所管が NuOutline の方へ移るので変数の名前が変わってしまいます。しばらくは古い変数の方も見るようにしたいところですが……

outline info → parser

unite.vim の source である unite-outline の source……的な存在であった outline info は所管が NuOutline の方へ移ります。それにともない名称も outline info から parser に変更し、配置場所も

  • autoload/nuoutline/parsers/

とします。*4unite.vim における sources, kinds, filters などと同様の位置付けになります。

DRY でない create_heading() よ消え去れ!

outline info の仕様には(今となっては)気に入らない部分がたくさんあり、その最たるものが見出し抽出のためのパターンが複雑になると create_heading() の中身が DRY でなくなるというものです。例えば、

'^\s*\(foo\|bar\|baz\|fizz\|buzz\)\>'

のようなパターンを設定したとして、マッチングが行われ、マッチが見つかって create_heading() が呼び出されたとします。その場合、マッチしたサブマッチがどれかによって処理を分けなければならないことがほとんどで、現行の outline info ではマッチした文字列に対して再度マッチングを試み、サブマッチのうちのどれに実際にマッチしたのかを判定するような DRY でないコードが氾濫しています。

これは outline info を書く側の問題ではなくて、そのようにしか書けなくしている outline info の仕様の方に問題があるのです。

「こんなことでは outline info を書いてみようと思ってくれた人も離れていくわ!」

と強く思った私は、outline info の所管が NuOutline へ移るこのタイミングで、その仕様を刷新することにしました。

まだ仕様を練っている段階ですが、

let s:parser.foo_pattern = "foo's pattern"
function! s:parser.create_foo_heading()
endfunction

let s:parser.bar_pattern = "bar's pattern"
function! s:parser.create_bar_heading()
endfunction

let s:parser.baz_pattern = "baz's pattern"
function! s:parser.create_baz_heading()
endfunction

こんな風に、見出しの種類ごとに個別にパターンを設定できるようにし、「どのパターンにマッチしたか」の判定までを本体側の責任として、適切なコールバックが呼び出されるようにします。parser側では見出し(辞書)の作成に専念できるようになるので、パターンマッチ方式の parser を作るための敷居はかなり下がるはずです。

構文解析が必要になるケースのサポート

create_heading() について力説した直後にアレですが、「正規表現によるパターンマッチ → create_heading() 」というやり方で見出しが拾えるファイルタイプは実は少なく、ほとんどのプログラミング言語では見出しを「ちゃんと」拾うのに構文解析が必要になります。

具体的に言うと、ctags などの外部の構文解析プログラムを呼び出し、その出力を解析して見出しの木を parser が自前で作る必要があります。

今後、対応ファイルタイプを増やしていくためにも、その辺の処理が書きやすくなるようなユーティリティコードの充実やドキュメントの整備などが必要と考えています。

まとめ

最初からちゃんと設計しとけや! ヽ(*`Д´)ノゴルァ

うう、当初は「正規表現でマッチさせて拾うだけやん」程度の軽いノリで作り始めたのでございます。最初にコミットされたバージョンの素朴さがすべてを物語っている……

しかし、まあ、ここまできたらプラグインとしてちゃんとしたものにしたいですね!

*1:むしろ逆で、変なブーストが入ったw

*2:今年の抱負ということになってるので、遅くとも年内にはw

*3:alignta に unite-alignta が同梱されているのがこの構成ですね。

*4:outline info は autoload/outline/ に配置してもよいことになっていましたが、これは autoload/unite/sources/outline/ が自作の outline info を配置する場所として位置が深すぎるだろうとの配慮からでした。通常、autoload/ に何かを配置させるにあたっては、プラグイン名できちんと名前空間を切るべきです。

2012-01-16

Vim のプラグインのプラグインの呼称

Vimプラグインプラグインの呼称について、自分が知っている範囲でまとめてみた。

PluginPlugin's PluginLoad Function
neocomplcachesource#define()
QuickRunmodule#new()*1
ref.vimsource#define()
unite.vimsource, kind, filter#define()
unite-outlineoutline info#outline_info()

うーん……

outline info っていう名前が気に入らないなー

元々、unite.vim の source である unite-outline のプラグインがこれまた source だったら紛らわしいなあと思い、何か別の呼称を考えようということで outline info としたのが事の始まり。しかし、今にして思うと「〜info」みたいな名前はそれが何かを端的に表しておらず、漠然としていて、決していい名前とはいえない。おまけに長いし。

何かもっとよい呼称はないものか。*2

unite-outline の次期バージョンでは、本体を独立したプラグインとして切り出し、unite-outline をそのプラグインに添付する、という構成にする予定で、ディレクトリの構成とか思いっ切り変わるので、これを機に outline info という呼称をなくそうと考え中。

*1:使用する際は明示的に quickrun#register_module() などで登録する。

*2:一応候補は見つかった。

2012-01-15

非同期砂漠

プラギンが哭(な)いてる ユーザーに罵られて

 Vim はやさしさを どこに棄ててきたの

  だけどわたしは 好きよこのエディタ

   肩を寄せあえる vimproc・・・system_bg() がいる

    呼び出し側で あゝブロックしないならば

     つらくはないわ この非同期砂漠

      あなたがいれば あゝユーザー待たさないで

       コマンド実行できる この非同期砂漠


( ^ω^)・・・


なんか vimproc の使い方調べてたらいつの間にかサビの部分を口ずさんでいたw

昔テレビの CM(ビルの屋上でバスケしてるやつw)で流れていたサビの部分の歌詞とメロディしか知らないというのに、いつの間にかその部分で替え歌ができるというのですから、テレビの力というのは恐ろしいものですね!

ちなみに原曲は 内山田洋とクールファイブの「東京砂漠」で昭和51年の曲。なんと私が生まれる前の曲でした。(さりげなくオッサンじゃないアピール)

2012-01-12

Vim のプラグインを書きたいと思っている人は今すぐ :help write-plugin を読もう!

特に *use-cpo-save* のところ。プラグインのソースでよく見かける、

これ

let s:save_cpo = &cpo
set cpo&vim

と、これ

let &cpo = s:save_cpo

がなぜ必要なのかが書いてあるよ!

というか、行の継続を許容するためだったとか全然知らんかったw

ということで、このイディオムの効用を勝手に誤解していた自分のプラグインでは、行の継続をばりばり使っているにも関わらず、正しく set cpo&vim していないソースが散見されたので、ここ数日それをせっせと直していました。あわわ(汗

これまでこの件でエラーの報告がきたことはないので、Vim を起動したら(意図せず) 'compatible' が ON になる、という条件*1のユーザーは極めて少ないんだとは思うけど、自作のプラグインを公開するものとして、所作は正しく身に付けておきたいものです(キリッ ( ← 数日前までできてなかった)

See also

*1Vimプラグインを拾ってきて自分の .vim に配置できて、なおかつ vimrc も gvimrc もない、という条件のユーザー。かなり想像しにくい。

2012-01-07

今年の抱負 - Vimプラギン編

抱負を述べるには時期を逸した感があるものの、こういうものは一応でも言っておいた方が、一年を通してほどよいプレッシャーになっていいと思うのでw

alignta

去年やるはずだったのにできなかった、

  • 前回の整列を繰り返す機能
  • unite-alignta と連動した履歴の機能
  • ヘルプの英訳

これらの ToDo に引き続き取り組む。

後、自分で使っていて、「あれ、そうじゃないって(汗」という局面がまだまだあるので、そういう整列の細かい挙動などを少しずつ改善していく。

整列方法の指定書式はこれ以上増やしたくないが、既存の書式で「こう揃えたい」を表現できないとなれば、何か加える可能性はある。*1

(取り組む本人が)面白そうな ToDo としてこんなのもある。*2

後、コードをもっとすっきりさせたいし、プロファイルをとって少しでも速くしたい。機能の追加が控えているうちは多分手を付けないと思うけど、その辺までいければ今年は御の字といえる。

unite-outline

今年の課題はずばり以下の2つ。

他にも細かい ToDo はいろいろとあるけれども、この2つに比べれば瑣末といえる。また、この2つの課題をクリアするためには、中核部分はほとんど書き直しに近い変更が必要になると思うので、それまで他の ToDo はとりあえず塩漬けになると思う。

現在のバージョンに対しては最低限のバグフィックスのみを行うようにし、別ブランチで新バージョンを開発、みたいな流れかな。具体的にどうやって進めていくかはまだ決めてないけど。

GitHub で最初に公開したバージョンからずっと、見出しの抽出は「現在のバッファ」と密に結合していて、かつ「同期的」だった。昨年はこれらの問題点が明らかになった年で、今年はそれらを解決する年としたい。

*1:とはいえ、あまり複雑にし過ぎると暗号のようになり、「覚えいにくい」「使いにくい」といったことになるので、その辺のバランスは難しい。

*2:ヘルプの英訳よりはやく終わるかもw

2011-12-28

Vim script のベンチマーク

数万回くらい実行されるループの中で文字列が空かどうかを調べなければならないとする。

  • empty(str)
  • str == ""

さて、どちらがいいだろう? empty() を使う方が、文字列が空かどうかを調べる、という意図が明確になっていいような気がするが、empty() の方は関数呼び出しなので、ひょっとすると ==演算子に比べて相当遅いかも知れないぞ……

この手の疑問/迷いを解消するために、同じことをする複数の Vim script コード片の速度性能を簡単に比較できるツールを作ってみました。

+reltime と +float が必要。7.2以降の Vim なら多分大丈夫(だと思うけど自信はないw)

使い方はこんな感じ。最初の疑問を解消すべく、ベンチマーク・スクリプトを作ってみる。

let s:bm = benchmark#new("String is empty?")

let s:N = 10000
let s:str = "hello"

function! s:bm.empty()
  let i = 0
  while i < s:N
    if empty(s:str)
    endif
    let i += 1
  endwhile
endfunction

function! s:bm.op_equal()
  let i = 0
  while i < s:N
    if s:str == ""
    endif
    let i += 1
  endwhile
endfunction

function! s:bm.op_match()
  let i = 0
  while i < s:N
    if s:str =~ '^\s*$'
    endif
    let i += 1
  endwhile
endfunction

call s:bm.run(3)

で、:source % または :QuickRun すると……

Benchmark: String is empty?

Trial #1
  op_equal : 0.078297
  empty    : 0.086356
  op_match : 0.114139

Trial #2
  op_equal : 0.080296
  empty    : 0.086518
  op_match : 0.109003
    
Trial #3
  op_equal : 0.080530
  empty    : 0.091485
  op_match : 0.118427

こういう結果が得られます。

所要時間にはループのコストも含まれていて、実はそれが所要時間の半分以上を占めていたりするので*1、これがそのまま ==演算子と empty() の速度差というわけではないものの、whileループの中に置いた場合の差は大体この程度、というのがわかります。(空の whileループの所要時間も同時に計測すれば、ループのコストを差し引いたおおよその速度差も検証できるはず)

Vim script を書いていて、ある処理を記述する複数の書き方のどれを選択するかで迷ったとき、これを使って速度比較を行ってみると、思わぬ発見があるやも。


もっと詳細に性能の評価を行いたい場合は……

このスクリプトのように、ループの中身が1行だけみたいな場合、ループ自体のコストが無視できないレベルになるので、このツールは対象コードの純粋な性能評価には向いていません。どちらかというと複数ある選択肢の中のどれが一番いいか、を比較するためのものです。

もっと詳細に行単位で性能を評価したいとか、全体のボトルネックを突き止めたいとかいった場合には、Vim のプロファイル機能を利用するべきでしょう。

*1Vim script の while は for に比べて相当遅いです。付属のサンプル参照。

2011-12-05

word の中の単語を選択する textobj-wiw を書いた

(この記事は Vim Advent Calendar 2011 5日目の記事です。前日は thincaさんでした!)


前々から、地味にフラストレーションを感じていたこととして、

この状態から、"word" の部分を書き換えたい場合、 

this_is_a_word_in_a_very_long_identifier

 ↓

this_is_a_word_in_a_very_long_identifier

こう選択する text-object が欲しいんだよ! というのがありました。


また、選択と同様に、

ここから、

this_is_a_word_in_a_very_long_identifier

 ↓

this_is_a_word_in_a_very_long_identifier

this_is_a_word_in_a_very_long_identifier

this_is_a_word_in_a_very_long_identifier

こんな具合にジャンプしていきたいんだよ! とか。


で、この手の「word の中の単語」は上に挙げたような snake case の中だけでなく、thisIsAWordInAVeryLongIdentifier のような camel case の中にもあるし、this#is#a#word#in#a#very#long#identifier のような、Vim script のオートロード関数の名前の中にもあります。

それら「word の中の単語」を一様に取り扱える text-object があれば便利だと思い、kanaさんの textobj-user を使って textobj-wiw というのを書いてみました。

wiw は word in word の略です。「word の中の単語」とは要するに、「Vim における word に含まれている、人間が単語と認識している部分」のことです。

これを使うと、「word の中の単語」間をジャンプで移動できたり、おなじみの iw, aw と同様の操作で「word の中の単語」をサクッと編集/削除/選択できるようになります。

インストール

プラグインをインストールするいつもの方法で

の2つをインストールして下さい。

説明になってないような気もしますが、重要なことは textobj-user が必要だということです。

キーマッピング

textobj-wiw には w, b, e, ge, iw, aw に相当する一連のキーマッピングがあり、デフォルトでは以下のキーマッピングを定義するようになっています。

        lhs     Modes   rhs
        ---------------------------------------
        ,w      nxo     <Plug>(textobj-wiw-n)
        ,b      nxo     <Plug>(textobj-wiw-p)
        ,e      nxo     <Plug>(textobj-wiw-N)
        ,ge     nxo     <Plug>(textobj-wiw-P)

        a,w     xo      <Plug>(textobj-wiw-a)
        i,w     xo      <Plug>(textobj-wiw-i)

対応する動作がわかりやすいように prefix を付ける方式にしました。prefix は g:textobj_wiw_default_key_mappings_prefix変数で設定できます。(デフォルトは ",")

デフォルトのキーマッピング体系が気に入らない場合は、vimrc にて g:textobj_wiw_no_default_key_mappings 変数を定義すればデフォルトキーマッピングの定義をキャンセルできます。その上で、各自でよさげなキーマッピングを定義して下さい。

似ている text-objects

キャメルケースについては既にやっている方がいました。あわわ。

また、ある特定の文字で囲まれた部分、を取り扱う text-object としては thincaさんの textobj-between もあります。こちらは選択範囲が word内に限定されません。

探せば他にもあるような気がしますが、恐いのでこれ以上は探しませんw


それでは皆さん、Happy Vimmer days!

2011-11-06

unite-outline の近況: Ruby と RSpec の見出し抽出を改善

今秋から Rails やってます。というわけで、unite-outline の Rubyサポートを強化中。まずは、Ruby用 outline info の改善から。

def 以外のメソッド定義も拾う

なんと、メソッドの定義は def しか拾ってなかったので、attr_accessor, attr_reader, attr_writer, alias などが今まで見出しになっていなかったという驚愕の事実。おいw

こいつらもメソッドの定義だから見出しになるべき、ということで、そのように修正。

Rails だと mattr_ とか cattr_ とかもあるよねー、ということでそっちにも対応。

正しい構造のツリー

Ruby(というか Rails界隈?)では protected, private なメソッドを public なものより一段字下げするという慣習で書かれているコードが少なからずあって、Ruby ではインデントの深さを元に見出しレベルを決めている関係上、一連の privateメソッドが直前の publicメソッドの小見出しになるということが起こっていた。(:help unite-outline-known-issues を参照)

で、これはちょっとかっこ悪いというんで、protected または private の後で字下げしている場合には、字下げされているメソッドを protected または private の下に束ねて、直前の publicメソッドの小見出しにならないようにした。

Before:

f:id:h1mesuke:20111106040844p:image

▲ privateメソッドが publicメソッドの小見出しになってしまっている。

 ↓

After:

f:id:h1mesuke:20111106040843p:image

▲ private を見出しにすることで回避

これで絞り込みの結果に無関係なメソッドが残ることはなくなった。

DSL対応

後、ftdetect で複合ファイルタイプの設定をしなくても DSL用の outline info へ処理を委譲できる仕組みを実装した。これにより、ファイルタイプが ruby のままでもファイル名が *_spec.rb なら RSpec用の見出し抽出を行う、みたいなことができるようになった。

f:id:h1mesuke:20111106040845p:image

▲ describe, it などが見出しになっているが、実はファイルタイプはただの ruby

具体的には、オートロード関数 path#to#outline#{filetype}#outline_info() にて、呼び出し側から渡される context の情報を元に、別のファイルタイプ名(例えば 'ruby/rspec' とか)を文字列として返すと、そっちへ処理が委譲される。(ファイルタイプ・リダイレクトと命名)

Ruby用の outline info ではこの仕組みを使い、ファイル名が *_spec.rb なら RSpec用の outline info へ処理をリダイレクトさせている。

この仕組みを使い、Ruby の主要な DSL に順次対応していこうと思っている。とりあえず次のターゲットは Rake あたり。

2011-10-10

unite-outline の近況: C のマクロがどう展開されるかを表示するようにした

C のマクロが展開されてどうなるかを => の後に表示するようにしてみた。

Before:

f:id:h1mesuke:20111010163037p:image

 ↓

After:

f:id:h1mesuke:20111010163030p:image

上の画像より空間の利用効率が上がって情報量が増えたのがわかると思う。

do { ... } while(0) のような複数行に渡るものは最初の数行分しか表示できないけど、アウトラインってことならこんなもんで十分だろう。詳細はプレビューするなりジャンプするなりすれば確認できるわけだし。

2011-09-25

alignta 秋の仕様改訂、の近況

とりあえずここまでの作業分を master へマージしました。

でやろうって言った変更と、

にあるもののうち、「繰り返し」と「履歴機能」以外の部分。

書式が変更になる部分では原則 version 0.2.1 以前のそれも引き続き使えるようになっているので、大きな混乱は起きないと思いますが、古い書式は将来的には使えなくする予定*1なので、preset arguments や preset options などに古い書式が残っている場合は、今からでも新しい書式に書き換えておくと吉です。

具体的には、

  • :Alignta! は :Alignta に変える。
    • ! の有無は今や意味を持たない。
  • 整列回数指定は pattern/number または pattern/g になったので変える。
    • pattern{1} → pattern/1
    • pattern{+} → pattern/g
  • マージン指定の @ は要らなくなったので取る。

alignta の書式が変わるのはおそらく*2これが最後になると思います。*3書式の改善はこれで一段落し、今後は「繰り返し」や「履歴機能」といった機能の追加が主になります。*4

*1:残しておいてもいいのですが、パターンと衝突した場合に、「なぜ?」となりそうなので、将来的にはなくしたい。

*2:ちょっと弱気w しかし世の中何が起こるかわからないからなあ。

*3:実のところ、こういった書式の変更をある程度見越していたので、これまで alignta を大っぴらには喧伝してこなかったというのもある。

*4:年内をめどにぼちぼちやっていこうかと。