ruby-talk:308431に回答する
内容の理解
Subject: Readline and conditional tab results based on input From: Marc Heiler <shevegen linuxmail.org> Date: Fri, 18 Jul 2008 01:44:26 +0900
最初に「cd」を入力してから TAB を押すとファイルやディレクトリのパスを補完するが、不正な文字 (例えば「blabalblabla」) だと補完しないようにしたいとのこと。
このときの方法を知りたいようだ。
回答のひとつに、方法としては Readline.completer_word_break_characters にスペースを含めないようにすれば cd を含め、全ての入力が complete_proc のブロックパラメータとして渡される。というものがある。[ruby-talk:308526]
現在の Readline ではこのような力技を使うしかない。GNU Readline の API のようなメソッドを用意してあげたい。
補完の前の文字列を取ることをする patch が RubyForge にあった気がする。確認する。
以下がそうだ。
http://rubyforge.org/tracker/index.php?func=detail&aid=3212&group_id=426&atid=1698
パッチは見つけた。あとはこれをどうやって適用するか。
まずは、Readline の他のメソッドのテストを書こう。
とりあえずの回答のメールを送る
あと、質問の回答のメールを書いておこう。
私は Marc Heiler の要求を満たせるようなメソッドを追加したいと考えている。 I want to add some methods that satisfy the Marc Heiler's demands. RubyForge に投稿されたパッチを取り込む予定です。 The patch contributed to the RubyForge is taken. [#3212] Readline does not provide enough context to the completion_proc http://rubyforge.org/tracker/index.php?func=detail&aid=3212&group_id=426&atid=1698 まずは、 ruby 1.9 に取り込みます。その後、 1.8 系にバックポートする予定です。 First of all, I will take it into ruby 1.9. Afterwards, backport to 1.8.
「パッチの適用」準備
メールも書いたし、「パッチを適用する」準備をする。
まずは、既存のメソッドのテストを記述する。
テストは RubySpec を参考にした方が良いだろうね。と思って、RubySpec を眺めてみたけど、自動生成されたままのものが多くて参考にならない。自分で考えることにする。
テストを書いてコミットした。
- r18489
- r18491
パッチの適用
ようやくパッチを適用します。
前田さんと相談し、補完の実装を助けるようにするという方針はいいのだが、API の名前をよく考えてください、と言われています。
パッチでは以下のメソッドを追加している。
- line_buffer
- 入力している 1 行
- match_start
- 補完の対象の単語の開始インデックス
- match_end
- 補完の対象の単語の終了インデックス。現在のカーソルの位置と同じ。
line_buffer は GNU Readline に rl_line_buffer が存在する。採用しても問題ないと考えている。
match_start は GNU Readline では存在しません。採用は難しいでしょう。
match_end は GNU Readline では rl_end です。 completion_proc のブロックパラメータ text の長さと等しいので不要だと思います。
以上の考察から、次の Readline のクラスメソッドを追加します。
- line_buffer
- 入力している 1 行
- point
- 現在のカーソル位置
この情報を元に次のようなことを実現できます。
require "readline" Readline.completion_proc = proc { |text| cmds = Readline.line_buffer.strip.split(/\s+/) if cmds.empty? || (cmds.length == 1 && text.length > 0) ["cd"] else case cmds.first when "cd" ["/usr", "/var", "/home"].grep(/\A#{Regexp.escape(text)}/) else [] end end } while buf = Readline.readline("> ", true) p([:buf, buf]) p([:line_buffer, Readline.line_buffer]) end
ここで注意すべきなのは point を使っていないことです。point は本当に必要なんでしょうかね。
それと、match_start は text と point を使って取得できます。このサンプルを RDoc に記述しておけば match_start はなくても良いと考えています。
require "readline" Readline.completion_proc = proc { |text| start = Readline.point - text.length ... }
ということで、私の考える修正案を ruby-talk や RubyForge などに投稿して、みなさんの意見を聞くことにします。
とりあえず、今回の問題の対応は(私の中では)これで一段落しました。
しばらく様子を見て、反応がないようであればコミットします。
Ruby 1.8へのバックポートも検討します。