vim の ruby インタフェースをうまいこと使って、ちょっとした計算なりなんなりを vim のバッファ上でやってしまいましょう、というお話。
面倒くさいのでいきなり本題に入ると、こういうスクリプトをおもむろに .vimrc に書くと。
nmap <Space> [Space]
xmap <Space> [Space]
if has('ruby')
nnoremap <silent> [Space]r :set operatorfunc=Ruby<CR>g@
nnoremap <silent> [Space]rr :call RubyLines()<CR>
nmap [Space]R [Space]r$
nnoremap <silent> [Space]rp :<C-u>call RubyPaste()<CR>
xnoremap <silent> [Space]r :<C-u>call Ruby(visualmode(), 1)<CR>
xnoremap <silent> [Space]R :<C-u>call Ruby('V', 1)<CR>
command! -range Ruby :<line1>,<line2>call RubyLines()
command! -range RubyPaste :<line1>,<line2>call RubyPaste()
let s:ruby_buffer = ""
class VimRuby
@@binding = binding
def VimRuby.evaluate(code)
result = eval(code, @@binding)
if result.instance_of?(String)
str = result.gsub(/"/, '\\"')
VIM.command(%(let s:ruby_buffer = "#{str}" . "\n"))
else
str = result.inspect.gsub(/"/, '\\"')
VIM.command(%(let s:ruby_buffer = '#{str}' . "\n"))
end
return result
end
end
function! RubyEval(code)
ruby p VimRuby.evaluate(VIM.evaluate("a:code"))
endfunction
function! Ruby(type, ...)
let saved_sel = &selection
let &selection = "inclusive"
let saved_reg = @"
if a:0
silent exe "normal! `<" . a:type . "`>y"
elseif a:type == 'line'
silent exe "normal! '[V']y"
elseif a:type == 'block'
silent exe "normal! `[\<C-v>`]y"
else
silent exe "normal! `[v`]y"
endif
call RubyEval(@")
let &selection = saved_sel
let @" = saved_reg
endfunction
function! RubyLines() range
let lines = getline(a:firstline, a:lastline)
let code = join(lines, "\n")
call RubyEval(code)
endfunction
function! RubyPaste()
let saved_reg = @"
let @" = s:ruby_buffer
normal! p
let @" = saved_reg
endfunction
endif
でもって、こんな vim のバッファがあったとしよう。
a = 1
b = 2
a + b
ここで、<Space>rr で1行がrubyのコードとして実行されるので、1行目を実行すると「1」、2行目を実行すると「2」、3行目を実行すると「3」という結果がコマンドラインに表示される。3行同時に実行したければ、1行目で 3<Space>rr や v でビジュアルに入って <Space>R するなりして実行すれば良い。コードはまるごと ruby の eval() に入るので、最後の式の値が評価される。そして、最後の実行結果は <Space>rp で好きな場所にペーストできる。
もしも最終結果が文字列であれば、文字列としてペーストできる。
[1, 2, 3, 4, 5].join("\n")
この式を <Space>rr で評価すると、コマンドラインには「"1\n2\n3\n4\n5"」と表示されるが、<Space>rp でペーストすると、1, 2, 3, 4, 5 という行が挿入される。
モーションやテキストオブジェクトも使えるので、[] の中で <Space>ra[ などのコマンドも確かめてみると良い。
で、私が元々困っていたシチュエーションはこうだ。ネットワークの転送レートを計測した以下のようなデータがあった。
286.62/230.49
292.82/240.64
297.05/234.34
281.48/221.32
286.97/240.80
250.33/211.16
284.06/269.95
278.59/240.34
286.34/233.32
285.81/228.98
1つめが送信のレート、2つめが受信のレートで、10回試行している。そして送信受信それぞれの平均値を求めてテキストに加えたい。これが今回のシステムを使うと以下のようにすると計算ができる。
str = <<EOF
286.62/230.49
292.82/240.64
297.05/234.34
281.48/221.32
286.97/240.80
250.33/211.16
284.06/269.95
278.59/240.34
286.34/233.32
285.81/228.98
EOF
str.split("\n").
map {|i| i.split("/").map {|i| i.to_f}}.
inject([0, 0]) {|r, i| [r[0] + i[0], r[1] + i[1]]}.
map {|i| i / 10.0}.
join("/")
「283.007/235.134」という結果が得られて、最後に<Space>rp してペーストすれば、完了と。らくちんなのである。