mirror of
https://github.com/IllusionMan1212/gorc.git
synced 2026-10-24 02:12:33 +00:00
feat: handle more commands and properly implement older ones
* fix parser tests * write logs to file * gracefully terminate program * make viewport follow output * properly handle channel nicks and display them in the sidepanel * general refactors and fixes
This commit is contained in:
@@ -3,3 +3,6 @@ gorc
|
|||||||
|
|
||||||
# vscode stuff
|
# vscode stuff
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
# log file
|
||||||
|
gorc.log
|
||||||
|
|||||||
+26
-27
@@ -22,11 +22,13 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
// Nickname of user
|
// User prefix in channel
|
||||||
Nick string
|
Prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
@@ -37,27 +39,28 @@ type Channel struct {
|
|||||||
History string
|
History string
|
||||||
|
|
||||||
// Users in this channel
|
// Users in this channel
|
||||||
Users []User
|
// The map key is the user's nickname
|
||||||
|
// and the user struct holds data about that user
|
||||||
|
// such as, prefixes in this channel and etc...
|
||||||
|
Users map[string]User
|
||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
// tcp connection
|
// tcp connection
|
||||||
TcpConn net.Conn
|
TcpConn net.Conn
|
||||||
|
|
||||||
// Channel to receive quit msg
|
|
||||||
Quit chan struct{}
|
|
||||||
|
|
||||||
Host string
|
Host string
|
||||||
Port string
|
Port string
|
||||||
Nickname string
|
Nickname string
|
||||||
MOTD string // TODO:
|
MOTD string // TODO:
|
||||||
Channels map[string]Channel // TODO:
|
Channels map[string]Channel // TODO:
|
||||||
ActiveChannel string
|
ActiveChannel string
|
||||||
|
Tea *tea.Program
|
||||||
}
|
}
|
||||||
|
|
||||||
const CRLF = "\r\n"
|
const CRLF = "\r\n"
|
||||||
|
|
||||||
func NewClient(host string, port string, tlsEnabled bool) Client {
|
func (s *Client) Initialize(host string, port string, tlsEnabled bool) {
|
||||||
addr := fmt.Sprintf("%s:%s", host, port)
|
addr := fmt.Sprintf("%s:%s", host, port)
|
||||||
|
|
||||||
if tlsEnabled {
|
if tlsEnabled {
|
||||||
@@ -68,14 +71,12 @@ func NewClient(host string, port string, tlsEnabled bool) Client {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Client{
|
s.TcpConn = conn
|
||||||
TcpConn: conn,
|
s.Host = host
|
||||||
Host: host,
|
s.Port = port
|
||||||
Port: port,
|
s.ActiveChannel = host
|
||||||
Quit: make(chan struct{}),
|
s.Channels = make(map[string]Channel)
|
||||||
ActiveChannel: host,
|
return
|
||||||
Channels: make(map[string]Channel),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addrTCP, err := net.ResolveTCPAddr("tcp", addr)
|
addrTCP, err := net.ResolveTCPAddr("tcp", addr)
|
||||||
@@ -88,14 +89,11 @@ func NewClient(host string, port string, tlsEnabled bool) Client {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Client{
|
s.TcpConn = conn
|
||||||
TcpConn: conn,
|
s.Host = host
|
||||||
Host: host,
|
s.Port = port
|
||||||
Port: port,
|
s.ActiveChannel = host
|
||||||
Quit: make(chan struct{}),
|
s.Channels = make(map[string]Channel)
|
||||||
ActiveChannel: host,
|
|
||||||
Channels: make(map[string]Channel),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Register(nick string, password string, channel string) {
|
func (c *Client) Register(nick string, password string, channel string) {
|
||||||
@@ -112,11 +110,12 @@ func (c *Client) Register(nick string, password string, channel string) {
|
|||||||
c.SendCommand("CAP", "END")
|
c.SendCommand("CAP", "END")
|
||||||
// joining a channel when registering is optional
|
// joining a channel when registering is optional
|
||||||
if channel != "" {
|
if channel != "" {
|
||||||
c.SendCommand("JOIN", channel)
|
|
||||||
c.ActiveChannel = channel
|
c.ActiveChannel = channel
|
||||||
c.Channels[c.ActiveChannel] = Channel{}
|
c.SendCommand("JOIN", channel)
|
||||||
} else {
|
}
|
||||||
c.Channels[c.ActiveChannel] = Channel{}
|
|
||||||
|
c.Channels[c.ActiveChannel] = Channel{
|
||||||
|
Users: make(map[string]User),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,13 +106,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
/* --- Channel membership prefixes --- */
|
/* --- Channel membership prefixes --- */
|
||||||
const (
|
var UserPrefixes = map[string]bool{
|
||||||
Founder = "+q"
|
"~": true,
|
||||||
Protected = "+a"
|
"&": true,
|
||||||
Operator = "@"
|
"@": true,
|
||||||
HalfOp = "%"
|
"%": true,
|
||||||
Voice = "+v"
|
"+": true,
|
||||||
)
|
}
|
||||||
|
|
||||||
/* --- Numeric Replies --- */
|
/* --- Numeric Replies --- */
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ go 1.16
|
|||||||
replace github.com/charmbracelet/bubbles => /home/illusion/Desktop/repos/bubbles
|
replace github.com/charmbracelet/bubbles => /home/illusion/Desktop/repos/bubbles
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/charmbracelet/bubbles v0.9.0
|
github.com/charmbracelet/bubbles v0.9.0
|
||||||
github.com/charmbracelet/bubbletea v0.19.1
|
github.com/charmbracelet/bubbletea v0.19.3
|
||||||
github.com/charmbracelet/lipgloss v0.4.0
|
github.com/charmbracelet/lipgloss v0.4.0
|
||||||
|
github.com/containerd/console v1.0.3 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
|
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,46 +1,48 @@
|
|||||||
github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY=
|
|
||||||
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/charmbracelet/bubbletea v0.14.1/go.mod h1:b5lOf5mLjMg1tRn1HVla54guZB+jvsyV0yYAQja95zE=
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/charmbracelet/bubbletea v0.19.1 h1:VHuzkJbnTAkxhOfi9+Lb5PYfNM9+Oh+qhP8uDX5ReOU=
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/charmbracelet/bubbletea v0.19.1/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA=
|
github.com/charmbracelet/bubbletea v0.19.3 h1:OKeO/Y13rQQqt4snX+lePB0QrnW80UdrMNolnCcmoAw=
|
||||||
|
github.com/charmbracelet/bubbletea v0.19.3/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA=
|
||||||
github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||||
github.com/charmbracelet/lipgloss v0.3.0/go.mod h1:VkhdBS2eNAmRkTwRKLJCFhCOVkjntMusBDxv7TXahuk=
|
github.com/charmbracelet/lipgloss v0.3.0/go.mod h1:VkhdBS2eNAmRkTwRKLJCFhCOVkjntMusBDxv7TXahuk=
|
||||||
github.com/charmbracelet/lipgloss v0.4.0 h1:768h64EFkGUr8V5yAKV7/Ta0NiVceiPaV+PphaW1K9g=
|
github.com/charmbracelet/lipgloss v0.4.0 h1:768h64EFkGUr8V5yAKV7/Ta0NiVceiPaV+PphaW1K9g=
|
||||||
github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM=
|
github.com/charmbracelet/lipgloss v0.4.0/go.mod h1:vmdkHvce7UzX6xkyf4cca8WlwdQ5RQr8fzta+xl7BOM=
|
||||||
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
|
|
||||||
github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn9dE=
|
|
||||||
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
|
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
|
||||||
|
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
||||||
|
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
|
||||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
|
||||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||||
|
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA=
|
||||||
|
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||||
github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0=
|
github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0=
|
||||||
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
|
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
|
||||||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
|
||||||
|
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
|||||||
@@ -24,10 +24,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := tea.NewProgram(app.InitialState(),
|
_app := app.InitialState()
|
||||||
|
|
||||||
|
p := tea.NewProgram(
|
||||||
|
_app,
|
||||||
tea.WithAltScreen(),
|
tea.WithAltScreen(),
|
||||||
tea.WithMouseCellMotion(),
|
tea.WithMouseCellMotion(),
|
||||||
).Start(); err != nil {
|
)
|
||||||
|
|
||||||
|
_app.Client.Tea = p
|
||||||
|
|
||||||
|
f, err := tea.LogToFile("gorc.log", "gorc")
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = p.Start()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+175
-35
@@ -23,7 +23,11 @@ import "testing"
|
|||||||
func TestParser(t *testing.T) {
|
func TestParser(t *testing.T) {
|
||||||
t.Run("Test simple command", func(t *testing.T) {
|
t.Run("Test simple command", func(t *testing.T) {
|
||||||
testMessage := "foo bar baz asdf"
|
testMessage := "foo bar baz asdf"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Command != "foo" {
|
if ircMessage.Command != "foo" {
|
||||||
t.Fatal("Parsing message with only command and params")
|
t.Fatal("Parsing message with only command and params")
|
||||||
@@ -44,7 +48,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test source", func(t *testing.T) {
|
t.Run("Test source", func(t *testing.T) {
|
||||||
testMessage := ":coolguy foo bar baz asdf"
|
testMessage := ":coolguy foo bar baz asdf"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "coolguy" {
|
if ircMessage.Source != "coolguy" {
|
||||||
t.Fatal("Parsing regular source")
|
t.Fatal("Parsing regular source")
|
||||||
@@ -69,7 +77,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test trailing param", func(t *testing.T) {
|
t.Run("Test trailing param", func(t *testing.T) {
|
||||||
testMessage := "foo bar baz :asdf quux"
|
testMessage := "foo bar baz :asdf quux"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Parameters[0] != "bar" {
|
if ircMessage.Parameters[0] != "bar" {
|
||||||
t.Fatal("Parsing 1st param in message with trailing param")
|
t.Fatal("Parsing 1st param in message with trailing param")
|
||||||
@@ -86,7 +98,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test empty trailing param", func(t *testing.T) {
|
t.Run("Test empty trailing param", func(t *testing.T) {
|
||||||
testMessage := "foo bar baz :"
|
testMessage := "foo bar baz :"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Parameters[0] != "bar" {
|
if ircMessage.Parameters[0] != "bar" {
|
||||||
t.Fatal("Parsing 1st param in message with trailing param")
|
t.Fatal("Parsing 1st param in message with trailing param")
|
||||||
@@ -103,7 +119,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test multiple colons at trailing param", func(t *testing.T) {
|
t.Run("Test multiple colons at trailing param", func(t *testing.T) {
|
||||||
testMessage := "foo bar baz ::asdf"
|
testMessage := "foo bar baz ::asdf"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Parameters[0] != "bar" {
|
if ircMessage.Parameters[0] != "bar" {
|
||||||
t.Fatal("Parsing 1st param in message with trailing param")
|
t.Fatal("Parsing 1st param in message with trailing param")
|
||||||
@@ -120,7 +140,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test source and trailing param", func(t *testing.T) {
|
t.Run("Test source and trailing param", func(t *testing.T) {
|
||||||
testMessage := ":coolguy foo bar baz :asdf quux"
|
testMessage := ":coolguy foo bar baz :asdf quux"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "coolguy" {
|
if ircMessage.Source != "coolguy" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -145,7 +169,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test source and trailing param with spaces", func(t *testing.T) {
|
t.Run("Test source and trailing param with spaces", func(t *testing.T) {
|
||||||
testMessage := ":coolguy foo bar baz : asdf quux "
|
testMessage := ":coolguy foo bar baz : asdf quux "
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "coolguy" {
|
if ircMessage.Source != "coolguy" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -170,7 +198,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test source and trailing param 2", func(t *testing.T) {
|
t.Run("Test source and trailing param 2", func(t *testing.T) {
|
||||||
testMessage := ":coolguy PRIVMSG bar :lol :) "
|
testMessage := ":coolguy PRIVMSG bar :lol :) "
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "coolguy" {
|
if ircMessage.Source != "coolguy" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -191,7 +223,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test source and empty trailing param", func(t *testing.T) {
|
t.Run("Test source and empty trailing param", func(t *testing.T) {
|
||||||
testMessage := ":coolguy foo bar baz :"
|
testMessage := ":coolguy foo bar baz :"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "coolguy" {
|
if ircMessage.Source != "coolguy" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -216,7 +252,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test source and trailing param with spaces 2", func(t *testing.T) {
|
t.Run("Test source and trailing param with spaces 2", func(t *testing.T) {
|
||||||
testMessage := ":coolguy foo bar baz : "
|
testMessage := ":coolguy foo bar baz : "
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "coolguy" {
|
if ircMessage.Source != "coolguy" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -241,7 +281,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test tags", func(t *testing.T) {
|
t.Run("Test tags", func(t *testing.T) {
|
||||||
testMessage := "@id=234AB;rose :dan!d@localhost PRIVMSG #chan :Hey what's up!"
|
testMessage := "@id=234AB;rose :dan!d@localhost PRIVMSG #chan :Hey what's up!"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Tags["id"] != "234AB" {
|
if ircMessage.Tags["id"] != "234AB" {
|
||||||
t.Fatal("Parsing regular tag")
|
t.Fatal("Parsing regular tag")
|
||||||
@@ -270,7 +314,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test tags 2", func(t *testing.T) {
|
t.Run("Test tags 2", func(t *testing.T) {
|
||||||
testMessage := "@a=b;c=32;k;rt=q17 foo"
|
testMessage := "@a=b;c=32;k;rt=q17 foo"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Tags["a"] != "b" {
|
if ircMessage.Tags["a"] != "b" {
|
||||||
t.Fatal("Parsing regular tag")
|
t.Fatal("Parsing regular tag")
|
||||||
@@ -296,7 +344,11 @@ func TestParser(t *testing.T) {
|
|||||||
// TODO: fails
|
// TODO: fails
|
||||||
t.Run("Test escaped tags", func(t *testing.T) {
|
t.Run("Test escaped tags", func(t *testing.T) {
|
||||||
testMessage := "@a=b\\\\and\\nk;c=72\\s45;d=gh\\:764 foo"
|
testMessage := "@a=b\\\\and\\nk;c=72\\s45;d=gh\\:764 foo"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Tags["a"] != "b\\and\nk" {
|
if ircMessage.Tags["a"] != "b\\and\nk" {
|
||||||
t.Fatal("Parsing escaped tag")
|
t.Fatal("Parsing escaped tag")
|
||||||
@@ -317,7 +369,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test tags with source", func(t *testing.T) {
|
t.Run("Test tags with source", func(t *testing.T) {
|
||||||
testMessage := "@c;h=;a=b :quux ab cd"
|
testMessage := "@c;h=;a=b :quux ab cd"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Tags["c"] != "" {
|
if ircMessage.Tags["c"] != "" {
|
||||||
t.Log(ircMessage.Tags)
|
t.Log(ircMessage.Tags)
|
||||||
@@ -348,7 +404,11 @@ func TestParser(t *testing.T) {
|
|||||||
// TODO: FAIL
|
// TODO: FAIL
|
||||||
t.Run("Test single param with preceeding :", func(t *testing.T) {
|
t.Run("Test single param with preceeding :", func(t *testing.T) {
|
||||||
testMessage := ":src JOIN :#chan"
|
testMessage := ":src JOIN :#chan"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Parameters[0] != "#chan" {
|
if ircMessage.Parameters[0] != "#chan" {
|
||||||
t.Fatal("Parsing a single param with a preceeding \":\"")
|
t.Fatal("Parsing a single param with a preceeding \":\"")
|
||||||
@@ -357,7 +417,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test with no space as last param", func(t *testing.T) {
|
t.Run("Test with no space as last param", func(t *testing.T) {
|
||||||
testMessage := ":src AWAY"
|
testMessage := ":src AWAY"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "src" {
|
if ircMessage.Source != "src" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -374,7 +438,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test with space as last param", func(t *testing.T) {
|
t.Run("Test with space as last param", func(t *testing.T) {
|
||||||
testMessage := ":src AWAY "
|
testMessage := ":src AWAY "
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "src" {
|
if ircMessage.Source != "src" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -392,7 +460,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test tab as space", func(t *testing.T) {
|
t.Run("Test tab as space", func(t *testing.T) {
|
||||||
testMessage := ":cool\tguy foo bar baz"
|
testMessage := ":cool\tguy foo bar baz"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "cool\tguy" {
|
if ircMessage.Source != "cool\tguy" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -413,7 +485,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test weird control codes in source", func(t *testing.T) {
|
t.Run("Test weird control codes in source", func(t *testing.T) {
|
||||||
testMessage := ":coolguy!ag@net\x035w\x03ork.admin PRIVMSG foo :bar baz"
|
testMessage := ":coolguy!ag@net\x035w\x03ork.admin PRIVMSG foo :bar baz"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "coolguy!ag@net\x035w\x03ork.admin" {
|
if ircMessage.Source != "coolguy!ag@net\x035w\x03ork.admin" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -434,7 +510,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test more weird control codes", func(t *testing.T) {
|
t.Run("Test more weird control codes", func(t *testing.T) {
|
||||||
testMessage := ":coolguy!~ag@n\x02et\x0305w\x0fork.admin PRIVMSG foo :bar baz"
|
testMessage := ":coolguy!~ag@n\x02et\x0305w\x0fork.admin PRIVMSG foo :bar baz"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "coolguy!~ag@n\x02et\x0305w\x0fork.admin" {
|
if ircMessage.Source != "coolguy!~ag@n\x02et\x0305w\x0fork.admin" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -455,7 +535,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test full message", func(t *testing.T) {
|
t.Run("Test full message", func(t *testing.T) {
|
||||||
testMessage := "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= :irc.example.com COMMAND param1 param2 :param3 param3"
|
testMessage := "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= :irc.example.com COMMAND param1 param2 :param3 param3"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Tags["tag1"] != "value1" {
|
if ircMessage.Tags["tag1"] != "value1" {
|
||||||
t.Fatal("Parsing regular tag")
|
t.Fatal("Parsing regular tag")
|
||||||
@@ -496,7 +580,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test message without tags", func(t *testing.T) {
|
t.Run("Test message without tags", func(t *testing.T) {
|
||||||
testMessage := ":irc.example.com COMMAND param1 param2 :param3 param3"
|
testMessage := ":irc.example.com COMMAND param1 param2 :param3 param3"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "irc.example.com" {
|
if ircMessage.Source != "irc.example.com" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -521,7 +609,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test message without source", func(t *testing.T) {
|
t.Run("Test message without source", func(t *testing.T) {
|
||||||
testMessage := "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= COMMAND param1 param2 :param3 param3"
|
testMessage := "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= COMMAND param1 param2 :param3 param3"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Tags["tag1"] != "value1" {
|
if ircMessage.Tags["tag1"] != "value1" {
|
||||||
t.Fatal("Parsing regular tag")
|
t.Fatal("Parsing regular tag")
|
||||||
@@ -563,7 +655,11 @@ func TestParser(t *testing.T) {
|
|||||||
// TODO: FAIL
|
// TODO: FAIL
|
||||||
t.Run("Test yaml encoding with slashes", func(t *testing.T) {
|
t.Run("Test yaml encoding with slashes", func(t *testing.T) {
|
||||||
testMessage := "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND"
|
testMessage := "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Tags["foo"] != "\\\\;\\s \r\n" {
|
if ircMessage.Tags["foo"] != "\\\\;\\s \r\n" {
|
||||||
t.Fatal("Parsing escaped tag")
|
t.Fatal("Parsing escaped tag")
|
||||||
@@ -576,7 +672,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test broken messages 1", func(t *testing.T) {
|
t.Run("Test broken messages 1", func(t *testing.T) {
|
||||||
testMessage := ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters"
|
testMessage := ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "gravel.mozilla.org" {
|
if ircMessage.Source != "gravel.mozilla.org" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -597,7 +697,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test broken messages 2", func(t *testing.T) {
|
t.Run("Test broken messages 2", func(t *testing.T) {
|
||||||
testMessage := ":gravel.mozilla.org MODE #tckk +n "
|
testMessage := ":gravel.mozilla.org MODE #tckk +n "
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "gravel.mozilla.org" {
|
if ircMessage.Source != "gravel.mozilla.org" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -618,7 +722,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test broken messages 3", func(t *testing.T) {
|
t.Run("Test broken messages 3", func(t *testing.T) {
|
||||||
testMessage := ":services.esper.net MODE #foo-bar +o foobar "
|
testMessage := ":services.esper.net MODE #foo-bar +o foobar "
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "services.esper.net" {
|
if ircMessage.Source != "services.esper.net" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -644,7 +752,11 @@ func TestParser(t *testing.T) {
|
|||||||
// TODO: FAIL
|
// TODO: FAIL
|
||||||
t.Run("Test tags. they should be parsed char-at-a-time", func(t *testing.T) {
|
t.Run("Test tags. they should be parsed char-at-a-time", func(t *testing.T) {
|
||||||
testMessage := "@tag1=value\\\\ntest COMMAND"
|
testMessage := "@tag1=value\\\\ntest COMMAND"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Tags["tag1"] != "value\\ntest" {
|
if ircMessage.Tags["tag1"] != "value\\ntest" {
|
||||||
t.Fatal("Parsing escaped tag")
|
t.Fatal("Parsing escaped tag")
|
||||||
@@ -662,7 +774,11 @@ func TestParser(t *testing.T) {
|
|||||||
// TODO: FAIl
|
// TODO: FAIl
|
||||||
t.Run("Test tag escape for char that doesn't need it", func(t *testing.T) {
|
t.Run("Test tag escape for char that doesn't need it", func(t *testing.T) {
|
||||||
testMessage := "@tag1=value\\1 COMMAND"
|
testMessage := "@tag1=value\\1 COMMAND"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Tags["tag1"] != "value1" {
|
if ircMessage.Tags["tag1"] != "value1" {
|
||||||
t.Fatal("Parsing escaped tag")
|
t.Fatal("Parsing escaped tag")
|
||||||
@@ -676,7 +792,11 @@ func TestParser(t *testing.T) {
|
|||||||
// TODO: FAIL
|
// TODO: FAIL
|
||||||
t.Run("Test slash at end of tag", func(t *testing.T) {
|
t.Run("Test slash at end of tag", func(t *testing.T) {
|
||||||
testMessage := "@tag1=value1\\ COMMAND"
|
testMessage := "@tag1=value1\\ COMMAND"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Tags["tag1"] != "value1" {
|
if ircMessage.Tags["tag1"] != "value1" {
|
||||||
t.Fatal("Parsing escaped tag")
|
t.Fatal("Parsing escaped tag")
|
||||||
@@ -689,7 +809,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test duplicate tags", func(t *testing.T) {
|
t.Run("Test duplicate tags", func(t *testing.T) {
|
||||||
testMessage := "@tag1=1;tag2=3;tag3=4;tag1=5 COMMAND"
|
testMessage := "@tag1=1;tag2=3;tag3=4;tag1=5 COMMAND"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Tags["tag1"] != "5" {
|
if ircMessage.Tags["tag1"] != "5" {
|
||||||
t.Fatal("Parsing duplicate tag")
|
t.Fatal("Parsing duplicate tag")
|
||||||
@@ -710,7 +834,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test vendored tags can have the same name as unvendored tag", func(t *testing.T) {
|
t.Run("Test vendored tags can have the same name as unvendored tag", func(t *testing.T) {
|
||||||
testMessage := "@tag1=1;tag2=3;tag3=4;tag1=5;vendor/tag2=8 COMMAND"
|
testMessage := "@tag1=1;tag2=3;tag3=4;tag1=5;vendor/tag2=8 COMMAND"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Tags["tag1"] != "5" {
|
if ircMessage.Tags["tag1"] != "5" {
|
||||||
t.Fatal("Parsing duplicate tag")
|
t.Fatal("Parsing duplicate tag")
|
||||||
@@ -735,7 +863,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test: some parsers handle /MODE in a special way", func(t *testing.T) {
|
t.Run("Test: some parsers handle /MODE in a special way", func(t *testing.T) {
|
||||||
testMessage := ":SomeOp MODE #channel :+i"
|
testMessage := ":SomeOp MODE #channel :+i"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "SomeOp" {
|
if ircMessage.Source != "SomeOp" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -756,7 +888,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test: some parsers handle /MODE in a special way 2", func(t *testing.T) {
|
t.Run("Test: some parsers handle /MODE in a special way 2", func(t *testing.T) {
|
||||||
testMessage := ":SomeOp MODE #channel +oo SomeUser :AnotherUser"
|
testMessage := ":SomeOp MODE #channel +oo SomeUser :AnotherUser"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "SomeOp" {
|
if ircMessage.Source != "SomeOp" {
|
||||||
t.Fatal("Parsing source")
|
t.Fatal("Parsing source")
|
||||||
@@ -785,7 +921,11 @@ func TestParser(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Test multiple spaces", func(t *testing.T) {
|
t.Run("Test multiple spaces", func(t *testing.T) {
|
||||||
testMessage := ":src JOIN #chan :thing lol haha"
|
testMessage := ":src JOIN #chan :thing lol haha"
|
||||||
ircMessage := ParseIRCMessage(testMessage)
|
ircMessage, value := ParseIRCMessage(testMessage)
|
||||||
|
|
||||||
|
if !value {
|
||||||
|
t.Fatal("Invalid irc message")
|
||||||
|
}
|
||||||
|
|
||||||
if ircMessage.Source != "src" {
|
if ircMessage.Source != "src" {
|
||||||
t.Fatal("Parsing source with multiple spaces in between")
|
t.Fatal("Parsing source with multiple spaces in between")
|
||||||
|
|||||||
+4
-6
@@ -30,7 +30,7 @@ import (
|
|||||||
type Screen int
|
type Screen int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Login = iota
|
Login Screen = iota
|
||||||
MainScreen
|
MainScreen
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,15 +52,13 @@ func initialUiState(client *client.Client) UI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitialState() State {
|
func InitialState() *State {
|
||||||
client := &client.Client{}
|
client := &client.Client{}
|
||||||
|
|
||||||
s := State{
|
return &State{
|
||||||
Client: client,
|
Client: client,
|
||||||
UI: initialUiState(client),
|
UI: initialUiState(client),
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s State) Init() tea.Cmd {
|
func (s State) Init() tea.Cmd {
|
||||||
@@ -94,7 +92,7 @@ func (s State) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
s.UI.CurrentScreen = MainScreen
|
s.UI.CurrentScreen = MainScreen
|
||||||
|
|
||||||
// create new client with the provided host and port
|
// create new client with the provided host and port
|
||||||
(*s.Client) = client.NewClient(host, port, tlsEnabled)
|
s.Client.Initialize(host, port, tlsEnabled)
|
||||||
s.Client.Register(nickname, password, channel)
|
s.Client.Register(nickname, password, channel)
|
||||||
|
|
||||||
// 512 bytes as a base + 8192 additional bytes for tags
|
// 512 bytes as a base + 8192 additional bytes for tags
|
||||||
|
|||||||
@@ -19,13 +19,8 @@ package app
|
|||||||
import tea "github.com/charmbracelet/bubbletea"
|
import tea "github.com/charmbracelet/bubbletea"
|
||||||
|
|
||||||
func (s *State) Quit() tea.Msg {
|
func (s *State) Quit() tea.Msg {
|
||||||
if s.Client.Quit != nil {
|
|
||||||
close(s.Client.Quit)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Client.TcpConn != nil {
|
if s.Client.TcpConn != nil {
|
||||||
s.Client.SendCommand("QUIT")
|
s.Client.SendCommand("QUIT")
|
||||||
s.Client.TcpConn.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tea.Quit()
|
return tea.Quit()
|
||||||
|
|||||||
+18
-6
@@ -17,6 +17,7 @@
|
|||||||
package mainscreen
|
package mainscreen
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -26,13 +27,13 @@ import (
|
|||||||
|
|
||||||
func (s *State) ReadLoop() {
|
func (s *State) ReadLoop() {
|
||||||
for {
|
for {
|
||||||
select {
|
|
||||||
case <-s.Client.Quit:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
msg, err := s.ConnReader.ReadString('\n')
|
msg, err := s.ConnReader.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
if err != io.EOF {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
s.Client.TcpConn.Close()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = strings.Replace(msg, "\r\n", "", 1)
|
msg = strings.Replace(msg, "\r\n", "", 1)
|
||||||
@@ -40,7 +41,6 @@ func (s *State) ReadLoop() {
|
|||||||
s.HandleCommand(ircMessage)
|
s.HandleCommand(ircMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendPrivMsg struct {
|
type SendPrivMsg struct {
|
||||||
@@ -52,3 +52,15 @@ func (s InputState) SendingPrivMsg(msg string) tea.Cmd {
|
|||||||
return SendPrivMsg{Msg: msg}
|
return SendPrivMsg{Msg: msg}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ReceivedIRCMsgMsg struct{}
|
||||||
|
|
||||||
|
func ReceivedIRCMsg() tea.Msg {
|
||||||
|
return ReceivedIRCMsgMsg{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwitchChannelsMsg struct{}
|
||||||
|
|
||||||
|
func SwitchChannels() tea.Msg {
|
||||||
|
return SwitchChannelsMsg{}
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,7 +62,20 @@ func (s InputState) Update(msg tea.Msg) (InputState, tea.Cmd) {
|
|||||||
return s, cmd
|
return s, cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InputState) Focus() {
|
||||||
|
s.Input.Focus()
|
||||||
|
s.Style = s.Style.Copy().BorderForeground(lipgloss.Color("105"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InputState) Blur() {
|
||||||
|
s.Input.Blur()
|
||||||
|
s.Style = s.Style.Copy().BorderForeground(lipgloss.Color("#EEE"))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InputState) SetSize(width int) {
|
func (s *InputState) SetSize(width int) {
|
||||||
|
// set a max width for the input field so it scrolls horizontally instead of wrapping to a newline
|
||||||
|
// -4 for input prompt char, cursor, and some extra magical padding
|
||||||
|
s.Input.Width = width - s.Style.GetHorizontalFrameSize() - 4
|
||||||
s.Style = s.Style.Width(width - s.Style.GetHorizontalBorderSize())
|
s.Style = s.Style.Width(width - s.Style.GetHorizontalBorderSize())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+118
-51
@@ -19,6 +19,7 @@ package mainscreen
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
@@ -34,7 +35,8 @@ import (
|
|||||||
type Window int
|
type Window int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Viewport = iota
|
Viewport Window = iota
|
||||||
|
SidePanel
|
||||||
InputBox
|
InputBox
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,7 +44,6 @@ type State struct {
|
|||||||
ConnReader *bufio.Reader
|
ConnReader *bufio.Reader
|
||||||
Client *client.Client
|
Client *client.Client
|
||||||
Viewport *viewport.Model
|
Viewport *viewport.Model
|
||||||
Style lipgloss.Style
|
|
||||||
FocusIndex Window
|
FocusIndex Window
|
||||||
|
|
||||||
InputBox InputState
|
InputBox InputState
|
||||||
@@ -52,14 +53,14 @@ type State struct {
|
|||||||
func NewMainScreen(client *client.Client) State {
|
func NewMainScreen(client *client.Client) State {
|
||||||
newViewport := &viewport.Model{
|
newViewport := &viewport.Model{
|
||||||
HighPerformanceRendering: false,
|
HighPerformanceRendering: false,
|
||||||
Wrap: true,
|
Wrap: viewport.Wrap,
|
||||||
|
Style: MessagesStyle.Copy(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return State{
|
return State{
|
||||||
ConnReader: nil,
|
ConnReader: nil,
|
||||||
Client: client,
|
Client: client,
|
||||||
Viewport: newViewport,
|
Viewport: newViewport,
|
||||||
Style: MessagesStyle.Copy(),
|
|
||||||
FocusIndex: InputBox,
|
FocusIndex: InputBox,
|
||||||
InputBox: NewInputBox(),
|
InputBox: NewInputBox(),
|
||||||
SidePanel: NewSidePanel(client),
|
SidePanel: NewSidePanel(client),
|
||||||
@@ -71,6 +72,9 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
|
|||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
|
case ReceivedIRCMsgMsg:
|
||||||
|
s.Viewport.GotoBottom()
|
||||||
|
return s, nil
|
||||||
case SendPrivMsg:
|
case SendPrivMsg:
|
||||||
// for sending slash commands
|
// for sending slash commands
|
||||||
// TODO: make this better
|
// TODO: make this better
|
||||||
@@ -83,21 +87,28 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
if strings.ToUpper(command) == commands.JOIN {
|
if strings.ToUpper(command) == commands.JOIN {
|
||||||
s.Client.ActiveChannel = params[0]
|
s.Client.ActiveChannel = params[0]
|
||||||
s.Client.Channels[params[0]] = client.Channel{}
|
if _, ok := s.Client.Channels[params[0]]; !ok {
|
||||||
s.Client.SendCommand(command, params...)
|
s.Client.Channels[params[0]] = client.Channel{
|
||||||
return s, nil
|
Users: make(map[string]client.User),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Client.SendCommand(commands.JOIN, params...)
|
||||||
|
return s, SwitchChannels
|
||||||
}
|
}
|
||||||
s.Client.SendCommand(command, params...)
|
s.Client.SendCommand(command, params...)
|
||||||
} else {
|
} else {
|
||||||
// TODO: dont send command when activechannel == c.host
|
if s.Client.ActiveChannel != s.Client.Host {
|
||||||
fullMsg := s.Client.Nickname + ": " + msg.Msg
|
fullMsg := s.Client.Nickname + ": " + msg.Msg
|
||||||
if c, ok := s.Client.Channels[s.Client.ActiveChannel]; ok {
|
if c, ok := s.Client.Channels[s.Client.ActiveChannel]; ok {
|
||||||
c.History += fullMsg + client.CRLF
|
c.History += fullMsg + client.CRLF
|
||||||
|
|
||||||
s.Client.Channels[s.Client.ActiveChannel] = c
|
s.Client.Channels[s.Client.ActiveChannel] = c
|
||||||
}
|
}
|
||||||
|
// 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.Client.SendCommand(commands.PRIVMSG, s.Client.ActiveChannel, msg.Msg)
|
||||||
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannel].History)
|
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannel].History)
|
||||||
|
s.Viewport.GotoBottom()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
@@ -111,35 +122,32 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
|
|||||||
s.FocusIndex--
|
s.FocusIndex--
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.FocusIndex > 1 {
|
if s.FocusIndex > 2 {
|
||||||
s.FocusIndex = 0
|
s.FocusIndex = 0
|
||||||
} else if s.FocusIndex < 0 {
|
} else if s.FocusIndex < 0 {
|
||||||
s.FocusIndex = 1
|
s.FocusIndex = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
switch s.FocusIndex {
|
switch s.FocusIndex {
|
||||||
case Viewport:
|
case Viewport:
|
||||||
s.Style = s.Style.Copy().BorderForeground(lipgloss.Color("105"))
|
s.Focus()
|
||||||
s.InputBox.Style = s.InputBox.Style.Copy().BorderForeground(lipgloss.Color("#EEE"))
|
s.InputBox.Blur()
|
||||||
|
s.SidePanel.Blur()
|
||||||
s.InputBox.Input.Blur()
|
|
||||||
case InputBox:
|
case InputBox:
|
||||||
s.Style = s.Style.Copy().BorderForeground(lipgloss.Color("#EEE"))
|
|
||||||
s.InputBox.Style = s.InputBox.Style.Copy().BorderForeground(lipgloss.Color("105"))
|
|
||||||
|
|
||||||
s.InputBox, cmd = s.InputBox.Update(msg)
|
|
||||||
cmds = append(cmds, cmd)
|
|
||||||
cmds = append(cmds, textinput.Blink)
|
cmds = append(cmds, textinput.Blink)
|
||||||
s.InputBox.Input.Focus()
|
s.Blur()
|
||||||
|
s.InputBox.Focus()
|
||||||
|
s.SidePanel.Blur()
|
||||||
|
case SidePanel:
|
||||||
|
s.Blur()
|
||||||
|
s.InputBox.Blur()
|
||||||
|
s.SidePanel.Focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, tea.Batch(cmds...)
|
return s, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: maybe we still need this, not sure
|
|
||||||
// s.Viewport.SetContent(s.Client.ActiveChannel.History)
|
|
||||||
|
|
||||||
switch s.FocusIndex {
|
switch s.FocusIndex {
|
||||||
case Viewport:
|
case Viewport:
|
||||||
*s.Viewport, cmd = s.Viewport.Update(msg)
|
*s.Viewport, cmd = s.Viewport.Update(msg)
|
||||||
@@ -149,22 +157,35 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
|
|||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*s.SidePanel, cmd = s.SidePanel.Update(msg)
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
return s, tea.Batch(cmds...)
|
return s, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *State) Focus() {
|
||||||
|
s.Viewport.Style = s.Viewport.Style.Copy().BorderForeground(lipgloss.Color("105"))
|
||||||
|
}
|
||||||
|
func (s *State) Blur() {
|
||||||
|
s.Viewport.Style = s.Viewport.Style.Copy().BorderForeground(lipgloss.Color("#EEE"))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *State) SetSize(width, height int) {
|
func (s *State) SetSize(width, height int) {
|
||||||
s.InputBox.SetSize(width)
|
s.InputBox.SetSize(width)
|
||||||
s.SidePanel.SetSize(width, height, s.InputBox.Style.Copy().GetVerticalFrameSize())
|
s.SidePanel.SetSize(width, height, s.InputBox.Style.GetVerticalFrameSize())
|
||||||
|
|
||||||
// -1 is for some extra invisible margin or padding or whatever (idk what it is tbh)
|
// We floor here because width is an int and some fractions are lost
|
||||||
newViewportWidth := (width * 8 / 10) - s.Style.GetHorizontalBorderSize() - 1
|
// and also because we ceil the sidepanel's width
|
||||||
newViewportHeight := height - s.InputBox.Style.GetVerticalFrameSize() - s.Style.GetVerticalBorderSize() - 1
|
// -1 is for some extra invisible margin or padding between the main viewport and the inputbox
|
||||||
|
newWidth := int(math.Floor(float64(width)*8.0/10.0) - float64(s.Viewport.Style.GetHorizontalFrameSize()))
|
||||||
|
newHeight := height - s.InputBox.Style.GetVerticalFrameSize() - s.Viewport.Style.GetVerticalFrameSize() - 1
|
||||||
|
|
||||||
|
s.Viewport.Width = newWidth
|
||||||
|
s.Viewport.Height = newHeight
|
||||||
|
|
||||||
|
s.Viewport.Style = s.Viewport.Style.Width(newWidth)
|
||||||
|
s.Viewport.Style = s.Viewport.Style.Height(newHeight)
|
||||||
|
|
||||||
// copying the existing style is important here otherwise we'll end up with artifacting
|
|
||||||
s.Viewport.Width = newViewportWidth
|
|
||||||
s.Style = s.Style.Width(newViewportWidth)
|
|
||||||
s.Viewport.Height = newViewportHeight
|
|
||||||
s.Style = s.Style.Height(newViewportHeight)
|
|
||||||
// we need to re-set the content because words wrap differently on different sizes
|
// we need to re-set the content because words wrap differently on different sizes
|
||||||
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannel].History)
|
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannel].History)
|
||||||
}
|
}
|
||||||
@@ -173,15 +194,16 @@ func (s *State) HandleCommand(msg parser.IRCMessage) {
|
|||||||
// TODO: handle different commands
|
// TODO: handle different commands
|
||||||
switch msg.Command {
|
switch msg.Command {
|
||||||
case commands.PING:
|
case commands.PING:
|
||||||
s.Client.SendCommand(commands.PONG, msg.Parameters[0])
|
token := msg.Parameters[0]
|
||||||
|
s.Client.SendCommand(commands.PONG, token)
|
||||||
case commands.PRIVMSG:
|
case commands.PRIVMSG:
|
||||||
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
||||||
channel := msg.Parameters[0]
|
channel := msg.Parameters[0]
|
||||||
msgContent := msg.Parameters[1]
|
msgContent := msg.Parameters[1]
|
||||||
fullMsg := fmt.Sprintf("%s: %s", nick, msgContent)
|
privMsg := fmt.Sprintf("%s: %s", nick, msgContent)
|
||||||
|
|
||||||
if c, ok := s.Client.Channels[channel]; ok {
|
if c, ok := s.Client.Channels[channel]; ok {
|
||||||
c.History += fullMsg + client.CRLF
|
c.History += privMsg + client.CRLF
|
||||||
|
|
||||||
s.Client.Channels[channel] = c
|
s.Client.Channels[channel] = c
|
||||||
}
|
}
|
||||||
@@ -190,40 +212,81 @@ func (s *State) HandleCommand(msg parser.IRCMessage) {
|
|||||||
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
||||||
channel := msg.Parameters[0]
|
channel := msg.Parameters[0]
|
||||||
|
|
||||||
fullMsg := fmt.Sprintf("== %s has joined", nick)
|
joinMsg := fmt.Sprintf("== %s has joined", nick)
|
||||||
if c, ok := s.Client.Channels[channel]; ok {
|
if c, ok := s.Client.Channels[channel]; ok {
|
||||||
c.History += fullMsg + client.CRLF
|
c.History += joinMsg + client.CRLF
|
||||||
|
if _, exists := c.Users[nick]; !exists {
|
||||||
|
c.Users[nick] = client.User{}
|
||||||
|
}
|
||||||
|
|
||||||
s.Client.Channels[channel] = c
|
s.Client.Channels[channel] = c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if channel == s.Client.ActiveChannel {
|
||||||
|
s.SidePanel.UpdateNicks()
|
||||||
|
}
|
||||||
|
|
||||||
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannel].History)
|
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannel].History)
|
||||||
case commands.QUIT:
|
case commands.QUIT:
|
||||||
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
||||||
reason := msg.Parameters[0]
|
reason := msg.Parameters[0]
|
||||||
fullMsg := fmt.Sprintf("== %s has quit (%s)", nick, reason)
|
quitMsg := fmt.Sprintf("== %s has quit (%s)", nick, reason)
|
||||||
if c, ok := s.Client.Channels[s.Client.ActiveChannel]; ok {
|
|
||||||
c.History += fullMsg + client.CRLF
|
|
||||||
|
|
||||||
s.Client.Channels[s.Client.ActiveChannel] = c
|
for chanName, channel := range s.Client.Channels {
|
||||||
|
channel.History += quitMsg + client.CRLF
|
||||||
|
delete(channel.Users, nick)
|
||||||
|
|
||||||
|
s.Client.Channels[chanName] = channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.SidePanel.UpdateNicks()
|
||||||
|
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannel].History)
|
||||||
|
case commands.PART:
|
||||||
|
nick := strings.SplitN(msg.Source, "!", 2)[0]
|
||||||
|
channel := msg.Parameters[0]
|
||||||
|
reason := ""
|
||||||
|
if len(msg.Parameters) > 1 {
|
||||||
|
reason = msg.Parameters[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
partMsg := fmt.Sprintf("== %s has left %s (%s)", nick, channel, reason)
|
||||||
|
if c, ok := s.Client.Channels[channel]; ok {
|
||||||
|
c.History += partMsg + client.CRLF
|
||||||
|
delete(c.Users, nick)
|
||||||
|
|
||||||
|
s.Client.Channels[channel] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
s.SidePanel.UpdateNicks()
|
||||||
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannel].History)
|
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannel].History)
|
||||||
case commands.RPL_NAMREPLY:
|
case commands.RPL_NAMREPLY:
|
||||||
// TODO: put the users into their respective channels
|
// TODO: do i need these
|
||||||
// client := msg.Parameters[0]
|
// client := msg.Parameters[0]
|
||||||
// chanSymbol := msg.Parameters[1]
|
// chanSymbol := msg.Parameters[1]
|
||||||
// channel := msg.Parameters[2]
|
channel := msg.Parameters[2]
|
||||||
nicks := strings.Split(msg.Parameters[3], " ")
|
nicks := strings.Split(msg.Parameters[3], " ")
|
||||||
|
|
||||||
s.SidePanel.Nicks = append(s.SidePanel.Nicks, nicks...)
|
if c, ok := s.Client.Channels[channel]; ok {
|
||||||
case commands.RPL_ENDOFNAMES:
|
for _, nick := range nicks {
|
||||||
fullMsg := fmt.Sprintf("%s %s %s %s", msg.Tags, msg.Source, msg.Command, strings.Join(msg.Parameters, " "))
|
prefix := ""
|
||||||
if c, ok := s.Client.Channels[s.Client.ActiveChannel]; ok {
|
_nick := nick
|
||||||
c.History += fullMsg + client.CRLF
|
|
||||||
|
|
||||||
s.Client.Channels[s.Client.ActiveChannel] = c
|
if commands.UserPrefixes[string(nick[0])] {
|
||||||
|
prefix = string(nick[0])
|
||||||
|
_nick = nick[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannel].History)
|
c.Users[_nick] = client.User{
|
||||||
|
Prefix: prefix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if channel == s.Client.ActiveChannel {
|
||||||
|
s.SidePanel.UpdateNicks()
|
||||||
|
}
|
||||||
|
case commands.RPL_ENDOFNAMES:
|
||||||
|
// TODO: what do i do here lol
|
||||||
default:
|
default:
|
||||||
fullMsg := fmt.Sprintf("%s %s %s %s", msg.Tags, msg.Source, msg.Command, strings.Join(msg.Parameters, " "))
|
fullMsg := fmt.Sprintf("%s %s %s %s", msg.Tags, msg.Source, msg.Command, strings.Join(msg.Parameters, " "))
|
||||||
if c, ok := s.Client.Channels[s.Client.ActiveChannel]; ok {
|
if c, ok := s.Client.Channels[s.Client.ActiveChannel]; ok {
|
||||||
@@ -234,10 +297,14 @@ func (s *State) HandleCommand(msg parser.IRCMessage) {
|
|||||||
|
|
||||||
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannel].History)
|
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannel].History)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// send a receivedIRCmsg tea message so the ui can update
|
||||||
|
// we also use this tea message to scroll the viewport down
|
||||||
|
s.Client.Tea.Send(ReceivedIRCMsg())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s State) View() string {
|
func (s State) View() string {
|
||||||
top := lipgloss.JoinHorizontal(lipgloss.Right, s.Style.Render(s.Viewport.View()), s.SidePanel.View())
|
top := lipgloss.JoinHorizontal(lipgloss.Right, s.Viewport.View(), s.SidePanel.View())
|
||||||
screen := lipgloss.JoinVertical(0, top, s.InputBox.View())
|
screen := lipgloss.JoinVertical(0, top, s.InputBox.View())
|
||||||
|
|
||||||
return ui.MainStyle.Render(screen)
|
return ui.MainStyle.Render(screen)
|
||||||
|
|||||||
+71
-13
@@ -18,6 +18,7 @@ package mainscreen
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/viewport"
|
"github.com/charmbracelet/bubbles/viewport"
|
||||||
@@ -27,34 +28,91 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SidePanelState struct {
|
type SidePanelState struct {
|
||||||
// TODO: make a viewport and use that to display the names
|
Client *client.Client
|
||||||
// OR: we could use a list
|
|
||||||
Style lipgloss.Style
|
|
||||||
Nicks []string
|
|
||||||
Viewport viewport.Model
|
Viewport viewport.Model
|
||||||
Content string
|
Focused bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SidePanelState) getHeader() string {
|
||||||
|
separator := strings.Repeat("—", s.Viewport.Width) + "\n"
|
||||||
|
header := fmt.Sprintf("Users in this channel (%d)\n", len(s.Client.Channels[s.Client.ActiveChannel].Users)) + separator
|
||||||
|
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SidePanelState) getLatestNicks() []string {
|
||||||
|
nicks := make([]string, 0)
|
||||||
|
|
||||||
|
for nick, user := range s.Client.Channels[s.Client.ActiveChannel].Users {
|
||||||
|
_nick := user.Prefix + nick
|
||||||
|
nicks = append(nicks, _nick)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nicks
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSidePanel(client *client.Client) *SidePanelState {
|
func NewSidePanel(client *client.Client) *SidePanelState {
|
||||||
return &SidePanelState{
|
return &SidePanelState{
|
||||||
|
Client: client,
|
||||||
|
Viewport: viewport.Model{
|
||||||
|
HighPerformanceRendering: false,
|
||||||
|
Wrap: viewport.Wrap,
|
||||||
Style: SidePanelStyle.Copy(),
|
Style: SidePanelStyle.Copy(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SidePanelState) Update(msg tea.Msg) (SidePanelState, tea.Cmd) {
|
func (s SidePanelState) Update(msg tea.Msg) (SidePanelState, tea.Cmd) {
|
||||||
|
var cmd tea.Cmd
|
||||||
|
var cmds []tea.Cmd
|
||||||
|
|
||||||
|
switch msg.(type) {
|
||||||
|
case SwitchChannelsMsg:
|
||||||
|
s.UpdateNicks()
|
||||||
return s, nil
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Focused {
|
||||||
|
s.Viewport, cmd = s.Viewport.Update(msg)
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, tea.Batch(cmds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SidePanelState) Focus() {
|
||||||
|
s.Focused = true
|
||||||
|
s.Viewport.Style = s.Viewport.Style.BorderForeground(lipgloss.Color("105"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SidePanelState) Blur() {
|
||||||
|
s.Focused = false
|
||||||
|
s.Viewport.Style = s.Viewport.Style.BorderForeground(lipgloss.Color("#EEE"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SidePanelState) UpdateNicks() {
|
||||||
|
header := s.getHeader()
|
||||||
|
nicks := s.getLatestNicks()
|
||||||
|
|
||||||
|
s.Viewport.SetContent(header + strings.Join(nicks, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SidePanelState) SetSize(width, height, inputboxHeight int) {
|
func (s *SidePanelState) SetSize(width, height, inputboxHeight int) {
|
||||||
s.Style = s.Style.Copy().Width(width * 2 / 10)
|
// We ceil here because width is an int and some fractions are lost
|
||||||
// -1 is for some extra invisible margin or padding or whatever (idk what it is tbh)
|
// -1 is for some extra invisible margin or padding between the sidepanel and the inputbox
|
||||||
s.Style = s.Style.Copy().Height(height - inputboxHeight - s.Style.GetVerticalBorderSize() - 1)
|
newWidth := int(math.Ceil(math.Ceil((float64(width) * 2.0 / 10.0)) - float64(s.Viewport.Style.GetHorizontalFrameSize())))
|
||||||
|
newHeight := height - inputboxHeight - s.Viewport.Style.GetVerticalFrameSize() - 1
|
||||||
|
|
||||||
|
s.Viewport.Style = s.Viewport.Style.Width(newWidth)
|
||||||
|
s.Viewport.Style = s.Viewport.Style.Height(newHeight)
|
||||||
|
|
||||||
|
s.Viewport.Width = newWidth
|
||||||
|
s.Viewport.Height = newHeight
|
||||||
|
|
||||||
|
// on resize recalculate the width and re-set the viewport contents
|
||||||
|
s.UpdateNicks()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SidePanelState) View() string {
|
func (s SidePanelState) View() string {
|
||||||
separator := strings.Repeat("-", s.Style.GetWidth()) + "\n"
|
return s.Viewport.View()
|
||||||
header := fmt.Sprintf("Users in this channel (%d)\n", len(s.Nicks)) + separator
|
|
||||||
nicks := strings.Join(s.Nicks, "\n")
|
|
||||||
|
|
||||||
return s.Style.Render(header + nicks)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,4 @@ var (
|
|||||||
Border(lipgloss.NormalBorder(), true).
|
Border(lipgloss.NormalBorder(), true).
|
||||||
Width(ui.MainStyle.GetWidth() * 2 / 10).
|
Width(ui.MainStyle.GetWidth() * 2 / 10).
|
||||||
BorderForeground(lipgloss.Color("#EEE"))
|
BorderForeground(lipgloss.Color("#EEE"))
|
||||||
|
|
||||||
InputBoxHeight = 1
|
|
||||||
)
|
)
|
||||||
|
|||||||
+2
-2
@@ -20,8 +20,8 @@ import "github.com/charmbracelet/lipgloss"
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
MainStyle = lipgloss.NewStyle().
|
MainStyle = lipgloss.NewStyle().
|
||||||
Width(172).
|
Width(0).
|
||||||
Height(40)
|
Height(0)
|
||||||
|
|
||||||
DialogStyle = lipgloss.NewStyle().
|
DialogStyle = lipgloss.NewStyle().
|
||||||
Border(lipgloss.RoundedBorder(), true).
|
Border(lipgloss.RoundedBorder(), true).
|
||||||
|
|||||||
Reference in New Issue
Block a user