Hatena::ブログ(Diary)

高尾宏治日記 on はてな このページをアンテナに追加 RSSフィード

2009年07月11日

Readline.refresh_lineの追加

ruby(MRI) 1.9にReadline.refresh_lineメソッドを追加しました。(r24020)これは、twitter:jugyoさんからご要望があり、twitter:koichirooさんからパッチをもらいました。

なお、このメソッドの使い方は[ruby-list:45922]から始まる一連のスレッドをみてください。ちょっと不親切ですみません。(実は、ほとんどの人は使わないメソッドのような。。。)

GNU Readlineで試すのは当然ですが、我らがMac OS XのEdit Line(libedit)でも試しました。

GNU Readlineの場合

$ ./ruby19trunk -I.ext/i386-darwin9.6.0 -Ilib -rreadline -e 'p Readline::VERSION; Readline.refresh_line'
"5.2"

Edit Lineの場合

$ ./ruby19trunk -I.ext/i386-darwin9.6.0 -Ilib -rreadline -e 'p Readline::VERSION; Readline.refresh_line'
"EditLine wrapper"
-e:1:in `refresh_line': refresh_line() function is unimplemented on this machine (NotImplementedError)
        from -e:1:in `<main>'

さすがですね。refresh_lineなんて知りません。Edit Lineにとっては当然のことですよね :-P

ということで、Readlineモジュールの新しいメソッドrefresh_lineをよろしくお願いします。

2009年02月03日

Readline.set_screen_size と Readline.get_screen_size を追加

ruby の trunk (r22013) に Readline.set_screen_size と Readline.get_screen_size を追加しました。それぞれのメソッドを以下に説明します。

Readline.set_screen_size(rows, columns)

  端末のサイズを rows で指定した行数と、columns で指定したカラム数に設定します。
  詳しくは GNU Readline の rl_set_screen_size 関数を参照してください。
  
Readline.get_screen_size -> [rows, columns]

  端末の行数とカラム数を返す。
  詳しくは GNU Readline の rl_get_screen_size 関数を参照してください。

これまでは、端末の行数やカラム数は、それぞれ ENV["LINES"] や ENV["COLUMNS"] で取得したり、Curses を使用していたと思います。これからは、Readline.get_screen_size を使って以下のように書けます。

require "readline"

rows, columns = *Readline.get_screen_size

(なんとなく) ENV よりも安心感がないですか?

次は Readline の補完処理を記述しやすくするため、TAB を押すなどにより、ユーザが補完を要求したときに、現在入力中の文字列やカーソル位置を取得できるようにしよう。GNU Readline はグローバル変数をつかっているので、RubyAPI をどのようにするか悩ましいのですよね。補完処理が終わった後に、補完した時点の入力中の文字列を操作できるといやだろうし。。。

2008年08月12日

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 にあった気がする。確認する。

以下がそうだ。

RubyForge: Ruby: Detail: 3212 Readline does not provide enough context to the completion_proc

パッチは見つけた。あとはこれをどうやって適用するか。

まずは、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へのバックポートも検討します。