Back

Charmでターミナル UI を構築する

Charmでターミナル UI を構築する

lazygitk9s、またはhtopを使ったことがあれば、よく構築されたターミナル UI がどのようなものか体験したことがあるでしょう。レスポンシブで構造化され、驚くほどエレガントです。このようなものを構築するには、ncurses と格闘したり、生のエスケープコードを書いたりする必要がありました。Charmエコシステムは、それを完全に変えます。

この記事では、Go を使用して Charm でターミナル UI を構築する方法を、Bubble Teaとその関連ライブラリに焦点を当てて説明します。Charm ライブラリの現在のメジャーバージョンは、更新された Go モジュールパスを使用しており、以下の例にそれが反映されています。コンポーネントと状態で考える場合、メンタルモデルはすぐに馴染みのあるものに感じられるでしょう。

重要なポイント

  • Bubble Tea は Elm アーキテクチャ(Model、Update、View)に従っており、状態管理を予測可能で構成可能にします。
  • Lip Gloss はターミナル出力の宣言的なスタイリングを提供し、Bubbles はテキスト入力、スピナー、ビューポートなどのすぐに使える UI コンポーネントを提供します。
  • Charm エコシステムのライブラリは互いにクリーンに重ね合わせることができ、生のエスケープコードや ncurses を使わずに Go で洗練された TUI を構築できます。
  • Huh と Wish は、それぞれフォームと SSH ホスト型 TUI のためにスタックを拡張します。

TUI とは何か、なぜ構築するのか?

**ターミナルユーザーインターフェース(TUI)**は、ターミナル内で動作するインタラクティブで視覚的に構造化されたアプリケーションです。コマンドを受け取って終了する単純な CLI とは異なり、TUI は状態を維持し、リアルタイムでキーボード入力に応答し、動的なレイアウトをレンダリングします。

TUI は以下のような場合に構築する価値があります:

  • ブラウザなしで SSH 経由で動作する開発者ツール
  • Electron アプリよりも低いリソースオーバーヘッド
  • スクリプト可能でありながら洗練された感じのもの

Charm エコシステムの概要

Charm は、互いにうまく連携するいくつかのライブラリを保守しています:

ライブラリ役割
Bubble Teaアプリケーションフレームワーク(イベントループ、状態)
Lip Glossスタイリングとレイアウト
Bubbles事前構築された UI コンポーネント
Huhフォームと入力プリミティブ
WishTUI をリモートでホストするための SSH サーバー

Bubble Tea がコアです。他のすべてはその上に重ねられます。

Bubble Tea のアーキテクチャの仕組み(Model、Update、View)

Bubble Tea は Elm アーキテクチャに従っており、これは Redux や React の useReducer からフロントエンド開発者が認識するパターンです。

すべての Bubble Tea アプリは 3 つのものを定義します:

  • Model — アプリケーションの状態
  • Update — メッセージを処理し、新しいモデルを返す関数
  • View — モデルをターミナルビューとしてレンダリングする関数
package main

import (
    "fmt"
    "log"

    tea "charm.land/bubbletea/v2"
)

type model struct {
    count int
}

func (m model) Init() tea.Cmd {
    return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyPressMsg:
        switch msg.String() {
        case "up":
            m.count++
        case "q", "ctrl+c":
            return m, tea.Quit
        }
    }
    return m, nil
}

func (m model) View() tea.View {
    return tea.NewView(fmt.Sprintf("Count: %d\n(press up to increment, q to quit)", m.count))
}

func main() {
    p := tea.NewProgram(model{})
    if _, err := p.Run(); err != nil {
        log.Fatal(err)
    }
}

charm.land/bubbletea/v2 インポートパスに注目してください。Charm ライブラリの現在のリリースは、多くの古いチュートリアルに登場する古い github.com/charmbracelet/* インポートではなく、charm.land モジュールパスを使用しています。

Lip Gloss でレイアウトとスタイリングを追加する

Lip Gloss はスタイリングを処理します。スタイルを値として定義し、レンダリング前に文字列に適用します。

import "charm.land/lipgloss/v2"

var titleStyle = lipgloss.NewStyle().
    Bold(true).
    Foreground(lipgloss.Color("#FF79C6")).
    Padding(0, 1)

func (m model) View() tea.View {
    return tea.NewView(titleStyle.Render("My TUI App"))
}

注意: Lip Gloss の最近のバージョンでは、適応的な色の動作の処理方法が変更されました。背景検出とスタイル適応は、ライブラリによって自動的に処理されるのではなく、アプリケーションコードでより明示的に処理されるようになりました。

Bubbles から事前構築されたコンポーネントを使用する

Bubbles は、すぐに使えるコンポーネント(テキスト入力、スピナー、プログレスバー、ビューポートなど)を提供します。各コンポーネントは同じ Model/Update/View 契約に従っているため、独自のモデルに直接埋め込むことができます。

import "charm.land/bubbles/v2/textinput"

type model struct {
    input textinput.Model
}

この構成可能性が、Bubble Tea をクリーンにスケールさせるものです。小さく焦点を絞ったモデルを構築し、それらをより大きなモデルに構成します。これは、フロントエンドフレームワークでコンポーネントを構成するのとまったく同じです。

Huh または Wish を使用するタイミング

TUI にフォーム(複数フィールド入力、確認、選択メニュー)が必要な場合、Huh がゼロから構築することなくそれを処理します。ユーザーがローカルに何もインストールせずにアクセスできるように、SSH 経由で TUI をホストする場合、Wish が Bubble Tea プログラムを SSH サーバーでラップします。

はじめに

go mod init mytui
go get charm.land/bubbletea/v2
go get charm.land/lipgloss/v2
go get charm.land/bubbles/v2

プログラムを次のように実行します:

func main() {
    p := tea.NewProgram(model{})
    if _, err := p.Run(); err != nil {
        log.Fatal(err)
    }
}

まとめ

Charm Bubble Tea スタックは、Go でターミナルアプリケーションを構築するための構造化された構成可能な方法を提供します。Model/Update/View パターンは状態を予測可能に保ち、Lip Gloss は宣言的にスタイリングを処理し、Bubbles は何日もかけて書くことになるコンポーネントを提供します。コンポーネントベースのフロントエンド思考に慣れている場合、Charm でターミナル UI を構築することは、新しいパラダイムを学ぶというよりも、すでに知っているものを適用することのように感じられるでしょう。ただし、ターミナルで。

よくある質問

Bubble Tea は Go ライブラリなので、使用するには少なくとも Go の基本的な理解が必要です。とはいえ、Elm にインスパイアされたアーキテクチャは分かりやすいです。任意の言語から状態、メッセージ、レンダリング関数などの概念に精通している場合、Go 固有の構文を比較的早く習得できます。

現在のリリースは、多くの古いチュートリアルに見られる古い github.com/charmbracelet インポートの代わりに、charm.land/bubbletea/v2 などの charm.land モジュールパスを使用しています。Init 関数はコマンドのみを返し、View 関数は生の文字列ではなくターミナルビューを返します。

はい。Update は、メッセージを受け取り、新しいモデルとコマンドを返す純粋関数であるため、特定のメッセージで Update を直接呼び出し、返されたモデルの状態をアサートすることで、アプリケーションロジックをユニットテストできます。View 関数も、レンダリングされた出力をチェックすることでテストできます。

はい。Wish ライブラリを使用すると、任意の Bubble Tea プログラムを SSH サーバーでラップできます。ユーザーは SSH 経由で接続し、ローカルに何もインストールせずにターミナルで直接 TUI と対話します。これは、共有開発者ツール、ダッシュボード、またはインタラクティブなデモに役立ちます。

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay