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