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 pub(crate) content_hash: Option<u64>,
18}
19
20#[derive(Debug, Clone, Copy, Default)]
22pub enum SelectionMode {
23 #[default]
27 Block,
28 Linear,
33}
34
35#[derive(Debug)]
40pub enum CellIterator {
41 Block(BlockCellIterator),
43 Linear(LinearCellIterator),
45}
46
47#[derive(Debug)]
52pub struct BlockCellIterator {
53 cols: u16,
54 start: (u16, u16),
55 end: (u16, u16),
56 current: (u16, u16),
57 finished: bool,
58}
59
60#[derive(Debug)]
65pub struct LinearCellIterator {
66 cols: u16,
67 current_idx: usize,
68 end_idx: usize,
69 finished: bool,
70}
71
72pub fn select(mode: SelectionMode) -> CellQuery {
84 CellQuery { mode, ..CellQuery::default() }
85}
86
87impl CellQuery {
88 pub fn start(mut self, start: (u16, u16)) -> Self {
93 self.start = Some(start);
94 self
95 }
96
97 pub fn end(mut self, end: (u16, u16)) -> Self {
102 self.end = Some(end);
103 self
104 }
105
106 pub fn is_empty(&self) -> bool {
108 self.start.is_none() && self.end.is_none()
109 }
110
111 pub fn range(&self) -> Option<((u16, u16), (u16, u16))> {
116 if let (Some(start), Some(end)) = (self.start, self.end) {
117 Some((
118 (start.0.min(end.0), start.1.min(end.1)),
119 (start.0.max(end.0), start.1.max(end.1)),
120 ))
121 } else {
122 None
123 }
124 }
125
126 pub fn trim_trailing_whitespace(mut self, enabled: bool) -> Self {
131 self.trim_trailing_whitespace = enabled;
132 self
133 }
134
135 pub(crate) fn with_content_hash(mut self, hash: u64) -> Self {
140 self.content_hash = Some(hash);
141 self
142 }
143}
144
145impl Iterator for CellIterator {
146 type Item = (usize, bool); fn next(&mut self) -> Option<Self::Item> {
149 match self {
150 CellIterator::Block(iter) => iter.next(),
151 CellIterator::Linear(iter) => iter.next(),
152 }
153 }
154}
155
156impl Iterator for BlockCellIterator {
157 type Item = (usize, bool);
158
159 fn next(&mut self) -> Option<Self::Item> {
160 if self.finished || self.current.1 > self.end.1 {
161 return None;
162 }
163
164 let idx = self.current.1 as usize * self.cols as usize + self.current.0 as usize;
165
166 let is_end_of_row = self.current.0 == self.end.0;
168 let is_last_row = self.current.1 == self.end.1;
169 let needs_newline = is_end_of_row && !is_last_row;
170
171 if self.current.0 < self.end.0 {
173 self.current.0 += 1;
174 } else {
175 self.current.0 = self.start.0;
176 self.current.1 += 1;
177 if self.current.1 > self.end.1 {
178 self.finished = true;
179 }
180 }
181
182 Some((idx, needs_newline))
183 }
184}
185
186impl Iterator for LinearCellIterator {
187 type Item = (usize, bool);
188
189 fn next(&mut self) -> Option<Self::Item> {
190 if self.finished || self.current_idx > self.end_idx {
191 return None;
192 }
193
194 let idx = self.current_idx;
195
196 let is_row_start = idx.is_multiple_of(self.cols as usize);
198 let is_first_cell = idx == self.current_idx;
199 let needs_newline_before = is_row_start && !is_first_cell;
200
201 self.current_idx += 1;
202 if self.current_idx > self.end_idx {
203 self.finished = true;
204 }
205
206 let needs_newline_after = if self.current_idx <= self.end_idx {
208 self.current_idx
209 .is_multiple_of(self.cols as usize)
210 } else {
211 false
212 };
213
214 Some((idx, needs_newline_after))
215 }
216}
217
218impl BlockCellIterator {
219 fn new(cols: u16, start: (u16, u16), end: (u16, u16), max_cells: usize) -> Self {
223 let start = (
225 start.0.min(cols.saturating_sub(1)),
226 start
227 .1
228 .min((max_cells / cols as usize).saturating_sub(1) as u16),
229 );
230 let end = (
231 end.0.min(cols.saturating_sub(1)),
232 end.1
233 .min((max_cells / cols as usize).saturating_sub(1) as u16),
234 );
235 let (start, end) = if start > end { (end, start) } else { (start, end) };
236
237 Self {
238 cols,
239 start,
240 end,
241 current: start,
242 finished: start.1 > end.1,
243 }
244 }
245}
246
247impl LinearCellIterator {
248 fn new(cols: u16, start: (u16, u16), end: (u16, u16), max_cells: usize) -> Self {
252 let cols_usize = cols as usize;
253
254 let start = (
256 start.0.min(cols.saturating_sub(1)),
257 start
258 .1
259 .min((max_cells / cols_usize).saturating_sub(1) as u16),
260 );
261 let end = (
262 end.0.min(cols.saturating_sub(1)),
263 end.1
264 .min((max_cells / cols_usize).saturating_sub(1) as u16),
265 );
266 let (start, end) = if start > end { (end, start) } else { (start, end) };
267
268 let start_idx = start.1 as usize * cols_usize + start.0 as usize;
269 let end_idx = end.1 as usize * cols_usize + end.0 as usize;
270 let end_idx = end_idx.min(max_cells.saturating_sub(1));
271 let start_idx = start_idx.min(end_idx);
272
273 Self {
274 cols,
275 current_idx: start_idx,
276 end_idx,
277 finished: start_idx > end_idx,
278 }
279 }
280}
281
282impl TerminalGrid {
283 pub fn cell_iter(&self, selection: CellQuery) -> CellIterator {
291 let cols = self.terminal_size().0;
292 let max_cells = self.cell_count();
293
294 let (start, end) = selection.range().unwrap_or_default();
295
296 match selection.mode {
297 SelectionMode::Block => {
298 CellIterator::Block(BlockCellIterator::new(cols, start, end, max_cells))
299 },
300 SelectionMode::Linear => {
301 CellIterator::Linear(LinearCellIterator::new(cols, start, end, max_cells))
302 },
303 }
304 }
305
306 pub fn get_text(&self, selection: CellQuery) -> CompactString {
317 if selection.is_empty() {
318 return CompactString::const_new("");
319 }
320
321 let text = self.get_symbols(self.cell_iter(selection));
322
323 if selection.trim_trailing_whitespace {
324 text.lines().map(str::trim_end).join_compact("\n")
325 } else {
326 text
327 }
328 }
329}