mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
refactor
This commit is contained in:
@@ -12,9 +12,6 @@ use color_eyre::eyre::Result;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyEventKind;
|
||||
use crossterm::execute;
|
||||
use crossterm::terminal::EnterAlternateScreen;
|
||||
use crossterm::terminal::LeaveAlternateScreen;
|
||||
use crossterm::terminal::supports_keyboard_enhancement;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::text::Line;
|
||||
@@ -28,33 +25,33 @@ use tokio::select;
|
||||
use tokio::sync::mpsc::unbounded_channel;
|
||||
|
||||
pub(crate) struct App {
|
||||
server: Arc<ConversationManager>,
|
||||
app_event_tx: AppEventSender,
|
||||
chat_widget: ChatWidget,
|
||||
pub(crate) server: Arc<ConversationManager>,
|
||||
pub(crate) app_event_tx: AppEventSender,
|
||||
pub(crate) chat_widget: ChatWidget,
|
||||
|
||||
/// Config is stored here so we can recreate ChatWidgets as needed.
|
||||
config: Config,
|
||||
pub(crate) config: Config,
|
||||
|
||||
file_search: FileSearchManager,
|
||||
pub(crate) file_search: FileSearchManager,
|
||||
|
||||
transcript_lines: Vec<Line<'static>>,
|
||||
pub(crate) transcript_lines: Vec<Line<'static>>,
|
||||
|
||||
// Transcript overlay state
|
||||
transcript_overlay: Option<TranscriptApp>,
|
||||
pub(crate) transcript_overlay: Option<TranscriptApp>,
|
||||
// If true, overlay is opened as an Esc-backtrack preview.
|
||||
transcript_overlay_is_backtrack: bool,
|
||||
deferred_history_lines: Vec<Line<'static>>,
|
||||
transcript_saved_viewport: Option<Rect>,
|
||||
pub(crate) transcript_overlay_is_backtrack: bool,
|
||||
pub(crate) deferred_history_lines: Vec<Line<'static>>,
|
||||
pub(crate) transcript_saved_viewport: Option<Rect>,
|
||||
|
||||
enhanced_keys_supported: bool,
|
||||
pub(crate) enhanced_keys_supported: bool,
|
||||
|
||||
/// Controls the animation thread that sends CommitTick events.
|
||||
commit_anim_running: Arc<AtomicBool>,
|
||||
pub(crate) commit_anim_running: Arc<AtomicBool>,
|
||||
|
||||
// Esc-backtracking state
|
||||
esc_backtrack_primed: bool,
|
||||
esc_backtrack_base: Option<uuid::Uuid>,
|
||||
esc_backtrack_count: usize,
|
||||
pub(crate) esc_backtrack_primed: bool,
|
||||
pub(crate) esc_backtrack_base: Option<uuid::Uuid>,
|
||||
pub(crate) esc_backtrack_count: usize,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -125,73 +122,7 @@ impl App {
|
||||
event: TuiEvent,
|
||||
) -> Result<bool> {
|
||||
if self.transcript_overlay.is_some() {
|
||||
// Intercept Esc/Enter when overlay is a backtrack preview.
|
||||
let mut handled = false;
|
||||
if self.transcript_overlay_is_backtrack {
|
||||
match event {
|
||||
TuiEvent::Key(KeyEvent { code: KeyCode::Esc, kind: KeyEventKind::Press | KeyEventKind::Repeat, .. }) => {
|
||||
if self.esc_backtrack_base.is_some() {
|
||||
self.esc_backtrack_count = self.esc_backtrack_count.saturating_add(1);
|
||||
let header_idx =
|
||||
crate::backtrack_helpers::find_nth_last_user_header_index(
|
||||
&self.transcript_lines,
|
||||
self.esc_backtrack_count,
|
||||
);
|
||||
let offset = header_idx.map(|idx| {
|
||||
crate::backtrack_helpers::wrapped_offset_before(
|
||||
&self.transcript_lines,
|
||||
idx,
|
||||
tui.terminal.viewport_area.width,
|
||||
)
|
||||
});
|
||||
let hl = crate::backtrack_helpers::highlight_range_for_nth_last_user(
|
||||
&self.transcript_lines,
|
||||
self.esc_backtrack_count,
|
||||
);
|
||||
if let Some(overlay) = &mut self.transcript_overlay {
|
||||
if let Some(off) = offset { overlay.scroll_offset = off; }
|
||||
overlay.set_highlight_range(hl);
|
||||
}
|
||||
tui.frame_requester().schedule_frame();
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
TuiEvent::Key(KeyEvent { code: KeyCode::Enter, kind: KeyEventKind::Press, .. }) => {
|
||||
// Confirm the backtrack: close overlay, fork, and prefill.
|
||||
let base = self.esc_backtrack_base;
|
||||
let count = self.esc_backtrack_count;
|
||||
self.close_transcript_overlay(tui);
|
||||
if let Some(base_id) = base {
|
||||
if count > 0 {
|
||||
if let Err(e) = self.fork_and_render_backtrack(tui, base_id, count).await {
|
||||
tracing::error!("Backtrack confirm failed: {e:#}");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reset backtrack state after confirming.
|
||||
self.esc_backtrack_primed = false;
|
||||
self.esc_backtrack_base = None;
|
||||
self.esc_backtrack_count = 0;
|
||||
handled = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// Forward to overlay if not handled
|
||||
if !handled {
|
||||
if let Some(overlay) = &mut self.transcript_overlay {
|
||||
overlay.handle_event(tui, event)?;
|
||||
if overlay.is_done {
|
||||
self.close_transcript_overlay(tui);
|
||||
if self.transcript_overlay_is_backtrack {
|
||||
self.esc_backtrack_primed = false;
|
||||
self.esc_backtrack_base = None;
|
||||
self.esc_backtrack_count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tui.frame_requester().schedule_frame();
|
||||
let _ = self.handle_backtrack_overlay_event(tui, event).await?;
|
||||
} else {
|
||||
match event {
|
||||
TuiEvent::Key(key_event) => {
|
||||
@@ -355,95 +286,20 @@ impl App {
|
||||
} if self.chat_widget.composer_is_empty() => {
|
||||
self.app_event_tx.send(AppEvent::ExitRequest);
|
||||
}
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('t'),
|
||||
modifiers: crossterm::event::KeyModifiers::CONTROL,
|
||||
kind: KeyEventKind::Press,
|
||||
..
|
||||
} => {
|
||||
// Enter alternate screen and set viewport to full size.
|
||||
let _ = execute!(tui.terminal.backend_mut(), EnterAlternateScreen);
|
||||
if let Ok(size) = tui.terminal.size() {
|
||||
self.transcript_saved_viewport = Some(tui.terminal.viewport_area);
|
||||
tui.terminal
|
||||
.set_viewport_area(Rect::new(0, 0, size.width, size.height));
|
||||
let _ = tui.terminal.clear();
|
||||
}
|
||||
|
||||
self.transcript_overlay = Some(TranscriptApp::new(self.transcript_lines.clone()));
|
||||
tui.frame_requester().schedule_frame();
|
||||
KeyEvent { code: KeyCode::Char('t'), modifiers: crossterm::event::KeyModifiers::CONTROL, kind: KeyEventKind::Press, .. } => {
|
||||
self.open_transcript_overlay(tui);
|
||||
}
|
||||
KeyEvent {
|
||||
code: KeyCode::Esc,
|
||||
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
||||
..
|
||||
} => {
|
||||
// Only handle backtracking when composer is empty to avoid clobbering edits.
|
||||
if self.chat_widget.composer_is_empty() {
|
||||
if !self.esc_backtrack_primed {
|
||||
// Arm backtracking and record base conversation.
|
||||
self.esc_backtrack_primed = true;
|
||||
self.esc_backtrack_count = 0;
|
||||
self.esc_backtrack_base = self.chat_widget.session_id();
|
||||
} else if self.transcript_overlay.is_none() {
|
||||
// Open transcript overlay in backtrack preview mode and jump to the target message.
|
||||
self.open_transcript_overlay(tui);
|
||||
self.transcript_overlay_is_backtrack = true;
|
||||
self.esc_backtrack_count = self.esc_backtrack_count.saturating_add(1);
|
||||
let header_idx =
|
||||
crate::backtrack_helpers::find_nth_last_user_header_index(
|
||||
&self.transcript_lines,
|
||||
self.esc_backtrack_count,
|
||||
);
|
||||
let offset = header_idx.map(|idx| {
|
||||
crate::backtrack_helpers::wrapped_offset_before(
|
||||
&self.transcript_lines,
|
||||
idx,
|
||||
tui.terminal.viewport_area.width,
|
||||
)
|
||||
});
|
||||
let hl = crate::backtrack_helpers::highlight_range_for_nth_last_user(
|
||||
&self.transcript_lines,
|
||||
self.esc_backtrack_count,
|
||||
);
|
||||
if let Some(overlay) = &mut self.transcript_overlay {
|
||||
if let Some(off) = offset { overlay.scroll_offset = off; }
|
||||
overlay.set_highlight_range(hl);
|
||||
}
|
||||
} else if self.transcript_overlay_is_backtrack {
|
||||
// Already previewing: step to the next older message.
|
||||
self.esc_backtrack_count = self.esc_backtrack_count.saturating_add(1);
|
||||
let header_idx =
|
||||
crate::backtrack_helpers::find_nth_last_user_header_index(
|
||||
&self.transcript_lines,
|
||||
self.esc_backtrack_count,
|
||||
);
|
||||
let offset = header_idx.map(|idx| {
|
||||
crate::backtrack_helpers::wrapped_offset_before(
|
||||
&self.transcript_lines,
|
||||
idx,
|
||||
tui.terminal.viewport_area.width,
|
||||
)
|
||||
});
|
||||
let hl = crate::backtrack_helpers::highlight_range_for_nth_last_user(
|
||||
&self.transcript_lines,
|
||||
self.esc_backtrack_count,
|
||||
);
|
||||
if let Some(overlay) = &mut self.transcript_overlay {
|
||||
if let Some(off) = offset { overlay.scroll_offset = off; }
|
||||
overlay.set_highlight_range(hl);
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyEvent { code: KeyCode::Esc, kind: KeyEventKind::Press | KeyEventKind::Repeat, .. } => {
|
||||
self.handle_backtrack_esc_key(tui);
|
||||
}
|
||||
// Enter confirms backtrack when primed + count > 0. Otherwise pass to widget.
|
||||
KeyEvent { code: KeyCode::Enter, kind: KeyEventKind::Press, .. }
|
||||
if self.esc_backtrack_primed && self.esc_backtrack_count > 0 && self.chat_widget.composer_is_empty() =>
|
||||
{
|
||||
if let Some(base_id) = self.esc_backtrack_base {
|
||||
if let Err(e) = self.fork_and_render_backtrack(tui, base_id, self.esc_backtrack_count).await {
|
||||
tracing::error!("Backtrack confirm failed: {e:#}");
|
||||
}
|
||||
if let Some(base_id) = self.esc_backtrack_base
|
||||
&& let Err(e) = self.fork_and_render_backtrack(tui, base_id, self.esc_backtrack_count).await
|
||||
{
|
||||
tracing::error!("Backtrack confirm failed: {e:#}");
|
||||
}
|
||||
// Reset backtrack state after confirming.
|
||||
self.esc_backtrack_primed = false;
|
||||
@@ -462,90 +318,5 @@ impl App {
|
||||
};
|
||||
}
|
||||
|
||||
/// Re-render the full transcript into the terminal scrollback in one call.
|
||||
/// Useful when switching sessions to ensure prior history remains visible.
|
||||
pub(crate) fn render_transcript_once(&mut self, tui: &mut tui::Tui) {
|
||||
if !self.transcript_lines.is_empty() {
|
||||
tui.insert_history_lines(self.transcript_lines.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn open_transcript_overlay(&mut self, tui: &mut tui::Tui) {
|
||||
// Enter alternate screen and set viewport to full size.
|
||||
let _ = execute!(tui.terminal.backend_mut(), EnterAlternateScreen);
|
||||
if let Ok(size) = tui.terminal.size() {
|
||||
self.transcript_saved_viewport = Some(tui.terminal.viewport_area);
|
||||
tui.terminal
|
||||
.set_viewport_area(Rect::new(0, 0, size.width, size.height));
|
||||
let _ = tui.terminal.clear();
|
||||
}
|
||||
self.transcript_overlay = Some(TranscriptApp::new(self.transcript_lines.clone()));
|
||||
tui.frame_requester().schedule_frame();
|
||||
}
|
||||
|
||||
fn close_transcript_overlay(&mut self, tui: &mut tui::Tui) {
|
||||
// Exit alternate screen and restore viewport.
|
||||
let _ = execute!(tui.terminal.backend_mut(), LeaveAlternateScreen);
|
||||
if let Some(saved) = self.transcript_saved_viewport.take() {
|
||||
tui.terminal.set_viewport_area(saved);
|
||||
}
|
||||
if !self.deferred_history_lines.is_empty() {
|
||||
let lines = std::mem::take(&mut self.deferred_history_lines);
|
||||
tui.insert_history_lines(lines);
|
||||
}
|
||||
self.transcript_overlay = None;
|
||||
self.transcript_overlay_is_backtrack = false;
|
||||
}
|
||||
|
||||
async fn fork_and_render_backtrack(
|
||||
&mut self,
|
||||
tui: &mut tui::Tui,
|
||||
base_id: uuid::Uuid,
|
||||
drop_last_messages: usize,
|
||||
) -> color_eyre::eyre::Result<()> {
|
||||
// Compute the text to prefill by extracting the N-th last user message
|
||||
// from the UI transcript lines already rendered.
|
||||
let prefill = crate::backtrack_helpers::nth_last_user_text(
|
||||
&self.transcript_lines,
|
||||
drop_last_messages,
|
||||
);
|
||||
|
||||
// Fork conversation with the requested drop.
|
||||
let fork = self
|
||||
.server
|
||||
.fork_conversation(base_id, drop_last_messages, self.config.clone())
|
||||
.await?;
|
||||
// Replace chat widget with one attached to the new conversation.
|
||||
self.chat_widget = ChatWidget::new_from_existing(
|
||||
self.config.clone(),
|
||||
fork.conversation,
|
||||
fork.session_configured,
|
||||
tui.frame_requester(),
|
||||
self.app_event_tx.clone(),
|
||||
self.enhanced_keys_supported,
|
||||
);
|
||||
|
||||
// Trim transcript to preserve only content up to the selected user message.
|
||||
if let Some(cut_idx) = crate::backtrack_helpers::find_nth_last_user_header_index(
|
||||
&self.transcript_lines,
|
||||
drop_last_messages,
|
||||
) {
|
||||
self.transcript_lines.truncate(cut_idx);
|
||||
} else {
|
||||
self.transcript_lines.clear();
|
||||
}
|
||||
let _ = tui.terminal.clear();
|
||||
self.render_transcript_once(tui);
|
||||
|
||||
// Prefill the composer with the dropped user message text, if any.
|
||||
if let Some(text) = prefill {
|
||||
if !text.is_empty() {
|
||||
self.chat_widget.insert_str(&text);
|
||||
}
|
||||
}
|
||||
tui.frame_requester().schedule_frame();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// (moved helper functions to backtrack_helpers.rs)
|
||||
// Backtrack helpers moved to app_backtrack.rs and backtrack_helpers.rs
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ use tracing_subscriber::EnvFilter;
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
mod app;
|
||||
mod app_backtrack;
|
||||
mod app_event;
|
||||
mod app_event_sender;
|
||||
mod backtrack_helpers;
|
||||
|
||||
Reference in New Issue
Block a user