diff --git a/README.md b/README.md index c61c469..e91b0d8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ Sqish is now my default shell on my home computer, and I'll keep adding features 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: -* Before anything : keep reformating the code. The main loop could use a struct containing current_pos, max_pos, etc. * Allow redirecting >> to the end of a file ? * Allow && in commands ? * Improve word jumping diff --git a/src/lib.rs b/src/lib.rs index 386f394..babd18c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ pub mod shell { 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; @@ -258,8 +258,6 @@ pub mod shell { } - - fn run_cmd(mycommand: &mut String, current_number: &mut i32, conf: &mut SqishConf, @@ -368,262 +366,334 @@ pub mod shell { }).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); + } + + 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, + //Current history number + current_number: i32, + //Configuration + conf: SqishConf, + } + + 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::(); + 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); + }, + _ => (), + }; + } + + //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(); - 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; - - //Get conf - let mut conf = handle_conf(&mut stdout); //Handle Ctrl+C set_ctrlc(); - &conf.update_prompt(get_curr_history_number()); + &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(); - 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, "{}", 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::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') => { - write!(stdout, "{}{}", termion::clear::All, termion::cursor::Goto(1,1)); - write!(stdout, "{}{}", conf.promptline, mycommand); - + screen_clear(&mut elems); }, Key::Ctrl('p') => { - let steps = find_next_space(¤t_pos, &max_pos, &mycommand); - //println!("steps: {:?} cur {:?} max {:?}", steps, current_pos, max_pos); - if steps > 0 { - print!("{}", cursor::Right(steps as u16)); - current_pos += steps; - } + jmp_nxt_word(&mut elems); }, Key::Ctrl('o') => { - let command_rev = mycommand.chars().rev().collect::(); - let curr_rev = max_pos - current_pos + 1; - let steps = find_next_space(&curr_rev, &max_pos, &command_rev); - if steps > 0 { - print!("{}", cursor::Left(steps as u16)); - current_pos -= steps; - } + 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-- HOTKEYS --> \r\n{:?}\r\n", conf.hotkeys); - cmd = format!("{}\r\n-- ALIASES -->\r\n{:?}\r\n", cmd, conf.aliases); - cmd = cmd.replace(",", "\r\n"); - cmd = cmd.replace('"', ""); - write!(stdout, "{}", cmd); - }, - - _ => (), - }; + press_alt(&mut elems, x); }, _ => (), }; - stdout.flush().unwrap(); + elems.stdout.flush().unwrap(); } }