1use crate::grid::{GridLine, GridLineFlags};
2use crate::input::{Colour, COLOUR_DEFAULT};
3
4use super::Screen;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct ScreenCellRef<'a> {
9 text: &'a str,
10 width: u8,
11 padding: bool,
12 attr: u16,
13 fg: Colour,
14 bg: Colour,
15 us: Colour,
16 link: u32,
17}
18
19impl<'a> ScreenCellRef<'a> {
20 #[must_use]
22 pub const fn text(&self) -> &'a str {
23 self.text
24 }
25
26 #[must_use]
28 pub const fn width(&self) -> u8 {
29 self.width
30 }
31
32 #[must_use]
34 pub const fn is_padding(&self) -> bool {
35 self.padding
36 }
37
38 #[must_use]
40 pub const fn attr(&self) -> u16 {
41 self.attr
42 }
43
44 #[must_use]
46 pub const fn fg(&self) -> Colour {
47 self.fg
48 }
49
50 #[must_use]
52 pub const fn bg(&self) -> Colour {
53 self.bg
54 }
55
56 #[must_use]
58 pub const fn us(&self) -> Colour {
59 self.us
60 }
61
62 #[must_use]
64 pub const fn link(&self) -> u32 {
65 self.link
66 }
67}
68
69fn blank_cell_ref() -> ScreenCellRef<'static> {
70 ScreenCellRef {
71 text: " ",
72 width: 1,
73 padding: false,
74 attr: 0,
75 fg: COLOUR_DEFAULT,
76 bg: COLOUR_DEFAULT,
77 us: COLOUR_DEFAULT,
78 link: 0,
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct ScreenCellView {
85 pub(super) text: String,
86 pub(super) width: u8,
87 pub(super) padding: bool,
88 pub(super) attr: u16,
89 pub(super) fg: crate::input::Colour,
90 pub(super) bg: crate::input::Colour,
91 pub(super) us: crate::input::Colour,
92 pub(super) link: u32,
93}
94
95impl ScreenCellView {
96 #[must_use]
98 pub fn text(&self) -> &str {
99 &self.text
100 }
101
102 #[must_use]
104 pub const fn width(&self) -> u8 {
105 self.width
106 }
107
108 #[must_use]
110 pub const fn is_padding(&self) -> bool {
111 self.padding
112 }
113
114 #[must_use]
116 pub const fn attr(&self) -> u16 {
117 self.attr
118 }
119
120 #[must_use]
122 pub const fn fg(&self) -> crate::input::Colour {
123 self.fg
124 }
125
126 #[must_use]
128 pub const fn bg(&self) -> crate::input::Colour {
129 self.bg
130 }
131
132 #[must_use]
134 pub const fn us(&self) -> crate::input::Colour {
135 self.us
136 }
137
138 #[must_use]
140 pub const fn link(&self) -> u32 {
141 self.link
142 }
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct ScreenLineView {
148 pub(super) cells: Vec<ScreenCellView>,
149 width: u32,
150 pub(super) wrapped: bool,
151 pub(super) start_prompt: bool,
152 pub(super) start_output: bool,
153 pub(super) time: i64,
154}
155
156impl ScreenLineView {
157 #[must_use]
159 pub fn cells(&self) -> &[ScreenCellView] {
160 &self.cells
161 }
162
163 #[must_use]
165 pub const fn width(&self) -> u32 {
166 self.width
167 }
168
169 #[must_use]
171 pub fn cell(&self, x: u32) -> Option<&ScreenCellView> {
172 self.cells.get(x as usize)
173 }
174
175 #[must_use]
177 pub const fn wrapped(&self) -> bool {
178 self.wrapped
179 }
180
181 #[must_use]
183 pub const fn start_prompt(&self) -> bool {
184 self.start_prompt
185 }
186
187 #[must_use]
189 pub const fn start_output(&self) -> bool {
190 self.start_output
191 }
192
193 #[must_use]
195 pub const fn time(&self) -> i64 {
196 self.time
197 }
198
199 #[must_use]
201 pub fn owning_cell_x(&self, x: u32) -> Option<u32> {
202 if x >= self.width {
203 return None;
204 }
205 let Some(cell) = self.cell(x) else {
206 return Some(x);
207 };
208 if !cell.is_padding() {
209 return Some(x);
210 }
211
212 let mut owner = x;
213 while owner > 0 {
214 owner -= 1;
215 let candidate = self.cell(owner)?;
216 if !candidate.is_padding() {
217 let width = u32::from(candidate.width().max(1));
218 if owner.saturating_add(width) > x {
219 return Some(owner);
220 }
221 return None;
222 }
223 }
224 None
225 }
226}
227
228impl Screen {
229 pub fn visit_visible_line_cells(
236 &self,
237 row: usize,
238 cols: usize,
239 mut visit: impl FnMut(ScreenCellRef<'_>),
240 ) -> bool {
241 let Some(line) = self
242 .grid
243 .visible_line(u32::try_from(row).unwrap_or(u32::MAX))
244 else {
245 return false;
246 };
247 if let Some(text) = line.plain_text() {
248 let text_cols = text.len().min(cols);
249 for col in 0..text_cols {
250 visit(ScreenCellRef {
251 text: &text[col..col + 1],
252 width: 1,
253 padding: false,
254 attr: 0,
255 fg: COLOUR_DEFAULT,
256 bg: COLOUR_DEFAULT,
257 us: COLOUR_DEFAULT,
258 link: 0,
259 });
260 }
261 for _ in text_cols..cols {
262 visit(blank_cell_ref());
263 }
264 return true;
265 }
266
267 let mut emitted = 0_usize;
268 for cell in line.cells().iter().take(cols) {
269 visit(ScreenCellRef {
270 text: cell.text(),
271 width: cell.width(),
272 padding: cell.is_padding(),
273 attr: cell.attr(),
274 fg: cell.fg(),
275 bg: cell.bg(),
276 us: cell.us(),
277 link: cell.link(),
278 });
279 emitted += 1;
280 }
281 for _ in emitted..cols {
282 visit(blank_cell_ref());
283 }
284 true
285 }
286
287 #[must_use]
289 pub fn absolute_line_view(&self, absolute_y: usize) -> Option<ScreenLineView> {
290 let line = self.grid.absolute_line(absolute_y)?;
291 let width = u32::from(self.grid.size().cols.max(1));
292 let cells = if let Some(text) = line.plain_text() {
293 let mut cells = text
294 .bytes()
295 .map(|byte| ScreenCellView {
296 text: char::from(byte).to_string(),
297 width: 1,
298 padding: false,
299 attr: 0,
300 fg: crate::input::COLOUR_DEFAULT,
301 bg: crate::input::COLOUR_DEFAULT,
302 us: crate::input::COLOUR_DEFAULT,
303 link: 0,
304 })
305 .collect::<Vec<_>>();
306 cells.resize_with(width as usize, || ScreenCellView {
307 text: " ".to_owned(),
308 width: 1,
309 padding: false,
310 attr: 0,
311 fg: crate::input::COLOUR_DEFAULT,
312 bg: crate::input::COLOUR_DEFAULT,
313 us: crate::input::COLOUR_DEFAULT,
314 link: 0,
315 });
316 cells
317 } else {
318 line.cells()
319 .iter()
320 .map(|cell| ScreenCellView {
321 text: cell.text().to_owned(),
322 width: cell.width(),
323 padding: cell.is_padding(),
324 attr: cell.attr(),
325 fg: cell.fg(),
326 bg: cell.bg(),
327 us: cell.us(),
328 link: cell.link(),
329 })
330 .collect()
331 };
332 Some(ScreenLineView {
333 cells,
334 width,
335 wrapped: line.flags().contains(GridLineFlags::WRAPPED),
336 start_prompt: line.flags().contains(GridLineFlags::START_PROMPT),
337 start_output: line.flags().contains(GridLineFlags::START_OUTPUT),
338 time: line.time(),
339 })
340 }
341
342 #[must_use]
344 pub fn clone_viewport(&self, top_line: usize, cursor_x: u32, cursor_absolute_y: usize) -> Self {
345 let size = self.grid.size();
346 let rows = usize::from(size.rows.max(1));
347 let cols = u32::from(size.cols.max(1));
348 let total_lines = self.absolute_line_count();
349 let top_line = top_line.min(total_lines.saturating_sub(rows));
350 let mut viewport = Self::new(size, 0);
351
352 viewport.mode = self.mode;
353 viewport.cursor_style = self.cursor_style;
354 viewport.title = self.title.clone();
355 viewport.window_name = self.window_name.clone();
356 viewport.path = self.path.clone();
357 viewport.title_stack = self.title_stack.clone();
358 viewport.hyperlinks = self.hyperlinks.clone();
359 viewport.active_hyperlink = self.active_hyperlink;
360 viewport.bell_count = 0;
361 viewport.utf8_config = self.utf8_config.clone();
362
363 let lines = (0..rows)
364 .map(|offset| {
365 self.grid
366 .absolute_line(top_line + offset)
367 .cloned()
368 .unwrap_or_else(|| GridLine::new(cols))
369 })
370 .collect();
371 viewport.grid.replace_visible(lines);
372
373 viewport.cursor_x = cursor_x.min(viewport.max_cursor_x());
374 viewport.cursor_y = if (top_line..top_line + rows).contains(&cursor_absolute_y) {
375 (cursor_absolute_y - top_line) as u32
376 } else {
377 0
378 };
379 viewport.pending_wrap = false;
380 viewport.saved_cursor_x = None;
381 viewport.saved_cursor_y = None;
382 viewport.saved_cursor_pending_wrap = false;
383 viewport.saved_grid = None;
384 viewport.rupper = 0;
385 viewport.rlower = u32::from(size.rows.max(1)).saturating_sub(1);
386 viewport.reset_tabs();
387 viewport
388 }
389}