mirror of
https://github.com/openai/codex.git
synced 2026-10-20 00:18:49 +00:00
Show the Plan mode cycling hint in the fullscreen status line (#49037)
## Why The fullscreen status line showed Plan mode without explaining how to leave it. ## What changed Show `Plan mode (shift+tab to cycle)` when the composer is idle and accepts input, and the hint fits alongside the status text. Fall back to the mode label when space is limited, and hide the shortcut during tasks, popups, interactive footer use, or other non-composer modes. ## Testing Add a rendering snapshot and regression assertions for wide and narrow layouts, running tasks, interactive search, empty status text, disabled input, and parent-owned threads. GitOrigin-RevId: a1cf2762b33f5442ab0611a15205f2e36c84b075
This commit is contained in:
@@ -4617,7 +4617,7 @@ impl ChatComposer {
|
||||
popup: popup_rect,
|
||||
footer: footer_rect,
|
||||
} = self.layout_with_options(area, options);
|
||||
self.render_status_surface(status, buf);
|
||||
self.render_status_surface(status, buf, options);
|
||||
if self.popups.active.is_above_composer()
|
||||
&& options.command_popup_placement != CommandPopupPlacement::Hidden
|
||||
{
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
---
|
||||
source: tui/src/bottom_pane/chat_composer/status_surface_tests.rs
|
||||
assertion_line: 102
|
||||
expression: wide
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
› Ask Codex
|
||||
|
||||
MODEL · ~/project · Context 20% used Plan mode (shift+tab to cycle)
|
||||
? for shortcuts
|
||||
@@ -19,16 +19,43 @@ impl ChatComposer {
|
||||
props
|
||||
}
|
||||
|
||||
pub(super) fn render_status_surface(&self, area: Rect, buf: &mut Buffer) {
|
||||
pub(super) fn render_status_surface(
|
||||
&self,
|
||||
area: Rect,
|
||||
buf: &mut Buffer,
|
||||
options: ComposerRenderOptions<'_>,
|
||||
) {
|
||||
if area.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut props = self.footer_props();
|
||||
let show_cycle_hint = !props.is_task_running
|
||||
&& !self.blocks_direct_input
|
||||
&& self.draft.input_enabled
|
||||
&& !self.popup_active()
|
||||
&& matches!(
|
||||
props.mode,
|
||||
FooterMode::ComposerEmpty | FooterMode::ComposerHasDraft
|
||||
)
|
||||
&& options.footer.is_none_or(|footer| !footer.is_interactive);
|
||||
// Persistent context is independent of help, queue, search, and quit modes.
|
||||
props.mode = FooterMode::ComposerHasDraft;
|
||||
props.is_task_running = false;
|
||||
let line = passive_footer_status_line(&props);
|
||||
let right = self.mode_indicator_line(/*show_cycle_hint*/ false);
|
||||
let full = self.mode_indicator_line(show_cycle_hint);
|
||||
let left_width = line
|
||||
.as_ref()
|
||||
.map_or(/*default*/ 0, |line| line.width() as u16);
|
||||
let full_width = full
|
||||
.as_ref()
|
||||
.map_or(/*default*/ 0, |line| line.width() as u16);
|
||||
let right = if full_width <= inset_footer_hint_area(area).width
|
||||
&& can_show_left_with_context(area, left_width, full_width)
|
||||
{
|
||||
full
|
||||
} else {
|
||||
self.mode_indicator_line(/*show_cycle_hint*/ false)
|
||||
};
|
||||
let right_width = right
|
||||
.as_ref()
|
||||
.map_or(/*default*/ 0, |line| line.width() as u16);
|
||||
|
||||
@@ -89,6 +89,65 @@ fn passive_activity_keeps_shortcuts_only_when_the_complete_hint_fits() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fullscreen_plan_indicator_keeps_the_cycle_hint_when_it_fits() {
|
||||
let mut composer = composer();
|
||||
composer.set_collaboration_modes_enabled(/*enabled*/ true);
|
||||
composer.set_collaboration_mode_indicator(Some(CollaborationModeIndicator::Plan));
|
||||
let (wide, _) = render(&composer, /*width*/ 100, /*footer*/ None);
|
||||
assert!(
|
||||
wide.contains("Plan mode (shift+tab to cycle)"),
|
||||
"idle fullscreen must explain how to leave Plan mode: {wide}"
|
||||
);
|
||||
insta::assert_snapshot!("fullscreen_plan_cycle_hint", wide);
|
||||
|
||||
let (narrow, _) = render(&composer, /*width*/ 44, /*footer*/ None);
|
||||
assert!(narrow.contains("Plan mode"), "{narrow}");
|
||||
assert!(!narrow.contains("shift+tab"), "{narrow}");
|
||||
|
||||
composer.set_task_running(/*running*/ true);
|
||||
let (running, _) = render(&composer, /*width*/ 100, /*footer*/ None);
|
||||
assert!(running.contains("Plan mode"), "{running}");
|
||||
assert!(!running.contains("shift+tab"), "{running}");
|
||||
composer.set_task_running(/*running*/ false);
|
||||
|
||||
let footer = TranscriptFooter {
|
||||
text: "Search: verify".into(),
|
||||
cursor_column: Some(8),
|
||||
is_interactive: true,
|
||||
};
|
||||
let (search, _) = render(&composer, /*width*/ 100, Some(&footer));
|
||||
assert!(search.contains("Plan mode"), "{search}");
|
||||
assert!(!search.contains("shift+tab"), "{search}");
|
||||
|
||||
// Configured items such as git-branch can have no value outside a repository.
|
||||
composer.set_status_line(/*status_line*/ None);
|
||||
let (empty_narrow, _) = render(&composer, /*width*/ 24, /*footer*/ None);
|
||||
assert_eq!(
|
||||
empty_narrow.lines().rev().nth(1).map(str::trim),
|
||||
Some("Plan mode")
|
||||
);
|
||||
let (empty_wide, _) = render(&composer, /*width*/ 100, /*footer*/ None);
|
||||
assert!(
|
||||
empty_wide.contains("Plan mode (shift+tab to cycle)"),
|
||||
"{empty_wide}"
|
||||
);
|
||||
|
||||
composer.set_input_enabled(/*enabled*/ false, /*placeholder*/ None);
|
||||
let (read_only, _) = render(&composer, /*width*/ 100, /*footer*/ None);
|
||||
assert_eq!(
|
||||
read_only.lines().rev().nth(1).map(str::trim),
|
||||
Some("Plan mode")
|
||||
);
|
||||
composer.set_input_enabled(/*enabled*/ true, /*placeholder*/ None);
|
||||
composer.set_parent_owned_thread();
|
||||
let (parent_owned, _) = render(&composer, /*width*/ 100, /*footer*/ None);
|
||||
assert_eq!(
|
||||
parent_owned.lines().rev().nth(1).map(str::trim),
|
||||
Some("Plan mode")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shortcut_help_stays_above_the_prompt_and_bottom_status() {
|
||||
let mut composer = composer();
|
||||
|
||||
Reference in New Issue
Block a user