sqishrc ok
This commit is contained in:
parent
2dd83d0097
commit
ac775f8ce0
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -76,6 +76,12 @@ version = "0.2.138"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linked-hash-map"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.17"
|
version = "0.4.17"
|
||||||
@ -178,6 +184,7 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"users",
|
"users",
|
||||||
"which",
|
"which",
|
||||||
|
"yaml-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -341,3 +348,12 @@ name = "windows_x86_64_msvc"
|
|||||||
version = "0.42.0"
|
version = "0.42.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yaml-rust"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||||
|
dependencies = [
|
||||||
|
"linked-hash-map",
|
||||||
|
]
|
||||||
|
@ -15,3 +15,4 @@ termion = "2.0.1"
|
|||||||
unicode-segmentation = "1.6.0"
|
unicode-segmentation = "1.6.0"
|
||||||
which = { version = "4.4.0", features = ["regex"] }
|
which = { version = "4.4.0", features = ["regex"] }
|
||||||
regex = "1.7.1"
|
regex = "1.7.1"
|
||||||
|
yaml-rust = "0.4.5"
|
||||||
|
13
README.md
13
README.md
@ -1,11 +1,12 @@
|
|||||||
# Sqish
|
# Sqish
|
||||||
Rust Shell. This is an attempt to create a simple shell in Rust, because why not.
|
Rust Shell. This is an attempt to create a simple shell in Rust, because why not.
|
||||||
|
|
||||||
!!! LOOK into the pty crate to handle pseudoterminals such as in top or ssh
|
TO DO:
|
||||||
|
* Add prompt RGB support
|
||||||
|
* Add hotkeys in sqishrc (albeit in the same manner as per colors, the conf Struct already has an hashmap field for it)
|
||||||
|
* Add aliases in sqishrc (same old same old)
|
||||||
|
|
||||||
|
## sqishrc (Config)
|
||||||
|
See the included sqishrc.yaml.example file included, and copy it as ~/.sqishrc.yaml
|
||||||
|
|
||||||
My starting point is [this article](https://www.joshmcguigan.com/blog/build-your-own-shell-rust/) which is excellent. I will start with the final form of a shell given in the article and try to reformat it:
|
|
||||||
|
|
||||||
## To fix
|
|
||||||
* Autocomplete : when nothing found, return input
|
|
||||||
* Autocomplete : Weird permission issues sometimes (ignore unallowed files and folders)
|
|
||||||
* Bug with find_common_chars (if I type Cargo.t and press tab, we can't choose between Cargo.toml and Cargo.lock)
|
|
||||||
|
24
sqishrc.yaml
Normal file
24
sqishrc.yaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
#This is an example sqishrc. to be copied and modified as ~/.sqish.yaml (not .yml,
|
||||||
|
#I can't be bothered to deal with 2 different extensions).
|
||||||
|
#
|
||||||
|
#Prompt: Defines the promptline. Values are defined in all-caps, between $ and _.
|
||||||
|
#Possible values:
|
||||||
|
#HISTNUMBER: Current history number. Useful for !callback
|
||||||
|
#USER : Username.
|
||||||
|
#HOSTNAME : Hostname of the machine.
|
||||||
|
#DIR : Name of the current directory.
|
||||||
|
#
|
||||||
|
#Colors : All colors come from Termion.
|
||||||
|
#Possible values:
|
||||||
|
#RESET : Resets the color. Don't forget to add one in the end of your prompt !
|
||||||
|
#GREEN
|
||||||
|
#BLUE
|
||||||
|
#RED
|
||||||
|
#BLACK
|
||||||
|
#WHITE
|
||||||
|
#CYAN
|
||||||
|
#LBLACK (light black, a dark gray in fact)
|
||||||
|
#Will definitely add RGB support, stay tuned.
|
||||||
|
prompt: "$COLORLBLACK_ !$HISTNUMBER_$COLORCYAN_[$USER_@$HOSTNAME_] $DIR_> $COLORRESET_"
|
||||||
|
|
70
src/lib.rs
70
src/lib.rs
@ -16,6 +16,7 @@ pub mod shell {
|
|||||||
use termion::color;
|
use termion::color;
|
||||||
use termion;
|
use termion;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
mod history;
|
mod history;
|
||||||
use crate::shell::history::*;
|
use crate::shell::history::*;
|
||||||
@ -23,8 +24,8 @@ pub mod shell {
|
|||||||
mod autocomplete;
|
mod autocomplete;
|
||||||
use crate::shell::autocomplete::*;
|
use crate::shell::autocomplete::*;
|
||||||
|
|
||||||
mod prompt;
|
mod config;
|
||||||
use crate::shell::prompt::*;
|
use crate::shell::config::*;
|
||||||
|
|
||||||
//used for home directory
|
//used for home directory
|
||||||
extern crate dirs;
|
extern crate dirs;
|
||||||
@ -58,11 +59,11 @@ pub mod shell {
|
|||||||
Err(e) => eprintln!(" Err: {}", e),
|
Err(e) => eprintln!(" Err: {}", e),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ssh" => {
|
// "ssh" => {
|
||||||
let ssh_args = args.peekable().peek().map_or(" ", |x| *x);
|
// let ssh_args = args.peekable().peek().map_or(" ", |x| *x);
|
||||||
let mut child = Command::new("ssh").arg(ssh_args).spawn().unwrap();
|
// let mut child = Command::new("ssh").arg(ssh_args).spawn().unwrap();
|
||||||
let _ = child.wait().unwrap();
|
// let _ = child.wait().unwrap();
|
||||||
},
|
// },
|
||||||
command => {
|
command => {
|
||||||
if commands.peek().is_some() {
|
if commands.peek().is_some() {
|
||||||
let stdin = match previous_command {
|
let stdin = match previous_command {
|
||||||
@ -230,14 +231,30 @@ pub mod shell {
|
|||||||
//Used in conjunction with Up arrow to go back in history
|
//Used in conjunction with Up arrow to go back in history
|
||||||
//Resetted by pressing Enter
|
//Resetted by pressing Enter
|
||||||
let mut current_number = get_curr_history_number();
|
let mut current_number = get_curr_history_number();
|
||||||
let mut prompt = build_prompt(¤t_number);
|
|
||||||
|
|
||||||
//Used to track the location of the cursor inside the command
|
//Used to track the location of the cursor inside the command
|
||||||
let mut current_pos: usize = 0;
|
let mut current_pos: usize = 0;
|
||||||
let mut max_pos: usize = 0;
|
let mut max_pos: usize = 0;
|
||||||
|
|
||||||
|
let mut conf = match SqishConf::from_sqishrc() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
let ret_line = format!("Could not build conf, got {}\r\nUsing default\r\n", e);
|
||||||
|
write!(stdout, "{}", ret_line);
|
||||||
|
let conf = SqishConf {
|
||||||
|
promptline: String::from("$COLORGREEN_ [$USER_@$HOSTNAME_] "),
|
||||||
|
promptline_base: String::from("$COLORGREEN_ [$USER_@$HOSTNAME_] "),
|
||||||
|
aliases: HashMap::new(),
|
||||||
|
hotkeys: HashMap::new(),
|
||||||
|
};
|
||||||
|
conf
|
||||||
|
},
|
||||||
|
};
|
||||||
|
&conf.update_prompt(get_curr_history_number());
|
||||||
|
|
||||||
|
|
||||||
//Initialize
|
//Initialize
|
||||||
write!(stdout, "\r\n SquiShell (sqish)--- \r\n{}", prompt);
|
write!(stdout, "\r\n SquiShell (sqish)--- \r\n{}", conf.promptline);
|
||||||
stdout.flush();
|
stdout.flush();
|
||||||
|
|
||||||
for c in stdin.keys() {
|
for c in stdin.keys() {
|
||||||
@ -248,44 +265,42 @@ pub mod shell {
|
|||||||
if *&mycommand.len() == 0 {
|
if *&mycommand.len() == 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Search
|
//Search
|
||||||
|
*&mut mycommand = replace_signs(&mycommand);
|
||||||
let (res, list) = Search::build(&mycommand).unwrap().autocomplete();
|
let (res, list) = Search::build(&mycommand).unwrap().autocomplete();
|
||||||
write!(stdout, "\r\n{}\r\n", list);
|
write!(stdout, "\r\n{}\r\n", list);
|
||||||
mycommand.clear();
|
mycommand.clear();
|
||||||
mycommand.push_str(&res);
|
mycommand.push_str(&res);
|
||||||
max_pos = res.len();
|
max_pos = res.len();
|
||||||
current_pos = max_pos;
|
current_pos = max_pos;
|
||||||
write!(stdout, "\r\n{}{}", prompt, res);
|
write!(stdout, "\r\n{}{}", conf.promptline, res);
|
||||||
stdout.flush();
|
stdout.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
Key::Char('\n') => {
|
Key::Char('\n') => {
|
||||||
current_number = get_curr_history_number();
|
|
||||||
prompt = build_prompt(¤t_number);
|
|
||||||
current_pos = 0;
|
current_pos = 0;
|
||||||
max_pos = 0;
|
max_pos = 0;
|
||||||
if (mycommand != String::from("\n")) && (mycommand != String::from("exit")) {
|
if (mycommand != String::from("\n")) && (mycommand != String::from("exit")) {
|
||||||
let comm = replace_signs(&mycommand);
|
let comm = replace_signs(&mycommand);
|
||||||
RawTerminal::suspend_raw_mode(&stdout);
|
RawTerminal::suspend_raw_mode(&stdout);
|
||||||
let res = handle_input(&comm);
|
let _res = handle_input(&comm);
|
||||||
RawTerminal::activate_raw_mode(&stdout);
|
RawTerminal::activate_raw_mode(&stdout);
|
||||||
mycommand.clear();
|
mycommand.clear();
|
||||||
//Proper printing
|
//Proper printing
|
||||||
for line in res.split('\n') {
|
//for line in res.split('\n') {
|
||||||
if line != "\r" {
|
// if line != "\r" {
|
||||||
write!(stdout, "\r\n{}", line);
|
// write!(stdout, "\r\n{}", line);
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
current_number = get_curr_history_number();
|
&conf.update_prompt(get_curr_history_number());
|
||||||
prompt = build_prompt(¤t_number);
|
write!(stdout, "\r\n{}", conf.promptline).unwrap();
|
||||||
write!(stdout, "{}", prompt).unwrap();
|
|
||||||
stdout.flush();
|
stdout.flush();
|
||||||
} else if mycommand == String::from("exit") {
|
} else if mycommand == String::from("exit") {
|
||||||
write!(stdout, "\r\n Sayonara \r\n");
|
write!(stdout, "\r\n Sayonara \r\n");
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
write!(stdout, "\r\n{}", prompt).unwrap();
|
&conf.update_prompt(get_curr_history_number());
|
||||||
|
write!(stdout, "\r\n{}", conf.promptline).unwrap();
|
||||||
stdout.flush();
|
stdout.flush();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -300,9 +315,7 @@ pub mod shell {
|
|||||||
max_pos = 0;
|
max_pos = 0;
|
||||||
mycommand.clear();
|
mycommand.clear();
|
||||||
write!(stdout, "\r\n--CANCEL--\r\n");
|
write!(stdout, "\r\n--CANCEL--\r\n");
|
||||||
current_number = get_curr_history_number();
|
write!(stdout, "{}", conf.promptline);
|
||||||
prompt = build_prompt(¤t_number);
|
|
||||||
write!(stdout, "{}", prompt);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Key::Ctrl('t') => {
|
Key::Ctrl('t') => {
|
||||||
@ -327,6 +340,7 @@ pub mod shell {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
Key::Char(c) => {
|
Key::Char(c) => {
|
||||||
//if at the end of the command...
|
//if at the end of the command...
|
||||||
if current_pos == max_pos {
|
if current_pos == max_pos {
|
||||||
@ -350,7 +364,7 @@ pub mod shell {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Key::Delete => {
|
Key::Delete => {
|
||||||
if current_pos >= 0 && current_pos < max_pos {
|
if current_pos < max_pos {
|
||||||
mycommand.remove(current_pos);
|
mycommand.remove(current_pos);
|
||||||
if current_pos > 0 {
|
if current_pos > 0 {
|
||||||
current_pos -= 1;
|
current_pos -= 1;
|
||||||
@ -388,7 +402,6 @@ pub mod shell {
|
|||||||
};
|
};
|
||||||
mycommand = lastcmd.trim().to_string();
|
mycommand = lastcmd.trim().to_string();
|
||||||
(max_pos, current_pos) = get_cmd_curs_pos(&mycommand);
|
(max_pos, current_pos) = get_cmd_curs_pos(&mycommand);
|
||||||
//let prompt = build_prompt();
|
|
||||||
write!(stdout, "{}", mycommand);
|
write!(stdout, "{}", mycommand);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -401,7 +414,6 @@ pub mod shell {
|
|||||||
};
|
};
|
||||||
mycommand = lastcmd.trim().to_string();
|
mycommand = lastcmd.trim().to_string();
|
||||||
(max_pos, current_pos) = get_cmd_curs_pos(&mycommand);
|
(max_pos, current_pos) = get_cmd_curs_pos(&mycommand);
|
||||||
//let prompt = build_prompt();
|
|
||||||
write!(stdout, "{}", mycommand);
|
write!(stdout, "{}", mycommand);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
123
src/shell/config.rs
Normal file
123
src/shell/config.rs
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
use users::{get_user_by_uid, get_current_uid};
|
||||||
|
use termion::color;
|
||||||
|
use std::{env, fs};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
extern crate dirs;
|
||||||
|
|
||||||
|
extern crate yaml_rust;
|
||||||
|
use yaml_rust::{YamlLoader};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SqishConf {
|
||||||
|
pub promptline: String,
|
||||||
|
pub promptline_base: String,
|
||||||
|
pub aliases: HashMap<String, String>,
|
||||||
|
pub hotkeys: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SqishConf {
|
||||||
|
pub fn from_sqishrc() -> Result<SqishConf, String> {
|
||||||
|
let mut conf_path = match dirs::home_dir() {
|
||||||
|
Some(p) => p,
|
||||||
|
None => {
|
||||||
|
return Err(String::from("Home dir could not be determined."));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
conf_path.push(".sqishrc.yaml");
|
||||||
|
|
||||||
|
let sqishrc_file = match fs::read_to_string(conf_path) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(String::from("Could not read ~/.sqishrc.yaml"));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let sqishrc = match YamlLoader::load_from_str(&sqishrc_file) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(String::from("sqishrc is not valid yaml"));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let sqishrc = &sqishrc[0];
|
||||||
|
let out_str = String::from(sqishrc["prompt"].as_str().unwrap());
|
||||||
|
|
||||||
|
let mut out_conf = SqishConf {
|
||||||
|
promptline: out_str.clone(),
|
||||||
|
promptline_base: out_str,
|
||||||
|
aliases: HashMap::new(),
|
||||||
|
hotkeys: HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
out_conf.handle_colors();
|
||||||
|
|
||||||
|
return Ok(out_conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[warn(unused_assignments)]
|
||||||
|
pub fn update_prompt(&mut self, hist_nbr: i32) {
|
||||||
|
|
||||||
|
let user = String::from(
|
||||||
|
get_user_by_uid(get_current_uid())
|
||||||
|
.unwrap()
|
||||||
|
.name()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let host = String::from(
|
||||||
|
gethostname::gethostname()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let current_dir = String::from(
|
||||||
|
env::current_dir()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let s = current_dir.split('/');
|
||||||
|
let dir_last = String::from(
|
||||||
|
s.last()
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let hist_nbr = hist_nbr.to_string();
|
||||||
|
|
||||||
|
let mut prompt = self.promptline_base.replace("$USER_", &user);
|
||||||
|
prompt = prompt.replace("$HOSTNAME_", &host);
|
||||||
|
prompt = prompt.replace("$DIR_", &dir_last);
|
||||||
|
prompt = prompt.replace("$HISTNUMBER_", &hist_nbr);
|
||||||
|
prompt = prompt.to_owned();
|
||||||
|
self.promptline = prompt;
|
||||||
|
self.handle_colors();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn handle_colors(&mut self) {
|
||||||
|
let reset = format!("{}", color::Fg(color::Reset));
|
||||||
|
let green = format!("{}", color::Fg(color::Green));
|
||||||
|
let blue = format!("{}", color::Fg(color::Blue));
|
||||||
|
let red = format!("{}", color::Fg(color::Red));
|
||||||
|
let black = format!("{}", color::Fg(color::Black));
|
||||||
|
let white = format!("{}", color::Fg(color::White));
|
||||||
|
let cyan = format!("{}", color::Fg(color::Cyan));
|
||||||
|
let lightblack = format!("{}", color::Fg(color::LightBlack));
|
||||||
|
|
||||||
|
let mut prompt = self.promptline.replace("$COLORGREEN_", &green);
|
||||||
|
prompt = prompt.replace("$COLORBLUE_", &blue);
|
||||||
|
prompt = prompt.replace("$COLORRED_", &red);
|
||||||
|
prompt = prompt.replace("$COLORBLACK_", &black);
|
||||||
|
prompt = prompt.replace("$COLORWHITE_", &white);
|
||||||
|
prompt = prompt.replace("$COLORCYAN_", &cyan);
|
||||||
|
prompt = prompt.replace("$COLORLBLACK_", &lightblack);
|
||||||
|
prompt = prompt.replace("$COLORRESET_", &reset);
|
||||||
|
let promptown = prompt.to_owned();
|
||||||
|
self.promptline = promptown;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,42 +0,0 @@
|
|||||||
use users::{get_user_by_uid, get_current_uid};
|
|
||||||
use termion::color;
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
pub fn build_prompt(curr_number: &i32) -> String {
|
|
||||||
|
|
||||||
let user = String::from(
|
|
||||||
get_user_by_uid(get_current_uid())
|
|
||||||
.unwrap()
|
|
||||||
.name()
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
let host = String::from(
|
|
||||||
gethostname::gethostname()
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
let current_dir = String::from(
|
|
||||||
env::current_dir()
|
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
let s = current_dir.split('/');
|
|
||||||
let dir_last = String::from(
|
|
||||||
s.last()
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
let prompt = String::from(format!("{}{}{}[{}@{} in {}]{} ",
|
|
||||||
color::Fg(color::LightBlack),
|
|
||||||
curr_number,
|
|
||||||
color::Fg(color::LightCyan),
|
|
||||||
user, host, dir_last,
|
|
||||||
color::Fg(color::Reset)));
|
|
||||||
return prompt;
|
|
||||||
}
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user