Vim の組込み Ruby に編集中のコードを実行させる
- 素敵なところ
- キーマップがかっこいい。ここには載せてないけど元記事の matchit.vim 対応と合わせると、素敵度がさらに上がる。
- 実行結果を覚えていてくれる。
- 残念なところ
- 実行結果を覚えていてくれる。というか、忘れてくれない。
- 実行結果が、シェル上で実行したものと、必ずしも一致しない。例えば
require "pp"
としても、うちの環境では pp が使えなかった。 - 実行結果がコマンドライン上に表示されるので、結果を眺めながらコードをいじる事が出来ない。
上記の特徴から、この方法は完成したコードの実行結果を見るためというより、融通の利く irb という感じで使う事になる。改造を施したのは 1番目の不満点。F10キーを押すと、それまでの実行結果(定義したクラス、ローカル変数)等を破棄する。グローバル変数は破棄出来ない。
"選択範囲のスクリプトを実行。 "http://d.hatena.ne.jp/tanakaBox/20070827/1188149288 nmap <buffer><silent> <C-CR> :call <SID>Eval(function("<SID>YankNormal"))<CR> vmap <buffer><silent> <C-CR> :call <SID>EvalVisual()<CR> nmap <buffer><silent> <f10> :call <SID>Create_ruby_binding()<CR> " yank で取り込んだコードを実行するコンテクストを作成する。 " " この Create_ruby_binding() を実行すると toplevel の self (= main) を複製す " る。複製されたインスタンスは、自分自身の文脈を持った binding をインスタンス " 変数として持つ。 " eval にこの bindig を第2引数として渡すと、eval に渡された文字列はこの複製さ " れたインスタンスの文脈で実行される。 " もう1度 Create_ruby_binding() を実行すると、新たにインスタンスが複製され、代 " 入されなおすため、それまでの文脈情報が破棄される。 function! s:Create_ruby_binding() ruby << EOR toplevel = self.clone class << toplevel def create_binding @binding = binding end def context_inf @binding end end toplevel.create_binding ruby_box = toplevel EOR endfunction call <SID>Create_ruby_binding() function! s:Eval(Yank) let s:saved_reg = @" call a:Yank() execute 'ruby begin; p eval(%q(' . @" . '), ruby_box.context_inf); rescue ScriptError, StandardError => e; puts e.message;end' endfunc function! s:EvalVisual() range call <SID>Eval(function("<SID>YankVisual")) endfunc function! s:YankVisual() silent normal `<v`>y endfunc function! s:YankNormal() silent normal yy endfunc
調べた事/やり残した事
現在までの道のり
こちらの方法の利点、「実行結果を覚えていてくれる」は確かに便利なのだが、未知のメソッドの挙動を観察しているときには、それまでの実行結果をリセットしたくなる事がある。ところが、Ruby には一旦定義したローカル変数を未定義にする事が出来ないらしい。
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-dev/2522
*1
定義されているものに、片っ端から nil を代入しようかとも思ったが、未定義の変数と nil を持った変数はやっぱり違う。
元のスクリプトを眺めると eval の第2引数に __vim_bind という変数を渡している。eval なんか普段使わないんで binding というものも知らなかったのだが、調べてみるとこいつをいじくれば色々な事がなかった事に出来そうだ。最初は toplevel で作った binding を dup したり clone したりして代入しなおしてみたのだが、思ったような結果にならない。
次にこの文脈を保存しておくためだけのクラスを作って(文脈をクラス内に封入する)、文脈を捨てるには、そのクラスとクラスから生成されたインスタンスを破棄すればいいんじゃないかと考え、以下のように試してみた。
# このコードが Create_ruby_binding()の度に呼ばれる。 ContextInformation = Class.new class ContextInformation def initialize @binding = binding end def context_inf @binding end end ruby_box = ContextInformation.new
すると、文脈を捨て変数を未定義に戻す事が出来た。のだが、この方法だと、編集中のコードの見かけ上の toplevel はこのクラスのインスタンスという事になる。大抵の場合困らないのだが、このインスタンスには include メソッドが定義されていないので、toplevel では include が使えない事になる。
http://www.ruby-lang.org/ja/man/html/Ruby_CDD1B8ECBDB8.html#main
それほど困らないとはいえなんだかしゃくなので、include をどうにかして委譲できないかと色々試して挫折。上記用語集の main の項目を眺めていたら
単なるObjectクラスのインスタンスである
のなら、そのインスタンスを複製して、その複製に binding を生成する特異メソッドをつけてやればいいんじゃね? と思いついて、書かれたのが上のもの。
積み残し
あわわ。4/12に id:tanakaBox コメントをもらってたようです。お知らせメールに今頃気付きました。削除などした覚えはないのですが…。
内容は以下の通り。
tanakaBox
『こんにちは、「実行結果をプレビュウウィンドウに」ってやつですが、redirを使うと、変数に保存出来ます。http://d.hatena.ne.jp/tanakaBox/20080331/1206927370
のReadirect関数使えば何とかなるかなぁと思います。Scheme用ですけど、参考にしてください。』
わざわざ、どうもありがとうございます。
やっぱこういう方法がありますよね。というか、:redir 以前使った覚えがあるなぁ…。
と、いうわけで、以下に記述された方法を使うべき場面は、あまりないと思われます。
あと、実行結果をプレビュウウィンドウに表示させたかったのだけど、挫折。
例えば :ls の結果をバッファに流し込むのってどうやるんだろう。可能なんだろうか? 多分方法はあるのだろうけど、例によって:h の迷宮で屍となる。
Vim 側のいじり方が分からないので、Ruby 側をいじって解決しようと試みた。
Ruby の p, puts, print などのメソッドの出力先は $stdout であり、この変数に write という名前のメソッドを持ったオブジェクトを代入してやれば、出力先はそのオブジェクトになるはずである。
http://www.ruby-lang.org/ja/man/html/_C1C8A4DFB9FEA4DFCAD1BFF4.html#a.24defout
というわけで、Vim 側で定義されている Vim のバッファを表すクラス VIM::Buffer に以下のように write メソッドを加えた。
ruby << EOR class VIM::Buffer def write( str ) self.append(self.length, str) end end EOR
そして、カレントバッファのインスタンスを $stdout に代入してみた。すると、puts, print メソッドの出力結果は、余計な改行が1つずつ付いているものの、カレントバッファに出力する事が出来た。ただ p メソッドの出力だけは、カレントバッファにではなくコマンドラインに出力されてしまう。なぜ p だけ…? といったところで、今回はあきらめる。
*1:日付が10年前のものだからもしかしたら状況は変わっているのかもしれないが、もしあるとしてもその方法を見つけられなかった。