二度忘れた事を三度忘れないようにする

しがないフリーランスIT系エンジニア

Golangでtviewを使ってTUIアプリを作った

Golang勉強がてらPingを打つツールを作りました。
なお、以前のGithubActions関連の記事はこれを作る際に試したモノでした。

github.com

Go(langで作った)M(ultiホストへ)Ping(するツール)、です。
次のような機能をもったPingツールがあると超局所的に役立つかな、と思って作りました。
Ping対象のグループ化
・1画面に対して複数グループ表示
・1画面にグループが収まらない場合はページング

勉強とはいえPingとTUIをゼロから実装するのはさすがに挫折するので、Pinggo-pingを、TUIはtviewを使って実装しました。

Pingについてはgo-fastpingも候補にあったのですが、わからんなりにコードを見てはみましたが関数的にPingの結果が返ってくる使い方が簡単に出来るgo-pingを採用することにしました。あと、go-fastpingはPing処理がループしてて、Pingが返ってきた時に何をするか、を書く感じで、かつ、応答なしのカウントが出来そうになかったというのも理由です。(実は出来るかもしれないですが、、、

TUIはtermbox-go系と迷ったのですが、ぱっとみの印象でtcell系のtviewを使うことにしました。目的はGolangの勉強なので本当に勘です。

Pingについてはgo-pingに変えるまで色々試行錯誤したのですが、go-pingに変えた瞬間、NewしてPing打つ関数実行したらPing結果を得られたので、今回に関しては特に困ることはなかったです。あと、関数的に扱いたかった理由としてはgoroutineを使ってみたかった為で、go-fastpingは内部的にgoroutineを使って実装していたっぽいので、さらにラップしていくのはなんだかな、と思っただけです。はい。

TUIのtviewはレイアウトするための特徴を掴むのになかなか時間がかかりました。一つ一つ動かして思い描く感じに動かせそうか、というのを試しました。
自分なりに理解したレイアウトの概念的なモノを今回のツールを基にザックリと図におこしてみました。なお、tviewのサンプルをベースに作りましたが、実装的に正しい(もっとスマートに出来た)かどうかはわからないですが、実現したい見た目にはなりました。

f:id:knhko:20200522163528p:plain

まずTUIツールの土台となる「Application」があります。これはマウス操作の制御であったり、後述する中身(Widgets)を与えそれの描写処理等を行うモノで、これ自体にレイアウトさせたりする機能はありません。

次に「Flex1」としているベースになるレイアウトをFlex構造体を使って定義しています。Flexとはその名のとおりターミナルの大きさに合わせて動的に調整するWidgetsです。
FlexにはAddItemという関数があり、さらに子Widgetsを入れていくことが出来ます。これを使ってベースのレイアウトをしているわけです。
今回は子Widgetsとして「Pages」はPages構造体を、「Flex4」はFlex構造体を使ったものを入れて、Ping画面(Pages)とページ処理・ヘルプ(Flex4)を表現しています。

先に「Flex4」を説明すると、子WidgetsとしてTextView構造体を使っています。右側のTextViewは文字を表示させているだけなのですが、左側は後述のページング処理が入ります。ただ、その処理というところについてはコードを読んでみたモノの、恥かしながら色々理解が浅くなんとなくしかわかってないです。。。

Ping画面の「Pages」ですが、想像通りPage要素をもっており、そのPage要素にAddPage関数を使ってWidgetsを入れていきます。今回は所謂ページングなのでページ番号に対してWidgetsを入れるのですが、「ページ番号」にあたるところはstringで指定するようになっているので、ページングと見てintと思い込んでしまわないように注意です。(Golangの場合直ぐ気づくかもしれませんが。。。

次に各Pageに「Flex2」としているFlex構造体を入れています。これは冒頭に書いた1画面に複数のPingグループを表示させたかったので、そのための枠となります。子Widgetsとして「Flex3」というFlex構造体を複数入れていて、一つの「Flex3」で1グループを表現しています。

Flex3」の子Widgetsとして「Table」というTable構造体を入れています。TableはTableCell要素を持っており、これにPing結果を入れていきます。また、なぜTableを使ったか、というと、表示させる項目で更新する必要が「ある項目」と「無い項目」があり、ある項目だけをピンポイントで更新したかったためです。TextViewやList等でも似たような表現は出来るような気がしますし、Applicationが再描画する処理をちゃんと読んでないので、Tableである必要性は無かったかもしれません。ただ、見た目的には簡単かつ綺麗に表示出来たと思うのでそれはメリットだったのかなと思います。