かなりすごいブログ

vimprocでRubyでプロセス通信・ソケット通信しよう

Vim Advent Calendar 2012 123日目の記事になります。昨日は私のVim script家庭教師ことthincaさんの「Vim を起動したときに Vim Girl に会いたい!」でした。Vim Girlかわいい。

さて風邪で1週間くらい寝込んでいたのですが、布団にこもってなんとかVimで快適に画像や動画などのメディアを表示できないか考えていました。それに対する一つの提案としてあるプラグインを開発しています。残念ながら本日リリースは間に合いませんでしたが、実装にあたって色々と勉強になったので、Tipsを書いておきます。本来Advent Calendarってそういうものですし!問題ない!

Vim scriptと外部プログラムとでソケット通信したい

前述の開発中プラグインでは、Vimから独立したソフトウェアを起動・操作するという仕組みを導入しています。今回は例としてvimprocを用いRubyと連携する簡単なプログラムを書いてみます。

socket.vim & socket.rb

let g:socket_test_ruby_port = 12345
let g:socket_test_ruby_path = 'ruby'
let g:socket_test_ruby_script_path = './socket.rb'

function! g:socket_test_send(command)
  let sock = vimproc#socket_open('localhost', g:socket_test_ruby_port)
  call sock.write(a:command)
  call sock.close()
endfunction

function! g:socket_test_start()
  if !g:socket_test_is_run()
    let g:socket_test_pid = vimproc#popen2(join([g:socket_test_ruby_path, g:socket_test_ruby_script_path, g:socket_test_ruby_port], ' '))['pid']
  endif
endfunction

function! g:socket_test_stop()
  if g:socket_test_is_run()
    call vimproc#kill(g:socket_test_pid, 3)
  endif
endfunction

function! g:socket_test_is_run()
  if exists('g:socket_test_pid')
    return vimproc#kill(g:socket_test_pid, 0) == 0
  else
    return 0
  endif
endfunction

require 'socket'
require 'thread'

Signal.trap(:INT){ exit(0) }
Signal.trap(:QUIT){ exit(0) }

gs = TCPServer.open(ARGV[0])
addr = gs.addr
addr.shift
printf("server is on %s\n", addr.join(":"))
printf("my pid is on %s\n", $$)

loop do
  Thread.start(gs.accept) do |s|
    puts s.gets
    s.close
  end
end

 

解説

まず、start関数ではvimproc#popen2関数を使いプロセスを開始しています。popen2はプロセスオブジェクトを返してくれ、そのメンバpidにはその名の通り起動したプロセスのIDが格納されています。start関数ではこうして取得したpidをスクリプトローカル変数として保存します。popen2関数には任意のコマンドを渡すことができます。今回の例では’ruby ./socket.rb 12345′というコマンドが渡されます。

次にstop関数では、先ほど取得したpidのプロセスがまだ終了していない場合、プロセスを終了させます。ここではvimproc#kill関数を使用します。kill関数では、任意のpidを持つプロセスにシグナルを送信することができます。ここではプロセスを終了させたいので、SIGQUITを意味する3をシグナルとしてpidに渡しています。

また、start関数とstop関数で使用しているis_run関数についても説明します。is_run関数はstartによって保存されたpidを持つプロセスがまだ終了していないかどうかの判定を行います。先ほど説明したvimproc#kill関数では、のシグナル部分に0を指定することで生死確認を行うことができます。返り値が0の場合はプロセスが生きており、1の場合は終了してしまっています。

最後に、send関数について説明します。この関数はvimproc#socket_open関数を使用しソケット通信を用いてrubyスクリプトに文字列(a:command)を渡しています。vimproc#socket_openの第二引数はportになります。start関数ではs:portをrubyスクリプトに渡しており、rubyスクリプトは指定されたportでsocket通信の待機を行います。

rubyスクリプトも簡単に解説しておきます。このrubyスクリプトは、引数としてソケット通信の待機portを受け取り、ソケット通信のlistenを開始します。Vim script側から通信があるたびに、それを文字列として表示させています。

また、Signal.trapを用い指定のSIGNALが送られた時の処理を定義しています。vim scriptのstop関数から送られるSIGQUIT命令を受け取ったらプログラムを終了する、といった形ですね。

このように、vimprocを使用することでとても簡単にプロセスのハンドリングやソケット通信を行うことができます。Vimと外部ソフトウェアとの連携によってより高度な表現が可能になると思いますので、みなさんもぜひ活用してみてください。

Vim Advent Calendar 2012 124日目

Vim Advent Calendar 2012、明日124日目は@cohamaさんです…!