tui: exit session on Ctrl+C in cwd change prompt (#12040)
## Summary - change the cwd-change prompt (shown when resuming/forking across different directories) so `Ctrl+C`/`Ctrl+D` exits the session instead of implicitly selecting "Use session directory" - introduce explicit prompt and resolver exit outcomes so this intent is propagated cleanly through both startup resume/fork and in-app `/resume` flows - add a unit test that verifies `Ctrl+C` exits rather than selecting an option ## Why Previously, pressing `Ctrl+C` on this prompt silently picked one of the options, which made it hard to abort. This aligns the prompt with the expected quit behavior. ## Codex author `codex resume 019c6d39-bbfb-7dc3-8008-1388a054e86d`
This commit is contained in:
parent
c4bb7db159
commit
709e2133bb
3 changed files with 67 additions and 15 deletions
|
|
@ -1452,8 +1452,11 @@ impl App {
|
|||
)
|
||||
.await?
|
||||
{
|
||||
Some(cwd) => cwd,
|
||||
None => current_cwd.clone(),
|
||||
crate::ResolveCwdOutcome::Continue(Some(cwd)) => cwd,
|
||||
crate::ResolveCwdOutcome::Continue(None) => current_cwd.clone(),
|
||||
crate::ResolveCwdOutcome::Exit => {
|
||||
return Ok(AppRunControl::Exit(ExitReason::UserRequested));
|
||||
}
|
||||
};
|
||||
let mut resume_config = if crate::cwds_differ(¤t_cwd, &resume_cwd) {
|
||||
match self.rebuild_config_for_cwd(resume_cwd).await {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,12 @@ pub(crate) enum CwdSelection {
|
|||
Session,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum CwdPromptOutcome {
|
||||
Selection(CwdSelection),
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl CwdSelection {
|
||||
fn next(self) -> Self {
|
||||
match self {
|
||||
|
|
@ -72,7 +78,7 @@ pub(crate) async fn run_cwd_selection_prompt(
|
|||
action: CwdPromptAction,
|
||||
current_cwd: &Path,
|
||||
session_cwd: &Path,
|
||||
) -> Result<CwdSelection> {
|
||||
) -> Result<CwdPromptOutcome> {
|
||||
let mut screen = CwdPromptScreen::new(
|
||||
tui.frame_requester(),
|
||||
action,
|
||||
|
|
@ -102,7 +108,13 @@ pub(crate) async fn run_cwd_selection_prompt(
|
|||
}
|
||||
}
|
||||
|
||||
Ok(screen.selection().unwrap_or(CwdSelection::Session))
|
||||
if screen.should_exit {
|
||||
Ok(CwdPromptOutcome::Exit)
|
||||
} else {
|
||||
Ok(CwdPromptOutcome::Selection(
|
||||
screen.selection().unwrap_or(CwdSelection::Session),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct CwdPromptScreen {
|
||||
|
|
@ -112,6 +124,7 @@ struct CwdPromptScreen {
|
|||
session_cwd: String,
|
||||
highlighted: CwdSelection,
|
||||
selection: Option<CwdSelection>,
|
||||
should_exit: bool,
|
||||
}
|
||||
|
||||
impl CwdPromptScreen {
|
||||
|
|
@ -128,6 +141,7 @@ impl CwdPromptScreen {
|
|||
session_cwd,
|
||||
highlighted: CwdSelection::Session,
|
||||
selection: None,
|
||||
should_exit: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +152,9 @@ impl CwdPromptScreen {
|
|||
if key_event.modifiers.contains(KeyModifiers::CONTROL)
|
||||
&& matches!(key_event.code, KeyCode::Char('c') | KeyCode::Char('d'))
|
||||
{
|
||||
self.select(CwdSelection::Session);
|
||||
self.selection = None;
|
||||
self.should_exit = true;
|
||||
self.request_frame.schedule_frame();
|
||||
return;
|
||||
}
|
||||
match key_event.code {
|
||||
|
|
@ -166,7 +182,7 @@ impl CwdPromptScreen {
|
|||
}
|
||||
|
||||
fn is_done(&self) -> bool {
|
||||
self.selection.is_some()
|
||||
self.should_exit || self.selection.is_some()
|
||||
}
|
||||
|
||||
fn selection(&self) -> Option<CwdSelection> {
|
||||
|
|
@ -283,4 +299,12 @@ mod tests {
|
|||
screen.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
assert_eq!(screen.selection(), Some(CwdSelection::Current));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cwd_prompt_ctrl_c_exits_instead_of_selecting() {
|
||||
let mut screen = new_prompt();
|
||||
screen.handle_key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL));
|
||||
assert_eq!(screen.selection(), None);
|
||||
assert!(screen.is_done());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
|||
use codex_utils_oss::ensure_oss_provider_ready;
|
||||
use codex_utils_oss::get_default_model_for_oss_provider;
|
||||
use cwd_prompt::CwdPromptAction;
|
||||
use cwd_prompt::CwdPromptOutcome;
|
||||
use cwd_prompt::CwdSelection;
|
||||
use std::fs::OpenOptions;
|
||||
use std::path::Path;
|
||||
|
|
@ -672,8 +673,22 @@ async fn run_ratatui_app(
|
|||
};
|
||||
let fallback_cwd = match action_and_path_if_resume_or_fork {
|
||||
Some((action, path)) => {
|
||||
resolve_cwd_for_resume_or_fork(&mut tui, ¤t_cwd, path, action, allow_prompt)
|
||||
match resolve_cwd_for_resume_or_fork(&mut tui, ¤t_cwd, path, action, allow_prompt)
|
||||
.await?
|
||||
{
|
||||
ResolveCwdOutcome::Continue(cwd) => cwd,
|
||||
ResolveCwdOutcome::Exit => {
|
||||
restore();
|
||||
session_log::log_session_end();
|
||||
return Ok(AppExitInfo {
|
||||
token_usage: codex_core::protocol::TokenUsage::default(),
|
||||
thread_id: None,
|
||||
thread_name: None,
|
||||
update_action: None,
|
||||
exit_reason: ExitReason::UserRequested,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
|
@ -780,25 +795,35 @@ pub(crate) fn cwds_differ(current_cwd: &Path, session_cwd: &Path) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) enum ResolveCwdOutcome {
|
||||
Continue(Option<PathBuf>),
|
||||
Exit,
|
||||
}
|
||||
|
||||
pub(crate) async fn resolve_cwd_for_resume_or_fork(
|
||||
tui: &mut Tui,
|
||||
current_cwd: &Path,
|
||||
path: &Path,
|
||||
action: CwdPromptAction,
|
||||
allow_prompt: bool,
|
||||
) -> color_eyre::Result<Option<PathBuf>> {
|
||||
) -> color_eyre::Result<ResolveCwdOutcome> {
|
||||
let Some(history_cwd) = read_session_cwd(path).await else {
|
||||
return Ok(None);
|
||||
return Ok(ResolveCwdOutcome::Continue(None));
|
||||
};
|
||||
if allow_prompt && cwds_differ(current_cwd, &history_cwd) {
|
||||
let selection =
|
||||
let selection_outcome =
|
||||
cwd_prompt::run_cwd_selection_prompt(tui, action, current_cwd, &history_cwd).await?;
|
||||
return Ok(Some(match selection {
|
||||
CwdSelection::Current => current_cwd.to_path_buf(),
|
||||
CwdSelection::Session => history_cwd,
|
||||
}));
|
||||
return Ok(match selection_outcome {
|
||||
CwdPromptOutcome::Selection(CwdSelection::Current) => {
|
||||
ResolveCwdOutcome::Continue(Some(current_cwd.to_path_buf()))
|
||||
}
|
||||
CwdPromptOutcome::Selection(CwdSelection::Session) => {
|
||||
ResolveCwdOutcome::Continue(Some(history_cwd))
|
||||
}
|
||||
CwdPromptOutcome::Exit => ResolveCwdOutcome::Exit,
|
||||
});
|
||||
}
|
||||
Ok(Some(history_cwd))
|
||||
Ok(ResolveCwdOutcome::Continue(Some(history_cwd)))
|
||||
}
|
||||
|
||||
#[expect(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue