use std::ops::Range;
use crate::buffer::ChatBuffer;
use crate::message::{Block, MessageFlags, MessageId, MessageKind};
use crate::wrap::LineSource;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FocusItem {
Speaker { message: MessageId },
Link {
message: MessageId,
source: LineSource,
range: Range<usize>,
},
LoadMore { message: MessageId },
Image { message: MessageId, block: usize },
}
impl FocusItem {
pub fn message(&self) -> MessageId {
match self {
FocusItem::Speaker { message }
| FocusItem::Link { message, .. }
| FocusItem::LoadMore { message }
| FocusItem::Image { message, .. } => *message,
}
}
}
impl ChatBuffer {
pub fn focus_items(&self, row: usize) -> Vec<FocusItem> {
let (Some(message), Some(msg)) = (self.id_at(row), self.message_at(row)) else {
return Vec::new();
};
if let MessageKind::LoadMore(_) = msg.kind {
return vec![FocusItem::LoadMore { message }];
}
let mut out = Vec::new();
let shown = !msg.flags.contains(MessageFlags::GROUPED)
&& (msg.gutter.as_ref().is_some_and(|g| !g.text.is_empty())
|| self.params().avatar_size > 0);
if shown && msg.speaker.as_ref().is_some_and(|s| s.key != 0) {
out.push(FocusItem::Speaker { message });
}
for (bi, b) in msg.blocks.iter().enumerate() {
let parsed = match b {
Block::Text(p) | Block::Quote { content: p, .. } => p,
Block::Image { .. } => {
out.push(FocusItem::Image { message, block: bi });
continue;
}
Block::Code { .. } => continue,
};
let mut links: Vec<_> = parsed.links.iter().map(|l| l.range.clone()).collect();
links.sort_by_key(|r| r.start);
out.extend(links.into_iter().map(|range| FocusItem::Link {
message,
source: LineSource::Block(bi),
range,
}));
}
out
}
pub fn step_focus(
&self,
from: Option<&FocusItem>,
forward: bool,
rows: (usize, usize),
) -> Option<FocusItem> {
let (first, last) = (rows.0, rows.1.min(self.len().checked_sub(1)?));
let shown = |r: usize| r >= first && r <= last;
let mut row = if forward { first } else { last };
if let Some(f) = from {
if let Some(r) = self.row_of(f.message()).filter(|r| shown(*r)) {
let items = self.focus_items(r);
if let Some(at) = items.iter().position(|i| i == f) {
let next = if forward {
items.get(at + 1)
} else {
at.checked_sub(1).and_then(|i| items.get(i))
};
if let Some(next) = next {
return Some(next.clone());
}
row = if forward { r + 1 } else { r.checked_sub(1)? };
}
}
}
while shown(row) {
let items = self.focus_items(row);
let pick = if forward {
items.into_iter().next()
} else {
items.into_iter().next_back()
};
if pick.is_some() {
return pick;
}
row = if forward {
row + 1
} else {
row.checked_sub(1)?
};
}
None
}
}