diff --git a/crates/steel-repl/src/repl.rs b/crates/steel-repl/src/repl.rs index 6863972eb..39f487ae9 100644 --- a/crates/steel-repl/src/repl.rs +++ b/crates/steel-repl/src/repl.rs @@ -1,10 +1,14 @@ extern crate rustyline; use colored::*; use rustyline::history::FileHistory; +use rustyline::{ + Cmd, ConditionalEventHandler, Event, EventContext, EventHandler, KeyEvent, RepeatCount, +}; use steel::compiler::modules::steel_home; use steel::rvals::{Custom, SteelString}; use std::error::Error; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::channel; use std::sync::{Arc, Mutex}; @@ -182,6 +186,33 @@ pub fn readline_module(vm: &mut Engine) { vm.register_module(module); } +struct CtrlCHandler { + close_on_interrupt: Arc, + empty_line_cancelled: AtomicBool, +} + +impl CtrlCHandler { + fn new(close_on_interrupt: Arc) -> Self { + CtrlCHandler { + close_on_interrupt, + empty_line_cancelled: AtomicBool::new(false), + } + } +} + +impl ConditionalEventHandler for CtrlCHandler { + fn handle(&self, _: &Event, _: RepeatCount, _: bool, ctx: &EventContext) -> Option { + if !ctx.line().is_empty() { + // if the line is not empty, reset the PREVIOUS_LINE_CANCELLED state + self.empty_line_cancelled.store(false, Ordering::Release); + } else if self.empty_line_cancelled.swap(true, Ordering::Release) { + self.close_on_interrupt.store(true, Ordering::Release); + } + + Some(Cmd::Interrupt) + } +} + /// Entry point for the repl /// Automatically adds the prelude and contracts for the core library pub fn repl_base(mut vm: Engine) -> std::io::Result<()> { @@ -236,7 +267,9 @@ pub fn repl_base(mut vm: Engine) -> std::io::Result<()> { safepoint.resume(); }; - let mut previous_line_cancelled = false; + let close_on_interrupt = Arc::new(AtomicBool::new(false)); + let ctrlc = Box::new(CtrlCHandler::new(close_on_interrupt.clone())); + rl.bind_sequence(KeyEvent::ctrl('c'), EventHandler::Conditional(ctrlc)); while rx.try_recv().is_err() { // Update globals for highlighting @@ -257,7 +290,6 @@ pub fn repl_base(mut vm: Engine) -> std::io::Result<()> { match readline { Ok(line) => { rl.add_history_entry(line.as_str()).ok(); - previous_line_cancelled = false; match line.as_str() { ":q" | ":quit" => return Ok(()), ":time" => { @@ -316,10 +348,9 @@ pub fn repl_base(mut vm: Engine) -> std::io::Result<()> { } } Err(ReadlineError::Interrupted) => { - if previous_line_cancelled { + if close_on_interrupt.load(Ordering::Acquire) { break; } else { - previous_line_cancelled = true; println!("CTRL-C"); continue; }