use std::ffi::OsStr; use std::io::{self, IsTerminal, Read, Write}; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::AsRawFd; use std::path::Path; use std::process::Command; mod cursor; mod linebuf; mod parse; mod raw; mod panic; use linebuf::LineBuf; use raw::*; use crate::cursor::{Direction, move_cursor}; macro_rules! print { ($($x:tt)*) => {{ write!(io::stdout(), $($x)*).unwrap(); io::stdout().flush().unwrap(); }} } macro_rules! println { () => {{ println!("") }}; ($($x:tt)*) => {{ write!(io::stdout(), $($x)*).unwrap(); write!(io::stdout(), "\r\n").unwrap(); io::stdout().flush().unwrap(); }}; } const PROMPT: &str = "> "; fn run_command(raw: &ScopedRawMode, line: Vec) { let parsed = parse::do_parse(&line); println!("{parsed:?}"); let mut words: Vec<&[u8]> = line.split(|x| *x == b' ').collect(); words.retain(|w| !w.is_empty()); if words.is_empty() { print!("{PROMPT}"); return; } let cmd = words[0]; match cmd { b"cd" => { let target: &Path = match words.get(1) { Some(&b"-") => todo!("prev"), Some(path) => OsStr::from_bytes(path).as_ref(), None => todo!("homedir"), }; if let Err(_) = std::env::set_current_dir(target) { print!("ERR {PROMPT}"); } else { print!("{PROMPT}"); } return; } _ => (), } let mut cmd = Command::new(OsStr::from_bytes(cmd)); for arg in words[1..].iter() { cmd.arg(OsStr::from_bytes(arg)); } raw.disable(); let status = cmd.status(); raw.enable(); let status_string = match status { Ok(ec) if ec.success() => String::new(), Ok(ec) => format!("{ec} "), Err(_) => String::from("IO ERROR "), }; print!("{status_string}{PROMPT}"); } fn main() { crate::panic::hook(); let stdin = io::stdin(); let stdout = io::stdout(); if !stdin.is_terminal() { println!("need to run in a tty"); return; } let fd = stdin.as_raw_fd(); let raw = ScopedRawMode::on_fd(fd); raw.enable(); let mut stdin = stdin.lock(); let mut stdout = stdout.lock(); let mut buffer = [0u8; 1]; let mut line = LineBuf::new(); print!("{PROMPT}"); loop { let Ok(_) = stdin.read_exact(&mut buffer) else { break; }; match buffer[0] { // EOF 4 => { break; } // Enter b'\r' => { let line = line.dump(); if !line.is_empty() { print!("\r\n"); run_command(&raw, line); } } // Backspace (127 on most systems) 127 => { if line.del_left().is_some() { print!("\x08 \x08"); } } // Escape sequence 27 => { let mut seq = [0u8; 2]; stdin.read_exact(&mut seq).unwrap(); if seq[0] == b'[' { match seq[1] { b'A' => { // up } b'B' => { // down } b'C' => { move_cursor(Direction::Right, 1); line.right(); } b'D' => { move_cursor(Direction::Left, 1); line.left(); } x => todo!("escape character {x}"), } } } // Normal character x => { line.add(x); stdout.write_all(&[x]).unwrap(); line.display_post(); } } } raw.disable(); println!("bye"); }