This commit is contained in:
Raptorox 2025-09-20 23:18:12 +02:00
parent d7fc72e65d
commit 03131fda71
No known key found for this signature in database
GPG key ID: 8B3556FC3ED1F6D8
3 changed files with 83 additions and 45 deletions

View file

@ -1,4 +1,9 @@
use std::{env, fs::{File, OpenOptions}, io::{BufRead, ErrorKind, Write}, path::PathBuf}; use std::{
env,
fs::{File, OpenOptions},
io::{BufRead, ErrorKind, Write},
path::PathBuf,
};
#[derive(Debug)] #[derive(Debug)]
pub struct Data { pub struct Data {
@ -8,7 +13,7 @@ pub struct Data {
history: Vec<String>, history: Vec<String>,
hist_file: String, hist_file: String,
hist_pos: usize, hist_pos: usize,
hist_partial: String hist_partial: String,
} }
impl Default for Data { impl Default for Data {
@ -27,7 +32,7 @@ impl Default for Data {
history, history,
hist_file, hist_file,
hist_pos, hist_pos,
hist_partial hist_partial,
} }
} }
} }
@ -57,20 +62,32 @@ impl Data {
} }
pub fn get_prev_hist_item(&mut self) -> Option<String> { pub fn get_prev_hist_item(&mut self) -> Option<String> {
if self.history.is_empty() { return None; } if self.history.is_empty() {
if self.hist_pos <= self.history.len() && self.hist_pos > 0 { self.hist_pos -= 1; Some(self.history[self.hist_pos].clone()) } return None;
else { 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> { 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() {
if self.hist_pos < self.history.len() { Some(self.history[self.hist_pos].clone()) } self.hist_pos += 1;
else if self.hist_pos == self.history.len() { Some(self.hist_partial.clone()) } }
else { None } 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 { pub fn at_hist_end(&self) -> bool {
self.hist_pos+1 == self.history.len() self.hist_pos + 1 == self.history.len()
} }
pub fn set_hist_file(&mut self, hist_file: &str) { pub fn set_hist_file(&mut self, hist_file: &str) {
@ -78,15 +95,20 @@ impl Data {
} }
pub fn save_history(&self) { pub fn save_history(&self) {
let mut hist_file = match OpenOptions::new().create(true).write(true).open(&self.hist_file) { let mut hist_file = match OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&self.hist_file)
{
Ok(file) => file, Ok(file) => file,
Err(e) => { Err(e) => {
eprint!("\r\n{e}"); eprint!("\r\n{e}");
return return;
} }
}; };
for line in &self.history { for line in &self.history {
write!(hist_file, "{line}\n").unwrap(); writeln!(hist_file, "{line}").unwrap();
} }
} }
@ -94,9 +116,12 @@ impl Data {
let hist_file = match File::open(&self.hist_file) { let hist_file = match File::open(&self.hist_file) {
Ok(file) => file, Ok(file) => file,
Err(e) => { Err(e) => {
if e.kind() == ErrorKind::NotFound {eprint!("\r\nHistory file not found, creating")} if e.kind() == ErrorKind::NotFound {
else { eprint!("\r\n{e}"); } eprint!("\r\nHistory file not found, creating")
return } else {
eprint!("\r\n{e}");
}
return;
} }
}; };
let reader = std::io::BufReader::new(hist_file); let reader = std::io::BufReader::new(hist_file);
@ -106,10 +131,10 @@ impl Data {
Ok(line) => { Ok(line) => {
self.history.push(line.trim().to_string()); self.history.push(line.trim().to_string());
self.hist_pos += 1; self.hist_pos += 1;
}, }
Err(e) => { Err(e) => {
eprint!("\r\n{e}"); eprint!("\r\n{e}");
return return;
} }
} }
} }

View file

@ -1,4 +1,4 @@
use std::io::{stdin, Read}; use std::io::{Read, stdin};
#[derive(PartialEq)] #[derive(PartialEq)]
pub enum Key { pub enum Key {
@ -10,8 +10,7 @@ pub enum Key {
ArrowDown, ArrowDown,
ArrowRight, ArrowRight,
ArrowLeft, ArrowLeft,
#[allow(dead_code)] Unknown(Vec<u8>),
Unknown(Vec<u8>)
} }
impl From<u8> for Key { impl From<u8> for Key {
@ -30,11 +29,11 @@ impl From<u8> for Key {
[b'[', b'B'] => Key::ArrowDown, [b'[', b'B'] => Key::ArrowDown,
[b'[', b'C'] => Key::ArrowRight, [b'[', b'C'] => Key::ArrowRight,
[b'[', b'D'] => Key::ArrowLeft, [b'[', b'D'] => Key::ArrowLeft,
_ => Key::Unknown(vec![27, buf[0], buf[1]]) _ => Key::Unknown(vec![27, buf[0], buf[1]]),
}
} }
},
char if char.is_ascii() => Key::Char(byte as char), char if char.is_ascii() => Key::Char(byte as char),
byte => Key::Unknown(vec![byte]) byte => Key::Unknown(vec![byte]),
} }
} }
} }

View file

@ -1,12 +1,12 @@
use std::{ use std::{
env, env,
io::{self, stdin, stdout, Read, Stdin, Stdout, Write}, io::{self, Read, Stdin, Stdout, Write, stdin, stdout},
process::Command, process::Command,
str::SplitWhitespace, str::SplitWhitespace,
}; };
mod raw; mod raw;
use raw::{enable_raw_mode, disable_raw_mode}; use raw::{disable_raw_mode, enable_raw_mode};
mod key; mod key;
use key::Key; use key::Key;
@ -16,7 +16,11 @@ use data::Data;
fn print_prompt(pre_input: &str, data: &mut Data, newline: bool) -> io::Result<()> { fn print_prompt(pre_input: &str, data: &mut Data, newline: bool) -> io::Result<()> {
if newline { if newline {
print!("\r\n{}\r\n> {}", data.get_current_path().display(), pre_input); print!(
"\r\n{}\r\n> {}",
data.get_current_path().display(),
pre_input
);
} else { } else {
print!("\r> {}", pre_input); print!("\r> {}", pre_input);
} }
@ -55,21 +59,23 @@ fn handle_input(stdin: &mut Stdin, stdout: &mut Stdout, data: &mut Data) -> io::
Key::Escape => continue, Key::Escape => continue,
Key::ArrowUp => { Key::ArrowUp => {
if let Some(hist_command) = data.get_prev_hist_item() { if let Some(hist_command) = data.get_prev_hist_item() {
if data.at_hist_end() { data.save_command(input.clone()); } if data.at_hist_end() {
data.save_command(input.clone());
}
flush_prompt(input.len())?; flush_prompt(input.len())?;
input = hist_command.clone(); input = hist_command.clone();
print_prompt(&input, data, false)?; print_prompt(&input, data, false)?;
} }
}, }
Key::ArrowDown => { Key::ArrowDown => {
if let Some(hist_command) = data.get_next_hist_item() { if let Some(hist_command) = data.get_next_hist_item() {
flush_prompt(input.len())?; flush_prompt(input.len())?;
input = hist_command.clone(); input = hist_command.clone();
print_prompt(&input, data, false)?; print_prompt(&input, data, false)?;
} }
}, }
Key::ArrowLeft => {}, Key::ArrowLeft => {}
Key::ArrowRight => {}, Key::ArrowRight => {}
Key::Char(c) => { Key::Char(c) => {
input.push(c); input.push(c);
write!(stdout, "{c}")?; write!(stdout, "{c}")?;
@ -94,13 +100,15 @@ where
fn run_command(command: &str, args: SplitWhitespace, data: &mut Data) -> Result<u8, String> { fn run_command(command: &str, args: SplitWhitespace, data: &mut Data) -> Result<u8, String> {
match command { match command {
"exit" => return Ok(1), "exit" => Ok(1),
"cd" => { "cd" => {
let path = args.peekable().peek().map_or("~", |dir| *dir); let path = args.peekable().peek().map_or("~", |dir| *dir);
let mut new_path = data.get_current_path(); let mut new_path = data.get_current_path();
if path.chars().nth(0).unwrap() == '/' { new_path.push("/"); } if path.starts_with('/') {
new_path.push("/");
}
for subpath in path.split("/") { for subpath in path.split("/") {
match subpath { match subpath {
"-" => { "-" => {
@ -129,8 +137,11 @@ fn run_command(command: &str, args: SplitWhitespace, data: &mut Data) -> Result<
let child = Command::new(command).args(args).spawn(); let child = Command::new(command).args(args).spawn();
match child { match child {
Ok(mut child) => { child.wait().unwrap(); Ok(0) } Ok(mut child) => {
Err(e) => { return Err(e.to_string()); } child.wait().unwrap();
Ok(0)
}
Err(e) => Err(e.to_string()),
} }
} }
} }
@ -145,17 +156,20 @@ fn rush_loop(data: &mut Data, orig: libc::termios) -> io::Result<()> {
let input = handle_input(&mut stdin, &mut stdout, data)?; let input = handle_input(&mut stdin, &mut stdout, data)?;
let input = input.trim(); let input = input.trim();
if input.is_empty() { continue; } if input.is_empty() {
continue;
}
let input = input.split_whitespace(); let input = input.split_whitespace();
let (command, args) = parse_input(input); let (command, args) = parse_input(input);
disable_raw_mode(&orig); disable_raw_mode(&orig);
match run_command(command, args, data) { match run_command(command, args, data) {
Ok(status) => match status { Ok(status) => {
1 => return Ok(()), if status == 1 {
_ => {} return Ok(());
}, }
Err(e) => eprint!("{e}\r\n") }
Err(e) => eprint!("{e}\r\n"),
} }
enable_raw_mode(); enable_raw_mode();
} }