Compare commits

..

No commits in common. "1eb8b971f5537aea85015ab48182037f0ffada23" and "b0dcb6b1708362a7906a6212413dd0216d20cd39" have entirely different histories.

4 changed files with 45 additions and 136 deletions

View file

@ -115,11 +115,12 @@ impl Cri {
.borrow() .borrow()
.send(IrcCommand::JOIN(channel.clone(), tokens.next().map(String::from), None).into()) .send(IrcCommand::JOIN(channel.clone(), tokens.next().map(String::from), None).into())
.unwrap(); .unwrap();
self.message_log.set_active(Some(&channel)); self.message_log.set_active(Some(channel));
} }
fn handle_query_command(&mut self, mut tokens: std::str::SplitWhitespace<'_>) { fn handle_query_command(&mut self, mut tokens: std::str::SplitWhitespace<'_>) {
self.message_log.set_active(Some(tokens.next().unwrap())); self.message_log
.set_active(Some(tokens.next().unwrap().into()));
} }
fn handle_list_command(&self) { fn handle_list_command(&self) {
@ -136,7 +137,11 @@ impl Cri {
timestamp: DateTime<Local>, timestamp: DateTime<Local>,
message_id: Option<&str>, message_id: Option<&str>,
) { ) {
if !self.message_log.has_channel(chanlist) let already_joined = self.message_log.has_channel(dbg!(chanlist));
self.message_log
.on_join(chanlist, source_nickname, &timestamp, message_id);
if !already_joined
&& source_nickname == &self.nickname && source_nickname == &self.nickname
&& self.capabilities.contains(&"draft/chathistory".into()) && self.capabilities.contains(&"draft/chathistory".into())
{ {
@ -155,9 +160,6 @@ impl Cri {
.into(), .into(),
) )
.unwrap(); .unwrap();
} else {
self.message_log
.on_join(chanlist, source_nickname, &timestamp, message_id);
} }
} }
} }
@ -212,8 +214,6 @@ impl Application for Cri {
let message_id = tags_map.get("msgid").cloned().flatten(); let message_id = tags_map.get("msgid").cloned().flatten();
let message_id = message_id.as_deref(); let message_id = message_id.as_deref();
let batch = tags_map.get("batch").cloned().flatten();
match &message.command { match &message.command {
IrcCommand::CAP(_, CapSubCommand::ACK, capability, _) => { IrcCommand::CAP(_, CapSubCommand::ACK, capability, _) => {
let capability = capability.as_ref().unwrap(); let capability = capability.as_ref().unwrap();
@ -245,7 +245,6 @@ impl Application for Cri {
comment.as_deref(), comment.as_deref(),
&timestamp, &timestamp,
message_id, message_id,
batch,
); );
} }
@ -301,8 +300,8 @@ impl Application for Cri {
self.input_value.clear(); self.input_value.clear();
} }
ui_message::UiMessage::HandleChannelPress(channel_name) => { ui_message::UiMessage::HandleChannelPress(channel) => {
self.message_log.set_active(channel_name.as_deref()) self.message_log.set_active(channel)
} }
ui_message::UiMessage::None => (), ui_message::UiMessage::None => (),
} }

View file

@ -4,7 +4,6 @@ mod irc_handler;
mod irc_message; mod irc_message;
mod message_log; mod message_log;
mod ui_message; mod ui_message;
mod util;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use iced::{Application, Settings}; use iced::{Application, Settings};

View file

@ -1,7 +1,4 @@
use crate::{ use crate::irc_message::{IrcMessage, MessageDetail};
irc_message::{IrcMessage, MessageDetail},
util,
};
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
use iced::{ use iced::{
@ -10,16 +7,16 @@ use iced::{
Background, Color, Length, Background, Color, Length,
}; };
use irc::proto::BatchSubCommand; use irc::proto::BatchSubCommand;
use regex::Regex;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
#[derive(Default)]
pub struct Channel { pub struct Channel {
pub channel_name: Option<String>,
pub messages: Vec<IrcMessage>, pub messages: Vec<IrcMessage>,
pub message_ids: HashSet<String>, pub message_ids: HashSet<String>,
pub names: Vec<String>, pub names: Vec<String>,
pub topic: Option<String>,
pub topic: Option<String>,
pub unread_messages: i32, pub unread_messages: i32,
pub unread_highlights: i32, pub unread_highlights: i32,
pub unread_events: i32, pub unread_events: i32,
@ -31,29 +28,6 @@ pub struct Channel {
pub multiline_message_id: Option<String>, pub multiline_message_id: Option<String>,
} }
impl Channel {
fn new(channel_name: Option<&str>) -> Self {
Self {
channel_name: channel_name.map(String::from),
messages: Vec::new(),
message_ids: HashSet::new(),
names: Vec::new(),
topic: None,
unread_messages: 0,
unread_highlights: 0,
unread_events: 0,
is_multiline: false,
multiline_privmsgs: None,
multiline_timestamp: None,
multiline_nickname: None,
multiline_message_id: None,
}
}
}
pub struct MessageLog { pub struct MessageLog {
channels: HashMap<Option<String>, Channel>, channels: HashMap<Option<String>, Channel>,
pub active_channel: Option<String>, pub active_channel: Option<String>,
@ -65,7 +39,7 @@ pub struct MessageLog {
impl<'a> MessageLog { impl<'a> MessageLog {
pub fn new() -> Self { pub fn new() -> Self {
let mut channels = HashMap::new(); let mut channels = HashMap::new();
channels.insert(None, Channel::new(None)); channels.insert(None, Default::default());
Self { Self {
channels, channels,
active_channel: None, active_channel: None,
@ -87,10 +61,8 @@ impl<'a> MessageLog {
self.channels.get(channel) self.channels.get(channel)
} }
pub fn get_mut(&mut self, channel_name: Option<&str>) -> &mut Channel { pub fn get_mut(&mut self, channel_name: Option<String>) -> &mut Channel {
self.channels self.channels.entry(channel_name).or_default()
.entry(channel_name.map(String::from))
.or_insert_with(|| Channel::new(channel_name))
} }
pub fn on_join( pub fn on_join(
@ -101,7 +73,7 @@ impl<'a> MessageLog {
message_id: Option<&str>, message_id: Option<&str>,
) { ) {
let is_active = self.active_channel.as_deref() != Some(channel_name); let is_active = self.active_channel.as_deref() != Some(channel_name);
let channel = self.get_mut(Some(channel_name)); let channel = self.get_mut(Some(channel_name.into()));
if message_id.is_some() && !channel.message_ids.insert(message_id.unwrap().into()) { if message_id.is_some() && !channel.message_ids.insert(message_id.unwrap().into()) {
return; return;
@ -129,7 +101,7 @@ impl<'a> MessageLog {
message_id: Option<&str>, message_id: Option<&str>,
) { ) {
let is_active = self.active_channel.as_deref() != Some(channel_name); let is_active = self.active_channel.as_deref() != Some(channel_name);
let channel = self.get_mut(Some(channel_name)); let channel = self.get_mut(Some(channel_name.into()));
if message_id.is_some() && !channel.message_ids.insert(message_id.unwrap().into()) { if message_id.is_some() && !channel.message_ids.insert(message_id.unwrap().into()) {
return; return;
@ -180,49 +152,22 @@ impl<'a> MessageLog {
reason: Option<&str>, reason: Option<&str>,
timestamp: &DateTime<Local>, timestamp: &DateTime<Local>,
message_id: Option<&str>, message_id: Option<&str>,
batch: Option<String>,
) { ) {
if let Some(batch) = batch { // TODO increment event counter for each relevant channel
let (subcommand, channel_name) = &self.batch_channels[&batch]; for channel in self.channels.values_mut() {
if subcommand == &BatchSubCommand::CUSTOM("CHATHISTORY".into()) {
if let Some(channel) = self.channels.get_mut(&Some(channel_name.to_owned())) {
if message_id.is_some()
&& !channel.message_ids.insert(message_id.unwrap().into())
{
return;
}
Self::add_quit(
channel,
self.active_channel.as_deref(),
nickname,
reason,
message_id,
timestamp,
);
return;
}
}
}
for (_, channel) in self.channels.iter_mut() {
if !channel.names.is_empty() && !channel.names.contains(&nickname.into()) {
continue;
}
if message_id.is_some() && !channel.message_ids.insert(message_id.unwrap().into()) { if message_id.is_some() && !channel.message_ids.insert(message_id.unwrap().into()) {
continue; continue;
} }
Self::add_quit( // TODO only show in relevant channels
channel, channel.messages.push(IrcMessage {
self.active_channel.as_deref(), detail: MessageDetail::Quit {
nickname, nickname: nickname.into(),
reason, reason: reason.map(String::from),
message_id, },
timestamp, message_id: message_id.map(String::from),
); timestamp: *timestamp,
})
} }
} }
@ -242,7 +187,8 @@ impl<'a> MessageLog {
(subcommand.unwrap().clone(), channel_name.clone()), (subcommand.unwrap().clone(), channel_name.clone()),
); );
let channel = self.get_mut(Some(channel_name)); let channel = self.get_mut(Some(channel_name.clone()));
channel.is_multiline = true; channel.is_multiline = true;
channel.multiline_privmsgs = Some(Vec::new()); channel.multiline_privmsgs = Some(Vec::new());
channel.multiline_timestamp = Some(*timestamp); channel.multiline_timestamp = Some(*timestamp);
@ -252,15 +198,10 @@ impl<'a> MessageLog {
tag.into(), tag.into(),
(subcommand.unwrap().clone(), channel_name.clone()), (subcommand.unwrap().clone(), channel_name.clone()),
); );
// TODO Collect messages into a separate list and update them
// at the end of the batch instead of just clearing the channel
let channel = self.get_mut(Some(channel_name));
channel.messages.clear();
} }
} else if let Some(tag) = tag.strip_prefix('-') { } else if let Some(tag) = tag.strip_prefix('-') {
if let Some((subcommand, channel_name)) = self.batch_channels.remove(tag) { if let Some((subcommand, channel_name)) = self.batch_channels.remove(tag) {
let channel = self.get_mut(Some(&channel_name)); let channel = self.get_mut(Some(channel_name.clone()));
if subcommand == BatchSubCommand::CUSTOM("DRAFT/MULTILINE".into()) { if subcommand == BatchSubCommand::CUSTOM("DRAFT/MULTILINE".into()) {
channel.is_multiline = false; channel.is_multiline = false;
@ -278,7 +219,7 @@ impl<'a> MessageLog {
&timestamp, &timestamp,
); );
} else if subcommand == BatchSubCommand::CUSTOM("CHATHISTORY".into()) { } else if subcommand == BatchSubCommand::CUSTOM("CHATHISTORY".into()) {
// TODO channel.messages.sort_by_key(|m| m.timestamp);
} }
} }
} }
@ -294,7 +235,7 @@ impl<'a> MessageLog {
timestamp: &DateTime<Local>, timestamp: &DateTime<Local>,
) { ) {
let is_active = self.active_channel.as_deref() == Some(channel_name); let is_active = self.active_channel.as_deref() == Some(channel_name);
let channel = self.get_mut(Some(channel_name)); let channel = self.get_mut(Some(channel_name.into()));
if channel.is_multiline { if channel.is_multiline {
channel.multiline_nickname = Some(nickname.into()); channel.multiline_nickname = Some(nickname.into());
@ -323,7 +264,9 @@ impl<'a> MessageLog {
} }
if !is_active { if !is_active {
if util::contains_word(current_nickname, message) { let highlight_regex =
Regex::new(&format!(r"\b{}\b", regex::escape(current_nickname))).unwrap();
if highlight_regex.is_match(message) {
channel.unread_highlights += 1; channel.unread_highlights += 1;
} else { } else {
channel.unread_messages += 1; channel.unread_messages += 1;
@ -341,8 +284,8 @@ impl<'a> MessageLog {
}) })
} }
pub fn set_active(&mut self, channel_name: Option<&str>) { pub fn set_active(&mut self, channel_name: Option<String>) {
self.active_channel = channel_name.map(String::from); self.active_channel = channel_name.clone();
let channel = self.get_mut(channel_name); let channel = self.get_mut(channel_name);
channel.unread_events = 0; channel.unread_events = 0;
channel.unread_messages = 0; channel.unread_messages = 0;
@ -446,7 +389,7 @@ impl<'a> MessageLog {
Some( Some(
container( container(
container(text(format!("{nickname} quit the server{reason}"))) container(text(format!("{nickname} left the server{reason}")))
.style(move |_: &_| event_appearance) .style(move |_: &_| event_appearance)
.padding([3, 10]), .padding([3, 10]),
) )
@ -519,37 +462,12 @@ impl<'a> MessageLog {
} }
pub fn on_topic(&mut self, channel: &str, topic: &str) { pub fn on_topic(&mut self, channel: &str, topic: &str) {
self.get_mut(Some(channel)).topic = Some(topic.into()); self.get_mut(Some(channel.into())).topic = Some(topic.into());
} }
pub fn on_names_reply(&mut self, channel: &str, names: Vec<&str>) { pub fn on_names_reply(&mut self, channel: &str, names: Vec<&str>) {
self.get_mut(Some(channel)).names.extend( self.get_mut(Some(channel.into()))
names .names
.iter() .extend(names.iter().map(|&n| String::from(n)));
.map(|&n| String::from(n.trim_matches(&['~', '&', '@', '%', '+'] as &[_]))),
);
}
fn add_quit(
channel: &mut Channel,
active_channel_name: Option<&str>,
nickname: &str,
reason: Option<&str>,
message_id: Option<&str>,
timestamp: &DateTime<Local>,
) {
let is_active = active_channel_name != Some(channel.channel_name.as_ref().unwrap());
if is_active {
channel.unread_events += 1;
}
channel.messages.push(IrcMessage {
detail: MessageDetail::Quit {
nickname: nickname.into(),
reason: reason.map(String::from),
},
message_id: message_id.map(String::from),
timestamp: *timestamp,
});
} }
} }

View file

@ -1,7 +0,0 @@
use regex::Regex;
pub fn contains_word(needle: &str, haystack: &str) -> bool {
let pattern = format!(r"\b{}\b", regex::escape(needle));
let regex = Regex::new(&pattern).unwrap();
regex.is_match(haystack)
}