in codex-rs/tui/src/chatwidget/slash_dispatch.rs [612:838]
fn dispatch_prepared_command_with_args(
&mut self,
cmd: SlashCommand,
prepared: PreparedSlashCommandArgs,
) {
let PreparedSlashCommandArgs {
args,
text_elements,
local_images,
remote_image_urls,
mention_bindings,
source,
} = prepared;
let trimmed = args.trim();
match cmd {
SlashCommand::Ide => {
self.handle_ide_command_args(trimmed);
}
SlashCommand::Mcp => match trimmed.to_ascii_lowercase().as_str() {
"verbose" => self.add_mcp_output(McpServerStatusDetail::Full),
_ => self.add_error_message("Usage: /mcp [verbose]".to_string()),
},
SlashCommand::Keymap => match trimmed.to_ascii_lowercase().as_str() {
"" => self.open_keymap_picker(),
"debug" => {
match crate::keymap::RuntimeKeymap::from_config(&self.config.tui_keymap) {
Ok(runtime_keymap) => self.open_keymap_debug(&runtime_keymap),
Err(err) => {
self.add_error_message(format!(
"Invalid `tui.keymap` configuration: {err}"
));
}
}
}
_ => self.add_error_message("Usage: /keymap [debug]".to_string()),
},
SlashCommand::Raw => match trimmed.to_ascii_lowercase().as_str() {
"on" => {
self.set_raw_output_mode_and_notify(/*enabled*/ true);
self.emit_raw_output_mode_changed(/*enabled*/ true);
}
"off" => {
self.set_raw_output_mode_and_notify(/*enabled*/ false);
self.emit_raw_output_mode_changed(/*enabled*/ false);
}
_ => self.add_error_message(RAW_USAGE.to_string()),
},
SlashCommand::Rename if !trimmed.is_empty() => {
if !self.ensure_thread_rename_allowed() {
return;
}
self.session_telemetry
.counter("codex.thread.rename", /*inc*/ 1, &[]);
let Some(name) = crate::legacy_core::util::normalize_thread_name(&args) else {
self.add_error_message("Thread name cannot be empty.".to_string());
return;
};
self.app_event_tx.set_thread_name(name);
}
SlashCommand::Plan if !trimmed.is_empty() => {
if !self.apply_plan_slash_command() {
return;
}
let user_message = self.prepared_inline_user_message(
args,
text_elements,
local_images,
remote_image_urls,
mention_bindings,
source,
);
if self.is_session_configured() {
self.reasoning_buffer.clear();
self.full_reasoning_buffer.clear();
self.set_status_header(String::from("Working"));
self.submit_user_message(user_message);
} else {
self.queue_user_message(user_message);
}
}
SlashCommand::Goal if !trimmed.is_empty() => {
if !self.config.features.enabled(Feature::Goals) {
return;
}
enum GoalControlCommand {
Clear,
SetStatus(AppThreadGoalStatus),
}
let control_command = match trimmed.to_ascii_lowercase().as_str() {
"clear" => Some(GoalControlCommand::Clear),
"edit" => {
self.app_event_tx.send(AppEvent::OpenThreadGoalEditor {
thread_id: self.thread_id,
});
if source == SlashCommandDispatchSource::Live {
self.bottom_pane.drain_pending_submission_state();
}
return;
}
"pause" => Some(GoalControlCommand::SetStatus(AppThreadGoalStatus::Paused)),
"resume" => Some(GoalControlCommand::SetStatus(AppThreadGoalStatus::Active)),
_ => None,
};
if let Some(command) = control_command {
let Some(thread_id) = self.thread_id else {
self.add_info_message(
GOAL_USAGE.to_string(),
Some(
"The session must start before you can change a goal.".to_string(),
),
);
return;
};
match command {
GoalControlCommand::Clear => {
self.app_event_tx
.send(AppEvent::ClearThreadGoal { thread_id });
}
GoalControlCommand::SetStatus(status) => {
self.app_event_tx
.send(AppEvent::SetThreadGoalStatus { thread_id, status });
}
}
self.append_message_history_entry(format!("/goal {trimmed}"));
if source == SlashCommandDispatchSource::Live {
self.bottom_pane.drain_pending_submission_state();
}
return;
}
let objective = args.trim();
if objective.is_empty() {
self.add_error_message("Goal objective must not be empty.".to_string());
self.add_info_message(
GOAL_USAGE.to_string(),
Some(GOAL_USAGE_HINT.to_string()),
);
if source == SlashCommandDispatchSource::Live {
self.bottom_pane.drain_pending_submission_state();
}
return;
}
let validation_source = match source {
SlashCommandDispatchSource::Live => GoalObjectiveValidationSource::Live,
SlashCommandDispatchSource::Queued => GoalObjectiveValidationSource::Queued,
};
if !self.goal_objective_is_allowed(objective, validation_source) {
return;
}
let Some(thread_id) = self.thread_id else {
if source == SlashCommandDispatchSource::Live {
self.queue_user_message_with_options(
UserMessage {
text: format!("/goal {args}"),
local_images: Vec::new(),
remote_image_urls: Vec::new(),
text_elements: Vec::new(),
mention_bindings: Vec::new(),
},
QueuedInputAction::ParseSlash,
);
self.bottom_pane.drain_pending_submission_state();
} else {
self.add_info_message(
GOAL_USAGE.to_string(),
Some("The session must start before you can set a goal.".to_string()),
);
}
return;
};
self.app_event_tx.send(AppEvent::SetThreadGoalObjective {
thread_id,
objective: objective.to_string(),
mode: ThreadGoalSetMode::ConfirmIfExists,
});
self.append_message_history_entry(format!("/goal {trimmed}"));
if source == SlashCommandDispatchSource::Live {
self.bottom_pane.drain_pending_submission_state();
}
}
SlashCommand::Side | SlashCommand::Btw if !trimmed.is_empty() => {
let Some(parent_thread_id) = self.thread_id else {
let command = cmd.command();
self.add_error_message(format!(
"'/{command}' is unavailable before the session starts."
));
return;
};
let user_message = self.prepared_inline_user_message(
args,
text_elements,
local_images,
remote_image_urls,
mention_bindings,
source,
);
self.request_side_conversation(parent_thread_id, Some(user_message));
}
SlashCommand::Review if !trimmed.is_empty() => {
self.submit_op(AppCommand::review(ReviewTarget::Custom {
instructions: args,
}));
}
SlashCommand::Resume if !trimmed.is_empty() => {
self.app_event_tx
.send(AppEvent::ResumeSessionByIdOrName(args));
}
SlashCommand::SandboxReadRoot if !trimmed.is_empty() => {
self.app_event_tx
.send(AppEvent::BeginWindowsSandboxGrantReadRoot { path: args });
}
SlashCommand::Pets
if matches!(
args.trim().to_ascii_lowercase().as_str(),
"disable" | "disabled" | "hide" | "hidden" | "off" | "none"
) =>
{
self.app_event_tx.send(AppEvent::PetDisabled);
}
SlashCommand::Pets if !trimmed.is_empty() => {
self.select_pet_by_id(args);
}
_ => self.dispatch_command(cmd),
}
if source == SlashCommandDispatchSource::Live && cmd != SlashCommand::Goal {
self.bottom_pane.drain_pending_submission_state();
}
}