Edited at
Go4Day 12

golang で libvterm

空いていたので穴埋め


libvterm とは

libvterm は libtermkey の作者でおなじみの Paul Evans 氏によって作られた、VT220/xterm/ECMA-48 といった端末エミュレータの仕様を抽象化し疑似する為のライブラリです。このライブラリの仕様に従ってプログラミングするとプラットフォームや GUI/CUI に依存せず端末エミュレータが扱えるという物です。neovim や vim の :terminal 機能として使われています。

libvterm はシェルではありません。またユーザインタフェースでもありません。純粋な仮想端末エミュレータの実装です。ですのでキーも読み取りませんし、何も描画しません。出来るのは端末のセルに対するエスケープシーケンス等の書き込みと、それによって制御された端末の出力結果を取得出来るのみです。

ですので例えばシェルを起動しユーザと対話できる端末エミュレータを作る場合、プログラムは子プロセス(シェル)を起動し、その pty 出力を読み取って libvterm に流し込みます。また libvterm からのコールバックイベントに伴い端末のセル情報を読み取って独自 UI にレンダリングする事になります。

libvterm


libvterm をGo言語から使いたい

以前 libvterm をGo言語から扱う為のパッケージを書きました。

https://github.com/mattn/go-libvterm

このパッケージを使って、80x25 の端末に赤字で「Hello」緑色で「World」と表示するプログラムを書いてみます。

vt := vterm.New(25, 80)

defer vt.Close()

vt.SetUTF8(true)

screen := vt.ObtainScreen()
screen.Reset(true)

_, err := vt.Write([]byte("\033[31mHello \033[32mGolang\033[0m"))
if err != nil {
log.Fatal(err)
}
screen.Flush()

このコードを実行しても実際には何も表示されません。「これのどこが良いか分からない」そう思われるかもしれません。では続けて以下のコードを書いてみます。

    rows, cols := vt.Size()

img := image.NewRGBA(image.Rect(0, 0, cols*7, rows*13))
draw.Draw(img, img.Bounds(), &image.Uniform{color.Black}, image.ZP, draw.Src)

for row := 0; row < rows; row++ {
for col := 0; col < cols; col++ {
cell, err := screen.GetCellAt(row, col)
if err != nil {
log.Fatal(err)
}
chars := cell.Chars()
if len(chars) > 0 && chars[0] != 0 {
drawChar(img, (col+1)*7, (row+1)*13, cell.Fg(), string(chars))
}
}
}
f, err := os.Create("output.png")
if err != nil {
log.Fatal(err)
}
defer f.Close()
err = png.Encode(f, img)
if err != nil {
log.Fatal(err)
}

こうする事で、80x25 の端末のスクリーンショットが撮れる訳です。

output.png

プログラム全体のコードを Gist に貼り付けておきます。

https://gist.github.com/mattn/06bfda007b8fc66890190052e06782d7


go-libvterm で出来ること

アプリケーションがデバイスに依存しない端末用キャンバスを内蔵できる事になるので、例えば Windows の仮想端末実装である winpty と合わせて使うと、コマンドプロンプトに tmux の様な枠を作りその中でさらにコマンドプロンプトを住まわせる事も出来るのです。

terminal6.gif

自分で作ったウィンドウアプリケーションの一部分に、Visual Studio Code の端末機能や Vim/neovim の :terminal 機能を付けたい場合には libvterm が便利です。そしてそのアプリケーションが Go で実装されているならば go-libvterm を使う事が出来ます。

宜しければご利用下さい。