aboutsummaryrefslogtreecommitdiffstats
path: root/src/line/mod.rs
diff options
context:
space:
mode:
authorJonas Maier <jonas@x77.dev>2026-05-22 21:26:41 +0200
committerJonas Maier <jonas@x77.dev>2026-05-22 21:26:41 +0200
commiteeb267c46340d5d47f41cc2440f0b281f9ae9261 (patch)
treeabcbc6624e0903cc1c7cd919d15a42ebb970692a /src/line/mod.rs
parent07daff9331dbdc607584edbf1a8fb3e415c338ea (diff)
downloadpish-eeb267c46340d5d47f41cc2440f0b281f9ae9261.tar.gz
basic syntax highlighting
Diffstat (limited to 'src/line/mod.rs')
-rw-r--r--src/line/mod.rs219
1 files changed, 219 insertions, 0 deletions
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()
+ }
+}