glibc 標準の /usr/share/i18n/charmaps/UTF-8
は、 ambiwidth characters が1文字幅になっている。
https://github.com/eagletmt/misc/blob/master/ruby/ambiwidth.rb を使うと、これらを2文字幅にした UTF-8-CJK
ファイルを作成できる。
zsh は、一部の結合文字などに対する wcwidth
等の誤った文字幅判定を防ぐため、 configure 時にテストを行い、理想的な wcwidth
実装でなかった場合に内部的な実装 (wcwidth9
) に切り替える。
ただ、 wcwidth9
のラッパー u9_wcwidth
が問題で、これは ambiwidth や private な文字の文字幅を1として返す関数なのである。
すなわち、曖昧文字幅だったりプライベート領域(たとえば font-awesome で使われている領域など)の文字幅が2であるような環境では、 zsh が内部で用いる文字幅判定が食い違うことになる。
そこで、このテストの結果を無視するパッチを用意した。 以下は zsh-5.4.2 用である。
diff -Naurp zsh-5.4.2.orig/configure.ac zsh-5.4.2/configure.ac
--- zsh-5.4.2.orig/configure.ac 2017-06-25 04:35:12.000000000 +0900
+++ zsh-5.4.2/configure.ac 2018-01-24 03:37:36.883348761 +0900
@@ -2656,6 +2656,13 @@ if test x$zsh_cv_c_unicode_support = xye
zsh_cv_c_broken_wcwidth=yes,
zsh_cv_c_broken_wcwidth=no,
zsh_cv_c_broken_wcwidth=no)])
+ dnl Ignore this test, because:
+ dnl * many terminals can't combine characters and users will hope
+ dnl combining characters to be printable with non-zero width,
+ dnl * CJK users wants zsh to print ambiguous width characters and
+ dnl private characters as full-width, but zsh's `u9_wcwidth()`
+ dnl treats them as half-width when `ENABLE_UNICODE9` is defined.
+ zsh_cv_c_broken_wcwidth=no
if test x$zsh_cv_c_broken_wcwidth = xyes; then
AC_DEFINE(ENABLE_UNICODE9)
fi
該当ソース: zsh-5.4.2, Src/wcwidth9.h
, 1286--1323 行:
static inline int wcwidth9(int c) {
if (c == 0) {
return 0;
}
if (c < 0|| c > 0x10ffff) {
return -1;
}
if (wcwidth9_intable(wcwidth9_nonprint, WCWIDTH9_ARRAY_SIZE(wcwidth9_nonprint), c)) {
return -1;
}
if (wcwidth9_intable(wcwidth9_combining, WCWIDTH9_ARRAY_SIZE(wcwidth9_combining), c)) {
return 0;
}
if (wcwidth9_intable(wcwidth9_not_assigned, WCWIDTH9_ARRAY_SIZE(wcwidth9_not_assigned), c)) {
return -1;
}
if (wcwidth9_intable(wcwidth9_private, WCWIDTH9_ARRAY_SIZE(wcwidth9_private), c)) {
return -3;
}
if (wcwidth9_intable(wcwidth9_ambiguous, WCWIDTH9_ARRAY_SIZE(wcwidth9_ambiguous), c)) {
return -2;
}
if (wcwidth9_intable(wcwidth9_doublewidth, WCWIDTH9_ARRAY_SIZE(wcwidth9_doublewidth), c)) {
return 2;
}
if (wcwidth9_intable(wcwidth9_emoji_width, WCWIDTH9_ARRAY_SIZE(wcwidth9_emoji_width), c)) {
return 2;
}
return 1;
}
wcwidth9()
は、 ambiwidth の場合に -2
, private の場合に -3
を返す。
さて、 wcwidth
の代わりとして利用される u9_wcwidth
の実装は、以下のようになっている。
該当ソース: zsh-5.4.2, Src/compat.c
, 670--681 行:
#ifdef ENABLE_UNICODE9
#include "./wcwidth9.h"
/**/
int
u9_wcwidth(wchar_t ucs)
{
int w = wcwidth9(ucs);
if (w < -1)
return 1;
return w;
}
せっかく wcwidth9()
が ambiwidth や private を区別しているところ、完全に無視して return 1
している。
これが、 ambiwidth や private な文字が強制的に1文字幅扱いされる原因である。
tmux では、(2.x あたりから)システムの wcwidth
を用いるようになったと聞いている(ただし en_US.UTF-8 固定だか何だか) [要出典]。
しかも、ペイン分割などで用いられる罫線は半角前提で使用されるため、日本語フォントや罫線記号の wcwidth
文字幅が2である環境で使うと表示が壊れる。
この問題を解決する安直な方法は、罫線文字として Unicode の罫線でなく ASCII 記号を使うことである。
以下の gist リポジトリが、そのようなパッチを提供している(更に wcwidth
をいい感じにしていたりもするっぽい)。
また、以下のページで解説がある。
alacritty は Rust 製のターミナルエミュレータ。
これは wcwidth
の実装として unicode_width
crate を使っているが、これはロケールの自動検出機能を持たず、 ambiwidth 等を1文字幅と見做す width()
関数と、2文字幅と見做す width_cjk()
関数の両方を持っている。
そして、現状 (2018-02-06 時点の master) の alacritty は、 width()
のみを使っているため、 CJK 圏では文字幅の定義がシステムやフォントと食い違う。
これを修正するには、自動検出かユーザの明示的な設定によって、 width()
か width_cjk()
の適当な方を選択して使うようにする必要がある。
それを実装したのが以下のプルリクエストだ。
- Support config to control width of ambiguous width characters by lo48576 · Pull Request #1049 · jwilm/alacritty
- 該当ブランチ
残念ながら merge してもらえる気配がしていない (2018-02-06) ため、今すぐ利用したいなら自分で rebase なり merge なりして使うしかない。
当該箇所のソース: 2466f81, src/term/mod.rs
1240--1241 行:
// Number of cells the char will occupy
if let Some(width) = c.width() {
これは強制的に non-CJK の文字幅を使っているのと同じことなので、切り替えられるようにするか、システムの wcwidth
を使うことで解決できる。
私のプルリクエストでは前者を選んだ。
該当箇所のソース: lo48576/alacritty (fork), cdb57ff, src/term/mod.rs
1244--1249 行
// Number of cells the char will occupy
let width = if self.east_asian_fullwidth {
c.width_cjk()
} else {
c.width()
};
if let Some(width) = width {