Back

Создание терминальных интерфейсов с помощью Charm

Создание терминальных интерфейсов с помощью Charm

Если вы когда-либо использовали lazygit, k9s или htop, вы испытали на себе, каким может быть хорошо построенный терминальный интерфейс — отзывчивым, структурированным и удивительно элегантным. Раньше создание чего-то подобного означало борьбу с ncurses или написание необработанных escape-кодов. Экосистема Charm полностью меняет это.

В этой статье мы рассмотрим, как создавать терминальные интерфейсы с помощью Charm, используя Go, с акцентом на Bubble Tea и сопутствующие библиотеки. Текущие основные версии библиотек Charm используют обновленные пути модулей Go, что отражено в примерах ниже. Если вы мыслите компонентами и состоянием, ментальная модель покажется вам сразу знакомой.

Ключевые выводы

  • Bubble Tea следует архитектуре Elm (Model, Update, View), что делает управление состоянием предсказуемым и композируемым.
  • Lip Gloss обеспечивает декларативную стилизацию терминального вывода, а Bubbles предлагает готовые UI-компоненты, такие как текстовые поля ввода, спиннеры и области просмотра.
  • Библиотеки экосистемы Charm чисто наслаиваются друг на друга, позволяя создавать отполированные TUI на Go без необработанных escape-кодов или ncurses.
  • Huh и Wish расширяют стек для форм и TUI, размещенных через SSH, соответственно.

Что такое TUI и зачем его создавать?

Терминальный пользовательский интерфейс (TUI) — это интерактивное, визуально структурированное приложение, которое работает внутри терминала. В отличие от обычного CLI, который принимает команду и завершает работу, TUI поддерживает состояние, реагирует на ввод с клавиатуры в реальном времени и отрисовывает динамические макеты.

TUI стоит создавать, когда вам нужно:

  • Инструмент для разработчиков, который работает через SSH без браузера
  • Меньшее потребление ресурсов по сравнению с приложением на Electron
  • Что-то скриптуемое, но при этом выглядящее отполированным

Экосистема Charm: общий обзор

Charm поддерживает несколько библиотек, которые хорошо работают вместе:

БиблиотекаРоль
Bubble TeaФреймворк приложения (цикл событий, состояние)
Lip GlossСтилизация и компоновка
BubblesГотовые UI-компоненты
HuhПримитивы форм и ввода
WishSSH-сервер для удаленного размещения TUI

Bubble Tea — это ядро. Всё остальное наслаивается поверх него.

Как работает архитектура Bubble Tea (Model, Update, View)

Bubble Tea следует архитектуре Elm — паттерну, который фронтенд-разработчики узнают по Redux или useReducer в React.

Каждое приложение Bubble Tea определяет три вещи:

  • 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 используют пути модулей charm.land, а не старые импорты github.com/charmbracelet/*, которые встречаются во многих старых руководствах.

Добавление макетов и стилизации с помощью 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 справится с этим без необходимости создавать всё с нуля. Для размещения TUI через SSH, чтобы пользователи могли получить к нему доступ без локальной установки, 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 будет не столько изучением новой парадигмы, сколько применением той, которую вы уже знаете — просто в терминале.

Часто задаваемые вопросы

Bubble Tea — это библиотека Go, поэтому вам нужно хотя бы базовое понимание Go для её использования. Тем не менее, архитектура, вдохновленная Elm, довольно проста. Если вы знакомы с концепциями состояния, сообщений и функций отрисовки из любого языка, вы сможете относительно быстро освоить специфичный для Go синтаксис.

Текущие релизы используют пути модулей charm.land, такие как charm.land/bubbletea/v2, вместо старых импортов github.com/charmbracelet, встречающихся во многих старых руководствах. Функция 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