1use std::fmt;
8
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11mod attrs;
12mod cell;
13mod color;
14mod cursor;
15mod glyph;
16
17pub use attrs::PaneAttributes;
18pub use cell::PaneCell;
19pub use color::PaneColor;
20pub use cursor::PaneCursor;
21pub use glyph::PaneGlyph;
22
23#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
33pub struct PaneSnapshot {
34 pub cols: u16,
36 pub rows: u16,
38 pub cells: Vec<PaneCell>,
40 pub cursor: PaneCursor,
42 pub revision: u64,
51}
52
53impl PaneSnapshot {
54 pub fn new(
60 cols: u16,
61 rows: u16,
62 cells: Vec<PaneCell>,
63 cursor: PaneCursor,
64 ) -> Result<Self, PaneSnapshotShapeError> {
65 let snapshot = Self {
66 cols,
67 rows,
68 cells,
69 cursor,
70 revision: 0,
71 };
72 snapshot.validate_shape()?;
73 Ok(snapshot)
74 }
75
76 #[must_use]
78 pub fn with_revision(mut self, revision: u64) -> Self {
79 self.revision = revision;
80 self
81 }
82
83 #[must_use]
85 pub fn expected_cell_count(&self) -> usize {
86 expected_cell_count(self.cols, self.rows)
87 }
88
89 #[must_use]
91 pub fn is_row_major_shape(&self) -> bool {
92 self.cells.len() == self.expected_cell_count()
93 }
94
95 pub fn validate_shape(&self) -> Result<(), PaneSnapshotShapeError> {
97 let expected = self.expected_cell_count();
98 if self.cells.len() == expected {
99 Ok(())
100 } else {
101 Err(PaneSnapshotShapeError {
102 cols: self.cols,
103 rows: self.rows,
104 actual_cells: self.cells.len(),
105 expected_cells: expected,
106 })
107 }
108 }
109
110 #[must_use]
112 pub fn cell(&self, row: u16, col: u16) -> Option<&PaneCell> {
113 if row >= self.rows || col >= self.cols {
114 return None;
115 }
116 let index = usize::from(row)
117 .saturating_mul(usize::from(self.cols))
118 .saturating_add(usize::from(col));
119 self.cells.get(index)
120 }
121
122 #[must_use]
127 pub fn row_cells(&self, row: u16) -> Option<&[PaneCell]> {
128 if row >= self.rows {
129 return None;
130 }
131
132 let cols = usize::from(self.cols);
133 let start = usize::from(row).checked_mul(cols)?;
134 let end = start.checked_add(cols)?;
135 self.cells.get(start..end)
136 }
137
138 #[must_use]
144 pub fn owning_cell_col(&self, row: u16, col: u16) -> Option<u16> {
145 let cell = self.cell(row, col)?;
146 if !cell.is_padding() {
147 return Some(col);
148 }
149
150 let mut owner = col;
151 while owner > 0 {
152 owner -= 1;
153 let candidate = self.cell(row, owner)?;
154 if !candidate.is_padding() {
155 let width = u16::from(candidate.glyph.width.max(1));
156 if owner.saturating_add(width) > col {
157 return Some(owner);
158 }
159 return None;
160 }
161 }
162
163 None
164 }
165
166 pub fn visible_cells(&self) -> impl Iterator<Item = (u16, u16, &PaneCell)> + '_ {
171 let cols = usize::from(self.cols);
172 let rows = usize::from(self.rows);
173 self.cells
174 .iter()
175 .enumerate()
176 .filter_map(move |(index, cell)| {
177 if cols == 0 || cell.is_padding() {
178 return None;
179 }
180
181 let row = index / cols;
182 if row >= rows {
183 return None;
184 }
185 let col = index % cols;
186 Some((row as u16, col as u16, cell))
187 })
188 }
189
190 #[must_use]
197 pub fn visible_row_text(&self, row: u16) -> Option<String> {
198 self.lossy_row_cells(row).map(render_cells_lossy)
199 }
200
201 #[must_use]
203 pub fn row_text(&self, row: u16) -> String {
204 self.visible_row_text(row).unwrap_or_default()
205 }
206
207 #[must_use]
212 pub fn visible_lines(&self) -> Vec<String> {
213 (0..self.rows)
214 .map(|row| self.visible_row_text(row).unwrap_or_default())
215 .collect()
216 }
217
218 #[must_use]
222 pub fn visible_text(&self) -> String {
223 self.visible_lines().join("\n")
224 }
225
226 fn lossy_row_cells(&self, row: u16) -> Option<&[PaneCell]> {
227 if row >= self.rows {
228 return None;
229 }
230
231 let cols = usize::from(self.cols);
232 if cols == 0 {
233 return Some(&[]);
234 }
235
236 let start = usize::from(row).checked_mul(cols)?;
237 if start >= self.cells.len() {
238 return Some(&[]);
239 }
240 let end = start.saturating_add(cols).min(self.cells.len());
241 Some(&self.cells[start..end])
242 }
243}
244
245impl Serialize for PaneSnapshot {
246 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
247 where
248 S: Serializer,
249 {
250 self.validate_shape().map_err(serde::ser::Error::custom)?;
251 PaneSnapshotFieldsRef {
252 cols: self.cols,
253 rows: self.rows,
254 cells: &self.cells,
255 cursor: &self.cursor,
256 revision: self.revision,
257 }
258 .serialize(serializer)
259 }
260}
261
262impl<'de> Deserialize<'de> for PaneSnapshot {
263 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
264 where
265 D: Deserializer<'de>,
266 {
267 let fields = PaneSnapshotFields::deserialize(deserializer)?;
268 Self::new(fields.cols, fields.rows, fields.cells, fields.cursor)
269 .map(|snapshot| snapshot.with_revision(fields.revision))
270 .map_err(serde::de::Error::custom)
271 }
272}
273
274#[derive(Serialize)]
275struct PaneSnapshotFieldsRef<'a> {
276 cols: u16,
277 rows: u16,
278 cells: &'a [PaneCell],
279 cursor: &'a PaneCursor,
280 revision: u64,
281}
282
283#[derive(Debug, Default, Deserialize)]
284#[serde(default)]
285struct PaneSnapshotFields {
286 cols: u16,
287 rows: u16,
288 cells: Vec<PaneCell>,
289 cursor: PaneCursor,
290 revision: u64,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, Hash)]
295pub struct PaneSnapshotShapeError {
296 cols: u16,
297 rows: u16,
298 actual_cells: usize,
299 expected_cells: usize,
300}
301
302impl PaneSnapshotShapeError {
303 #[must_use]
305 pub const fn cols(&self) -> u16 {
306 self.cols
307 }
308
309 #[must_use]
311 pub const fn rows(&self) -> u16 {
312 self.rows
313 }
314
315 #[must_use]
317 pub const fn actual_cells(&self) -> usize {
318 self.actual_cells
319 }
320
321 #[must_use]
323 pub const fn expected_cells(&self) -> usize {
324 self.expected_cells
325 }
326}
327
328impl fmt::Display for PaneSnapshotShapeError {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 write!(
331 f,
332 "pane snapshot shape mismatch: {}x{} expects {} cells, got {}",
333 self.cols, self.rows, self.expected_cells, self.actual_cells
334 )
335 }
336}
337
338impl std::error::Error for PaneSnapshotShapeError {}
339
340fn expected_cell_count(cols: u16, rows: u16) -> usize {
341 usize::from(cols) * usize::from(rows)
342}
343
344fn render_cells_lossy(cells: &[PaneCell]) -> String {
345 let mut rendered = String::new();
346 for cell in cells {
347 if cell.is_padding() {
348 continue;
349 }
350 rendered.push_str(cell.text());
351 }
352 while rendered.ends_with(' ') {
353 rendered.pop();
354 }
355 rendered
356}