diff --git a/Cargo.lock b/Cargo.lock index 5b25ae6..9ede201 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + [[package]] name = "proc-macro2" version = "1.0.47" @@ -97,6 +103,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_termios" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" +dependencies = [ + "redox_syscall", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -114,6 +129,7 @@ version = "0.1.0" dependencies = [ "dirs", "gethostname", + "termion", "users", ] @@ -128,6 +144,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termion" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659c1f379f3408c7e5e84c7d0da6d93404e3800b6b9d063ba24436419302ec90" +dependencies = [ + "libc", + "numtoa", + "redox_syscall", + "redox_termios", +] + [[package]] name = "thiserror" version = "1.0.37" diff --git a/Cargo.toml b/Cargo.toml index 1f8696b..08b4e6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rshell" +name = "sqish" version = "0.1.0" edition = "2021" @@ -11,3 +11,4 @@ codegen-units = 1 dirs = "4.0.0" users = "0.11.0" gethostname = "0.4.1" +termion = "2.0.1" diff --git a/src/lib.rs b/src/lib.rs index 57669a6..559840d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ pub mod shell { use std::io::ErrorKind; use std::path::Path; use std::env; + use std::str; use std::process::Child; use std::process::Stdio; use std::fs::File; @@ -14,15 +15,20 @@ pub mod shell { use std::io::{self, prelude::*, BufReader}; use users::{get_user_by_uid, get_current_uid}; use gethostname::gethostname; + use termion::event::Key; + use termion::input::TermRead; + use termion::raw::IntoRawMode; + //used for home directory extern crate dirs; - fn process_line(input: String) -> bool { + fn process_line(input: &str) -> String { let mut commands = input.trim().split(" | ").peekable(); let mut previous_command = None; + let mut resultat = String::new(); while let Some(command) = commands.next() { let mut parts = command.trim().split_whitespace(); @@ -34,52 +40,61 @@ pub mod shell { let new_dir = args.peekable().peek().map_or("/", |x| *x); let root = Path::new(new_dir); if let Err(e) = env::set_current_dir(&root) { - eprintln!("{}", e); + eprintln!(" Err: {}", e); } - previous_command = None; }, - "exit" => return true, "history" => { let stdout = Stdio::inherit(); - print_history(); + let res = get_history(); + match res { + Ok(r) => return r, + Err(e) => eprintln!(" Err: {}", e), + } }, command => { - let stdin = previous_command.map_or( - Stdio::inherit(), - |output: Child| Stdio::from(output.stdout.unwrap()) - ); - - let stdout = if commands.peek().is_some() { - Stdio::piped() - } else { - Stdio::inherit() - }; + if commands.peek().is_some() { + let stdin = match previous_command { + None => Stdio::inherit(), + Some(o) => o, + }; + let stdout = Stdio::piped(); - let output = Command::new(command) - .args(args) - .stdin(stdin) - .stdout(stdout) - .spawn(); + let output = Command::new(command) + .args(args) + .stdout(stdout) + .stdin(stdin) + .spawn(); - match output { - Ok(output) => { previous_command = Some(output); }, - Err(e) => { - previous_command = None; - eprintln!("{}", e); - }, - }; - } - } + match output { + Ok(o) => previous_command = Some(Stdio::from(o.stdout.unwrap())), + Err(e) => { + previous_command = None; + eprintln!(" Err: {}", e); + }, + }; + } else { + let stdin = match previous_command { + None => Stdio::inherit(), + Some(o) => o, + }; + let output = Command::new(command) + .args(args) + .stdin(stdin) + .output() + .expect("???"); + &mut resultat.push_str(str::from_utf8(&output.stdout) + .expect("Could not convert command to str") + ); + previous_command = None; + } + }, + }; } - - if let Some(mut final_command) = previous_command { - // block until the final command has finished - final_command.wait(); - } - return false; + return resultat; } + fn write_to_history(command: &str) -> Result<(), std::io::Error> { //Write a command to history let filepath = dirs::home_dir().unwrap().join("history.rshell"); @@ -109,14 +124,16 @@ pub mod shell { return Ok(number); } - fn print_history() -> Result<(), std::io::Error> { + fn get_history() -> Result { let filepath = dirs::home_dir().unwrap().join("history.rshell"); let file = File::open(filepath)?; let contents = BufReader::new(file).lines(); + let mut res = String::new(); for line in contents { - println!("{}", line.unwrap()); + let myline = format!("\r\n{}", line.unwrap()); + res.push_str(&myline); } - return Ok(()); + return Ok(res); } fn get_hist_from_number(number: &i32) -> Option { @@ -194,8 +211,8 @@ pub mod shell { return prompt; } - fn replace_signs(line: String) -> String { - let mut ayo = String::from(&line); + fn replace_signs(line: &String) -> String { + let mut ayo = String::from(line); if ayo.contains('~') { let homedir = dirs::home_dir().unwrap().into_os_string().into_string().unwrap(); let result = str::replace(&ayo, "~", &homedir); @@ -211,38 +228,147 @@ pub mod shell { return ayo; } - pub fn run(){ - loop { - print!("{}", build_prompt()); - stdout().flush(); - let mut input = String::new(); - let bytes = stdin().read_line(&mut input).unwrap(); - input = replace_signs(input); - - //history callback - if (bytes > 0) && (input != String::from("\n")) && (input.starts_with('!')) { - let command_found = treat_history_callback(&input); - match command_found { - Some(c) => { - write_to_history(&c); - process_line(c); - }, - None => () - }; - //Regular command - } else if (bytes > 0) && (input != String::from("\n")) { - write_to_history(&input); - if process_line(input) { - return - } - //Nothing - } else { - //Command was empty, new line - continue; - } + fn handle_input(line: &str) -> String { + //history callback + if (line.len() > 0) && (line.starts_with('!')) { + let command_found = treat_history_callback(line); + match command_found { + Some(c) => { + write_to_history(&c); + let outp = process_line(&c); + return outp; + }, + None => return String::from(" "), + }; + //Regular command + } else if line.len() > 0 { + write_to_history(line); + return process_line(line); + //Nothing + } else { + //Command was empty, new line + return String::from(" "); } } + fn fake_tab(input: &str) -> String { + let faketab = format!(" --This is a fake output for autocomplete from {input}-- "); + //In reality, we would keep the input and append to it + let aaa = String::from(faketab); + return aaa; + } + + pub fn run_raw() { + let stdin = stdin(); + let mut stdout = stdout().into_raw_mode().unwrap(); + let prompt = build_prompt(); + let mut mycommand = String::new(); + + //Initialize + write!(stdout, "\r\n SquiShell (sqish)--- \r\n{}", prompt); + stdout.flush(); + + for c in stdin.keys() { + + match c.unwrap() { + Key::Char('\t') => { + let res = fake_tab(&mycommand); + mycommand.clear(); + mycommand.push_str(&res); + write!(stdout, "\r\n{}{}", prompt, res); + stdout.flush(); + } + + Key::Char('\n') => { + if (mycommand != String::from("\n")) && (mycommand != String::from("exit")) { + let comm = replace_signs(&mycommand); + let res = handle_input(&comm); + mycommand.clear(); + //Proper printing + for line in res.split('\n') { + write!(stdout, "\r\n{}", line); + } + write!(stdout, "\r\n{}", prompt).unwrap(); + stdout.flush(); + } else if mycommand == String::from("exit") { + break; + } else { + write!(stdout, "\r\n{}", prompt).unwrap(); + stdout.flush(); + } + }, + + Key::Ctrl('q') => break, + + Key::Char(C) => { + mycommand.push(C); + write!(stdout, "{}", C).unwrap(); + }, + + Key::Backspace => { + match mycommand.pop() { + Some(_) => {}, + None => continue, + } + //Move cursor left + write!(stdout, "\x1b[D").unwrap(); + //Delete one character there + write!(stdout, "\x1b[K").unwrap(); + stdout.flush(); + }, + + _ => (), + } + + stdout.flush().unwrap(); + } + + } + +// pub fn run(){ +// loop { +// print!("{}", build_prompt()); +// stdout().flush(); +// let mut input = String::new(); +// let bytes = stdin().read_line(&mut input).unwrap(); +// input = replace_signs(input); +// +// //history callback +// if (bytes > 0) && (input != String::from("\n")) && (input.starts_with('!')) { +// let command_found = treat_history_callback(&input); +// match command_found { +// Some(c) => { +// write_to_history(&c); +// process_line(&c); +// }, +// None => () +// }; +// //Regular command +// } else if (bytes > 0) && (input != String::from("\n")) { +// write_to_history(&input); +// if process_line(input) { +// return +// } +// //Nothing +// } else { +// //Command was empty, new line +// continue; +// } +// } +// } + + + + + + + + + + + + + #[cfg(test)] mod tests { use super::*; diff --git a/src/main.rs b/src/main.rs index 8f53b36..37d4be4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ -pub use ::rshell::shell::run; +pub use ::rshell::shell::run_raw; fn main() { - run(); + run_raw(); }