pyc_shell/utils/
console.rs

1//! ## Console
2//!
3//! `Console` module provides an API for the terminal console
4
5/*
6*
7*   Copyright (C) 2020 Christian Visintin - christian.visintin1997@gmail.com
8*
9* 	This file is part of "Pyc"
10*
11*   Pyc is free software: you can redistribute it and/or modify
12*   it under the terms of the GNU General Public License as published by
13*   the Free Software Foundation, either version 3 of the License, or
14*   (at your option) any later version.
15*
16*   Pyc is distributed in the hope that it will be useful,
17*   but WITHOUT ANY WARRANTY; without even the implied warranty of
18*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19*   GNU General Public License for more details.
20*
21*   You should have received a copy of the GNU General Public License
22*   along with Pyc.  If not, see <http://www.gnu.org/licenses/>.
23*
24*/
25extern crate nix;
26extern crate termios;
27
28use std::io::{self, Read, Write};
29use std::os::unix::io::RawFd;
30
31const STDIN_FILENO: RawFd = 0;
32
33/// ## InputEvent
34/// 
35/// InputEvent enum represents an Input Event got from user on a read call
36#[derive(std::fmt::Debug, std::cmp::PartialEq)]
37pub enum InputEvent {
38    Key(String),
39    Ctrl(u8),
40    Enter,
41    CarriageReturn,
42    Backspace,
43    ArrowUp,
44    ArrowLeft,
45    ArrowRight,
46    ArrowDown
47}
48
49
50/// ### backspace
51/// 
52/// Remove last typed character from prompt
53pub fn backspace() {
54    //To backspace we have to go back of 1 position, print blank and go back again
55    print(String::from("\x08 \x08"));
56}
57
58pub fn move_cursor_right() {
59    print(String::from("\x1b[1C"));
60}
61
62pub fn move_cursor_left() {
63    print(String::from("\x1b[1D"));
64}
65
66/// ### carriage_return
67/// 
68/// Return to the beginning of the line
69pub fn carriage_return() {
70    print(String::from("\r"));
71}
72
73/// ### clear
74/// 
75/// Clear console
76pub fn clear() {
77    print(String::from("\x1b[H\x1b[2J"));
78}
79
80/// ### read
81/// 
82/// Read user input and returns an individual InputEvent (or None)
83pub fn read() -> Option<InputEvent> {
84    let stdin_read = |buff: &mut [u8]| -> io::Result<()> {
85        io::stdin().read_exact(buff)
86    };
87    prepare_termios();
88    let ev: Option<InputEvent> = to_input_event(&input_ready, &stdin_read);
89    reset_termios();
90    ev
91}
92
93/// ### to_input_event
94/// 
95/// Get input through callback and convert it to an Input Event
96fn to_input_event(ready_fn: &dyn Fn() -> bool, read_fn: &dyn Fn(&mut [u8]) -> io::Result<()>) -> Option<InputEvent> {
97    //Configure terminal
98    match ready_fn() {
99        false => None,
100        true => {
101            //Read
102            let mut buf: Vec<u8> = vec![0u8; 1];
103            let _ = read_fn(&mut buf);
104            //Handle input
105            let key: u8 = *buf.get(0).unwrap_or(&0);
106            let ev: InputEvent = match key {
107                8 | 127 => InputEvent::Backspace,
108                10 => InputEvent::Enter,
109                13 => InputEvent::CarriageReturn,
110                0..=26 => InputEvent::Ctrl(key), //CTRL key (exclude 8, 10, 13)
111                27 => { //Is Arrow Key
112                    //Read twice
113                    let _ = read_fn(&mut buf);
114                    let _ = read_fn(&mut buf);
115                    let direction: char = *buf.get(0).unwrap_or(&0) as char;
116                    match direction {
117                        'A' => InputEvent::ArrowUp,
118                        'B' => InputEvent::ArrowDown,
119                        'C' => InputEvent::ArrowRight,
120                        'D' => InputEvent::ArrowLeft,
121                        _ => return None //Unknown event
122                    }
123                },
124                _ => { //Handle normal key
125                    //@! Read until it's a valid UTF8 string
126                    //NOTE: 4 is the maximum amount of bytes used by a UTF-8
127                    let mut utfbuffer: [u8; 4] = [0; 4];
128                    let mut buff_index: usize = 0;
129                    let mut keystr: Option<String> = None;
130                    loop {
131                        //Copy last character into utf buffer
132                        if buff_index >= 4 { //Overflow
133                            break
134                        }
135                        utfbuffer[buff_index] = *buf.get(0).unwrap_or(&0);
136                        buff_index += 1;
137                        //Check if utf buffer is a valid utf8 string
138                        match std::str::from_utf8(&utfbuffer[0..buff_index]) { //If buffer is a valid
139                            Ok(key) => {
140                                keystr = Some(String::from(key));
141                                break
142                            },
143                            Err(_) => { //If not valid...
144                                if let Err(_) = read_fn(&mut buf) {
145                                    break
146                                }
147                                continue
148                            }
149                        };
150                    }
151                    match keystr {
152                        Some(s) => InputEvent::Key(s),
153                        None => return None //Unknown key
154                    }
155                }
156            };
157            Some(ev)
158        }
159    }
160}
161
162/// ### rewrite
163/// 
164/// Rewrite current stdout line
165pub fn rewrite(row: String, len: usize) {
166    for _ in 0..len {
167        backspace();
168    }
169    print(row);
170}
171
172/// ### print
173/// 
174/// print on this line without newline
175pub fn print(row: String) {
176    print!("{}", row);
177    let _ = io::stdout().flush();
178}
179
180/// ### println
181/// 
182/// Print line and go to new line
183pub fn println(row: String) {
184    println!("{}", row);
185}
186
187/// ### input_ready
188/// 
189/// Returns whether stdin is ready to be read
190fn input_ready() -> bool {
191    prepare_termios();
192    let mut poll_fds: [nix::poll::PollFd; 1] = [nix::poll::PollFd::new(STDIN_FILENO, nix::poll::PollFlags::POLLIN | nix::poll::PollFlags::POLLRDBAND | nix::poll::PollFlags::POLLHUP)];
193    let ready: bool = match nix::poll::poll(&mut poll_fds, 100) {
194        Ok(ret) => {
195            if ret > 0 && poll_fds[0].revents().is_some() { //Stdin is available to be read
196                let event: nix::poll::PollFlags = poll_fds[0].revents().unwrap();
197                if event.intersects(nix::poll::PollFlags::POLLIN) || event.intersects(nix::poll::PollFlags::POLLRDBAND) {
198                    true
199                } else {
200                    false
201                }
202            } else {
203                false
204            }
205        },
206        Err(_) => false
207    };
208    reset_termios();
209    ready
210}
211
212/// ### prepare_termios
213/// 
214/// Prepare termios for console
215fn prepare_termios() {
216    let mut term = termios::Termios::from_fd(STDIN_FILENO).unwrap();
217    let _ = termios::tcgetattr(STDIN_FILENO, &mut term);
218    term.c_lflag &= !termios::ICANON;
219    term.c_lflag &= !termios::ECHO;
220    let _ = termios::tcsetattr(STDIN_FILENO, termios::TCSANOW, &term);
221}
222
223/// ### reset_termios
224/// 
225/// Restore previous termios configuration
226fn reset_termios() {
227    let mut term = termios::Termios::from_fd(STDIN_FILENO).unwrap();
228    let _ = termios::tcgetattr(STDIN_FILENO, &mut term);
229    term.c_lflag |= termios::ICANON;
230    term.c_lflag &= termios::ECHO;
231    let _ = termios::tcsetattr(STDIN_FILENO, termios::TCSADRAIN, &term);
232}
233
234/// ### input_event_to_string
235/// 
236/// Converts an input event to a string
237pub fn input_event_to_string(ev: InputEvent) -> String {
238    match ev {
239        InputEvent::ArrowDown => String::from("\x1b[B"),
240        InputEvent::ArrowLeft => String::from("\x1b[D"),
241        InputEvent::ArrowRight => String::from("\x1b[C"),
242        InputEvent::ArrowUp => String::from("\x1b[A"),
243        InputEvent::Backspace => String::from("\x7F"),
244        InputEvent::CarriageReturn => String::from("\x0D"),
245        InputEvent::Ctrl(sig) => {
246            let ch = sig as char;
247            let mut s = String::new();
248            s.push(ch);
249            s
250        },
251        InputEvent::Enter => String::from("\x0A"),
252        InputEvent::Key(k) => String::from(k)
253    }
254}
255
256#[cfg(test)]
257mod tests {
258
259    use super::*;
260
261    #[test]
262    fn test_utils_console_backspace() {
263        backspace();
264    }
265
266    #[test]
267    fn test_utils_console_move_cursor() {
268        move_cursor_left();
269        move_cursor_right();
270        carriage_return();
271    }
272
273    #[test]
274    fn test_utils_console_clear() {
275        clear();
276    }
277
278    #[test]
279    fn test_utils_console_print() {
280        print(String::from("foo"));
281        rewrite(String::from("Foobar"), 3);
282        println(String::from("bar"));
283    }
284
285    #[test]
286    fn test_utils_console_input_ready() {
287        assert_eq!(input_ready(), false);
288    }
289    
290    #[test]
291    fn test_utils_console_termios() {
292        prepare_termios();
293        reset_termios();
294    }
295
296    #[test]
297    fn test_utils_console_read() {
298        assert!(read().is_none());
299        //Test read - input ready false
300        let ready_fn = || -> bool {
301            false
302        };
303        let read_fn = |_buff: &mut [u8]| -> io::Result<()> {
304            Ok(())
305        };
306        assert!(to_input_event(&ready_fn, &read_fn).is_none());
307        //Teast read - Backspace
308        let ready_fn = || -> bool {
309            true
310        };
311        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
312            buff[0] = 127;
313            Ok(())
314        };
315        assert_eq!(to_input_event(&ready_fn, &read_fn).unwrap(), InputEvent::Backspace);
316        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
317            buff[0] = 8;
318            Ok(())
319        };
320        assert_eq!(to_input_event(&ready_fn, &read_fn).unwrap(), InputEvent::Backspace);
321        //Test read - enter
322        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
323            buff[0] = 10;
324            Ok(())
325        };
326        assert_eq!(to_input_event(&ready_fn, &read_fn).unwrap(), InputEvent::Enter);
327        //Test read - Carriage return
328        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
329            buff[0] = 13;
330            Ok(())
331        };
332        assert_eq!(to_input_event(&ready_fn, &read_fn).unwrap(), InputEvent::CarriageReturn);
333        //Test read - Ctrl key
334        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
335            buff[0] = 3;
336            Ok(())
337        };
338        assert_eq!(to_input_event(&ready_fn, &read_fn).unwrap(), InputEvent::Ctrl(3));
339        //Test read - Arrow key
340        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
341            let curr_value: u8 = buff[0];
342            match curr_value {
343                91 => buff[0] = 'A' as u8,
344                27 => buff[0] = 91,
345                _ => buff[0] = 27
346            }
347            Ok(())
348        };
349        assert_eq!(to_input_event(&ready_fn, &read_fn).unwrap(), InputEvent::ArrowUp);
350        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
351            let curr_value: u8 = buff[0];
352            match curr_value {
353                91 => buff[0] = 'B' as u8,
354                27 => buff[0] = 91,
355                _ => buff[0] = 27
356            }
357            Ok(())
358        };
359        assert_eq!(to_input_event(&ready_fn, &read_fn).unwrap(), InputEvent::ArrowDown);
360        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
361            let curr_value: u8 = buff[0];
362            match curr_value {
363                91 => buff[0] = 'C' as u8,
364                27 => buff[0] = 91,
365                _ => buff[0] = 27
366            }
367            Ok(())
368        };
369        assert_eq!(to_input_event(&ready_fn, &read_fn).unwrap(), InputEvent::ArrowRight);
370        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
371            let curr_value: u8 = buff[0];
372            match curr_value {
373                91 => buff[0] = 'D' as u8,
374                27 => buff[0] = 91,
375                _ => buff[0] = 27
376            }
377            Ok(())
378        };
379        assert_eq!(to_input_event(&ready_fn, &read_fn).unwrap(), InputEvent::ArrowLeft);
380        //Unknown Arrow
381        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
382            let curr_value: u8 = buff[0];
383            match curr_value {
384                91 => buff[0] = 'E' as u8,
385                27 => buff[0] = 91,
386                _ => buff[0] = 27
387            }
388            Ok(())
389        };
390        assert!(to_input_event(&ready_fn, &read_fn).is_none());
391        //Test read - ASCII key
392        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
393            buff[0] = 'A' as u8;
394            Ok(())
395        };
396        assert_eq!(to_input_event(&ready_fn, &read_fn).unwrap(), InputEvent::Key(String::from("A")));
397        //Test read - UTF8 (п)
398        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
399            let curr_value: u8 = buff[0];
400            match curr_value {
401                0xd0 => buff[0] = 0xbf,
402                _ => buff[0] = 0xd0
403            }
404            Ok(())
405        };
406        assert_eq!(to_input_event(&ready_fn, &read_fn).unwrap(), InputEvent::Key(String::from("п")));
407        //Test read - UTF8 (😂)
408        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
409            let curr_value: u8 = buff[0];
410            match curr_value {
411                0x98 => buff[0] = 0x82,
412                0x9f => buff[0] = 0x98,
413                0xf0 => buff[0] = 0x9f,
414                _ => buff[0] = 0xf0
415            }
416            Ok(())
417        };
418        assert_eq!(to_input_event(&ready_fn, &read_fn).unwrap(), InputEvent::Key(String::from("😂")));
419        //Unknown key
420        let read_fn = |buff: &mut [u8]| -> io::Result<()> {
421            let curr_value: u8 = buff[0];
422            match curr_value {
423                0x98 => buff[0] = 0xff,
424                0x9f => buff[0] = 0x98,
425                0xf0 => buff[0] = 0x9f,
426                _ => buff[0] = 0xf0
427            }
428            Ok(())
429        };
430        assert!(to_input_event(&ready_fn, &read_fn).is_none());
431    }
432
433    #[test]
434    fn test_utils_console_input_event_to_str() {
435        assert_eq!(input_event_to_string(InputEvent::ArrowDown), String::from("\x1b[B"));
436        assert_eq!(input_event_to_string(InputEvent::ArrowLeft), String::from("\x1b[D"));
437        assert_eq!(input_event_to_string(InputEvent::ArrowRight), String::from("\x1b[C"));
438        assert_eq!(input_event_to_string(InputEvent::ArrowUp), String::from("\x1b[A"));
439        assert_eq!(input_event_to_string(InputEvent::Backspace), String::from("\x7F"));
440        assert_eq!(input_event_to_string(InputEvent::CarriageReturn), String::from("\x0D"));
441        assert_eq!(input_event_to_string(InputEvent::Ctrl(3)), String::from("\x03"));
442        assert_eq!(input_event_to_string(InputEvent::Enter), String::from("\x0A"));
443        assert_eq!(input_event_to_string(InputEvent::Key(String::from("A"))), String::from("A"));
444    }
445
446}