refactor: handle slash commands in a separate function

* quit gorc when "/quit" is sent
* move tea commands into their own package
* other small fixes
This commit is contained in:
2022-01-24 04:23:02 +02:00
parent e34cc39e63
commit 913e76f9db
11 changed files with 161 additions and 133 deletions
+28 -5
View File
@@ -1,5 +1,5 @@
// gorc project
// Copyright (C) 2021 IllusionMan1212
// Copyright (C) 2022 IllusionMan1212
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -14,19 +14,36 @@
// You should have received a copy of the GNU General Public License along
// with this program; if not, see https://www.gnu.org/licenses.
package mainscreen
package cmds
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/illusionman1212/gorc/irc"
)
type SendPrivMsg struct {
type ConnectMsg struct{}
func Connect() tea.Msg {
return ConnectMsg{}
}
func Quit(client *irc.Client) tea.Cmd {
return func() tea.Msg {
if client.TcpConn != nil {
client.SendCommand("QUIT")
}
return tea.Quit()
}
}
type SendPrivMsgMsg struct {
Msg string
}
func (s InputState) SendingPrivMsg(msg string) tea.Cmd {
func SendPrivMsg(msg string) tea.Cmd {
return func() tea.Msg {
return SendPrivMsg{Msg: msg}
return SendPrivMsgMsg{Msg: msg}
}
}
@@ -41,3 +58,9 @@ type SwitchChannelsMsg struct{}
func SwitchChannels() tea.Msg {
return SwitchChannelsMsg{}
}
type UpdateTabBarMsg struct{}
func UpdateTabBar() tea.Msg {
return UpdateTabBarMsg{}
}
+6
View File
@@ -72,6 +72,12 @@ type Client struct {
// Reference to the bubbletea program
Tea *tea.Program
// Index of the first visible tab in the tab bar
FirstTabIndexInTabBar int
// Index of the last visible tab in the tab bar
LastTabIndexInTabBar int
}
const CRLF = "\r\n"
+6 -6
View File
@@ -23,10 +23,10 @@ import (
"log"
"strings"
"github.com/illusionman1212/gorc/cmds"
"github.com/illusionman1212/gorc/irc"
"github.com/illusionman1212/gorc/irc/commands"
"github.com/illusionman1212/gorc/irc/parser"
"github.com/illusionman1212/gorc/ui/mainscreen"
)
func ReadLoop(client *irc.Client) {
@@ -83,7 +83,7 @@ func handleJoin(msg parser.IRCMessage, client *irc.Client) {
}
if channel == client.ActiveChannel {
client.Tea.Send(mainscreen.SwitchChannels())
client.Tea.Send(cmds.SwitchChannels())
}
}
@@ -101,7 +101,7 @@ func handleQuit(msg parser.IRCMessage, client *irc.Client) {
delete(client.Channels[i].Users, nick)
}
client.Tea.Send(mainscreen.SwitchChannels())
client.Tea.Send(cmds.SwitchChannels())
}
func handlePart(msg parser.IRCMessage, client *irc.Client) {
@@ -121,7 +121,7 @@ func handlePart(msg parser.IRCMessage, client *irc.Client) {
}
}
client.Tea.Send(mainscreen.SwitchChannels())
client.Tea.Send(cmds.SwitchChannels())
}
func handleWELCOME(msg parser.IRCMessage, client *irc.Client) {
@@ -216,7 +216,7 @@ func handleNAMREPLY(msg parser.IRCMessage, client *irc.Client) {
}
if channel == client.ActiveChannel {
client.Tea.Send(mainscreen.SwitchChannels())
client.Tea.Send(cmds.SwitchChannels())
}
}
@@ -293,5 +293,5 @@ func HandleCommand(msg parser.IRCMessage, client *irc.Client) {
// send a receivedIRCmsg tea message so the ui can update
// we also use this tea message to scroll the viewport down
client.Tea.Send(mainscreen.ReceivedIRCMsg())
client.Tea.Send(cmds.ReceivedIRCMsg())
}
+78
View File
@@ -0,0 +1,78 @@
// gorc project
// Copyright (C) 2021 IllusionMan1212
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see https://www.gnu.org/licenses.
package handler
import (
"strings"
tea "github.com/charmbracelet/bubbletea"
"github.com/illusionman1212/gorc/cmds"
"github.com/illusionman1212/gorc/irc"
"github.com/illusionman1212/gorc/irc/commands"
)
func handleSlashJoin(params []string, client *irc.Client) tea.Cmd {
// TODO: only add channel to client slice when
// we get a JOIN command from the server with our nickname
// TODO: handle comma separated channels
var cmdsToProcess []tea.Cmd
channel := ""
if len(params) > 0 {
channel = params[0]
}
client.ActiveChannel = channel
for i, c := range client.Channels {
if c.Name == channel {
client.ActiveChannelIndex = i
break
} else if i == len(client.Channels)-1 {
client.Channels = append(client.Channels, irc.Channel{
Name: channel,
Users: make(map[string]irc.User),
})
client.ActiveChannelIndex = len(client.Channels) - 1
cmdsToProcess = append(cmdsToProcess, cmds.UpdateTabBar)
}
}
client.SendCommand(commands.JOIN, params...)
cmdsToProcess = append(cmdsToProcess, cmds.SwitchChannels)
return tea.Batch(cmdsToProcess...)
}
func HandleSlashCommand(msg string, client *irc.Client) tea.Cmd {
substrs := strings.Fields(msg[1:])
command := strings.ToUpper(substrs[0])
var params []string
if len(substrs) > 1 {
params = substrs[1:]
}
switch command {
case commands.JOIN:
return handleSlashJoin(params, client)
case commands.QUIT:
client.SendCommand(command, params...)
return cmds.Quit(client)
default:
client.SendCommand(command, params...)
return nil
}
}
+4 -3
View File
@@ -19,6 +19,7 @@ package app
import (
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/illusionman1212/gorc/cmds"
"github.com/illusionman1212/gorc/irc"
"github.com/illusionman1212/gorc/irc/handler"
"github.com/illusionman1212/gorc/ui"
@@ -69,7 +70,7 @@ func (s State) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c":
return s, s.Quit
return s, cmds.Quit(s.Client)
}
case tea.WindowSizeMsg:
@@ -80,7 +81,7 @@ func (s State) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
s.UI.MainScreen.SetSize(msg.Width, msg.Height)
return s, nil
case login.ConnectingMsg:
case cmds.ConnectMsg:
host := s.UI.Login.Inputs[0].Value()
port := s.UI.Login.Inputs[1].Value()
channel := s.UI.Login.Inputs[2].Value()
@@ -94,7 +95,7 @@ func (s State) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
s.Client.Initialize(host, port, tlsEnabled)
s.Client.Register(nickname, password, channel)
mainscreen.LastTabIndexInTabBar = len(s.Client.Channels) - 1
s.Client.LastTabIndexInTabBar = len(s.Client.Channels) - 1
go handler.ReadLoop(s.Client)
-27
View File
@@ -1,27 +0,0 @@
// gorc project
// Copyright (C) 2021 IllusionMan1212
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see https://www.gnu.org/licenses.
package app
import tea "github.com/charmbracelet/bubbletea"
func (s *State) Quit() tea.Msg {
if s.Client.TcpConn != nil {
s.Client.SendCommand("QUIT")
}
return tea.Quit()
}
-27
View File
@@ -1,27 +0,0 @@
// gorc project
// Copyright (C) 2021 IllusionMan1212
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see https://www.gnu.org/licenses.
package login
import (
tea "github.com/charmbracelet/bubbletea"
)
type ConnectingMsg struct{}
func connect() tea.Msg {
return ConnectingMsg{}
}
+2 -1
View File
@@ -23,6 +23,7 @@ import (
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/illusionman1212/gorc/cmds"
"github.com/illusionman1212/gorc/ui"
)
@@ -79,7 +80,7 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
case " ", "enter":
// run the newClient cmd when pressing "enter" while focused on the connect button
if key == "enter" && s.FocusIndex == len(s.Inputs)+1 {
return s, connect
return s, cmds.Connect
}
// toggle tls state when pressing "enter" or "space" while focused on the checkbox
+3 -1
View File
@@ -20,6 +20,8 @@ import (
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/illusionman1212/gorc/cmds"
)
type InputState struct {
@@ -51,7 +53,7 @@ func (s InputState) Update(msg tea.Msg) (InputState, tea.Cmd) {
s.Input.Reset()
return s, s.SendingPrivMsg(value)
return s, cmds.SendPrivMsg(value)
}
}
+29 -59
View File
@@ -24,8 +24,10 @@ import (
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/illusionman1212/gorc/cmds"
"github.com/illusionman1212/gorc/irc"
"github.com/illusionman1212/gorc/irc/commands"
"github.com/illusionman1212/gorc/irc/handler"
"github.com/illusionman1212/gorc/ui"
)
@@ -43,10 +45,6 @@ const (
Right
)
// NOTE: I ABSOLUTELY HATE THIS
var FirstTabIndexInTabBar = 0
var LastTabIndexInTabBar = 0
type State struct {
Client *irc.Client
Viewport *viewport.Model
@@ -74,10 +72,10 @@ func NewMainScreen(client *irc.Client) State {
func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
var cmd tea.Cmd
var cmds []tea.Cmd
var cmdsToProcess []tea.Cmd
switch msg := msg.(type) {
case ReceivedIRCMsgMsg:
case cmds.ReceivedIRCMsgMsg:
wasAtBottom := s.Viewport.AtBottom()
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannelIndex].History)
@@ -85,39 +83,10 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
s.Viewport.GotoBottom()
}
return s, nil
case SendPrivMsg:
// for sending slash commands
// TODO: make this better
case cmds.SendPrivMsgMsg:
if msg.Msg[0] == '/' {
substrs := strings.SplitN(msg.Msg[1:], " ", 2)
command := substrs[0]
var params []string
if len(substrs) > 1 {
params = strings.Split(substrs[1], " ")
}
if strings.ToUpper(command) == commands.JOIN {
// TODO: handle comma separated channels
channel := params[0]
s.Client.ActiveChannel = channel
for i, c := range s.Client.Channels {
if c.Name == channel {
s.Client.ActiveChannelIndex = i
break
} else if i == len(s.Client.Channels)-1 {
s.Client.Channels = append(s.Client.Channels, irc.Channel{
Name: channel,
Users: make(map[string]irc.User),
})
s.Client.ActiveChannelIndex = len(s.Client.Channels) - 1
LastTabIndexInTabBar = len(s.Client.Channels) - 1
s.TabRenderingDirection = Left
}
}
s.Client.SendCommand(commands.JOIN, params...)
return s, SwitchChannels
}
s.Client.SendCommand(command, params...)
cmd = handler.HandleSlashCommand(msg.Msg, s.Client)
return s, cmd
} else {
if s.Client.ActiveChannel != s.Client.Host {
fullMsg := s.Client.Nickname + ": " + msg.Msg
@@ -130,14 +99,16 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
}
return s, nil
case SwitchChannelsMsg:
case cmds.SwitchChannelsMsg:
s.Viewport.SetContent(s.Client.Channels[s.Client.ActiveChannelIndex].History)
s.Viewport.GotoBottom()
*s.SidePanel, cmd = s.SidePanel.Update(msg)
cmds = append(cmds, cmd)
return s, tea.Batch(cmds...)
return s, cmd
case cmds.UpdateTabBarMsg:
s.Client.LastTabIndexInTabBar = len(s.Client.Channels) - 1
s.TabRenderingDirection = Left
return s, nil
case tea.KeyMsg:
key := msg.String()
switch key {
@@ -160,7 +131,6 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
s.InputBox.Blur()
s.SidePanel.Blur()
case InputBox:
cmds = append(cmds, textinput.Blink)
s.Blur()
s.InputBox.Focus()
s.SidePanel.Blur()
@@ -170,7 +140,7 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
s.SidePanel.Focus()
}
return s, tea.Batch(cmds...)
return s, textinput.Blink
case "right", "left":
// we need this so that the viewport doesnt scroll to the bottom
if len(s.Client.Channels) == 1 {
@@ -189,16 +159,16 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
s.Client.ActiveChannelIndex = len(s.Client.Channels) - 1
}
if s.Client.ActiveChannelIndex > LastTabIndexInTabBar {
if s.Client.ActiveChannelIndex > s.Client.LastTabIndexInTabBar {
s.TabRenderingDirection = Left
LastTabIndexInTabBar = s.Client.ActiveChannelIndex
} else if s.Client.ActiveChannelIndex < FirstTabIndexInTabBar {
FirstTabIndexInTabBar = s.Client.ActiveChannelIndex
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
return s, SwitchChannels
return s, cmds.SwitchChannels
case "g":
if s.FocusIndex == Viewport {
s.Viewport.GotoTop()
@@ -213,16 +183,16 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) {
switch s.FocusIndex {
case Viewport:
*s.Viewport, cmd = s.Viewport.Update(msg)
cmds = append(cmds, cmd)
cmdsToProcess = append(cmdsToProcess, cmd)
case InputBox:
s.InputBox, cmd = s.InputBox.Update(msg)
cmds = append(cmds, cmd)
cmdsToProcess = append(cmdsToProcess, cmd)
}
*s.SidePanel, cmd = s.SidePanel.Update(msg)
cmds = append(cmds, cmd)
cmdsToProcess = append(cmdsToProcess, cmd)
return s, tea.Batch(cmds...)
return s, tea.Batch(cmdsToProcess...)
}
func (s *State) Focus() {
@@ -279,7 +249,7 @@ func (s State) buildTabBar(rightArrow string, leftArrow string) string {
switch s.TabRenderingDirection {
case Left:
for i := LastTabIndexInTabBar; i >= 0; i-- {
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 {
@@ -293,14 +263,14 @@ func (s State) buildTabBar(rightArrow string, leftArrow string) string {
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
FirstTabIndexInTabBar = i + 1
s.Client.FirstTabIndexInTabBar = i + 1
// dont render the newly added tab
renderedTabs = renderedTabs[1:]
break
}
}
case Right:
for i := FirstTabIndexInTabBar; i < len(s.Client.Channels); i++ {
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 {
@@ -314,7 +284,7 @@ func (s State) buildTabBar(rightArrow string, leftArrow string) string {
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
LastTabIndexInTabBar = i - 1
s.Client.LastTabIndexInTabBar = i - 1
// dont render the newly added tab
renderedTabs = renderedTabs[:len(renderedTabs)-1]
break
@@ -337,11 +307,11 @@ func (s State) View() string {
tabs := s.buildTabBar(leftArrow, rightArrow)
tabBar := strings.Builder{}
if FirstTabIndexInTabBar != 0 {
if s.Client.FirstTabIndexInTabBar != 0 {
leftArrow = leftArrowLit.Render("❰")
}
if LastTabIndexInTabBar != len(s.Client.Channels)-1 {
if s.Client.LastTabIndexInTabBar != len(s.Client.Channels)-1 {
rightArrow = rightArrowLit.Render("❱")
}
+5 -4
View File
@@ -24,6 +24,7 @@ import (
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/illusionman1212/gorc/cmds"
"github.com/illusionman1212/gorc/irc"
)
@@ -70,10 +71,10 @@ func NewSidePanel(client *irc.Client) *SidePanelState {
func (s SidePanelState) Update(msg tea.Msg) (SidePanelState, tea.Cmd) {
var cmd tea.Cmd
var cmds []tea.Cmd
var cmdsToProcess []tea.Cmd
switch msg := msg.(type) {
case SwitchChannelsMsg:
case cmds.SwitchChannelsMsg:
s.UpdateNicks()
return s, nil
case tea.KeyMsg:
@@ -92,10 +93,10 @@ func (s SidePanelState) Update(msg tea.Msg) (SidePanelState, tea.Cmd) {
if s.Focused {
s.Viewport, cmd = s.Viewport.Update(msg)
cmds = append(cmds, cmd)
cmdsToProcess = append(cmdsToProcess, cmd)
}
return s, tea.Batch(cmds...)
return s, tea.Batch(cmdsToProcess...)
}
func (s *SidePanelState) Focus() {