1use crate::grid::{Grid, GridCapture, GridRenderOptions, GridStringState, RenderedLineSpan};
2use crate::hyperlinks::Hyperlinks;
3use crate::input::ScreenWriter;
4use crate::style::Style;
5use crate::transcript::{resolve_screen_capture_range, ScreenCaptureRange};
6
7use super::Screen;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10enum CaptureSurfaceBoundary {
11 Continuous,
12 HistoryToAlternateViewport,
13}
14
15impl CaptureSurfaceBoundary {
16 fn separates_after(self, absolute_y: usize, history_size: usize, capture_end: usize) -> bool {
17 matches!(self, Self::HistoryToAlternateViewport)
18 && history_size > 0
19 && absolute_y.saturating_add(1) == history_size
20 && capture_end >= history_size
21 }
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct RecoveryRow {
31 pub bytes: Vec<u8>,
33 pub wrapped: bool,
35 pub span: RenderedLineSpan,
37 pub trailing_reflow_gap: u32,
39}
40
41pub struct RecoveryRowRenderer<'a> {
46 screen: &'a Screen,
47 saved_grid: Option<Grid>,
48 hyperlinks: Hyperlinks,
49 metadata_complete: bool,
50}
51
52impl RecoveryRowRenderer<'_> {
53 #[must_use]
55 pub const fn metadata_complete(&self) -> bool {
56 self.metadata_complete
57 }
58
59 #[must_use]
61 pub fn active_row(&self, absolute_y: usize, options: GridRenderOptions) -> Option<RecoveryRow> {
62 render_grid_row_independent(&self.screen.grid, &self.hyperlinks, absolute_y, options)
63 }
64
65 #[must_use]
67 pub fn recovery_history_row_wrapped(&self, absolute_y: usize) -> Option<bool> {
68 let grid = self.saved_grid.as_ref().unwrap_or(&self.screen.grid);
69 (absolute_y < grid.hsize())
70 .then(|| grid.absolute_line_wrapped(absolute_y))
71 .flatten()
72 }
73
74 #[must_use]
76 pub fn saved_row(&self, absolute_y: usize, options: GridRenderOptions) -> Option<RecoveryRow> {
77 let saved = self.saved_grid.as_ref()?;
78 render_grid_row_independent(
79 saved,
80 &self.hyperlinks,
81 saved.hsize().saturating_add(absolute_y),
82 options,
83 )
84 }
85
86 #[must_use]
88 pub fn recovery_history_size(&self) -> usize {
89 self.saved_grid
90 .as_ref()
91 .map_or_else(|| self.screen.grid.hsize(), Grid::hsize)
92 }
93
94 #[must_use]
96 pub fn recovery_history_row(
97 &self,
98 absolute_y: usize,
99 options: GridRenderOptions,
100 ) -> Option<RecoveryRow> {
101 let grid = self.saved_grid.as_ref().unwrap_or(&self.screen.grid);
102 (absolute_y < grid.hsize())
103 .then(|| render_grid_row_independent(grid, &self.hyperlinks, absolute_y, options))
104 .flatten()
105 }
106}
107
108impl Screen {
109 #[must_use]
111 pub fn recovery_row_renderer(
112 &self,
113 max_hyperlink_entry_bytes: usize,
114 max_hyperlink_total_bytes: usize,
115 ) -> RecoveryRowRenderer<'_> {
116 let (hyperlinks, metadata_complete) = self
117 .hyperlinks
118 .clone_bounded(max_hyperlink_entry_bytes, max_hyperlink_total_bytes);
119 let saved_grid = self.recovery_saved_grid();
120 RecoveryRowRenderer {
121 screen: self,
122 saved_grid,
123 hyperlinks,
124 metadata_complete,
125 }
126 }
127
128 fn recovery_saved_grid(&self) -> Option<Grid> {
129 let saved_rows = usize::try_from(self.saved_grid.as_ref()?.grid.sy()).unwrap_or(usize::MAX);
130 let restore_cursor = self.alternate_saved_cursor().is_some();
131 let mut restored = self.clone();
132 restored
133 .grid
134 .set_hlimit(restored.grid.hsize().saturating_add(saved_rows));
135 restored.alternate_off(crate::input::COLOUR_DEFAULT, restore_cursor);
136 Some(restored.grid)
137 }
138
139 #[cfg_attr(not(test), allow(dead_code))]
140 #[must_use]
141 pub(crate) fn capture_grid(&self, join_wrapped: bool) -> GridCapture {
142 self.grid.capture(join_wrapped)
143 }
144
145 #[must_use]
147 pub fn capture_transcript(
148 &self,
149 range: ScreenCaptureRange,
150 options: GridRenderOptions,
151 ) -> Vec<u8> {
152 let boundary = if self.is_alternate() {
153 CaptureSurfaceBoundary::HistoryToAlternateViewport
154 } else {
155 CaptureSurfaceBoundary::Continuous
156 };
157 capture_grid_bytes(&self.grid, &self.hyperlinks, range, options, boundary)
158 }
159
160 #[must_use]
162 pub fn capture_line_format_flags(&self, range: ScreenCaptureRange) -> Vec<u8> {
163 capture_grid_line_format_flags(&self.grid, range)
164 }
165
166 #[must_use]
172 pub fn capture_transcript_lines_independent(
173 &self,
174 range: ScreenCaptureRange,
175 options: GridRenderOptions,
176 ) -> Vec<Vec<u8>> {
177 capture_grid_rows_independent(&self.grid, &self.hyperlinks, range, options)
178 .into_iter()
179 .map(|(bytes, _wrapped)| bytes)
180 .collect()
181 }
182
183 #[must_use]
190 pub fn capture_transcript_rows_independent(
191 &self,
192 range: ScreenCaptureRange,
193 options: GridRenderOptions,
194 ) -> Vec<(Vec<u8>, bool)> {
195 capture_grid_rows_independent(&self.grid, &self.hyperlinks, range, options)
196 }
197
198 #[must_use]
199 pub fn visible_line_revision(&self, row: usize) -> Option<u64> {
201 self.grid
202 .visible_line(u32::try_from(row).ok()?)
203 .map(|line| line.revision())
204 }
205
206 #[must_use]
207 pub fn render_visible_line_independent(
209 &self,
210 row: usize,
211 options: GridRenderOptions,
212 ) -> Option<Vec<u8>> {
213 let absolute_y = self.grid.hsize().checked_add(row)?;
214 let mut state = GridStringState::default();
215 self.grid
216 .render_absolute_line(absolute_y, options, &mut state, Some(&self.hyperlinks))
217 .map(String::into_bytes)
218 }
219
220 #[must_use]
221 pub fn render_visible_line_independent_with_default_style(
224 &self,
225 row: usize,
226 options: GridRenderOptions,
227 style: &Style,
228 ) -> Option<Vec<u8>> {
229 let mut state = GridStringState::default();
230 self.grid
231 .render_visible_line_with_default_style(
232 row,
233 options,
234 &mut state,
235 Some(&self.hyperlinks),
236 style,
237 )
238 .map(String::into_bytes)
239 }
240
241 #[must_use]
243 pub fn capture_saved_transcript(
244 &self,
245 range: ScreenCaptureRange,
246 options: GridRenderOptions,
247 ) -> Option<Vec<u8>> {
248 self.saved_grid.as_ref().map(|saved| {
249 capture_grid_bytes(
250 &saved.grid,
251 &self.hyperlinks,
252 range,
253 options,
254 CaptureSurfaceBoundary::Continuous,
255 )
256 })
257 }
258
259 #[must_use]
261 pub fn capture_saved_transcript_lines_independent(
262 &self,
263 range: ScreenCaptureRange,
264 options: GridRenderOptions,
265 ) -> Option<Vec<Vec<u8>>> {
266 self.saved_grid.as_ref().map(|saved| {
267 capture_grid_rows_independent(&saved.grid, &self.hyperlinks, range, options)
268 .into_iter()
269 .map(|(bytes, _wrapped)| bytes)
270 .collect()
271 })
272 }
273
274 #[must_use]
276 pub fn capture_saved_transcript_rows_independent(
277 &self,
278 range: ScreenCaptureRange,
279 options: GridRenderOptions,
280 ) -> Option<Vec<(Vec<u8>, bool)>> {
281 self.saved_grid.as_ref().map(|saved| {
282 capture_grid_rows_independent(&saved.grid, &self.hyperlinks, range, options)
283 })
284 }
285
286 #[must_use]
291 pub fn capture_saved_recovery_rows_independent(
292 &self,
293 options: GridRenderOptions,
294 ) -> Option<Vec<(Vec<u8>, bool)>> {
295 let saved = self.saved_grid.as_ref()?;
296 let mut rows = capture_grid_absolute_rows_independent(
297 &self.grid,
298 &self.hyperlinks,
299 0..self.grid.hsize(),
300 options,
301 );
302 let saved_total =
303 saved.grid.hsize() + usize::try_from(saved.grid.sy()).unwrap_or(usize::MAX);
304 rows.extend(capture_grid_absolute_rows_independent(
305 &saved.grid,
306 &self.hyperlinks,
307 saved.grid.hsize()..saved_total,
308 options,
309 ));
310 Some(rows)
311 }
312
313 #[must_use]
315 pub fn capture_saved_line_format_flags(&self, range: ScreenCaptureRange) -> Option<Vec<u8>> {
316 self.saved_grid
317 .as_ref()
318 .map(|saved| capture_grid_line_format_flags(&saved.grid, range))
319 }
320}
321
322fn render_grid_row_independent(
323 grid: &Grid,
324 hyperlinks: &Hyperlinks,
325 absolute_y: usize,
326 options: GridRenderOptions,
327) -> Option<RecoveryRow> {
328 let mut state = GridStringState::default();
329 let (line, span) =
330 grid.render_absolute_line_measured(absolute_y, options, &mut state, Some(hyperlinks))?;
331 Some(RecoveryRow {
332 bytes: line.into_bytes(),
333 wrapped: grid.absolute_line_wrapped(absolute_y).unwrap_or(false),
334 span,
335 trailing_reflow_gap: grid
336 .absolute_line_trailing_reflow_gap(absolute_y)
337 .unwrap_or(0),
338 })
339}
340
341fn capture_grid_rows_independent(
342 grid: &Grid,
343 hyperlinks: &Hyperlinks,
344 range: ScreenCaptureRange,
345 options: GridRenderOptions,
346) -> Vec<(Vec<u8>, bool)> {
347 let total_lines = grid.hsize() + usize::try_from(grid.sy()).unwrap_or(usize::MAX);
348 let Some(range) = resolve_screen_capture_range(range, grid.hsize(), total_lines) else {
349 return Vec::new();
350 };
351
352 capture_grid_absolute_rows_independent(grid, hyperlinks, range, options)
353}
354
355fn capture_grid_absolute_rows_independent(
356 grid: &Grid,
357 hyperlinks: &Hyperlinks,
358 rows: impl IntoIterator<Item = usize>,
359 options: GridRenderOptions,
360) -> Vec<(Vec<u8>, bool)> {
361 let mut output = Vec::new();
362 for absolute_y in rows {
363 let mut state = GridStringState::default();
364 let Some(line) =
365 grid.render_absolute_line(absolute_y, options, &mut state, Some(hyperlinks))
366 else {
367 continue;
368 };
369 output.push((
370 line.into_bytes(),
371 grid.absolute_line_wrapped(absolute_y).unwrap_or(false),
372 ));
373 }
374 output
375}
376
377fn capture_grid_bytes(
378 grid: &Grid,
379 hyperlinks: &Hyperlinks,
380 range: ScreenCaptureRange,
381 options: GridRenderOptions,
382 boundary: CaptureSurfaceBoundary,
383) -> Vec<u8> {
384 let total_lines = grid.hsize() + usize::try_from(grid.sy()).unwrap_or(usize::MAX);
385 let Some(range) = resolve_screen_capture_range(range, grid.hsize(), total_lines) else {
386 return Vec::new();
387 };
388 let capture_end = *range.end();
389
390 let line_count = range.end().saturating_sub(*range.start()).saturating_add(1);
391 let mut output = Vec::with_capacity(capture_capacity_hint(
392 line_count,
393 usize::try_from(grid.sx()).unwrap_or(usize::MAX),
394 ));
395 let mut state = GridStringState::default();
396 for absolute_y in range {
397 if grid
398 .append_rendered_absolute_line(
399 absolute_y,
400 options,
401 &mut state,
402 Some(hyperlinks),
403 &mut output,
404 )
405 .is_none()
406 {
407 continue;
408 };
409 let wrapped = grid.absolute_line_wrapped(absolute_y).unwrap_or(false);
410 if !options.join_wrapped
411 || !wrapped
412 || boundary.separates_after(absolute_y, grid.hsize(), capture_end)
413 {
414 state.reset_to_default_line_style(options, Some(hyperlinks), &mut output);
415 output.push(b'\n');
416 }
417 }
418 output
419}
420
421fn capture_grid_line_format_flags(grid: &Grid, range: ScreenCaptureRange) -> Vec<u8> {
422 let total_lines = grid.hsize() + usize::try_from(grid.sy()).unwrap_or(usize::MAX);
423 let Some(range) = resolve_screen_capture_range(range, grid.hsize(), total_lines) else {
424 return Vec::new();
425 };
426
427 let mut flags = Vec::new();
428 for absolute_y in range {
429 if grid.absolute_line(absolute_y).is_none() {
430 continue;
431 }
432 flags.push(if grid.absolute_line_wrapped(absolute_y).unwrap_or(false) {
433 b'W'
434 } else {
435 b'-'
436 });
437 }
438 flags
439}
440
441fn capture_capacity_hint(line_count: usize, line_width: usize) -> usize {
442 line_count
443 .saturating_mul(line_width.saturating_add(1))
444 .min(64 * 1024 * 1024)
445}