aboutsummaryrefslogtreecommitdiffstats
path: root/src/term.rs
blob: 3a56091b679fb2d503e6188405e5ed351b1e6a3f (plain) (blame)
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use libc::{termios, winsize};
use std::error::Error;
use std::io::{stdin, stdout, Read, Write};

pub fn size() -> Result<winsize, Box<dyn Error>> {
    unsafe {
        let mut window: winsize = std::mem::zeroed();
        let status = libc::ioctl(1, libc::TIOCGWINSZ, &mut window);
        if status < 0 {
            return Err(Box::from(format!(
                "terminal size: ioctl failed with code {}",
                status
            )));
        }
        return Ok(window);
    }
}

fn get_attr() -> Result<termios, Box<dyn Error>> {
    unsafe {
        let mut attr: termios = std::mem::zeroed();
        let status = libc::tcgetattr(1, &mut attr);
        if status < 0 {
            return Err(Box::from(format!("tcgetattr failed with code {}", status)));
        }
        return Ok(attr);
    }
}

fn set_attr(attrs: &termios) -> Result<(), Box<dyn Error>> {
    unsafe {
        let status = libc::tcsetattr(1, libc::TCSANOW, attrs);
        if status < 0 {
            return Err(Box::from(format!("set attributes: code {}", status)));
        }
        return Ok(());
    }
}

fn make_raw() -> termios {
    unsafe {
        let mut attr: termios = std::mem::zeroed();
        libc::cfmakeraw(&mut attr);
        return attr;
    }
}

pub struct RawHandle {
    old: termios,
}

impl Drop for RawHandle {
    fn drop(&mut self) {
        let _ = write!(stdout(), "\x1b[?7h");
        let _ = stdout().flush();
        let _ = set_attr(&self.old);
    }
}

pub fn raw() -> Result<RawHandle, Box<dyn Error>> {
    write!(stdout(), "\x1b[?7l")?;
    stdout().flush()?;
    let old = get_attr()?;
    set_attr(&make_raw())?;
    return Ok(RawHandle { old: old });
}

pub enum Key {
    CtrlQ,
    CtrlS,
    CtrlZ,
    Up,
    Down,
    Left,
    Right,
    Backspace,
    Char(u8),
}

pub fn read_key() -> Result<Key, Box<dyn Error>> {
    const CTRL_Q: u8 = 17;
    const CTRL_S: u8 = 19;
    const CTRL_Z: u8 = 26;
    const BACKSPACE: u8 = 127;
    const ESC: u8 = 27;

    loop {
        let mut buf = vec![0; 1];
        stdin().read_exact(&mut buf)?;
        if buf[0] == CTRL_Q {
            return Ok(Key::CtrlQ);
        }
        if buf[0] == CTRL_S {
            return Ok(Key::CtrlS);
        }
        if buf[0] == CTRL_Z {
            return Ok(Key::CtrlZ);
        }
        if buf[0] == BACKSPACE {
            return Ok(Key::Backspace);
        }
        if buf[0] != ESC {
            return Ok(Key::Char(buf[0]));
        }
        // Try to handle an escape sequence.
        let mut buf = vec![0; 2];
        stdin().read_exact(&mut buf)?;
        if buf[0] != b'[' {
            // Unknown escape sequence, just read another key.
            continue;
        }
        match buf[1] {
            b'A' => {
                return Ok(Key::Up);
            }
            b'B' => {
                return Ok(Key::Down);
            }
            b'C' => {
                return Ok(Key::Right);
            }
            b'D' => {
                return Ok(Key::Left);
            }
            _ => {
                // Unknown key, just ignore it.
                continue;
            }
        }
    }
}