diff options
Diffstat (limited to 'src/line')
| -rw-r--r-- | src/line/buf.rs | 150 | ||||
| -rw-r--r-- | src/line/mod.rs | 219 |
2 files changed, 369 insertions, 0 deletions
diff --git a/src/line/buf.rs b/src/line/buf.rs new file mode 100644 index 0000000..420546a --- /dev/null +++ b/src/line/buf.rs @@ -0,0 +1,150 @@ +use crate::cursor::*; +use std::io::Write; + +pub struct LineBuf { + pre: Vec<u8>, + post: Vec<u8>, + dirty: bool, +} + +impl Default for LineBuf { + fn default() -> Self { + Self::new() + } +} + +#[allow(unused)] +impl LineBuf { + pub fn new() -> Self { + Self { + pre: Vec::new(), + post: Vec::new(), + dirty: false, + } + } + + pub fn del_left(&mut self) -> Option<u8> { + self.dirty = true; + self.pre.pop() + } + + pub fn del_right(&mut self) -> Option<u8> { + self.dirty = true; + self.post.pop() + } + + pub fn len(&self) -> usize { + self.pre.len() + self.post.len() + } + + pub fn left(&mut self) -> bool { + if let Some(byte) = self.del_left() { + self.post.push(byte); + true + } else { + false + } + } + + pub fn right(&mut self) -> bool { + if let Some(byte) = self.del_right() { + self.pre.push(byte); + true + } else { + false + } + } + + pub fn all_left(&mut self) -> usize { + let n = self.pre.len(); + while self.left() {} + n + } + + pub fn all_right(&mut self) -> usize { + let n = self.post.len(); + while self.right() {} + n + } + + pub fn get_left(&self) -> Option<u8> { + self.pre.last().cloned() + } + + pub fn get_right(&self) -> Option<u8> { + self.post.last().cloned() + } + + pub fn add(&mut self, chr: u8) { + self.dirty = true; + self.pre.push(chr); + } + + pub fn is_empty(&self) -> bool { + self.pre.is_empty() && self.post.is_empty() + } + + pub fn is_dirty(&self) -> bool { + self.dirty + } + + /// sets content all to the left + pub fn set_content(&mut self, buf: Vec<u8>) { + self.pre = buf; + self.post = Vec::new(); + } + + pub fn pre(&self) -> &[u8] { + &self.pre + } + + pub fn into_bytes(&self) -> Vec<u8> { + let mut buf = Vec::with_capacity(self.pre.len() + self.post.len()); + buf.extend_from_slice(&self.pre); + for b in self.post.iter().rev() { + buf.push(*b); + } + buf + } + + pub fn distance_from_right_end(&self) -> usize { + self.post.len() + } + + /// returns the whole contents of the buffer, and empties it in the process + pub fn dump(&mut self) -> Vec<u8> { + while self.right() {} + let mut buf = Vec::new(); + core::mem::swap(&mut self.pre, &mut buf); + self.dirty = false; + buf + } + + pub fn clear(&mut self) { + self.pre.clear(); + self.post.clear(); + self.dirty = false; + } + + pub fn display_pre(&self) { + std::io::stdout().write_all(&self.pre).unwrap(); + } + + /// TODO: kinda ugly that this is here + pub fn display_post(&self, post: &[u8]) { + for &x in self.post.iter().rev() { + std::io::stdout().write_all(&[x]).unwrap(); + } + std::io::stdout().write_all(post).unwrap(); + move_cursor(Direction::Left, self.post.len() + post.len()); + std::io::stdout().flush().unwrap(); + } + + pub fn left_len(&self) -> usize { + self.pre.len() + } + + pub fn right_len(&self) -> usize { + self.post.len() + } +} diff --git a/src/line/mod.rs b/src/line/mod.rs new file mode 100644 index 0000000..72f2fe1 --- /dev/null +++ b/src/line/mod.rs @@ -0,0 +1,219 @@ +mod buf; + +use std::io::{self, Write}; + +use crate::cursor::{self, Direction}; +pub use buf::LineBuf; + +pub struct Line { + buf: LineBuf, + dirty: bool, +} + +impl std::ops::Deref for Line { + type Target = LineBuf; + + fn deref(&self) -> &Self::Target { + &self.buf + } +} + +impl Line { + pub fn new() -> Self { + Self { + buf: LineBuf::new(), + dirty: false, + } + } + + pub fn mark_clean(&mut self) { + self.dirty = false; + } + + pub fn is_dirty(&self) -> bool { + self.dirty + } + + pub fn had_contents(&self) -> bool { + self.buf.is_dirty() + } + + pub fn is_empty(&self) -> bool { + self.buf.is_empty() + } + + pub fn cursor_left(&mut self) -> io::Result<()> { + if self.buf.left() { + let mut stdout = io::stdout().lock(); + cursor::fmove_cursor(Direction::Left, 1, &mut stdout)?; + stdout.flush()?; + } + Ok(()) + } + + pub fn cursor_right(&mut self) -> io::Result<()> { + if self.buf.right() { + let mut stdout = io::stdout().lock(); + cursor::fmove_cursor(Direction::Right, 1, &mut stdout)?; + stdout.flush()?; + } + Ok(()) + } + + pub fn move_to_begin(&mut self) -> io::Result<()> { + let mut stdout = io::stdout().lock(); + cursor::fmove_cursor(Direction::Left, self.buf.all_left(), &mut stdout)?; + stdout.flush() + } + + pub fn move_to_end(&mut self) -> io::Result<()> { + let mut stdout = io::stdout().lock(); + cursor::fmove_cursor(Direction::Right, self.buf.all_right(), &mut stdout)?; + stdout.flush() + } + + // move to next + pub fn cursor_left_word(&mut self) -> io::Result<()> { + let mut i = 0; + + // find word + while let Some(b' ') = self.buf.get_left() { + self.buf.left(); + i += 1; + } + + // skip it + while let Some(x) = self.buf.get_left() + && !x.is_ascii_whitespace() + { + self.buf.left(); + i += 1; + } + + let mut stdout = io::stdout().lock(); + cursor::fmove_cursor(Direction::Left, i, &mut stdout)?; + stdout.flush() + } + + pub fn cursor_right_word(&mut self) -> io::Result<()> { + let mut i = 0; + + // find word + while let Some(b' ') = self.buf.get_right() { + self.buf.right(); + i += 1; + } + + // skip it + while let Some(x) = self.buf.get_right() + && !x.is_ascii_whitespace() + { + self.buf.right(); + i += 1; + } + + let mut stdout = io::stdout().lock(); + cursor::fmove_cursor(Direction::Right, i, &mut stdout)?; + stdout.flush() + } +} + +macro_rules! mutating_impls { + ($(pub fn $fun:ident(&mut $self:ident $($arg:tt)*) -> $ty:ty $body:block )*) => { + impl Line {$( + pub fn $fun(&mut $self $($arg)*) -> $ty { + $self.dirty = true; + $body + } + )*} + }; +} + +mutating_impls! { + pub fn clear_prompt(&mut self) -> io::Result<()> { + let mut stdout = io::stdout().lock(); + cursor::fmove_cursor( + Direction::Right, + self.buf.distance_from_right_end(), + &mut stdout, + )?; + for _ in 0..self.buf.len() { + stdout.write_all(b"\x08 \x08")?; + } + self.buf.clear(); + stdout.flush() + } + + pub fn type_bytes(&mut self, bs: &[u8]) -> io::Result<()> { + for b in bs.iter() { + self.buf.add(*b); + } + io::stdout().lock().write_all(&bs)?; + self.buf.display_post(b""); + Ok(()) + } + + pub fn type_byte(&mut self, b: u8) -> io::Result<()> { + self.type_bytes(&[b]) + } + + pub fn del_left(&mut self) -> io::Result<()> { + if self.buf.del_left().is_some() { + cursor::fmove_cursor(Direction::Left, 1, &mut io::stdout())?; + self.buf.display_post(b" "); + } + Ok(()) + } + + pub fn del_right(&mut self) -> io::Result<()> { + self.buf.del_right(); + self.buf.display_post(b" "); + Ok(()) + } + + pub fn del_left_word(&mut self) -> io::Result<()> { + let mut del = 0; + while let Some(x) = self.buf.get_left() + && x == b' ' + { + self.buf.del_left(); + del += 1; + } + while let Some(x) = self.buf.get_left() + && x != b' ' + { + self.buf.del_left(); + del += 1; + } + cursor::fmove_cursor(Direction::Left, del, &mut io::stdout())?; + self.buf.display_post(&vec![b' '; del]); + Ok(()) + } + + pub fn del_right_word(&mut self) -> io::Result<()> { + let mut del = 0; + while let Some(x) = self.buf.get_right() + && x == b' ' + { + self.buf.del_right(); + del += 1; + } + while let Some(x) = self.buf.get_right() + && x != b' ' + { + self.buf.del_right(); + del += 1; + } + self.buf.display_post(&vec![b' '; del]); + Ok(()) + } + + pub fn set_content(&mut self, content: Vec<u8>) -> io::Result<()> { + self.buf.set_content(content); + Ok(()) + } + + pub fn dump(&mut self) -> Vec<u8> { + self.buf.dump() + } +} |
