Skip to main content

rmux_core/screen/
capture.rs

1use crate::grid::{Grid, GridCapture, GridRenderOptions, GridStringState};
2use crate::hyperlinks::Hyperlinks;
3use crate::transcript::{resolve_screen_capture_range, ScreenCaptureRange};
4
5use super::Screen;
6
7impl Screen {
8    #[cfg_attr(not(test), allow(dead_code))]
9    #[must_use]
10    pub(crate) fn capture_grid(&self, join_wrapped: bool) -> GridCapture {
11        self.grid.capture(join_wrapped)
12    }
13
14    /// Captures a tmux-style line range over the current grid contents.
15    #[must_use]
16    pub fn capture_transcript(
17        &self,
18        range: ScreenCaptureRange,
19        options: GridRenderOptions,
20    ) -> Vec<u8> {
21        capture_grid_bytes(&self.grid, &self.hyperlinks, range, options)
22    }
23
24    /// Captures physical lines with each line rendered from a fresh ANSI state.
25    ///
26    /// This is intended for renderers that repaint individual terminal rows:
27    /// a row must carry its own SGR state instead of depending on a previous
28    /// captured row having been emitted first.
29    #[must_use]
30    pub fn capture_transcript_lines_independent(
31        &self,
32        range: ScreenCaptureRange,
33        options: GridRenderOptions,
34    ) -> Vec<Vec<u8>> {
35        capture_grid_lines_independent(&self.grid, &self.hyperlinks, range, options)
36    }
37
38    /// Captures the saved pre-alternate-screen copy when alternate mode is active.
39    #[must_use]
40    pub fn capture_saved_transcript(
41        &self,
42        range: ScreenCaptureRange,
43        options: GridRenderOptions,
44    ) -> Option<Vec<u8>> {
45        self.saved_grid
46            .as_ref()
47            .map(|saved| capture_grid_bytes(&saved.grid, &self.hyperlinks, range, options))
48    }
49}
50
51fn capture_grid_lines_independent(
52    grid: &Grid,
53    hyperlinks: &Hyperlinks,
54    range: ScreenCaptureRange,
55    options: GridRenderOptions,
56) -> Vec<Vec<u8>> {
57    let total_lines = grid.hsize() + usize::try_from(grid.sy()).unwrap_or(usize::MAX);
58    let Some(range) = resolve_screen_capture_range(range, grid.hsize(), total_lines) else {
59        return Vec::new();
60    };
61
62    let mut output = Vec::new();
63    for absolute_y in range {
64        let mut state = GridStringState::default();
65        let Some(line) =
66            grid.render_absolute_line(absolute_y, options, &mut state, Some(hyperlinks))
67        else {
68            continue;
69        };
70        output.push(line.into_bytes());
71    }
72    output
73}
74
75fn capture_grid_bytes(
76    grid: &Grid,
77    hyperlinks: &Hyperlinks,
78    range: ScreenCaptureRange,
79    options: GridRenderOptions,
80) -> Vec<u8> {
81    let total_lines = grid.hsize() + usize::try_from(grid.sy()).unwrap_or(usize::MAX);
82    let Some(range) = resolve_screen_capture_range(range, grid.hsize(), total_lines) else {
83        return Vec::new();
84    };
85
86    let mut output = Vec::new();
87    let mut state = GridStringState::default();
88    for absolute_y in range {
89        let Some(line) =
90            grid.render_absolute_line(absolute_y, options, &mut state, Some(hyperlinks))
91        else {
92            continue;
93        };
94        output.extend_from_slice(line.as_bytes());
95        let wrapped = grid.absolute_line_wrapped(absolute_y).unwrap_or(false);
96        if !options.join_wrapped || !wrapped {
97            output.push(b'\n');
98        }
99    }
100    output
101}