package internal import ( "fmt" "strings" "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" "u-tabs/internal/style" ) // --- View 入口 --- func (m *Model) View() tea.View { v := tea.NewView(m.render()) v.AltScreen = true v.MouseMode = tea.MouseModeCellMotion v.WindowTitle = "U-TABS" return v } func (m *Model) render() string { if m.width == 0 { return "loading..." } var b strings.Builder b.WriteString(m.renderTabBar()) b.WriteString("\n") b.WriteString(lipgloss.NewStyle().Foreground(style.BgPanel).Render(strings.Repeat("─", m.width))) b.WriteString("\n") if m.IsHistoryTab() { b.WriteString(m.renderHistory()) } else { b.WriteString(m.renderWorkspace()) } // footer b.WriteString("\n") if m.inputBuf != "" { b.WriteString(style.InputStyle.Render(fmt.Sprintf(" ▶ num:%s [Enter]go [Esc]cancel", m.inputBuf))) } if m.forkMode { b.WriteString(style.InputStyle.Render(fmt.Sprintf(" ⎇ fork:%s [Enter]confirm [Esc]cancel", m.forkBuf))) } if m.launched != "" { b.WriteString(lipgloss.NewStyle().Foreground(style.Accent).Render(fmt.Sprintf(" ✓ %s", m.launched))) m.launched = "" } if m.update.Done { b.WriteString(lipgloss.NewStyle().Foreground(style.Success).Bold(true). Render(fmt.Sprintf(" updated to v%s, restart to apply", m.update.NewVersion))) } else if m.update.Error != nil { b.WriteString(lipgloss.NewStyle().Foreground(style.Red). Render(fmt.Sprintf(" update failed: %v", m.update.Error))) } else if m.update.Updating { b.WriteString(lipgloss.NewStyle().Foreground(style.Warning). Render(" updating...")) } else if m.update.Available { b.WriteString(lipgloss.NewStyle().Foreground(style.Warning).Bold(true). Render(fmt.Sprintf(" v%s available [u]update", m.update.NewVersion))) } b.WriteString("\n") helpParts := m.renderHelp() helpStr := " " + strings.Join(helpParts, " ") verStr := lipgloss.NewStyle().Foreground(style.Dim).Render("绝尘 v" + Version) padW := m.width - lipgloss.Width(helpStr) - lipgloss.Width(verStr) if padW > 0 { b.WriteString(helpStr + strings.Repeat(" ", padW) + verStr) } else { b.WriteString(helpStr + " " + verStr) } if hint := ConfigHint(); hint != "" { b.WriteString("\n" + hint) } return b.String() } // renderTabBar 顶部两个主 tab func (m *Model) renderTabBar() string { var b strings.Builder b.WriteString(style.TitleStyle.Render(" U-TABS ")) sep := style.TabSep.Render(" │ ") wsLabel := " 1 工作空间 " if !m.IsHistoryTab() { b.WriteString(lipgloss.NewStyle(). Bold(true).Background(style.BgPanel).Foreground(style.Accent). Render(wsLabel)) } else { b.WriteString(style.TabInactiveStyle.Render(wsLabel)) } b.WriteString(sep) histLabel := " 2 历史对话 " if m.IsHistoryTab() { b.WriteString(lipgloss.NewStyle(). Bold(true).Background(style.BgPanel).Foreground(style.Cyan). Render(histLabel)) } else { b.WriteString(style.TabInactiveStyle.Render(histLabel)) } return b.String() } func (m *Model) renderHelp() []string { if m.IsHistoryTab() { return []string{ m.fmtHelp("↑↓", "sel"), m.fmtHelp("←→", "panel"), m.fmtHelp("Enter", "resume"), m.fmtHelp("f", "star"), m.fmtHelp("n", "new"), m.fmtHelp("c", "fork"), m.fmtHelp("1", "workspace"), m.fmtHelp("r/F5", "refresh"), m.fmtHelp("q", "quit"), } } return []string{ m.fmtHelp("←→", "col"), m.fmtHelp("↑↓", "sel"), m.fmtHelp("Enter", "run"), m.fmtHelp("2", "history"), m.fmtHelp("q", "quit"), } } func (m *Model) fmtHelp(key, desc string) string { return lipgloss.NewStyle().Foreground(style.Accent).Render(key) + lipgloss.NewStyle().Foreground(style.Dim).Render(":" + desc) } func renderNavEntry(lines *[]string, w int, label string, activeFg string, isCur, focused bool) { sep := lipgloss.NewStyle().Foreground(style.BgPanel).Render(fitWidth(" ─────────", w)) *lines = append(*lines, sep) if isCur { curLabel := " → " + label line := padRightByWidth(truncateByWidth(curLabel, w), w) if focused { *lines = append(*lines, style.SelStyle.Render(line)) } else { *lines = append(*lines, lipgloss.NewStyle().Foreground(lipgloss.Color(activeFg)).Bold(true).Render(line)) } } else { inactiveLabel := " " + label line := padRightByWidth(truncateByWidth(inactiveLabel, w), w) *lines = append(*lines, lipgloss.NewStyle().Foreground(style.Dim).Render(line)) } }