mcpField
G
gentleman-bubbletea

gentleman-bubbletea

github.com/gentleman-programming开发7.7k88 次安装今天更新
代码仓库

关于 gentleman-bubbletea

gentleman-bubbletea 是一项用于扩展基于 Bubble Tea 和 Lipgloss 的 Go TUI 安装程序的技能,说明屏幕状态、键盘处理、渲染和测试的约定。

主要功能

  1. 01在 model.go 定义屏幕和状态
  2. 02通过 Update() 分发输入
  3. 03为各屏幕设置按键处理器
  4. 04添加屏幕渲染和标题
  5. 05构建并测试安装程序

适用场景

  1. 01添加安装程序界面
  2. 02处理键盘导航
  3. 03创建 Lipgloss UI 组件
  4. 04管理屏幕切换与状态

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/gentleman-programming/engram --skill gentleman-bubbletea

安装后在 Claude Code 中输入/gentleman-bubbletea确认已加载。

详细介绍

适用场景

在以下情况下使用此技能:

  • 在 TUI 安装程序中添加新屏幕
  • 处理键盘输入或导航
  • 使用 Lipgloss 创建新的 UI 组件
  • 处理屏幕切换或状态管理

关键模式

模式 1:在 model.go 中定义屏幕常量

所有屏幕都必须在 model.go 中定义为 Screen 常量:

type Screen int

const (
    ScreenWelcome Screen = iota
    ScreenMainMenu
    ScreenOSSelect
    // ... new screens go here
    ScreenNewFeature      // Add new screen
    ScreenNewFeatureCat   // Add category screen if needed
)
模式 2:Model 结构体保存所有状态

model.go 中的 Model 结构体保存所有应用程序状态:

type Model struct {
    Screen      Screen
    PrevScreen  Screen      // For back navigation
    Width       int
    Height      int
    Cursor      int
    // Add new state here
    NewFeatureData    []SomeType
    NewFeatureScroll  int
}
模式 3:使用类型 switch 的 Update 模式

所有输入处理都通过带有类型 switch 的 Update() 完成:

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyMsg:
        return m.handleKeyPress(msg)
    case tea.WindowSizeMsg:
        m.Width = msg.Width
        m.Height = msg.Height
        return m, nil
    case customMsg:
        // Handle custom messages
        return m, nil
    }
    return m, nil
}
模式 4:按键处理函数返回 (Model, Cmd)

每个屏幕都使用独立的处理函数,并始终返回 (tea.Model, tea.Cmd):

func (m Model) handleNewFeatureKeys(key string) (tea.Model, tea.Cmd) {
    options := m.GetCurrentOptions()

    switch key {
    case "up", "k":
        if m.Cursor > 0 {
            m.Cursor--
            // Skip separator
            if strings.HasPrefix(options[m.Cursor], "───") && m.Cursor > 0 {
                m.Cursor--
            }
        }
    case "down", "j":
        if m.Cursor < len(options)-1 {
            m.Cursor++
            if strings.HasPrefix(options[m.Cursor], "───") && m.Cursor < len(options)-1 {
                m.Cursor++
            }
        }
    case "enter", " ":
        // Handle selection
        return m.handleNewFeatureSelection()
    case "esc":
        m.Screen = m.PrevScreen
        m.Cursor = 0
    }
    return m, nil
}

决策树

Adding a new screen?
├── Define Screen constant in model.go
├── Add state fields to Model struct
├── Add handler in handleKeyPress switch
├── Create handle{Screen}Keys function in update.go
├── Add view case in view.go
└── Add title in GetScreenTitle()

Adding navigation to existing screen?
├── Use m.PrevScreen for back navigation
├── Reset m.Cursor = 0 on screen change
└── Save scroll position if scrollable

Adding scrollable content?
├── Add {Screen}Scroll int to Model
├── Calculate visibleItems from m.Height
├── Handle up/down for scroll position
└── Reset scroll on screen exit

代码示例

示例 1:向 handleKeyPress 添加屏幕
// In handleKeyPress switch statement:
case ScreenNewFeature:
    return m.handleNewFeatureKeys(key)

case ScreenNewFeatureCat:
    return m.handleNewFeatureCatKeys(key)
示例 2:屏幕选项模式
func (m Model) GetCurrentOptions() []string {
    switch m.Screen {
    case ScreenNewFeature:
        categories := make([]string, len(m.NewFeatureData)+2)
        for i, item := range m.NewFeatureData {
            categories[i] = item.Name
        }
        categories[len(m.NewFeatureData)] = "─────────────"
        categories[len(m.NewFeatureData)+1] = "← Back"
        return categories
    // ...
    }
}
示例 3:可滚动视图模式
func (m Model) handleNewFeatureCatKeys(key string) (tea.Model, tea.Cmd) {
    data := m.NewFeatureData[m.SelectedNewFeature]

    visibleItems := m.Height - 9
    if visibleItems < 5 {
        visibleItems = 5
    }

    maxScroll := len(data.Items) - visibleItems
    if maxScroll < 0 {
        maxScroll = 0
    }

    switch key {
    case "up", "k":
        if m.NewFeatureScroll > 0 {
            m.NewFeatureScroll--
        }
    case "down", "j":
        if m.NewFeatureScroll < maxScroll {
            m.NewFeatureScroll++
        }
    case "esc", "q", "enter", " ":
        m.Screen = ScreenNewFeature
        m.NewFeatureScroll = 0
    }
    return m, nil
}
示例 4:自定义消息模式
// Define message type
type newFeatureLoadedMsg struct {
    data []SomeType
    err  error
}

// Send message from command
func loadNewFeatureCmd() tea.Cmd {
    return func() tea.Msg {
        data, err := loadData()
        return newFeatureLoadedMsg{data: data, err: err}
    }
}

// Handle in Update
case newFeatureLoadedMsg:
    if msg.err != nil {
        m.ErrorMsg = msg.err.Error()
        return m, nil
    }
    m.NewFeatureData = msg.data
    return m, nil

命令

cd installer && go build ./cmd/gentleman-installer  # Build installer
cd installer && go test ./internal/tui/...          # Run TUI tests
cd installer && go test -run TestNewFeature         # Run specific test

资源

  • Model:参见 installer/internal/tui/model.go,了解状态管理
  • Update:参见 installer/internal/tui/update.go,了解输入处理
  • View:参见 installer/internal/tui/view.go,了解渲染
  • Styles:参见 installer/internal/tui/styles.go,了解 Lipgloss 样式

评论

支持 Markdown