1use crate::view::terminal::Cell;
2use scribe::buffer::Position;
3use unicode_segmentation::UnicodeSegmentation;
4
5pub struct TerminalBufferIterator<'c> {
7 index: usize,
8 width: usize,
9 cells: &'c Vec<Cell<'c>>,
10}
11
12impl<'c> TerminalBufferIterator<'c> {
13 pub fn new(width: usize, cells: &'c Vec<Cell<'c>>) -> TerminalBufferIterator {
14 TerminalBufferIterator {
15 index: 0,
16 width,
17 cells,
18 }
19 }
20}
21
22impl<'c> Iterator for TerminalBufferIterator<'c> {
23 type Item = (Position, &'c Cell<'c>);
24
25 fn next(&mut self) -> Option<Self::Item> {
27 if self.index < self.cells.len() {
28 let position = Position {
29 line: self.index / self.width,
30 offset: self.index % self.width,
31 };
32 let cell = &self.cells[self.index];
33 self.index += cell.content.graphemes(true).count().max(1);
34
35 Some((position, cell))
36 } else {
37 None
38 }
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::TerminalBufferIterator;
45 use crate::view::terminal::Cell;
46 use crate::view::{Colors, Style};
47 use scribe::buffer::Position;
48 use std::borrow::Cow;
49
50 #[test]
51 fn terminal_buffer_iterator_yields_cells_and_their_positions() {
52 let width = 3;
53 let cells = vec![
54 Cell {
55 content: Cow::from("a"),
56 colors: Colors::Default,
57 style: Style::Default,
58 },
59 Cell {
60 content: Cow::from("m"),
61 colors: Colors::Default,
62 style: Style::Default,
63 },
64 Cell {
65 content: Cow::from("p"),
66 colors: Colors::Default,
67 style: Style::Default,
68 },
69 ];
70 let mut iterator = TerminalBufferIterator::new(width, &cells);
71
72 assert_eq!(
73 iterator.next(),
74 Some((Position { line: 0, offset: 0 }, &cells[0]))
75 );
76 assert_eq!(
77 iterator.next(),
78 Some((Position { line: 0, offset: 1 }, &cells[1]))
79 );
80 assert_eq!(
81 iterator.next(),
82 Some((Position { line: 0, offset: 2 }, &cells[2]))
83 );
84 assert_eq!(iterator.next(), None);
85 }
86
87 #[test]
88 fn terminal_buffer_iterator_considers_width_when_calculating_positions() {
89 let width = 2;
90 let cells = vec![
91 Cell {
92 content: Cow::from("a"),
93 colors: Colors::Default,
94 style: Style::Default,
95 },
96 Cell {
97 content: Cow::from("m"),
98 colors: Colors::Default,
99 style: Style::Default,
100 },
101 Cell {
102 content: Cow::from("p"),
103 colors: Colors::Default,
104 style: Style::Default,
105 },
106 ];
107 let mut iterator = TerminalBufferIterator::new(width, &cells);
108
109 assert_eq!(
110 iterator.nth(2),
111 Some((Position { line: 1, offset: 0 }, &cells[2]))
112 );
113 }
114
115 #[test]
116 fn terminal_buffer_iterator_handles_overlapping_cells_correctly() {
117 let width = 4;
118 let cells = vec![
119 Cell {
120 content: Cow::from("amp"),
121 colors: Colors::Default,
122 style: Style::Default,
123 },
124 Cell {
125 content: Cow::from("b"),
126 colors: Colors::Default,
127 style: Style::Default,
128 },
129 Cell {
130 content: Cow::from("c"),
131 colors: Colors::Default,
132 style: Style::Default,
133 },
134 Cell {
135 content: Cow::from("d"),
136 colors: Colors::Default,
137 style: Style::Default,
138 },
139 ];
140 let mut iterator = TerminalBufferIterator::new(width, &cells);
141
142 assert_eq!(
143 iterator.next(),
144 Some((Position { line: 0, offset: 0 }, &cells[0]))
145 );
146 assert_eq!(
147 iterator.next(),
148 Some((Position { line: 0, offset: 3 }, &cells[3]))
149 );
150 assert_eq!(iterator.next(), None);
151 }
152
153 #[test]
154 fn terminal_buffer_iterator_handles_empty_cells_correctly() {
155 let width = 4;
156 let cells = vec![
157 Cell {
158 content: Cow::from(""),
159 colors: Colors::Default,
160 style: Style::Default,
161 },
162 Cell {
163 content: Cow::from("a"),
164 colors: Colors::Default,
165 style: Style::Default,
166 },
167 ];
168 let mut iterator = TerminalBufferIterator::new(width, &cells);
169
170 assert_eq!(
171 iterator.next(),
172 Some((Position { line: 0, offset: 0 }, &cells[0]))
173 );
174 assert_eq!(
175 iterator.next(),
176 Some((Position { line: 0, offset: 1 }, &cells[1]))
177 );
178 assert_eq!(iterator.next(), None);
179 }
180}