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.is_multiple_of(self.cols as usize);
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
197 .is_multiple_of(self.cols as usize)
198 } else {
199 false
200 };
201
202 Some((idx, needs_newline_after))
203 }
204}
205
206impl BlockCellIterator {
207 fn new(cols: u16, start: (u16, u16), end: (u16, u16), max_cells: usize) -> Self {
211 let start = (
213 start.0.min(cols.saturating_sub(1)),
214 start
215 .1
216 .min((max_cells / cols as usize).saturating_sub(1) as u16),
217 );
218 let end = (
219 end.0.min(cols.saturating_sub(1)),
220 end.1
221 .min((max_cells / cols as usize).saturating_sub(1) as u16),
222 );
223 let (start, end) = if start > end { (end, start) } else { (start, end) };
224
225 Self {
226 cols,
227 start,
228 end,
229 current: start,
230 finished: start.1 > end.1,
231 }
232 }
233}
234
235impl LinearCellIterator {
236 fn new(cols: u16, start: (u16, u16), end: (u16, u16), max_cells: usize) -> Self {
240 let cols_usize = cols as usize;
241
242 let start = (
244 start.0.min(cols.saturating_sub(1)),
245 start
246 .1
247 .min((max_cells / cols_usize).saturating_sub(1) as u16),
248 );
249 let end = (
250 end.0.min(cols.saturating_sub(1)),
251 end.1
252 .min((max_cells / cols_usize).saturating_sub(1) as u16),
253 );
254 let (start, end) = if start > end { (end, start) } else { (start, end) };
255
256 let start_idx = start.1 as usize * cols_usize + start.0 as usize;
257 let end_idx = end.1 as usize * cols_usize + end.0 as usize;
258 let end_idx = end_idx.min(max_cells.saturating_sub(1));
259 let start_idx = start_idx.min(end_idx);
260
261 Self {
262 cols,
263 current_idx: start_idx,
264 end_idx,
265 finished: start_idx > end_idx,
266 }
267 }
268}
269
270impl TerminalGrid {
271 pub fn cell_iter(
279 &self,
280 start: (u16, u16),
281 end: (u16, u16),
282 mode: SelectionMode,
283 ) -> CellIterator {
284 let cols = self.terminal_size().0;
285 let max_cells = self.cell_count();
286
287 match mode {
288 SelectionMode::Block => {
289 CellIterator::Block(BlockCellIterator::new(cols, start, end, max_cells))
290 },
291 SelectionMode::Linear => {
292 CellIterator::Linear(LinearCellIterator::new(cols, start, end, max_cells))
293 },
294 }
295 }
296
297 pub fn get_text(&self, selection: CellQuery) -> CompactString {
308 if let Some((start, end)) = selection.range() {
309 let text = self.get_symbols(self.cell_iter(start, end, selection.mode));
310
311 if selection.trim_trailing_whitespace {
312 text.lines().map(str::trim_end).join_compact("\n")
313 } else {
314 text
315 }
316 } else {
317 CompactString::const_new("")
318 }
319 }
320}