1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//! The bytes that draw a terminal session's screen again from nothing: its scrollback, its screen,
//! the screen beneath when a program is on the alternate one, the cursor and the modes the
//! program turned on.
use super::terminal_notice::Heard;
use super::terminal_session::TerminalSession;
/// Leaves the alternate screen, putting back the cursor saved when it was entered.
const LEAVE_ALTERNATE: &[u8] = b"\x1b[?1049l";
/// Saves the cursor and enters the alternate screen, cleared.
const ENTER_ALTERNATE: &[u8] = b"\x1b[?1049h";
/// Ends a line of scrollback: attributes off, so the next line starts plain, and down a row.
const END_OF_LINE: &[u8] = b"\x1b[m\r\n";
/// Turns on the focus reports of xterm's mode 1004.
const FOCUS_REPORTS: &[u8] = b"\x1b[?1004h";
impl TerminalSession {
/// Bytes that, given to an empty session with the same screen size, draw what this one
/// draws: its scrollback, oldest line first, its screen with every colour and attribute, the
/// screen beneath when the program is on the alternate one, the cursor, and the modes the
/// program turned on, application keys, bracketed paste, mouse reporting, focus reports and
/// the kitty keyboard flags. Taken while the session's screen is locked, so it is one moment
/// of it.
///
/// It is what an [attached](Self::attach) session is sent first, before the bytes the program
/// writes after it: a screen that attaches to a program running elsewhere draws it as it was
/// when it left, then follows it. Leaving the alternate screen later shows the screen beneath
/// as it was, since that is drawn first.
///
/// The whole scrollback the session keeps is sent, which its
/// [`scrollback`](super::TerminalBuilder::scrollback) bounds; a line that wrapped arrives as a
/// line of its own. What the program said about itself, its title and folder, is reported by
/// its watch as it happens and is not repeated here.
#[must_use]
pub fn snapshot(&self) -> Vec<u8> {
snapshot(&self.parser())
}
}
/// The bytes of [`TerminalSession::snapshot`] for the screen `parser` holds.
pub(super) fn snapshot(parser: &vt100::Parser<Heard>) -> Vec<u8> {
let live = parser.screen();
let (rows, columns) = live.size();
let alternate = live.alternate_screen();
let heard = parser.callbacks();
// A copy to read from, so the screen a person may be scrolling back through is left where it is.
let mut copy = vt100::Parser::new(rows, columns, 0);
*copy.screen_mut() = live.clone();
if alternate {
// The screen beneath is not shown while the program is on the alternate one; the copy
// leaves it to read it.
copy.process(LEAVE_ALTERNATE);
}
let main = copy.screen_mut();
let mut out = Vec::new();
// The oldest kept line is as far back as the view goes; each one is the top row there.
main.set_scrollback(usize::MAX);
let kept = main.scrollback();
for back in (1..=kept).rev() {
main.set_scrollback(back);
if let Some(row) = main.rows_formatted(0, columns).next() {
out.extend_from_slice(&row);
}
out.extend_from_slice(END_OF_LINE);
}
if kept > 0 {
// Every line above ends with a line break, and the screen scrolls a line into the
// scrollback only for a break on its last row: as many more as the screen has rows less
// one push all of them up, and the screen is drawn over the blank rows left.
for _ in 1..rows {
out.extend_from_slice(b"\r\n");
}
}
main.set_scrollback(0);
out.extend_from_slice(&main.contents_formatted());
push_keyboard(&mut out, heard.keyboard.pushed(false));
if alternate {
// Saved on entering, so leaving later puts the cursor back where the screen beneath had it.
out.extend_from_slice(&main.cursor_state_formatted());
out.extend_from_slice(ENTER_ALTERNATE);
out.extend_from_slice(&live.contents_formatted());
push_keyboard(&mut out, heard.keyboard.pushed(true));
}
out.extend_from_slice(&live.input_mode_formatted());
if heard.focus.reporting() {
out.extend_from_slice(FOCUS_REPORTS);
}
out.extend_from_slice(&live.cursor_state_formatted());
out
}
/// Pushes the kitty keyboard flags `flags`, oldest first, on the screen in use.
fn push_keyboard(out: &mut Vec<u8>, flags: &[u16]) {
for flags in flags {
out.extend_from_slice(format!("\x1b[>{flags}u").as_bytes());
}
}