2011-12-12 [長年日記]

_ readline についてそろそろ一言いっておくか #ruby #advent11rb

これは Ruby Advent Calendar 2011 の 12 日目のエントリです。昨日は @ssig33 さんでした。明日は @u16suzu さんです。

一昨年はメソッド一つが private じゃないという細かい話、去年はマイクロ秒単位の細かい話をしたのですが、今年は空白一つが余計という細かい話です。

お客様の中に、うっかり readline.so が GNU readline 6.2 なんかとリンクされちゃってて、irb で補完候補が絞り込めた時に、単語の後ろに空白が一つ追加されて、イラッとしておられる方はいらっしゃいませんか?

具体的には、

>> Read[TAB]
>> Readline ::VER[TAB]
NameError: uninitialized constant VER
	from (irb):1

という感じです。何が起こっているかというと、Read までの入力で補完候補が Readline に絞り込めたので、続けて ::VER[TAB] と入力したかったところが、Readline の後ろに空白が一つ入ってしまったために VERSION が補完できず、勢い余って Readline というメソッドにトップレベルの VER という定数を渡そうとしたことにされて、VER が見つからず NameError になっているのです。イラッとしますね。空白が追加されなければ、

>> Read[TAB]
>> Readline::VER[TAB]
>> Readline::VERSION
"6.2"

となるはずだったのに。:: ではなく . によるメソッド呼び出しなら空白が入っても何とか実行できますが、メソッド名の補完やメソッドチェーンの入力に支障が出ます。

この空白はどうして入ってしまうのでしょう?答えは、Readline.completion_append_character という長いメソッド名で参照できる値が関連しています。irb では IRB::InputCompletor::CompletionProc という Proc オブジェクトで補完時の挙動を決めているのですが、Readline.completion_append_character は nil であるということを想定しています。コマンドにオプションを付けて実行するのが基本のインタラクティブなシェルとは違って、irb ではオブジェクトをレシーバにしてメソッドを連鎖的に呼び出すのですから、補完後にはその単語の直後にカーソルが来て欲しいですよね。

ところが、GNU readline 6.2 では、Readline.completion_append_character の値がデフォルトで " " になっていて、どんな値にセットしたとしても、補完操作(通常は TAB キーを押す)の後には勝手に " " に戻るという挙動をするようになっています。Readline.completion_append_character の値は Readline.completion_append_character= メソッドで変更出来るのですが、一度でも TAB キーを押してしまうと " " に戻ってしまうんです。

>> Readline.completion_append_character
=> " "
>> Readline.completion_append_character = nil
=> nil
(TAB を押さないようにすると……)
>> Readline.completion_append_character
=> nil
(セットは出来ていそうだけど、一度でも TAB キーを押してしまうと……)
>> Readline.completion_append_character
=> " "
(元に戻ってしまう)

これではあまりにも使いにくいので、元の挙動と同じような動作にしてもらうべく Feature #4635 として報告していて、Ruby の readline.so のメンテナ @takaokouji さんが修正作業に取り組んでおられます。

[追記]: なかださんがシンプルにしてくれたパッチが r34109 にて取り込まれました。たかおさん、なかださん、ありがとうございます。クリスマスを待たずして、なんというプレゼント。もう以下のコード片は不要です。そう、trunk 使ってるならね。

irb を常用しているので非常に困っていたのですが、最近になってやっと解決策を見つけたので、紹介しておきます。~/.irbrc などに、以下のコードを貼っておけば、少なくとも 補完後に単語の直後にカーソルを持ってくることが出来ます。

require "irb/completion"
Readline.completion_proc = ->(input) do
  Readline.completion_append_character = nil
  IRB::InputCompletor::CompletionProc.(input)
end

irb/completion を呼んだ上で、Readline.completion_proc を指定して、その proc の中で Readline.completion_append_character を nil にセットして、irb の補完の本体である IRB::InputCompletor::CompletionProc を呼び出すというものです。ずいぶんと効率は悪いですが、空白一つに作業効率を削られてしまうよりはずいぶんましですよね。

まだもうちょっと書きたいこともあるのですが、時間も余白もなくなって来たので、続きはチケットにしたためることにします。お楽しみに。


I wish you Merry Christmas & Happy New Year!


«前の日記(2011-12-05) 最新