Bubbles
GitHub概要
スター6,625
ウォッチ23
フォーク319
作成日:2020年1月18日
言語:Go
ライセンス:MIT License
トピックス
clielm-architecturehacktoberfestterminaltui
スター履歴
データ取得日時: 2025/7/25 11:09
Bubbles
Bubblesは、Bubble Teaフレームワーク用の公式コンポーネントライブラリです。テキスト入力、リスト、スピナー、プログレスバーなど、一般的なTUIコンポーネントを提供し、開発を大幅に加速します。Charmbraceletエコシステムの一部として、高品質なコンポーネントが継続的に追加されています。
特徴
豊富なコンポーネント
- 入力: textinput、textarea、textinput
- 選択: list、filepicker、table
- 表示: viewport、spinner、progress
- ナビゲーション: paginator、help
- 特殊: timer、stopwatch、cursor
Bubble Teaとの統合
- Modelベース: Elmアーキテクチャに準拠
- メッセージパッシング: コンポーネント間の通信
- カスタマイズ可能: 外観と動作の調整
- 組み合わせ可能: 複数コンポーネントの統合
スタイリング
- Lip Gloss統合: 美しいデフォルトスタイル
- テーマ対応: カスタムテーマの適用
- アダプティブ: ターミナル能力に応じた調整
- アニメーション: スムーズな動作
生産性
- すぐに使える: 最小限の設定で動作
- ドキュメント充実: 詳細な使用例
- テスト済み: 高品質なコード
- メンテナンス: Charmbraceletによる継続的更新
基本的な使用方法
インストール
go get github.com/charmbracelet/bubbles
テキスト入力
package main
import (
"fmt"
"os"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
)
type model struct {
textInput textinput.Model
err error
}
func initialModel() model {
ti := textinput.New()
ti.Placeholder = "Enter your name"
ti.Focus()
ti.CharLimit = 156
ti.Width = 20
return model{
textInput: ti,
err: nil,
}
}
func (m model) Init() tea.Cmd {
return textinput.Blink
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
}
case error:
m.err = msg
return m, nil
}
m.textInput, cmd = m.textInput.Update(msg)
return m, cmd
}
func (m model) View() string {
return fmt.Sprintf(
"What's your name?\n\n%s\n\n%s",
m.textInput.View(),
"(esc to quit)",
) + "\n"
}
func main() {
if _, err := tea.NewProgram(initialModel()).Run(); err != nil {
fmt.Printf("could not start program: %s\n", err)
os.Exit(1)
}
}
リストコンポーネント
package main
import (
"fmt"
"os"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
var docStyle = lipgloss.NewStyle().Margin(1, 2)
type item string
func (i item) FilterValue() string { return string(i) }
type model struct {
list list.Model
}
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.KeyMsg:
if msg.String() == "ctrl+c" {
return m, tea.Quit
}
case tea.WindowSizeMsg:
h, v := docStyle.GetFrameSize()
m.list.SetSize(msg.Width-h, msg.Height-v)
}
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}
func (m model) View() string {
return docStyle.Render(m.list.View())
}
func main() {
items := []list.Item{
item("🍃 Bubble Tea"),
item("🌈 Lip Gloss"),
item("🍕 Bubbles"),
item("🎆 Charm"),
item("🍓 Glow"),
item("🍰 Soft Serve"),
}
m := model{list: list.New(items, list.NewDefaultDelegate(), 0, 0)}
m.list.Title = "Charmbracelet Tools"
p := tea.NewProgram(m, tea.WithAltScreen())
if _, err := p.Run(); err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
}
}
プログレスバーとスピナー
package main
import (
"fmt"
"os"
"strings"
"time"
"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type model struct {
spinner spinner.Model
progress progress.Model
done bool
}
var (
currentPkgNameStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211"))
doneStyle = lipgloss.NewStyle().Margin(1, 2)
checkMark = lipgloss.NewStyle().Foreground(lipgloss.Color("42")).SetString("✓")
)
func newModel() model {
p := progress.New(
progress.WithDefaultGradient(),
progress.WithWidth(40),
progress.WithoutPercentage(),
)
s := spinner.New()
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
return model{
spinner: s,
progress: p,
}
}
func (m model) Init() tea.Cmd {
return tea.Batch(
spinner.Tick,
tickProgress(),
)
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
return m, tea.Quit
case tea.WindowSizeMsg:
m.progress.Width = msg.Width - 4
if m.progress.Width > 40 {
m.progress.Width = 40
}
return m, nil
case spinner.TickMsg:
var cmd tea.Cmd
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
case progressMsg:
var cmds []tea.Cmd
if msg >= 1.0 {
m.done = true
return m, tea.Quit
}
var cmd tea.Cmd
m.progress, cmd = m.progress.Update(msg)
cmds = append(cmds, cmd, tickProgress())
return m, tea.Batch(cmds...)
}
return m, nil
}
func (m model) View() string {
if m.done {
return doneStyle.Render(fmt.Sprintf("%s Done!\n", checkMark))
}
pkgName := currentPkgNameStyle.Render("bubbles")
prog := m.progress.View()
cellsRemaining := m.progress.Width - lipgloss.Width(prog)
gap := strings.Repeat(" ", cellsRemaining)
return "\n" +
m.spinner.View() + " Installing " + pkgName + gap + prog + "\n\n" +
"Press any key to quit\n"
}
type progressMsg float64
func tickProgress() tea.Cmd {
return tea.Tick(time.Millisecond*100, func(t time.Time) tea.Msg {
return progressMsg(time.Since(time.Now()).Seconds() / 3)
})
}
func main() {
if _, err := tea.NewProgram(newModel()).Run(); err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
}
}
テーブルコンポーネント
package main
import (
"fmt"
"os"
"github.com/charmbracelet/bubbles/table"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
var baseStyle = lipgloss.NewStyle().
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("240"))
type model struct {
table table.Model
}
func (m model) Init() tea.Cmd { return nil }
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "esc":
if m.table.Focused() {
m.table.Blur()
} else {
m.table.Focus()
}
case "q", "ctrl+c":
return m, tea.Quit
}
}
m.table, cmd = m.table.Update(msg)
return m, cmd
}
func (m model) View() string {
return baseStyle.Render(m.table.View()) + "\n"
}
func main() {
columns := []table.Column{
{Title: "Rank", Width: 4},
{Title: "City", Width: 10},
{Title: "Country", Width: 10},
{Title: "Population", Width: 10},
}
rows := []table.Row{
{"1", "Tokyo", "Japan", "37,274,000"},
{"2", "Delhi", "India", "32,065,760"},
{"3", "Shanghai", "China", "28,516,904"},
{"4", "Dhaka", "Bangladesh", "22,478,116"},
{"5", "São Paulo", "Brazil", "22,429,800"},
}
t := table.New(
table.WithColumns(columns),
table.WithRows(rows),
table.WithFocused(true),
table.WithHeight(7),
)
s := table.DefaultStyles()
s.Header = s.Header.
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.Color("240")).
BorderBottom(true).
Bold(false)
s.Selected = s.Selected.
Foreground(lipgloss.Color("229")).
Background(lipgloss.Color("57")).
Bold(false)
t.SetStyles(s)
m := model{t}
if _, err := tea.NewProgram(m).Run(); err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
}
}
高度な機能
ファイルピッカー
import "github.com/charmbracelet/bubbles/filepicker"
fp := filepicker.New()
fp.AllowedTypes = []string{".txt", ".md", ".go"}
fp.CurrentDirectory, _ = os.Getwd()
ビューポート
import "github.com/charmbracelet/bubbles/viewport"
vp := viewport.New(80, 20)
vp.SetContent("Long text content...")
vp.KeyMap = viewport.DefaultKeyMap()
タイマーとストップウォッチ
import (
"github.com/charmbracelet/bubbles/timer"
"github.com/charmbracelet/bubbles/stopwatch"
)
// タイマー
t := timer.NewWithInterval(5*time.Minute, time.Second)
// ストップウォッチ
sw := stopwatch.NewWithInterval(time.Millisecond * 100)
カスタムコンポーネントの作成
type customInput struct {
textinput.Model
label string
err error
}
func NewCustomInput(label string) customInput {
ti := textinput.New()
ti.Placeholder = "Enter " + label
ti.CharLimit = 50
ti.Width = 30
return customInput{
Model: ti,
label: label,
}
}
func (c customInput) View() string {
return fmt.Sprintf("%s: %s", c.label, c.Model.View())
}
エコシステム
関連プロジェクト
- Bubble Tea: ベースフレームワーク
- Lip Gloss: スタイリングライブラリ
- Harmonica: アニメーションライブラリ
- Log: ログライブラリ
採用例
- GitHub CLI: 公式ツールで使用
- Charm製ツール: Glow、Soft Serveなど
- コミュニティプロジェクト: 多数のTUIアプリ
利点
- 生産性: すぐに使えるコンポーネント
- 品質: テスト済みで安定
- 一貫性: 統一されたAPI設計
- カスタマイズ: 柔軟な拡張性
- メンテナンス: 活発な開発
制約事項
- Bubble Tea依存: 単体では動作しない
- 学習曲線: Elmアーキテクチャの理解が必要
- コンポーネント限定: 提供されていない機能は自作
他のライブラリとの比較
項目 | Bubbles | tview | termui |
---|---|---|---|
アーキテクチャ | Elm | ウィジェット | ウィジェット |
コンポーネント数 | 中 | 多 | 中 |
カスタマイズ性 | 非常に高 | 中 | 低 |
学習コスト | 中 | 低 | 低〜中 |
エコシステム | Charmbracelet | 独立 | 独立 |
まとめ
Bubblesは、Bubble TeaフレームワークでTUIアプリケーションを構築する際に必須のコンポーネントライブラリです。高品質なコンポーネントが提供されているため、開発者はアプリケーションロジックに集中でき、生産性が大幅に向上します。Charmbraceletエコシステムの一部として、継続的な改善と新しいコンポーネントの追加が期待できます。