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]
27 pub fn capture_line_format_flags(&self, range: ScreenCaptureRange) -> Vec<u8> {
28 capture_grid_line_format_flags(&self.grid, range)
29 }
30
31 #[must_use]
37 pub fn capture_transcript_lines_independent(
38 &self,
39 range: ScreenCaptureRange,
40 options: GridRenderOptions,
41 ) -> Vec<Vec<u8>> {
42 capture_grid_lines_independent(&self.grid, &self.hyperlinks, range, options)
43 }
44
45 #[must_use]
46 pub fn visible_line_revision(&self, row: usize) -> Option<u64> {
48 self.grid
49 .visible_line(u32::try_from(row).ok()?)
50 .map(|line| line.revision())
51 }
52
53 #[must_use]
54 pub fn render_visible_line_independent(
56 &self,
57 row: usize,
58 options: GridRenderOptions,
59 ) -> Option<Vec<u8>> {
60 let absolute_y = self.grid.hsize().checked_add(row)?;
61 let mut state = GridStringState::default();
62 self.grid
63 .render_absolute_line(absolute_y, options, &mut state, Some(&self.hyperlinks))
64 .map(String::into_bytes)
65 }
66
67 #[must_use]
68 pub fn render_visible_line_independent_with_default_style(
71 &self,
72 row: usize,
73 options: GridRenderOptions,
74 style: &Style,
75 ) -> Option<Vec<u8>> {
76 let mut state = GridStringState::default();
77 self.grid
78 .render_visible_line_with_default_style(
79 row,
80 options,
81 &mut state,
82 Some(&self.hyperlinks),
83 style,
84 )
85 .map(String::into_bytes)
86 }
87
88 #[must_use]
90 pub fn capture_saved_transcript(
91 &self,
92 range: ScreenCaptureRange,
93 options: GridRenderOptions,
94 ) -> Option<Vec<u8>> {
95 self.saved_grid
96 .as_ref()
97 .map(|saved| capture_grid_bytes(&saved.grid, &self.hyperlinks, range, options))
98 }
99
100 #[must_use]
102 pub fn capture_saved_line_format_flags(&self, range: ScreenCaptureRange) -> Option<Vec<u8>> {
103 self.saved_grid
104 .as_ref()
105 .map(|saved| capture_grid_line_format_flags(&saved.grid, range))
106 }
107}
108
109fn capture_grid_lines_independent(
110 grid: &Grid,
111 hyperlinks: &Hyperlinks,
112 range: ScreenCaptureRange,
113 options: GridRenderOptions,
114) -> Vec<Vec<u8>> {
115 let total_lines = grid.hsize() + usize::try_from(grid.sy()).unwrap_or(usize::MAX);
116 let Some(range) = resolve_screen_capture_range(range, grid.hsize(), total_lines) else {
117 return Vec::new();
118 };
119
120 let mut output = Vec::new();
121 for absolute_y in range {
122 let mut state = GridStringState::default();
123 let Some(line) =
124 grid.render_absolute_line(absolute_y, options, &mut state, Some(hyperlinks))
125 else {
126 continue;
127 };
128 output.push(line.into_bytes());
129 }
130 output
131}
132
133fn capture_grid_bytes(
134 grid: &Grid,
135 hyperlinks: &Hyperlinks,
136 range: ScreenCaptureRange,
137 options: GridRenderOptions,
138) -> Vec<u8> {
139 let total_lines = grid.hsize() + usize::try_from(grid.sy()).unwrap_or(usize::MAX);
140 let Some(range) = resolve_screen_capture_range(range, grid.hsize(), total_lines) else {
141 return Vec::new();
142 };
143
144 let line_count = range.end().saturating_sub(*range.start()).saturating_add(1);
145 let mut output = Vec::with_capacity(capture_capacity_hint(
146 line_count,
147 usize::try_from(grid.sx()).unwrap_or(usize::MAX),
148 ));
149 let mut state = GridStringState::default();
150 for absolute_y in range {
151 if grid
152 .append_rendered_absolute_line(
153 absolute_y,
154 options,
155 &mut state,
156 Some(hyperlinks),
157 &mut output,
158 )
159 .is_none()
160 {
161 continue;
162 };
163 let wrapped = grid.absolute_line_wrapped(absolute_y).unwrap_or(false);
164 if !options.join_wrapped || !wrapped {
165 state.reset_to_default_line_style(options, Some(hyperlinks), &mut output);
166 output.push(b'\n');
167 }
168 }
169 output
170}
171
172fn capture_grid_line_format_flags(grid: &Grid, range: ScreenCaptureRange) -> Vec<u8> {
173 let total_lines = grid.hsize() + usize::try_from(grid.sy()).unwrap_or(usize::MAX);
174 let Some(range) = resolve_screen_capture_range(range, grid.hsize(), total_lines) else {
175 return Vec::new();
176 };
177
178 let mut flags = Vec::new();
179 for absolute_y in range {
180 if grid.absolute_line(absolute_y).is_none() {
181 continue;
182 }
183 flags.push(if grid.absolute_line_wrapped(absolute_y).unwrap_or(false) {
184 b'W'
185 } else {
186 b'-'
187 });
188 }
189 flags
190}
191
192fn capture_capacity_hint(line_count: usize, line_width: usize) -> usize {
193 line_count
194 .saturating_mul(line_width.saturating_add(1))
195 .min(64 * 1024 * 1024)
196}