diff --git a/src/helper.rs b/src/helper.rs index addc385..0512173 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -1,15 +1,29 @@ +use std::cell::RefCell; +use std::rc::Rc; + use rustyline::completion::{Completer, Pair, extract_word}; use rustyline::highlight::Highlighter; use rustyline::hint::Hinter; use rustyline::validate::Validator; use rustyline::{Context, Helper, Result}; +use crate::state::ShellState; + const BUILTINS: &[&str] = &[ "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "base", "header", "exit", "help", - "history", "rerun", "set", "unset", "save", "run", "vars", "headers", "requests", "clear", + "history", "rerun", "set", "timeout", "unset", "save", "run", "vars", "headers", "requests", + "clear", ]; -pub struct ShellHelper; +pub struct ShellHelper { + state: Rc>, +} + +impl ShellHelper { + pub fn new(state: Rc>) -> Self { + ShellHelper { state } + } +} impl Helper for ShellHelper {} impl Hinter for ShellHelper { @@ -23,9 +37,9 @@ impl Completer for ShellHelper { fn complete(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Result<(usize, Vec)> { let (start, word) = extract_word(line, pos, None, |c| c == ' '); - let first_word = line[..start].trim().is_empty(); + let before_cursor = line[..start].trim(); - if first_word { + if before_cursor.is_empty() { let matches: Vec = BUILTINS .iter() .copied() @@ -39,6 +53,70 @@ impl Completer for ShellHelper { return Ok((start, matches)); } + let parts: Vec<&str> = before_cursor.split_whitespace().collect(); + + match parts[0] { + "run" => { + let state = self.state.borrow(); + let matches: Vec = state + .get_all_requests() + .keys() + .filter(|name| name.starts_with(word)) + .map(|name| Pair { + display: name.clone(), + replacement: name.clone(), + }) + .collect(); + return Ok((start, matches)); + } + "unset" if parts.len() == 1 => { + let state = self.state.borrow(); + let mut matches: Vec = state + .get_variables() + .keys() + .filter(|name| name.starts_with(word)) + .map(|name| Pair { + display: name.clone(), + replacement: name.clone(), + }) + .collect(); + if "header".starts_with(word) { + matches.push(Pair { + display: "header".to_string(), + replacement: "header".to_string(), + }); + } + return Ok((start, matches)); + } + "unset" if parts.len() >= 2 && parts[1] == "header" => { + let state = self.state.borrow(); + let matches: Vec = state + .get_headers() + .keys() + .filter(|name| name.starts_with(word)) + .map(|name| Pair { + display: name.clone(), + replacement: name.clone(), + }) + .collect(); + return Ok((start, matches)); + } + "header" => { + let state = self.state.borrow(); + let matches: Vec = state + .get_headers() + .keys() + .filter(|name| name.starts_with(word)) + .map(|name| Pair { + display: name.clone(), + replacement: name.clone(), + }) + .collect(); + return Ok((start, matches)); + } + _ => {} + } + Ok((start, vec![])) } } diff --git a/src/main.rs b/src/main.rs index fd195db..2aeddce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ +use std::cell::RefCell; use std::env; use std::path::PathBuf; +use std::rc::Rc; use reqsh::builtin::{ControlFlow, handle}; use reqsh::help::help_text; @@ -20,7 +22,7 @@ fn history_path() -> PathBuf { home.join(".reqsh_history") } -fn run_repl(ctx: &mut ShellState) { +fn run_repl(ctx: Rc>) { let config = Config::builder() .history_ignore_space(true) .completion_type(CompletionType::List) @@ -29,7 +31,7 @@ fn run_repl(ctx: &mut ShellState) { let hist_path = history_path(); let mut rl = Editor::with_config(config).unwrap(); - rl.set_helper(Some(ShellHelper)); + rl.set_helper(Some(ShellHelper::new(ctx.clone()))); rl.load_history(&hist_path).unwrap_or_default(); loop { @@ -53,19 +55,23 @@ fn run_repl(ctx: &mut ShellState) { match parse(raw) { Ok(parsed) => match parsed { - Parsed::Builtin(cmd) => match handle(cmd, ctx, rl.history()) { - Ok(ControlFlow::Continue) => {} - Ok(ControlFlow::Exit) => { - break; - } - Err(e) => { - eprintln!("{}", e.red().bold()); + Parsed::Builtin(cmd) => { + let mut state = ctx.borrow_mut(); + match handle(cmd, &mut state, rl.history()) { + Ok(ControlFlow::Continue) => {} + Ok(ControlFlow::Exit) => { + break; + } + Err(e) => { + eprintln!("{}", e.red().bold()); + } } - }, + } Parsed::Request(req) => { - ctx.set_last_request(req.clone()); - match execute(req, ctx) { + ctx.borrow_mut().set_last_request(req.clone()); + let state = ctx.borrow(); + match execute(req, &state) { Ok(res) => { println!("{}", res); } @@ -149,8 +155,8 @@ fn main() { match args.as_slice() { [] => { - let mut ctx = ShellState::new(); - run_repl(&mut ctx); + let ctx = Rc::new(RefCell::new(ShellState::new())); + run_repl(ctx); } [arg] if arg == "--help" || arg == "-h" => { @@ -166,9 +172,9 @@ fn main() { eprintln!("Invalid timeout: {value}"); std::process::exit(1); }); - let mut ctx = ShellState::new(); - ctx.set_timeout(secs); - run_repl(&mut ctx); + let ctx = Rc::new(RefCell::new(ShellState::new())); + ctx.borrow_mut().set_timeout(secs); + run_repl(ctx); } [unknown] => {