1use std::ops::Range;
10
11use crate::buffer::ChatBuffer;
12use crate::message::{Block, MessageFlags, MessageId, MessageKind};
13use crate::wrap::LineSource;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum FocusItem {
18 Speaker { message: MessageId },
20 Link {
22 message: MessageId,
23 source: LineSource,
24 range: Range<usize>,
25 },
26 LoadMore { message: MessageId },
28 Image { message: MessageId, block: usize },
30}
31
32impl FocusItem {
33 pub fn message(&self) -> MessageId {
35 match self {
36 FocusItem::Speaker { message }
37 | FocusItem::Link { message, .. }
38 | FocusItem::LoadMore { message }
39 | FocusItem::Image { message, .. } => *message,
40 }
41 }
42}
43
44impl ChatBuffer {
45 pub fn focus_items(&self, row: usize) -> Vec<FocusItem> {
49 let (Some(message), Some(msg)) = (self.id_at(row), self.message_at(row)) else {
50 return Vec::new();
51 };
52 if let MessageKind::LoadMore(_) = msg.kind {
53 return vec![FocusItem::LoadMore { message }];
54 }
55 let mut out = Vec::new();
56 let shown = !msg.flags.contains(MessageFlags::GROUPED)
58 && (msg.gutter.as_ref().is_some_and(|g| !g.text.is_empty())
59 || self.params().avatar_size > 0);
60 if shown && msg.speaker.as_ref().is_some_and(|s| s.key != 0) {
61 out.push(FocusItem::Speaker { message });
62 }
63 for (bi, b) in msg.blocks.iter().enumerate() {
64 let parsed = match b {
65 Block::Text(p) | Block::Quote { content: p, .. } => p,
66 Block::Image { .. } => {
67 out.push(FocusItem::Image { message, block: bi });
68 continue;
69 }
70 Block::Code { .. } => continue,
71 };
72 let mut links: Vec<_> = parsed.links.iter().map(|l| l.range.clone()).collect();
73 links.sort_by_key(|r| r.start);
74 out.extend(links.into_iter().map(|range| FocusItem::Link {
75 message,
76 source: LineSource::Block(bi),
77 range,
78 }));
79 }
80 out
81 }
82
83 pub fn step_focus(
89 &self,
90 from: Option<&FocusItem>,
91 forward: bool,
92 rows: (usize, usize),
93 ) -> Option<FocusItem> {
94 let (first, last) = (rows.0, rows.1.min(self.len().checked_sub(1)?));
95 let shown = |r: usize| r >= first && r <= last;
96 let mut row = if forward { first } else { last };
97 if let Some(f) = from {
98 if let Some(r) = self.row_of(f.message()).filter(|r| shown(*r)) {
99 let items = self.focus_items(r);
100 if let Some(at) = items.iter().position(|i| i == f) {
101 let next = if forward {
102 items.get(at + 1)
103 } else {
104 at.checked_sub(1).and_then(|i| items.get(i))
105 };
106 if let Some(next) = next {
107 return Some(next.clone());
108 }
109 row = if forward { r + 1 } else { r.checked_sub(1)? };
110 }
111 }
112 }
113 while shown(row) {
114 let items = self.focus_items(row);
115 let pick = if forward {
116 items.into_iter().next()
117 } else {
118 items.into_iter().next_back()
119 };
120 if pick.is_some() {
121 return pick;
122 }
123 row = if forward {
124 row + 1
125 } else {
126 row.checked_sub(1)?
127 };
128 }
129 None
130 }
131}