Chapter 08

合字は糞!Unicodeは糞!(WindowsTerminal)

zetamatta
zetamatta
2021.02.07に更新

サロゲートペアすら表示できないコンソールでは合字など表示できるはずもございません。

> そう考えていた時期がわたくしにもありました <

だが、そこに @nu8 氏は挑戦状を送ってきたのです。Nushell だと WindowsTerminal で👨‍🌾(Man Farmer)が表示できるのに、我らが日本語拡張シェル nyagos は表示できないようです…と

合字というのは、2つの文字をあわせて1つの文字として表示するという実装側の苦労をまったく考えない酷いUnicode仕様です。まじ Unicode は糞!UTF8になっていても糞!

nyagos の場合、👨‍🌾をペーストすると、農夫ではなく「👨<200D>🌾」になります。<200D>は  ZeroWidthJoiner といい、幅ゼロで隣接する2つの文字を連結させる時に使う記号です。が、これはどうせ Windows ではまともに表示されるはずもないと思って、このように代替表現で表示させていました。が、まさか 普通に表示できるターミナルが現れるとは…まさに想定外!

が、表示できることが分かれば対応はそう難しくありません。合字すべての要素をすべて一括して表示するようにしさえすればよいのです。めんどくさくはありますが。

そのためには今まで編集の最小単位が Unicode のコードポイント(rune)だったのを、複数のコードポイントの集合を1編集単位とできるようにする必要があります。こういうインターフェイスを作って、一行入力パッケージ:go-readline-ny の編集の最小単位を抽象化しました。

type Moji interface {
	Width() WidthT // = int。この文字が画面に占めるセル数を返す
	WriteTo(io.Writer) (int64, error) // (strings.Builder経由で)文字列する時に使う
	Put(io.Writer) // 標準出力に出力する時に使う(<UUUU>表記にする場合がある)
	IsSpace() bool // 空白文字か否か
}

type CodePoint rune // 1コードポイント=1文字の時
type ZeroWidthJoinSequence string // 合字の時
  1. WindowsTerminalではない場合は U+200D は従来どおりエスケープ表記する文字扱いとする
  2. Ctrl-Yでペーストされる時は MAN~ZWJ~RICEまで全部ひとまとめで来るので、これは ZWJ が見つかったら前後の文字をひっくるめて ZeroWidthJoinSequence という型にするだけでよい(→ "buffer.go" (*Buffer)InsertString関数 → "moji.go" string2moji関数)
  3. マウスの右クリックでペーストされる時は、すべて一発でこないので、ZWJが来た時点で{直前に入力された文字 , ZWJ } を「あとで処理するバッファ」に保持し、直前に入力された文字自体はバックスペース相当の処理をして消してしまう。そして、次にキーデータが来た時「あとで処理するバッファ」にデータが入っていたら、それと今回入力されたデータを連結して ZeroWidthJoinSequence型インスタンスを挿入すればよい(→ "keyfunc.go" keyFuncInsertSelf関数)

ただし、これらは WindowsTerminal の時のみの話となります。普通のコマンドプロンプトの場合はバラバラに CodePoint として処理しています。