readline覚書き

こないだ
complete
にて書いたpythonのrlcompleter.pyのattr_matches()で複数エントリが出てしまうの件、Linux側では普通に出るので不可思議だったのですが、GNU readline 5.2では重複がある場合は勝手にuniqされてしまうみたいですね。Leopard(というかMacOSX)に付属のReadlineがめっさ変、というだけだたのか*1

ついでに、readlineってとっても簡単だったので使い方を書いておきます*2

基本

まず、ただ使うだけなら本当に簡単。readline()を呼ぶだけ。

#include <readline/readline.h>
#include <readline/history.h>
#include <string.h>

main()
{
  char* buf;

  while(buf = readline("> ")) {
    // <なにかやる>
    if (buf && *buf) add_history(buf);
  }
}

こんなんで行編集+ヒストリ+デフォルトのファイル名/ユーザ名補完*3までできます。

カスタム補完

カスタム補完をするには、まず main でグローバル変数 rl_attempted_completion_functionにコールバック関数を設定します。

char** on_complete(const char* text, int start, int end);

main()
{
  char* buf;
  rl_attempted_completion_function = on_complete; //追加
  while(buf = readline("> ")) {
    printf("processing your input <<%s>>\n", buf);
    //    <なにかやる>
    if (buf && *buf) add_history(buf);
  }
}

んでコールバックを実装。コールバックの戻り値はマッチする全文字列のリストなので char** です。

char* word_generator(const char* text, int state); // Generator

char** on_complete(const char* text, int start, int end) 
{
  if (start==0) { //先頭の文字列だけ
    return rl_completion_matches(text, word_generator);
  }
  return NULL;
}

コールバック関数には編集文字列のポインタと共に、補完対象の文字列範囲が渡されます。
今回は面倒なのでわかりやすいように先頭の文字列補完だけ反応することにしました。

rl_completion_matches()にgenerator関数を指定して実行すると、textにマッチする文字列リストを作ってくれるので、これを利用しています。
# NULL返すとデフォルトのファイル名/ユーザ名補完が動くみたい。

generator関数はcompletion_matches()から一回呼ばれるごとに「一つずつ」マッチし得る補完候補を返します。pythonのgeneratorの書き方(yield)とおんなじような感じ。

char* words[]= {
  "monday",
  "tuesday",
  "wednesday",
  "thursday",
  "friday",
  "saturday",
  "sunday",
  "months",
  "minites",
  "seconds", 
  NULL,
};

char* word_generator(const char* text, int state)
{
  static int index, wordlen;
  char*  name;

  if (state == 0) {  // state = 0は初出の単語なので初期化
    wordlen = strlen(text);
    index = 0;
  }
  while (name = words[index]) {
    index++;     //[*]
    if (!strncmp(text, name, wordlen)) {
      return strdup(name);  // dup必要
    }
  }
  return NULL;
}

てな感じで単に候補を一個ずつ返すだけ。あまりに簡単で涙が。。。
words[]にたとえば

  "multiple",
  "multiple",
  "multiple",
  "multiple",

見たいなのを足してみると、

> [TAB][TAB]
friday     monday    [multiple]  seconds    thursday   wednesday  
minites    months     saturday   sunday     tuesday    

確かにmultipleがuniqされてる!
ということみたい。Leopardのreadlineでやると複数出るんだろうな*4

追記

rl_attempted_completion_function が rl_attempt_completion_functionと間違えてましたm_o_m。

あとLeopardのreadlineの素性は

/*      $NetBSD: readline.h,v 1.18 2006/08/21 12:45:30 christos Exp $   */
#define RL_READLINE_VERSION             0x0402

となってました。

予想通り、この実装では uniqされずに

friday     minites    monday     months     multiple   multiple   multiple   
multiple   saturday   seconds    sunday     thursday   tuesday    wednesday  
multiple   

のように出てました。ちなみにこのバージョンでコンパイルするには s/rl_completion_matches/completion_matches/する必要がありました。

*1:今手元にないのでLeopardのReadlineの家柄など詳細は不明っす

*2:使わず嫌いしてないでもっと早く試してみればよかった

*3:": complete"などが設定されている必要はあります。普通はデフォルトでそうなってるかな?

*4:ちなみにリスト中のコメントに [*] とあるのはこれの位置を間違えてstrncmp()の後にしてしまったので 注意 のつもりで書いたのですが、後から思った。こんなあほなミスするのオラくらいだべ T^T