bubble-tea

star 0

学习和复用 Charmbracelet Bubble Tea / Bubbles / Lip Gloss 的组件写法。

darktire By darktire schedule Updated 6/5/2026

name: bubble-tea description: > 学习和复用 Charmbracelet Bubble Tea / Bubbles / Lip Gloss 的组件写法。 disable-model-invocation: true license: MIT

Bubble Tea 组件索引

本技能用于让 LLM 在编写或调整 Bubble Tea / Bubbles / Lip Gloss 代码前,先吸收本文件中的组件模型、生命周期接入点和常见坑;实现时按任务读取 snippets/ 中的 Go 片段作为可复用知识材料,而不是把片段机械复制成固定框架。

通用接入规则

  • 先分发消息:退出、确认、窗口尺寸等应用级消息优先处理;但输入框、textarea 或 list 正在输入/过滤时,先让组件消费普通按键,避免全局快捷键抢输入。
  • 子组件更新后必须写回:Bubbles 多为值类型,component.Update(msg) 返回的新组件值要写回 model,得到的 tea.Cmd 也要继续返回或合并。
  • 副作用只放在 tea.Cmd:阻塞 I/O、轮询、等待、channel 监听都放进 tea.CmdCmd 只返回 tea.MsgUpdate 改状态,不要在 Cmd 里直接改 model;在 Cmd 中打开的资源也在 Cmd 内关闭。
  • 正确返回命令:Init / Update 返回的是命令值本身,不要提前调用;需要参数时写返回 tea.Cmd 的工厂函数。spinner/progress/timer 等组件返回的后续 cmd 也不要丢。
  • View 只渲染状态:不要在 View 里改 model、启动工作或读取外部资源。
  • 布局和命令编排:在 WindowSizeMsg 中同步 viewport、list、progress 等依赖终端尺寸的组件;并发命令用 tea.Batch,必须按顺序执行时用 tea.Sequence

根生命周期骨架

type model struct {
    // 组件状态:input textinput.Model、items list.Model、viewport viewport.Model
    // 业务状态:selectedPath string、loading bool、err error
}

func newModel() model {
    return model{
        // 创建组件;组件创建函数见 snippets
    }
}

func (m model) Init() tea.Cmd {
    // blink、tick、timer init、首次加载、终端能力请求等启动命令
    return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyPressMsg:
        switch msg.String() {
        case "q", "esc", "ctrl+c":
            return m, tea.Quit
        }
    case tea.WindowSizeMsg:
        // 同步依赖终端尺寸的组件宽高
    }

    // 转发给子组件,并把新组件值写回 m
    // var cmd tea.Cmd
    // m.input, cmd = m.input.Update(msg)
    // return m, cmd

    return m, nil
}

func (m model) View() tea.View {
    v := tea.NewView("")
    // v.AltScreen = true
    // v.MouseMode = tea.MouseModeCellMotion
    // v.Cursor = ...
    return v
}

组件与模式表

场景 字段 / 状态 代码片段 接入点
Program 启动选项 / 事件过滤 opts []tea.ProgramOption, filter func(tea.Model, tea.Msg) tea.Msg program.go: newProgram, preventQuitFilter, rendererOptions main: tea.NewProgram(newModel(), opts...); WithFilter: 拦截 tea.QuitMsg; daemon/非 TTY 可 WithoutRenderer
单行输入 / 搜索框 input textinput.Model inputs.go: newTextInput, updateTextInput, textInputCursor, textInputValue, setTextInputValue, setTextInputValueAtEnd Init: textinput.Blink; Update: 应用按键后转发; View: input.View();真实光标写入 tea.View.Cursor
管道输入 / stdin 初值 initial string, input textinput.Model inputs.go: stdinHasPipedInput, readTrimmedInput, setTextInputValueAtEnd main: 检测 stdin 并读取;newModel: SetValueCursorEnd; Init: blink
多字段表单 focus int, inputs []textinput.Model inputs.go: newTextFields, moveFormFocus, updateTextFields, textFieldValues tab/up/down: 切焦点; 普通输入: 批量转发; 提交: 取值
自动建议 textinput.Model + 建议状态 inputs.go: enableSuggestions, suggestionKeysEnabled; runtime.go: runWork, debounce, isCurrentDebounce 输入变化递增 token;防抖消息先比对 token;结果消息更新 suggestions;建议快捷键按候选状态启停
多行输入 area textarea.Model inputs.go: newTextarea, setTextareaDarkStyle, updateTextarea, resizeTextarea, textareaCursor Init: textarea.Blink,需要背景适配时加 tea.RequestBackgroundColor; WindowSizeMsg: resize; View: cursor
动态多行输入 area textarea.Model inputs.go: newDynamicTextarea, getTextareaMetrics 初始化 DynamicHeight/MinHeight/MaxHeight/ShowLineNumbers; View: 可显示高度、行列、滚动百分比
原生轻量列表 / 多选 cursor int, choices []string, selected map[int]struct{} raw_selection.go: moveCursorBounded, toggleIndexSelection, indexSelected, selectedCursorItem, renderChecklist 项目确认页、一次性多选、小菜单可直接手写状态;up/downj/k 移动;enter/space 切换;不需要过滤、分页或 delegate 时别急着上 list.Model
列表 / 菜单 items list.Model selection.go: optionItem, newOptionList, resizeList, updateList, selectedOption WindowSizeMsg: resize; Update: 转发; enter: 读选中项
列表动作 / 自定义 delegate list.Model, delegateKeys, appKeys selection.go: listIsFiltering, setListChrome, newActionDelegate, addAdditionalFullHelpKeys, insertListItemWithStatus, removeSelectedListItemWithStatus 过滤中跳过应用级快捷键;delegate UpdateFunc 处理 item 动作;状态提示用 NewStatusMessage
交互式表格 grid table.Model selection.go: newSelectableTable, updateSelectableTable, selectedTableRow enter: 读行; esc: focus/blur; 其他消息转发
静态表格 *ltable.Table selection.go: newStaticTable, resizeStaticTable 只展示;lipgloss/table 建议别名 ltable;复杂行列样式用 table 的 StyleFunc
分页 pager paginator.Model selection.go: newPaginator, updatePaginator, pageItems 翻页消息转发;View: 渲染当前页和 pager.View();数据量变化后同步总页数
文件选择 picker filepicker.Model, selectedPath string selection.go: newFilePicker, updateFilePicker Init: picker.Init(); 选择成功写路径;disabled file 显示错误并按需延迟清理
滚动内容 viewport viewport.Model, ready bool display.go: newViewport, ensureViewport, resizeViewport, resizeViewportWithChrome, updateViewport, viewportFooter 首次 WindowSizeMsg 后初始化;扣除 header/footer;键盘/鼠标滚动转发
Chat / log 追加到底部 messages []string, viewport viewport.Model, content string display.go: setViewportContentBottom, appendViewportMessage 新消息后 SetContent + GotoBottom; resize 后重排内容再保持底部
快捷键帮助 keys appKeys, help help.Model display.go: newAppKeys, ShortHelp, FullHelp, updateHelpView 应用级按键用 key.Matches; 底部 help.View(keys); ? 消费后不要再触发业务动作
加载动画 spin spinner.Model runtime.go: newSpinner, updateSpinner; shared.go: batch Init: spin.Tick; 每次动画 cmd 要返回,否则动画停止
进度条 bar progress.Model runtime.go: newProgress, resizeProgress, setProgress, updateProgressFrame, completeProgressAfterPause 业务消息更新 percent;FrameMsg 单独转发;WindowSizeMsg: 同步宽度;完成后可 pause 再 quit
倒计时 / 秒表 timer/stopwatch model, start/stop/reset keys runtime.go: updateTimer, updateStopwatch 启动命令放 Init; 暂停/继续/重置在应用层;按运行状态启停 key binding
异步任务 / 防抖 / channel 业务状态 + 消息类型 runtime.go: runWork, every, debounce, isCurrentDebounce, waitFor; shared.go: sequence 阻塞 I/O 包成 tea.Cmd; channel 事件回流主循环;连续监听要收到一次后再次返回 waitFor
外部 goroutine 发消息 p *tea.Program, 业务 resultMsg/progressMsg/errorMsg external.go: sendEvery, sendResult 创建 program 后启动 worker;worker 调 p.Send(msg)Update 统一处理消息;退出时用 stop channel 收尾
外部进程 / 编辑器 err error, 可选 altscreenActive bool external.go: execProcess, execEditor, externalResultMsg Update 中按键返回 tea.ExecProcess; 完成消息处理 err;交互式外部程序不要用普通 runWork
挂起 / 恢复 suspending bool terminal.go: requestSuspend, didResume ctrl+z: 返回 tea.Suspend; tea.ResumeMsg: 清理 suspend 状态、刷新视图
背景色自适应 / 终端能力 dark bool, styles, capability 状态 terminal.go: requestBackgroundColor, backgroundIsDark, requestWindowSize, requestCapability, capabilityFromMsg Init: 请求背景色或能力;Update: BackgroundColorMsg 后用 msg.IsDark() 重建 styles
键盘增强 / KeyRelease supportsEventTypes bool terminal.go: keyboardEnhancementsFromMsg; shared.go: newTUIView View: ReportKeyReleases; Update: KeyboardEnhancementsMsg, KeyReleaseMsg
焦点报告 focused bool terminal.go: focusFromMsg; shared.go: newTUIView View: ReportFocus; Update: FocusMsg / BlurMsg
鼠标事件 mouseMode tea.MouseMode, hover/selection 状态 shared.go: newTUIView, mousePositionFromMsg, setViewMouseHandler View: MouseModeCellMotionMouseModeAllMotion; Update: MouseMsg; 需要 view-local hit test 时用 OnMouse 转领域消息
View 终端外观 title, fg/bg/cursor color, flags shared.go: viewOptions, newTUIView View: 设置 WindowTitle, ForegroundColor, BackgroundColor, Cursor, AltScreen, mouse/focus/keyboard flags
临时输出 无或业务状态 terminal.go: printStatus, printAndQuit Update: 返回 tea.Printf/tea.Println;退出前用 tea.Sequence(tea.Printf(...), tea.Quit)
多视图 / 子模型组合 mode, active child, 多个 child cmds shared.go: batch, sequence; 各组件 update* helpers Update: 转发给 active child;多个 cmd 用 batch; View: 渲染 active view;cursor 从 active child 透传

Reference Links

Install via CLI
npx skills add https://github.com/darktire/skills --skill bubble-tea
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator