oxicode_vtui/presentation/
transcript.rs1use crate::tui::core::{InlineMessageKind, InlineSegment};
8
9#[derive(Debug, Clone)]
11pub struct TranscriptLine {
12 pub kind: InlineMessageKind,
13 pub segments: Vec<InlineSegment>,
14 pub block_id: usize,
16}
17
18#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
20pub enum BlockDisplayMode {
21 Collapsed,
23 #[default]
25 Truncated,
26 Expanded,
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub enum VisibleItem {
33 Line { source_index: usize, folded: bool },
35 Gap {
37 source_index: usize,
38 hidden_lines: usize,
39 },
40}
41
42pub fn visible_items(
48 transcript: &[TranscriptLine],
49 mode_for: impl Fn(usize) -> BlockDisplayMode,
50) -> Vec<VisibleItem> {
51 const TRUNC_TAIL: usize = 3;
52
53 let mut visible = Vec::with_capacity(transcript.len());
54 let mut start = 0;
55 while start < transcript.len() {
56 let block_id = transcript[start].block_id;
57 let mut end = start + 1;
58 while end < transcript.len() && transcript[end].block_id == block_id {
59 end += 1;
60 }
61
62 let len = end - start;
63 match mode_for(block_id) {
64 BlockDisplayMode::Collapsed => visible.push(VisibleItem::Line {
65 source_index: start,
66 folded: true,
67 }),
68 BlockDisplayMode::Expanded => {
69 visible.extend((start..end).map(|source_index| VisibleItem::Line {
70 source_index,
71 folded: false,
72 }));
73 }
74 BlockDisplayMode::Truncated if len > TRUNC_TAIL + 1 => {
75 visible.push(VisibleItem::Line {
76 source_index: start,
77 folded: false,
78 });
79 visible.push(VisibleItem::Gap {
80 source_index: start,
81 hidden_lines: len - 1 - TRUNC_TAIL,
82 });
83 visible.extend(
84 (end - TRUNC_TAIL..end).map(|source_index| VisibleItem::Line {
85 source_index,
86 folded: false,
87 }),
88 );
89 }
90 BlockDisplayMode::Truncated => {
91 visible.extend((start..end).map(|source_index| VisibleItem::Line {
92 source_index,
93 folded: false,
94 }));
95 }
96 }
97 start = end;
98 }
99 visible
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 fn line(block_id: usize) -> TranscriptLine {
107 TranscriptLine {
108 kind: InlineMessageKind::Agent,
109 segments: Vec::new(),
110 block_id,
111 }
112 }
113
114 #[test]
115 fn truncated_cells_keep_head_gap_and_tail() {
116 let transcript = vec![line(7), line(7), line(7), line(7), line(7), line(7)];
117 assert_eq!(
118 visible_items(&transcript, |_| BlockDisplayMode::Truncated),
119 vec![
120 VisibleItem::Line {
121 source_index: 0,
122 folded: false
123 },
124 VisibleItem::Gap {
125 source_index: 0,
126 hidden_lines: 2
127 },
128 VisibleItem::Line {
129 source_index: 3,
130 folded: false
131 },
132 VisibleItem::Line {
133 source_index: 4,
134 folded: false
135 },
136 VisibleItem::Line {
137 source_index: 5,
138 folded: false
139 },
140 ]
141 );
142 }
143
144 #[test]
145 fn collapsed_cells_keep_only_the_head() {
146 let transcript = vec![line(1), line(1), line(2)];
147 assert_eq!(
148 visible_items(&transcript, |_| BlockDisplayMode::Collapsed),
149 vec![
150 VisibleItem::Line {
151 source_index: 0,
152 folded: true
153 },
154 VisibleItem::Line {
155 source_index: 2,
156 folded: true
157 },
158 ]
159 );
160 }
161}