extern crate termion; use termion::event::Key; use termion::input::TermRead; use termion::raw::IntoRawMode; use std::io::Write; use std::io; // use std::fs; use std::collections::HashMap; type Entry = f64; const PROMPT_STR: &str = "> "; const BEEP_STR: &str = "\x07"; fn min(a: T, b: T) -> T { if b < a {b} else {a} } fn draw_entries(entries: &[Entry], up: isize) { assert!(up < 1<<16); if up < 0 { print!("{}", termion::cursor::Down((-up) as u16)); } else if up > 0 { print!("{}", termion::cursor::Up(up as u16)); } print!("\r"); let mut first = true; for entry in entries { if first {first = false;} else {print!("\n\r");} print!("{}{}{}", " ".repeat(PROMPT_STR.len()), entry, termion::clear::UntilNewline); } } fn valid_input_char(c: char) -> bool { c.is_digit(10) || c == '.' || c == 'e' } type Callback = Box<(Fn(&mut Vec) -> usize + 'static)>; fn mk_callback(f: F) -> Callback where F: Fn(&mut Vec) -> usize + 'static { Box::new(f) as Callback } fn stk_binop(f: F) -> Callback where F: Fn(Entry, Entry) -> Entry + 'static { mk_callback(move |stk| { let v2 = stk.pop().unwrap(); let v1 = stk.pop().unwrap(); stk.push(f(v1,v2)); 1 }) } struct State<'a> { stk: Vec, entries_drawn: usize, term_height: usize, input_buffer: String, input_cursor: usize, command_map: &'a HashMap, } fn handle_key(key: Key, st: &mut State) -> Option { match key { // Quitting Key::Ctrl('c') | Key::Ctrl('d') => { if st.input_buffer.len() > 0 { print!("\n\r"); } else { print!("\r{}", termion::clear::CurrentLine); } return Some(false); } // Moving around Key::Ctrl('a') => { print!("\r{}", termion::cursor::Right(PROMPT_STR.len() as u16)); st.input_cursor = 0; } Key::Ctrl('e') => { print!("\r{}", termion::cursor::Right((PROMPT_STR.len() + st.input_buffer.len()) as u16)); st.input_cursor = st.input_buffer.len(); } Key::Left => { if st.input_cursor > 0 { print!("{}", termion::cursor::Left(1)); st.input_cursor -= 1; } else { return None; } } Key::Right => { if st.input_cursor < st.input_buffer.len() { print!("{}", termion::cursor::Right(1)); st.input_cursor += 1; } else { return None; } } // Editing Key::Ctrl('u') => { st.input_buffer.clear(); print!("\r{}{}", termion::clear::CurrentLine, PROMPT_STR); st.input_cursor = 0; } Key::Backspace => { if st.input_cursor == 0 { return None; } st.input_cursor -= 1; st.input_buffer.remove(st.input_cursor); let rest = &st.input_buffer[st.input_cursor..]; print!("{}{}{}", termion::cursor::Left(1), rest, termion::clear::UntilNewline); if rest.len() > 0 { print!("{}", termion::cursor::Left(rest.len() as u16)); } } Key::Delete => { if st.input_buffer.len() == 0 { if st.stk.len() == 0 { return None; } if st.entries_drawn < st.stk.len() { draw_entries(&st.stk[(st.stk.len() - st.entries_drawn - 1)..(st.stk.len() - 1)], st.entries_drawn as isize); st.input_buffer = match st.stk.last() { None => String::new(), Some(val) => val.to_string(), }; print!("\n\r{}{}", PROMPT_STR, st.input_buffer); st.input_cursor = st.input_buffer.len(); } else { st.entries_drawn -= 1; st.input_buffer = match st.stk.last() { None => String::new(), Some(val) => val.to_string(), }; st.input_cursor = st.input_buffer.len(); print!("{}{}\r{}{}", termion::clear::CurrentLine, termion::cursor::Up(1), PROMPT_STR, termion::cursor::Right(st.input_cursor as u16)) } st.stk.pop(); } else if st.input_cursor == st.input_buffer.len() { return None; } else { st.input_buffer.remove(st.input_cursor); let rest = &st.input_buffer[st.input_cursor..]; print!("{}{}", rest, termion::clear::UntilNewline); if rest.len() > 0 { print!("{}", termion::cursor::Left(rest.len() as u16)); } } } // Inputting Key::Char(c) if valid_input_char(c) => { st.input_buffer.insert(st.input_cursor, c); let rest = &st.input_buffer[st.input_cursor..]; print!("{}", rest); if rest.len() > 1 { print!("{}", termion::cursor::Left((rest.len() - 1) as u16)); } st.input_cursor += 1; } // Submitting Key::Char('\n') | Key::Char('\r') => { match st.input_buffer.parse::() { Ok(val) => { st.stk.push(val); print!("{}\r{}{}\n\r{}", termion::clear::CurrentLine, " ".repeat(PROMPT_STR.len()), val, PROMPT_STR); st.input_buffer.clear(); st.input_cursor = 0; if st.entries_drawn < st.term_height - 1 { st.entries_drawn += 1; } } Err(_) => return None, } } // Command Key::Char(c) | Key::Alt(c) => { let mut delay_lf = 0; if st.input_buffer.len() != 0 { match st.input_buffer.parse::() { Ok(val) => { st.stk.push(val); print!("{}\r{}{}", termion::clear::CurrentLine, " ".repeat(PROMPT_STR.len()), val); st.input_buffer.clear(); st.input_cursor = 0; if st.entries_drawn < st.term_height - 1 { st.entries_drawn += 1; } delay_lf = 1; } Err(_) => return None, } } let name = String::from(c.to_string()); if let Some(&(nargs, ref f)) = st.command_map.get(&name) { if st.stk.len() < nargs { return None; } let prevlen = st.stk.len(); let npushed = f(&mut st.stk); let sizeincr = st.stk.len() as isize - prevlen as isize; assert!(sizeincr < 0 || npushed >= sizeincr as usize); let mut print_newline = true; if sizeincr >= 0 { // Stack grew let usizeincr = sizeincr as usize; draw_entries(&st.stk[(st.stk.len() - npushed)..], npushed as isize - sizeincr - delay_lf); st.entries_drawn = min(st.entries_drawn + usizeincr, st.term_height - 1); } else if prevlen >= st.term_height && st.stk.len() < st.term_height { // Didn't fit before, but fits now draw_entries(&st.stk[..], st.stk.len() as isize); st.entries_drawn = st.stk.len(); } else if prevlen < st.term_height { // Fit before, and still fits assert!(st.stk.len() < st.term_height); draw_entries(&st.stk[(st.stk.len() - npushed)..], (-sizeincr) + npushed as isize - delay_lf); st.entries_drawn = st.stk.len(); if npushed == 0 {print_newline = false;} } else { // Didn't fit before, and still doesn't fit assert!(prevlen >= st.term_height && st.stk.len() >= st.term_height); if sizeincr + delay_lf == 0 { let up = min(npushed, st.term_height - 1); draw_entries(&st.stk[(st.stk.len() - up)..], up as isize); } else { draw_entries(&st.stk[(st.stk.len() - st.term_height + 1)..], st.term_height as isize - 1); } st.entries_drawn = st.term_height - 1; } if print_newline {print!("\n\r");} print!("{}{}", termion::clear::AfterCursor, PROMPT_STR); } else { return None; } } // Otherwise _ => return None, } Some(true) } fn main() { let command_map = { let mut h = HashMap::new(); h.insert(String::from("+"), (2, stk_binop(|a,b| a+b))); h.insert(String::from("-"), (2, stk_binop(|a,b| a-b))); h.insert(String::from("*"), (2, stk_binop(|a,b| a*b))); h.insert(String::from("/"), (2, stk_binop(|a,b| a/b))); h.insert(String::from("m"), (1, mk_callback(|stk| { let v = stk.pop().unwrap(); stk.push(-v); 1 }))); h.insert(String::from("s"), (2, mk_callback(|stk| { let v = stk.pop().unwrap(); let len = stk.len(); stk.insert(len - 1, v); 2 }))); h.insert(String::from("d"), (1, mk_callback(|stk| { let v = stk[stk.len() - 1]; stk.push(v); 2 }))); h.insert(String::from("D"), (1, mk_callback(|stk| { stk.pop(); 0 }))); h }; let mut st = State { stk: Vec::::new(), entries_drawn: 0, term_height: 0, input_buffer: String::new(), input_cursor: 0, command_map: &command_map, }; let stdin = io::stdin(); let mut stdout = io::stdout().into_raw_mode().unwrap(); print!("{}", PROMPT_STR); stdout.flush().unwrap(); for key in stdin.keys() { let new_term_height = termion::terminal_size().unwrap().1 as usize; if new_term_height < st.term_height { st.entries_drawn = min(new_term_height - 1, st.stk.len()); } else if new_term_height > st.term_height { let extra_num = min(new_term_height - st.term_height, st.stk.len() - st.entries_drawn); if extra_num > 0 { let idx = st.stk.len() - st.entries_drawn - extra_num; draw_entries(&st.stk[idx..], (st.stk.len() - st.entries_drawn) as isize); st.entries_drawn += extra_num; } } st.term_height = new_term_height; match handle_key(key.unwrap(), &mut st) { None => print!("{}", BEEP_STR), Some(true) => {}, Some(false) => break, } stdout.flush().unwrap(); } }