1use super::Term;
25use ansi::{Cell, CellWidth, Color, Cursor, Parser, Screen, Style, Utf8Decoder};
26use std::fmt;
27
28const MAGIC: &[u8; 4] = b"VTCK";
29const VERSION: u16 = 4;
30const MAX_CELLS: usize = 16 * 1024 * 1024;
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct RestoreError(&'static str);
36
37impl fmt::Display for RestoreError {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(f, "invalid terminal checkpoint: {}", self.0)
40 }
41}
42
43impl std::error::Error for RestoreError {}
44
45impl Term {
46 pub fn can_checkpoint(&self) -> bool {
49 self.parser.is_ground() && !self.decoder.is_pending()
50 }
51
52 pub fn checkpoint(&self) -> Option<Vec<u8>> {
56 if !self.can_checkpoint() {
57 return None;
58 }
59 let mut w = Vec::with_capacity(4096);
60 w.extend_from_slice(MAGIC);
61 put_u16(&mut w, VERSION);
62 let flags = (self.in_alt as u8)
63 | (self.wrap_pending as u8) << 1
64 | (self.autowrap as u8) << 2
65 | (self.cursor_visible as u8) << 3
66 | (self.in_sync as u8) << 4
67 | (self.saved.is_some() as u8) << 5
68 | (self.sync_frame.is_some() as u8) << 6
69 | (self.newline_mode as u8) << 7;
70 w.push(flags);
71 w.push(self.bracketed_paste as u8);
73 put_u32(&mut w, self.history as u32);
75 put_u32(&mut w, self.history_bytes as u32);
76 put_u32(&mut w, (self.history_bytes as u64 >> 32) as u32);
77 put_style(&mut w, &self.style);
78 put_u32(&mut w, self.scroll_top as u32);
79 put_u32(&mut w, self.scroll_bottom as u32);
80 if let Some((cursor, style)) = &self.saved {
81 put_u32(&mut w, cursor.row as u32);
82 put_u32(&mut w, cursor.col as u32);
83 put_style(&mut w, style);
84 }
85 put_u32(&mut w, self.tabs.len() as u32);
86 for chunk in self.tabs.chunks(8) {
87 let mut byte = 0u8;
88 for (i, &t) in chunk.iter().enumerate() {
89 byte |= (t as u8) << i;
90 }
91 w.push(byte);
92 }
93 put_screen(&mut w, &self.primary);
94 put_screen(&mut w, &self.alt);
95 if let Some(frame) = &self.sync_frame {
96 put_screen(&mut w, frame);
97 }
98 Some(w)
99 }
100
101 pub fn restore(bytes: &[u8]) -> Result<Term, RestoreError> {
105 let mut r = Reader {
106 bytes,
107 at: 0,
108 version: 0,
109 };
110 if r.take(4)? != MAGIC {
111 return Err(RestoreError("not a checkpoint"));
112 }
113 let version = r.u16()?;
114 if !(1..=VERSION).contains(&version) {
115 return Err(RestoreError("unsupported version"));
116 }
117 r.version = version;
118 let flags = r.u8()?;
119 let flags2 = if version >= 2 { r.u8()? } else { 0 };
123 let (history, history_bytes) = if version >= 4 {
126 let lines = r.u32()? as usize;
127 let lo = r.u32()? as u64;
128 let hi = r.u32()? as u64;
129 (
130 lines,
131 usize::try_from(lo | (hi << 32)).unwrap_or(usize::MAX),
132 )
133 } else {
134 (0, 0)
135 };
136 let bit = |n: u8| flags & (1 << n) != 0;
137 let style = r.style()?;
138 let scroll_top = r.u32()? as usize;
139 let scroll_bottom = r.u32()? as usize;
140 let saved = if bit(5) {
141 let row = r.u32()? as usize;
142 let col = r.u32()? as usize;
143 Some((Cursor::new(row, col), r.style()?))
144 } else {
145 None
146 };
147 let ntabs = r.u32()? as usize;
148 let tab_bytes = r.take(ntabs.div_ceil_compat(8))?;
149 let tabs: Vec<bool> = (0..ntabs)
150 .map(|i| tab_bytes[i / 8] & (1 << (i % 8)) != 0)
151 .collect();
152 let mut primary = r.screen(history)?;
153 primary.set_history_bytes(history_bytes);
154 let alt = r.screen(0)?;
155 let sync_frame = if bit(6) { Some(r.screen(0)?) } else { None };
156 if r.at != bytes.len() {
157 return Err(RestoreError("trailing bytes"));
158 }
159
160 let (rows, cols) = (primary.rows(), primary.cols());
161 let same = |s: &Screen| s.rows() == rows && s.cols() == cols;
162 if !same(&alt) || !sync_frame.as_ref().map_or(true, same) {
163 return Err(RestoreError("screens disagree on size"));
164 }
165 if tabs.len() != cols {
166 return Err(RestoreError("tab stops do not match the width"));
167 }
168 if scroll_top > scroll_bottom || scroll_bottom >= rows {
169 return Err(RestoreError("scroll region outside the screen"));
170 }
171 if saved.is_some_and(|(c, _)| c.row >= rows || c.col >= cols) {
172 return Err(RestoreError("saved cursor outside the screen"));
173 }
174 if bit(4) != sync_frame.is_some() {
175 return Err(RestoreError("synchronized update without its frame"));
176 }
177
178 let mut term = Term::new(rows, cols);
179 term.history = history;
180 term.history_bytes = history_bytes;
181 term.primary = primary;
182 term.alt = alt;
183 term.in_alt = bit(0);
184 term.wrap_pending = bit(1);
185 term.autowrap = bit(2);
186 term.cursor_visible = bit(3);
187 term.newline_mode = bit(7);
190 term.bracketed_paste = flags2 & 1 != 0;
191 term.in_sync = bit(4);
192 term.style = style;
193 term.scroll_top = scroll_top;
194 term.scroll_bottom = scroll_bottom;
195 term.saved = saved;
196 term.tabs = tabs;
197 term.sync_frame = sync_frame;
198 term.parser = Parser::new();
199 term.decoder = Utf8Decoder::new();
200 term.dirty = true;
201 Ok(term)
202 }
203}
204
205trait DivCeil {
207 fn div_ceil_compat(self, d: usize) -> usize;
208}
209
210impl DivCeil for usize {
211 fn div_ceil_compat(self, d: usize) -> usize {
212 (self + d - 1) / d
213 }
214}
215
216fn put_u16(w: &mut Vec<u8>, v: u16) {
217 w.extend_from_slice(&v.to_le_bytes());
218}
219
220fn put_u32(w: &mut Vec<u8>, v: u32) {
221 w.extend_from_slice(&v.to_le_bytes());
222}
223
224fn put_color(w: &mut Vec<u8>, c: Color) {
225 match c {
226 Color::Default => w.extend_from_slice(&[0, 0, 0, 0]),
227 Color::Indexed(i) => w.extend_from_slice(&[1, i, 0, 0]),
228 Color::Rgb(r, g, b) => w.extend_from_slice(&[2, r, g, b]),
229 }
230}
231
232fn put_style(w: &mut Vec<u8>, s: &Style) {
233 put_color(w, s.fg);
234 put_color(w, s.bg);
235 w.push(
236 (s.bold as u8)
237 | (s.dim as u8) << 1
238 | (s.italic as u8) << 2
239 | (s.underline as u8) << 3
240 | (s.reverse as u8) << 4
241 | (s.strike as u8) << 5,
242 );
243}
244
245fn put_cell(w: &mut Vec<u8>, c: &Cell) {
246 put_u32(w, c.ch as u32);
247 w.push(match c.width {
248 CellWidth::Continuation => 0,
249 CellWidth::Single => 1,
250 CellWidth::Wide => 2,
251 });
252 put_style(w, &c.style);
253}
254
255fn put_screen(w: &mut Vec<u8>, s: &Screen) {
257 put_u32(w, s.rows() as u32);
258 put_u32(w, s.cols() as u32);
259 let cursor = s.cursor();
260 put_u32(w, cursor.row as u32);
261 put_u32(w, cursor.col as u32);
262 let mut run: Option<(Cell, u32)> = None;
263 for row in 0..s.rows() {
264 for col in 0..s.cols() {
265 let cell = s.cell(row, col);
266 match &mut run {
267 Some((c, n)) if *c == cell => *n += 1,
268 _ => {
269 if let Some((c, n)) = run.take() {
270 put_u32(w, n);
271 put_cell(w, &c);
272 }
273 run = Some((cell, 1));
274 }
275 }
276 }
277 }
278 if let Some((c, n)) = run {
279 put_u32(w, n);
280 put_cell(w, &c);
281 }
282 for chunk in (0..s.rows()).collect::<Vec<_>>().chunks(8) {
284 let mut byte = 0u8;
285 for (i, &r) in chunk.iter().enumerate() {
286 byte |= (s.row_wrapped(r) as u8) << i;
287 }
288 w.push(byte);
289 }
290}
291
292struct Reader<'a> {
293 bytes: &'a [u8],
294 at: usize,
295 version: u16,
297}
298
299impl<'a> Reader<'a> {
300 fn take(&mut self, n: usize) -> Result<&'a [u8], RestoreError> {
301 let end = self.at.checked_add(n).ok_or(RestoreError("truncated"))?;
302 let s = self
303 .bytes
304 .get(self.at..end)
305 .ok_or(RestoreError("truncated"))?;
306 self.at = end;
307 Ok(s)
308 }
309 fn u8(&mut self) -> Result<u8, RestoreError> {
310 Ok(self.take(1)?[0])
311 }
312 fn u16(&mut self) -> Result<u16, RestoreError> {
313 let b = self.take(2)?;
314 Ok(u16::from_le_bytes([b[0], b[1]]))
315 }
316 fn u32(&mut self) -> Result<u32, RestoreError> {
317 let b = self.take(4)?;
318 Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
319 }
320 fn color(&mut self) -> Result<Color, RestoreError> {
321 let b = self.take(4)?;
322 match b[0] {
323 0 => Ok(Color::Default),
324 1 => Ok(Color::Indexed(b[1])),
325 2 => Ok(Color::Rgb(b[1], b[2], b[3])),
326 _ => Err(RestoreError("unknown colour")),
327 }
328 }
329 fn style(&mut self) -> Result<Style, RestoreError> {
330 let fg = self.color()?;
331 let bg = self.color()?;
332 let a = self.u8()?;
333 if a >> 6 != 0 {
334 return Err(RestoreError("unknown style attribute"));
335 }
336 Ok(Style {
337 fg,
338 bg,
339 bold: a & 1 != 0,
340 dim: a & 2 != 0,
341 italic: a & 4 != 0,
342 underline: a & 8 != 0,
343 reverse: a & 16 != 0,
344 strike: a & 32 != 0,
345 })
346 }
347 fn cell(&mut self) -> Result<Cell, RestoreError> {
348 let ch = char::from_u32(self.u32()?).ok_or(RestoreError("invalid character"))?;
349 let width = match self.u8()? {
350 0 => CellWidth::Continuation,
351 1 => CellWidth::Single,
352 2 => CellWidth::Wide,
353 _ => return Err(RestoreError("unknown cell width")),
354 };
355 Ok(Cell {
356 ch,
357 style: self.style()?,
358 width,
359 })
360 }
361 fn screen(&mut self, history: usize) -> Result<Screen, RestoreError> {
364 let rows = self.u32()? as usize;
365 let cols = self.u32()? as usize;
366 let total = rows
367 .checked_mul(cols)
368 .filter(|&n| n > 0 && n <= MAX_CELLS)
369 .ok_or(RestoreError("screen size out of range"))?;
370 let (crow, ccol) = (self.u32()? as usize, self.u32()? as usize);
371 if crow >= rows || ccol >= cols {
372 return Err(RestoreError("cursor outside the screen"));
373 }
374 let mut screen = Screen::with_history(rows, cols, history);
375 let mut filled = 0usize;
376 while filled < total {
377 let n = self.u32()? as usize;
378 if n == 0 || n > total - filled {
379 return Err(RestoreError("bad cell run"));
380 }
381 let cell = self.cell()?;
382 if cell != Cell::default() {
386 for k in filled..filled + n {
387 screen.set(k / cols, k % cols, cell);
388 }
389 }
390 filled += n;
391 }
392 screen.set_cursor(Cursor::new(crow, ccol));
393 if self.version >= 3 {
395 let marks = self.take(rows.div_ceil_compat(8))?;
396 for r in 0..rows {
397 screen.set_row_wrapped(r, marks[r / 8] & (1 << (r % 8)) != 0);
398 }
399 }
400 Ok(screen)
401 }
402}