From 913e76f9db88eab66cf5fe64012feeadd1ff452e Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Mon, 24 Jan 2022 04:23:02 +0200 Subject: [PATCH] refactor: handle slash commands in a separate function * quit gorc when "/quit" is sent * move tea commands into their own package * other small fixes --- {ui/mainscreen => cmds}/cmds.go | 33 +++++++++++-- irc/client.go | 6 +++ irc/handler/handler.go | 12 ++--- irc/handler/slash.go | 78 +++++++++++++++++++++++++++++ ui/app/app.go | 7 +-- ui/app/cmds.go | 27 ---------- ui/login/cmds.go | 27 ---------- ui/login/login.go | 3 +- ui/mainscreen/input.go | 4 +- ui/mainscreen/mainscreen.go | 88 +++++++++++---------------------- ui/mainscreen/sidepanel.go | 9 ++-- 11 files changed, 161 insertions(+), 133 deletions(-) rename {ui/mainscreen => cmds}/cmds.go (65%) create mode 100644 irc/handler/slash.go delete mode 100644 ui/app/cmds.go delete mode 100644 ui/login/cmds.go diff --git a/ui/mainscreen/cmds.go b/cmds/cmds.go similarity index 65% rename from ui/mainscreen/cmds.go rename to cmds/cmds.go index 3c44945..3972960 100644 --- a/ui/mainscreen/cmds.go +++ b/cmds/cmds.go @@ -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{} +} diff --git a/irc/client.go b/irc/client.go index 897deb9..ff52b56 100644 --- a/irc/client.go +++ b/irc/client.go @@ -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" diff --git a/irc/handler/handler.go b/irc/handler/handler.go index 56e4e2c..4f15bfd 100644 --- a/irc/handler/handler.go +++ b/irc/handler/handler.go @@ -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()) } diff --git a/irc/handler/slash.go b/irc/handler/slash.go new file mode 100644 index 0000000..e0763e2 --- /dev/null +++ b/irc/handler/slash.go @@ -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 + } +} diff --git a/ui/app/app.go b/ui/app/app.go index b572e0f..be8d8ac 100644 --- a/ui/app/app.go +++ b/ui/app/app.go @@ -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) diff --git a/ui/app/cmds.go b/ui/app/cmds.go deleted file mode 100644 index 6620329..0000000 --- a/ui/app/cmds.go +++ /dev/null @@ -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() -} diff --git a/ui/login/cmds.go b/ui/login/cmds.go deleted file mode 100644 index 209b2af..0000000 --- a/ui/login/cmds.go +++ /dev/null @@ -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{} -} diff --git a/ui/login/login.go b/ui/login/login.go index c0c7059..faaa95c 100644 --- a/ui/login/login.go +++ b/ui/login/login.go @@ -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 diff --git a/ui/mainscreen/input.go b/ui/mainscreen/input.go index 2ee3d7d..651a610 100644 --- a/ui/mainscreen/input.go +++ b/ui/mainscreen/input.go @@ -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) } } diff --git a/ui/mainscreen/mainscreen.go b/ui/mainscreen/mainscreen.go index 1e61525..8813aeb 100644 --- a/ui/mainscreen/mainscreen.go +++ b/ui/mainscreen/mainscreen.go @@ -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("❱") } diff --git a/ui/mainscreen/sidepanel.go b/ui/mainscreen/sidepanel.go index e7159f8..f2c562e 100644 --- a/ui/mainscreen/sidepanel.go +++ b/ui/mainscreen/sidepanel.go @@ -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() {