Windows Consoleでも256color、さらにtrue colorなEmacsする
termcapとかterminfoとかに詳しくなれました
Windowsでの端末エミュレータ事情
Windowsにおける標準の端末エミュレータ(?)として、Windows Console Hostがあります。cmd.exeやpowershellで使われるガワの環境のことです。WSLでも使われてます。
上の記事に書いてあるように、Windows 10 November UpdateでANSIエスケープシーケンスがWindows Console上で使えるようになりました。
cmd.exeを開いて以下を実行すると、色のついたaaaが表示されます。
echo ^[[33m aaa ^[[m
ここで^[
は、^
と[
を個別に入力したものではなく、Ctrlキーを押しながら[
を押すことで入力できるASCII制御文字のESC(0x1b)です(カーソルを左右に動かすとこれが1文字であることがわかります)。1
Windows Console上のCUIアプリケーションで色を使う場合は、Win32 console APIを呼び出すのが伝統的な手法でした。しかし、このときからANSIエスケープシーケンスを用いても色を使うことができるようになりました。同じことをするのに2種類の方法が存在することなったのです。なぜこの機能が追加されたのかは、WSLが関係しているんだと思います。
(ANSIエスケープシーケンスは、文字の色付け以外にも端末での様々な操作ができます。)
そして、Windows Consoleで24bit colorがサポートされました。
^[[38;2;<r>;<g>;<b>m
で文字の色、^[[48;2;<r>;<g>;<b>m
で背景の色が指定できます。このときr, g, bにはそれぞれ0~255の値を設定できます。つまり、000000〜FFFFFFのすべての色を区別して出力できます。これは通常のディスプレイで表示できる色と同等で、true colorとか24bit colorと呼ばれます。
Win32 console APIには256colorや24bit colorを利用する機能はないため、このような多くの色をWindows Consoleで表現したい場合、ANSIエスケープシーケンスを用いる必要があります(多分)。
Emacs on Console Host
最近のGNU EmacsはWindows向けの移植を含んでいて、かつWindows向けのバイナリを配布しています。
実行ファイルはemacs.exe
で、cmd.exeからemacs.exe -nw
すると、UNIXでのと同様Windows Console上でTUIアプリケーションとして実行されます。
ただし、現在のEmacsのWindows向け実装ではWin32 console APIを使っているため、16色しか表示できません。これは多くのUNIXで使われる256colorな端末で表現できる色より色数が相当少ないため、uglyな見た目になることがあります。
例えば、zenburnテーマを適用しているととても見づらいものとなります。
今回の話は、Win32 console APIで色付けしている現在のTUIなEmacsを、ANSIエスケープシーケンスを使って色付けするように改変して、256色、それ以上の16777216色(true color)表示可能にさせようという話です。
Vimが参考
Windows ConsoleでANSIエスケープシーケンスを用いてtrue colorを表示する先行事例として、Vimがあります。Vimではすでに対応済みで、本家にマージされているという状況でした。TUIなvim.exeでset termguicolors
するとtrue colorで表示できます。
Win32のAPIを全く知らなかったので、関数の使い方などかなり参考になりました(あと日本語なのでわかりやすかったです)。
GNU Emacsの修正
src/term.c
init_tty
関数: WINDOWSNTのとき、tty->TN_max_colors = 16;となっているところを変えます。
src/w32console.c
w32con_write_glyphs
関数: Win32 console APIでコンソール上の文字の色を変える方法として、FillConsoleOutputAttribute
してからWriteConsoleOutputCharacter
するという実装になっています。
これを単にWriteConsole
するものに置き換えます(Vimを参考にしました)。
また、src/term.cにあるturn_on_face
やturn_off_face
を持ってきて、WriteConsole
の前、後ろで実行します。
turn_on_face
、turn_off_face
ではterminfo由来のtty->TS_set_background
などは使わず、エスケープシーケンス をベタ書きしました。
大事なこととして、SetConsoleMode
関数で出力のモードにENABLE_VIRTUAL_TERMINAL_PROCESSING
を足します。
これをすることで、ANSIエスケープシーケンスを認識してくれます。
lisp/term/w32console.el
lisp/term/xterm.elを参考にtty-color-define
するところを修正します。
修正した結果
ということで、Windows Consoleでtrue colorなEmacsを実現できました。
今までは存在しなかったunderlineにも対応するようになりました。
下の画像はmacOSのiTerm2.appで開いたTUIなEmacsです。上のtrue colorに対応したWindows ConsoleでのEmacsはこれとそっくりになっています2。
終わりに
結構バグいですが、意外と簡単に実装できてよかったです。