Goでエスケープシーケンスを扱う(文字色、カーソル移動)

## やりたかったこと
なんとなくコンソールに「zzz」と立体（平面）的に出力させるコマンドラインツールを作りたくなった。完成形が以下である。（Windows?知らない子ですね。。。）
![sleep-go.gif](https://qiita-image-store.s3.amazonaws.com/0/157975/1fca98cb-4012-402a-0467-28650535560f.gif)

ソースについてもいたってシンプルで、単に3種類の大きさのZのAAを順に出力しているだけである。

```golang:zzz.go
package main

import (
	"fmt"
	"os"
	"time"
)

const (
	BLUE = "\033[34m"
	END  = "\033[0m"
)

func main() {
	defer fmt.Fprint(os.Stdout, END)
	fmt.Fprint(os.Stdout, BLUE)
	for i := 0; i < 5; i++ {
		for _, zzz := range zzzs {
			fmt.Fprintf(os.Stdout, "\r%s", zzz)
			time.Sleep(500 * time.Millisecond)
			fmt.Fprint(os.Stdout, "\033[13A")
		}
		fmt.Fprintln(os.Stdout, "")
		fmt.Fprint(os.Stdout, "\033[1A")
	}
}
```

```golang:sleeps.go
package main

var zzzs = [4]string{zzz1, zzz2, zzz3, zzz4}

const zzz1 = `







ZZZZZZZZZZ
       ZZ
     ZZ
   ZZ
 ZZ
ZZZZZZZZZZ`

const zzz2 = `




                 ZZZZZZZZZZZZZZZZ
                              ZZ
                            ZZ
ZZZZZZZZZZ                ZZ
       ZZ               ZZ
     ZZ               ZZ
   ZZ               ZZ
 ZZ               ZZ
ZZZZZZZZZZ       ZZZZZZZZZZZZZZZZ`

const zzz3 = `
                                         ZZZZZZZZZZZZZZZZZZZZZZZZ
                                                              ZZ
                                                            ZZ
                                                          ZZ
                 ZZZZZZZZZZZZZZZZ                       ZZ
                              ZZ                      ZZ
                            ZZ                      ZZ
ZZZZZZZZZZ                ZZ                      ZZ
       ZZ               ZZ                      ZZ
     ZZ               ZZ                      ZZ
   ZZ               ZZ                      ZZ
 ZZ               ZZ                      ZZ
ZZZZZZZZZZ       ZZZZZZZZZZZZZZZZ        ZZZZZZZZZZZZZZZZZZZZZZZZ`

const zzz4 = `
                                                                 
                                                                 
                                                                 
                                                                 
                                                                 
                                                                 
                                                                 
                                                                 
                                                                 
                                                                 
                                                                 
                                                                 
                                                                 `
```

## エスケープシーケンスの操作
上記のツールを作成する上で、解決しないといけない課題は大きく分けて2つあった。

1. 文字色の変更: 必要性があるわけではなかったが、なんとなく見栄えが良さそうなのでZのAAの文字色を変更した。
2. カーソルの移動: 普通にZのAAを順に出力するだけでは、添付のgifにあるような同じ画面が更新され続けるような見た目にすることは出来ない。前に表示されたZのAAが残ったままである。
![out-sleep-go.gif](https://qiita-image-store.s3.amazonaws.com/0/157975/b2d46766-b6fc-cc92-ebde-3e0cb43873dc.gif)

1, 2 の問題はともにエスケープシーケンスを用いることにより解決することができ、実際のところGo特有というわけでもなく、rubyなど別の言語でも同様の手法を使える。  
詳しくはエスケープシーケンスでググるといい。

### 1. 文字色の変更
標準出力に`"\033[33m"`, `"\033[37m"` などを出力することにより、その出力後の文字色を変更できる([とmの間の数字を変更)。必ず処理終了後は文字色を戻すこと。

```golang:zzz.go(関係する部分のみ抽出)
const (
	BLUE = "\033[34m"
	END  = "\033[0m"
)

func main() {
	defer fmt.Fprint(os.Stdout, END)
	fmt.Fprint(os.Stdout, BLUE)
```

### 2. カーソルの移動
前述の通り、普通にZのAAを順に出力するだけでは、添付のgifにあるような同じ画面が更新され続けるような見た目にできず、前に表示されたZのAAが残ったままである。  
これを解決するにはZのAAが出力されるたびに、ZのAAの表示位置のカーソル位置をコマンドを実行した場所まで上に移動させる必要がある。  
これは、標準出力に`"\033[1A"`, `"\033[13A"` などを出力することにより実現できる([とAの間に数字を変更)。Aの部分をB, C, Dと変えることによりカーソル位置を上下左右に変更できる。

```golang:zzz.go(関係する部分のみ抽出)
func main() {
	defer fmt.Fprint(os.Stdout, END)
	fmt.Fprint(os.Stdout, BLUE)
	for i := 0; i < 5; i++ {
		for _, zzz := range zzzs {
			fmt.Fprintf(os.Stdout, "\r%s", zzz)
			time.Sleep(500 * time.Millisecond)
			fmt.Fprint(os.Stdout, "\033[13A")
		}
		fmt.Fprintln(os.Stdout, "")
		fmt.Fprint(os.Stdout, "\033[1A")
	}
}
```
## 最後に
エスケープシーケンスを用いると他にも様々なことが実現でき、それを特にコマンドラインツールの作成において役立つ。  
こんな画面表示をコマンドラインツールで実現したいがどうするればいいのだろうと思った際には一度エスケープシーケンスに調べてみると道が開けるかもしれない。