fix: resolve missing users in piped commands and refine TUI distribution

This commit is contained in:
Jose Luis Montañes Ojados
2026-01-16 23:54:36 +01:00
parent 4f26d9b430
commit 78e7988db1
6 changed files with 145 additions and 39 deletions

View File

@@ -2,6 +2,7 @@ package main
import (
"fmt"
"regexp"
"sort"
"strings"
"time"
@@ -36,6 +37,7 @@ type ChannelNode struct {
Users []UserNode
Expanded bool
Selected bool
Depth int
}
// UserNode represents a user in a channel
@@ -362,41 +364,70 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
type logMsg string
func (m *Model) updateChannelList(channels []*ts3client.Channel) {
// Sort channels by ID for stable ordering
sortedChannels := make([]*ts3client.Channel, len(channels))
copy(sortedChannels, channels)
sort.Slice(sortedChannels, func(i, j int) bool {
return sortedChannels[i].ID < sortedChannels[j].ID
})
m.channels = make([]ChannelNode, 0, len(sortedChannels))
for _, ch := range sortedChannels {
node := ChannelNode{
ID: ch.ID,
Name: ch.Name,
Users: []UserNode{},
Expanded: true,
// Build adjacency map: ParentID -> PreviousID(Order) -> Channel
levelMap := make(map[uint64]map[uint64]*ts3client.Channel)
for _, ch := range channels {
if levelMap[ch.ParentID] == nil {
levelMap[ch.ParentID] = make(map[uint64]*ts3client.Channel)
}
// Get users in this channel
for _, cl := range m.client.GetClients() {
if cl.ChannelID == ch.ID {
node.Users = append(node.Users, UserNode{
ID: cl.ID,
Nickname: cl.Nickname,
IsMe: cl.ID == m.selfID,
Talking: m.talkingClients[cl.ID],
})
}
}
// Sort users by ID for stable ordering
sort.Slice(node.Users, func(i, j int) bool {
return node.Users[i].ID < node.Users[j].ID
})
m.channels = append(m.channels, node)
levelMap[ch.ParentID][ch.Order] = ch
}
var sortedNodes []ChannelNode
// Recursive function to flatten the tree in order
var visit func(parentID uint64, depth int)
visit = func(parentID uint64, depth int) {
prevID := uint64(0)
for {
ch, ok := levelMap[parentID][prevID]
if !ok {
break // End of list for this parent
}
// Create node
node := ChannelNode{
ID: ch.ID,
Name: ch.Name,
Users: []UserNode{},
Expanded: true,
Depth: depth,
}
// Get users in this channel
for _, cl := range m.client.GetClients() {
if cl.ChannelID == ch.ID {
node.Users = append(node.Users, UserNode{
ID: cl.ID,
Nickname: cl.Nickname,
IsMe: cl.ID == m.selfID,
Talking: m.talkingClients[cl.ID],
})
}
}
// Sort users by ID for stable ordering
sort.Slice(node.Users, func(i, j int) bool {
return node.Users[i].ID < node.Users[j].ID
})
sortedNodes = append(sortedNodes, node)
// Recursively visit children
visit(ch.ID, depth+1)
// Move to next sibling
prevID = ch.ID
}
}
// Start from root (ParentID = 0)
visit(0, 0)
// In case there are orphaned channels (shouldn't happen on standard server), standard logic ignores them.
// We'll stick to valid tree.
m.channels = sortedNodes
}
func (m *Model) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
@@ -772,6 +803,9 @@ func (m *Model) renderStatusBar() string {
return headerStyle.Render(status)
}
// Regex for TeamSpeak spacers: [spacer0], [cspacer], [*spacer], etc.
var spacerRegex = regexp.MustCompile(`^\[([*cZr]?spacer[\w\d]*)\](.*)`)
func (m *Model) renderChannels() string {
if len(m.channels) == 0 {
return "No channels..."
@@ -782,9 +816,11 @@ func (m *Model) renderChannels() string {
lines = append(lines, "")
for i, ch := range m.channels {
prefix := " "
// Indentation based on depth
indent := strings.Repeat(" ", ch.Depth)
prefix := indent + " "
if i == m.selectedIdx {
prefix = "► "
prefix = indent + "► "
}
style := lipgloss.NewStyle()
@@ -792,7 +828,56 @@ func (m *Model) renderChannels() string {
style = style.Bold(true).Foreground(lipgloss.Color("212"))
}
lines = append(lines, style.Render(prefix+ch.Name))
// Check for spacer
displayName := ch.Name
matches := spacerRegex.FindStringSubmatch(ch.Name)
if len(matches) > 0 {
tag := matches[1] // e.g. "spacer0", "cspacer", "*spacer"
content := matches[2] // e.g. "---", "Section"
// Calculate effective width: width/4 - 6 (border+padding)
width := (m.width / 4) - 6
if width < 0 {
width = 0
}
if strings.HasPrefix(tag, "*") {
// Repeat content
if len(content) > 0 {
count := width / len(content)
if count > 0 {
displayName = strings.Repeat(content, count+1)[:width]
}
} else {
displayName = strings.Repeat("-", width)
}
} else if strings.HasPrefix(tag, "c") {
// Center content
if len(content) > len(displayName) { // If parsed content is safer?
// No, just align content
gap := (width - len(content)) / 2
if gap > 0 {
displayName = strings.Repeat(" ", gap) + content
} else {
displayName = content
}
} else {
// Fallback if measurement is tricky, just show content
// Actually, let's try to center strictly
gap := (width - len(content)) / 2
if gap > 0 {
displayName = strings.Repeat(" ", gap) + content
} else {
displayName = content
}
}
} else {
// Standard spacer (left align)
displayName = content
}
}
lines = append(lines, style.Render(prefix+displayName))
// Show users in channel
for _, user := range ch.Users {