Add /title terminal title override

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Omer Strulovich
2026-03-04 15:50:23 -05:00
parent 8159f05dfd
commit d1e20cdcac
8 changed files with 213 additions and 1 deletions

View File

@@ -607,6 +607,7 @@ pub(crate) struct ChatWidget {
pending_status_indicator_restore: bool,
thread_id: Option<ThreadId>,
thread_name: Option<String>,
title_override: Option<String>,
forked_from: Option<ThreadId>,
frame_requester: FrameRequester,
// Whether to include the initial welcome banner on session configured
@@ -2913,6 +2914,7 @@ impl ChatWidget {
pending_status_indicator_restore: false,
thread_id: None,
thread_name: None,
title_override: None,
forked_from: None,
queued_user_messages: VecDeque::new(),
queued_message_edit_binding,
@@ -3093,6 +3095,7 @@ impl ChatWidget {
pending_status_indicator_restore: false,
thread_id: None,
thread_name: None,
title_override: None,
forked_from: None,
saw_plan_update_this_turn: false,
saw_plan_item_this_turn: false,
@@ -3262,6 +3265,7 @@ impl ChatWidget {
pending_status_indicator_restore: false,
thread_id: None,
thread_name: None,
title_override: None,
forked_from: None,
queued_user_messages: VecDeque::new(),
queued_message_edit_binding,
@@ -3607,6 +3611,9 @@ impl ChatWidget {
self.otel_manager.counter("codex.thread.rename", 1, &[]);
self.show_rename_prompt();
}
SlashCommand::Title => {
self.show_title_prompt();
}
SlashCommand::Model => {
self.open_model_popup();
}
@@ -3936,6 +3943,16 @@ impl ChatWidget {
.send(AppEvent::CodexOp(Op::SetThreadName { name }));
self.bottom_pane.drain_pending_submission_state();
}
SlashCommand::Title if !trimmed.is_empty() => {
let Some(name) = codex_core::util::normalize_thread_name(trimmed) else {
self.add_error_message("Title cannot be empty.".to_string());
return;
};
let cell = Self::title_confirmation_cell(&name);
self.add_boxed_history(Box::new(cell));
self.set_title_override(name);
self.request_redraw();
}
SlashCommand::Plan if !trimmed.is_empty() => {
self.dispatch_command(cmd);
if self.active_mode_kind() != ModeKind::Plan {
@@ -4030,6 +4047,28 @@ impl ChatWidget {
self.bottom_pane.show_view(Box::new(view));
}
fn show_title_prompt(&mut self) {
let tx = self.app_event_tx.clone();
let view = CustomPromptView::new(
"Set title".to_string(),
"Type a title and press Enter".to_string(),
None,
Box::new(move |name: String| {
let Some(name) = codex_core::util::normalize_thread_name(&name) else {
tx.send(AppEvent::InsertHistoryCell(Box::new(
history_cell::new_error_event("Title cannot be empty.".to_string()),
)));
return;
};
let cell = Self::title_confirmation_cell(&name);
tx.send(AppEvent::InsertHistoryCell(Box::new(cell)));
tx.send(AppEvent::SetTitle(name));
}),
);
self.bottom_pane.show_view(Box::new(view));
}
pub(crate) fn handle_paste(&mut self, text: String) {
self.bottom_pane.handle_paste(text);
}
@@ -7306,6 +7345,12 @@ impl ChatWidget {
PlainHistoryCell::new(vec![line.into()])
}
fn title_confirmation_cell(name: &str) -> PlainHistoryCell {
PlainHistoryCell::new(vec![
vec!["".into(), "Title set to ".into(), name.to_string().cyan()].into(),
])
}
pub(crate) fn add_mcp_output(&mut self) {
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
self.config.codex_home.clone(),
@@ -7984,6 +8029,14 @@ impl ChatWidget {
self.thread_name.clone()
}
pub(crate) fn title_override(&self) -> Option<String> {
self.title_override.clone()
}
pub(crate) fn set_title_override(&mut self, title: String) {
self.title_override = Some(title);
}
/// Returns the current thread's precomputed rollout path.
///
/// For fresh non-ephemeral threads this path may exist before the file is