kiro_editor/
input.rs

1use crate::error::{Error, Result};
2use std::fmt;
3use std::io::{self, Read};
4use std::ops::{Deref, DerefMut};
5use std::os::unix::io::AsRawFd;
6use std::str;
7
8pub struct StdinRawMode {
9    stdin: io::Stdin,
10    orig: termios::Termios,
11}
12
13// TODO: Separate editor into frontend and backend. In frontend, it handles actual screen and user input.
14// It interacts with backend by responding to request from frontend. Frontend focues on core editor
15// logic. This is useful when adding a new frontend (e.g. wasm).
16
17impl StdinRawMode {
18    pub fn new() -> Result<StdinRawMode> {
19        use termios::*;
20
21        let stdin = io::stdin();
22        let fd = stdin.as_raw_fd();
23        let mut termios = Termios::from_fd(fd)?;
24        let orig = termios;
25
26        // Set terminal raw mode. Disable echo back, canonical mode, signals (SIGINT, SIGTSTP) and Ctrl+V.
27        termios.c_lflag &= !(ECHO | ICANON | ISIG | IEXTEN);
28        // Disable control flow mode (Ctrl+Q/Ctrl+S) and CR-to-NL translation
29        termios.c_iflag &= !(IXON | ICRNL | BRKINT | INPCK | ISTRIP);
30        // Disable output processing such as \n to \r\n translation
31        termios.c_oflag &= !OPOST;
32        // Ensure character size is 8bits
33        termios.c_cflag |= CS8;
34        // Implement blocking read for efficient reading of input
35        termios.c_cc[VMIN] = 1;
36        termios.c_cc[VTIME] = 0;
37        // Apply terminal configurations
38        tcsetattr(fd, TCSAFLUSH, &termios)?;
39
40        Ok(StdinRawMode { stdin, orig })
41    }
42
43    pub fn input_keys(self) -> InputSequences {
44        InputSequences { stdin: self }
45    }
46}
47
48impl Drop for StdinRawMode {
49    fn drop(&mut self) {
50        // Restore original terminal mode
51        termios::tcsetattr(self.stdin.as_raw_fd(), termios::TCSAFLUSH, &self.orig).unwrap();
52    }
53}
54
55impl Deref for StdinRawMode {
56    type Target = io::Stdin;
57
58    fn deref(&self) -> &Self::Target {
59        &self.stdin
60    }
61}
62
63impl DerefMut for StdinRawMode {
64    fn deref_mut(&mut self) -> &mut Self::Target {
65        &mut self.stdin
66    }
67}
68
69#[derive(PartialEq, Debug, Clone)]
70pub enum KeySeq {
71    Unidentified,
72    Utf8Key(char),
73    Key(u8), // Char code and ctrl mod
74    LeftKey,
75    RightKey,
76    UpKey,
77    DownKey,
78    PageUpKey,
79    PageDownKey,
80    HomeKey,
81    EndKey,
82    DeleteKey,
83    Cursor(usize, usize), // Pseudo key (x, y)
84}
85
86impl fmt::Display for KeySeq {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        use KeySeq::*;
89        match self {
90            Unidentified => write!(f, "UNKNOWN"),
91            Key(b' ') => write!(f, "SPACE"),
92            Key(b) if b.is_ascii_control() => write!(f, "\\x{:x}", b),
93            Key(b) => write!(f, "{}", *b as char),
94            Utf8Key(c) => write!(f, "{}", c),
95            LeftKey => write!(f, "LEFT"),
96            RightKey => write!(f, "RIGHT"),
97            UpKey => write!(f, "UP"),
98            DownKey => write!(f, "DOWN"),
99            PageUpKey => write!(f, "PAGEUP"),
100            PageDownKey => write!(f, "PAGEDOWN"),
101            HomeKey => write!(f, "HOME"),
102            EndKey => write!(f, "END"),
103            DeleteKey => write!(f, "DELETE"),
104            Cursor(r, c) => write!(f, "CURSOR({},{})", r, c),
105        }
106    }
107}
108
109#[derive(PartialEq, Debug, Clone)]
110pub struct InputSeq {
111    pub key: KeySeq,
112    pub ctrl: bool,
113    pub alt: bool,
114}
115
116impl InputSeq {
117    pub fn new(key: KeySeq) -> Self {
118        Self {
119            key,
120            ctrl: false,
121            alt: false,
122        }
123    }
124
125    pub fn ctrl(key: KeySeq) -> Self {
126        Self {
127            key,
128            ctrl: true,
129            alt: false,
130        }
131    }
132
133    pub fn alt(key: KeySeq) -> Self {
134        Self {
135            key,
136            ctrl: false,
137            alt: true,
138        }
139    }
140}
141
142impl fmt::Display for InputSeq {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        if self.ctrl {
145            write!(f, "C-")?;
146        }
147        if self.alt {
148            write!(f, "M-")?;
149        }
150        write!(f, "{}", self.key)
151    }
152}
153
154pub struct InputSequences {
155    stdin: StdinRawMode,
156}
157
158impl InputSequences {
159    fn read_byte(&mut self) -> Result<Option<u8>> {
160        let mut one_byte: [u8; 1] = [0];
161        Ok(if self.stdin.read(&mut one_byte)? == 0 {
162            None
163        } else {
164            Some(one_byte[0])
165        })
166    }
167
168    fn decode_escape_sequence(&mut self) -> Result<InputSeq> {
169        use KeySeq::*;
170
171        // Try to read expecting '[' as escape sequence header. Note that, if next input does
172        // not arrive within next tick, it means that it is not an escape sequence.
173        // TODO?: Should we consider sequences not starting with '['?
174        match self.read_byte()? {
175            Some(b'[') => { /* fall through */ }
176            Some(b) if b.is_ascii_control() => return Ok(InputSeq::new(Key(0x1b))), // Ignore control characters after ESC
177            Some(b) => {
178                // Alt key is sent as ESC prefix (e.g. Alt-A => \x1b\x61
179                // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Alt-and-Meta-Keys
180                let mut seq = self.decode(b)?;
181                seq.alt = true;
182                return Ok(seq);
183            }
184            None => return Ok(InputSeq::new(Key(0x1b))),
185        };
186
187        // Now confirmed \1xb[ which is a header of escape sequence. Eat it until the end
188        // of sequence with blocking
189        let mut buf = vec![];
190        let cmd = loop {
191            if let Some(b) = self.read_byte()? {
192                match b {
193                    // Control command chars from http://ascii-table.com/ansi-escape-sequences-vt-100.php
194                    b'A' | b'B' | b'C' | b'D' | b'F' | b'H' | b'K' | b'J' | b'R' | b'c' | b'f'
195                    | b'g' | b'h' | b'l' | b'm' | b'n' | b'q' | b't' | b'y' | b'~' => break b,
196                    _ => buf.push(b),
197                }
198            } else {
199                // Unknown escape sequence ignored
200                return Ok(InputSeq::new(Unidentified));
201            }
202        };
203
204        fn parse_bytes_as_usize(b: &[u8]) -> Option<usize> {
205            str::from_utf8(b).ok().and_then(|s| s.parse().ok())
206        }
207
208        let mut args = buf.split(|b| *b == b';');
209        match cmd {
210            b'R' => {
211                // https://vt100.net/docs/vt100-ug/chapter3.html#CPR e.g. \x1b[24;80R
212                let mut i = args.filter_map(parse_bytes_as_usize);
213                match (i.next(), i.next()) {
214                    (Some(r), Some(c)) => Ok(InputSeq::new(Cursor(r, c))),
215                    _ => Ok(InputSeq::new(Unidentified)),
216                }
217            }
218            // e.g. <LEFT> => \x1b[C
219            // e.g. C-<LEFT> => \x1b[1;5C
220            b'A' | b'B' | b'C' | b'D' => {
221                let key = match cmd {
222                    b'A' => UpKey,
223                    b'B' => DownKey,
224                    b'C' => RightKey,
225                    b'D' => LeftKey,
226                    _ => unreachable!(),
227                };
228                let ctrl = args.next() == Some(b"1") && args.next() == Some(b"5");
229                let alt = false;
230                Ok(InputSeq { key, ctrl, alt })
231            }
232            b'~' => {
233                // e.g. \x1b[5~
234                match args.next() {
235                    Some(b"5") => Ok(InputSeq::new(PageUpKey)),
236                    Some(b"6") => Ok(InputSeq::new(PageDownKey)),
237                    Some(b"1") | Some(b"7") => Ok(InputSeq::new(HomeKey)),
238                    Some(b"4") | Some(b"8") => Ok(InputSeq::new(EndKey)),
239                    Some(b"3") => Ok(InputSeq::new(DeleteKey)),
240                    _ => Ok(InputSeq::new(Unidentified)),
241                }
242            }
243            b'H' | b'F' => {
244                // C-HOME => \x1b[1;5H
245                let key = match cmd {
246                    b'H' => HomeKey,
247                    b'F' => EndKey,
248                    _ => unreachable!(),
249                };
250                let ctrl = args.next() == Some(b"1") && args.next() == Some(b"5");
251                let alt = false;
252                Ok(InputSeq { key, ctrl, alt })
253            }
254            _ => unreachable!(),
255        }
256    }
257
258    fn decode_utf8(&mut self, b: u8) -> Result<InputSeq> {
259        // TODO: Use arrayvec crate
260        let mut buf = [0; 4];
261        buf[0] = b;
262        let mut len = 1;
263
264        loop {
265            if let Some(b) = self.read_byte()? {
266                buf[len] = b;
267                len += 1;
268            } else {
269                return Err(Error::NotUtf8Input(buf[..len].to_vec()));
270            }
271
272            if let Ok(s) = str::from_utf8(&buf) {
273                return Ok(InputSeq::new(KeySeq::Utf8Key(s.chars().next().unwrap())));
274            }
275
276            if len == 4 {
277                return Err(Error::NotUtf8Input(buf.to_vec()));
278            }
279        }
280    }
281
282    fn decode(&mut self, b: u8) -> Result<InputSeq> {
283        use KeySeq::*;
284        match b {
285            // C0 control characters
286            0x00..=0x1f => match b {
287                // (Maybe) Escape sequence. Ctrl-{ is not available due to this
288                0x1b => self.decode_escape_sequence(),
289                // Ctrl-SPACE and Ctrl-?. 0x40, 0x3f, 0x60, 0x5f are not available
290                0x00 | 0x1f => Ok(InputSeq::ctrl(Key(b | 0b0010_0000))),
291                // Ctrl-\ and Ctrl-]. 0x3c, 0x3d, 0x7c, 0x7d are not available
292                0x01c | 0x01d => Ok(InputSeq::ctrl(Key(b | 0b0100_0000))),
293                // 0x00~0x1f keys are ascii keys with ctrl. Ctrl mod masks key with 0b11111.
294                // Here unmask it with 0b1100000. It only works with 0x61~0x7f.
295                _ => Ok(InputSeq::ctrl(Key(b | 0b0110_0000))),
296            },
297            // Printable ASCII characters (0x20..0x7f)
298            // Ascii key inputs
299            0x20..=0x7f => Ok(InputSeq::new(Key(b))),
300            // C1 control characters https://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_controls
301            // XXX: Kiro ignores them since I have no idea how to handle them on terminal. Perhaps
302            // SSA and ESA can be handled as selecting region.
303            0x80..=0x9f => Ok(InputSeq::new(KeySeq::Unidentified)),
304            // Other UTF-8 characters
305            0xa0..=0xff => self.decode_utf8(b),
306        }
307    }
308
309    fn read_seq(&mut self) -> Result<InputSeq> {
310        if let Some(b) = self.read_byte()? {
311            self.decode(b)
312        } else {
313            Ok(InputSeq::new(KeySeq::Unidentified))
314        }
315    }
316}
317
318impl Iterator for InputSequences {
319    type Item = Result<InputSeq>;
320
321    // Read next byte from stdin, if nothing was read, it returns InputSeq::Unidentified.
322    // This method never returns None so for loop never ends.
323    fn next(&mut self) -> Option<Self::Item> {
324        Some(self.read_seq())
325    }
326}