use std::{ env, io::{self, Read, Stdin, Stdout, Write, stdin, stdout}, process::Command, str::SplitWhitespace, }; mod raw; use raw::{disable_raw_mode, enable_raw_mode}; mod key; use key::Key; mod data; use data::Data; fn print_prompt(pre_input: &str, data: &mut Data, newline: bool) -> io::Result<()> { if newline { print!( "\r\n{}\r\n> {}", data.get_current_path().display(), pre_input ); } else { print!("\r> {}", pre_input); } stdout().flush()?; Ok(()) } fn flush_prompt(flush_count: usize) -> io::Result<()> { print!("\r> {}", " ".repeat(flush_count)); print!("\r> {}", "\x08".repeat(flush_count)); Ok(()) } fn handle_input(stdin: &mut Stdin, stdout: &mut Stdout, data: &mut Data) -> io::Result { let mut input = String::new(); let mut buffer = [0u8; 1]; loop { stdin.read_exact(&mut buffer)?; let key = Key::from(buffer[0]); match key { Key::Ctrl('c') => { } Key::Ctrl('d') => { break; } Key::Ctrl(_) => { } Key::Enter => { data.add_to_hist(input.clone()); write!(stdout, "\r\n")?; break; } Key::Backspace => { if !input.is_empty() { input.pop(); write!(stdout, "\x08 \x08")?; stdout.flush()?; } } Key::Escape => continue, Key::ArrowUp => { if let Some(hist_command) = data.get_prev_hist_item() { if data.at_hist_end() { data.save_command(input.clone()); } flush_prompt(input.len())?; input = hist_command.clone(); print_prompt(&input, data, false)?; } } Key::ArrowDown => { if let Some(hist_command) = data.get_next_hist_item() { flush_prompt(input.len())?; input = hist_command.clone(); print_prompt(&input, data, false)?; } } Key::ArrowLeft => {} Key::ArrowRight => {} Key::Char(c) => { input.push(c); write!(stdout, "{c}")?; stdout.flush()?; } Key::Unknown(_) => {} } } Ok(input) } fn parse_input(mut input: I) -> (::Item, I) where I: Iterator, { let command = input.next().unwrap(); let args = input; (command, args) } fn run_command(command: &str, args: SplitWhitespace, data: &mut Data) -> Result { match command { "exit" => Ok(1), "cd" => { let path = args.peekable().peek().map_or("~", |dir| *dir); let mut new_path = data.get_current_path(); if path.starts_with('/') { new_path.push("/"); } for subpath in path.split("/") { match subpath { "-" => { new_path = data.get_previous_path(); break; } "~" => { new_path.push(env::home_dir().unwrap()); } ".." => { new_path.pop(); } "." => {} path => new_path.push(path), } } if let Err(e) = env::set_current_dir(&new_path) { Err(e.to_string()) } else { data.set_path(new_path); Ok(0) } } command => { let child = Command::new(command).args(args).spawn(); match child { Ok(mut child) => { child.wait().unwrap(); Ok(0) } Err(e) => Err(e.to_string()), } } } } fn rush_loop(data: &mut Data, orig: libc::termios) -> io::Result<()> { let mut stdin = stdin(); let mut stdout = stdout(); loop { print_prompt("", data, true)?; let input = handle_input(&mut stdin, &mut stdout, data)?; let input = input.trim(); if input.is_empty() { continue; } let input = input.split_whitespace(); let (command, args) = parse_input(input); disable_raw_mode(&orig); match run_command(command, args, data) { Ok(status) => { if status == 1 { return Ok(()); } } Err(e) => eprint!("{e}\r\n"), } enable_raw_mode(); } } fn main() { let mut data = Data::default(); let home = env::home_dir().unwrap(); let home_str = home.into_os_string().into_string().unwrap(); data.set_hist_file(&format!("{}/.rush_history", home_str)); data.load_history(); let orig = enable_raw_mode(); rush_loop(&mut data, orig).unwrap(); disable_raw_mode(&orig); }