Julia のREPL上でカラフルな出力をする（お遊び）

# 本日は

- [TerminalMenus](https://github.com/JuliaLang/julia/tree/master/stdlib/REPL/src/TerminalMenus) の実装を調べてたらこういうことできそうだなという物で作ったネタです．TerminalMenus 自体はJuilaのREPL上で対話操作を可能にしその結果に応じて処理を分岐させることができるツールとしてJuliaの標準に組み込まれています．今回は実装を眺めててえられた知見から得られたコードを紹介します．

# できたこと

下記のGIFをごらんください

![out.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/e651b563-f64a-fe6d-6527-49c6f15f5b46.gif)

ターミナル上に出力される文字（の背後）に色をつけたものを逐次プリントしているものになります．

# 実装

下記のコードをそのまま実行します．

```
$ julia --color=yes partypeople.jl
```

`--color` オプションを有効にしておかないと背景に色が修飾されないので寂しいアウトプットになります．

```julia:partypeople.jl
#=
julia --color=yes partypeople.jl
=#

using Crayons
using Crayons.Box
using ColorTypes


const SPACE = " " # one white space
BG_COLORS=[
    #BLACK_BG         ,
    RED_BG           ,
    GREEN_BG         ,
    YELLOW_BG        ,
    BLUE_BG          ,
    MAGENTA_BG       ,
    CYAN_BG          ,
    LIGHT_GRAY_BG    ,
    DEFAULT_BG       ,
    DARK_GRAY_BG     ,
    LIGHT_RED_BG     ,
    LIGHT_GREEN_BG   ,
    LIGHT_YELLOW_BG  ,
    LIGHT_BLUE_BG    ,
    LIGHT_MAGENTA_BG ,
    LIGHT_CYAN_BG    ,
    WHITE_BG         ,
]

function draw(c,H,W; init::Bool=false)
    H,W = 5,10
    buf = IOBuffer()
    if !init
        print(buf, "\x1b[999D\x1b[$(H)A") #rollback H-times
    end
    for i in 1:H
        msg = repeat("$(rand(1:9))",W)
        println(buf, c, msg, Crayon(reset=true))
    end
    print(buf |> take! |> String)
end

function clean(H)
    buf = IOBuffer()
    for i in 1:H+1
        print(buf, "\x1b[2K") # clear line
        print(buf, "\x1b[999D\x1b[$(1)A") # rollback
    end
    print(buf |> take! |> String)
end

function partypeople()
    H,W=5,10
    draw(BLACK_BG,H,W,init=true)
    for c in BG_COLORS
        draw(c,H,W)
        sleep(0.1)
    end
    clean(H)
end

partypeople()
```

# やっていること

## Crayon で色付け

`draw` 関数で色付きの文字を出力します．for ループの中を見ると `msg` に格納した文字列を `println` している様子がわかります.
色を付けるために `Crayons.jl` で定義されているCrayon構造体を `msg` の前後に挟んでいます．実体はプリントの制御をする `\e[31m` (赤色)や `\e[0m` (元に戻す)のような命令を挟んでいるだけです．

例えば下図のように出力に使う色を制御させることができます．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/24ec5852-25e6-bcfc-b2b7-66135627ebee.png)

## 繰り返しプリントするために

### プリントしたら元に戻す

`print(buf, "\x1b[999D\x1b[$(H)A")` をするとプリントをする場所をH行分戻してくれます．これをすることで出力がダーーーーっと流れるのを防いでいます．

普通 println("Hello") とするとプリントした結果の次の行にカーソルが出ますね．続けて `println("World")` とすると

```
Hello
World
```

のように前の出力である `Hello` が残っています．カーソルを１行戻して `World` を書くと `Hello` が消えて `World` ”だけ”が残るという仕組みです．

```
Step 1      |     Step 2      | Step 3
Hello       |   > Hello       | World
World>      | (上に持っていく)  |
```

実際に動かしてもらった方が理解が進むと思います．

## 後片付け

`clean` 関数で `draw` をした結果を消していきます．`print(buf, "\x1b[2K")` をして行を消してカーソルを１行上に持っていくという操作をプリントした行数繰り返すということをしています．


# まとめ

- 以上の動作を高速にすることで色付きの文字がパッパと入れ替わるように見せることができました．
- TerminnalMenu の実装から面白いところを取ってきてJuliaのお遊びコードを書きました．TerminalMenus の中では waitKey という関数が中で動いており，ユーザーのキーの入力に合わせてアクションをしその中で IOBuffer を制御
させてターミナルでの対話操作を可能にしています．頑張ったらJuliaだけで簡単なエディタ作れそうですね．お暇な方是非どうぞ．


# おまけ

おつきさんがグルグル回転します．

```julia
function moonbar()
    try
        print("\x1b[?25l") # hide cursor
        while true
            for 🌝 ∈ '🌑':'🌘'
                sleep(0.1)
                print(🌝)
                print('\r')
            end
        end
    finally
        print("\x1b[?25h") # unhide cursor
    end
end

moonbar()
```

これも同様で出力する場所をコントロールしています．Nowloading...って感じのシチュエーションで使ってみてください．