Compare commits
22 Commits
a24680debc
...
dev
Author | SHA1 | Date | |
---|---|---|---|
f333069a32 | |||
7677adfafe | |||
09cea688b7 | |||
5743678e23 | |||
522b8e7037 | |||
643c69623c | |||
68dc23a15e | |||
1eb1b239c2 | |||
488c9c6e0a | |||
4c64bb8038 | |||
9e1d0dfcde | |||
aec1641024 | |||
b5ef54c8f4 | |||
df6aee4a49 | |||
0656f163fd | |||
eda72c00ab | |||
a515c81e49 | |||
b2f64ea970 | |||
9979140645 | |||
d22e34c8bd | |||
6f63294857 | |||
d903bb4bc8 |
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -195,6 +195,12 @@ version = "0.6.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||
|
||||
[[package]]
|
||||
name = "sqish"
|
||||
version = "1.0.0"
|
||||
@ -203,6 +209,7 @@ dependencies = [
|
||||
"dirs",
|
||||
"gethostname",
|
||||
"regex",
|
||||
"shell-words",
|
||||
"termion",
|
||||
"unicode-segmentation",
|
||||
"users",
|
||||
|
@ -17,3 +17,4 @@ which = { version = "4.4.0", features = ["regex"] }
|
||||
regex = "1.7.1"
|
||||
yaml-rust = "0.4.5"
|
||||
ctrlc = "3.2.5"
|
||||
shell-words = "1.1.0"
|
||||
|
35
README.md
35
README.md
@ -1,12 +1,32 @@
|
||||
# Sqish
|
||||
Rust Shell. This is an attempt to create a simple but usable shell in Rust, because why not.
|
||||
## Sqish's goal
|
||||
Squi's Shell, aka Sqish. This is an attempt to create a simple but usable shell in Rust, because why not. The goal (for me) is also to get more familiar with Rust since I read the Rust book and want to try things out. And, at last, building and using your own tools is pretty cool.
|
||||
|
||||
TODO:
|
||||
* git commit -m "message" does not work
|
||||
* Allow redirecting >> to the end of a file ?
|
||||
I also implemented user-configurable hotkeys (they're more akin to a form of macros really), because I don't think a lot of shells do that and I wanted something unique.
|
||||
|
||||
## Current state
|
||||
Currently, the shell is pretty rough around the edges, but usable, at least for my day to day use. It's not really and equivalent to bash as it does not implement any form of scripting; the only supported operation is piping. My goal is to build a shell for my everyday use as a Linux sysadmin: simple, fast, with just the features that I need.
|
||||
|
||||
## Scripting ?
|
||||
I don't like shell scripting and don't use it (I know the basics, but Ansible and Python are my bread and butter). Building a lexer is not part of the project. Bash / Zsh / Python / etc are still usable when running sqish, so that's that.
|
||||
|
||||
Sqish is now my default shell on my home computer, and I'll keep adding features as I come across them.
|
||||
|
||||
Also keep in mind I'm a beginner in Rust, so the code is pretty dirty; I will probably come around to reformating it once I have all the features I want and ironed out all the bugs.
|
||||
|
||||
# TODO
|
||||
|
||||
TODO (Reformat):
|
||||
* Creating a struct "shell" that contains builder method taking an input and an output as parameters as well as a conf could be cool
|
||||
|
||||
TODO (features):
|
||||
* Add a &&
|
||||
|
||||
TODO (Bugz):
|
||||
* Autocomplete shits itself when typing a path starting with /
|
||||
|
||||
## sqishrc (Config)
|
||||
See the included sqishrc file included, and copy it as ~/.sqishrc
|
||||
See the included sqishrc file included for comments, and copy it as ~/.sqishrc for use (optionnal).
|
||||
|
||||
## Built-in shortcuts
|
||||
### As Hotkeys
|
||||
@ -14,10 +34,13 @@ Some shortcuts are built in. They may be unique to Sqish or be copied from Zsh f
|
||||
* Ctrl+Q or Ctrl+D : Quits the shell immediatly
|
||||
* Ctrl+U : Clears currently written line
|
||||
* Alt+. : Appends the last word from previous command
|
||||
* Alt+! : Show a list of the user-configured hotkeys
|
||||
* Alt+! : Show a list of the user-configured hotkeys and aliases
|
||||
* Ctrl+A : Jumps to the end of the line
|
||||
* Ctrl+E : Jumps to the start of the line
|
||||
* Ctrl+C : Clears the current line and starts a new one
|
||||
* Ctrl+L : Clears the screen
|
||||
* Ctrl+P : Jump to the next word (kinda)
|
||||
* Ctrl+O : Jump to the previous word (kinda)
|
||||
|
||||
### In Text:
|
||||
* !12 : Returns command number 12 from history (Replace the number 12 with any number from history). You can add commands after it, like: "!12 | grep squirrel"
|
||||
|
10
sqishrc
10
sqishrc
@ -24,6 +24,11 @@
|
||||
#RGB !
|
||||
#RGB is defined as $RGB|red|green|blue_
|
||||
#For example $RGB|108|84|30_ gives you an ugly shade of brown
|
||||
#
|
||||
#Styles:
|
||||
#BOLD : start of a bold block
|
||||
#ITA : start of an italic block
|
||||
#STYLERESET : reset all styles.
|
||||
prompt: "$COLORLBLACK_ !$HISTNUMBER_$COLORCYAN_[$USER_@$HOSTNAME_]$RGB|125|0|125_$DIR_> $COLORRESET_"
|
||||
|
||||
#Classic aliases, can be used in conjunction with hotkeys
|
||||
@ -39,3 +44,8 @@ hotkeys:
|
||||
|
||||
#Init : A command to run on startup. Can be used for many things...
|
||||
init: "echo ---Initialization over---"
|
||||
|
||||
#Env : to set environment variables at launch
|
||||
env:
|
||||
CAT: "meow"
|
||||
PATH: /home/cat/bin:$PATH
|
||||
|
700
src/lib.rs
700
src/lib.rs
@ -1,9 +1,10 @@
|
||||
#[allow(unused_must_use)]
|
||||
pub mod shell {
|
||||
use regex::Regex;
|
||||
use std::io::Stdout;
|
||||
use std::process::Command;
|
||||
use std::io::stdin;
|
||||
use std::io::stdout;
|
||||
use std::io::{stdout};
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::env;
|
||||
@ -14,7 +15,7 @@ pub mod shell {
|
||||
use termion::input::TermRead;
|
||||
use termion::raw::IntoRawMode;
|
||||
use termion::raw::RawTerminal;
|
||||
use termion::cursor;
|
||||
use termion::{color, cursor};
|
||||
use termion;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use std::collections::HashMap;
|
||||
@ -30,55 +31,135 @@ pub mod shell {
|
||||
|
||||
//used for home directory
|
||||
extern crate dirs;
|
||||
|
||||
extern crate shell_words;
|
||||
extern crate ctrlc;
|
||||
|
||||
///Stores essential terminal variables
|
||||
struct TermElements {
|
||||
//Stores currently typed command
|
||||
command: String,
|
||||
//Current cursor position in the line
|
||||
cur_pos: usize,
|
||||
//Max position of cursor in the line
|
||||
max_pos: usize,
|
||||
stdout: RawTerminal<Stdout>,
|
||||
//Current history number
|
||||
current_number: i32,
|
||||
//Configuration
|
||||
conf: SqishConf,
|
||||
}
|
||||
|
||||
///Used to unify command output and error handling
|
||||
///across functions
|
||||
struct CmdOutput {
|
||||
outp: String,
|
||||
rc: i16,
|
||||
}
|
||||
|
||||
impl CmdOutput {
|
||||
fn from_values(output: String, rc: i16) -> CmdOutput {
|
||||
let myoutp = CmdOutput {
|
||||
outp: output,
|
||||
rc: rc,
|
||||
};
|
||||
return myoutp;
|
||||
}
|
||||
fn new_empty() -> CmdOutput {
|
||||
let outp = CmdOutput {
|
||||
outp: String::new(),
|
||||
rc: 0,
|
||||
};
|
||||
return outp;
|
||||
}
|
||||
}
|
||||
|
||||
fn export_var(command: String) -> Result<CmdOutput, CmdOutput> {
|
||||
let cmd_split = command.split('=').collect::<Vec<&str>>();
|
||||
if cmd_split.len() < 2 {
|
||||
return Err(CmdOutput::from_values(
|
||||
String::from("Please a format such as : VAR=value"),
|
||||
255));
|
||||
}
|
||||
let key = String::from(cmd_split[0].to_uppercase());
|
||||
let val = String::from(cmd_split[1]);
|
||||
let mut map = HashMap::new();
|
||||
map.insert(key, val);
|
||||
set_envvars(&map);
|
||||
return Ok(CmdOutput::new_empty());
|
||||
}
|
||||
|
||||
fn change_dir(args: &Vec<String>, previous_command: &mut Option<Stdio>) -> Result<CmdOutput, CmdOutput> {
|
||||
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 mut new_dir = String::new();
|
||||
if args.len() > 0 {
|
||||
*&mut new_dir = args.join(" ");
|
||||
} else {
|
||||
*&mut new_dir = String::from(homedir.as_str());
|
||||
}
|
||||
|
||||
let root = Path::new(&new_dir);
|
||||
if let Err(e) = env::set_current_dir(&root) {
|
||||
return Err(CmdOutput::from_values(
|
||||
format!("Error setting directory to {}, got {:?}",
|
||||
new_dir, e), -1));
|
||||
};
|
||||
*previous_command = None;
|
||||
return Ok(CmdOutput::new_empty());
|
||||
}
|
||||
|
||||
fn print_hist(previous_command: &mut Option<Stdio>, command_next: bool) -> Result<CmdOutput, CmdOutput> {
|
||||
//let stdout = Stdio::inherit();
|
||||
let res = get_history();
|
||||
match res {
|
||||
Ok(r) => {
|
||||
let filepath = dirs::home_dir().unwrap().join(".history.sqish");
|
||||
let file = File::open(filepath).unwrap();
|
||||
*previous_command = Some(Stdio::from(file));
|
||||
//if !commands.peek().is_some() {
|
||||
if !command_next {
|
||||
print!("{}", r);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(CmdOutput::from_values(format!("Could not print history, got {:?}", e), 255));
|
||||
},
|
||||
}
|
||||
return Ok(CmdOutput::new_empty());
|
||||
}
|
||||
|
||||
|
||||
fn process_line(input: &str) -> String {
|
||||
let mut commands = input.trim().split(" | ").peekable();
|
||||
fn process_line(input: &str) -> Result<CmdOutput, CmdOutput> {
|
||||
let mut commands = input.trim().split("|").peekable();
|
||||
let mut previous_command = None;
|
||||
let mut resultat = String::new();
|
||||
let mut rc: i16 = 0;
|
||||
|
||||
while let Some(command) = commands.next() {
|
||||
let mut parts = command.trim().split_whitespace();
|
||||
let command = parts.next().unwrap();
|
||||
let args = parts;
|
||||
let parts = match shell_words::split(&command.trim()) {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
return Err(CmdOutput::from_values(format!("Could not parse command {:?}", e), -1));
|
||||
},
|
||||
};
|
||||
if parts.len() < 1 {
|
||||
return Err(CmdOutput::from_values(String::from("Could not parse command"), -1));
|
||||
}
|
||||
let command = parts[0].as_str();
|
||||
let args = Vec::from(&parts[1..]);
|
||||
|
||||
match command {
|
||||
"cd" => {
|
||||
let args = args.collect::<Vec<&str>>();
|
||||
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 mut new_dir = String::new();
|
||||
if args.len() > 0 {
|
||||
*&mut new_dir = args.join(" ");
|
||||
} else {
|
||||
*&mut new_dir = String::from(homedir.as_str());
|
||||
}
|
||||
|
||||
let root = Path::new(&new_dir);
|
||||
if let Err(e) = env::set_current_dir(&root) {
|
||||
eprintln!(" Err: {}", e);
|
||||
} else {
|
||||
println!("");
|
||||
}
|
||||
previous_command = None;
|
||||
change_dir(&args, &mut previous_command)?;
|
||||
print!("\r\n");
|
||||
},
|
||||
"history" => {
|
||||
//let stdout = Stdio::inherit();
|
||||
let res = get_history();
|
||||
match res {
|
||||
Ok(r) => {
|
||||
let filepath = dirs::home_dir().unwrap().join(".history.sqish");
|
||||
let file = File::open(filepath).unwrap();
|
||||
*&mut previous_command = Some(Stdio::from(file));
|
||||
if !commands.peek().is_some() {
|
||||
print!("{}", r);
|
||||
}
|
||||
},
|
||||
Err(e) => eprintln!(" Err reading history: {}", e),
|
||||
}
|
||||
let next = commands.peek().is_some();
|
||||
print_hist(&mut previous_command, next)?;
|
||||
},
|
||||
"export" => {
|
||||
export_var(args.join(" "))?;
|
||||
print!("\r\n");
|
||||
},
|
||||
command => {
|
||||
if commands.peek().is_some() {
|
||||
@ -116,7 +197,7 @@ pub mod shell {
|
||||
Ok(h) => h,
|
||||
Err(_) => {
|
||||
let err_string = format!("Not found : {}", command);
|
||||
return String::from(err_string);
|
||||
return Err(CmdOutput::from_values(err_string, -1));
|
||||
},
|
||||
};
|
||||
|
||||
@ -130,12 +211,15 @@ pub mod shell {
|
||||
previous_command = None;
|
||||
let format_res = format!("{}",
|
||||
status);
|
||||
let _ = &mut resultat.push_str(&format_res);
|
||||
*&mut rc = format_res.parse::<i16>().unwrap();
|
||||
*&mut resultat = String::from_utf8(output.stdout).unwrap();
|
||||
//let _ = &mut resultat.push_str(&format_res);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
return resultat;
|
||||
//return resultat;
|
||||
return Ok(CmdOutput::from_values(resultat, rc));
|
||||
}
|
||||
|
||||
fn replace_signs(line: &String) -> String {
|
||||
@ -155,17 +239,18 @@ pub mod shell {
|
||||
return ayo;
|
||||
}
|
||||
|
||||
fn handle_input(line: &str) -> String {
|
||||
fn handle_input(line: &str) -> Result<CmdOutput, CmdOutput> {
|
||||
//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;
|
||||
return process_line(&c);
|
||||
},
|
||||
None => return String::from(" "),
|
||||
None => return Err(CmdOutput::from_values(
|
||||
String::from("No such value"),
|
||||
255)),
|
||||
};
|
||||
//Regular command
|
||||
} else if line.len() > 0 {
|
||||
@ -174,7 +259,7 @@ pub mod shell {
|
||||
//Nothing
|
||||
} else {
|
||||
//Command was empty, new line
|
||||
return String::from("");
|
||||
return Ok(CmdOutput::new_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,31 +333,40 @@ pub mod shell {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
fn run_cmd(mycommand: &mut String,
|
||||
current_number: &mut i32,
|
||||
conf: &mut SqishConf,
|
||||
stdout: &mut RawTerminal<Stdout>) {
|
||||
//NORMAL CMD
|
||||
if (mycommand != &String::from("")) && (mycommand != &String::from("exit")) {
|
||||
//Run the command
|
||||
let comm = replace_signs(&mycommand);
|
||||
RawTerminal::suspend_raw_mode(&stdout);
|
||||
let res = handle_input(&comm);
|
||||
//Display the results
|
||||
match res {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!("\r\n{}Got error {}, RC : {:?}{}",
|
||||
color::Fg(color::Red),
|
||||
e.outp,
|
||||
e.rc,
|
||||
color::Fg(color::Reset));
|
||||
},
|
||||
};
|
||||
//Prepare for a new command
|
||||
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;
|
||||
//EXITTING
|
||||
} else if mycommand == &String::from("exit") {
|
||||
write!(stdout, "\r\nThanks for using Sqish!\r\nSayonara \r\n");
|
||||
RawTerminal::suspend_raw_mode(&stdout);
|
||||
std::process::exit(0);
|
||||
//EMPTY LINE
|
||||
} else {
|
||||
conf.update_prompt(get_curr_history_number());
|
||||
write!(stdout, "\r\n{}", conf.promptline).unwrap();
|
||||
@ -314,27 +408,26 @@ pub mod shell {
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
fn find_next_space(current_pos: &usize, max_pos: &usize, command: &String) -> usize {
|
||||
let mut steps: usize = 0;
|
||||
if *current_pos == *max_pos { return steps; }
|
||||
for letter in command.chars().skip(*current_pos) {
|
||||
//println!("L : {:?}, S : {:?} C: {:?} M {:?}", letter, steps, current_pos, max_pos);
|
||||
//Skip if we start on a space
|
||||
if steps == 0 && (letter == ' ' || letter == ',' || letter == ';') {
|
||||
continue;
|
||||
} else if letter != ' ' && *current_pos + steps < *max_pos {
|
||||
steps += 1;
|
||||
} else {
|
||||
return steps;
|
||||
}
|
||||
}
|
||||
let max: usize = (0 + max_pos) - current_pos;
|
||||
return max;
|
||||
}
|
||||
|
||||
//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;
|
||||
|
||||
|
||||
//Handle Ctrl+C
|
||||
ctrlc::set_handler(|| {
|
||||
();
|
||||
}).unwrap();
|
||||
|
||||
let mut conf = match SqishConf::from_sqishrc() {
|
||||
fn handle_conf(stdout: &mut Stdout) -> SqishConf {
|
||||
let conf = match SqishConf::from_sqishrc() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
let conf = SqishConf {
|
||||
@ -343,6 +436,7 @@ pub mod shell {
|
||||
aliases: HashMap::new(),
|
||||
hotkeys: HashMap::new(),
|
||||
init: String::new(),
|
||||
env: HashMap::new(),
|
||||
};
|
||||
let ret_line = format!("Could not build conf, got {}\
|
||||
\r\nUsing default promptline", e);
|
||||
@ -350,222 +444,362 @@ pub mod shell {
|
||||
conf
|
||||
},
|
||||
};
|
||||
&conf.update_prompt(get_curr_history_number());
|
||||
return conf;
|
||||
}
|
||||
|
||||
fn set_ctrlc() {
|
||||
ctrlc::set_handler(|| {
|
||||
();
|
||||
}).unwrap();
|
||||
}
|
||||
|
||||
fn tab(elems: &mut TermElements) {
|
||||
//Do NOT search on an empty command
|
||||
if *&elems.command.len() == 0 {
|
||||
return;
|
||||
}
|
||||
//Search
|
||||
*&mut elems.command = replace_signs(&elems.command);
|
||||
let (res, list) = Search::build(&elems.command).unwrap().autocomplete();
|
||||
if list.len() > 1 { write!(elems.stdout, "\r\n{}", list); }
|
||||
elems.command.clear();
|
||||
elems.command.push_str(&res);
|
||||
elems.max_pos = res.len();
|
||||
elems.cur_pos = elems.max_pos;
|
||||
write!(elems.stdout, "\r\n{}{}", elems.conf.promptline, res);
|
||||
elems.stdout.flush();
|
||||
}
|
||||
|
||||
fn enter(elems: &mut TermElements) {
|
||||
elems.current_number = get_curr_history_number();
|
||||
elems.cur_pos = 0;
|
||||
elems.max_pos = 0;
|
||||
*&mut elems.command = transform_alias(&elems.conf, elems.command.clone());
|
||||
run_cmd(&mut elems.command,
|
||||
&mut elems.current_number,
|
||||
&mut elems.conf,
|
||||
&mut elems.stdout);
|
||||
}
|
||||
|
||||
fn cmd_clear(elems: &mut TermElements) {
|
||||
elems.cur_pos = 0;
|
||||
elems.max_pos = 0;
|
||||
elems.current_number = get_curr_history_number();
|
||||
elems.command.clear();
|
||||
write!(elems.stdout, "\r\n--CANCEL--\r\n");
|
||||
write!(elems.stdout, "{}", elems.conf.promptline);
|
||||
}
|
||||
|
||||
fn undo_line(elems: &mut TermElements) {
|
||||
clear_line(&elems.max_pos, &elems.cur_pos);
|
||||
elems.max_pos = 0;
|
||||
elems.cur_pos = 0;
|
||||
elems.command.clear();
|
||||
}
|
||||
|
||||
fn jmp_start(elems: &mut TermElements) {
|
||||
if elems.cur_pos != 0 {
|
||||
write!(elems.stdout, "{}", cursor::Left(elems.cur_pos as u16));
|
||||
elems.cur_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn jmp_end(elems: &mut TermElements) {
|
||||
if elems.cur_pos != elems.max_pos {
|
||||
let chars_left = elems.max_pos - elems.cur_pos;
|
||||
write!(elems.stdout, "{}", cursor::Right(chars_left as u16));
|
||||
elems.cur_pos = elems.max_pos;
|
||||
}
|
||||
}
|
||||
|
||||
fn press_del(elems: &mut TermElements) {
|
||||
if elems.command.chars().count() == 1
|
||||
&& elems.cur_pos == 0 {
|
||||
clear_line(&elems.max_pos, &elems.cur_pos);
|
||||
elems.command.clear();
|
||||
elems.max_pos = 0;
|
||||
elems.cur_pos = 0;
|
||||
} else if elems.cur_pos < elems.max_pos {
|
||||
elems.command.remove(elems.cur_pos);
|
||||
if elems.cur_pos > 0 {
|
||||
elems.cur_pos -= 1;
|
||||
write!(elems.stdout, "{}", cursor::Left(1));
|
||||
}
|
||||
elems.max_pos -= 1;
|
||||
write!(elems.stdout, "{}", cursor::Save);
|
||||
clear_line(&elems.max_pos, &elems.cur_pos);
|
||||
write!(elems.stdout, "{}", elems.command);
|
||||
write!(elems.stdout, "{}", cursor::Restore);
|
||||
elems.stdout.flush();
|
||||
}
|
||||
}
|
||||
|
||||
fn backspace(elems: &mut TermElements) {
|
||||
if elems.cur_pos == 0 {
|
||||
return;
|
||||
}
|
||||
if elems.command.chars().count() == 1 {
|
||||
clear_line(&elems.max_pos, &elems.cur_pos);
|
||||
elems.command.clear();
|
||||
elems.cur_pos = 0;
|
||||
elems.max_pos = 0;
|
||||
} else {
|
||||
elems.cur_pos -= 1;
|
||||
elems.max_pos -= 1;
|
||||
elems.command.remove(elems.cur_pos);
|
||||
write!(elems.stdout, "{}", cursor::Left(1));
|
||||
write!(elems.stdout, "{}", cursor::Save);
|
||||
clear_line(&elems.max_pos, &elems.cur_pos);
|
||||
write!(elems.stdout, "{}", elems.command);
|
||||
write!(elems.stdout, "{}", cursor::Restore);
|
||||
elems.stdout.flush();
|
||||
}
|
||||
}
|
||||
|
||||
fn uparrow(elems: &mut TermElements) {
|
||||
if elems.current_number > 2 {
|
||||
elems.command = delete_current_cmd(elems.command.clone());
|
||||
elems.current_number -= 1;
|
||||
let lastcmd = match get_hist_from_number(&elems.current_number) {
|
||||
Some(c) => c,
|
||||
None => return
|
||||
};
|
||||
elems.command = lastcmd.trim().to_string();
|
||||
(elems.max_pos, elems.cur_pos) = get_cmd_curs_pos(&elems.command);
|
||||
write!(elems.stdout, "{}", elems.command);
|
||||
}
|
||||
}
|
||||
|
||||
fn downarrow(elems: &mut TermElements) {
|
||||
if elems.current_number < get_curr_history_number() {
|
||||
elems.command = delete_current_cmd(elems.command.clone());
|
||||
elems.current_number += 1;
|
||||
let lastcmd = match get_hist_from_number(&elems.current_number) {
|
||||
Some(c) => c,
|
||||
None => return
|
||||
};
|
||||
elems.command = lastcmd.trim().to_string();
|
||||
(elems.max_pos, elems.cur_pos) = get_cmd_curs_pos(&elems.command);
|
||||
write!(elems.stdout, "{}", elems.command);
|
||||
}
|
||||
}
|
||||
|
||||
fn leftarrow(elems: &mut TermElements) {
|
||||
if elems.cur_pos > 0 {
|
||||
elems.cur_pos -= 1;
|
||||
write!(elems.stdout, "{}", cursor::Left(1));
|
||||
}
|
||||
}
|
||||
|
||||
fn rightarrow(elems: &mut TermElements) {
|
||||
if elems.cur_pos < elems.max_pos {
|
||||
print!("{}", cursor::Right(1));
|
||||
elems.cur_pos += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn screen_clear(elems: &mut TermElements) {
|
||||
write!(elems.stdout, "{}{}", termion::clear::All, termion::cursor::Goto(1,1));
|
||||
write!(elems.stdout, "{}{}", elems.conf.promptline, elems.command);
|
||||
}
|
||||
|
||||
fn jmp_nxt_word(elems: &mut TermElements) {
|
||||
let steps = find_next_space(&elems.cur_pos, &elems.max_pos, &elems.command);
|
||||
//println!("steps: {:?} cur {:?} max {:?}", steps, elems.cur_pos, elems.max_pos);
|
||||
if steps > 0 {
|
||||
print!("{}", cursor::Right(steps as u16));
|
||||
elems.cur_pos += steps;
|
||||
}
|
||||
}
|
||||
|
||||
fn jmp_prev_word(elems: &mut TermElements) {
|
||||
let command_rev = elems.command.chars().rev().collect::<String>();
|
||||
let curr_rev = elems.max_pos - elems.cur_pos + 1;
|
||||
let steps = find_next_space(&curr_rev, &elems.max_pos, &command_rev);
|
||||
if steps > 0 {
|
||||
print!("{}", cursor::Left(steps as u16));
|
||||
elems.cur_pos -= steps;
|
||||
}
|
||||
}
|
||||
|
||||
fn press_alt(elems: &mut TermElements, x: char) {
|
||||
match x {
|
||||
'a'..='z' => {
|
||||
let x = String::from(x);
|
||||
if elems.conf.hotkeys.contains_key(&x) {
|
||||
let hotcmd = &elems.conf.hotkeys[&x].clone();
|
||||
for c in hotcmd.chars() {
|
||||
if c != '\n' {
|
||||
write_letter(&mut elems.command,
|
||||
&mut elems.cur_pos,
|
||||
&mut elems.max_pos,
|
||||
c);
|
||||
} else {
|
||||
elems.cur_pos = 0;
|
||||
elems.max_pos = 0;
|
||||
elems.command = transform_alias(&elems.conf, elems.command.clone());
|
||||
run_cmd(&mut elems.command,
|
||||
&mut elems.current_number,
|
||||
&mut elems.conf,
|
||||
&mut elems.stdout);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
'.' => {
|
||||
append_prev_arg(&mut elems.command, &mut elems.cur_pos, &mut elems.max_pos);
|
||||
},
|
||||
'!' => {
|
||||
let mut cmd = format!("\r\n-- HOTKEYS --> \r\n{:?}\r\n", elems.conf.hotkeys);
|
||||
cmd = format!("{}\r\n-- ALIASES -->\r\n{:?}\r\n", cmd, elems.conf.aliases);
|
||||
cmd = cmd.replace(",", "\r\n");
|
||||
cmd = cmd.replace('"', "");
|
||||
write!(elems.stdout, "{}", cmd);
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
//I smell horrible code in here
|
||||
fn set_envvars(vars: &HashMap<String, String>) {
|
||||
let vars = vars.clone();
|
||||
let re = Regex::new(r"\$[A-Za-z_]+").unwrap();
|
||||
|
||||
for (key, value) in vars {
|
||||
let mut after = value.clone();
|
||||
let mut nbr_of_vars = *&value.matches('$').count();
|
||||
while nbr_of_vars >= 1 {
|
||||
let temp = after.clone();
|
||||
let caps = re.captures(&temp).unwrap();
|
||||
for cap in caps.iter() {
|
||||
match cap {
|
||||
Some(c) => {
|
||||
let myvar = String::from(c.as_str()).replace('$', "");
|
||||
match env::var(myvar) {
|
||||
Ok(r) => {
|
||||
*&mut after = after.replace(c.as_str(), &r);
|
||||
},
|
||||
Err(_) => continue,
|
||||
};
|
||||
},
|
||||
None => continue,
|
||||
};
|
||||
}
|
||||
nbr_of_vars -= 1;
|
||||
}
|
||||
env::set_var(&key, &after);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//THE ENTRYPOINT FUNCTION
|
||||
pub fn run_raw() {
|
||||
|
||||
let mut elems = TermElements {
|
||||
command: String::new(),
|
||||
cur_pos: 0,
|
||||
max_pos: 0,
|
||||
stdout: stdout().into_raw_mode().unwrap(),
|
||||
current_number: get_curr_history_number(),
|
||||
conf: handle_conf(&mut stdout().into_raw_mode().unwrap()),
|
||||
};
|
||||
|
||||
let stdin = stdin();
|
||||
|
||||
//Handle Ctrl+C
|
||||
set_ctrlc();
|
||||
|
||||
&elems.conf.update_prompt(get_curr_history_number());
|
||||
|
||||
//Initializing
|
||||
write!(stdout, "\r\n ---Sqish initializing...--- \r\n{}", conf.promptline);
|
||||
stdout.flush();
|
||||
write!(elems.stdout, "\r\n ---Sqish initializing--- \r\n{}", elems.conf.promptline);
|
||||
elems.stdout.flush();
|
||||
set_envvars(&elems.conf.env);
|
||||
|
||||
if conf.init != String::from("") {
|
||||
run_cmd(&mut String::from(&conf.init), &mut current_number, &mut conf, &mut stdout);
|
||||
if elems.conf.init != String::from("") {
|
||||
run_cmd(&mut String::from(&elems.conf.init), &mut elems.current_number, &mut elems.conf, &mut elems.stdout);
|
||||
}
|
||||
|
||||
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();
|
||||
tab(&mut elems);
|
||||
}
|
||||
Key::Char('\n') => {
|
||||
current_number = get_curr_history_number();
|
||||
current_pos = 0;
|
||||
max_pos = 0;
|
||||
mycommand = transform_alias(&conf, mycommand);
|
||||
run_cmd(&mut mycommand,
|
||||
&mut current_number,
|
||||
&mut conf,
|
||||
&mut stdout);
|
||||
enter(&mut elems);
|
||||
},
|
||||
|
||||
Key::Ctrl('q'|'d') => {
|
||||
RawTerminal::suspend_raw_mode(&stdout);
|
||||
writeln!(stdout, "\r\nThanks for using Sqish !\r\nSayonara \r\n");
|
||||
writeln!(elems.stdout, "\r\nThanks for using Sqish !\r\nSayonara \r\n");
|
||||
break;
|
||||
},
|
||||
|
||||
Key::Ctrl('c') => {
|
||||
current_pos = 0;
|
||||
max_pos = 0;
|
||||
current_number = get_curr_history_number();
|
||||
mycommand.clear();
|
||||
write!(stdout, "\r\n--CANCEL--\r\n");
|
||||
write!(stdout, "{}", conf.promptline);
|
||||
cmd_clear(&mut elems);
|
||||
},
|
||||
|
||||
Key::Ctrl('u') => {
|
||||
clear_line(&max_pos, ¤t_pos);
|
||||
max_pos = 0;
|
||||
current_pos = 0;
|
||||
mycommand.clear();
|
||||
undo_line(&mut elems);
|
||||
},
|
||||
|
||||
Key::Ctrl('a') => {
|
||||
if current_pos != 0 {
|
||||
write!(stdout, "{}", cursor::Left(current_pos as u16));
|
||||
current_pos = 0;
|
||||
}
|
||||
jmp_start(&mut elems);
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
jmp_end(&mut elems);
|
||||
},
|
||||
|
||||
Key::Char(c) => {
|
||||
write_letter(&mut mycommand, &mut current_pos, &mut max_pos, c);
|
||||
write_letter(&mut elems.command, &mut elems.cur_pos, &mut elems.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();
|
||||
}
|
||||
press_del(&mut elems);
|
||||
},
|
||||
|
||||
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();
|
||||
}
|
||||
backspace(&mut elems);
|
||||
},
|
||||
|
||||
Key::Up => {
|
||||
if current_number > 2 {
|
||||
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);
|
||||
}
|
||||
uparrow(&mut elems);
|
||||
},
|
||||
|
||||
Key::Down => {
|
||||
if current_number < get_curr_history_number() {
|
||||
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);
|
||||
}
|
||||
downarrow(&mut elems);
|
||||
},
|
||||
|
||||
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));
|
||||
}
|
||||
leftarrow(&mut elems);
|
||||
},
|
||||
|
||||
Key::Right => {
|
||||
if current_pos < max_pos {
|
||||
print!("{}", cursor::Right(1));
|
||||
current_pos += 1;
|
||||
}
|
||||
rightarrow(&mut elems);
|
||||
},
|
||||
|
||||
Key::Ctrl('l') => {
|
||||
screen_clear(&mut elems);
|
||||
},
|
||||
|
||||
Key::Ctrl('p') => {
|
||||
jmp_nxt_word(&mut elems);
|
||||
},
|
||||
|
||||
Key::Ctrl('o') => {
|
||||
jmp_prev_word(&mut elems);
|
||||
},
|
||||
|
||||
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;
|
||||
mycommand = transform_alias(&conf, mycommand);
|
||||
run_cmd(&mut mycommand,
|
||||
&mut current_number,
|
||||
&mut conf,
|
||||
&mut stdout);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
'.' => {
|
||||
append_prev_arg(&mut mycommand, &mut current_pos, &mut max_pos);
|
||||
},
|
||||
'!' => {
|
||||
let mut cmd = format!("\r\n-- ALIASES --> \r\n{:?}\r\n", conf.hotkeys);
|
||||
cmd = cmd.replace(",", "\r\n");
|
||||
write!(stdout, "{}", cmd);
|
||||
},
|
||||
|
||||
_ => (),
|
||||
};
|
||||
press_alt(&mut elems, x);
|
||||
},
|
||||
|
||||
_ => (),
|
||||
};
|
||||
|
||||
stdout.flush().unwrap();
|
||||
elems.stdout.flush().unwrap();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ pub struct Search {
|
||||
local_search: bool,
|
||||
//the kind of search
|
||||
search_type: SearchType,
|
||||
//the original command
|
||||
original: String,
|
||||
}
|
||||
|
||||
impl Search {
|
||||
@ -83,6 +85,7 @@ impl Search {
|
||||
searchee: searchee,
|
||||
local_search: local,
|
||||
search_type: search_type,
|
||||
original: String::from(input),
|
||||
};
|
||||
return Ok(s);
|
||||
}
|
||||
@ -141,6 +144,8 @@ fn autocomplete_file(search: &Search) -> std::io::Result<(String, String)> {
|
||||
let mut last_found = String::new();
|
||||
let mut results = Vec::new();
|
||||
|
||||
|
||||
//This is pretty messy.
|
||||
for file in fs::read_dir(search.search_path.clone())? {
|
||||
let filepath = file.unwrap().path();
|
||||
let filename = filepath
|
||||
@ -192,18 +197,44 @@ fn autocomplete_file(search: &Search) -> std::io::Result<(String, String)> {
|
||||
*&mut last_found = search.searchee.clone();
|
||||
}
|
||||
|
||||
//Handle the path when tabbing in a long path, zsh-like
|
||||
//then return the value
|
||||
let xxx = &search.search_path.clone().into_os_string().into_string().unwrap();
|
||||
let mut return_val = String::new();
|
||||
if !&search.local_search && !&last_found.contains(&xxx.as_str()) {
|
||||
*&mut return_val = format!("{}{}{}", &search.command, &search.search_path.display(), last_found);
|
||||
} else {
|
||||
*&mut return_val = format!("{}{}", &search.command, last_found);
|
||||
//Not looking for anything in particular, this is a special case.
|
||||
if search.searchee == "" {
|
||||
return Ok((String::from(&search.original), all_res));
|
||||
}
|
||||
|
||||
//Remote search... is annoying
|
||||
if !search.local_search {
|
||||
last_found = last_found.replace("./", "");
|
||||
return Ok((format!("{}{}{}", search.command, search.search_path.display(), last_found), String::new()));
|
||||
}
|
||||
|
||||
last_found = last_found.replace("./", "");
|
||||
|
||||
//Otherwise... Handling the return as gracefully as I can
|
||||
let mut return_val = String::new();
|
||||
if all_res.len() > 0 {
|
||||
let ret_string = replace_last_word(&String::from(&search.original), last_found);
|
||||
*&mut return_val = format!("{}", ret_string);
|
||||
} else {
|
||||
*&mut return_val = format!("{}", &search.original);
|
||||
}
|
||||
|
||||
return Ok((return_val, all_res));
|
||||
}
|
||||
|
||||
//Matches on space or slash
|
||||
fn replace_last_word(sentence: &String, replacement: String) -> String {
|
||||
let lastword = sentence.split(|c| c == ' ' || c == '/').last().unwrap_or("");
|
||||
let mut work_sent = sentence.clone();
|
||||
work_sent.truncate(sentence.len() - lastword.len());
|
||||
work_sent.push_str(&replacement);
|
||||
return work_sent;
|
||||
}
|
||||
|
||||
//fn return_last_word(sentence: &String) -> String {
|
||||
// let lastword = sentence.split(|c| c == ' ' || c == '/').last().unwrap_or("");
|
||||
// return String::from(lastword);
|
||||
//}
|
||||
|
||||
fn autocomplete_cmd(input: &String, search: &Search) -> (String, String) {
|
||||
let found_bins = find_bin(input);
|
||||
|
236
src/shell/commandrun.rs
Normal file
236
src/shell/commandrun.rs
Normal file
@ -0,0 +1,236 @@
|
||||
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::fs::File;
|
||||
use std::process::Stdio;
|
||||
use termion::event::Key;
|
||||
use termion::input::TermRead;
|
||||
use termion::raw::IntoRawMode;
|
||||
use termion::raw::RawTerminal;
|
||||
use termion::{color, cursor};
|
||||
use termion;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use std::collections::HashMap;
|
||||
|
||||
mod history;
|
||||
use crate::shell::history::*;
|
||||
|
||||
///Used to unify command output and error handling
|
||||
///across functions
|
||||
pub struct CmdOutput {
|
||||
pub outp: String,
|
||||
pub rc: i16,
|
||||
}
|
||||
|
||||
impl CmdOutput {
|
||||
pub fn from_values(output: String, rc: i16) -> CmdOutput {
|
||||
let myoutp = CmdOutput {
|
||||
outp: output,
|
||||
rc: rc,
|
||||
};
|
||||
return myoutp;
|
||||
}
|
||||
pub fn new_empty() -> CmdOutput {
|
||||
let outp = CmdOutput {
|
||||
outp: String::new(),
|
||||
rc: 0,
|
||||
};
|
||||
return outp;
|
||||
}
|
||||
}
|
||||
|
||||
fn change_dir(args: &Vec<String>, previous_command: &mut Option<Stdio>) -> Result<CmdOutput, CmdOutput> {
|
||||
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 mut new_dir = String::new();
|
||||
if args.len() > 0 {
|
||||
*&mut new_dir = args.join(" ");
|
||||
} else {
|
||||
*&mut new_dir = String::from(homedir.as_str());
|
||||
}
|
||||
|
||||
let root = Path::new(&new_dir);
|
||||
if let Err(e) = env::set_current_dir(&root) {
|
||||
return Err(CmdOutput::from_values(
|
||||
format!("Error setting directory to {}, got {:?}",
|
||||
new_dir, e), -1));
|
||||
};
|
||||
*previous_command = None;
|
||||
return Ok(CmdOutput::new_empty());
|
||||
}
|
||||
|
||||
fn print_hist(previous_command: &mut Option<Stdio>, command_next: bool) -> Result<CmdOutput, CmdOutput> {
|
||||
//let stdout = Stdio::inherit();
|
||||
let res = get_history();
|
||||
match res {
|
||||
Ok(r) => {
|
||||
let filepath = dirs::home_dir().unwrap().join(".history.sqish");
|
||||
let file = File::open(filepath).unwrap();
|
||||
*previous_command = Some(Stdio::from(file));
|
||||
//if !commands.peek().is_some() {
|
||||
if !command_next {
|
||||
print!("{}", r);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(CmdOutput::from_values(format!("Could not print history, got {:?}", e), 255));
|
||||
},
|
||||
}
|
||||
return Ok(CmdOutput::new_empty());
|
||||
}
|
||||
|
||||
|
||||
fn process_line(input: &str) -> Result<CmdOutput, CmdOutput> {
|
||||
let mut commands = input.trim().split("|").peekable();
|
||||
let mut previous_command = None;
|
||||
let mut resultat = String::new();
|
||||
let mut rc: i16 = 0;
|
||||
|
||||
while let Some(command) = commands.next() {
|
||||
let parts = match shell_words::split(&command.trim()) {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
return Err(CmdOutput::from_values(format!("Could not parse command {:?}", e), -1));
|
||||
},
|
||||
};
|
||||
let command = parts[0].as_str();
|
||||
let args = Vec::from(&parts[1..]);
|
||||
|
||||
match command {
|
||||
"cd" => {
|
||||
change_dir(&args, &mut previous_command)?;
|
||||
},
|
||||
"history" => {
|
||||
let next = commands.peek().is_some();
|
||||
print_hist(&mut previous_command, next)?;
|
||||
},
|
||||
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 Err(CmdOutput::from_values(err_string, -1));
|
||||
},
|
||||
};
|
||||
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.expect("Failed to wait on child");
|
||||
let status = output.status
|
||||
.code()
|
||||
.unwrap_or(255)
|
||||
.to_string();
|
||||
previous_command = None;
|
||||
let format_res = format!("{}",
|
||||
status);
|
||||
*&mut rc = format_res.parse::<i16>().unwrap();
|
||||
*&mut resultat = String::from_utf8(output.stdout).unwrap();
|
||||
//let _ = &mut resultat.push_str(&format_res);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
//return resultat;
|
||||
return Ok(CmdOutput::from_values(resultat, rc));
|
||||
}
|
||||
|
||||
pub fn run_cmd(mycommand: &mut String,
|
||||
current_number: &mut i32,
|
||||
conf: &mut SqishConf,
|
||||
stdout: &mut RawTerminal<Stdout>) {
|
||||
//NORMAL CMD
|
||||
if (mycommand != &String::from("")) && (mycommand != &String::from("exit")) {
|
||||
//Run the command
|
||||
let comm = replace_signs(&mycommand);
|
||||
RawTerminal::suspend_raw_mode(&stdout);
|
||||
let res = handle_input(&comm);
|
||||
//Display the results
|
||||
match res {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
println!("\r\n{}Got error {}, RC : {:?}{}",
|
||||
color::Fg(color::Red),
|
||||
e.outp,
|
||||
e.rc,
|
||||
color::Fg(color::Reset));
|
||||
},
|
||||
};
|
||||
//Prepare for a new command
|
||||
RawTerminal::activate_raw_mode(&stdout);
|
||||
mycommand.clear();
|
||||
conf.update_prompt(get_curr_history_number());
|
||||
write!(stdout, "{}", conf.promptline).unwrap();
|
||||
stdout.flush();
|
||||
*current_number += 1;
|
||||
//EXITTING
|
||||
} else if mycommand == &String::from("exit") {
|
||||
write!(stdout, "\r\nThanks for using Sqish!\r\nSayonara \r\n");
|
||||
RawTerminal::suspend_raw_mode(&stdout);
|
||||
std::process::exit(0);
|
||||
//EMPTY LINE
|
||||
} else {
|
||||
conf.update_prompt(get_curr_history_number());
|
||||
write!(stdout, "\r\n{}", conf.promptline).unwrap();
|
||||
}
|
||||
stdout.flush();
|
||||
}
|
||||
|
||||
pub fn handle_input(line: &str) -> Result<CmdOutput, CmdOutput> {
|
||||
//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);
|
||||
return process_line(&c);
|
||||
},
|
||||
None => return Err(CmdOutput::from_values(
|
||||
String::from("No such value"),
|
||||
255)),
|
||||
};
|
||||
//Regular command
|
||||
} else if line.len() > 0 {
|
||||
write_to_history(line);
|
||||
return process_line(line);
|
||||
//Nothing
|
||||
} else {
|
||||
//Command was empty, new line
|
||||
return Ok(CmdOutput::new_empty());
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use users::{get_user_by_uid, get_current_uid};
|
||||
use termion::color;
|
||||
use termion::{color, style};
|
||||
use std::{env, fs};
|
||||
use std::collections::HashMap;
|
||||
use regex::Regex;
|
||||
@ -16,6 +16,7 @@ pub struct SqishConf {
|
||||
pub aliases: HashMap<String, String>,
|
||||
pub hotkeys: HashMap<String, String>,
|
||||
pub init: String,
|
||||
pub env: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl SqishConf {
|
||||
@ -54,12 +55,16 @@ impl SqishConf {
|
||||
let hotkeys_yaml = &sqishrc["hotkeys"];
|
||||
let hotkeys = Self::yaml_dict2hashmap(hotkeys_yaml);
|
||||
|
||||
let env_yaml = &sqishrc["env"];
|
||||
let env = Self::yaml_dict2hashmap(env_yaml);
|
||||
|
||||
let mut out_conf = SqishConf {
|
||||
promptline: out_str.clone(),
|
||||
promptline_base: out_str,
|
||||
aliases: aliases,
|
||||
hotkeys: hotkeys,
|
||||
init: startup,
|
||||
env: env,
|
||||
};
|
||||
|
||||
out_conf.handle_rgb();
|
||||
@ -112,6 +117,7 @@ impl SqishConf {
|
||||
|
||||
|
||||
fn handle_colors(&mut self) {
|
||||
//Colors
|
||||
let reset = format!("{}", color::Fg(color::Reset));
|
||||
let green = format!("{}", color::Fg(color::Green));
|
||||
let blue = format!("{}", color::Fg(color::Blue));
|
||||
@ -121,6 +127,12 @@ impl SqishConf {
|
||||
let cyan = format!("{}", color::Fg(color::Cyan));
|
||||
let lightblack = format!("{}", color::Fg(color::LightBlack));
|
||||
|
||||
//Styles
|
||||
let ita = format!("{}", style::Italic);
|
||||
let bold = format!("{}", style::Bold);
|
||||
let stylereset = format!("{}", style::Reset);
|
||||
|
||||
//Colors replace
|
||||
let mut prompt = self.promptline.replace("$COLORGREEN_", &green);
|
||||
prompt = prompt.replace("$COLORBLUE_", &blue);
|
||||
prompt = prompt.replace("$COLORRED_", &red);
|
||||
@ -129,6 +141,11 @@ impl SqishConf {
|
||||
prompt = prompt.replace("$COLORCYAN_", &cyan);
|
||||
prompt = prompt.replace("$COLORLBLACK_", &lightblack);
|
||||
prompt = prompt.replace("$COLORRESET_", &reset);
|
||||
|
||||
//Styles replace
|
||||
prompt = prompt.replace("$ITA_", &ita);
|
||||
prompt = prompt.replace("$BOLD_", &bold);
|
||||
prompt = prompt.replace("$STYLERESET_", &stylereset);
|
||||
let promptown = prompt.to_owned();
|
||||
self.promptline = promptown;
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ pub fn get_hist_from_number(number: &i32) -> Option<String> {
|
||||
}
|
||||
|
||||
pub fn treat_history_callback(line: &str) -> Option<String> {
|
||||
let mut mystring = String::from(line);
|
||||
let mystring = String::from(line);
|
||||
let temp = mystring.split_whitespace().collect::<Vec<&str>>();
|
||||
let mystring = temp.first().unwrap_or(&line);
|
||||
let mut chars = mystring.chars();
|
||||
|
Reference in New Issue
Block a user