1use compact_str::{CompactString, CompactStringExt};
2
3use crate::gl::TerminalGrid;
4
5#[derive(Debug, Clone, Copy, Default)]
10pub struct CellQuery {
11 pub(crate) mode: SelectionMode,
12 pub(super) start: Option<(u16, u16)>,
13 pub(super) end: Option<(u16, u16)>,
14 pub(super) trim_trailing_whitespace: bool,
15}
16
17#[derive(Debug, Clone, Copy, Default)]
19pub enum SelectionMode {
20 #[default]
24 Block,
25 Linear,
30}
31
32#[derive(Debug)]
37pub enum CellIterator {
38 Block(BlockCellIterator),
40 Linear(LinearCellIterator),
42}
43
44#[derive(Debug)]
49pub struct BlockCellIterator {
50 cols: u16,
51 start: (u16, u16),
52 end: (u16, u16),
53 current: (u16, u16),
54 finished: bool,
55}
56
57#[derive(Debug)]
62pub struct LinearCellIterator {
63 cols: u16,
64 current_idx: usize,
65 end_idx: usize,
66 finished: bool,
67}
68
69pub fn select(mode: SelectionMode) -> CellQuery {
81 CellQuery { mode, ..CellQuery::default() }
82}
83
84impl CellQuery {
85 pub fn start(mut self, start: (u16, u16)) -> Self {
90 self.start = Some(start);
91 self
92 }
93
94 pub fn end(mut self, end: (u16, u16)) -> Self {
99 self.end = Some(end);
100 self
101 }
102
103 pub fn is_empty(&self) -> bool {
105 self.start.is_none() && self.end.is_none()
106 }
107
108 pub fn range(&self) -> Option<((u16, u16), (u16, u16))> {
113 if let (Some(start), Some(end)) = (self.start, self.end) {
114 Some((
115 (start.0.min(end.0), start.1.min(end.1)),
116 (start.0.max(end.0), start.1.max(end.1)),
117 ))
118 } else {
119 None
120 }
121 }
122
123 pub fn trim_trailing_whitespace(mut self, enabled: bool) -> Self {
128 self.trim_trailing_whitespace = enabled;
129 self
130 }
131}
132
133impl Iterator for CellIterator {
134 type Item = (usize, bool); fn next(&mut self) -> Option<Self::Item> {
137 match self {
138 CellIterator::Block(iter) => iter.next(),
139 CellIterator::Linear(iter) => iter.next(),
140 }
141 }
142}
143
144impl Iterator for BlockCellIterator {
145 type Item = (usize, bool);
146
147 fn next(&mut self) -> Option<Self::Item> {
148 if self.finished || self.current.1 > self.end.1 {
149 return None;
150 }
151
152 let idx = self.current.1 as usize * self.cols as usize + self.current.0 as usize;
153
154 let is_end_of_row = self.current.0 == self.end.0;
156 let is_last_row = self.current.1 == self.end.1;
157 let needs_newline = is_end_of_row && !is_last_row;
158
159 if self.current.0 < self.end.0 {
161 self.current.0 += 1;
162 } else {
163 self.current.0 = self.start.0;
164 self.current.1 += 1;
165 if self.current.1 > self.end.1 {
166 self.finished = true;
167 }
168 }
169
170 Some((idx, needs_newline))
171 }
172}
173
174impl Iterator for LinearCellIterator {
175 type Item = (usize, bool);
176
177 fn next(&mut self) -> Option<Self::Item> {
178 if self.finished || self.current_idx > self.end_idx {
179 return None;
180 }
181
182 let idx = self.current_idx;
183
184 let is_row_start = idx % self.cols as usize == 0;
186 let is_first_cell = idx == self.current_idx;
187 let needs_newline_before = is_row_start && !is_first_cell;
188
189 self.current_idx += 1;
190 if self.current_idx > self.end_idx {
191 self.finished = true;
192 }
193
194 let needs_newline_after = if self.current_idx <= self.end_idx {
196 self.current_idx % self.cols as usize == 0
197 } else {
198 false
199 };
200
201 Some((idx, needs_newline_after))
202 }
203}
204
205impl BlockCellIterator {
206 fn new(cols: u16, start: (u16, u16), end: (u16, u16), max_cells: usize) -> Self {
210 let start = (
212 start.0.min(cols.saturating_sub(1)),
213 start.1.min((max_cells / cols as usize).saturating_sub(1) as u16),
214 );
215 let end = (
216 end.0.min(cols.saturating_sub(1)),
217 end.1.min((max_cells / cols as usize).saturating_sub(1) as u16),
218 );
219 let (start, end) = if start > end { (end, start) } else { (start, end) };
220
221 Self {
222 cols,
223 start,
224 end,
225 current: start,
226 finished: start.1 > end.1,
227 }
228 }
229}
230
231impl LinearCellIterator {
232 fn new(cols: u16, start: (u16, u16), end: (u16, u16), max_cells: usize) -> Self {
236 let cols_usize = cols as usize;
237
238 let start = (
240 start.0.min(cols.saturating_sub(1)),
241 start.1.min((max_cells / cols_usize).saturating_sub(1) as u16),
242 );
243 let end = (
244 end.0.min(cols.saturating_sub(1)),
245 end.1.min((max_cells / cols_usize).saturating_sub(1) as u16),
246 );
247 let (start, end) = if start > end { (end, start) } else { (start, end) };
248
249 let start_idx = start.1 as usize * cols_usize + start.0 as usize;
250 let end_idx = end.1 as usize * cols_usize + end.0 as usize;
251 let end_idx = end_idx.min(max_cells.saturating_sub(1));
252 let start_idx = start_idx.min(end_idx);
253
254 Self {
255 cols,
256 current_idx: start_idx,
257 end_idx,
258 finished: start_idx > end_idx,
259 }
260 }
261}
262
263impl TerminalGrid {
264 pub fn cell_iter(
272 &self,
273 start: (u16, u16),
274 end: (u16, u16),
275 mode: SelectionMode,
276 ) -> CellIterator {
277 let cols = self.terminal_size().0;
278 let max_cells = self.cell_count();
279
280 match mode {
281 SelectionMode::Block => {
282 CellIterator::Block(BlockCellIterator::new(cols, start, end, max_cells))
283 },
284 SelectionMode::Linear => {
285 CellIterator::Linear(LinearCellIterator::new(cols, start, end, max_cells))
286 },
287 }
288 }
289
290 pub fn get_text(&self, selection: CellQuery) -> CompactString {
301 if let Some((start, end)) = selection.range() {
302 let text = self.get_symbols(self.cell_iter(start, end, selection.mode));
303
304 if selection.trim_trailing_whitespace {
305 text.lines().map(str::trim_end).join_compact("\n")
306 } else {
307 text
308 }
309 } else {
310 CompactString::const_new("")
311 }
312 }
313}