mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Speed up /side parent restore replay (#20815)
## Why Returning from a `/side` conversation restores the parent thread by replaying its snapshot into the TUI. For very long parent threads, replaying every transcript row can take noticeable time even though most rows immediately scroll out of terminal history. ## What Changed - Buffer thread-switch replay for parent restores when terminal resize reflow is enabled. - Reuse the existing resize-reflow tail renderer so only the retained transcript tail is written back to scrollback when a row cap is configured.
This commit is contained in:
committed by
GitHub
Unverified
parent
3c2dcbef85
commit
f072119ccf
@@ -423,6 +423,7 @@ struct SessionSummary {
|
||||
#[derive(Debug, Default)]
|
||||
struct InitialHistoryReplayBuffer {
|
||||
retained_lines: VecDeque<Line<'static>>,
|
||||
render_from_transcript_tail: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct App {
|
||||
|
||||
@@ -182,6 +182,9 @@ impl App {
|
||||
AppEvent::BeginInitialHistoryReplayBuffer => {
|
||||
self.begin_initial_history_replay_buffer();
|
||||
}
|
||||
AppEvent::BeginThreadSwitchHistoryReplayBuffer => {
|
||||
self.begin_thread_switch_history_replay_buffer();
|
||||
}
|
||||
AppEvent::InsertHistoryCell(cell) => {
|
||||
let cell: Arc<dyn HistoryCell> = cell.into();
|
||||
if let Some(Overlay::Transcript(t)) = &mut self.overlay {
|
||||
|
||||
@@ -119,6 +119,23 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
/// Start retaining a thread-switch transcript replay without rendering each historical cell.
|
||||
///
|
||||
/// Thread switches already rebuild `transcript_cells` from source. When a row cap exists, we can
|
||||
/// defer terminal writes until the replay is complete and reuse the resize-reflow tail renderer
|
||||
/// so only the rows the terminal would retain are formatted and inserted.
|
||||
pub(super) fn begin_thread_switch_history_replay_buffer(&mut self) {
|
||||
if self.terminal_resize_reflow_enabled()
|
||||
&& self.resize_reflow_max_rows().is_some()
|
||||
&& self.overlay.is_none()
|
||||
{
|
||||
self.initial_history_replay_buffer = Some(InitialHistoryReplayBuffer {
|
||||
retained_lines: VecDeque::new(),
|
||||
render_from_transcript_tail: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Flush retained initial resume replay rows into terminal scrollback.
|
||||
///
|
||||
/// The buffer stores display lines, not cells, because the cap is measured in terminal rows.
|
||||
@@ -130,6 +147,13 @@ impl App {
|
||||
};
|
||||
|
||||
if buffer.retained_lines.is_empty() {
|
||||
if buffer.render_from_transcript_tail {
|
||||
let width = tui.terminal.last_known_screen_size.width;
|
||||
let reflowed_lines = self.render_transcript_lines_for_reflow(width).lines;
|
||||
if !reflowed_lines.is_empty() {
|
||||
tui.insert_history_lines(reflowed_lines);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -143,6 +167,14 @@ impl App {
|
||||
cell: &dyn HistoryCell,
|
||||
width: u16,
|
||||
) {
|
||||
if self
|
||||
.initial_history_replay_buffer
|
||||
.as_ref()
|
||||
.is_some_and(|buffer| buffer.render_from_transcript_tail)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let display = self.display_lines_for_history_insert(cell, width);
|
||||
|
||||
if display.is_empty() {
|
||||
|
||||
@@ -4022,6 +4022,33 @@ async fn initial_replay_buffer_keeps_recent_rows_when_row_cap_present() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_switch_replay_buffer_uses_transcript_tail_mode_when_row_cap_present() {
|
||||
let (mut app, _rx, _op_rx) = make_test_app_with_channels().await;
|
||||
enable_terminal_resize_reflow(&mut app);
|
||||
app.config.terminal_resize_reflow.max_rows = TerminalResizeReflowMaxRows::Limit(3);
|
||||
|
||||
app.begin_thread_switch_history_replay_buffer();
|
||||
|
||||
let buffer = app
|
||||
.initial_history_replay_buffer
|
||||
.as_ref()
|
||||
.expect("thread switch replay buffer should be active");
|
||||
assert!(buffer.render_from_transcript_tail);
|
||||
assert!(buffer.retained_lines.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_switch_replay_buffer_is_disabled_without_row_cap() {
|
||||
let (mut app, _rx, _op_rx) = make_test_app_with_channels().await;
|
||||
enable_terminal_resize_reflow(&mut app);
|
||||
app.config.terminal_resize_reflow.max_rows = TerminalResizeReflowMaxRows::Disabled;
|
||||
|
||||
app.begin_thread_switch_history_replay_buffer();
|
||||
|
||||
assert!(app.initial_history_replay_buffer.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn height_shrink_schedules_resize_reflow() {
|
||||
let (mut app, _rx, _op_rx) = make_test_app_with_channels().await;
|
||||
|
||||
@@ -1238,6 +1238,12 @@ impl App {
|
||||
snapshot: ThreadEventSnapshot,
|
||||
resume_restored_queue: bool,
|
||||
) {
|
||||
let should_buffer_replay = self.terminal_resize_reflow_enabled()
|
||||
&& (!snapshot.turns.is_empty() || !snapshot.events.is_empty());
|
||||
if should_buffer_replay {
|
||||
self.app_event_tx
|
||||
.send(AppEvent::BeginThreadSwitchHistoryReplayBuffer);
|
||||
}
|
||||
let suppress_replay_notices =
|
||||
replay_filter::snapshot_has_pending_interactive_request(&snapshot);
|
||||
if let Some(session) = snapshot.session {
|
||||
@@ -1263,6 +1269,10 @@ impl App {
|
||||
}
|
||||
self.handle_thread_event_replay(event);
|
||||
}
|
||||
if should_buffer_replay {
|
||||
self.app_event_tx
|
||||
.send(AppEvent::EndInitialHistoryReplayBuffer);
|
||||
}
|
||||
self.chat_widget
|
||||
.set_queue_autosend_suppressed(/*suppressed*/ false);
|
||||
self.chat_widget
|
||||
|
||||
@@ -485,6 +485,10 @@ pub(crate) enum AppEvent {
|
||||
/// Begin buffering initial resume replay rows before they are written to scrollback.
|
||||
BeginInitialHistoryReplayBuffer,
|
||||
|
||||
/// Begin buffering thread-switch replay cells so the final scrollback write can reuse the
|
||||
/// resize-reflow tail renderer.
|
||||
BeginThreadSwitchHistoryReplayBuffer,
|
||||
|
||||
InsertHistoryCell(Box<dyn HistoryCell>),
|
||||
|
||||
/// Finish buffering initial resume replay after all replay events have been queued.
|
||||
|
||||
Reference in New Issue
Block a user