diff --git a/src/irc_message.rs b/src/irc_message.rs new file mode 100644 index 0000000..2414322 --- /dev/null +++ b/src/irc_message.rs @@ -0,0 +1,21 @@ +pub enum IrcMessage { + Join { + nickname: String, + }, + Part { + nickname: String, + reason: Option, + }, + Nick { + old: String, + new: String, + }, + Quit { + nickname: String, + reason: Option, + }, + Privmsg { + nickname: String, + message: String, + }, +} diff --git a/src/main.rs b/src/main.rs index 58a9aff..89a66d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,9 @@ +mod irc_message; +mod message_log; + +use crate::irc_message::IrcMessage; +use crate::message_log::MessageLog; use std::cell::RefCell; -use std::collections::HashMap; use color_eyre::eyre::Result; use futures::StreamExt; @@ -66,38 +70,6 @@ async fn main() -> Result<()> { Ok(()) } -enum IrcMessage { - Join { - nickname: String, - }, - Part { - nickname: String, - reason: Option, - }, - Nick { - old: String, - new: String, - }, - Quit { - nickname: String, - reason: Option, - }, - Privmsg { - nickname: String, - message: String, - }, -} - -struct Cri { - message_rx: RefCell>>, - input_tx: RefCell>, - - active_channel: Option, - - message_log: HashMap, Vec>, - input_value: String, -} - struct CriFlags { pub message_rx: UnboundedReceiver, pub input_tx: UnboundedSender, @@ -111,6 +83,16 @@ enum UiMessage { HandleChannelPress(Option), } +struct Cri { + message_rx: RefCell>>, + input_tx: RefCell>, + + active_channel: Option, + + message_log: MessageLog, + input_value: String, +} + impl Application for Cri { type Executor = executor::Default; type Message = UiMessage; @@ -118,9 +100,6 @@ impl Application for Cri { type Flags = CriFlags; fn new(flags: Self::Flags) -> (Self, iced::Command) { - let mut message_log = HashMap::new(); - message_log.insert(None, Vec::new()); - ( Self { message_rx: RefCell::new(Some(flags.message_rx)), @@ -128,7 +107,7 @@ impl Application for Cri { active_channel: None, - message_log, + message_log: MessageLog::new(), input_value: String::new(), }, iced::Command::none(), @@ -140,74 +119,41 @@ impl Application for Cri { } fn update(&mut self, message: Self::Message) -> iced::Command { + use irc::proto::Command; + match message { UiMessage::IrcMessageReceived(message) => { - let message = *message; - // TODO use actual nickname let source_nickname = message.source_nickname().unwrap_or("cri").to_string(); match &message.command { - irc::proto::Command::JOIN(chanlist, _, _) => { + Command::JOIN(chanlist, _, _) => { + self.message_log.on_join(chanlist.clone(), &source_nickname); + } + + Command::PART(chanlist, comment) => { + self.message_log.on_part( + chanlist.clone(), + &source_nickname, + comment.as_deref(), + ); + } + + Command::NICK(new) => { + self.message_log.on_nick(&source_nickname, new); + } + + Command::QUIT(comment) => { self.message_log - .entry(Some(chanlist.to_string())) - .or_insert_with(Vec::new) - .push(IrcMessage::Join { - nickname: source_nickname, - }); + .on_quit(&source_nickname, comment.as_deref()); } - irc::proto::Command::PART(chanlist, comment) => { + + Command::PRIVMSG(msgtarget, content) => { + let channel = message.response_target().unwrap_or(msgtarget).to_string(); self.message_log - .entry(Some(chanlist.to_string())) - .or_insert_with(Vec::new) - .push(IrcMessage::Part { - nickname: source_nickname, - reason: comment.clone(), - }); + .on_privmsg(channel, &source_nickname, content); } - - irc::proto::Command::NICK(new) => { - let channels = self.message_log.keys().cloned().collect::>(); - for channel in channels { - if channel.is_some() { - self.message_log - .get_mut(&channel) - .unwrap() - .push(IrcMessage::Nick { - old: source_nickname.clone(), - new: new.to_string(), - }); - } - } - } - - irc::proto::Command::QUIT(comment) => { - let channels = self.message_log.keys().cloned().collect::>(); - for channel in channels { - if channel.is_some() { - self.message_log - .get_mut(&channel) - .unwrap() - .push(IrcMessage::Quit { - nickname: source_nickname.clone(), - reason: comment.clone(), - }); - } - } - } - - irc::proto::Command::PRIVMSG(msgtarget, content) => { - let channel = message.response_target().unwrap_or(msgtarget); - - self.message_log - .entry(Some(channel.to_string())) - .or_insert_with(Vec::new) - .push(IrcMessage::Privmsg { - nickname: source_nickname, - message: content.to_string(), - }) - } _ => (), } } @@ -220,13 +166,12 @@ impl Application for Cri { ); let message: irc::proto::Message = command.into(); - self.message_log - .get_mut(&self.active_channel) - .unwrap() - .push(IrcMessage::Privmsg { + self.message_log.get_mut(self.active_channel.clone()).push( + IrcMessage::Privmsg { nickname: String::from("cri"), message: self.input_value.clone(), - }); + }, + ); self.input_tx.borrow().send(message.clone()).unwrap(); } @@ -254,38 +199,39 @@ impl Application for Cri { let _darker_grey = Color::new(0.498, 0.549, 0.553, 1.0); let log = scrollable(column( - self.message_log[&self.active_channel] + self.message_log + .get(&self.active_channel) + .unwrap() .iter() - .flat_map(|message| { - match message { - IrcMessage::Join { nickname } => { - Some(text(format!("* {nickname} joined the channel")).style(dark_green)) - } - IrcMessage::Part { nickname, reason } => { - let reason = match reason { - Some(reason) => format!(" ({reason})"), - None => String::new(), - }; - Some( - text(format!("* {nickname} left the channel{reason}")) - .style(dark_green), - ) - } - IrcMessage::Nick { old, new } => { - Some(text(format!("* {old} changed their nickname to {new}")).style(dark_green)) - } - IrcMessage::Quit { nickname, reason }=> { - let reason = match reason { - Some(reason) => format!(" ({reason})"), - None => String::new(), - }; + .flat_map(|message| match message { + IrcMessage::Join { nickname } => { + Some(text(format!("* {nickname} joined the channel")).style(dark_green)) + } + IrcMessage::Part { nickname, reason } => { + let reason = match reason { + Some(reason) => format!(" ({reason})"), + None => String::new(), + }; + Some( + text(format!("* {nickname} left the channel{reason}")) + .style(dark_green), + ) + } + IrcMessage::Nick { old, new } => Some( + text(format!("* {old} changed their nickname to {new}")).style(dark_green), + ), + IrcMessage::Quit { nickname, reason } => { + let reason = match reason { + Some(reason) => format!(" ({reason})"), + None => String::new(), + }; - Some(text(format!("* {nickname} quit the server{reason}")) - .style(dark_green)) - }, - IrcMessage::Privmsg { nickname, message } => { - Some(text(format!("<{nickname}> {message}"))) - } + Some( + text(format!("* {nickname} quit the server{reason}")).style(dark_green), + ) + } + IrcMessage::Privmsg { nickname, message } => { + Some(text(format!("<{nickname}> {message}"))) } }) .map(|element| element.into()) @@ -301,15 +247,11 @@ impl Application for Cri { let channels = column( self.message_log - .keys() + .get_channels() + .iter() .map(|channel| { - let channel_name = channel - .as_ref() - .unwrap_or(&String::from("Server")) - .to_string(); - - let text = text(channel_name); - let is_active = &self.active_channel == channel; + let text = text(channel); + let is_active = self.active_channel.as_ref() == Some(channel); let container = container(text) .style(move |_: &_| { let background = if is_active { @@ -326,7 +268,7 @@ impl Application for Cri { .width(Length::Fill); mouse_area(container) - .on_press(UiMessage::HandleChannelPress(channel.clone())) + .on_press(UiMessage::HandleChannelPress(Some(channel.to_string()))) .into() }) .collect::>(), diff --git a/src/message_log.rs b/src/message_log.rs new file mode 100644 index 0000000..c611f61 --- /dev/null +++ b/src/message_log.rs @@ -0,0 +1,64 @@ +use crate::irc_message::IrcMessage; +use std::collections::HashMap; + +pub struct MessageLog(HashMap, Vec>); + +impl<'a> MessageLog { + pub fn new() -> Self { + let mut log = HashMap::new(); + log.insert(None, Vec::new()); + Self(log) + } + + pub fn get_channels(&'a self) -> Vec<&String> { + let mut keys = self.0.keys().flatten().collect::>(); + keys.sort_unstable(); + keys + } + + pub fn get(&self, channel: &Option) -> Option<&Vec> { + self.0.get(channel) + } + + pub fn get_mut(&mut self, channel: Option) -> &mut Vec { + self.0.entry(channel).or_insert_with(Vec::new) + } + + pub fn on_join(&mut self, channel: String, nickname: &str) { + self.get_mut(Some(channel)).push(IrcMessage::Join { + nickname: nickname.to_string(), + }); + } + + pub fn on_part(&mut self, channel: String, nickname: &str, comment: Option<&str>) { + self.get_mut(Some(channel)).push(IrcMessage::Part { + nickname: nickname.to_string(), + reason: comment.map(str::to_string), + }); + } + + pub fn on_nick(&mut self, old: &str, new: &str) { + for log in self.0.values_mut() { + log.push(IrcMessage::Nick { + old: old.to_string(), + new: new.to_string(), + }); + } + } + + pub fn on_quit(&mut self, nickname: &str, reason: Option<&str>) { + for log in self.0.values_mut() { + log.push(IrcMessage::Quit { + nickname: nickname.to_string(), + reason: reason.map(str::to_string), + }) + } + } + + pub fn on_privmsg(&mut self, channel: String, nickname: &str, message: &str) { + self.get_mut(Some(channel)).push(IrcMessage::Privmsg { + nickname: nickname.to_string(), + message: message.to_string(), + }) + } +}