#[allow(unused_must_use)] pub mod shell { use std::io::Stdout; use std::process::Command; use std::io::stdin; use std::io::stdout; use std::io::Write; use std::path::Path; use std::env; use std::str; use std::process::Stdio; use termion::event::Key; use termion::input::TermRead; use termion::raw::IntoRawMode; use termion::raw::RawTerminal; use termion::cursor; use termion; use unicode_segmentation::UnicodeSegmentation; use std::collections::HashMap; mod history; use crate::shell::history::*; mod autocomplete; use crate::shell::autocomplete::*; mod config; use crate::shell::config::*; //used for home directory extern crate dirs; 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(); let command = parts.next().unwrap(); let args = parts; match command { "cd" => { let homedir = dirs::home_dir().unwrap().into_os_string().into_string().unwrap(); let new_dir = args.peekable().peek().map_or(homedir.as_str(), |x| *x); let root = Path::new(new_dir); if let Err(e) = env::set_current_dir(&root) { eprintln!(" Err: {}", e); } else { println!(""); } previous_command = None; }, "history" => { //let stdout = Stdio::inherit(); let res = get_history(); match res { Ok(r) => println!("{}", r), Err(e) => eprintln!(" Err: {}", e), } }, // "ssh" => { // let ssh_args = args.peekable().peek().map_or(" ", |x| *x); // let mut child = Command::new("ssh").arg(ssh_args).spawn().unwrap(); // let _ = child.wait().unwrap(); // }, command => { 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) .stdout(stdout) .stdin(stdin) .spawn(); 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, }; print!("\r\n"); let child = match Command::new(command) .args(args) .stdin(stdin) .spawn() { Ok(h) => h, Err(_) => { let err_string = format!("Not found : {}", command); return String::from(err_string); }, }; let output = child .wait_with_output() .expect("Failed to wait on child"); let status = output.status .code() .expect("Could not get retcode") .to_string(); previous_command = None; let format_res = format!("{}", status); let _ = &mut resultat.push_str(&format_res); } }, }; } return resultat; } 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); ayo = String::from(result); } if ayo.contains("!!") { let prev_comm = get_hist_from_number(&get_history_number().unwrap()).unwrap(); let result = str::replace(&ayo, "!!", &prev_comm); ayo = String::from(result); } return ayo; } 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 delete_current_cmd(current_cmd: String) -> String { //Deletes currently written command let mut stdout = stdout().into_raw_mode().unwrap(); //write!(stdout, "{}", cursor::Save); for _c in current_cmd.graphemes(true) { write!(stdout, "\x1b[D").unwrap(); write!(stdout, "\x1b[K").unwrap(); } stdout.flush(); //write!(stdout, "{}", cursor::Restore); return String::new(); } fn get_cmd_curs_pos(command: &String) -> (usize, usize) { let mut max_pos: usize = 0; let mut current_pos: usize = 0; for _c in command.graphemes(true) { max_pos += 1; current_pos += 1; } return (max_pos, current_pos); } fn clear_line(max_pos: &usize, current_pos: &usize) { //clears currently written command from the screen //...NOT from the variable ! let mut stdout = stdout().into_raw_mode().unwrap(); let chars_left = max_pos - current_pos; if current_pos < max_pos { write!(stdout, "{}", cursor::Right(chars_left as u16)); } for _i in 0..*max_pos { write!(stdout, "{}", cursor::Left(1)); write!(stdout, "\x1b[K").unwrap(); } //write!(stdout, "{}", cursor::Left(1)); //write!(stdout, "\x1b[K").unwrap(); stdout.flush(); } fn run_cmd(mycommand: &mut String, current_number: &mut i32, conf: &mut SqishConf, stdout: &mut RawTerminal) { //Handling aliases if conf.aliases.contains_key(mycommand) { *mycommand = conf.aliases[mycommand].clone(); } if (mycommand != &String::from("")) && (mycommand != &String::from("exit")) { let comm = replace_signs(&mycommand); RawTerminal::suspend_raw_mode(&stdout); let res = handle_input(&comm); RawTerminal::activate_raw_mode(&stdout); mycommand.clear(); for line in res.split("\r\n") { if line != "" && line.starts_with('N'){ write!(stdout, "{}\r\n", line); } } conf.update_prompt(get_curr_history_number()); write!(stdout, "{}", conf.promptline).unwrap(); stdout.flush(); *current_number += 1; } else if mycommand == &String::from("exit") { write!(stdout, "\r\n Sayonara \r\n"); RawTerminal::suspend_raw_mode(&stdout); std::process::exit(0); } else { conf.update_prompt(get_curr_history_number()); write!(stdout, "\r\n{}", conf.promptline).unwrap(); } stdout.flush(); } fn write_letter(mycommand: &mut String, current_pos: &mut usize, max_pos: &mut usize, c: char) { let mut stdout = stdout().into_raw_mode().unwrap(); if *current_pos == *max_pos { mycommand.push(c); *current_pos += 1; *max_pos += 1; write!(stdout, "{}", c).unwrap(); } else if *current_pos < *max_pos { //Inserting a char inside the command... write!(stdout, "{}", cursor::Save); mycommand.insert(*current_pos, c); let command_piece = &mycommand.as_str()[*current_pos..]; write!(stdout, "{}", command_piece); stdout.flush(); write!(stdout, "{}", cursor::Restore); write!(stdout, "{}", cursor::Right(1)); *max_pos += 1; *current_pos += 1; } } fn append_prev_arg(mycommand: &mut String, current_pos: &mut usize, max_pos: &mut usize) { let prev_arg = get_prev_arg(); match prev_arg { Some(a) => { for letter in a.chars() { write_letter(mycommand, current_pos, max_pos, letter); } }, None => (), }; } pub fn run_raw() { let stdin = stdin(); let mut stdout = stdout().into_raw_mode().unwrap(); //RawTerminal::suspend_raw_mode(&stdout); let mut mycommand = String::new(); //Used in conjunction with Up arrow to go back in history //Resetted by pressing Enter let mut current_number = get_curr_history_number(); //Used to track the location of the cursor inside the command let mut current_pos: usize = 0; let mut max_pos: usize = 0; let mut conf = match SqishConf::from_sqishrc() { Ok(c) => c, Err(e) => { let conf = SqishConf { promptline: String::from("!$HISTNUMBER$COLORGREEN_[$USER_@$HOSTNAME_]$COLORRESET_ "), promptline_base: String::from("!$HISTNUMBER_$COLORGREEN_[$USER_@$HOSTNAME_]$COLORRESET_ "), aliases: HashMap::new(), hotkeys: HashMap::new(), }; let ret_line = format!("Could not build conf, got {}\ \r\nUsing default promptline", e); write!(stdout, "{}", ret_line); conf }, }; &conf.update_prompt(get_curr_history_number()); //Initialize write!(stdout, "\r\n SquiShell (sqish)--- \r\n{}", conf.promptline); stdout.flush(); for c in stdin.keys() { let k = c.unwrap(); match k { Key::Char('\t') => { //Do NOT search on an empty command if *&mycommand.len() == 0 { continue; } //Search *&mut mycommand = replace_signs(&mycommand); let (res, list) = Search::build(&mycommand).unwrap().autocomplete(); if list.len() > 0 { write!(stdout, "\r\n{}\r\n", list); } mycommand.clear(); mycommand.push_str(&res); max_pos = res.len(); current_pos = max_pos; write!(stdout, "\r\n{}{}", conf.promptline, res); stdout.flush(); } Key::Char('\n') => { current_pos = 0; max_pos = 0; run_cmd(&mut mycommand, &mut current_number, &mut conf, &mut stdout); }, Key::Ctrl('q'|'d') => { RawTerminal::suspend_raw_mode(&stdout); writeln!(stdout, "\r\n Sayonara \r\n"); break; }, Key::Ctrl('c') => { current_pos = 0; max_pos = 0; mycommand.clear(); write!(stdout, "\r\n--CANCEL--\r\n"); write!(stdout, "{}", conf.promptline); }, Key::Ctrl('u') => { clear_line(&max_pos, ¤t_pos); max_pos = 0; current_pos = 0; mycommand.clear(); }, Key::Ctrl('a') => { if current_pos != 0 { write!(stdout, "{}", cursor::Left(current_pos as u16)); current_pos = 0; } }, Key::Ctrl('e') => { if current_pos != max_pos { let chars_left = max_pos - current_pos; write!(stdout, "{}", cursor::Right(chars_left as u16)); current_pos = max_pos; } }, Key::Char(c) => { write_letter(&mut mycommand, &mut current_pos, &mut max_pos, c); } Key::Delete => { if mycommand.chars().count() == 1 && current_pos == 0 { clear_line(&max_pos, ¤t_pos); mycommand.clear(); max_pos = 0; current_pos = 0; } else if current_pos < max_pos { mycommand.remove(current_pos); if current_pos > 0 { current_pos -= 1; write!(stdout, "{}", cursor::Left(1)); } max_pos -= 1; write!(stdout, "{}", cursor::Save); clear_line(&max_pos, ¤t_pos); write!(stdout, "{}", mycommand); write!(stdout, "{}", cursor::Restore); stdout.flush(); } }, Key::Backspace => { if current_pos == 0 { continue; } if mycommand.chars().count() == 1 { clear_line(&max_pos, ¤t_pos); mycommand.clear(); current_pos = 0; max_pos = 0; } else { current_pos -= 1; max_pos -= 1; mycommand.remove(current_pos); write!(stdout, "{}", cursor::Left(1)); write!(stdout, "{}", cursor::Save); clear_line(&max_pos, ¤t_pos); write!(stdout, "{}", mycommand); write!(stdout, "{}", cursor::Restore); stdout.flush(); } }, Key::Up => { mycommand = delete_current_cmd(mycommand); current_number -= 1; let lastcmd = match get_hist_from_number(¤t_number) { Some(c) => c, None => continue }; mycommand = lastcmd.trim().to_string(); (max_pos, current_pos) = get_cmd_curs_pos(&mycommand); write!(stdout, "{}", mycommand); }, Key::Down => { mycommand = delete_current_cmd(mycommand); current_number += 1; let lastcmd = match get_hist_from_number(¤t_number) { Some(c) => c, None => continue }; mycommand = lastcmd.trim().to_string(); (max_pos, current_pos) = get_cmd_curs_pos(&mycommand); write!(stdout, "{}", mycommand); }, Key::Ctrl('x') => { clear_line(&max_pos, ¤t_pos); mycommand.clear(); current_pos = 0; max_pos = 0; } Key::Left => { if current_pos > 0 { current_pos -= 1; write!(stdout, "{}", cursor::Left(1)); } }, Key::Right => { if current_pos < max_pos { print!("{}", cursor::Right(1)); current_pos += 1; } }, Key::Alt(x) => { match x { 'a'..='z' => { let x = String::from(x); if conf.hotkeys.contains_key(&x) { let hotcmd = &conf.hotkeys[&x].clone(); for c in hotcmd.chars() { if c != '\n' { write_letter(&mut mycommand, &mut current_pos, &mut max_pos, c); } else { current_pos = 0; max_pos = 0; run_cmd(&mut mycommand, &mut current_number, &mut conf, &mut stdout); } } } }, '.' => { append_prev_arg(&mut mycommand, &mut current_pos, &mut max_pos); }, _ => (), }; }, _ => (), }; stdout.flush().unwrap(); } } }