跳转至

Charm 终端 UI 库

为什么要学 Charm

Charm 是一个用 Go 编写的终端 UI 生态系统,包含多个库和工具:Bubble Tea(TUI 框架)、Lip Gloss(样式)、Bubbles(组件)等。它让你能够用 Go 语言构建美观的终端应用。Charm 生态是现代终端工具(Glow、VHS、Slides 等)的基石,学习它可以构建自己的专业级终端工具。


核心概念

概念白话解释用途
Bubble TeaTUI 框架基于 Elm 架构的终端 UI 框架
Lip Gloss样式引擎终端文本的 CSS 式样式
Bubbles组件库预构建的 UI 组件(输入框、列表等)
Huh表单库构建终端交互式表单
Log日志库美化的终端日志输出
WishSSH 应用通过 SSH 服务 TUI 应用

安装配置

# 创建 Go 项目
mkdir my-tui && cd my-tui
go mod init my-tui

# 安装核心库
go get github.com/charmbracelet/bubbletea
go get github.com/charmbracelet/lipgloss
go get github.com/charmbracelet/bubbles
go get github.com/charmbracelet/huh
go get github.com/charmbracelet/log

快速上手

Lip Gloss 样式

package main

import (
    "fmt"
    "github.com/charmbracelet/lipgloss"
)

func main() {
    // 定义样式
    style := lipgloss.NewStyle().
        Bold(true).
        Foreground(lipgloss.Color("#FAFAFA")).
        Background(lipgloss.Color("#7D56F4")).
        PaddingTop(1).
        PaddingLeft(4).
        PaddingRight(4).
        PaddingBottom(1).
        MarginTop(1).
        Width(40).
        Align(lipgloss.Center).
        BorderStyle(lipgloss.RoundedBorder()).
        BorderForeground(lipgloss.Color("#874BFD"))

    fmt.Println(style.Render("Hello, Charm! 🎉"))

    // 组合布局
    left := lipgloss.NewStyle().Width(20).Align(lipgloss.Left).Render("Left")
    right := lipgloss.NewStyle().Width(20).Align(lipgloss.Right).Render("Right")
    row := lipgloss.JoinHorizontal(lipgloss.Top, left, right)
    fmt.Println(row)
}

Bubble Tea 应用

package main

import (
    "fmt"
    tea "github.com/charmbracelet/bubbletea"
)

// Model
type model struct {
    choices  []string
    cursor   int
    selected map[int]struct{}
}

func initialModel() model {
    return model{
        choices:  []string{"Python", "Go", "Rust", "JavaScript"},
        selected: make(map[int]struct{}),
    }
}

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

// Update
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyMsg:
        switch msg.String() {
        case "q", "ctrl+c":
            return m, tea.Quit
        case "up", "k":
            if m.cursor > 0 { m.cursor-- }
        case "down", "j":
            if m.cursor < len(m.choices)-1 { m.cursor++ }
        case " ", "enter":
            if _, ok := m.selected[m.cursor]; ok {
                delete(m.selected, m.cursor)
            } else {
                m.selected[m.cursor] = struct{}{}
            }
        }
    }
    return m, nil
}

// View
func (m model) View() string {
    s := "选择你喜欢的语言:\n\n"
    for i, choice := range m.choices {
        cursor := " "
        if m.cursor == i { cursor = ">" }
        checked := " "
        if _, ok := m.selected[i]; ok { checked = "x" }
        s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice)
    }
    s += "\n按 q 退出\n"
    return s
}

func main() {
    p := tea.NewProgram(initialModel())
    if _, err := p.Run(); err != nil {
        fmt.Printf("Error: %v", err)
    }
}

Huh 表单

package main

import (
    "fmt"
    "github.com/charmbracelet/huh"
)

func main() {
    var name string
    var lang string
    var confirm bool

    form := huh.NewForm(
        huh.NewGroup(
            huh.NewInput().
                Title("你的名字").
                Value(&name),
            huh.NewSelect[string]().
                Title("首选语言").
                Options(
                    huh.NewOption("Python", "python"),
                    huh.NewOption("Go", "go"),
                    huh.NewOption("Rust", "rust"),
                ).
                Value(&lang),
            huh.NewConfirm().
                Title("确认提交?").
                Value(&confirm),
        ),
    )

    err := form.Run()
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    if confirm {
        fmt.Printf("你好 %s,你选择了 %s!\n", name, lang)
    }
}

进阶用法

使用 Bubbles 组件

import (
    "github.com/charmbracelet/bubbles/spinner"
    "github.com/charmbracelet/bubbles/progress"
    "github.com/charmbracelet/bubbles/textinput"
    "github.com/charmbracelet/bubbles/list"
    "github.com/charmbracelet/bubbles/table"
    "github.com/charmbracelet/bubbles/viewport"
)

// Spinner(加载动画)
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))

// Progress(进度条)
p := progress.New(progress.WithDefaultGradient())

// TextInput(文本输入)
ti := textinput.New()
ti.Placeholder = "输入搜索关键词..."
ti.Focus()

// Table(表格)
t := table.New(
    table.WithColumns(columns),
    table.WithRows(rows),
    table.WithFocused(true),
    table.WithHeight(10),
)

美化日志

import "github.com/charmbracelet/log"

log.Info("服务已启动", "port", 8080)
log.Warn("磁盘空间不足", "usage", "85%")
log.Error("连接失败", "err", err)
log.Debug("处理请求", "path", "/api/users")

// 自定义样式
logger := log.NewWithOptions(os.Stderr, log.Options{
    ReportTimestamp: true,
    TimeFormat:      time.Kitchen,
    Prefix:          "MyApp",
})

常见问题

Q: 与其他 TUI 框架比较?

  • Bubble Tea (Go):Elm 架构、组件丰富、Charm 生态
  • Textual (Python):功能最强、CSS 式布局
  • Ratatui (Rust):性能最好、更底层

Q: 如何发布终端应用?

编译为单一二进制,通过 GitHub Releases、Homebrew、apt 等分发。


参考资源

  • Charm 官网:https://charm.sh/
  • Bubble Tea:https://github.com/charmbracelet/bubbletea
  • Lip Gloss:https://github.com/charmbracelet/lipgloss
  • Bubbles:https://github.com/charmbracelet/bubbles
  • Huh:https://github.com/charmbracelet/huh
  • 教程:https://github.com/charmbracelet/bubbletea/tree/master/tutorials