sqish/src/lib.rs
2023-02-20 00:27:46 +01:00

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, &current_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, &current_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, &current_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, &current_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, &current_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(&current_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(&current_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, &current_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();
}
}
}