mutt-1.2 (mutt-1.2.5.1i) | 2003-01-05 19:51 |
mutt-1.4 (mutt-1.4.2.1i-ja.1) | 2004-02-14 09:42 |
mutt-1.5 (mutt-1.5.23) | 2014-07-10 16:14 |
mutt-1.6 (mutt-1.6.1-ja.1) | 2016-08-22 17:11 |
mutt-ja (ja-mutt-1.11.4) | 2019-04-28 20:53 |
mutt-ja-package (FreeBSD9 pkg) | 2014-08-09 20:14 |
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の方に渡されている。