mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
## What changed - Replace footer chord hints with a floating guide that shows available completions after a 350 ms delay, including custom prefixes. - Share guide rendering across conversations, transcript overlays, the session picker, startup drafts, hook review, and daemon recovery. Preserve workspace-specific pane labels and filter startup drafts to supported actions. - Adapt the guide to short inline viewports, retain `Esc` cancellation, and redraw when pending chords clear. Consume clicks on the guide so they do not activate controls beneath it. ## Testing Extend tests and snapshots for delayed reveal, timeout, custom prefixes, compact and inline layouts, startup-safe completions, and picker redraws after chord completion or cancellation. GitOrigin-RevId: afa2fe3ae1b5fbea4cd871c17d35206e749c842c
221 lines
8.2 KiB
Rust
221 lines
8.2 KiB
Rust
//! App-level handlers for ambient terminal pet events.
|
|
|
|
use super::*;
|
|
|
|
impl App {
|
|
pub(super) fn draw_pet_images(
|
|
&mut self,
|
|
tui: &mut tui::Tui,
|
|
screen_size: Size,
|
|
rendered_area: Rect,
|
|
) -> Result<()> {
|
|
if self.chat_widget.ambient_pet_image_enabled() {
|
|
let ambient_pet_area = self.conversation_area(screen_size.into());
|
|
let request = self
|
|
.chat_widget
|
|
.ambient_pet_draw(ambient_pet_area, rendered_area.bottom())
|
|
.filter(|pet| {
|
|
tui.shortcut_hud_area
|
|
.is_none_or(|hud| !hud.intersects(pet.area()))
|
|
});
|
|
if let Err(err) = tui.draw_ambient_pet_image(request) {
|
|
self.handle_ambient_pet_image_render_error(tui, err)?;
|
|
}
|
|
}
|
|
if let Some(request) = self.chat_widget.pet_picker_preview_draw() {
|
|
if let Err(err) = tui.draw_pet_picker_preview_image(Some(request)) {
|
|
self.handle_pet_picker_preview_image_render_error(tui, err)?;
|
|
}
|
|
} else if self.chat_widget.should_clear_pet_picker_preview_image()
|
|
&& let Err(err) = tui.draw_pet_picker_preview_image(/*request*/ None)
|
|
{
|
|
self.handle_pet_picker_preview_image_render_error(tui, err)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(super) fn disable_ambient_pet_before_shutdown(&mut self, tui: &mut tui::Tui) -> Result<()> {
|
|
self.chat_widget.disable_ambient_pet_for_session();
|
|
if let Err(clear_err) = tui.clear_ambient_pet_image() {
|
|
match clear_err {
|
|
crate::pets::PetImageRenderError::Terminal(err) => return Err(err.into()),
|
|
crate::pets::PetImageRenderError::Asset(err) => {
|
|
tracing::warn!(
|
|
error = %err,
|
|
"failed to clear ambient pet image before shutdown feedback"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(super) fn handle_ambient_pet_image_render_error(
|
|
&mut self,
|
|
tui: &mut tui::Tui,
|
|
err: crate::pets::PetImageRenderError,
|
|
) -> Result<()> {
|
|
match err {
|
|
crate::pets::PetImageRenderError::Terminal(err) => Err(err.into()),
|
|
crate::pets::PetImageRenderError::Asset(err) => {
|
|
tracing::warn!(
|
|
error = %err,
|
|
"failed to render ambient pet image; disabling pet for session"
|
|
);
|
|
self.chat_widget.disable_ambient_pet_for_session();
|
|
if let Err(clear_err) = tui.clear_ambient_pet_image() {
|
|
match clear_err {
|
|
crate::pets::PetImageRenderError::Terminal(err) => return Err(err.into()),
|
|
crate::pets::PetImageRenderError::Asset(err) => {
|
|
tracing::warn!(
|
|
error = %err,
|
|
"failed to clear ambient pet image after render failure"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_pet_picker_preview_image_render_error(
|
|
&mut self,
|
|
tui: &mut tui::Tui,
|
|
err: crate::pets::PetImageRenderError,
|
|
) -> Result<()> {
|
|
match err {
|
|
crate::pets::PetImageRenderError::Terminal(err) => Err(err.into()),
|
|
crate::pets::PetImageRenderError::Asset(err) => {
|
|
tracing::warn!(error = %err, "failed to render pet picker preview image");
|
|
self.chat_widget
|
|
.fail_pet_picker_preview_render(err.to_string());
|
|
if let Err(clear_err) = tui.draw_pet_picker_preview_image(/*request*/ None) {
|
|
match clear_err {
|
|
crate::pets::PetImageRenderError::Terminal(err) => return Err(err.into()),
|
|
crate::pets::PetImageRenderError::Asset(err) => {
|
|
tracing::warn!(
|
|
error = %err,
|
|
"failed to clear pet picker preview image after render failure"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_pet_selected(&mut self, tui: &mut tui::Tui, pet_id: String) {
|
|
let request_id = self.chat_widget.show_pet_selection_loading_popup();
|
|
tui.frame_requester().schedule_frame();
|
|
let codex_home = self.local_settings.codex_home.clone();
|
|
let frame_requester = tui.frame_requester();
|
|
let animations_enabled = self.local_settings.tui.animations;
|
|
let tx = self.app_event_tx.clone();
|
|
let pet_http_client = self.chat_widget.pet_http_client.clone();
|
|
std::mem::drop(tokio::spawn(async move {
|
|
let result = crate::pets::load_pet_with_assets(
|
|
pet_id.clone(),
|
|
codex_home,
|
|
frame_requester,
|
|
animations_enabled,
|
|
&pet_http_client,
|
|
)
|
|
.await
|
|
.map(Some)
|
|
.map_err(|err| err.to_string());
|
|
tx.send(AppEvent::PetSelectionLoaded {
|
|
request_id,
|
|
pet_id,
|
|
result,
|
|
});
|
|
}));
|
|
}
|
|
|
|
pub(super) async fn handle_pet_disabled(&mut self, tui: &mut tui::Tui) {
|
|
let edit = crate::legacy_core::config::edit::tui_pet_edit(crate::pets::DISABLED_PET_ID);
|
|
let apply_result = ConfigEditsBuilder::new(&self.local_settings.codex_home)
|
|
.with_edits([edit])
|
|
.apply()
|
|
.await;
|
|
match apply_result {
|
|
Ok(()) => {
|
|
self.sync_tui_pet_disabled();
|
|
tui.frame_requester().schedule_frame();
|
|
}
|
|
Err(err) => {
|
|
self.chat_widget
|
|
.add_error_message(format!("Failed to disable pets: {err}"));
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_pet_preview_loaded(
|
|
&mut self,
|
|
tui: &mut tui::Tui,
|
|
request_id: u64,
|
|
result: Result<crate::pets::AmbientPet, String>,
|
|
) {
|
|
self.chat_widget
|
|
.finish_pet_picker_preview_load(request_id, result);
|
|
tui.frame_requester().schedule_frame();
|
|
}
|
|
|
|
pub(super) async fn handle_pet_selection_loaded(
|
|
&mut self,
|
|
tui: &mut tui::Tui,
|
|
request_id: u64,
|
|
pet_id: String,
|
|
result: Result<Option<crate::pets::AmbientPet>, String>,
|
|
) -> Result<AppRunControl> {
|
|
if !self
|
|
.chat_widget
|
|
.finish_pet_selection_loading_popup(request_id)
|
|
{
|
|
return Ok(AppRunControl::Continue);
|
|
}
|
|
match result {
|
|
Ok(ambient_pet) => {
|
|
let edit = crate::legacy_core::config::edit::tui_pet_edit(&pet_id);
|
|
match ConfigEditsBuilder::new(&self.local_settings.codex_home)
|
|
.with_edits([edit])
|
|
.apply()
|
|
.await
|
|
{
|
|
Ok(()) => {
|
|
self.local_settings.tui.pet = Some(pet_id.clone());
|
|
self.chat_widget
|
|
.set_tui_pet_loaded(Some(pet_id), ambient_pet);
|
|
}
|
|
Err(err) => {
|
|
self.chat_widget
|
|
.add_error_message(format!("Failed to save pet selection: {err}"));
|
|
}
|
|
}
|
|
}
|
|
Err(err) => {
|
|
self.chat_widget
|
|
.add_error_message(format!("Failed to load pet: {err}"));
|
|
}
|
|
}
|
|
tui.frame_requester().schedule_frame();
|
|
Ok(AppRunControl::Continue)
|
|
}
|
|
|
|
pub(super) fn handle_configured_pet_loaded(
|
|
&mut self,
|
|
tui: &mut tui::Tui,
|
|
generation: crate::chatwidget::ConfiguredPetLoadGeneration,
|
|
pet_id: String,
|
|
result: Result<Option<crate::pets::AmbientPet>, String>,
|
|
) {
|
|
if self
|
|
.chat_widget
|
|
.finish_configured_pet_load(generation, pet_id, result)
|
|
{
|
|
tui.frame_requester().schedule_frame();
|
|
}
|
|
}
|
|
}
|