199 lines
5.2 KiB
Rust
199 lines
5.2 KiB
Rust
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<String> {
|
|
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<I>(mut input: I) -> (<I as Iterator>::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<u8, String> {
|
|
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);
|
|
}
|