rmux_core/screen/
capture.rs1use crate::grid::{Grid, GridCapture, GridRenderOptions, GridStringState};
2use crate::hyperlinks::Hyperlinks;
3use crate::style::Style;
4use crate::transcript::{resolve_screen_capture_range, ScreenCaptureRange};
5
6use super::Screen;
7
8impl Screen {
9 #[cfg_attr(not(test), allow(dead_code))]
10 #[must_use]
11 pub(crate) fn capture_grid(&self, join_wrapped: bool) -> GridCapture {
12 self.grid.capture(join_wrapped)
13 }
14
15 #[must_use]
17 pub fn capture_transcript(
18 &self,
19 range: ScreenCaptureRange,
20 options: GridRenderOptions,
21 ) -> Vec<u8> {
22 capture_grid_bytes(&self.grid, &self.hyperlinks, range, options)
23 }
24
25 #[must_use]
31 pub fn capture_transcript_lines_independent(
32 &self,
33 range: ScreenCaptureRange,
34 options: GridRenderOptions,
35 ) -> Vec<Vec<u8>> {
36 capture_grid_lines_independent(&self.grid, &self.hyperlinks, range, options)
37 }
38
39 #[must_use]
40 pub fn visible_line_revision(&self, row: usize) -> Option<u64> {
42 self.grid
43 .visible_line(u32::try_from(row).ok()?)
44 .map(|line| line.revision())
45 }
46
47 #[must_use]
48 pub fn render_visible_line_independent(
50 &self,
51 row: usize,
52 options: GridRenderOptions,
53 ) -> Option<Vec<u8>> {
54 let absolute_y = self.grid.hsize().checked_add(row)?;
55 let mut state = GridStringState::default();
56 self.grid
57 .render_absolute_line(absolute_y, options, &mut state, Some(&self.hyperlinks))
58 .map(String::into_bytes)
59 }
60
61 #[must_use]
62 pub fn render_visible_line_independent_with_default_style(
65 &self,
66 row: usize,
67 options: GridRenderOptions,
68 style: &Style,
69 ) -> Option<Vec<u8>> {
70 let mut state = GridStringState::default();
71 self.grid
72 .render_visible_line_with_default_style(
73 row,
74 options,
75 &mut state,
76 Some(&self.hyperlinks),
77 style,
78 )
79 .map(String::into_bytes)
80 }
81
82 #[must_use]
84 pub fn capture_saved_transcript(
85 &self,
86 range: ScreenCaptureRange,
87 options: GridRenderOptions,
88 ) -> Option<Vec<u8>> {
89 self.saved_grid
90 .as_ref()
91 .map(|saved| capture_grid_bytes(&saved.grid, &self.hyperlinks, range, options))
92 }
93}
94
95fn capture_grid_lines_independent(
96 grid: &Grid,
97 hyperlinks: &Hyperlinks,
98 range: ScreenCaptureRange,
99 options: GridRenderOptions,
100) -> Vec<Vec<u8>> {
101 let total_lines = grid.hsize() + usize::try_from(grid.sy()).unwrap_or(usize::MAX);
102 let Some(range) = resolve_screen_capture_range(range, grid.hsize(), total_lines) else {
103 return Vec::new();
104 };
105
106 let mut output = Vec::new();
107 for absolute_y in range {
108 let mut state = GridStringState::default();
109 let Some(line) =
110 grid.render_absolute_line(absolute_y, options, &mut state, Some(hyperlinks))
111 else {
112 continue;
113 };
114 output.push(line.into_bytes());
115 }
116 output
117}
118
119fn capture_grid_bytes(
120 grid: &Grid,
121 hyperlinks: &Hyperlinks,
122 range: ScreenCaptureRange,
123 options: GridRenderOptions,
124) -> Vec<u8> {
125 let total_lines = grid.hsize() + usize::try_from(grid.sy()).unwrap_or(usize::MAX);
126 let Some(range) = resolve_screen_capture_range(range, grid.hsize(), total_lines) else {
127 return Vec::new();
128 };
129
130 let line_count = range.end().saturating_sub(*range.start()).saturating_add(1);
131 let mut output = Vec::with_capacity(capture_capacity_hint(
132 line_count,
133 usize::try_from(grid.sx()).unwrap_or(usize::MAX),
134 ));
135 let mut state = GridStringState::default();
136 for absolute_y in range {
137 if grid
138 .append_rendered_absolute_line(
139 absolute_y,
140 options,
141 &mut state,
142 Some(hyperlinks),
143 &mut output,
144 )
145 .is_none()
146 {
147 continue;
148 };
149 let wrapped = grid.absolute_line_wrapped(absolute_y).unwrap_or(false);
150 if !options.join_wrapped || !wrapped {
151 state.reset_to_default_line_style(options, Some(hyperlinks), &mut output);
152 output.push(b'\n');
153 }
154 }
155 output
156}
157
158fn capture_capacity_hint(line_count: usize, line_width: usize) -> usize {
159 line_count
160 .saturating_mul(line_width.saturating_add(1))
161 .min(64 * 1024 * 1024)
162}