mirror of
https://github.com/openai/codex.git
synced 2026-11-05 00:27:29 +00:00
## Why `chatwidget.rs` is still carrying too many unrelated responsibilities in one file. #22269 started a five-phase cleanup to move coherent behavior domains into focused modules while keeping `chatwidget.rs` as the composition layer. #22407 completed phase 2 by extracting input and submission flow, #22433 completed phase 3 by extracting protocol, replay, streaming, and tool lifecycle handling, and #22518 completed phase 4 by extracting settings, popups, and status surfaces. This PR is phase 5. It cleans up the remaining constructor and orchestration code now that the larger behavior domains have moved out, leaving `chatwidget.rs` much closer to the composition layer the cleanup was aiming for. This is once again a mechanical movement of existing functions. No functional changes. ## What Changed - Added focused modules for widget construction and initial wiring, session configuration flow, key/composer interaction routing, review popup orchestration, desktop notification coalescing, and render composition. - Moved the remaining constructor, session setup, interaction, notification, review picker, and rendering helpers out of `codex-rs/tui/src/chatwidget.rs`. - Preserved the existing startup/session behavior, keyboard handling, review picker flow, notification priority behavior, and render composition while shrinking the central widget module substantially. - Left `codex-rs/tui/src/chatwidget.rs` as the registration and composition surface for the extracted behavior modules. ## Cleanup Phases The five-phase cleanup plan from #22269 is: 1. Phase 1: mechanical helper and state moves. Completed in #22269. 2. Phase 2: extract input and submission flow, including queued user messages, shell prompt submission, pending steer restoration, and thread input snapshot/restore behavior. Completed in #22407. 3. Phase 3: extract protocol, replay, streaming, and tool lifecycle handling, while preserving active-cell grouping, transcript invalidation, interrupt deferral, and final-message separator behavior. Completed in #22433. 4. Phase 4: extract settings, popups, and status surfaces, including model/reasoning/collaboration/personality popups, permission prompts, rate-limit UI, and connectors helpers. Completed in #22518. 5. Phase 5: clean up the remaining constructor and orchestration code once the larger behavior domains have moved out, leaving `chatwidget.rs` as the composition layer. This PR. ## Verification - `cargo check -p codex-tui` - `cargo test -p codex-tui chatwidget::tests::popups_and_settings` - `cargo test -p codex-tui chatwidget::tests::plan_mode` - `cargo test -p codex-tui chatwidget::tests::review_mode` - `cargo test -p codex-tui chatwidget::tests::status_and_layout` `cargo test -p codex-tui` also compiles and begins running, but aborts in the unchanged app-side test `app::tests::discard_side_thread_keeps_local_state_when_server_close_fails` with the same reproducible stack overflow noted in phase 4.
183 lines
6.3 KiB
Rust
183 lines
6.3 KiB
Rust
//! Review preset selection and custom review prompt surfaces.
|
|
|
|
use super::*;
|
|
|
|
impl ChatWidget {
|
|
pub(crate) fn open_review_popup(&mut self) {
|
|
let mut items: Vec<SelectionItem> = Vec::new();
|
|
|
|
items.push(SelectionItem {
|
|
name: "Review against a base branch".to_string(),
|
|
description: Some("(PR Style)".into()),
|
|
actions: vec![Box::new({
|
|
let cwd = self.config.cwd.to_path_buf();
|
|
move |tx| {
|
|
tx.send(AppEvent::OpenReviewBranchPicker(cwd.clone()));
|
|
}
|
|
})],
|
|
dismiss_on_select: false,
|
|
dismiss_parent_on_child_accept: true,
|
|
..Default::default()
|
|
});
|
|
|
|
items.push(SelectionItem {
|
|
name: "Review uncommitted changes".to_string(),
|
|
actions: vec![Box::new(move |tx: &AppEventSender| {
|
|
tx.review(ReviewTarget::UncommittedChanges);
|
|
})],
|
|
dismiss_on_select: true,
|
|
..Default::default()
|
|
});
|
|
|
|
items.push(SelectionItem {
|
|
name: "Review a commit".to_string(),
|
|
actions: vec![Box::new({
|
|
let cwd = self.config.cwd.to_path_buf();
|
|
move |tx| {
|
|
tx.send(AppEvent::OpenReviewCommitPicker(cwd.clone()));
|
|
}
|
|
})],
|
|
dismiss_on_select: false,
|
|
dismiss_parent_on_child_accept: true,
|
|
..Default::default()
|
|
});
|
|
|
|
items.push(SelectionItem {
|
|
name: "Custom review instructions".to_string(),
|
|
actions: vec![Box::new(move |tx| {
|
|
tx.send(AppEvent::OpenReviewCustomPrompt);
|
|
})],
|
|
dismiss_on_select: false,
|
|
dismiss_parent_on_child_accept: true,
|
|
..Default::default()
|
|
});
|
|
|
|
self.bottom_pane.show_selection_view(SelectionViewParams {
|
|
title: Some("Select a review preset".into()),
|
|
footer_hint: Some(standard_popup_hint_line()),
|
|
items,
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
pub(crate) async fn show_review_branch_picker(&mut self, cwd: &Path) {
|
|
let branches = local_git_branches(cwd).await;
|
|
let current_branch = current_branch_name(cwd)
|
|
.await
|
|
.unwrap_or_else(|| "(detached HEAD)".to_string());
|
|
let mut items: Vec<SelectionItem> = Vec::with_capacity(branches.len());
|
|
|
|
for option in branches {
|
|
let branch = option.clone();
|
|
items.push(SelectionItem {
|
|
name: format!("{current_branch} -> {branch}"),
|
|
actions: vec![Box::new(move |tx3: &AppEventSender| {
|
|
tx3.review(ReviewTarget::BaseBranch {
|
|
branch: branch.clone(),
|
|
});
|
|
})],
|
|
dismiss_on_select: true,
|
|
search_value: Some(option),
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
self.bottom_pane.show_selection_view(SelectionViewParams {
|
|
title: Some("Select a base branch".to_string()),
|
|
footer_hint: Some(standard_popup_hint_line()),
|
|
items,
|
|
is_searchable: true,
|
|
search_placeholder: Some("Type to search branches".to_string()),
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
pub(crate) async fn show_review_commit_picker(&mut self, cwd: &Path) {
|
|
let commits = recent_commits(cwd, /*limit*/ 100).await;
|
|
|
|
let mut items: Vec<SelectionItem> = Vec::with_capacity(commits.len());
|
|
for entry in commits {
|
|
let subject = entry.subject.clone();
|
|
let sha = entry.sha.clone();
|
|
let search_val = format!("{subject} {sha}");
|
|
|
|
items.push(SelectionItem {
|
|
name: subject.clone(),
|
|
actions: vec![Box::new(move |tx3: &AppEventSender| {
|
|
tx3.review(ReviewTarget::Commit {
|
|
sha: sha.clone(),
|
|
title: Some(subject.clone()),
|
|
});
|
|
})],
|
|
dismiss_on_select: true,
|
|
search_value: Some(search_val),
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
self.bottom_pane.show_selection_view(SelectionViewParams {
|
|
title: Some("Select a commit to review".to_string()),
|
|
footer_hint: Some(standard_popup_hint_line()),
|
|
items,
|
|
is_searchable: true,
|
|
search_placeholder: Some("Type to search commits".to_string()),
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
pub(crate) fn show_review_custom_prompt(&mut self) {
|
|
let tx = self.app_event_tx.clone();
|
|
let view = CustomPromptView::new(
|
|
"Custom review instructions".to_string(),
|
|
"Type instructions and press Enter".to_string(),
|
|
/*initial_text*/ String::new(),
|
|
/*context_label*/ None,
|
|
Box::new(move |prompt: String| {
|
|
let trimmed = prompt.trim().to_string();
|
|
if trimmed.is_empty() {
|
|
return;
|
|
}
|
|
tx.review(ReviewTarget::Custom {
|
|
instructions: trimmed,
|
|
});
|
|
}),
|
|
);
|
|
self.bottom_pane.show_view(Box::new(view));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn show_review_commit_picker_with_entries(
|
|
chat: &mut ChatWidget,
|
|
entries: Vec<CommitLogEntry>,
|
|
) {
|
|
let mut items: Vec<SelectionItem> = Vec::with_capacity(entries.len());
|
|
for entry in entries {
|
|
let subject = entry.subject.clone();
|
|
let sha = entry.sha.clone();
|
|
let search_val = format!("{subject} {sha}");
|
|
|
|
items.push(SelectionItem {
|
|
name: subject.clone(),
|
|
actions: vec![Box::new(move |tx3: &AppEventSender| {
|
|
tx3.review(ReviewTarget::Commit {
|
|
sha: sha.clone(),
|
|
title: Some(subject.clone()),
|
|
});
|
|
})],
|
|
dismiss_on_select: true,
|
|
search_value: Some(search_val),
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
chat.bottom_pane.show_selection_view(SelectionViewParams {
|
|
title: Some("Select a commit to review".to_string()),
|
|
footer_hint: Some(standard_popup_hint_line()),
|
|
items,
|
|
is_searchable: true,
|
|
search_placeholder: Some("Type to search commits".to_string()),
|
|
..Default::default()
|
|
});
|
|
}
|