diff --git a/src/main.rs b/src/main.rs index 0a6797b..4f009f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use std::io::{Write, stdout, stdin}; + use termion::event::Key; use termion::input::TermRead; use termion::raw::IntoRawMode; @@ -6,24 +7,38 @@ use termion::raw::RawTerminal; use termion::{color, style}; mod parser; +mod promptbuffer; +use crate::promptbuffer::PromptBuffer; + use crate::parser::Token; //use crate::parser::ParserError; use crate::parser::LineLocation; use crate::parser::Eval; -fn draw_line(stdout: &mut RawTerminal, s: &String) -> Result<(), std::io::Error> { +fn draw_line( + stdout: &mut RawTerminal, + s: &String, + clear_len: usize +) -> Result<(), std::io::Error> { write!( - stdout, "\r{}{}==>{}{} {s} {}", + stdout, "\r{}{}==>{}{} {}", style::Bold, color::Fg(color::Blue), color::Fg(color::Reset), style::Reset, - - // Our string can change by at most one character each iteration. - // Clearing is done by inserting an extra space, then moving the cursor left. - termion::cursor::Left(1) + s )?; + + // If this string is shorter, clear the remaining old one. + if clear_len != 0 { + write!( + stdout, "{}{}", + " ".repeat(clear_len as usize), + termion::cursor::Left(clear_len as u16) + )?; + } + stdout.flush()?; return Ok(()); @@ -35,19 +50,26 @@ fn main() -> Result<(), std::io::Error> { //let size = termion::terminal_size().unwrap(); //write!(stdout, "{:?}", size).unwrap(); - let mut s: String = String::with_capacity(64); + let mut pb: PromptBuffer = PromptBuffer::new(64); + let mut last_len: usize = 0; 'outer: loop { - s.clear(); - draw_line(&mut stdout, &s)?; + draw_line( + &mut stdout, + pb.get_contents(), + if pb.get_contents().len() >= last_len + { 0 } else {last_len - pb.get_contents().len()} + )?; + last_len = pb.get_contents().len(); + let stdin = stdin(); for c in stdin.keys() { if let Key::Char(q) = c.as_ref().unwrap() { match q { '\n' => { - let s = s.trim().to_string(); + let s = pb.enter(); if s == "" { write!(stdout, "\r\n")?; break; } RawTerminal::suspend_raw_mode(&stdout)?; @@ -56,7 +78,7 @@ fn main() -> Result<(), std::io::Error> { RawTerminal::activate_raw_mode(&stdout)?; match g { - Ok(g) => { + Ok(g) => { let n = g.eval(); if let Token::Number(_, v) = n { write!( @@ -82,25 +104,32 @@ fn main() -> Result<(), std::io::Error> { break; }, - '/' => { s.push('÷'); }, - '*' => { s.push('×'); }, - _ => { s.push(*q); } + '/' => { pb.add_char('÷'); }, + '*' => { pb.add_char('×'); }, + _ => { pb.add_char(*q); } }; } else { match c.unwrap() { - Key::Backspace => { s.pop(); }, - Key::Delete => { s.pop(); }, + Key::Backspace => { pb.backspace(); }, + Key::Delete => { pb.delete(); }, Key::Left => {}, Key::Right => {}, - Key::Up => {}, - Key::Down => {}, + Key::Up => { pb.hist_up(); }, + Key::Down => { pb.hist_down(); }, Key::Ctrl('d') | Key::Ctrl('c') => { break 'outer; }, _ => {} }; }; - draw_line(&mut stdout, &s)?; + + draw_line( + &mut stdout, + pb.get_contents(), + if pb.get_contents().len() >= last_len + { 0 } else {last_len - pb.get_contents().len()} + )?; + last_len = pb.get_contents().len(); } } diff --git a/src/promptbuffer.rs b/src/promptbuffer.rs new file mode 100644 index 0000000..3045f6a --- /dev/null +++ b/src/promptbuffer.rs @@ -0,0 +1,96 @@ +use std::collections::VecDeque; + + +#[derive(Debug)] +pub struct PromptBuffer { + // History + hist: VecDeque, + hist_maxlen: usize, + + // Counts from back of hist. + // 0 means "not on history", + // 1 means "on last item of history" + hist_cursor: usize, + + buffer: String, + buffer_changed: bool, + //cursor: usize // Counts from back of buffer +} + +impl PromptBuffer { + pub fn new(maxlen: usize) -> PromptBuffer { + return PromptBuffer { + hist: VecDeque::with_capacity(maxlen/2), + hist_maxlen: maxlen, + hist_cursor: 0, + buffer: String::with_capacity(64), + buffer_changed: false + //cursor: 0, + }; + } + + + // Prompt methods + pub fn get_contents(&self) -> &String {&self.buffer} + + pub fn enter(&mut self) -> String{ + let s = String::from(self.buffer.trim()); + self.buffer.clear(); + self.hist_cursor = 0; + self.buffer_changed = false; + + if s != "" { self.hist.push_back(s.clone()); } + return s; + } + + // Buffer manipulation + pub fn add_char(&mut self, c: char) { + self.buffer.push(c); + self.buffer_changed = true; + } + pub fn backspace(&mut self) { + if self.buffer.len() != 0 { + self.buffer_changed = true; + self.buffer.pop(); + } + } + pub fn delete(&mut self) { + self.backspace(); + } + + + // History manipulation + pub fn hist_up(&mut self) { + if self.buffer_changed && self.buffer.len() != 0 { return; } + + if self.hist_cursor < self.hist.len() { + if self.buffer.len() != 0 || !self.buffer_changed { + self.hist_cursor += 1; + } + + self.buffer_changed = false; + if self.hist_cursor == 0 { + self.buffer.clear(); + } else { + self.buffer = self.hist[self.hist.len() - self.hist_cursor].clone(); + } + } + } + pub fn hist_down(&mut self) { + if self.buffer_changed && self.buffer.len() != 0 { return; } + + if self.hist_cursor > 0 { + self.hist_cursor -= 1; + + self.buffer_changed = false; + if self.hist_cursor == 0 { + self.buffer.clear(); + } else { + self.buffer = self.hist[self.hist.len() - self.hist_cursor].clone(); + } + } else { + self.buffer.clear(); + } + } + +} \ No newline at end of file