mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
Add quick dial presets for terminal title and status line layouts (#54388)
## What changed Add current terminal title and status line options to the preset picker. Generate `/title` and `/statusline` commands that preserve item order and duplicates, normalize item aliases, and retain the status line color setting. Use `--clear` for empty layouts and disable options containing unavailable items. ## Testing Extend picker tests and snapshots to cover the new options, generated commands, empty layouts, and unavailable terminal title items. GitOrigin-RevId: b836b15e87c03249cf479b1c3baf0bd71931a6be
This commit is contained in:
@@ -145,6 +145,29 @@ impl ChatWidget {
|
||||
Ok(dial)
|
||||
}
|
||||
|
||||
pub(super) fn current_terminal_title_quick_dial(&self) -> Result<QuickDial, &'static str> {
|
||||
status_surface_quick_dial::<TerminalTitleItem>(
|
||||
"/title",
|
||||
self.configured_terminal_title_items(),
|
||||
/*suffix*/ None,
|
||||
"Choose available terminal-title items with /title before saving.",
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn current_status_line_quick_dial(&self) -> Result<QuickDial, &'static str> {
|
||||
let colors = if self.local_settings.tui.status_line_use_colors {
|
||||
"--colors"
|
||||
} else {
|
||||
"--no-colors"
|
||||
};
|
||||
status_surface_quick_dial::<StatusLineItem>(
|
||||
"/statusline",
|
||||
self.configured_status_line_items(),
|
||||
/*suffix*/ Some(colors),
|
||||
"Choose available status-line items with /statusline before saving.",
|
||||
)
|
||||
}
|
||||
|
||||
fn quick_dials_available(&mut self) -> bool {
|
||||
if self.blocks_direct_input
|
||||
|| self.active_side_conversation
|
||||
@@ -384,9 +407,13 @@ impl ChatWidget {
|
||||
.unwrap_or(Err(
|
||||
"Select a named permission profile with /permissions first.",
|
||||
));
|
||||
let terminal_title = self.current_terminal_title_quick_dial();
|
||||
let status_line = self.current_status_line_quick_dial();
|
||||
let items = [
|
||||
("Current model", self.current_model_quick_dial()),
|
||||
("Current permissions", permissions),
|
||||
("Current terminal title", terminal_title),
|
||||
("Current status line", status_line),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(name, dial)| {
|
||||
@@ -415,7 +442,7 @@ impl ChatWidget {
|
||||
.collect();
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
view_id: Some("quick-dial-prefills"),
|
||||
max_visible_rows: 2,
|
||||
max_visible_rows: 4,
|
||||
title: Some("Start preset from current settings".into()),
|
||||
items,
|
||||
..SelectionViewParams::picker()
|
||||
@@ -566,3 +593,38 @@ impl ChatWidget {
|
||||
consumed
|
||||
}
|
||||
}
|
||||
|
||||
fn status_surface_quick_dial<T>(
|
||||
command: &str,
|
||||
item_ids: Vec<String>,
|
||||
suffix: Option<&str>,
|
||||
invalid_items_error: &'static str,
|
||||
) -> Result<QuickDial, &'static str>
|
||||
where
|
||||
T: std::str::FromStr + ToString,
|
||||
{
|
||||
let items = item_ids
|
||||
.into_iter()
|
||||
.map(|id| {
|
||||
id.parse::<T>()
|
||||
.map(|item| item.to_string())
|
||||
.map_err(|_| invalid_items_error)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let mut input = command.to_string();
|
||||
if items.is_empty() {
|
||||
input.push_str(" --clear");
|
||||
} else {
|
||||
for item in items {
|
||||
input.push(' ');
|
||||
input.push_str(&item);
|
||||
}
|
||||
}
|
||||
if let Some(suffix) = suffix {
|
||||
input.push(' ');
|
||||
input.push_str(suffix);
|
||||
}
|
||||
let dial = QuickDial { input, label: None };
|
||||
dial.validate()?;
|
||||
Ok(dial)
|
||||
}
|
||||
|
||||
+5
-3
@@ -5,8 +5,10 @@ expression: "render_bottom_popup(&chat, 80)"
|
||||
Start preset from current settings
|
||||
|
||||
|
||||
› 1. Current model /model gpt-5.5 --speed=standard
|
||||
2. Current permissions /permissions :workspace --approval-policy=on-request
|
||||
--reviewer=user
|
||||
› 1. Current model /model gpt-5.5 --speed=standard
|
||||
2. Current permissions /permissions :workspace --approval-policy=on-
|
||||
request --reviewer=user
|
||||
3. Current terminal title /title model project-name model
|
||||
4. Current status line /statusline model project-name model --no-colors
|
||||
|
||||
enter select · esc back
|
||||
|
||||
+7
-4
@@ -5,9 +5,12 @@ expression: "render_bottom_popup(&chat, 80)"
|
||||
Start preset from current settings
|
||||
|
||||
|
||||
Current model Choose an available reasoning effort with /model
|
||||
before saving.
|
||||
› 1. Current permissions /permissions :workspace --approval-policy=on-request
|
||||
--reviewer=auto_review
|
||||
Current model Choose an available reasoning effort with /model
|
||||
before saving.
|
||||
› 1. Current permissions /permissions :workspace --approval-policy=on-
|
||||
request --reviewer=auto_review
|
||||
Current terminal title Choose available terminal-title items with /title
|
||||
before saving.
|
||||
2. Current status line /statusline model --colors
|
||||
|
||||
enter select · esc back
|
||||
|
||||
@@ -5,10 +5,21 @@ use crate::chatwidget::quick_dials::QuickDialEditorTarget;
|
||||
use codex_config::types::QuickDial;
|
||||
|
||||
#[tokio::test]
|
||||
async fn current_settings_offer_model_and_permissions() {
|
||||
async fn current_settings_offer_model_permissions_and_status_layouts() {
|
||||
let (mut chat, mut events, _ops) =
|
||||
make_chatwidget_manual(/*model_override*/ Some("gpt-5.5")).await;
|
||||
chat.thread_id = Some(ThreadId::new());
|
||||
chat.local_settings.tui.terminal_title = Some(vec![
|
||||
"model-name".into(),
|
||||
"project".into(),
|
||||
"model-name".into(),
|
||||
]);
|
||||
chat.local_settings.tui.status_line = Some(vec![
|
||||
"model-name".into(),
|
||||
"project-root".into(),
|
||||
"model-name".into(),
|
||||
]);
|
||||
chat.local_settings.tui.status_line_use_colors = false;
|
||||
chat.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
|
||||
PermissionProfile::workspace_write(),
|
||||
ActivePermissionProfile::new(":workspace"),
|
||||
@@ -31,14 +42,60 @@ async fn current_settings_offer_model_and_permissions() {
|
||||
}) if input == "/permissions :workspace --approval-policy=on-request --reviewer=user"
|
||||
);
|
||||
|
||||
chat.open_quick_dial_prefills();
|
||||
chat.handle_key_event(KeyCode::Down.into());
|
||||
chat.handle_key_event(KeyCode::Down.into());
|
||||
chat.handle_key_event(KeyCode::Enter.into());
|
||||
assert_matches!(
|
||||
events.try_recv(),
|
||||
Ok(AppEvent::OpenQuickDialEditor {
|
||||
target: QuickDialEditorTarget::PickSlot,
|
||||
dial: Some(QuickDial { input, label: None }),
|
||||
}) if input == "/title model project-name model"
|
||||
);
|
||||
|
||||
chat.open_quick_dial_prefills();
|
||||
chat.handle_key_event(KeyCode::Down.into());
|
||||
chat.handle_key_event(KeyCode::Down.into());
|
||||
chat.handle_key_event(KeyCode::Down.into());
|
||||
chat.handle_key_event(KeyCode::Enter.into());
|
||||
assert_matches!(
|
||||
events.try_recv(),
|
||||
Ok(AppEvent::OpenQuickDialEditor {
|
||||
target: QuickDialEditorTarget::PickSlot,
|
||||
dial: Some(QuickDial { input, label: None }),
|
||||
}) if input == "/statusline model project-name model --no-colors"
|
||||
);
|
||||
|
||||
chat.local_settings.tui.terminal_title = Some(Vec::new());
|
||||
chat.local_settings.tui.status_line = Some(Vec::new());
|
||||
chat.local_settings.tui.status_line_use_colors = true;
|
||||
assert_eq!(
|
||||
chat.current_terminal_title_quick_dial(),
|
||||
Ok(QuickDial {
|
||||
input: "/title --clear".into(),
|
||||
label: None,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
chat.current_status_line_quick_dial(),
|
||||
Ok(QuickDial {
|
||||
input: "/statusline --clear --colors".into(),
|
||||
label: None,
|
||||
})
|
||||
);
|
||||
|
||||
chat.set_reasoning_effort(Some(ReasoningEffortConfig::Custom("removed-effort".into())));
|
||||
chat.terminal_title_invalid_items_warned
|
||||
.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
chat.local_settings.tui.terminal_title = Some(vec!["removed-title-item".into()]);
|
||||
chat.local_settings.tui.status_line = Some(vec!["model".into()]);
|
||||
chat.config.approvals_reviewer = ApprovalsReviewer::AutoReview;
|
||||
chat.open_quick_dial_prefills();
|
||||
assert_chatwidget_snapshot!(
|
||||
"saved_quick_dials_source_unavailable_model",
|
||||
render_bottom_popup(&chat, /*width*/ 80)
|
||||
);
|
||||
chat.handle_key_event(KeyCode::Down.into());
|
||||
chat.handle_key_event(KeyCode::Enter.into());
|
||||
assert_matches!(
|
||||
events.try_recv(),
|
||||
@@ -67,7 +124,6 @@ async fn current_settings_offer_model_and_permissions() {
|
||||
))
|
||||
.expect("set named permission profile");
|
||||
chat.open_quick_dial_prefills();
|
||||
chat.handle_key_event(KeyCode::Down.into());
|
||||
chat.handle_key_event(KeyCode::Enter.into());
|
||||
let policy = serde_json::to_string(&granular).expect("granular policy serializes");
|
||||
assert_matches!(
|
||||
|
||||
Reference in New Issue
Block a user