From f88c2af404d546221103da6905932c4b1122a1a5 Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Mon, 18 Aug 2025 21:10:04 +0200 Subject: [PATCH] WIP: refactor --- irc/client.go | 64 +++++-- irc/handler/handler.go | 329 +++++++++++++++++++++++------------- irc/handler/slash.go | 33 ++-- ui/app/app.go | 19 ++- ui/mainscreen/mainscreen.go | 183 +++++++++++--------- ui/mainscreen/sidepanel.go | 12 +- 6 files changed, 395 insertions(+), 245 deletions(-) diff --git a/irc/client.go b/irc/client.go index feaf1f0..3cd4ee5 100644 --- a/irc/client.go +++ b/irc/client.go @@ -45,6 +45,12 @@ type User struct { Prefix string } +type Node[T any] struct { + Next *Node[T] + Prev *Node[T] + Value T +} + type Channel struct { // Channel name Name string @@ -75,14 +81,11 @@ type Client struct { // Nickname currently in use by the user Nickname string - // Joined channels - Channels []Channel + // The root of the doubly linked list of channel + RootChannel *Node[Channel] - // Active channel name - ActiveChannel string - - // Active channel index - ActiveChannelIndex int + // Active channel + ActiveChannel *Node[Channel] // The channel to join immediately after registration completes. InitialChannel string @@ -91,10 +94,10 @@ type Client struct { Tea *tea.Program // Index of the first visible tab in the tab bar - FirstTabIndexInTabBar int + // FirstTabIndexInTabBar int // Index of the last visible tab in the tab bar - LastTabIndexInTabBar int + // LastTabIndexInTabBar int // The acknowledged capabilities EnabledCapabilities Capabilities @@ -172,8 +175,6 @@ func (c *Client) Initialize(host string, port string, tlsEnabled bool) { c.Host = host c.Port = port - c.ActiveChannel = host - c.Channels = make([]Channel, 0) c.EnabledCapabilities = make(Capabilities, 0) c.EnabledFeatures = make(Features, 0) @@ -203,10 +204,16 @@ func (c *Client) Initialize(host string, port string, tlsEnabled bool) { } func (c *Client) Register(nick string, password string, channel string) { - c.Channels = append(c.Channels, Channel{ + hostChannel := Channel{ Name: c.Host, Users: make(map[string]User), - }) + } + root := &Node[Channel]{Value: hostChannel} + root.Next = root + root.Prev = root + + c.ActiveChannel = root + c.RootChannel = root c.SendCommand(commands.CAP, "LS", "302") if password != "" { @@ -225,12 +232,39 @@ func (c *Client) SetDay() { now := time.Now() msg := fmt.Sprintf("————— %s %d —————", now.Month().String(), now.Day()) - c.Channels[0].AppendMsg("", msg, msgOpts) + c.RootChannel.Value.AppendMsg("", msg, msgOpts) // TODO: append new day to each channel whenever the day changes } -func (c Client) SendCommand(cmd string, params ...string) { +func (c *Client) AppendChannel(channel Channel) *Node[Channel] { + first := c.RootChannel + last := c.RootChannel.Prev + + newNode := &Node[Channel]{ + Value: channel, + Next: first, + Prev: last, + } + + last.Next = newNode + first.Prev = newNode + + return newNode +} + +func (c *Client) RemoveChannel(channel *Node[Channel]) { + prev := channel.Prev + next := channel.Next + + prev.Next = next + next.Prev = prev + + channel.Prev = nil + channel.Next = nil +} + +func (c *Client) SendCommand(cmd string, params ...string) { if c.TCPConn == nil { // TODO: properly handle the error instead of Fatal-ing log.Fatal("Attempted to write data to nil connection") diff --git a/irc/handler/handler.go b/irc/handler/handler.go index b1f5cae..21babab 100644 --- a/irc/handler/handler.go +++ b/irc/handler/handler.go @@ -25,8 +25,6 @@ import ( "strings" "time" - "slices" - "github.com/illusionman1212/gorc/cmds" "github.com/illusionman1212/gorc/irc" "github.com/illusionman1212/gorc/irc/commands" @@ -72,11 +70,17 @@ func handlePrivMsg(msg irc.Message, client *irc.Client) { targetsLoop: for _, target := range targets { - for i, c := range client.Channels { - if c.Name == target || c.Name == source { - client.Channels[i].AppendMsg(msg.Timestamp, privMsg, msgOpts) + current := client.RootChannel + for { + if current.Value.Name == target || current.Value.Name == source { + current.Value.AppendMsg(msg.Timestamp, privMsg, msgOpts) continue targetsLoop } + + current = current.Next + if current == client.RootChannel { + break + } } if target == client.Nickname { @@ -86,7 +90,7 @@ targetsLoop: } newChannel.AppendMsg(msg.Timestamp, privMsg, msgOpts) - client.Channels = append(client.Channels, newChannel) + client.AppendChannel(newChannel) } } } @@ -103,13 +107,19 @@ func handleNotice(msg irc.Message, client *irc.Client) { for _, target := range targets { if target == "*" || target == client.Nickname { - client.Channels[0].AppendMsg(msg.Timestamp, notice, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, notice, msgOpts) continue } - for i, c := range client.Channels { - if c.Name == targets[0] { - client.Channels[i].AppendMsg(msg.Timestamp, notice, msgOpts) + current := client.RootChannel + for { + if current.Value.Name == targets[0] { + current.Value.AppendMsg(msg.Timestamp, notice, msgOpts) + } + + current = current.Next + if current == client.RootChannel { + break } } } @@ -128,29 +138,36 @@ func handleJoin(msg irc.Message, client *irc.Client) { } if nick == client.Nickname { - client.Channels = append(client.Channels, irc.Channel{ + newChannel := irc.Channel{ Name: channel, Users: make(map[string]irc.User), - }) - client.ActiveChannelIndex = len(client.Channels) - 1 - client.ActiveChannel = channel - client.Channels[client.ActiveChannelIndex].AppendMsg(msg.Timestamp, joinMsg, msgOpts) - if _, exists := client.Channels[client.ActiveChannelIndex].Users[nick]; !exists { - client.Channels[client.ActiveChannelIndex].Users[nick] = irc.User{} + } + + client.ActiveChannel = client.AppendChannel(newChannel) + client.ActiveChannel.Value.AppendMsg(msg.Timestamp, joinMsg, msgOpts) + + if _, exists := client.ActiveChannel.Value.Users[nick]; !exists { + client.ActiveChannel.Value.Users[nick] = irc.User{} } client.Tea.Send(cmds.UpdateTabBar()) } else { - for i, c := range client.Channels { - if c.Name == channel { - client.Channels[i].AppendMsg(msg.Timestamp, joinMsg, msgOpts) - if _, exists := client.Channels[i].Users[nick]; !exists { - client.Channels[i].Users[nick] = irc.User{} + current := client.RootChannel + for { + if current.Value.Name == channel { + current.Value.AppendMsg(msg.Timestamp, joinMsg, msgOpts) + if _, exists := current.Value.Users[nick]; !exists { + current.Value.Users[nick] = irc.User{} } } + + current = current.Next + if current == client.RootChannel { + break + } } } - if channel == client.ActiveChannel { + if channel == client.ActiveChannel.Value.Name { client.Tea.Send(cmds.SwitchChannels()) } } @@ -176,26 +193,37 @@ func handleNick(msg irc.Message, client *irc.Client) { message = fmt.Sprintf("%v changed their nickname to %v", oldNick, newNick) } - for i, channel := range client.Channels { + current := client.RootChannel + for { // Rename this user in every channel we can find - if user, ok := channel.Users[oldNick]; ok { - delete(client.Channels[i].Users, oldNick) - client.Channels[i].Users[newNick] = user - client.Channels[i].AppendMsg(msg.Timestamp, message, msgOpts) + if user, ok := current.Value.Users[oldNick]; ok { + delete(current.Value.Users, oldNick) + current.Value.Users[newNick] = user + current.Value.AppendMsg(msg.Timestamp, message, msgOpts) } // If we have a private channel open with this user, rename it as well. - if strings.EqualFold(channel.Name, oldNick) { - client.Channels[i].Name = newNick + if strings.EqualFold(current.Value.Name, oldNick) { + current.Value.Name = newNick - if client.ActiveChannelIndex == i { - client.ActiveChannel = newNick + if current == client.ActiveChannel { + current.Value.Name = newNick } + + // if client.ActiveChannelIndex == i { + // client.ActiveChannel = newNick + // } + break + } + + current = current.Next + if current == client.RootChannel { + break } } if isMe { - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) client.Nickname = newNick } @@ -222,25 +250,29 @@ func handleKick(msg irc.Message, client *irc.Client) { kicker := strings.SplitN(msg.Source, "!", 2)[0] message := fmt.Sprintf("%v kicked %v from %v (%v)", kicker, nick, channel, reason) - for i, c := range client.Channels { - if c.Name == channel { + current := client.RootChannel + for { + if current.Value.Name == channel { if nick == client.Nickname { - client.Channels = slices.Delete(client.Channels, i, i+1) - - if client.ActiveChannelIndex >= i { - client.ActiveChannelIndex-- - client.ActiveChannel = client.Channels[client.ActiveChannelIndex].Name + if current == client.ActiveChannel { + client.ActiveChannel = current.Prev } - client.LastTabIndexInTabBar-- + + client.RemoveChannel(current) message = fmt.Sprintf("You were kicked by %v from %v (%v)", kicker, channel, reason) - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) return } - delete(client.Channels[i].Users, nick) + delete(current.Value.Users, nick) - client.Channels[i].AppendMsg(msg.Timestamp, message, msgOpts) + current.Value.AppendMsg(msg.Timestamp, message, msgOpts) + } + + current = current.Next + if current == client.RootChannel { + break } } } @@ -258,13 +290,19 @@ func handleQuit(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - for i := range client.Channels { - // skip the server "channel" - if i == 0 { - continue + // skip the server "channel" + current := client.RootChannel.Next + for { + // if i == 0 { + // continue + // } + current.Value.AppendMsg(msg.Timestamp, quitMsg, msgOpts) + delete(current.Value.Users, nick) + + current = current.Next + if current == client.RootChannel { + break } - client.Channels[i].AppendMsg(msg.Timestamp, quitMsg, msgOpts) - delete(client.Channels[i].Users, nick) } client.Tea.Send(cmds.SwitchChannels()) @@ -285,19 +323,26 @@ func handlePart(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - for i, c := range client.Channels { - if c.Name == channel { + current := client.RootChannel + for { + if current.Value.Name == channel { if nick == client.Nickname { - client.Channels = slices.Delete(client.Channels, i, i+1) - if client.ActiveChannelIndex >= i { - client.ActiveChannelIndex-- - client.ActiveChannel = client.Channels[client.ActiveChannelIndex].Name + if current == client.ActiveChannel { + client.ActiveChannel = current.Prev } - client.LastTabIndexInTabBar-- + + client.RemoveChannel(current) } else { - client.Channels[i].AppendMsg(msg.Timestamp, partMsg, msgOpts) - delete(client.Channels[i].Users, nick) + current.Value.AppendMsg(msg.Timestamp, partMsg, msgOpts) + delete(current.Value.Users, nick) } + + break + } + + current = current.Next + if current == client.RootChannel { + break } } @@ -312,10 +357,17 @@ func handleTopic(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - for i, c := range client.Channels { - if c.Name == channel { - client.Channels[i].Topic = topic - client.Channels[i].AppendMsg(msg.Timestamp, fmt.Sprintf("Topic changed: %v", topic), msgOpts) + current := client.RootChannel + for { + if current.Value.Name == channel { + current.Value.Topic = topic + current.Value.AppendMsg(msg.Timestamp, fmt.Sprintf("Topic changed: %v", topic), msgOpts) + break + } + + current = current.Next + if current == client.RootChannel { + break } } } @@ -399,7 +451,7 @@ func handleCAP(msg irc.Message, client *irc.Client) { WithTimestamp: true, AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, "Unrecognized capabilities: "+strings.Join(caps, " "), msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, "Unrecognized capabilities: "+strings.Join(caps, " "), msgOpts) case "DEL": caps := strings.Split(msg.Parameters[2], " ") for _, capability := range caps { @@ -420,7 +472,7 @@ func handleWELCOME(msg irc.Message, client *irc.Client) { // set server-registered nickname because the server MAY return a different nickname than // the one the user chose because of length restrictions or otherwise. client.Nickname = nick - client.Channels[0].AppendMsg(msg.Timestamp, welcomeMsg, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, welcomeMsg, msgOpts) // Only join the user-requested channel AFTER registration is complete. if client.InitialChannel != "" { @@ -436,7 +488,7 @@ func handleYOURHOST(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, host, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, host, msgOpts) } func handleCREATED(msg irc.Message, client *irc.Client) { @@ -447,7 +499,7 @@ func handleCREATED(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, created, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, created, msgOpts) } func handleMYINFO(msg irc.Message, client *irc.Client) { @@ -458,7 +510,7 @@ func handleMYINFO(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, info, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, info, msgOpts) } func handleISUPPORT(msg irc.Message, client *irc.Client) { @@ -485,7 +537,7 @@ func handleISUPPORT(msg irc.Message, client *irc.Client) { log.Printf("Unknown feature: \"%v\" with value: \"%v\"\n", key, value) } - client.Channels[0].AppendMsg(msg.Timestamp, token, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, token, msgOpts) } } @@ -497,7 +549,7 @@ func handleLUSERCLIENT(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleLUSEROP(msg irc.Message, client *irc.Client) { @@ -508,7 +560,7 @@ func handleLUSEROP(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleLUSERUNKNOWN(msg irc.Message, client *irc.Client) { @@ -519,7 +571,7 @@ func handleLUSERUNKNOWN(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleLUSERCHANNELS(msg irc.Message, client *irc.Client) { @@ -530,7 +582,7 @@ func handleLUSERCHANNELS(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleLUSERME(msg irc.Message, client *irc.Client) { @@ -541,7 +593,7 @@ func handleLUSERME(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleLOCALUSERS(msg irc.Message, client *irc.Client) { @@ -558,7 +610,7 @@ func handleLOCALUSERS(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleGLOBALUSERS(msg irc.Message, client *irc.Client) { @@ -575,7 +627,7 @@ func handleGLOBALUSERS(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleAWAY(msg irc.Message, client *irc.Client) { @@ -588,7 +640,7 @@ func handleAWAY(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, awayMsg, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, awayMsg, msgOpts) } func handleUNAWAY(msg irc.Message, client *irc.Client) { @@ -599,7 +651,7 @@ func handleUNAWAY(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleNOWAWAY(msg irc.Message, client *irc.Client) { @@ -610,7 +662,7 @@ func handleNOWAWAY(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleWHOISUSER(msg irc.Message, client *irc.Client) { @@ -632,8 +684,8 @@ func handleWHOISUSER(msg irc.Message, client *irc.Client) { realName, ) - client.Channels[0].AppendMsg(msg.Timestamp, "WHOIS Information", msgOpts) - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, "WHOIS Information", msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleWHOISSERVER(msg irc.Message, client *irc.Client) { @@ -651,7 +703,7 @@ func handleWHOISSERVER(msg irc.Message, client *irc.Client) { serverInfo, ) - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleWHOISIDLE(msg irc.Message, client *irc.Client) { @@ -675,7 +727,7 @@ func handleWHOISIDLE(msg irc.Message, client *irc.Client) { idleSeconds, since.Format(time.ANSIC), ) - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleENDOFWHOIS(msg irc.Message, client *irc.Client) { @@ -686,7 +738,7 @@ func handleENDOFWHOIS(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleWHOISCHANNELS(msg irc.Message, client *irc.Client) { @@ -699,7 +751,7 @@ func handleWHOISCHANNELS(msg irc.Message, client *irc.Client) { message := fmt.Sprintf("channels: %s", chans) - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleNOTOPIC(msg irc.Message, client *irc.Client) { @@ -710,9 +762,16 @@ func handleNOTOPIC(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - for i, c := range client.Channels { - if c.Name == channel { - client.Channels[i].AppendMsg(msg.Timestamp, msgStr, msgOpts) + current := client.RootChannel + for { + if current.Value.Name == channel { + current.Value.AppendMsg(msg.Timestamp, msgStr, msgOpts) + break + } + + current = current.Next + if current == client.RootChannel { + break } } } @@ -725,10 +784,17 @@ func handleTOPIC(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - for i, c := range client.Channels { - if c.Name == channel { - client.Channels[i].Topic = topic - client.Channels[i].AppendMsg(msg.Timestamp, fmt.Sprintf("TOPIC: %v", topic), msgOpts) + current := client.RootChannel + for { + if current.Value.Name == channel { + current.Value.Topic = topic + current.Value.AppendMsg(msg.Timestamp, fmt.Sprintf("TOPIC: %v", topic), msgOpts) + break + } + + current = current.Next + if current == client.RootChannel { + break } } } @@ -748,7 +814,7 @@ func handleVERSION(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, versionMsg, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, versionMsg, msgOpts) } func handleNAMREPLY(msg irc.Message, client *irc.Client) { @@ -758,8 +824,9 @@ func handleNAMREPLY(msg irc.Message, client *irc.Client) { channel := msg.Parameters[2] nicks := strings.Split(msg.Parameters[3], " ") - for i, c := range client.Channels { - if c.Name == channel { + current := client.RootChannel + for { + if current.Value.Name == channel { for _, nick := range nicks { prefix := "" _nick := nick @@ -769,14 +836,21 @@ func handleNAMREPLY(msg irc.Message, client *irc.Client) { _nick = nick[1:] } - client.Channels[i].Users[_nick] = irc.User{ + current.Value.Users[_nick] = irc.User{ Prefix: prefix, } } + + break + } + + current = current.Next + if current == client.RootChannel { + break } } - if channel == client.ActiveChannel { + if channel == client.ActiveChannel.Value.Name { client.Tea.Send(cmds.SwitchChannels()) } } @@ -789,7 +863,7 @@ func handleINFO(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleENDOFINFO(msg irc.Message, client *irc.Client) { @@ -800,7 +874,7 @@ func handleENDOFINFO(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleMOTDStart(msg irc.Message, client *irc.Client) { @@ -811,7 +885,7 @@ func handleMOTDStart(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleMOTD(msg irc.Message, client *irc.Client) { @@ -822,7 +896,7 @@ func handleMOTD(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, messageLine, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, messageLine, msgOpts) } func handleWHOISHOST(msg irc.Message, client *irc.Client) { @@ -833,7 +907,7 @@ func handleWHOISHOST(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleWHOISMODES(msg irc.Message, client *irc.Client) { @@ -844,7 +918,7 @@ func handleWHOISMODES(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleNOSUCHNICK(msg irc.Message, client *irc.Client) { @@ -855,7 +929,7 @@ func handleNOSUCHNICK(msg irc.Message, client *irc.Client) { AsErrorMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleNOSUCHSERVER(msg irc.Message, client *irc.Client) { @@ -866,7 +940,7 @@ func handleNOSUCHSERVER(msg irc.Message, client *irc.Client) { AsErrorMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleNOSUCHCHANNEL(msg irc.Message, client *irc.Client) { @@ -879,7 +953,7 @@ func handleNOSUCHCHANNEL(msg irc.Message, client *irc.Client) { AsErrorMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleCANNOTSENDTOCHAN(msg irc.Message, client *irc.Client) { @@ -891,15 +965,21 @@ func handleCANNOTSENDTOCHAN(msg irc.Message, client *irc.Client) { AsErrorMsg: true, } - for i, c := range client.Channels { - if c.Name == channel { - client.Channels[i].AppendMsg(msg.Timestamp, message, msgOpts) + current := client.RootChannel + for { + if current.Value.Name == channel { + current.Value.AppendMsg(msg.Timestamp, message, msgOpts) return } + + current = current.Next + if current == client.RootChannel { + break + } } message = fmt.Sprintf("%v: %v", channel, message) - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleTOOMANYCHANNELS(msg irc.Message, client *irc.Client) { @@ -911,7 +991,7 @@ func handleTOOMANYCHANNELS(msg irc.Message, client *irc.Client) { AsErrorMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, fmt.Sprintf("%v: %v", channel, message), msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, fmt.Sprintf("%v: %v", channel, message), msgOpts) } func handleUNKNOWNCOMMAND(msg irc.Message, client *irc.Client) { @@ -922,7 +1002,7 @@ func handleUNKNOWNCOMMAND(msg irc.Message, client *irc.Client) { AsErrorMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleNOMOTD(msg irc.Message, client *irc.Client) { @@ -933,7 +1013,7 @@ func handleNOMOTD(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleNONICKNAMEGIVEN(msg irc.Message, client *irc.Client) { @@ -944,7 +1024,7 @@ func handleNONICKNAMEGIVEN(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleNEEDMOREPARAMS(msg irc.Message, client *irc.Client) { @@ -955,7 +1035,7 @@ func handleNEEDMOREPARAMS(msg irc.Message, client *irc.Client) { AsErrorMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleALREADYREGISTERED(msg irc.Message, client *irc.Client) { @@ -966,7 +1046,7 @@ func handleALREADYREGISTERED(msg irc.Message, client *irc.Client) { AsServerMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, message, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, message, msgOpts) } func handleBADCHANMASK(msg irc.Message, client *irc.Client) { @@ -978,7 +1058,7 @@ func handleBADCHANMASK(msg irc.Message, client *irc.Client) { AsErrorMsg: true, } - client.Channels[0].AppendMsg(msg.Timestamp, fmt.Sprintf("%v: %v", channel, message), msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, fmt.Sprintf("%v: %v", channel, message), msgOpts) } func handleCHANOPRIVSNEEDED(msg irc.Message, client *irc.Client) { @@ -990,9 +1070,16 @@ func handleCHANOPRIVSNEEDED(msg irc.Message, client *irc.Client) { AsErrorMsg: true, } - for i, c := range client.Channels { - if c.Name == channel { - client.Channels[i].AppendMsg(msg.Timestamp, message, msgOpts) + current := client.RootChannel + for { + if current.Value.Name == channel { + current.Value.AppendMsg(msg.Timestamp, message, msgOpts) + break + } + + current = current.Next + if current == client.RootChannel { + break } } } @@ -1130,7 +1217,7 @@ func HandleCommand(msg irc.Message, client *irc.Client) { NotImpl: true, } - client.Channels[0].AppendMsg(msg.Timestamp, fullMsg, msgOpts) + client.RootChannel.Value.AppendMsg(msg.Timestamp, fullMsg, msgOpts) } // send a receivedIRCmsg tea message so the ui can update diff --git a/irc/handler/slash.go b/irc/handler/slash.go index 17ffc2f..0fedf3e 100644 --- a/irc/handler/slash.go +++ b/irc/handler/slash.go @@ -44,16 +44,21 @@ func handleSlashPrivMsg(params []string, client *irc.Client) tea.Cmd { timestamp := fmt.Sprintf("[%02d:%02d]", now.Hour(), now.Minute()) msg := fmt.Sprintf("%s: %s", client.Nickname, strings.Join(params[1:], " ")) - for i, c := range client.Channels { - if c.Name == target { - client.ActiveChannelIndex = i - client.ActiveChannel = target + c := client.RootChannel + for { + if c.Value.Name == target { + client.ActiveChannel = c - client.Channels[i].AppendMsg(timestamp, msg, msgOpts) + c.Value.AppendMsg(timestamp, msg, msgOpts) client.SendCommand(commands.PRIVMSG, params...) return cmds.SwitchChannels } + + c = c.Next + if c == client.RootChannel { + break + } } // If we're messaging a user and their "channel" wasn't found in the previous loop, then create it and append it @@ -64,11 +69,8 @@ func handleSlashPrivMsg(params []string, client *irc.Client) tea.Cmd { } newChannel.AppendMsg(timestamp, msg, msgOpts) + client.ActiveChannel = client.AppendChannel(newChannel) - client.ActiveChannelIndex = len(client.Channels) - client.ActiveChannel = target - - client.Channels = append(client.Channels, newChannel) batchedCmds = append(batchedCmds, cmds.UpdateTabBar) } @@ -84,12 +86,17 @@ func handleSlashJoin(params []string, client *irc.Client) tea.Cmd { channel = strings.ToLower(params[0]) } - for i, c := range client.Channels { - if c.Name == channel { - client.ActiveChannelIndex = i - client.ActiveChannel = channel + c := client.RootChannel + for { + if c.Value.Name == channel { + client.ActiveChannel = c return cmds.SwitchChannels } + + c = c.Next + if c == client.RootChannel { + break + } } client.SendCommand(commands.JOIN, params...) diff --git a/ui/app/app.go b/ui/app/app.go index 7edc21e..b9ab6a7 100644 --- a/ui/app/app.go +++ b/ui/app/app.go @@ -41,8 +41,10 @@ type UI struct { } type State struct { - UI UI - Client *irc.Client + UI UI + TerminalWidth int + TerminalHeight int + Client *irc.Client } func initialUIState(client *irc.Client) UI { @@ -78,8 +80,15 @@ func (s State) Update(msg tea.Msg) (tea.Model, tea.Cmd) { Width(msg.Width). Height(msg.Height) - s.UI.Login.SetSize(msg.Width, msg.Height) - s.UI.MainScreen.SetSize(msg.Width, msg.Height) + s.TerminalWidth = msg.Width + s.TerminalHeight = msg.Height + + switch s.UI.CurrentScreen { + case Login: + s.UI.Login.SetSize(msg.Width, msg.Height) + case MainScreen: + s.UI.MainScreen.SetSize(msg.Width, msg.Height) + } return s, nil case cmds.ConnectMsg: @@ -97,6 +106,8 @@ func (s State) Update(msg tea.Msg) (tea.Model, tea.Cmd) { s.Client.Register(nickname, password, channel) s.Client.SetDay() + s.UI.MainScreen.SetSize(s.TerminalWidth, s.TerminalHeight) + go handler.ReadLoop(s.Client) return s, textinput.Blink diff --git a/ui/mainscreen/mainscreen.go b/ui/mainscreen/mainscreen.go index 0d9d79c..3564792 100644 --- a/ui/mainscreen/mainscreen.go +++ b/ui/mainscreen/mainscreen.go @@ -47,10 +47,10 @@ const ( ) type State struct { - Client *irc.Client - Viewport *viewport.Model - FocusIndex Window - TabRenderingDirection TabDirection + Client *irc.Client + Viewport *viewport.Model + FocusIndex Window + // TabRenderingDirection TabDirection InputBox InputState SidePanel *SidePanelState @@ -61,12 +61,12 @@ func NewMainScreen(client *irc.Client) State { newViewport.Style = MessagesStyle return State{ - Client: client, - Viewport: &newViewport, - FocusIndex: InputBox, - InputBox: NewInputBox(), - SidePanel: NewSidePanel(client), - TabRenderingDirection: Right, + Client: client, + Viewport: &newViewport, + FocusIndex: InputBox, + InputBox: NewInputBox(), + SidePanel: NewSidePanel(client), + // TabRenderingDirection: Right, } } @@ -77,7 +77,7 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) { switch msg := msg.(type) { case cmds.ReceivedIRCMsgMsg: wasAtBottom := s.Viewport.AtBottom() - s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannelIndex].History) + s.Viewport.SetContent(s.Client.ActiveChannel.Value.History) if wasAtBottom { s.Viewport.GotoBottom() @@ -88,30 +88,31 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) { cmd = handler.HandleSlashCommand(msg.Msg, s.Client) return s, cmd } else { - if s.Client.ActiveChannel != s.Client.Host { + if s.Client.ActiveChannel.Value.Name != s.Client.Host { fullMsg := s.Client.Nickname + ": " + msg.Msg msgOpts := irc.MsgFmtOpts{ WithTimestamp: true, } - s.Client.Channels[s.Client.ActiveChannelIndex].AppendMsg(msg.Timestamp, fullMsg, msgOpts) + s.Client.ActiveChannel.Value.AppendMsg(msg.Timestamp, fullMsg, msgOpts) // TODO: make sure to only append the message to the history if server sends back no errors - s.Client.SendCommand(commands.PRIVMSG, s.Client.ActiveChannel, msg.Msg) - s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannelIndex].History) + s.Client.SendCommand(commands.PRIVMSG, s.Client.ActiveChannel.Value.Name, msg.Msg) + s.Viewport.SetContent(s.Client.ActiveChannel.Value.History) s.Viewport.GotoBottom() } } return s, nil case cmds.SwitchChannelsMsg: - s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannelIndex].History) + s.Viewport.SetContent(s.Client.ActiveChannel.Value.History) + // log.Println(s) s.Viewport.GotoBottom() *s.SidePanel, cmd = s.SidePanel.Update(msg) return s, cmd case cmds.UpdateTabBarMsg: - s.Client.LastTabIndexInTabBar = len(s.Client.Channels) - 1 - s.TabRenderingDirection = Left + // s.Client.LastTabIndexInTabBar = len(s.Client.Channels) - 1 + // s.TabRenderingDirection = Left return s, nil case tea.KeyMsg: key := msg.String() @@ -152,31 +153,33 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) { } // we need this so that the viewport doesnt scroll to the bottom - if len(s.Client.Channels) == 1 { + if s.Client.RootChannel.Next == s.Client.RootChannel { return s, nil } if key == "right" { - s.Client.ActiveChannelIndex++ + // s.Client.ActiveChannelIndex++ + s.Client.ActiveChannel = s.Client.ActiveChannel.Next } else { - s.Client.ActiveChannelIndex-- + s.Client.ActiveChannel = s.Client.ActiveChannel.Prev + // s.Client.ActiveChannelIndex-- } - if s.Client.ActiveChannelIndex >= len(s.Client.Channels) { - s.Client.ActiveChannelIndex = 0 - } else if s.Client.ActiveChannelIndex < 0 { - s.Client.ActiveChannelIndex = len(s.Client.Channels) - 1 - } + // if s.Client.ActiveChannelIndex >= len(s.Client.Channels) { + // s.Client.ActiveChannelIndex = 0 + // } else if s.Client.ActiveChannelIndex < 0 { + // s.Client.ActiveChannelIndex = len(s.Client.Channels) - 1 + // } - if s.Client.ActiveChannelIndex > s.Client.LastTabIndexInTabBar { - s.TabRenderingDirection = Left - s.Client.LastTabIndexInTabBar = s.Client.ActiveChannelIndex - } else if s.Client.ActiveChannelIndex < s.Client.FirstTabIndexInTabBar { - s.Client.FirstTabIndexInTabBar = s.Client.ActiveChannelIndex - s.TabRenderingDirection = Right - } + // if s.Client.ActiveChannelIndex > s.Client.LastTabIndexInTabBar { + // s.TabRenderingDirection = Left + // s.Client.LastTabIndexInTabBar = s.Client.ActiveChannelIndex + // } else if s.Client.ActiveChannelIndex < s.Client.FirstTabIndexInTabBar { + // s.Client.FirstTabIndexInTabBar = s.Client.ActiveChannelIndex + // s.TabRenderingDirection = Right + // } - s.Client.ActiveChannel = s.Client.Channels[s.Client.ActiveChannelIndex].Name + // s.Client.ActiveChannel = s.Client.Channels[s.Client.ActiveChannelIndex].Name return s, cmds.SwitchChannels case "g": if s.FocusIndex == Viewport { @@ -272,10 +275,8 @@ func (s *State) SetSize(width, height int) { s.Viewport.Style = s.Viewport.Style.Height(newHeight) // we need this to render an empty viewport - history := "" - if len(s.Client.Channels) != 0 { - history = s.Client.Channels[s.Client.ActiveChannelIndex].History - } + // history := "" + history := s.Client.ActiveChannel.Value.History // we need to re-set the content because words wrap differently on different sizes s.Viewport.SetContent(history) @@ -286,51 +287,65 @@ func (s State) buildTabBar(rightArrow string, leftArrow string) string { var renderedTabs []string tabs := "" - switch s.TabRenderingDirection { - case Left: - for i := s.Client.LastTabIndexInTabBar; i >= 0; i-- { - if s.Client.Channels[i].Name == s.Client.ActiveChannel { - renderedTabs = append([]string{activeTab.Render(s.Client.Channels[i].Name)}, renderedTabs...) - } else { - renderedTabs = append([]string{tab.Render(s.Client.Channels[i].Name)}, renderedTabs...) - } - - tabs = lipgloss.JoinHorizontal( - lipgloss.Top, - renderedTabs..., - ) - - if lipgloss.Width(tabs) > lipgloss.Width(s.Viewport.View())-lipgloss.Width(leftArrow)-lipgloss.Width(rightArrow) { - // set the first tab to be displayed to the index of the previous tab in the loop - s.Client.FirstTabIndexInTabBar = i + 1 - // dont render the newly added tab - renderedTabs = renderedTabs[1:] - break - } + current := s.Client.RootChannel + for { + if current == s.Client.ActiveChannel { + renderedTabs = append(renderedTabs, activeTab.Render(current.Value.Name)) + } else { + renderedTabs = append(renderedTabs, tab.Render(current.Value.Name)) } - case Right: - for i := s.Client.FirstTabIndexInTabBar; i < len(s.Client.Channels); i++ { - if s.Client.Channels[i].Name == s.Client.ActiveChannel { - renderedTabs = append(renderedTabs, activeTab.Render(s.Client.Channels[i].Name)) - } else { - renderedTabs = append(renderedTabs, tab.Render(s.Client.Channels[i].Name)) - } - tabs = lipgloss.JoinHorizontal( - lipgloss.Top, - renderedTabs..., - ) - - if lipgloss.Width(tabs) > lipgloss.Width(s.Viewport.View())-lipgloss.Width(leftArrow)-lipgloss.Width(rightArrow) { - // set the last tab to be displayed to the index of the previous tab in the loop - s.Client.LastTabIndexInTabBar = i - 1 - // dont render the newly added tab - renderedTabs = renderedTabs[:len(renderedTabs)-1] - break - } + current = current.Next + if current == s.Client.RootChannel { + break } } + // switch s.TabRenderingDirection { + // case Left: + // for i := s.Client.LastTabIndexInTabBar; i >= 0; i-- { + // if s.Client.Channels[i].Name == s.Client.ActiveChannel.Name { + // renderedTabs = append([]string{activeTab.Render(s.Client.Channels[i].Name)}, renderedTabs...) + // } else { + // renderedTabs = append([]string{tab.Render(s.Client.Channels[i].Name)}, renderedTabs...) + // } + + // tabs = lipgloss.JoinHorizontal( + // lipgloss.Top, + // renderedTabs..., + // ) + + // if lipgloss.Width(tabs) > lipgloss.Width(s.Viewport.View())-lipgloss.Width(leftArrow)-lipgloss.Width(rightArrow) { + // // set the first tab to be displayed to the index of the previous tab in the loop + // s.Client.FirstTabIndexInTabBar = i + 1 + // // dont render the newly added tab + // renderedTabs = renderedTabs[1:] + // break + // } + // } + // case Right: + // for i := s.Client.FirstTabIndexInTabBar; i < len(s.Client.Channels); i++ { + // if s.Client.Channels[i].Name == s.Client.ActiveChannel.Name { + // renderedTabs = append(renderedTabs, activeTab.Render(s.Client.Channels[i].Name)) + // } else { + // renderedTabs = append(renderedTabs, tab.Render(s.Client.Channels[i].Name)) + // } + + // tabs = lipgloss.JoinHorizontal( + // lipgloss.Top, + // renderedTabs..., + // ) + + // if lipgloss.Width(tabs) > lipgloss.Width(s.Viewport.View())-lipgloss.Width(leftArrow)-lipgloss.Width(rightArrow) { + // // set the last tab to be displayed to the index of the previous tab in the loop + // s.Client.LastTabIndexInTabBar = i - 1 + // // dont render the newly added tab + // renderedTabs = renderedTabs[:len(renderedTabs)-1] + // break + // } + // } + // } + tabs = lipgloss.JoinHorizontal( lipgloss.Top, renderedTabs..., @@ -346,13 +361,13 @@ func (s State) View() string { tabs := s.buildTabBar(leftArrow, rightArrow) tabBar := strings.Builder{} - if s.Client.FirstTabIndexInTabBar != 0 { - leftArrow = leftArrowLit.Render("❰") - } + // if s.Client.FirstTabIndexInTabBar != 0 { + // leftArrow = leftArrowLit.Render("❰") + // } - if s.Client.LastTabIndexInTabBar != len(s.Client.Channels)-1 { - rightArrow = rightArrowLit.Render("❱") - } + // if s.Client.LastTabIndexInTabBar != len(s.Client.Channels)-1 { + // rightArrow = rightArrowLit.Render("❱") + // } tabBarLine := tabLine.Render( strings.Repeat( diff --git a/ui/mainscreen/sidepanel.go b/ui/mainscreen/sidepanel.go index 00ac7dc..b3146b3 100644 --- a/ui/mainscreen/sidepanel.go +++ b/ui/mainscreen/sidepanel.go @@ -39,9 +39,7 @@ type SidePanelState struct { func (s *SidePanelState) getHeader() string { usersCount := 0 - if len(s.Client.Channels) != 0 { - usersCount = len(s.Client.Channels[s.Client.ActiveChannelIndex].Users) - } + usersCount = len(s.Client.ActiveChannel.Value.Users) separator := strings.Repeat("—", s.Viewport.Width-s.Viewport.Style.GetHorizontalFrameSize()) + "\n" header := fmt.Sprintf("%d Users\n", usersCount) + separator @@ -78,11 +76,9 @@ func lessCaseInsensitive(s, t string) bool { func (s *SidePanelState) getLatestNicks() []string { nicks := make([]string, 0) - if len(s.Client.Channels) != 0 { - for nick, user := range s.Client.Channels[s.Client.ActiveChannelIndex].Users { - _nick := user.Prefix + nick - nicks = append(nicks, _nick) - } + for nick, user := range s.Client.ActiveChannel.Value.Users { + _nick := user.Prefix + nick + nicks = append(nicks, _nick) } sort.Slice(nicks, func(i, j int) bool { return lessCaseInsensitive(nicks[i], nicks[j]) })