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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
use libc::{termios, winsize};
use std::error::Error;
use std::io::{stdout, BufRead, BufReader, Stdin, Write};
const ESC: u8 = 27;
pub fn size() -> Result<winsize, Box<dyn Error>> {
unsafe {
let mut size = std::mem::zeroed();
if libc::ioctl(1, libc::TIOCGWINSZ, &mut size) < 0 {
return Err(Box::from("ioctl failed"));
}
Ok(size)
}
}
pub struct RawHandle {
old_attr: termios,
}
impl Drop for RawHandle {
fn drop(&mut self) {
unsafe {
libc::tcsetattr(1, libc::TCSANOW, &self.old_attr);
}
let _ = write!(stdout(), "\x1b[?7h\x1b[2J");
let _ = stdout().flush();
}
}
pub fn raw_mode() -> Result<RawHandle, Box<dyn Error>> {
unsafe {
let mut attr = std::mem::zeroed();
if libc::tcgetattr(1, &mut attr) < 0 {
return Err(Box::from("tcgetattr failed"));
}
let mut raw = std::mem::zeroed();
libc::cfmakeraw(&mut raw);
raw.c_cc[libc::VTIME] = 1;
raw.c_cc[libc::VMIN] = 0;
if libc::tcsetattr(1, libc::TCSANOW, &raw) < 0 {
return Err(Box::from("tcsetattr failed"));
}
write!(stdout(), "\x1b[?7l")?;
stdout().flush()?;
Ok(RawHandle { old_attr: attr })
}
}
fn parse_status_report(buf: &[u8]) -> Result<(u16, u16), Box<dyn Error>> {
let mut semicolon = buf.len();
let mut r = buf.len();
for (i, &c) in buf.iter().enumerate() {
if c == b';' {
semicolon = i;
}
if c == b'R' {
r = i;
break;
}
}
if buf.len() < 6
|| buf[0] != ESC
|| buf[1] != b'['
|| semicolon < 2
|| semicolon + 1 > r
|| r != buf.len() - 1
{
return Err(Box::from("invalid response"));
}
let row = std::str::from_utf8(&buf[2..semicolon])?.parse::<u16>()?;
let col = std::str::from_utf8(&buf[semicolon + 1..r])?.parse::<u16>()?;
Ok((row - 1, col - 1))
}
pub enum Key {
Timeout,
Up,
Down,
Left,
Right,
Home,
End,
PgDn,
PgUp,
Byte(u8),
}
pub struct Reader {
stdin: BufReader<Stdin>,
// expected_status_reports counts the number of unread status reports expected to come in
// on stdin.
expected_status_reports: i64,
}
impl Reader {
pub fn new(stdin: Stdin) -> Reader {
Reader {
stdin: BufReader::new(stdin),
expected_status_reports: 0,
}
}
pub fn read_key(&mut self) -> Result<Key, Box<dyn Error>> {
loop {
let buf = self.stdin.fill_buf()?;
if buf.is_empty() {
// No buffered input, so let's give up on any status reports we were expecting.
self.expected_status_reports = 0;
return Ok(Key::Timeout);
}
if buf[0] != ESC {
let b = buf[0];
self.stdin.consume(1);
return Ok(Key::Byte(b));
}
if buf.len() < 3 || buf[1] != b'[' {
// Unknown escape sequence, eat the escape and one more byte.
self.stdin.consume(2);
continue;
}
// An escape sequence usually starts with [, then has one or two numbers separated by
// semicolon, and ends with some terminating character. To try and munch the whole
// sequence, skip over any numbers and semicolon here.
let mut n = 2;
while let b'0'..=b'9' | b';' = buf[n] {
n += 1;
if n == buf.len() - 1 {
break;
}
}
// Skip the terminating character.
n += 1;
let seq = buf[..n].to_vec();
self.stdin.consume(n);
if seq[seq.len() - 1] == b'R' && parse_status_report(&seq).is_ok() {
if self.expected_status_reports > 0 {
self.expected_status_reports -= 1;
}
continue;
}
let Ok(s) = String::from_utf8(seq) else {
continue;
};
match &s[2..] {
"A" => {
return Ok(Key::Up);
}
"B" => {
return Ok(Key::Down);
}
"C" => {
return Ok(Key::Right);
}
"D" => {
return Ok(Key::Left);
}
"H" | "1~" => {
return Ok(Key::Home);
}
"F" | "8~" => {
return Ok(Key::End);
}
"5~" => {
return Ok(Key::PgUp);
}
"6~" => {
return Ok(Key::PgDn);
}
_ => {
continue;
}
}
}
}
// cursor_pos *WILL* fail if the user is typing on the keyboard. Be sure to handle
// errors appropriately.
pub fn cursor_pos(&mut self) -> Result<(u16, u16), Box<dyn Error>> {
if !self.stdin.buffer().is_empty() || self.expected_status_reports > 0 {
// If there is any buffered input, the below call to fill_buf will just return the
// buffered data and not actually read the terminal response. Also abort if we are
// expecting any status reports from previous failed calls to cursor_pos, since we
// would read an old response and return an incorrect answer.
return Err(Box::from("interrupted"));
}
write!(stdout(), "\x1b[6n")?;
self.expected_status_reports += 1;
stdout().flush()?;
let buf = self.stdin.fill_buf()?;
let (row, col) = parse_status_report(buf)?;
let n = buf.len();
self.stdin.consume(n);
self.expected_status_reports -= 1;
Ok((row, col))
}
}
|