Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions codex-rs/tui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,11 @@ async fn run_ratatui_app(
resume_picker::SessionSelection::StartFresh
};

if let Err(err) = startup_draft.update_session_selection(&mut tui, &session_selection) {
shutdown_startup_session(app_server.take(), &mut terminal_restore_guard).await;
return Err(err.into());
}

if matches!(
&session_selection,
resume_picker::SessionSelection::Resume(_) | resume_picker::SessionSelection::Fork(_)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
source: tui/src/startup_draft_tests.rs
expression: "snapshots.join(\"\\n---\\n\")"
---
resume lookup fallback:
before:
╭───────────────────────────────────────╮
│ >_ OpenAI Codex (v<VERSION>) │
│ │
│ model: loading /model to change │
│ directory: loading │
╰───────────────────────────────────────╯
Resuming session…

› draft while loading


after:
╭───────────────────────────────────────╮
│ >_ OpenAI Codex (v<VERSION>) │
│ │
│ model: loading /model to change │
│ directory: loading │
╰───────────────────────────────────────╯


› draft while loading


---
fork lookup fallback:
before:
╭───────────────────────────────────────╮
│ >_ OpenAI Codex (v<VERSION>) │
│ │
│ model: loading /model to change │
│ directory: loading │
╰───────────────────────────────────────╯
Forking session…

› draft while loading


after:
╭───────────────────────────────────────╮
│ >_ OpenAI Codex (v<VERSION>) │
│ │
│ model: loading /model to change │
│ directory: loading │
╰───────────────────────────────────────╯


› draft while loading


---
resume picker cancellation:
before:
hidden while picker owns input
after:
╭───────────────────────────────────────╮
│ >_ OpenAI Codex (v<VERSION>) │
│ │
│ model: loading /model to change │
│ directory: loading │
╰───────────────────────────────────────╯


› Ask Codex to do anything

? for shortcuts
---
fork picker cancellation:
before:
hidden while picker owns input
after:
╭───────────────────────────────────────╮
│ >_ OpenAI Codex (v<VERSION>) │
│ │
│ model: loading /model to change │
│ directory: loading │
╰───────────────────────────────────────╯


› Ask Codex to do anything

? for shortcuts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,32 @@ empty (48 columns):
› Ask Codex to do anything

? for shortcuts
---
resuming (48 columns):
╭───────────────────────────────────────╮
│ >_ OpenAI Codex (v<VERSION>) │
│ │
│ model: loading /model to change │
│ directory: loading │
╰───────────────────────────────────────╯
Resuming session…

› Ask Codex to do anything

? for shortcuts
---
forking (48 columns):
╭───────────────────────────────────────╮
│ >_ OpenAI Codex (v<VERSION>) │
│ │
│ model: loading /model to change │
│ directory: loading │
╰───────────────────────────────────────╯
Forking session…

› draft while loading


---
multiline (48 columns):
╭───────────────────────────────────────╮
Expand Down
58 changes: 55 additions & 3 deletions codex-rs/tui/src/startup_draft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crossterm::event::KeyModifiers;
use ratatui::layout::Size;
use ratatui::style::Modifier;
use ratatui::style::Style;
use ratatui::style::Stylize;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::sync::mpsc::unbounded_channel;
use tokio_stream::Stream;
Expand All @@ -38,6 +39,7 @@ use crate::render::renderable::FlexRenderable;
use crate::render::renderable::Renderable;
use crate::render::renderable::RenderableExt;
use crate::render::renderable::RenderableItem;
use crate::resume_picker::SessionSelection;
use crate::tui;
use crate::tui::FrameRequester;
use crate::tui::Tui;
Expand All @@ -55,6 +57,14 @@ pub(crate) enum StartupDraftInitialScreen {
SessionPicker,
}

/// Describes the session being prepared behind the provisional composer.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum StartupDraftSessionAction {
New,
Resume,
Fork,
}

/// Marks intentional startup cancellation so unrelated I/O interrupts remain errors.
#[derive(Debug, thiserror::Error)]
#[error("startup cancelled")]
Expand Down Expand Up @@ -82,12 +92,16 @@ pub(crate) struct StartupDraftPump {
events: Pin<Box<dyn Stream<Item = TuiEvent> + Send>>,
app_event_rx: UnboundedReceiver<AppEvent>,
initial_screen: StartupDraftInitialScreen,
session_action: StartupDraftSessionAction,
pending_paste_newline: Option<(Instant, String)>,
}

impl StartupDraft {
/// Initialize the terminal without showing a composer before an expected session picker.
pub(crate) fn new(initial_screen: StartupDraftInitialScreen) -> io::Result<Self> {
pub(crate) fn new(
initial_screen: StartupDraftInitialScreen,
session_action: StartupDraftSessionAction,
) -> io::Result<Self> {
let mut initialized_terminal = tui::init()?;
let terminal_restore_guard = TerminalRestoreGuard::new();
initialized_terminal.terminal.clear()?;
Expand All @@ -113,6 +127,7 @@ impl StartupDraft {
events,
app_event_rx,
initial_screen,
session_action,
pending_paste_newline: None,
},
};
Expand Down Expand Up @@ -161,6 +176,27 @@ impl StartupDraftPump {
}
}

/// Align the provisional loading message with the resolved session selection.
pub(crate) fn update_session_selection(
&mut self,
tui: &mut Tui,
session_selection: &SessionSelection,
) -> io::Result<()> {
let session_action = match session_selection {
SessionSelection::StartFresh | SessionSelection::Exit => StartupDraftSessionAction::New,
SessionSelection::Resume(_) => StartupDraftSessionAction::Resume,
SessionSelection::Fork(_) => StartupDraftSessionAction::Fork,
};
if self.session_action == session_action {
return Ok(());
}
self.session_action = session_action;
if self.initial_screen == StartupDraftInitialScreen::Composer {
self.draw(tui, tui.terminal.last_known_screen_size)?;
}
Ok(())
}

/// Poll one existing startup future alongside the original terminal input stream.
pub(crate) async fn run_until<F>(&mut self, tui: &mut Tui, future: F) -> io::Result<F::Output>
where
Expand Down Expand Up @@ -336,7 +372,8 @@ impl StartupDraftPump {
.schedule_frame_in(ChatComposer::recommended_paste_flush_delay());
}
self.bottom_pane.pre_draw_tick();
let renderable = startup_draft_renderable(&self.header, &self.bottom_pane);
let renderable =
startup_draft_renderable(&self.header, &self.bottom_pane, self.session_action);
let desired_height = renderable.desired_height(screen_size.width);
tui.draw_with_resize_reflow(desired_height, screen_size, |frame| {
let area = frame.area();
Expand Down Expand Up @@ -427,15 +464,30 @@ fn startup_session_header(config: Option<&Config>) -> Box<dyn HistoryCell> {
fn startup_draft_renderable<'a>(
header: &'a dyn Renderable,
bottom_pane: &'a BottomPane,
session_action: StartupDraftSessionAction,
) -> RenderableItem<'a> {
let mut renderable = FlexRenderable::new();
renderable.push(/*flex*/ 1, RenderableItem::Borrowed(header));
let loading_message = match session_action {
StartupDraftSessionAction::New => None,
StartupDraftSessionAction::Resume => Some(" Resuming session…"),
StartupDraftSessionAction::Fork => Some(" Forking session…"),
};
if let Some(loading_message) = loading_message {
renderable.push(
/*flex*/ 0,
RenderableItem::Owned(Box::new(loading_message.dim())),
);
}
renderable.push(
/*flex*/ 0,
bottom_pane
.as_renderable_with_composer_right_reserve(/*composer_right_reserve*/ 0)
.inset(Insets::tlbr(
/*top*/ 1, /*left*/ 0, /*bottom*/ 0, /*right*/ 0,
/*top*/ u16::from(loading_message.is_none()),
/*left*/ 0,
/*bottom*/ 0,
/*right*/ 0,
)),
);
RenderableItem::Owned(Box::new(renderable))
Expand Down
Loading
Loading