package main import ( "fmt" "sort" "strings" ) // ChannelList renders the channel sidebar for a server. func ChannelList(channels []Channel, activeIdx, hoverIdx, width, height int) string { if len(channels) == 0 { return ChannelListStyle. Width(width). Height(height). Render(HelpStyle.Render(" No channels")) } // Group channels by category type catGroup struct { name string channels []Channel } catOrder := make([]string, 0) catMap := make(map[string]*catGroup) for _, ch := range channels { cat := ch.Category if cat == "" { cat = "general" } if _, ok := catMap[cat]; !ok { catMap[cat] = &catGroup{name: cat} catOrder = append(catOrder, cat) } catMap[cat].channels = append(catMap[cat].channels, ch) } // Stable sort categories sort.Strings(catOrder) // Build flat list for index mapping var lines []string flatIdx := 0 for _, catName := range catOrder { group := catMap[catName] lines = append(lines, CategoryStyle.Render(strings.ToUpper(group.name))) for _, ch := range group.channels { prefix := " " if ch.Type == "voice" { prefix = VoiceChannelPrefix.Render("🔊 ") } else { prefix = TextChannelPrefix.Render("# ") } style := ChannelInactive if flatIdx == activeIdx { style = ChannelActive } else if flatIdx == hoverIdx { style = ChannelHover } label := ch.Name if len(label) > width-6 { label = label[:width-9] + "..." } lines = append(lines, style.Render(prefix+label)) flatIdx++ } } return ChannelListStyle. Width(width). Height(height). Render(strings.Join(lines, "\n")) } // ChannelFlatIndex converts a (category, channel index in category) to flat index. func ChannelFlatIndex(channels []Channel, targetID string) int { catOrder := getCatOrder(channels) flatIdx := 0 for _, catName := range catOrder { for _, ch := range groupByCategory(channels)[catName] { if ch.ID == targetID { return flatIdx } flatIdx++ } } return 0 } // ChannelByFlatIndex returns the channel at a flat index in the grouped view. func ChannelByFlatIndex(channels []Channel, flatIdx int) *Channel { catOrder := getCatOrder(channels) idx := 0 for _, catName := range catOrder { for _, ch := range groupByCategory(channels)[catName] { if idx == flatIdx { return &ch } idx++ } } return nil } func groupByCategory(channels []Channel) map[string][]Channel { m := make(map[string][]Channel) for _, ch := range channels { cat := ch.Category if cat == "" { cat = "general" } m[cat] = append(m[cat], ch) } return m } func getCatOrder(channels []Channel) []string { seen := make(map[string]bool) var order []string for _, ch := range channels { cat := ch.Category if cat == "" { cat = "general" } if !seen[cat] { seen[cat] = true order = append(order, cat) } } sort.Strings(order) return order } // ChannelCount returns total number of channels (for navigation limits). func ChannelCount(channels []Channel) int { return len(channels) } // formatChannelHeader renders the channel header for the chat pane. func formatChannelHeader(ch Channel) string { icon := "#" if ch.Type == "voice" { icon = "🔊" } return fmt.Sprintf("%s %s", icon, ch.Name) }