Codex Handbook
tui/src/chatwidget/hook_lifecycle.rs 144 lines
//! Hook run lifecycle handling for `ChatWidget`.//!//! This module keeps active hook cells, hook timers, and hook completion output//! together.use super::*;impl ChatWidget {    /// Drop transient live hook status without flushing it into history.    pub(super) fn clear_active_hook_cell(&mut self) {        if self.active_hook_cell.take().is_some() {            self.bump_active_cell_revision();            self.request_pending_usage_output_insertion();        }    }    pub(super) fn on_hook_started(&mut self, run: codex_app_server_protocol::HookRunSummary) {        self.record_visible_turn_activity();        self.flush_answer_stream_with_separator();        self.flush_completed_hook_output();        match self.active_hook_cell.as_mut() {            Some(cell) => {                cell.start_run(run);                self.bump_active_cell_revision();            }            None => {                self.active_hook_cell = Some(history_cell::new_active_hook_cell(                    run,                    self.config.animations,                ));                self.bump_active_cell_revision();            }        }        self.request_redraw();    }    pub(super) fn on_hook_completed(        &mut self,        completed: codex_app_server_protocol::HookRunSummary,    ) {        let completed_existing_run = self            .active_hook_cell            .as_mut()            .map(|cell| cell.complete_run(completed.clone()))            .unwrap_or(false);        if completed_existing_run {            self.bump_active_cell_revision();        } else {            match self.active_hook_cell.as_mut() {                Some(cell) => {                    cell.add_completed_run(completed);                    self.bump_active_cell_revision();                }                None => {                    let cell =                        history_cell::new_completed_hook_cell(completed, self.config.animations);                    if !cell.is_empty() {                        self.active_hook_cell = Some(cell);                        self.bump_active_cell_revision();                    }                }            }        }        self.flush_completed_hook_output();        self.finish_active_hook_cell_if_idle();        self.request_redraw();    }    pub(super) fn flush_completed_hook_output(&mut self) {        let Some(completed_cell) = self            .active_hook_cell            .as_mut()            .and_then(HookCell::take_completed_persistent_runs)        else {            return;        };        let active_cell_is_empty = self            .active_hook_cell            .as_ref()            .is_some_and(HookCell::is_empty);        if active_cell_is_empty {            self.active_hook_cell = None;        }        self.bump_active_cell_revision();        self.transcript.needs_final_message_separator = true;        self.app_event_tx            .send(AppEvent::InsertHistoryCell(Box::new(completed_cell)));        self.request_pending_usage_output_insertion();    }    pub(super) fn finish_active_hook_cell_if_idle(&mut self) {        let Some(cell) = self.active_hook_cell.as_ref() else {            return;        };        if cell.is_empty() {            self.active_hook_cell = None;            self.bump_active_cell_revision();            self.request_pending_usage_output_insertion();            return;        }        if cell.should_flush()            && let Some(cell) = self.active_hook_cell.take()        {            self.bump_active_cell_revision();            self.transcript.needs_final_message_separator = true;            self.app_event_tx                .send(AppEvent::InsertHistoryCell(Box::new(cell)));            self.request_pending_usage_output_insertion();        }    }    pub(super) fn update_due_hook_visibility(&mut self) {        let Some(cell) = self.active_hook_cell.as_mut() else {            return;        };        let now = Instant::now();        if cell.advance_time(now) {            self.bump_active_cell_revision();        }        self.finish_active_hook_cell_if_idle();    }    pub(super) fn schedule_hook_timer_if_needed(&self) {        if self.config.animations            && self                .active_hook_cell                .as_ref()                .is_some_and(HookCell::has_visible_running_run)        {            self.frame_requester                .schedule_frame_in(Duration::from_millis(50));        }        let Some(deadline) = self            .active_hook_cell            .as_ref()            .and_then(HookCell::next_timer_deadline)        else {            return;        };        let delay = deadline.saturating_duration_since(Instant::now());        self.frame_requester.schedule_frame_in(delay);    }}