Skip to content

Instantly share code, notes, and snippets.

@lo48576
Last active April 25, 2020 19:17
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lo48576/194f05f9266761d6925495237594edbc to your computer and use it in GitHub Desktop.
Save lo48576/194f05f9266761d6925495237594edbc to your computer and use it in GitHub Desktop.
ambiwidth patches

Ambiwidth 環境整備用情報

charmap

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

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

tmux では、(2.x あたりから)システムの wcwidth を用いるようになったと聞いている(ただし en_US.UTF-8 固定だか何だか) [要出典]。 しかも、ペイン分割などで用いられる罫線は半角前提で使用されるため、日本語フォントや罫線記号の wcwidth 文字幅が2である環境で使うと表示が壊れる。

この問題を解決する安直な方法は、罫線文字として Unicode の罫線でなく ASCII 記号を使うことである。 以下の gist リポジトリが、そのようなパッチを提供している(更に wcwidth をいい感じにしていたりもするっぽい)。

また、以下のページで解説がある。

alacritty

alacritty は Rust 製のターミナルエミュレータ。

これは wcwidth の実装として unicode_width crate を使っているが、これはロケールの自動検出機能を持たず、 ambiwidth 等を1文字幅と見做す width() 関数と、2文字幅と見做す width_cjk() 関数の両方を持っている。 そして、現状 (2018-02-06 時点の master) の alacritty は、 width() のみを使っているため、 CJK 圏では文字幅の定義がシステムやフォントと食い違う。

これを修正するには、自動検出かユーザの明示的な設定によって、 width()width_cjk() の適当な方を選択して使うようにする必要がある。 それを実装したのが以下のプルリクエストだ。

残念ながら 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 {
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment