512 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			512 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| #[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<Stdout>) {
 | |
|         //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();
 | |
|         }
 | |
| 
 | |
|     }
 | |
| }
 |