PR

Go言語(Golang)でTUIライブラリ「tview」の使い方

3. 応用

こんにちは。Tomoyuki(@tomoyuki65)です。

Go言語(Golang)でツール開発をする際など、何らかのユーザーインターフェースが必要になったりします。

その際はブラウザを利用することを最初に想像しますが、無駄に複雑になったりしてわざわざブラウザまでは使いたくないことも多いです。

そんな時はTUI(Text User Interface / Terminal User Interface)と呼ばれるように、ターミナルでユーザーインターフェースを作る方法があり、Go言語なら「tview」というライブラリがあったりします。

この記事では、そんなGo言語(Golang)の「tview」の使い方についてご紹介します。

 

Go言語(Golang)でTUIライブラリ「tview」の使い方

まずは以下のコマンドを実行し、各種ファイルを作成します。

$ mkdir go-tview && cd go-tview
$ mkdir -p docker/local/go && touch docker/local/go/Dockerfile
$ mkdir src
$ touch compose.yml

 

次に作成したファイルをそれぞれ以下のように記述します。

・「docker/local/go/Dockerfile」

FROM golang:1.25.6-alpine3.23

WORKDIR /go/src

COPY ./src .

# Install dependencies if go.mod exists
RUN if [ -f ./go.mod ]; then \
  go install ./...; \
fi

# Install development libraries
RUN go install honnef.co/go/tools/cmd/staticcheck@latest

EXPOSE 8080

 

・「compose.yml」

services:
  app:
    container_name: go-app
    build:
      context: .
      dockerfile: ./docker/local/go/Dockerfile
    volumes:
      - ./src:/go/src
    ports:
      - "8080:8080"
    environment:
      ENV: local

 

次に以下のコマンドを実行し、Dockerコンテナをビルドします。

$ docker compose build --no-cache

 

次に以下のコマンドを実行し、Goの初期化をします。

$ docker compose run --rm app go mod init go-tview

 

サンプル1:テキスト表示

次に以下のコマンドを実行し、サンプルファイルを作成します。

$ mkdir -p src/cmd/sample1 && touch src/cmd/sample1/main.go

 

次に作成したファイルを以下のように記述します。

・「src/cmd/sample1/main.go」

package main

import (
    "github.com/rivo/tview"
)

func main() {
    // -- アプリ定義(マウス有効化)--
    app := tview.NewApplication().
        EnableMouse(true)

    // -- ページ定義 --
    pages := tview.NewPages()

    // -- テキストビュー設定 --
    textView := tview.NewTextView().
       SetDynamicColors(true).
       SetText("Hello World !!")

    // -- メニュー設定 --
    mainMenu := tview.NewFlex().
        SetDirection(tview.FlexRow).
        AddItem(textView, 0, 1, false)

    mainMenu.SetBorder(true).SetTitle(" Main menu ")

    // -- ページ設定 --
    pages.AddPage("main_menu", mainMenu, true, true)

    // -- アプリ起動処理 --
    if err := app.SetRoot(pages, true).Run(); err != nil {
        panic(err)
    }
}

※tview.NewFlex()のAddItem()のパラメータについては、左から「追加するUIコンポーネント」、「固定サイズ」(0なら可変)、「可変領域の比率」、「フォーカス対象にするかどうか」になります。

 

次に以下のコマンドを実行し、go.modの更新をします。

$ docker compose run --rm app go mod tidy

 

次に以下のコマンドを実行し、サンプルファイルを実行します。

$ docker compose run --rm app go run ./cmd/sample1

 

コマンド実行後、ターミナルで下図のように表示されればOKです。

 

終了したい場合、キーボードのショートカット「control + c」(MacOSの場合)を実行して下さい。

 

サンプル2:リスト表示

次に以下のコマンドを実行し、サンプルファイルを作成します。

$ mkdir -p src/cmd/sample2 && touch src/cmd/sample2/main.go

 

次に作成したファイルを以下のように記述します。

・「src/cmd/sample2/main.go」

package main

import (
    "github.com/rivo/tview"
)

func main() {
    // -- アプリ設定(マウス有効化)--
    app := tview.NewApplication().
        EnableMouse(true)

    // -- ページ定義 --
    pages := tview.NewPages()

    // -- リスト定義 --
    list := tview.NewList()

    // -- リストにアイテム追加 --
    list.AddItem("・リスト1", "", 0, nil)
    list.AddItem("・リスト2", "説明", 0, nil)
    list.AddItem("・Quit", "", 'q', func() {
        app.Stop()
    })

    // メニュー設定
    mainMenu := tview.NewFlex().
        SetDirection(tview.FlexRow).
        AddItem(list, 0, 1, true)

    mainMenu.SetBorder(true).SetTitle(" Main menu ")

    // -- ページ設定 --
    pages.AddPage("main_menu", mainMenu, true, true)

    // -- アプリ起動処理 --
    if err := app.SetRoot(pages, true).Run(); err != nil {
        panic(err)
    }
}

※tview.NewList()のAddItem()のパラメータについては、左から「表示タイトル」、「説明文」、「ショートカットキー」、「処理」になります。

 

次に以下のコマンドを実行し、サンプルファイルを実行します。

$ docker compose run --rm app go run ./cmd/sample2

 

コマンド実行後、ターミナルで下図のように表示されればOKです。

キーボードから「・Quit」を選択して実行すると終了できます。

 

サンプル3:フォーム表示

次に以下のコマンドを実行し、サンプルファイルを作成します。

$ mkdir -p src/cmd/sample3 && touch src/cmd/sample3/main.go

 

次に作成したファイルを以下のように記述します。

・「src/cmd/sample3/main.go」

package main

import (
    "github.com/rivo/tview"
)

func main() {
    // -- アプリ設定(マウス有効化)--
    app := tview.NewApplication().
        EnableMouse(true)

    // -- ページ定義 --
    pages := tview.NewPages()

    // -- リスト定義 --
    list := tview.NewList()

    // -- リストにアイテム追加 --
    list.AddItem("・リスト1", "", 0, func() {
        textView := tview.NewTextView().
            SetDynamicColors(true).
            SetText("リスト1の内容表示")

        // フォーム設定
        confirmForm := tview.NewForm().
            AddButton("Back", func() {
                // 詳細ページを削除
                pages.RemovePage("detail")
            })

        // 詳細ページ設定
        detail := tview.NewFlex().
            SetDirection(tview.FlexRow).
            AddItem(textView, 2, 1, false).
            AddItem(confirmForm, 0, 1, true)

        // 詳細ページを追加
        pages.AddPage("detail", detail, true, true)
    })

    list.AddItem("・リスト2", "説明", 0, nil)

    list.AddItem("・Quit", "", 'q', func() {
        app.Stop()
    })

    // -- メニュー設定 --
    mainMenu := tview.NewFlex().
        SetDirection(tview.FlexRow).
        AddItem(list, 0, 1, true)

    mainMenu.SetBorder(true).SetTitle(" Main menu ")

    // -- ページ設定 --
    pages.AddPage("main_menu", mainMenu, true, true)

    // -- アプリ起動処理 --
    if err := app.SetRoot(pages, true).Run(); err != nil {
        panic(err)
    }
}

 

次に以下のコマンドを実行し、サンプルファイルを実行します。

$ docker compose run --rm app go run ./cmd/sample3

 

コマンド実行後、上記と同様にリストが表示されるので、「・リスト1」を実行します。

 

「・リスト1」を実行後、下図のように詳細画面が表示されればOKです。

そして、「Back」ボタンにフォーカスが合っているため、実行すると一つ前のリスト画面に戻れます。

 

サンプル4:フォームで入力フィールドの利用

次に以下のコマンドを実行し、サンプルファイルを作成します。

$ mkdir -p src/cmd/sample4 && touch src/cmd/sample4/main.go

 

次に作成したファイルを以下のように記述します。

・「src/cmd/sample4/main.go」

package main

import (
    "fmt"

    "github.com/rivo/tview"
)

func main() {
    // -- アプリ設定(マウス有効化)--
    app := tview.NewApplication().
        EnableMouse(true)

    // -- ページ定義 --
    pages := tview.NewPages()

    // -- リスト定義 --
    list := tview.NewList()

    // -- リストにアイテム追加 --
    list.AddItem("・リスト1", "", 0, nil)

    list.AddItem("・リスト2", "説明", 0, func() {
        textView := tview.NewTextView().
            SetDynamicColors(true).
            SetText("リスト2の内容表示")

        // 入力フィールド設定
        inputField := tview.NewInputField().
            SetFieldWidth(70)

        // フォーム設定
        confirmForm := tview.NewForm().
            AddFormItem(inputField).
            AddButton("Back", func() {
               pages.RemovePage("detail")
            }).
            AddButton("Run", func() {
                // 入力フィールドからテキストを取得
                inputText := inputField.GetText()

                // 実行中モーダル定義
                runningModal := tview.NewModal().SetText("Running......")

                // 実行中モーダルを表示
                pages.AddPage("running_modal", runningModal, true, true)

                // 非同期処理実行
                go func() {
                    // 何らかの処理を実行

                    // 処理完了後の画面更新処理
                    app.QueueUpdateDraw(func() {
                        pages.RemovePage("running_modal")

                        // UIの再描画(強制リフレッシュ)
                        app.Sync()

                        // 成功モーダル設定
                        successModal := tview.NewModal().
                            SetText(fmt.Sprintf("inputText: %s", inputText)).
                            AddButtons([]string{"Close"}).
                            SetDoneFunc(func(buttonIndex int, buttonLabel string) {
                                // Closeボタンの処理
                                pages.RemovePage("success")
                                pages.RemovePage("detail")
                            })

                        // 成功モーダルを表示
                        pages.AddPage("success", successModal, true, true)
                    })
                }()
            })

        // 詳細ページ設定
        detail := tview.NewFlex().
            SetDirection(tview.FlexRow).
            AddItem(textView, 2, 1, false).
            AddItem(confirmForm, 0, 1, true)

        // 詳細ページを追加
        pages.AddPage("detail", detail, true, true)
    })

    list.AddItem("・Quit", "", 'q', func() {
        app.Stop()
    })

    // -- メニュー設定 --
    mainMenu := tview.NewFlex().
        SetDirection(tview.FlexRow).
        AddItem(list, 0, 1, true)

    mainMenu.SetBorder(true).SetTitle(" Main menu ")

    // -- ページ設定 --
    pages.AddPage("main_menu", mainMenu, true, true)

    // -- アプリ起動処理 --
    if err := app.SetRoot(pages, true).Run(); err != nil {
        panic(err)
    }
}

 

次に以下のコマンドを実行し、サンプルファイルを実行します。

$ docker compose run --rm app go run ./cmd/sample4

 

コマンド実行後、上記と同様にリストが表示されるので、「・リスト2」を実行します。

 

「・リスト2」を実行後、下図のようにフォームが表示されればOKです。

 

次に入力フィールドに「Hello !!」を入力し、TABキーで「Run」を選択して実行します。

 

実行後、下図のようにモーダルが表示され、入力したテキストが表示されればOKです。

「Close」ボタンを実行すると、一つ前のリスト画面に戻れます。

 

スポンサーリンク

最後に

今回はGo言語(Golang)の「tview」の使い方について解説しました。

今回は基本的な部分だけご紹介しましたが、ループ処理や適宜関数化などを組み合わせながら実装すると、ターミナルUIを構築できます。

Go言語でツールを作ったりする際にTUIが必要になったりするので、その際はぜひ参考にしてみて下さい。

 

この記事を書いた人
Tomoyuki

SE→ブロガーを経て、現在はSoftware Engineer(Web/Gopher)をしています!

Tomoyukiをフォローする
3. 応用
スポンサーリンク
Tomoyukiをフォローする

コメント

タイトルとURLをコピーしました