tmuxの文字幅がおかしい問題をクリアするpatchたち(一応charmapの続きなどの続き)

この記事の前の記事

charmapの続きなど - コンピュータ技術者になるための備忘録
これの最後に書いたtmuxのutf8に関することの続きのつもりです
w3mで一部のテーブルタグを表示した時に崩れてしまうので直します…

この記事の内容

tmuxで表示に関する文字幅がおかしいのとコピーモードの時のカーソル移動の文字幅を修正します

この記事に関するpatch

追記_2013/03/29

1.8以降のpatchはhttps://github.com/silenvx/PKGBUILD/tree/master/tmuxにおいてるかもしれない

追記3、さりげなく上2つのpatchをtmux1.7用に更新
追記4_2012/10/18_00時頃、それぞれのpatchの最後に改行コード(0x0a)がなかったのを修正しました
追記5_2012/10/22_09時頃、wcwidthが-1を返した場合にフリーズして使用メモリがどんどん増えて行かないようにwcwidthが-1を返した場合にwidthは1と返すように修正しました

tmuxの表示幅にwcwidthを使うpatch
https://gist.github.com/3895445

コピーモードの時に幅が2以上の文字を扱う時の挙動をいじったpatch
具体的にいうとカーソル移動の時に半角1文字ずつ動くのではなく1文字ずつ動くように修正。他に選択の場合もいじりました

追記1、1つ前の動作で左右に動かしていない時にカーソルを移動させても見た目が動いていないバグを修正したはず…。
追記2、左にカーソルを動かすときの処理がおかしかったので修正しました。ちゃんとデバッグすればよかった

https://gist.github.com/3895438

ついでに前回書いたborderのpatchもあげとく
正直、これは.tmux.confでいじれるように改変した方がいいと思ったので今は必要ないので気が向いたら書く
https://gist.github.com/3895468

以下、patchを作った時の内容。読む必要はあまりない

表示に関する文字幅を修正する方法

※この記事はtmux1.6を想定して書いています
これは前置きです

今回修正する必要がある部分はtmuxのソースファイルの

    tmux-1.6/utf8.c

ここにあるファイルです
この問題はtmuxが独自の文字幅が2になる文字を判定するテーブルを持っていることが原因だと推測できます
その証拠に前の記事のさらに前の記事で作ったcharmapのWIDTHからEND WIDTHに書いてあるデータをそのままこのテーブルに移植すると正常に動きます

※重複したデータを入れてしまうとtmuxが起動しないみたいなので注意

そういうわけでここを直せばいいわけです
ちなみにそのテーブルはソースファイル内の

    struct utf8_width_entry utf8_width_table[] = {

この変数です

ここからが本番です

別にこれでこの問題はクリアしたので終わってもいいのですがcharmapをいじった時にまた再コンパイルするなんて面倒すぎるのでwcwidth()を使うように改変します
そういうわけで今回は

    struct utf8_width_entry

これに依存しているものをwcwidth()を使うように改変、そしてこの変数のために存在している関数や変数を消して消した関数を呼び出しているところも消す作業をするだけです
複数のファイルをいじることになりますし、長いのでgistにpatchをあげることにしました
上の方に書いたpatchと同じです
https://gist.github.com/3895445
これです
ほぼ消すだけだったので楽でした

コピーモードの時のカーソル移動の文字幅を修正する方法

これは前回のノウハウから横に移動する時に文字の半角、全角関わらず内部データ的に1個ずつ移動しているためだと思うので文字幅にあわせて、移動するようにいじります
コピーモードの時の方向キーの左右やhやlでのカーソルの移動は

左の場合
    void window_copy_cursor_left(struct window_pane *);

右の場合
    void window_copy_cursor_right(struct window_pane *);

のようで、その中で

    void window_copy_update_cursor(struct window_pane *, u_int, u_int);

この関数に位置情報を送って更新しているみたいでした
なのでカーソル位置の文字が全角か半角かを判定して全角ならばその分飛べばよさそうです
出力した文字の取得はコピーモードのコピーする箇所を改変して使えばよさそうなのでその関数を探したところ

    void window_copy_copy_selection(struct window_pane *, int);

これで位置情報を生成して

    void window_copy_copy_line(struct window_pane *, char **, size_t *, u_int, u_int, u_int);

これでいじっているようでした
この中で文字の取得をしていそうなのは

    gc = grid_peek_cell(gd, i, sy);
と
    gu = grid_peek_utf8(gd, i, sy);

iがx、syがyのように思えます
どうやらutf8かそうでないかで取得の仕方を変えてるみたいです
これらのsyはgird_peek_(cell|utf8)に送るのは絶対値で、window_copy_cursor_(left|right)で扱っているのは相対値です
window_copy_copy_selectionの

    screen_hsize(data->backing) + data->cy - data->oy;

この演算で最新の絶対値を取得しているのでこれまでに手に入れた情報を統合してpatchを作りますがコピーをしようと選択を始める場合に不具合が起きることがあります

    012345678
    aあいbうc

1行目の0から8の数値はカーソル位置だと思ってください
例えば全角の「あ」は1と2のカーソル位置でできていて
tmuxのコピーモードでは1の位置が選択範囲に含まれていないとコピーされない
つまり2の位置だけだと「あ」は選択されないのでこれも修正します
カーソルを動かす関数全てに修正を施すのは骨が折れるし必要ないと思うので
選択開始時と選択終了時に修正するようにします
文字が入っていないところは
tmux-1.6/grid.cの

    const struct grid_cell grid_default_cell = { 0, 0, 8, 8, ' ' };

これで定義されているみたいです
文字は' 'です

ここから補正が必要かどうかは

    hitしたutf8のwidth > 文字がgrid_default_cellと同じの間、utf8がhitするまで左に移動した回数

が成り立つかどうかを見れば良さそうです

ちなみに

選択開始時
    void window_copy_start_selection(struct window_pane *);

選択終了時
    void window_copy_copy_selection(struct window_pane *, int);

です

そういうわけで補正したpatchはこちら(上の方に書いたpatchと同じです)
https://gist.github.com/3895438

後書き

これでこれまで不満に思っていたtmuxの不具合は直ったかな…
使ってて特に不具合が出なければ本家にpatchを送ってみることを検討してみますがそれはきちんとした英語が書けるようになってからの話かな…
logの出し方とか調べずにソースコードを読んでデバッグしてたからだいぶtmuxのソースが読めるようになった気がする