Macのrubyのreadlineに不具合がある!?(6)

私のパッチを ruby-dev に報告したあと、以下のことが ruby-dev([ruby-dev:35532]、[ruby-dev:35534]) で挙がった。

  • libedit の動作(history_base - 1)がバグなのか。バグなら将来のバージョンで修正されるのでは?
  • libedit において、 history_get や remove_historyhistory_base - 1 で正しく動作するのか?

ということで、早速、GNU Readline と libedit の動作を確認した。
以下のコードを作成し、GNU Readline、 Mac OSXの libedit、最新の libedit で動作を確認した。

#include <stdio.h>

#ifdef HAVE_EDITLINE_READLINE_H
#include <editline/readline.h>
#else
#include <readline/readline.h>
#include <readline/history.h>
#endif /* HAVE_EDITLINE_READLINE_H */

int main(int argc, char **argv)
{
  HIST_ENTRY *entry;
  int i;

  using_history();

  printf("history_base: %d\n", history_base);

  add_history("1");
  add_history("2");
  add_history("3");

  printf("added 1, 2, 3 to history\n");
  printf("history_base: %d\n", history_base);
  printf("history_length: %d\n", history_length);

  entry = history_get(history_base);
  printf("history_get(history_base): %s\n", entry->line);
  
  for (i = 0; i <= history_length; i++) {
    entry = history_get(i);
    printf("history_get(%d): %s\n", i, entry ? entry->line : "NULL");
  }
  
  return 0;
}

以下、実行結果。

---- no212_on-macosx.gnu_readline ----
history_base: 1
added 1, 2, 3 to history
history_base: 1
history_length: 3
history_get(history_base): 1
history_get(0): NULL
history_get(1): 1
history_get(2): 2
history_get(3): 3
---- no212_on-macosx.editline ----
history_base: 1
added 1, 2, 3 to history
history_base: 1
history_length: 3
history_get(history_base): 2
history_get(0): 1
history_get(1): 2
history_get(2): 3
history_get(3): NULL
---- no212_on-macosx.editline_20080712-2.11 ----
history_base: 1
added 1, 2, 3 to history
history_base: 1
history_length: 3
history_get(history_base): 2
history_get(0): 1
history_get(1): 2
history_get(2): 3
history_get(3): NULL

上記から次のことが分かった。

  • GNU Readline と libedit の history_base の値は 1 であるため等しい。
  • GNU Readline はヒストリのインデックスが 1 から始まる。history_base が 1 なので history_base から始まるってことなのかも。
  • libedit は 0 からヒストリのインデックスが 0 から始まる。history_base とは関係ない。というか GNU Readline と非互換だ。
  • 1番目のヒストリを取得するには GNU Readline は 1、libedit は 0 を指定する必要がある。

ちなみに history_base のコメントには 「probably never subject to change」とあるので、history_base は定数ではないのですが、 1 が固定で指定されていることを想定しても良いと考えています。

libedit になんでこんな非互換が入っているのか不明ですが、回避する必要がありそうです。また、今更 libedit の挙動は変わらないような気がします。

ということで、Ruby の Readline モジュール(というか Readline::HISTORY) は以下の方針で修正してみます。

  • Readlineのコンパイル時には特別なことはしない。
  • Readlineの初期化時に GNU Readline とリンクしていると判断した場合、history_offset を history_base に設定する。そうでない場合、history_offset を 0 に設定する。
  • ヒストリの取得や削除などのメソッドでは、 history_offset を考慮する。

パッチはあとで。