平衡点


2017/02/08 [長年日記]

_ (解決) tmux で EAW二文字幅としてこの先生きのこるには

前回(平衡点(2017-02-03))「ようやくすっきりした」とか書いておきながら, 結局どうしたら良いのか書いていなかったので.

結論

お使いのターミナル次第ですが

  1. EAW 二文字幅派、一文字幅派どちらの場合においても、罫線描画に ACSC String を指定した際にこれをそのまま描くターミナル(rxvt, st, オプションで指定できる mlterm 等)の場合には、pane-border を弄る必要は無いと思われる。一方で、gnome-terminal 等の様に Unicode の BOX Drawing Char で描画するターミナルの場合にはpane-border に使われる文字を ASCII に置き換える必要がある。
  2. ロケールを弄ることができる環境(個人の計算機、Mac OS の場合にはドコを弄るのか知らないけれど)であれば、en_US.UTF-8 と ja_JP.UTF-8 のロケールにおいて EAW のレンジにある文字の幅を明示しておけば良い。一方で、ロケールを弄る事ができない場合には、tmux.c 内の setlocale(LC_CTPYE, "en_US.UTF-8")の部分を修正した上で、fumiyas/wcwidth-cjk 等で、wcwidth を上書きして tmux を実行する。
  3. フォントは適宜。二文字幅で生きる場合には、選択肢は多そう(日本語フォントの多くは EAW が二文字幅)。一文字幅で生活したいなら wacky612/ricty-custom とか Ufo by akahuku 等を使うのかな?

patch は二つ。

一つ目は pane-border 等、全て ASCII で描くパッチ。

二つ目は en_US.UTF-8 決め打ちを止めるパッチ。

一つ目のパッチは option として pane-border-ascii が追加される。初期値は 1 であり、オプションを指定しない場合には罫線描画に ACSC String が使われる。~/.tmux.conf 内で

set-option -g pane-border-ascii 0

と有効にすると、罫線描画に ASCII が使われる。

二つ目のパッチは、en_US.UTF-8 決め打ちを止めるパッチで、これを当てない場合には、カーソルの移動幅等の算出に en_US.UTF-8 が使われる。カーソル移動の際に、行中に文脈依存で幅の変わる EAW 文字種があろうが無かろうが、常に一文字幅でカーソルが移動するため、結果として悲惨な事になるだろう。

そもそも何が問題なのか

Wikipedia 先生に書いてありますが:

というわけで, Unicode の定義において *「文脈依存で幅が変わる文字」* があるわけで。(以下、EAW 文字と呼びます).

歴史的事情を汲んでくれた、と解釈すべきなのかな。とはいえ、ターミナルで生活している/していたい人(=私とか)にとっては、文字幅が時と場合によってヒョコヒョコ変わってしまって大変。

EAW 文字の取り扱い。

個々のアプリケーション毎に EAW 文字をどう扱うか対応している場合もあるけれども、その一方で、rxvt の様に「libc の wcwidth が返す値を使う」というアプリケーションもある:

数式が描かれたテキストや、過去のしがらみが無い場合には「EAW など知らん。この範囲の文字は 1 文字幅だ!」として、とくに問題は無いだろう。

  • ただし、現状の日本語フォントの多くが EAW文字 が二文字幅であることを想定して作成されているため、「半分だけ表示されるんですが...」という状況を許容するのであれば。
  • また、ロケールのデータ自体では EAW のままなので、表示と wcwidth の返す値を揃えるためには、ja_JP.UTF-8 が参照するロケールにおいて、EAW 文字の幅を "1" にしておくのが望ましいだろう。これが異なる場合にどんな祟りがあるのかは、試していないので良く知らない。

一文字幅派の利点は UTF-8-demo.txt が綺麗に表示される所かな。 あと、割と少ないのだけれど、EAW 文字を 1 文字幅としているフォントを使うのが良いだろう。例えば、

とか?

一方で、過去の慣習から EAW 文字種が二文字幅であることを前提にして作成されたテキストも多々あって、これらをターミナル内できちんと表示させたい場合には、やはりEAW 文字は二文字幅の方が嬉しい。

  • gnome-terminal 等には EAW 文字を半角とするか、全角とするかというオプションがある。
  • rxvt 等、wcwidth の返す値を使うターミナルの場合には, ロケールデータを修正するか fumiyas/wcwidth-cjk 等で wcwidth を上書きすれば良い。

tmux ではどうするか。

tmux <= 2.1 までは、自前で文字幅に関する情報を持っていたのでここに wcwidth 相当の修正を加えることで幸せになれていた。

一方、tmux 2.2 から wcwidth を参照するようになった。

tmux 2.2 からは wcwidth(1) を使うようになり、独自テーブルをやめてロケールの情報から文字幅を得るようになった https://github.com/tmux/tmux/commit/26945d7956bf1f160fba72677082e1a9c6968e0c 。 が、このコミットをよく見ると setlocale(LC_CTYPE, "en_US.UTF-8") で固定されており、LC_ALL や LC_CTYPE に関係なく en_US.UTF-8 が使われる。 tmux は UTF-8 を前提としており、そこを固定したい気持ちは分からなくもないが……

[tmux 2.2 以降で East Asian Ambiguous Width Character を正しく表示させる方法より引用]

でもって、何が問題かと言えば、上記にも書かれている通り、en_US.UTF-8 では EAW文字は一文字幅なので、二文字幅派にとってはカーソル移動/再描画の際に常に悲しみがつきまとう事になってしまう。

また、これは前からそうであった気もするが、pane の境界の描画に使われている罫線文字として Unicode の BOX DRAWING CHAR が使われている。これは EAW 文字であるため、tmux で上下分割すると、pane の境界線が二倍の長さで表示されてしまう(ため、ズレる)。

多くの人はこれに困りはてて、pane 境界の描画に ASCII を使い、wcwidth 相当の文字幅の算出関数を tmux 側で持つ、などといった修正を行なっている。

今回の記事は、結局のところ上記パッチでは私の欲求に答えられなかったのでじたばたした、というお話なのである(長いよ)。

で、結局どうしたのさ?

何が問題なのか、と言えば

  • 幅の算出の際に en_US.UTF-8 決め打ちなので、EAW 文字があるとズレる
  • pane 境界の描画に使う文字種だけが ASCII になっているので、curses ベースの端末アプリケーションの多くがズレる。

ということであろうか? 特に aptitude や alsamixier がズレてしまって、悲惨な目にあった。

いろいろ書いたけれど、

  1. 手元の環境のロケールデータをEAW文字と絵文字(level1)を全て全角として扱う 修正ロケールデータにおきかえる。 ちなみに、作っていたのは以下:

    微妙に間違っている気がしないでもないが、まあ良いか、的な。

    こっちの方が良いかもしれない。

  2. 最初に載せた二つのパッチを当てて、tmux 2.3 をビルド。
  3. rxvt の場合には特にオプション等の指定は不要。 gnome-terminal 等、BOX DRAWING CHAR を使うターミナルを使う場合には tmux 内で pane-border-ascii 0 を指定する

ということをやって、今は幸せになれました、とさ。

ASCII 描画、ACSC 描画 そして(元々の) Unicode BOX DRAWING CHAR は、オプションで選べると良いのではないだろうか、とか思いました。

en_US.UTF-8 決め打ちに関しては根が深そう、というか。なんでこんな安直に決め打ったんだろうか...。