つうさにメモブログ

つうさにがメモをブログとして書いていくところ

Windows Consoleでも256color、さらにtrue colorなEmacsする

termcapとかterminfoとかに詳しくなれました

Windowsでの端末エミュレータ事情

Windowsにおける標準の端末エミュレータ(?)として、Windows Console Hostがあります。cmd.exeやpowershellで使われるガワの環境のことです。WSLでも使われてます。

srad.jp

上の記事に書いてあるように、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エスケープシーケンスは、文字の色付け以外にも端末での様々な操作ができます。)

devblogs.microsoft.com

そして、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 EmacsWindows向けの移植を含んでいて、かつWindows向けのバイナリを配布しています

実行ファイルはemacs.exeで、cmd.exeからemacs.exe -nwすると、UNIXでのと同様Windows Console上でTUIアプリケーションとして実行されます。

ただし、現在のEmacsWindows向け実装ではWin32 console APIを使っているため、16色しか表示できません。これは多くのUNIXで使われる256colorな端末で表現できる色より色数が相当少ないため、uglyな見た目になることがあります。

例えば、zenburnテーマを適用しているととても見づらいものとなります。

f:id:tsuu_mmj:20190630021706p:plain
16色なので見づらい... GUIでのと全然色が違う...

今回の話は、Win32 console APIで色付けしている現在のTUIなEmacsを、ANSIエスケープシーケンスを使って色付けするように改変して、256色、それ以上の16777216色(true color)表示可能にさせようという話です。

Vimが参考

github.com

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_faceturn_off_faceを持ってきて、WriteConsoleの前、後ろで実行します。

turn_on_faceturn_off_faceではterminfo由来のtty->TS_set_backgroundなどは使わず、エスケープシーケンス をベタ書きしました。

大事なこととして、SetConsoleMode関数で出力のモードにENABLE_VIRTUAL_TERMINAL_PROCESSINGを足します。 これをすることで、ANSIエスケープシーケンスを認識してくれます。

lisp/term/w32console.el

lisp/term/xterm.elを参考にtty-color-defineするところを修正します。

修正した結果

f:id:tsuu_mmj:20190630030121p:plain
Windows Console上でtrue colorなemacs

ということで、Windows Consoleでtrue colorなEmacsを実現できました。

今までは存在しなかったunderlineにも対応するようになりました。

下の画像はmacOSのiTerm2.appで開いたTUIなEmacsです。上のtrue colorに対応したWindows ConsoleでのEmacsはこれとそっくりになっています2

f:id:tsuu_mmj:20190630031919p:plain
iTerm2.app上のemacs -nw (true color)

終わりに

github.com

GitHubソースコードを公開しています。

結構バグいですが、意外と簡単に実装できてよかったです。


  1. bashではCtrl+vでエスケープ状態にしてからCtrl+[を押すことでESCを入力できるのに対し、cmd.exeではCtrl+[でESCを入力できて楽

  2. よく見ると内容が違うのは、Windowsのほうではdisplay-mouse-p関数がnon-nilを返すから。Windows Console上のEmacsではデフォルトでマウスが使えるようです。