Vim 上で Ruby を動かす2つの方法
Vim 上で編集中の Ruby コードの実行結果を Vim に表示させたいとき、大雑把に分けて2つの方法がある。
1つは先日取り上げたtips : vim onlineによる方法*1。
もう1つはVim上でRubyを動かしたい。 - ボクノスで公開されている方法。
前者は Vim の外部コマンドを呼び出す機能を使ってシェル経由で Ruby を呼び出し、その結果を Vim 上で表示する*2。後者は Vim に組み込まれた Ruby を使っている。
上で挙げた 2つのスクリプトはどちらも便利なものなのだけど、ちょっとした不満が出てきたので、改造してみた。せっかくなので公開しておく。
シェル経由で Vim 上のスクリプトを実行する - 右往左往
Vim の組込み Ruby に編集中のコードを実行させる - 右往左往
- 環境
- Windows XP
- Vim version 7.1.145 kaoriya 版
- Ruby ruby 1.8.5 (2006-12-25 patchlevel 12) [i386-mswin32]
シェル経由で Vim 上のスクリプトを実行する
tips : vim online 経由地:VimでRubyスクリプトの実行と結果のプレビュー表示 - ナレッジエース
- 素敵なところ
- 残念なところ
- テンポラリファイルをもりもり作って、バッファ番号ももりもり増えていく。
- 裏バッファがテンポラリファイルに置き換わり、
したとき悲しい気持ちになる。
というわけで、テンポラリファイルを特定の1ファイルに固定して、そのファイルが裏バッファになるべく現れないようにした。
"編集中の .rb ファイルを実行。 "http://www.vim.org/tips/tip.php?tip_id=1244 function! s:Ruby_eval_split() range " プレビューウィンドウの高さ調整。不要なら後処理ともども削除。 let save_pwh = &previewheight let &previewheight = 8 "以下自分の環境に合わせて。 let src = 'ここに任意のファイルのフルパスを書く。' let dst = "RubyOutput" let backBuffer = "" if bufexists(0) let backBuffer = bufname("#") endif " 裏バッファが無いときにこのスクリプトを実行すると、裏バッファが src にな " る。その状態で n_ctrl-^ すると、src が buffer list に現れてしまう " (unlisted-buffer ではなくなってしまう)。以下の処理で src を buffer list " から消している。 if buflisted(src) let bufNum = bufnr(src) execute ":bd " . bufNum endif " put current buffer's content in a temp file silent execute ":" . a:firstline . "," . a:lastline . "w! " . src "↑の処理をした時点で src が裏バッファの内容となる。 let tempFileName = bufname("#") " open the preview window silent execute ":pedit! " . dst " change to preview window wincmd P " set options setlocal buftype=nofile setlocal noswapfile setlocal nobuflisted setlocal syntax=none setlocal bufhidden=delete " replace current buffer with ruby's output silent execute ":%! ruby " . '"' . src . '"' . " 2>&1 " " change back to the source buffer wincmd p " 後処理 " preview window の高さの値を以前のものに戻す。 let &previewheight = save_pwh " 裏バッファを元々持っていたものに戻す。 " 裏バッファを持っていなかった(または無名のバッファだった)時、裏バッファが " テンポラリファイルだった場合は何もしない。 if (tempFileName != backBuffer) && strlen(backBuffer) let currentBuffer = bufname("%") execute ":b " . backBuffer execute ":b " . currentBuffer endif endfunction vmap <buffer><silent> <F7> :call <SID>Ruby_eval_split()<cr> nmap <buffer><silent> <F7> mzggVG<F7>`z imap <buffer><silent> <F7> <ESC><F7>a map <buffer><silent> <S-F7> :pc<CR>
使い方
$home\vimfiles\ftplugin\ruby.vim(Windows の場合)に上記コードを追加。
7行目にある
let src = 'ここに任意のファイルのフルパスを書く。'
シングルクウォートの中身を、テンポラリファイルにしたいファイルの名前(フルパス)に書き換える。(うちでは$home\vimfiles\ruby_tmp_file として、 $home を展開した形で書いている。)
操作の詳細はキーマップを眺めれば分かるかと。
*1:シェルで実行してるし。
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年前のものだからもしかしたら状況は変わっているのかもしれないが、もしあるとしてもその方法を見つけられなかった。