diff options
-rw-r--r-- | src/buffer.rs | 148 | ||||
-rw-r--r-- | src/main.rs | 14 |
2 files changed, 129 insertions, 33 deletions
diff --git a/src/buffer.rs b/src/buffer.rs index eedb694..8668249 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -4,14 +4,47 @@ use std::sync::{Arc, Mutex}; // STDIN is file descriptor (fd) 0 on Linux and other UN*X-likes pub const STDIN: i32 = 0; +// Key input types from the user +enum Key { + Up, + Down, + Right, + Left, + Else(u8), + Ignored, +} + /// Retrieve a single byte of input /// /// Requires some setup beforehand (see beginning of repl()) -fn getchar() -> u8 { +fn getchar() -> Key { let mut b = [0; 1]; io::stdout().lock().flush().unwrap(); io::stdin().read_exact(&mut b).unwrap(); - b[0] + + // Might me an ASNI escape sequence + if b[0] == 27 { + io::stdin().read_exact(&mut b).unwrap(); + + if b[0] == 91 { + io::stdin().read_exact(&mut b).unwrap(); + + match b[0] { + // Arrow keys + 65 => return Key::Up, + 66 => return Key::Down, + 67 => return Key::Right, + 68 => return Key::Left, + + // Everything else + _ => return Key::Ignored, + } + } + + return Key::Ignored; + } + + Key::Else(b[0]) } /// Handle user input at the repl prompt @@ -23,46 +56,101 @@ fn getchar() -> u8 { /// means special cases for handling backspace, newlines, etc. Assumes /// that (ICANON and ECHO) are off. See the beginning of [crate::repl] /// for more details. -pub fn getline(buffer: &mut Arc<Mutex<Vec<u8>>>) -> usize { - // Keep track of position for backspaces - let mut pos: usize = 0; - +pub fn getline(buffer: &mut Arc<Mutex<Vec<u8>>>, pos: &mut Arc<Mutex<usize>>) -> usize { // Loop over characters until there is a newline loop { let c = getchar(); match c { - // enter/return - b'\n' => break, - - // tab - b'\t' => { - pos += 1; - print!(" "); - buffer.lock().unwrap().push(b' '); + Key::Up => { + continue; } - - // ctrl-d - 4 => return 0, - - // backspace - 127 => { - if pos == 0 { + Key::Down => { + continue; + } + Key::Right => { + if *pos.lock().unwrap() >= buffer.lock().unwrap().len() { continue; } - pos -= 1; - buffer.lock().unwrap().pop(); - print!("\u{8} \u{8}"); + print!("\x1b[1C"); + *pos.lock().unwrap() += 1; } - - // everything else - _ => { - pos += 1; - print!("{}", c as char); - buffer.lock().unwrap().push(c); + Key::Left => { + if *pos.lock().unwrap() == 0 { + continue; + } + print!("\u{8}"); + *pos.lock().unwrap() -= 1; + } + Key::Ignored => { + continue; } + Key::Else(c) => match c { + // enter/return + b'\n' => break, + + // tab + b'\t' => { + *pos.lock().unwrap() += 1; + print!(" "); + buffer.lock().unwrap().push(b' '); + } + + // ctrl-d + 4 => return 0, + + // backspace + 127 => { + if *pos.lock().unwrap() == 0 { + continue; + } + *pos.lock().unwrap() -= 1; + + if *pos.lock().unwrap() == buffer.lock().unwrap().len() { + buffer.lock().unwrap().pop(); + print!("\u{8} \u{8}"); + } else { + buffer.lock().unwrap().remove(*pos.lock().unwrap()); + print!( + "\u{8}{} ", + String::from_utf8_lossy( + &buffer.lock().unwrap()[*pos.lock().unwrap()..] + ) + ); + for _ in *pos.lock().unwrap()..buffer.lock().unwrap().len() + 1 { + print!("\u{8}"); + } + } + } + + // everything else + _ => { + // Print out the character as the user is typing + print!("{}", c as char); + + // Insert the character onto the buffer at whatever *pos.lock().unwrap()ition the cursor is at + buffer.lock().unwrap().insert(*pos.lock().unwrap(), c); + + // Increment our *pos.lock().unwrap()ition + *pos.lock().unwrap() += 1; + + // Reprint the end of the buffer if inserting at the front or middle + if *pos.lock().unwrap() != buffer.lock().unwrap().len() { + print!( + "{}", + String::from_utf8_lossy( + &buffer.lock().unwrap()[*pos.lock().unwrap()..] + ) + ); + for _ in *pos.lock().unwrap()..buffer.lock().unwrap().len() { + print!("\u{8}"); + } + } + } + }, } } + *pos.lock().unwrap() = 0; println!(); buffer.lock().unwrap().push(b'\n'); buffer.lock().unwrap().len() diff --git a/src/main.rs b/src/main.rs index d5147ff..527fb8c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,12 @@ use termios::{tcsetattr, Termios, ECHO, ICANON, TCSANOW}; /// repl(&mut away, &mut env); /// } /// ``` -fn repl(away: &mut Arc<Mutex<bool>>, buffer: &mut Arc<Mutex<Vec<u8>>>, env: &mut Environment) { +fn repl( + away: &mut Arc<Mutex<bool>>, + buffer: &mut Arc<Mutex<Vec<u8>>>, + pos: &mut Arc<Mutex<usize>>, + env: &mut Environment, +) { // Setup termios flags let mut termios = Termios::from_fd(STDIN).unwrap(); termios.c_lflag &= !(ICANON | ECHO); @@ -55,7 +60,7 @@ fn repl(away: &mut Arc<Mutex<bool>>, buffer: &mut Arc<Mutex<Vec<u8>>>, env: &mut *away.lock().unwrap() = false; // Wait for user input - let bytes = getline(buffer); + let bytes = getline(buffer, pos); // Check if we've reached EOF (i.e. <C-d>) if bytes == 0 { @@ -145,11 +150,14 @@ fn main() { // Handle signals let mut away = Arc::new(Mutex::new(true)); let mut buffer: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(vec![])); + let mut pos: Arc<Mutex<usize>> = Arc::new(Mutex::new(0)); unsafe { let away = Arc::clone(&away); let buffer = Arc::clone(&buffer); + let pos = Arc::clone(&pos); signal_hook::low_level::register(signal_hook::consts::SIGINT, move || { buffer.lock().unwrap().clear(); + *pos.lock().unwrap() = 0; if *away.lock().unwrap() { println!(); } else { @@ -168,5 +176,5 @@ fn main() { options(&mut env); // Begin evaluating commands - repl(&mut away, &mut buffer, &mut env); + repl(&mut away, &mut buffer, &mut pos, &mut env); } |