use crate::span::ParsedText;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct MessageId(pub u64);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum LoadMoreDirection {
Older,
Newer,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum MessageKind {
Live,
History { server_message_id: u64 },
Divider,
LoadMore(LoadMoreDirection),
System,
}
impl MessageKind {
pub fn is_history(&self) -> bool {
matches!(
self,
MessageKind::History { .. } | MessageKind::Divider | MessageKind::LoadMore(_)
)
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Speaker {
pub key: u64,
pub nick: String,
pub color: Option<u32>,
}
impl Message {
pub fn group_key(&self) -> Option<GroupKey<'_>> {
if self.flags.contains(MessageFlags::ACTION) || self.flags.contains(MessageFlags::DELETED) {
return None;
}
if self.kind == MessageKind::System {
return None;
}
let key = self.speaker.as_ref().map(|s| s.key).unwrap_or(0);
let nick = match &self.gutter {
Some(g) if !g.text.is_empty() => g.text.as_str(),
_ => return None,
};
Some(GroupKey { key, nick })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GroupKey<'a> {
pub key: u64,
pub nick: &'a str,
}
impl Speaker {
pub fn new(key: u64, nick: impl Into<String>) -> Speaker {
Speaker {
key,
nick: nick.into(),
color: None,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct ImageSize {
pub width: u32,
pub height: u32,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Block {
Text(ParsedText),
Code {
text: String,
language: Option<String>,
},
Quote {
content: ParsedText,
depth: u8,
},
Image {
token: u32,
size: Option<ImageSize>,
alt: String,
},
}
impl Block {
pub fn text(t: impl Into<String>) -> Block {
Block::Text(ParsedText::plain(t))
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Blocks {
One(Block),
Many(Vec<Block>),
}
impl Blocks {
pub fn new(mut v: Vec<Block>) -> Blocks {
if v.len() == 1 {
Blocks::One(v.pop().expect("one block"))
} else {
Blocks::Many(v)
}
}
}
impl std::ops::Deref for Blocks {
type Target = [Block];
fn deref(&self) -> &[Block] {
match self {
Blocks::One(b) => std::slice::from_ref(b),
Blocks::Many(v) => v,
}
}
}
impl std::ops::DerefMut for Blocks {
fn deref_mut(&mut self) -> &mut [Block] {
match self {
Blocks::One(b) => std::slice::from_mut(b),
Blocks::Many(v) => v,
}
}
}
impl From<Vec<Block>> for Blocks {
fn from(v: Vec<Block>) -> Blocks {
Blocks::new(v)
}
}
impl From<Block> for Blocks {
fn from(b: Block) -> Blocks {
Blocks::One(b)
}
}
impl<'a> IntoIterator for &'a Blocks {
type Item = &'a Block;
type IntoIter = std::slice::Iter<'a, Block>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> IntoIterator for &'a mut Blocks {
type Item = &'a mut Block;
type IntoIter = std::slice::IterMut<'a, Block>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct MessageFlags(pub u8);
impl MessageFlags {
pub const NONE: MessageFlags = MessageFlags(0);
pub const HIGHLIGHT: MessageFlags = MessageFlags(1 << 0);
pub const MUTED: MessageFlags = MessageFlags(1 << 1);
pub const ACTION: MessageFlags = MessageFlags(1 << 2);
pub const OUTGOING: MessageFlags = MessageFlags(1 << 3);
pub const DELETED: MessageFlags = MessageFlags(1 << 4);
pub const GROUPED: MessageFlags = MessageFlags(1 << 5);
#[inline]
pub fn contains(self, other: MessageFlags) -> bool {
self.0 & other.0 == other.0
}
#[inline]
pub fn union(self, other: MessageFlags) -> MessageFlags {
MessageFlags(self.0 | other.0)
}
}
impl std::fmt::Debug for MessageFlags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.0 == 0 {
return f.write_str("NONE");
}
let mut first = true;
for (bit, name) in [
(MessageFlags::HIGHLIGHT, "HIGHLIGHT"),
(MessageFlags::MUTED, "MUTED"),
(MessageFlags::ACTION, "ACTION"),
(MessageFlags::OUTGOING, "OUTGOING"),
(MessageFlags::DELETED, "DELETED"),
] {
if self.contains(bit) {
if !first {
f.write_str("|")?;
}
f.write_str(name)?;
first = false;
}
}
Ok(())
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Message {
pub kind: MessageKind,
pub timestamp: i64,
pub speaker: Option<Speaker>,
pub gutter: Option<ParsedText>,
pub blocks: Blocks,
pub flags: MessageFlags,
}
impl Message {
pub fn live(speaker: Speaker, body: ParsedText) -> Message {
Message {
kind: MessageKind::Live,
timestamp: 0,
speaker: Some(speaker),
gutter: None,
blocks: Blocks::One(Block::Text(body)),
flags: MessageFlags::NONE,
}
}
pub fn system(body: ParsedText) -> Message {
Message {
kind: MessageKind::System,
timestamp: 0,
speaker: None,
gutter: None,
blocks: Blocks::One(Block::Text(body)),
flags: MessageFlags::NONE,
}
}
pub fn compact(&mut self) {
use crate::span::{exact_string, exact_vec};
if let Some(s) = &mut self.speaker {
exact_string(&mut s.nick);
}
if let Some(g) = &mut self.gutter {
g.compact();
}
if let Blocks::Many(v) = &mut self.blocks {
exact_vec(v);
}
for b in self.blocks.iter_mut() {
match b {
Block::Text(p) => p.compact(),
Block::Quote { content, .. } => content.compact(),
Block::Code { text, language } => {
exact_string(text);
if let Some(l) = language {
exact_string(l);
}
}
Block::Image { alt, .. } => exact_string(alt),
}
}
}
pub fn with_flags(mut self, f: MessageFlags) -> Message {
self.flags = self.flags.union(f);
self
}
pub fn with_timestamp(mut self, ts: i64) -> Message {
self.timestamp = ts;
self
}
pub fn to_plain_text(&self) -> String {
let mut out = String::new();
for (i, b) in self.blocks.iter().enumerate() {
if i > 0 {
out.push('\n');
}
match b {
Block::Text(p) => out.push_str(&p.text),
Block::Code { text, .. } => out.push_str(text),
Block::Quote { content, .. } => out.push_str(&content.text),
Block::Image { alt, .. } => out.push_str(alt),
}
}
out
}
}