Repository: osa1/tiny (language: rust)

Issue:
/quit with a message
Currently, `/quit` doesn't accept any reason for leaving, so the only way to do that is to type the QUIT message into the server tab. I think if /quit could take arguments that could be set as the reason, that would be great.


Discussion / hints:
We could certainly do that, but other tiny users won't be able to see your quit messages as we don't show quit messages in tiny.

Fix patch:
diff --git a/README.md b/README.md
index 4b53646b..8035b4d9 100644
--- a/README.md
+++ b/README.md
@@ -171,7 +171,7 @@ Commands start with `/` character.
 - `/join <channel>`: Join to a channel
 
 - `/close`: Close the current tab. Leaves the channel if the current tab is a
-  channel. Leaves the server if the tab is a server.
+  channel. Leaves the server if the tab is a server. You can use `/close <reason>` to send a goodbye message.
 
 - `/connect <hostname>:<port>`: Connect to a server. Uses `defaults` in the
   config file for nick, realname, hostname and auto cmds.
@@ -202,7 +202,7 @@ Commands start with `/` character.
   Running this command in a server tab applies it to all channels of that
   server. You can check your notify state in the status line.
 
-- `/quit`: Quit
+- `/quit`: Quit. You can use `/quit <reason>` to send a goodbye message.
 
 ## Server commands
 
diff --git a/crates/libtiny_client/src/lib.rs b/crates/libtiny_client/src/lib.rs
index 17f2a98e..62b21638 100644
--- a/crates/libtiny_client/src/lib.rs
+++ b/crates/libtiny_client/src/lib.rs
@@ -222,8 +222,8 @@ impl Client {
     }
 
     /// Leave a channel.
-    pub fn part(&mut self, chan: &ChanNameRef) {
-        self.state.leave_channel(&mut self.msg_chan, chan)
+    pub fn part(&mut self, chan: &ChanNameRef, reason: Option<String>) {
+        self.state.leave_channel(&mut self.msg_chan, chan, reason)
     }
 
     /// Set away status. `None` means not away.
diff --git a/crates/libtiny_client/src/state.rs b/crates/libtiny_client/src/state.rs
index 24ed95a7..391720a8 100644
--- a/crates/libtiny_client/src/state.rs
+++ b/crates/libtiny_client/src/state.rs
@@ -70,8 +70,15 @@ impl State {
         self.inner.borrow().get_chan_nicks(chan)
     }
 
-    pub(crate) fn leave_channel(&self, msg_chan: &mut Sender<Cmd>, chan: &ChanNameRef) {
-        self.inner.borrow_mut().leave_channel(msg_chan, chan)
+    pub(crate) fn leave_channel(
+        &self,
+        msg_chan: &mut Sender<Cmd>,
+        chan: &ChanNameRef,
+        reason: Option<String>,
+    ) {
+        self.inner
+            .borrow_mut()
+            .leave_channel(msg_chan, chan, reason)
     }
 
     pub(crate) fn kill_join_tasks(&self) {
@@ -687,7 +694,12 @@ impl StateInner {
     }
 
     /// If channel is in Joining state cancel Joining task, otherwise sent part message
-    fn leave_channel(&mut self, msg_chan: &mut Sender<Cmd>, chan: &ChanNameRef) {
+    fn leave_channel(
+        &mut self,
+        msg_chan: &mut Sender<Cmd>,
+        chan: &ChanNameRef,
+        reason: Option<String>,
+    ) {
         if let Some(idx) = utils::find_idx(&self.chans, |c| c.name == *chan) {
             match &mut self.chans[idx].join_state {
                 JoinState::NotJoined => {}
@@ -695,7 +707,9 @@ impl StateInner {
                     debug!("Aborting task to retry joining {}", chan.display());
                     let _ = stop_task.try_send(());
                 }
-                JoinState::Joined => msg_chan.try_send(Cmd::Msg(wire::part(chan))).unwrap(),
+                JoinState::Joined => msg_chan
+                    .try_send(Cmd::Msg(wire::part(chan, reason)))
+                    .unwrap(),
             }
         }
     }
diff --git a/crates/libtiny_common/src/lib.rs b/crates/libtiny_common/src/lib.rs
index 4193ab74..0b75190a 100644
--- a/crates/libtiny_common/src/lib.rs
+++ b/crates/libtiny_common/src/lib.rs
@@ -212,7 +212,9 @@ pub enum TabStyle {
 /// UI events
 #[derive(Debug)]
 pub enum Event {
-    Abort,
+    Abort {
+        msg: Option<String>,
+    },
     Msg {
         msg: String,
         source: MsgSource,
diff --git a/crates/libtiny_tui/examples/chat.rs b/crates/libtiny_tui/examples/chat.rs
index 2de6b6b6..92e90e68 100644
--- a/crates/libtiny_tui/examples/chat.rs
+++ b/crates/libtiny_tui/examples/chat.rs
@@ -96,7 +96,7 @@ fn handle_input_ev(ui: &TUI, ev: Event, abort: &mut mpsc::Sender<()>) {
                 ui.set_nick(SERV, new_nick);
             }
         }
-        Abort => {
+        Abort { .. } => {
             abort.try_send(()).unwrap();
         }
         Msg { .. } | Lines { .. } => {}
diff --git a/crates/libtiny_tui/examples/tabs.rs b/crates/libtiny_tui/examples/tabs.rs
index 3d794f5d..ee496b2b 100644
--- a/crates/libtiny_tui/examples/tabs.rs
+++ b/crates/libtiny_tui/examples/tabs.rs
@@ -79,6 +79,6 @@ fn handle_input_ev(ui: &TUI, ev: Event) {
                 }
             }
         }
-        Abort | Msg { .. } | Lines { .. } => {}
+        Abort { .. } | Msg { .. } | Lines { .. } => {}
     }
 }
diff --git a/crates/libtiny_tui/src/lib.rs b/crates/libtiny_tui/src/lib.rs
index 7161c164..53e6b161 100644
--- a/crates/libtiny_tui/src/lib.rs
+++ b/crates/libtiny_tui/src/lib.rs
@@ -171,7 +171,7 @@ async fn input_handler<S>(
                 let tui_ret = tui.borrow_mut().handle_input_event(ev, &mut rcv_editor_ret);
                 match tui_ret {
                     TUIRet::Abort => {
-                        snd_ev.try_send(Event::Abort).unwrap();
+                        snd_ev.try_send(Event::Abort { msg: None }).unwrap();
                         let _ = snd_abort.try_send(());
                         return;
                     }
@@ -186,8 +186,8 @@ async fn input_handler<S>(
                                 CmdResult::Continue => {
                                     snd_ev.try_send(Event::Cmd { cmd, source: from }).unwrap()
                                 }
-                                CmdResult::Quit => {
-                                    snd_ev.try_send(Event::Abort).unwrap();
+                                CmdResult::Quit(msg) => {
+                                    snd_ev.try_send(Event::Abort { msg }).unwrap();
                                     let _ = snd_abort.try_send(());
                                     return;
                                 }
diff --git a/crates/libtiny_tui/src/tui.rs b/crates/libtiny_tui/src/tui.rs
index 08041d6b..2e24775a 100644
--- a/crates/libtiny_tui/src/tui.rs
+++ b/crates/libtiny_tui/src/tui.rs
@@ -61,7 +61,7 @@ impl CmdUsage {
     }
 }
 
-const QUIT_CMD: CmdUsage = CmdUsage::new("quit", "Quit tiny", "`/quit`");
+const QUIT_CMD: CmdUsage = CmdUsage::new("quit", "Quit tiny", "`/quit` or `/quit <reason>`");
 const CLEAR_CMD: CmdUsage = CmdUsage::new("clear", "Clears current tab", "`/clear`");
 const IGNORE_CMD: CmdUsage = CmdUsage::new("ignore", "Ignore join/quit messages", "`/ignore`");
 const NOTIFY_CMD: CmdUsage = CmdUsage::new(
@@ -107,8 +107,8 @@ pub(crate) enum CmdResult {
     Ok,
     /// Pass command through to cmd.rs for further handling
     Continue,
-    /// Quit command was executed
-    Quit,
+    /// Quit command was executed, with the payload as a quit message
+    Quit(Option<String>),
 }
 
 impl TUI {
@@ -299,7 +299,16 @@ impl TUI {
                 // Fall through to print help for cmd.rs commands
                 CmdResult::Continue
             }
-            Some("quit") => CmdResult::Quit,
+            Some("quit") => {
+                // Note: `SplitWhitespace::as_str` could be used here instead, when it gets stabilized.
+                let reason: String = cmd.chars().skip("quit ".len()).collect();
+
+                if reason.is_empty() {
+                    CmdResult::Quit(None)
+                } else {
+                    CmdResult::Quit(Some(reason))
+                }
+            }
             _ => CmdResult::Continue,
         }
     }
diff --git a/crates/libtiny_wire/src/lib.rs b/crates/libtiny_wire/src/lib.rs
index cc4463b1..4d2b37c8 100644
--- a/crates/libtiny_wire/src/lib.rs
+++ b/crates/libtiny_wire/src/lib.rs
@@ -47,8 +47,11 @@ where
     format!("JOIN {}\r\n", chans.join(","))
 }
 
-pub fn part(chan: &ChanNameRef) -> String {
-    format!("PART {}\r\n", chan.display())
+pub fn part(chan: &ChanNameRef, reason: Option<String>) -> String {
+    match reason {
+        None => format!("PART {}\r\n", chan.display()),
+        Some(reason) => format!("PART {} :{}\r\n", chan.display(), reason),
+    }
 }
 
 pub fn privmsg(msgtarget: &str, msg: &str) -> String {
diff --git a/crates/tiny/src/cmd.rs b/crates/tiny/src/cmd.rs
index ce3ea3d7..b050f7a1 100644
--- a/crates/tiny/src/cmd.rs
+++ b/crates/tiny/src/cmd.rs
@@ -133,12 +133,16 @@ static CLOSE_CMD: Cmd = Cmd {
     name: "close",
     cmd_fn: close,
     description: "Closes current tab",
-    usage: "`/close`",
+    usage: "`/close` or `/close <reason>`",
 };
 
 fn close(args: CmdArgs) {
     let CmdArgs {
-        ui, clients, src, ..
+        args,
+        ui,
+        clients,
+        src,
+        ..
     } = args;
     match src {
         MsgSource::Serv { ref serv } if serv == "mentions" => {
@@ -149,12 +153,20 @@ fn close(args: CmdArgs) {
             let client_idx = find_client_idx(clients, &serv).unwrap();
             // TODO: this probably won't close the connection?
             let mut client = clients.remove(client_idx);
-            client.quit(None);
+            if args.is_empty() {
+                client.quit(None);
+            } else {
+                client.quit(Some(args.to_string()));
+            }
         }
         MsgSource::Chan { serv, chan } => {
             ui.close_chan_tab(&serv, chan.borrow());
             let client_idx = find_client_idx(clients, &serv).unwrap();
-            clients[client_idx].part(&chan);
+            if args.is_empty() {
+                clients[client_idx].part(&chan, None);
+            } else {
+                clients[client_idx].part(&chan, Some(args.to_string()));
+            }
         }
         MsgSource::User { serv, nick } => {
             ui.close_user_tab(&serv, &nick);
diff --git a/crates/tiny/src/ui.rs b/crates/tiny/src/ui.rs
index b3820db2..8404ae06 100644
--- a/crates/tiny/src/ui.rs
+++ b/crates/tiny/src/ui.rs
@@ -112,9 +112,9 @@ fn handle_input_ev(
 ) {
     use libtiny_common::Event::*;
     match ev {
-        Abort => {
+        Abort { msg } => {
             for client in clients {
-                client.quit(None);
+                client.quit(msg.clone());
             }
         }
         Msg { msg, source } => {


Test patch: