Jeremy Rose
2025-09-02 10:29:58 -07:00
committed by GitHub
parent 3f8d6021ac
commit e442ecedab
84 changed files with 2896 additions and 2167 deletions

View File

@@ -101,7 +101,6 @@ pub(crate) struct ChatWidget {
// Stream lifecycle controller
stream: StreamController,
running_commands: HashMap<String, RunningCommand>,
pending_exec_completions: Vec<(Vec<String>, Vec<ParsedCommand>, CommandOutput)>,
task_complete_pending: bool,
// Queue of interruptive UI events deferred during an active write cycle
interrupts: InterruptManager,
@@ -113,7 +112,6 @@ pub(crate) struct ChatWidget {
frame_requester: FrameRequester,
// Whether to include the initial welcome banner on session configured
show_welcome_banner: bool,
last_history_was_exec: bool,
// User messages queued while a turn is in progress
queued_user_messages: VecDeque<UserMessage>,
}
@@ -333,6 +331,7 @@ impl ChatWidget {
auto_approved: event.auto_approved,
},
event.changes,
&self.config.cwd,
));
}
@@ -442,14 +441,14 @@ impl ChatWidget {
self.task_complete_pending = false;
}
// A completed stream indicates non-exec content was just inserted.
// Reset the exec header grouping so the next exec shows its header.
self.last_history_was_exec = false;
self.flush_interrupt_queue();
}
}
#[inline]
fn handle_streaming_delta(&mut self, delta: String) {
// Before streaming agent content, flush any active exec cell group.
self.flush_active_exec_cell();
let sink = AppEventHistorySink(self.app_event_tx.clone());
self.stream.begin(&sink);
self.stream.push_and_maybe_commit(&delta, &sink);
@@ -462,31 +461,29 @@ impl ChatWidget {
Some(rc) => (rc.command, rc.parsed_cmd),
None => (vec![ev.call_id.clone()], Vec::new()),
};
self.pending_exec_completions.push((
command,
parsed,
CommandOutput {
exit_code: ev.exit_code,
stdout: ev.stdout.clone(),
stderr: ev.stderr.clone(),
formatted_output: ev.formatted_output.clone(),
},
));
if self.running_commands.is_empty() {
self.active_exec_cell = None;
let pending = std::mem::take(&mut self.pending_exec_completions);
for (command, parsed, output) in pending {
let include_header = !self.last_history_was_exec;
let cell = history_cell::new_completed_exec_command(
command,
parsed,
output,
include_header,
ev.duration,
);
self.add_to_history(cell);
self.last_history_was_exec = true;
if self.active_exec_cell.is_none() {
// This should have been created by handle_exec_begin_now, but in case it wasn't,
// create it now.
self.active_exec_cell = Some(history_cell::new_active_exec_command(
ev.call_id.clone(),
command,
parsed,
));
}
if let Some(cell) = self.active_exec_cell.as_mut() {
cell.complete_call(
&ev.call_id,
CommandOutput {
exit_code: ev.exit_code,
stdout: ev.stdout.clone(),
stderr: ev.stderr.clone(),
formatted_output: ev.formatted_output.clone(),
},
ev.duration,
);
if cell.should_flush() {
self.flush_active_exec_cell();
}
}
}
@@ -495,9 +492,9 @@ impl ChatWidget {
&mut self,
event: codex_core::protocol::PatchApplyEndEvent,
) {
if event.success {
self.add_to_history(history_cell::new_patch_apply_success(event.stdout));
} else {
// If the patch was successful, just let the "Edited" block stand.
// Otherwise, add a failure block.
if !event.success {
self.add_to_history(history_cell::new_patch_apply_failure(event.stderr));
}
}
@@ -523,6 +520,7 @@ impl ChatWidget {
self.add_to_history(history_cell::new_patch_event(
PatchEventType::ApprovalRequest,
ev.changes.clone(),
&self.config.cwd,
));
let request = ApprovalRequest::ApplyPatch {
@@ -543,19 +541,28 @@ impl ChatWidget {
parsed_cmd: ev.parsed_cmd.clone(),
},
);
// Accumulate parsed commands into a single active Exec cell so they stack
match self.active_exec_cell.as_mut() {
Some(exec) => {
exec.parsed.extend(ev.parsed_cmd);
}
_ => {
let include_header = !self.last_history_was_exec;
if let Some(exec) = &self.active_exec_cell {
if let Some(new_exec) = exec.with_added_call(
ev.call_id.clone(),
ev.command.clone(),
ev.parsed_cmd.clone(),
) {
self.active_exec_cell = Some(new_exec);
} else {
// Make a new cell.
self.flush_active_exec_cell();
self.active_exec_cell = Some(history_cell::new_active_exec_command(
ev.command,
ev.parsed_cmd,
include_header,
ev.call_id.clone(),
ev.command.clone(),
ev.parsed_cmd.clone(),
));
}
} else {
self.active_exec_cell = Some(history_cell::new_active_exec_command(
ev.call_id.clone(),
ev.command.clone(),
ev.parsed_cmd.clone(),
));
}
// Request a redraw so the working header and command list are visible immediately.
@@ -585,7 +592,7 @@ impl ChatWidget {
Constraint::Max(
self.active_exec_cell
.as_ref()
.map_or(0, |c| c.desired_height(area.width)),
.map_or(0, |c| c.desired_height(area.width) + 1),
),
Constraint::Min(self.bottom_pane.desired_height(area.width)),
])
@@ -627,13 +634,11 @@ impl ChatWidget {
last_token_usage: TokenUsage::default(),
stream: StreamController::new(config),
running_commands: HashMap::new(),
pending_exec_completions: Vec::new(),
task_complete_pending: false,
interrupts: InterruptManager::new(),
reasoning_buffer: String::new(),
full_reasoning_buffer: String::new(),
session_id: None,
last_history_was_exec: false,
queued_user_messages: VecDeque::new(),
show_welcome_banner: true,
}
@@ -673,13 +678,11 @@ impl ChatWidget {
last_token_usage: TokenUsage::default(),
stream: StreamController::new(config),
running_commands: HashMap::new(),
pending_exec_completions: Vec::new(),
task_complete_pending: false,
interrupts: InterruptManager::new(),
reasoning_buffer: String::new(),
full_reasoning_buffer: String::new(),
session_id: None,
last_history_was_exec: false,
queued_user_messages: VecDeque::new(),
show_welcome_banner: false,
}
@@ -690,7 +693,7 @@ impl ChatWidget {
+ self
.active_exec_cell
.as_ref()
.map_or(0, |c| c.desired_height(width))
.map_or(0, |c| c.desired_height(width) + 1)
}
pub(crate) fn handle_key_event(&mut self, key_event: KeyEvent) {
@@ -891,18 +894,15 @@ impl ChatWidget {
fn flush_active_exec_cell(&mut self) {
if let Some(active) = self.active_exec_cell.take() {
self.last_history_was_exec = true;
self.app_event_tx
.send(AppEvent::InsertHistoryCell(Box::new(active)));
}
}
fn add_to_history(&mut self, cell: impl HistoryCell + 'static) {
// Only break exec grouping if the cell renders visible lines.
let has_display_lines = !cell.display_lines().is_empty();
self.flush_active_exec_cell();
if has_display_lines {
self.last_history_was_exec = false;
if !cell.display_lines(u16::MAX).is_empty() {
// Only break exec grouping if the cell renders visible lines.
self.flush_active_exec_cell();
}
self.app_event_tx
.send(AppEvent::InsertHistoryCell(Box::new(cell)));
@@ -1028,7 +1028,6 @@ impl ChatWidget {
let cell = cell.into_failed();
// Insert finalized exec into history and keep grouping consistent.
self.add_to_history(cell);
self.last_history_was_exec = true;
}
}
@@ -1284,6 +1283,9 @@ impl WidgetRef for &ChatWidget {
let [active_cell_area, bottom_pane_area] = self.layout_areas(area);
(&self.bottom_pane).render(bottom_pane_area, buf);
if let Some(cell) = &self.active_exec_cell {
let mut active_cell_area = active_cell_area;
active_cell_area.y += 1;
active_cell_area.height -= 1;
cell.render_ref(active_cell_area, buf);
}
}