Skip to main content

katui_present/
session.rs

1//! Terminal session sequences: alternate screen and cursor state, one
2//! enter, one leave, nothing between.
3
4use crate::Sink;
5use katui_proto::{clipboard, kitty};
6use std::io;
7
8/// Enter the alternate screen with the cursor hidden; everything drawn
9/// afterwards leaves the user's scrollback untouched.
10pub fn enter<W: Sink>(out: &mut W) -> io::Result<()> {
11    out.write_all(b"\x1b[?1049h\x1b[?25l")
12}
13
14/// Leave the alternate screen with the cursor back.
15pub fn leave<W: Sink>(out: &mut W) -> io::Result<()> {
16    out.write_all(b"\x1b[?25h\x1b[?1049l")
17}
18
19/// Turn on what an interactive application reads: all mouse motion in SGR
20/// form with pixel coordinates, focus changes, bracketed paste and
21/// unambiguous keys, then ask whether pixel coordinates took effect.
22pub fn enable_input<W: Sink>(out: &mut W) -> io::Result<()> {
23    out.write_all(b"\x1b[?1003h\x1b[?1006h\x1b[?1016h\x1b[?1004h\x1b[?2004h\x1b[>1u\x1b[?1016$p")
24}
25
26/// Undo [`enable_input`].
27pub fn disable_input<W: Sink>(out: &mut W) -> io::Result<()> {
28    out.write_all(b"\x1b[=0;1u\x1b[<u\x1b[?2004l\x1b[?1004l\x1b[?1016l\x1b[?1006l\x1b[?1003l")
29}
30
31/// Also report key repeats and releases, with every key as an escape
32/// code so that plain letters report theirs too. [`disable_input`] undoes
33/// this along with the rest.
34pub fn report_key_releases<W: Sink>(out: &mut W) -> io::Result<()> {
35    out.write_all(b"\x1b[=11;1u")
36}
37
38/// Ask the terminal to answer under `id` once it has consumed everything
39/// written before this.
40pub fn terminal_query<W: Sink>(out: &mut W, id: u32) -> io::Result<()> {
41    out.write_all(&kitty::barrier(kitty::ImageId(id)))
42}
43
44/// Put `text` on the system clipboard; `multiplexed` routes it through a
45/// tmux or rmux pane.
46pub fn copy_to_clipboard<W: Sink>(out: &mut W, text: &str, multiplexed: bool) -> io::Result<()> {
47    out.write_all(&clipboard::copy(text, multiplexed))
48}
49
50#[cfg(test)]
51mod tests {
52    use super::{disable_input, enable_input, enter, leave, report_key_releases};
53
54    #[test]
55    fn disabling_input_undoes_the_key_release_flags() {
56        let (mut flags, mut off) = (Vec::new(), Vec::new());
57        report_key_releases(&mut flags).expect("writing to a Vec cannot fail");
58        disable_input(&mut off).expect("writing to a Vec cannot fail");
59        assert_eq!(flags, b"\x1b[=11;1u");
60        assert!(
61            off.starts_with(b"\x1b[=0;1u\x1b[<u"),
62            "clearing the flags before popping covers terminals that keep them"
63        );
64    }
65
66    #[test]
67    fn disabling_input_turns_off_every_mode_enabling_set() {
68        let (mut on, mut off) = (Vec::new(), Vec::new());
69        enable_input(&mut on).expect("writing to a Vec cannot fail");
70        disable_input(&mut off).expect("writing to a Vec cannot fail");
71        let (on, off) = (
72            String::from_utf8_lossy(&on).into_owned(),
73            String::from_utf8_lossy(&off).into_owned(),
74        );
75        for mode in ["1003", "1006", "1016", "1004", "2004"] {
76            assert!(on.contains(&format!("?{mode}h")), "{mode} goes on");
77            assert!(off.contains(&format!("?{mode}l")), "{mode} goes off");
78        }
79    }
80
81    #[test]
82    fn enter_and_leave_write_their_exact_sequences() {
83        let mut out = Vec::new();
84        enter(&mut out).expect("writing to a Vec cannot fail");
85        leave(&mut out).expect("writing to a Vec cannot fail");
86        assert_eq!(
87            String::from_utf8_lossy(&out),
88            "\x1b[?1049h\x1b[?25l\x1b[?25h\x1b[?1049l"
89        );
90    }
91}