feat: ctrl-d only exits when there is no user input (#1589)

While this does make it so that `ctrl-d` will not exit Codex when the
composer is not empty, `ctrl-d` will still exit Codex if it is in the
"working" state.

Fixes https://github.com/openai/codex/issues/1443.
This commit is contained in:
Michael Bolin
2025-07-16 08:59:26 -07:00
committed by GitHub
parent f14b5adabf
commit 5b820c5ce7
5 changed files with 30 additions and 3 deletions

View File

@@ -199,7 +199,21 @@ impl<'a> App<'a> {
modifiers: crossterm::event::KeyModifiers::CONTROL, modifiers: crossterm::event::KeyModifiers::CONTROL,
.. ..
} => { } => {
self.app_event_tx.send(AppEvent::ExitRequest); match &mut self.app_state {
AppState::Chat { widget } => {
if widget.composer_is_empty() {
self.app_event_tx.send(AppEvent::ExitRequest);
} else {
// Treat Ctrl+D as a normal key event when the composer
// is not empty so that it doesn't quit the application
// prematurely.
self.dispatch_key_event(key_event);
}
}
AppState::Login { .. } | AppState::GitWarning { .. } => {
self.app_event_tx.send(AppEvent::ExitRequest);
}
}
} }
_ => { _ => {
self.dispatch_key_event(key_event); self.dispatch_key_event(key_event);

View File

@@ -76,6 +76,11 @@ impl ChatComposer<'_> {
this this
} }
/// Returns true if the composer currently contains no user input.
pub(crate) fn is_empty(&self) -> bool {
self.textarea.is_empty()
}
/// Update the cached *context-left* percentage and refresh the placeholder /// Update the cached *context-left* percentage and refresh the placeholder
/// text. The UI relies on the placeholder to convey the remaining /// text. The UI relies on the placeholder to convey the remaining
/// context when the composer is empty. /// context when the composer is empty.

View File

@@ -72,8 +72,7 @@ impl ChatComposerHistory {
return false; return false;
} }
let lines = textarea.lines(); if textarea.is_empty() {
if lines.len() == 1 && lines[0].is_empty() {
return true; return true;
} }
@@ -85,6 +84,7 @@ impl ChatComposerHistory {
return false; return false;
} }
let lines = textarea.lines();
matches!(&self.last_history_text, Some(prev) if prev == &lines.join("\n")) matches!(&self.last_history_text, Some(prev) if prev == &lines.join("\n"))
} }

View File

@@ -162,6 +162,10 @@ impl BottomPane<'_> {
} }
} }
pub(crate) fn composer_is_empty(&self) -> bool {
self.composer.is_empty()
}
pub(crate) fn is_task_running(&self) -> bool { pub(crate) fn is_task_running(&self) -> bool {
self.is_task_running self.is_task_running
} }

View File

@@ -432,6 +432,10 @@ impl ChatWidget<'_> {
} }
} }
pub(crate) fn composer_is_empty(&self) -> bool {
self.bottom_pane.composer_is_empty()
}
/// Forward an `Op` directly to codex. /// Forward an `Op` directly to codex.
pub(crate) fn submit_op(&self, op: Op) { pub(crate) fn submit_op(&self, op: Op) {
if let Err(e) = self.codex_op_tx.send(op) { if let Err(e) = self.codex_op_tx.send(op) {