mutttをUTF-8環境(LANGが ja_JP.UTF-8)の所で使うと、キー入力した時、Unicodeで、幅が不定な文字、例えば「★」の表示が乱れる。幅が定まっている文字、例えば「〒」は乱れない。現象としては下記の通り。
入力された文字列をハンドリングしているのは、 enter.c内の _mutt_enter_string()である。ここの、
287 clrtoeol ();
288 move (y, x + my_wcswidth (state->wbuf + state->begin, state->curpos - state->begin));
289 }
290 mutt_refresh ();
291
292 if ((ch = km_dokey (MENU_EDITOR)) == -1)
293 {
294 rv = -1;
295 goto bye;
296 }
の、mutt_refresh()を何回か通過した時に、入力された文字列が表示される。
実際にキーが入力されるのを待ち合わせるのは、keymap.cの中にある km_dokey()の中。更にその中から、
94 #ifdef KEY_RESIZE 95 /* ncurses 4.2 sends this when the screen is resized */ 96 ch = KEY_RESIZE; 97 while (ch == KEY_RESIZE) 98 #endif /* KEY_RESIZE */ 99 ch = getch (); 100 mutt_allow_interrupt (0); 101
で、mutt_getch()が呼ばれる。ここで★(UTF8では、E2 98 85)を入力すると、まず「E2」が帰る。ちなみに、Ctrl-Gを押すと、エラーが帰るように、mutt_getch()内でハードコードされている。入力した文字は、大域変数LastKeyに格納される。その後、mapテーブルを検索した後、retry_generic()を呼び出し、結果を返す。
663 k = mbrtowc (&wc, &c, 1, &mbstate);
664 if (k == (size_t)(-2))
665 continue;
666 else if (k && k != 1)
667 {
668 memset (&mbstate, 0, sizeof (mbstate));
669 continue;
670 }
671 }
次に、マルチバイト文字(UTF-8)からワイド文字(UCS-2)への変換を行う。これは、mbrtowc()関数を呼び出すことで行う。1バイトのみの解析なので、戻り値は-2(解析できなかった)が返り、ループの先頭に戻る。もう一度mutt_getch()を呼び出し、UTF-8の2バイト目を得る。これもmbrtowc()で解析失敗するので、-2が返る。3回目のループで、UTF-8の3バイト目を得る。ここで、結果が1となり、mbrtowcの引数wcにはUCS-2の文字が返る。kが1なので、mbstateの初期化は行わない。
699 else if (wc && (wc < ' ' || IsWPrint (wc))) /* why? */
700 {
701 if (state->lastchar >= state->wbuflen)
702 {
703 state->wbuflen = state->lastchar + 20;
704 safe_realloc (&state->wbuf, state->wbuflen * sizeof (wchar_t));
705 }
706 memmove (state->wbuf + state->curpos + 1, state->wbuf + state->curpos, (state->lastchar - state->curpos) * sizeof (wchar_t));
707 state->wbuf[state->curpos++] = wc;
708 state->lastchar++;
709 }
701行目のif文が真なので、703行目に移動する。すると、lastcharに20を加算したものをwbuflenとし、そのサイズをwchar_t単位(実際はint)で割り当て、wbufのcurposからwbufのcurpos+1に、lastcharからcurposの、wchar_t単位でのバイト数を移動する。言い換えれば、wbufの先頭1要素を空ける。その後、先頭にwcをコピーし、ポインタを1つずらす。またループの先頭に戻る。
ループに戻るとmy_wcwidth()が呼ばれるところがある。ここで、bufの先頭文字の大きさが返る。UCS-2文字なので2が返る。この文字(buf内にある文字)をmy_addwch()で描画する。その後mutt_addwch()で今度はマルチバイトに変換する。bufにUTF-8文字が設定され、3が返る。
その後、my_wcswidth()が呼ばれ、2が返る。
文字の大きさを決めるのはwcwidth()だが、それは呼ばれていない様子。ncurses内にも呼び出している所はあるが、ブレークポイントを仕掛けても引っかからない。--without-wc-funcsを指定しているからか。
→嘘。ちゃんと呼んでいるところがあった。ただし、wcwidthではなくて__wcwidth。で、★だと戻りが1,〒だと2になることを確認。そこで一旦止めて、★の場合、強引に戻り値を2にしたら表示が正しくなった。これが原因か。
wcwidthの仕組みはFreeBSDのwcwidthにまとめる。
シングルバイトの場合は、1バイトずつ、マルチバイトの場合は、_mutt_enter_string()でUTF-8文字の組み立てを終え、3バイトになってから、mutt_addwch()を呼び出す。この中で、addstr()を呼び出している。addstr()を呼び出す前に引数bufの中身がどうなっているかを見ると、
★の場合 [2011-02-13 20:54:35] before_addwch w=0002 wbuf=2605 [2011-02-13 20:54:35] addstr buf=ffffffe2 ffffff98 ffffff85 0 [2011-02-13 20:54:35] before move2 y,x=35,11 [2011-02-13 20:54:36] a [2011-02-13 20:54:36] ★
〒の場合 [2011-02-13 20:59:43] before move1 y,x=35,9 [2011-02-13 20:59:43] before_addwch w=0002 wbuf=3012 [2011-02-13 20:59:43] addstr buf=ffffffe3 ffffff80 ffffff92 0 [2011-02-13 20:59:43] before move2 y,x=35,11 [2011-02-13 20:59:43] a [2011-02-13 20:59:43] 〒
で、正しく3バイトがcursesの方に渡されている。