Skip to main content

rmux_core/screen/
view.rs

1use crate::grid::{GridLine, GridLineFlags};
2
3use super::Screen;
4
5/// Read-only copy of one rendered screen cell for copy-mode consumers.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct ScreenCellView {
8    pub(super) text: String,
9    pub(super) width: u8,
10    pub(super) padding: bool,
11    pub(super) attr: u16,
12    pub(super) fg: crate::input::Colour,
13    pub(super) bg: crate::input::Colour,
14    pub(super) us: crate::input::Colour,
15    pub(super) link: u32,
16}
17
18impl ScreenCellView {
19    /// Returns the stored cell text.
20    #[must_use]
21    pub fn text(&self) -> &str {
22        &self.text
23    }
24
25    /// Returns the display width of the cell.
26    #[must_use]
27    pub const fn width(&self) -> u8 {
28        self.width
29    }
30
31    /// Returns whether the cell is padding for a wide glyph.
32    #[must_use]
33    pub const fn is_padding(&self) -> bool {
34        self.padding
35    }
36
37    /// Returns the cell attributes.
38    #[must_use]
39    pub const fn attr(&self) -> u16 {
40        self.attr
41    }
42
43    /// Returns the cell foreground colour.
44    #[must_use]
45    pub const fn fg(&self) -> crate::input::Colour {
46        self.fg
47    }
48
49    /// Returns the cell background colour.
50    #[must_use]
51    pub const fn bg(&self) -> crate::input::Colour {
52        self.bg
53    }
54
55    /// Returns the cell underline colour.
56    #[must_use]
57    pub const fn us(&self) -> crate::input::Colour {
58        self.us
59    }
60
61    /// Returns the hyperlink inner ID for the cell.
62    #[must_use]
63    pub const fn link(&self) -> u32 {
64        self.link
65    }
66}
67
68/// Read-only copy of one absolute screen line for copy-mode consumers.
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct ScreenLineView {
71    pub(super) cells: Vec<ScreenCellView>,
72    pub(super) wrapped: bool,
73    pub(super) start_prompt: bool,
74    pub(super) start_output: bool,
75    pub(super) time: i64,
76}
77
78impl ScreenLineView {
79    /// Returns the stored cells for the line.
80    #[must_use]
81    pub fn cells(&self) -> &[ScreenCellView] {
82        &self.cells
83    }
84
85    /// Returns one cell by column.
86    #[must_use]
87    pub fn cell(&self, x: u32) -> Option<&ScreenCellView> {
88        self.cells.get(x as usize)
89    }
90
91    /// Returns whether the line wraps onto the following row.
92    #[must_use]
93    pub const fn wrapped(&self) -> bool {
94        self.wrapped
95    }
96
97    /// Returns whether the line starts a shell prompt block.
98    #[must_use]
99    pub const fn start_prompt(&self) -> bool {
100        self.start_prompt
101    }
102
103    /// Returns whether the line starts a shell output block.
104    #[must_use]
105    pub const fn start_output(&self) -> bool {
106        self.start_output
107    }
108
109    /// Returns the line timestamp.
110    #[must_use]
111    pub const fn time(&self) -> i64 {
112        self.time
113    }
114
115    /// Resolves the owning non-padding cell for a column.
116    #[must_use]
117    pub fn owning_cell_x(&self, x: u32) -> Option<u32> {
118        let cell = self.cell(x)?;
119        if !cell.is_padding() {
120            return Some(x);
121        }
122
123        let mut owner = x;
124        while owner > 0 {
125            owner -= 1;
126            let candidate = self.cell(owner)?;
127            if !candidate.is_padding() {
128                let width = u32::from(candidate.width().max(1));
129                if owner.saturating_add(width) > x {
130                    return Some(owner);
131                }
132                return None;
133            }
134        }
135        None
136    }
137}
138
139impl Screen {
140    /// Returns a read-only copy of one absolute line.
141    #[must_use]
142    pub fn absolute_line_view(&self, absolute_y: usize) -> Option<ScreenLineView> {
143        let line = self.grid.absolute_line(absolute_y)?;
144        Some(ScreenLineView {
145            cells: line
146                .cells()
147                .iter()
148                .map(|cell| ScreenCellView {
149                    text: cell.text().to_owned(),
150                    width: cell.width(),
151                    padding: cell.is_padding(),
152                    attr: cell.attr(),
153                    fg: cell.fg(),
154                    bg: cell.bg(),
155                    us: cell.us(),
156                    link: cell.link(),
157                })
158                .collect(),
159            wrapped: line.flags().contains(GridLineFlags::WRAPPED),
160            start_prompt: line.flags().contains(GridLineFlags::START_PROMPT),
161            start_output: line.flags().contains(GridLineFlags::START_OUTPUT),
162            time: line.time(),
163        })
164    }
165
166    /// Clones the screen as a standalone viewport over its absolute lines.
167    #[must_use]
168    pub fn clone_viewport(&self, top_line: usize, cursor_x: u32, cursor_absolute_y: usize) -> Self {
169        let size = self.grid.size();
170        let rows = usize::from(size.rows.max(1));
171        let cols = u32::from(size.cols.max(1));
172        let total_lines = self.absolute_line_count();
173        let top_line = top_line.min(total_lines.saturating_sub(rows));
174        let mut viewport = Self::new(size, 0);
175
176        viewport.mode = self.mode;
177        viewport.cursor_style = self.cursor_style;
178        viewport.title = self.title.clone();
179        viewport.window_name = self.window_name.clone();
180        viewport.path = self.path.clone();
181        viewport.title_stack = self.title_stack.clone();
182        viewport.hyperlinks = self.hyperlinks.clone();
183        viewport.active_hyperlink = self.active_hyperlink;
184        viewport.bell_count = 0;
185        viewport.utf8_config = self.utf8_config.clone();
186
187        let lines = (0..rows)
188            .map(|offset| {
189                self.grid
190                    .absolute_line(top_line + offset)
191                    .cloned()
192                    .unwrap_or_else(|| GridLine::new(cols))
193            })
194            .collect();
195        viewport.grid.replace_visible(lines);
196
197        viewport.cursor_x = cursor_x.min(viewport.max_cursor_x());
198        viewport.cursor_y = if (top_line..top_line + rows).contains(&cursor_absolute_y) {
199            (cursor_absolute_y - top_line) as u32
200        } else {
201            0
202        };
203        viewport.pending_wrap = false;
204        viewport.saved_cursor_x = None;
205        viewport.saved_cursor_y = None;
206        viewport.saved_cursor_pending_wrap = false;
207        viewport.saved_grid = None;
208        viewport.rupper = 0;
209        viewport.rlower = u32::from(size.rows.max(1)).saturating_sub(1);
210        viewport.reset_tabs();
211        viewport
212    }
213}