Compare commits
No commits in common. "d7fc72e65d36b60a1945dc79dc44054ba1b92a26" and "e9d64b38cde541e0b859493a95fd231535f007f2" have entirely different histories.
d7fc72e65d
...
e9d64b38cd
7 changed files with 2 additions and 416 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
|
@ -2,15 +2,6 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.175"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rush"
|
name = "rush"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,3 @@
|
||||||
name = "rush"
|
name = "rush"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
default-run = "rush"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
libc = "0.2.175"
|
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
use std::io::{stdin, stdout, Read, Write};
|
|
||||||
|
|
||||||
fn enable_raw_mode() -> libc::termios {
|
|
||||||
unsafe {
|
|
||||||
let fd = libc::STDIN_FILENO;
|
|
||||||
let mut termios = std::mem::zeroed();
|
|
||||||
libc::tcgetattr(fd, &mut termios);
|
|
||||||
|
|
||||||
let mut raw = termios;
|
|
||||||
// Input modes: no break, no CR to NL, no parity check, no strip char,
|
|
||||||
// no start/stop output control.
|
|
||||||
raw.c_iflag &= !(libc::BRKINT | libc::ICRNL | libc::INPCK | libc::ISTRIP | libc::IXON);
|
|
||||||
// Output modes: disable post processing
|
|
||||||
raw.c_oflag &= !(libc::OPOST);
|
|
||||||
// Control modes: set 8 bit chars
|
|
||||||
raw.c_cflag |= libc::CS8;
|
|
||||||
// Local modes: disable echo, canonical, extended functions, and signals
|
|
||||||
raw.c_lflag &= !(libc::ECHO | libc::ICANON | libc::IEXTEN | libc::ISIG);
|
|
||||||
// Control chars: set read timeout
|
|
||||||
raw.c_cc[libc::VMIN] = 1; // minimum number of bytes before read returns
|
|
||||||
raw.c_cc[libc::VTIME] = 0; // no timeout
|
|
||||||
|
|
||||||
libc::tcsetattr(fd, libc::TCSAFLUSH, &raw);
|
|
||||||
|
|
||||||
termios // return original so we can restore later
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn disable_raw_mode(orig: &libc::termios) {
|
|
||||||
unsafe {
|
|
||||||
libc::tcsetattr(libc::STDIN_FILENO, libc::TCSAFLUSH, orig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let orig = enable_raw_mode();
|
|
||||||
let mut buffer = [0u8; 1];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
stdin().read_exact(&mut buffer).unwrap();
|
|
||||||
if buffer[0] == 3 {break;}
|
|
||||||
|
|
||||||
print!("{:?}\r\n", buffer);
|
|
||||||
stdout().flush().unwrap();
|
|
||||||
}
|
|
||||||
disable_raw_mode(&orig);
|
|
||||||
}
|
|
||||||
117
src/data.rs
117
src/data.rs
|
|
@ -1,117 +0,0 @@
|
||||||
use std::{env, fs::{File, OpenOptions}, io::{BufRead, ErrorKind, Write}, path::PathBuf};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Data {
|
|
||||||
prev_path: PathBuf,
|
|
||||||
curr_path: PathBuf,
|
|
||||||
|
|
||||||
history: Vec<String>,
|
|
||||||
hist_file: String,
|
|
||||||
hist_pos: usize,
|
|
||||||
hist_partial: String
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Data {
|
|
||||||
fn default() -> Self {
|
|
||||||
let prev_path = PathBuf::new();
|
|
||||||
let curr_path = env::current_dir().unwrap();
|
|
||||||
let history: Vec<String> = Vec::new();
|
|
||||||
let hist_file = String::new();
|
|
||||||
let hist_pos = 0;
|
|
||||||
let hist_partial = String::new();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
prev_path,
|
|
||||||
curr_path,
|
|
||||||
|
|
||||||
history,
|
|
||||||
hist_file,
|
|
||||||
hist_pos,
|
|
||||||
hist_partial
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Data {
|
|
||||||
pub fn set_path(&mut self, path: PathBuf) {
|
|
||||||
self.prev_path = self.curr_path.clone();
|
|
||||||
self.curr_path = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_current_path(&self) -> PathBuf {
|
|
||||||
self.curr_path.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_previous_path(&self) -> PathBuf {
|
|
||||||
self.prev_path.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_to_hist(&mut self, command: String) {
|
|
||||||
self.history.push(command);
|
|
||||||
self.hist_pos += 1;
|
|
||||||
self.save_history();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_command(&mut self, command: String) {
|
|
||||||
self.hist_partial = command;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_prev_hist_item(&mut self) -> Option<String> {
|
|
||||||
if self.history.is_empty() { return None; }
|
|
||||||
if self.hist_pos <= self.history.len() && self.hist_pos > 0 { self.hist_pos -= 1; Some(self.history[self.hist_pos].clone()) }
|
|
||||||
else { None }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_next_hist_item(&mut self) -> Option<String> {
|
|
||||||
if self.hist_pos < self.history.len() { self.hist_pos += 1; }
|
|
||||||
if self.hist_pos < self.history.len() { Some(self.history[self.hist_pos].clone()) }
|
|
||||||
else if self.hist_pos == self.history.len() { Some(self.hist_partial.clone()) }
|
|
||||||
else { None }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn at_hist_end(&self) -> bool {
|
|
||||||
self.hist_pos+1 == self.history.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_hist_file(&mut self, hist_file: &str) {
|
|
||||||
self.hist_file = hist_file.to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_history(&self) {
|
|
||||||
let mut hist_file = match OpenOptions::new().create(true).write(true).open(&self.hist_file) {
|
|
||||||
Ok(file) => file,
|
|
||||||
Err(e) => {
|
|
||||||
eprint!("\r\n{e}");
|
|
||||||
return
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for line in &self.history {
|
|
||||||
write!(hist_file, "{line}\n").unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_history(&mut self) {
|
|
||||||
let hist_file = match File::open(&self.hist_file) {
|
|
||||||
Ok(file) => file,
|
|
||||||
Err(e) => {
|
|
||||||
if e.kind() == ErrorKind::NotFound {eprint!("\r\nHistory file not found, creating")}
|
|
||||||
else { eprint!("\r\n{e}"); }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let reader = std::io::BufReader::new(hist_file);
|
|
||||||
|
|
||||||
for line in reader.lines() {
|
|
||||||
match line {
|
|
||||||
Ok(line) => {
|
|
||||||
self.history.push(line.trim().to_string());
|
|
||||||
self.hist_pos += 1;
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
eprint!("\r\n{e}");
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
40
src/key.rs
40
src/key.rs
|
|
@ -1,40 +0,0 @@
|
||||||
use std::io::{stdin, Read};
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
pub enum Key {
|
|
||||||
Char(char),
|
|
||||||
Enter,
|
|
||||||
Backspace,
|
|
||||||
Escape,
|
|
||||||
ArrowUp,
|
|
||||||
ArrowDown,
|
|
||||||
ArrowRight,
|
|
||||||
ArrowLeft,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
Unknown(Vec<u8>)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u8> for Key {
|
|
||||||
fn from(byte: u8) -> Self {
|
|
||||||
match byte {
|
|
||||||
b'\n' | b'\r' => Key::Enter,
|
|
||||||
8 | 127 => Key::Backspace,
|
|
||||||
27 => {
|
|
||||||
let mut buf = [0u8; 2];
|
|
||||||
if stdin().read_exact(&mut buf).is_err() {
|
|
||||||
return Key::Escape;
|
|
||||||
}
|
|
||||||
|
|
||||||
match buf {
|
|
||||||
[b'[', b'A'] => Key::ArrowUp,
|
|
||||||
[b'[', b'B'] => Key::ArrowDown,
|
|
||||||
[b'[', b'C'] => Key::ArrowRight,
|
|
||||||
[b'[', b'D'] => Key::ArrowLeft,
|
|
||||||
_ => Key::Unknown(vec![27, buf[0], buf[1]])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
char if char.is_ascii() => Key::Char(byte as char),
|
|
||||||
byte => Key::Unknown(vec![byte])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
177
src/main.rs
177
src/main.rs
|
|
@ -1,176 +1,3 @@
|
||||||
use std::{
|
|
||||||
env,
|
|
||||||
io::{self, stdin, stdout, Read, Stdin, Stdout, Write},
|
|
||||||
process::Command,
|
|
||||||
str::SplitWhitespace,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod raw;
|
|
||||||
use raw::{enable_raw_mode, disable_raw_mode};
|
|
||||||
|
|
||||||
mod key;
|
|
||||||
use key::Key;
|
|
||||||
|
|
||||||
mod data;
|
|
||||||
use data::Data;
|
|
||||||
|
|
||||||
fn print_prompt(pre_input: &str, data: &mut Data, newline: bool) -> io::Result<()> {
|
|
||||||
if newline {
|
|
||||||
print!("\r\n{}\r\n> {}", data.get_current_path().display(), pre_input);
|
|
||||||
} else {
|
|
||||||
print!("\r> {}", pre_input);
|
|
||||||
}
|
|
||||||
stdout().flush()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush_prompt(flush_count: usize) -> io::Result<()> {
|
|
||||||
print!("\r> {}", " ".repeat(flush_count));
|
|
||||||
print!("\r> {}", "\x08".repeat(flush_count));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_input(stdin: &mut Stdin, stdout: &mut Stdout, data: &mut Data) -> io::Result<String> {
|
|
||||||
let mut input = String::new();
|
|
||||||
let mut buffer = [0u8; 1];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
stdin.read_exact(&mut buffer)?;
|
|
||||||
let key = Key::from(buffer[0]);
|
|
||||||
|
|
||||||
match key {
|
|
||||||
Key::Enter => {
|
|
||||||
data.add_to_hist(input.clone());
|
|
||||||
write!(stdout, "\r\n")?;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Key::Backspace => {
|
|
||||||
if !input.is_empty() {
|
|
||||||
input.pop();
|
|
||||||
write!(stdout, "\x08 \x08")?;
|
|
||||||
stdout.flush()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Key::Escape => continue,
|
|
||||||
Key::ArrowUp => {
|
|
||||||
if let Some(hist_command) = data.get_prev_hist_item() {
|
|
||||||
if data.at_hist_end() { data.save_command(input.clone()); }
|
|
||||||
flush_prompt(input.len())?;
|
|
||||||
input = hist_command.clone();
|
|
||||||
print_prompt(&input, data, false)?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Key::ArrowDown => {
|
|
||||||
if let Some(hist_command) = data.get_next_hist_item() {
|
|
||||||
flush_prompt(input.len())?;
|
|
||||||
input = hist_command.clone();
|
|
||||||
print_prompt(&input, data, false)?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Key::ArrowLeft => {},
|
|
||||||
Key::ArrowRight => {},
|
|
||||||
Key::Char(c) => {
|
|
||||||
input.push(c);
|
|
||||||
write!(stdout, "{c}")?;
|
|
||||||
stdout.flush()?;
|
|
||||||
}
|
|
||||||
Key::Unknown(_) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_input<I>(mut input: I) -> (<I as Iterator>::Item, I)
|
|
||||||
where
|
|
||||||
I: Iterator,
|
|
||||||
{
|
|
||||||
let command = input.next().unwrap();
|
|
||||||
let args = input;
|
|
||||||
|
|
||||||
(command, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_command(command: &str, args: SplitWhitespace, data: &mut Data) -> Result<u8, String> {
|
|
||||||
match command {
|
|
||||||
"exit" => return Ok(1),
|
|
||||||
"cd" => {
|
|
||||||
let path = args.peekable().peek().map_or("~", |dir| *dir);
|
|
||||||
|
|
||||||
let mut new_path = data.get_current_path();
|
|
||||||
|
|
||||||
if path.chars().nth(0).unwrap() == '/' { new_path.push("/"); }
|
|
||||||
for subpath in path.split("/") {
|
|
||||||
match subpath {
|
|
||||||
"-" => {
|
|
||||||
new_path = data.get_previous_path();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
"~" => {
|
|
||||||
new_path.push(env::home_dir().unwrap());
|
|
||||||
}
|
|
||||||
".." => {
|
|
||||||
new_path.pop();
|
|
||||||
}
|
|
||||||
"." => {}
|
|
||||||
path => new_path.push(path),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = env::set_current_dir(&new_path) {
|
|
||||||
Err(e.to_string())
|
|
||||||
} else {
|
|
||||||
data.set_path(new_path);
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
command => {
|
|
||||||
let child = Command::new(command).args(args).spawn();
|
|
||||||
|
|
||||||
match child {
|
|
||||||
Ok(mut child) => { child.wait().unwrap(); Ok(0) }
|
|
||||||
Err(e) => { return Err(e.to_string()); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rush_loop(data: &mut Data, orig: libc::termios) -> io::Result<()> {
|
|
||||||
let mut stdin = stdin();
|
|
||||||
let mut stdout = stdout();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
print_prompt("", data, true)?;
|
|
||||||
|
|
||||||
let input = handle_input(&mut stdin, &mut stdout, data)?;
|
|
||||||
let input = input.trim();
|
|
||||||
if input.is_empty() { continue; }
|
|
||||||
let input = input.split_whitespace();
|
|
||||||
let (command, args) = parse_input(input);
|
|
||||||
|
|
||||||
disable_raw_mode(&orig);
|
|
||||||
match run_command(command, args, data) {
|
|
||||||
Ok(status) => match status {
|
|
||||||
1 => return Ok(()),
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
Err(e) => eprint!("{e}\r\n")
|
|
||||||
}
|
|
||||||
enable_raw_mode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut data = Data::default();
|
|
||||||
let home = env::home_dir().unwrap();
|
}
|
||||||
let home_str = home.into_os_string().into_string().unwrap();
|
|
||||||
data.set_hist_file(&format!("{}/.rush_history", home_str));
|
|
||||||
data.load_history();
|
|
||||||
|
|
||||||
let orig = enable_raw_mode();
|
|
||||||
|
|
||||||
rush_loop(&mut data, orig).unwrap();
|
|
||||||
|
|
||||||
disable_raw_mode(&orig);
|
|
||||||
}
|
|
||||||
24
src/raw.rs
24
src/raw.rs
|
|
@ -1,24 +0,0 @@
|
||||||
pub fn enable_raw_mode() -> libc::termios {
|
|
||||||
unsafe {
|
|
||||||
let fd = libc::STDIN_FILENO;
|
|
||||||
let mut termios = std::mem::zeroed();
|
|
||||||
libc::tcgetattr(fd, &mut termios);
|
|
||||||
|
|
||||||
let mut raw = termios;
|
|
||||||
raw.c_iflag &= !(libc::BRKINT | libc::ICRNL | libc::INPCK | libc::ISTRIP | libc::IXON);
|
|
||||||
raw.c_oflag &= !(libc::OPOST);
|
|
||||||
raw.c_cflag |= libc::CS8;
|
|
||||||
raw.c_lflag &= !(libc::ECHO | libc::ICANON | libc::IEXTEN | libc::ISIG);
|
|
||||||
raw.c_cc[libc::VMIN] = 1;
|
|
||||||
raw.c_cc[libc::VTIME] = 0;
|
|
||||||
libc::tcsetattr(fd, libc::TCSAFLUSH, &raw);
|
|
||||||
|
|
||||||
termios
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disable_raw_mode(orig: &libc::termios) {
|
|
||||||
unsafe {
|
|
||||||
libc::tcsetattr(libc::STDIN_FILENO, libc::TCSAFLUSH, orig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue