Go言語 コンソールモードでコンソール上にグラフ、ボタン、スクロールテキストを表示するtermdashの紹介

# 概要

通常コンソールにはテキストしか表示できないと思い込んでいました。紹介するtermdashを使えばコンソール上にGUIもどきのものが表示できます。

# 機能

下記の画像はデモをWindowsで実行したものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/75eec165-c234-9233-d9de-5fcb8bea89cb.png)

次が画面の中央にある白いボタンの`sparklines`をマウスでクリックしたときの画面です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/20c7a405-7d8c-3606-cd9c-1be2704eb106.png)


下記のURLではグラフが時間変化する動画が見られます。

- Github URL 
    - https://github.com/mum4k/termdash
- レイアウト
    - Binary tree layout
        - https://github.com/mum4k/termdash/wiki/Binary-tree-layout
        - 縦や横を2分割します。それらをそれぞれさらに2分割する方法で画面が構成されます。
        - これをメインで試しました
        - 画面のリサイズにも対応しています。
    - Grid layout
        - https://github.com/mum4k/termdash/wiki/Grid-layout
        - 一般的なGridだと思います。まだ試していません。
- Widget
    - BarChart  棒グラフ (画像の右上）
        - https://github.com/mum4k/termdash/wiki/Barchart-API
    - Button  ボタン (画像の中央4ボタンと右下の2ボタン）
    - Donut   ドーナツグラフ　（画像のBarChartの下）
    - Gauge   ゲージ　（画像の中央の緑の帯）
    - LineChart  グラフ（画像の下、マウスによるズーム機能)
    - SegmentDisplay  未調査
    - Sparkline   ミニグラフ　（画像の中央）
    - Text　　（画像の中央）
        - スクロール
            - マウスやキーボードでスクロールできます。
        - テキストの折り返し
        - はみ出し分を省略

# 各 OS上での実行

- Windows 上記画像の通り問題なし
    - 日本語や中国語などを表示するときは`chcp 65001`でUTF-8にしないと画面が乱れる
- Mac  ほぼ問題はなかったが `Sparkline`に横筋が入っていた
- Linux 　(ubuntu 18.04)
    - Linux画面を直接操作することはないので以下のsshで試しました。
    - Mac ssh ではMac上と同じでした
    - Windows (おすすめは`git bash`のssh)
        - git bash のssh 
            - 一番良かった
            - ボタンも押せます　
            - sparklineの表示で塗りつぶされていない四角文字がでる（気にしなければ問題ない）
            - Scroll textでスクロールの特殊文字が表示されない
        - putty 
            -  グラフは出たが 枠の罫線が乱れている
            -  ボタンが押せない
        - openssh 
            - 時間変化させたグラフ等が乱れる。
            - ボタンが押せない
            - 一部枠の罫線が乱れる
        - WSL(ubuntu 18.04) のssh
            - LineChartが乱れる
            - Donutが乱れる
            - 中央の白いボタンが出ない
            - 右下のボタンは表示されて、クリックも出来る。


# Scroll textが２つのログコンソール画面の実装

次のような画面です。左がFatalログで右がInformationログです。下記の画面は右側をクリックしたので枠の色が黄色？になっていますのでマウスの中ボタンやキーボードのカーソルキーで上下にスクロールできます。ログの文字が折り返されたら画面を横に広げればリサイズされます。　Fatalの時刻は赤にして、Informationはシアンにしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258007/b2fda059-d2e1-f61a-c80d-265765eb04cb.png)

## ソースコード

デモのソースを簡単に改造しただけです。Fatalかどうかは乱数によって決めました。実際はログファイルやデータベース、ネットワークから取得すると思います。

`container.SplitVertical`を使って画面を縦に２分割しました。

```go:main.go
package main

import (
	"context"
	"fmt"
	"math/rand"
	"time"

	"github.com/mum4k/termdash"
	"github.com/mum4k/termdash/cell"
	"github.com/mum4k/termdash/container"
	"github.com/mum4k/termdash/linestyle"
	"github.com/mum4k/termdash/terminal/termbox"
	"github.com/mum4k/termdash/terminal/terminalapi"
	"github.com/mum4k/termdash/widgets/text"
)

func writeLines(ctx context.Context, fatal, info *text.Text, delay time.Duration) {
	s := rand.NewSource(time.Now().Unix())
	r := rand.New(s)
	ticker := time.NewTicker(delay)
	defer ticker.Stop()
	fatalCount := 0
	infoCount := 0
	for {
		select {
		case <-ticker.C:
			var err error
			d := time.Now().Format("03:04:05")
			i := r.Intn(3)
			if i == 0 {
				fatal.Write(d, text.WriteCellOpts(cell.FgColor(cell.ColorRed)))
				if err == nil {
					fatalCount++
					log := fmt.Sprintf(" %d fatal fatal fatal\n", fatalCount)
					err = fatal.Write(log)
				}
			} else {
				err = info.Write(d, text.WriteCellOpts(cell.FgColor(cell.ColorCyan)))
				if err == nil {
					infoCount++
					log := fmt.Sprintf(" %d information information information\n", infoCount)
					err = info.Write(log)
				}
			}
			if err != nil {
				panic(err)
			}
		case <-ctx.Done():
			return
		}
	}
}

func main() {
	t, err := termbox.New()
	if err != nil {
		panic(err)
	}
	defer t.Close()

	ctx, cancel := context.WithCancel(context.Background())

	rolledFatal, err := text.New(text.RollContent(), text.WrapAtWords())
	if err != nil {
		panic(err)
	}
	rolledInfo, err := text.New(text.RollContent(), text.WrapAtWords())
	if err != nil {
		panic(err)
	}
	go writeLines(ctx, rolledFatal, rolledInfo, 1*time.Second)

	c, err := container.New(
		t,
		container.Border(linestyle.Light),
		container.BorderTitle("PRESS Q TO QUIT"),
		container.SplitVertical(
			container.Left(
				container.Border(linestyle.Light),
				container.BorderTitle("  Fatal ログ"),
				container.PlaceWidget(rolledFatal),
			),
			container.Right(
				container.Border(linestyle.Light),
				container.BorderTitle("  Information ログ"),
				container.PlaceWidget(rolledInfo),
			),
		),
	)
	if err != nil {
		panic(err)
	}

	quitter := func(k *terminalapi.Keyboard) {
		if k.Key == 'q' || k.Key == 'Q' {
			cancel()
		}
	}

	if err := termdash.Run(ctx, t, c, termdash.KeyboardSubscriber(quitter)); err != nil {
		panic(err)
	}
}
```

# まとめ

サーバー系のモニタ画面に使えるのではないかと思います。 まさかコンソールでマウスが使えるとは思いませんでした。
各Layout, Widgetの使い方をQIITAに載せて頂けると嬉しいです。
git bashのsshよりもっと最適なWindowsのSSHソフトが有ったら教えてほしいです。