mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
fix: map Esc to interrupt and Ctrl+C to graceful quit (#267)
* fix: map Esc to interrupt and Ctrl+C to graceful quit * Apply suggestions from code review Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: Illia Polosukhin <[email protected]> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Illia Polosukhin
gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
parent
b68d67bd35
commit
3d4c647216
+1
-1
@@ -82,7 +82,7 @@ clap = { version = "4", features = ["derive", "env"] }
|
||||
|
||||
# Terminal
|
||||
crossterm = "0.28"
|
||||
rustyline = { version = "17", features = ["derive", "with-file-history"] }
|
||||
rustyline = { version = "17", features = ["custom-bindings", "derive", "with-file-history"] }
|
||||
termimad = "0.34"
|
||||
|
||||
# Channel integrations
|
||||
|
||||
+41
-4
@@ -15,6 +15,7 @@
|
||||
//! - `/compact` - Compact the context
|
||||
//! - `/new` - Start a new thread
|
||||
//! - `yes`/`no`/`always` - Respond to tool approval prompts
|
||||
//! - `Esc` - Interrupt current operation
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::io::{self, Write};
|
||||
@@ -28,7 +29,10 @@ use rustyline::error::ReadlineError;
|
||||
use rustyline::highlight::Highlighter;
|
||||
use rustyline::hint::Hinter;
|
||||
use rustyline::validate::Validator;
|
||||
use rustyline::{CompletionType, Editor, Helper};
|
||||
use rustyline::{
|
||||
Cmd as ReadlineCmd, CompletionType, ConditionalEventHandler, Editor, Event, EventContext,
|
||||
EventHandler, Helper, KeyCode, KeyEvent, Modifiers, RepeatCount,
|
||||
};
|
||||
use termimad::MadSkin;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
@@ -121,6 +125,23 @@ impl Highlighter for ReplHelper {
|
||||
impl Validator for ReplHelper {}
|
||||
impl Helper for ReplHelper {}
|
||||
|
||||
struct EscInterruptHandler {
|
||||
triggered: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl ConditionalEventHandler for EscInterruptHandler {
|
||||
fn handle(
|
||||
&self,
|
||||
_evt: &Event,
|
||||
_n: RepeatCount,
|
||||
_positive: bool,
|
||||
_ctx: &EventContext,
|
||||
) -> Option<ReadlineCmd> {
|
||||
self.triggered.store(true, Ordering::Relaxed);
|
||||
Some(ReadlineCmd::Interrupt)
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a termimad skin with our color scheme.
|
||||
fn make_skin() -> MadSkin {
|
||||
let mut skin = MadSkin::default();
|
||||
@@ -247,6 +268,7 @@ fn print_help() {
|
||||
println!(" {c}/compact{r} {d}compact context window{r}");
|
||||
println!(" {c}/new{r} {d}new conversation thread{r}");
|
||||
println!(" {c}/interrupt{r} {d}stop current operation{r}");
|
||||
println!(" {c}esc{r} {d}stop current operation{r}");
|
||||
println!();
|
||||
println!(" {h}Approval responses{r}");
|
||||
println!(" {c}yes{r} ({c}y{r}) {d}approve tool execution{r}");
|
||||
@@ -274,6 +296,7 @@ impl Channel for ReplChannel {
|
||||
let single_message = self.single_message.clone();
|
||||
let debug_mode = Arc::clone(&self.debug_mode);
|
||||
let suppress_banner = Arc::clone(&self.suppress_banner);
|
||||
let esc_interrupt_triggered_for_thread = Arc::new(AtomicBool::new(false));
|
||||
|
||||
std::thread::spawn(move || {
|
||||
// Single message mode: send it and return
|
||||
@@ -301,6 +324,13 @@ impl Channel for ReplChannel {
|
||||
|
||||
rl.set_helper(Some(ReplHelper));
|
||||
|
||||
rl.bind_sequence(
|
||||
KeyEvent(KeyCode::Esc, Modifiers::NONE),
|
||||
EventHandler::Conditional(Box::new(EscInterruptHandler {
|
||||
triggered: Arc::clone(&esc_interrupt_triggered_for_thread),
|
||||
})),
|
||||
);
|
||||
|
||||
// Load history
|
||||
let hist_path = history_path();
|
||||
if let Some(parent) = hist_path.parent() {
|
||||
@@ -360,9 +390,16 @@ impl Channel for ReplChannel {
|
||||
}
|
||||
}
|
||||
Err(ReadlineError::Interrupted) => {
|
||||
// Ctrl+C: send /interrupt
|
||||
let msg = IncomingMessage::new("repl", "default", "/interrupt");
|
||||
if tx.blocking_send(msg).is_err() {
|
||||
if esc_interrupt_triggered_for_thread.swap(false, Ordering::Relaxed) {
|
||||
// Esc: interrupt current operation and keep REPL open.
|
||||
let msg = IncomingMessage::new("repl", "default", "/interrupt");
|
||||
if tx.blocking_send(msg).is_err() {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Ctrl+C (VINTR): request graceful shutdown.
|
||||
let msg = IncomingMessage::new("repl", "default", "/quit");
|
||||
let _ = tx.blocking_send(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user