diff --git a/src/repl/init.rs b/src/repl/init.rs index 1204fa78..9cef4e52 100644 --- a/src/repl/init.rs +++ b/src/repl/init.rs @@ -32,15 +32,12 @@ impl Repl { let edit_mode: Box = if config.read().vi_keybindings { let mut normal_keybindings = default_vi_normal_keybindings(); let mut insert_keybindings = default_vi_insert_keybindings(); - Self::add_menu_keybindings(&mut normal_keybindings); - Self::add_menu_keybindings(&mut insert_keybindings); - Self::add_clear_keybindings(&mut normal_keybindings); - Self::add_clear_keybindings(&mut insert_keybindings); + Self::extra_keybindings(&mut normal_keybindings); + Self::extra_keybindings(&mut insert_keybindings); Box::new(Vi::new(insert_keybindings, normal_keybindings)) } else { let mut keybindings = default_emacs_keybindings(); - Self::add_menu_keybindings(&mut keybindings); - Self::add_clear_keybindings(&mut keybindings); + Self::extra_keybindings(&mut keybindings); Box::new(Emacs::new(keybindings)) }; let mut editor = Reedline::create() @@ -67,7 +64,7 @@ impl Repl { completer } - fn add_menu_keybindings(keybindings: &mut Keybindings) { + fn extra_keybindings(keybindings: &mut Keybindings) { keybindings.add_binding( KeyModifiers::NONE, KeyCode::Tab, @@ -76,14 +73,16 @@ impl Repl { ReedlineEvent::MenuNext, ]), ); - } - - fn add_clear_keybindings(keybindings: &mut Keybindings) { keybindings.add_binding( KeyModifiers::CONTROL, KeyCode::Char('l'), ReedlineEvent::ExecuteHostCommand(".clear screen".into()), ); + keybindings.add_binding( + KeyModifiers::CONTROL, + KeyCode::Char('s'), + ReedlineEvent::Submit, + ); } fn create_menu() -> ReedlineMenu { diff --git a/src/repl/mod.rs b/src/repl/mod.rs index ecb58d0b..2945b4cc 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -14,10 +14,12 @@ use crate::print_now; use crate::term; use anyhow::{Context, Result}; +use fancy_regex::Regex; +use lazy_static::lazy_static; use reedline::Signal; use std::rc::Rc; -pub const REPL_COMMANDS: [(&str, &str); 14] = [ +pub const REPL_COMMANDS: [(&str, &str); 15] = [ (".info", "Print system-wide information"), (".set", "Modify the configuration temporarily"), (".model", "Choose a model"), @@ -28,12 +30,17 @@ pub const REPL_COMMANDS: [(&str, &str); 14] = [ (".clear conversation", "End current conversation."), (".copy", "Copy the last output to the clipboard"), (".read", "Read the contents of a file into the prompt"), + (".edit", "Multi-line editing (CTRL+S to finish)"), (".history", "Print the history"), (".clear history", "Clear the history"), (".help", "Print this help message"), (".exit", "Exit the REPL"), ]; +lazy_static! { + static ref EDIT_RE: Regex = Regex::new(r"^\s*.edit\s*").unwrap(); +} + impl Repl { pub fn run(&mut self, config: SharedConfig) -> Result<()> { let abort = AbortSignal::new(); @@ -85,8 +92,7 @@ impl Repl { } fn handle_line(&mut self, handler: &Rc, line: &str) -> Result { - let line = line.trim().replace("\\\n", "\n"); - match parse_command(line.as_ref()) { + match parse_command(line) { Some((cmd, args)) => match cmd { ".exit" => { return Ok(true); @@ -119,10 +125,6 @@ impl Repl { Some(name) => handler.handle(ReplCmd::SetRole(name.to_string()))?, None => print_now!("Usage: .role \n\n"), }, - ".read" => match args { - Some(file) => handler.handle(ReplCmd::ReadFile(file.to_string()))?, - None => print_now!("Usage: .read \n\n"), - }, ".info" => { handler.handle(ReplCmd::ViewInfo)?; } @@ -144,6 +146,15 @@ impl Repl { ".copy" => { handler.handle(ReplCmd::Copy)?; } + ".read" => match args { + Some(file) => handler.handle(ReplCmd::ReadFile(file.to_string()))?, + None => print_now!("Usage: .read \n\n"), + }, + ".edit" => { + if let Some(text) = args { + handler.handle(ReplCmd::Submit(text.to_string()))?; + } + } _ => dump_unknown_command(), }, None => { diff --git a/src/repl/validator.rs b/src/repl/validator.rs index 1a5f4054..21a7a3c5 100644 --- a/src/repl/validator.rs +++ b/src/repl/validator.rs @@ -1,12 +1,13 @@ +use super::EDIT_RE; + use reedline::{ValidationResult, Validator}; /// A default validator which checks for mismatched quotes and brackets -#[allow(clippy::module_name_repetitions)] pub struct ReplValidator; impl Validator for ReplValidator { fn validate(&self, line: &str) -> ValidationResult { - if line.ends_with('\\') { + if let Ok(true) = EDIT_RE.is_match(line) { ValidationResult::Incomplete } else { ValidationResult::Complete