mirror of
https://github.com/openai/codex.git
synced 2026-09-14 11:57:03 +00:00
tui: centralize slash command serialization
Introduce a small slash command invocation serializer and consolidate built-in command metadata behind a single spec table without changing slash command runtime behavior. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -192,6 +192,7 @@ use crate::render::Insets;
|
||||
use crate::render::RectExt;
|
||||
use crate::render::renderable::Renderable;
|
||||
use crate::slash_command::SlashCommand;
|
||||
use crate::slash_command_invocation::SlashCommandInvocation;
|
||||
use crate::style::user_message_style;
|
||||
use codex_protocol::custom_prompts::CustomPrompt;
|
||||
use codex_protocol::custom_prompts::PROMPTS_CMD_PREFIX;
|
||||
@@ -1423,12 +1424,13 @@ impl ChatComposer {
|
||||
return (InputResult::Command(cmd), true);
|
||||
}
|
||||
|
||||
let starts_with_cmd = first_line
|
||||
.trim_start()
|
||||
.starts_with(&format!("/{}", cmd.command()));
|
||||
let bare_command =
|
||||
SlashCommandInvocation::bare(cmd).into_prefixed_string();
|
||||
let starts_with_cmd =
|
||||
first_line.trim_start().starts_with(&bare_command);
|
||||
if !starts_with_cmd {
|
||||
self.textarea
|
||||
.set_text_clearing_elements(&format!("/{} ", cmd.command()));
|
||||
.set_text_clearing_elements(&format!("{bare_command} "));
|
||||
}
|
||||
if !self.textarea.text().is_empty() {
|
||||
cursor_target = Some(self.textarea.text().len());
|
||||
|
||||
@@ -14,11 +14,6 @@ use codex_protocol::custom_prompts::CustomPrompt;
|
||||
use codex_protocol::custom_prompts::PROMPTS_CMD_PREFIX;
|
||||
use std::collections::HashSet;
|
||||
|
||||
// Hide alias commands in the default popup list so each unique action appears once.
|
||||
// `quit` is an alias of `exit`, so we skip `quit` here.
|
||||
// `approvals` is an alias of `permissions`.
|
||||
const ALIAS_COMMANDS: &[SlashCommand] = &[SlashCommand::Quit, SlashCommand::Approvals];
|
||||
|
||||
/// A selectable item in the popup: either a built-in command or a user prompt.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum CommandItem {
|
||||
@@ -145,7 +140,7 @@ impl CommandPopup {
|
||||
if filter.is_empty() {
|
||||
// Built-ins first, in presentation order.
|
||||
for (_, cmd) in self.builtins.iter() {
|
||||
if ALIAS_COMMANDS.contains(cmd) {
|
||||
if !cmd.show_in_command_popup() {
|
||||
continue;
|
||||
}
|
||||
out.push((CommandItem::Builtin(*cmd), None));
|
||||
|
||||
@@ -122,6 +122,7 @@ mod session_log;
|
||||
mod shimmer;
|
||||
mod skills_helpers;
|
||||
mod slash_command;
|
||||
mod slash_command_invocation;
|
||||
mod status;
|
||||
mod status_indicator_widget;
|
||||
mod streaming;
|
||||
|
||||
@@ -67,55 +67,278 @@ pub enum SlashCommand {
|
||||
}
|
||||
|
||||
impl SlashCommand {
|
||||
fn spec(self) -> SlashCommandSpec {
|
||||
match self {
|
||||
SlashCommand::Model => SlashCommandSpec {
|
||||
description: "choose what model and reasoning effort to use",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Fast => SlashCommandSpec {
|
||||
description: "toggle Fast mode to enable fastest inference at 2X plan usage",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Approvals => SlashCommandSpec {
|
||||
description: "choose what Codex is allowed to do",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Permissions => SlashCommandSpec {
|
||||
description: "choose what Codex is allowed to do",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::ElevateSandbox => SlashCommandSpec {
|
||||
description: "set up elevated agent sandbox",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::SandboxReadRoot => SlashCommandSpec {
|
||||
description: "let sandbox read a directory: /sandbox-add-read-dir <absolute_path>",
|
||||
available_during_task: false,
|
||||
is_disabled: !cfg!(target_os = "windows"),
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Experimental => SlashCommandSpec {
|
||||
description: "toggle experimental features",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Skills => SlashCommandSpec {
|
||||
description: "use skills to improve how Codex performs specific tasks",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Review => SlashCommandSpec {
|
||||
description: "review my current changes and find issues",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Rename => SlashCommandSpec {
|
||||
description: "rename the current thread",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::New => SlashCommandSpec {
|
||||
description: "start a new chat during a conversation",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Resume => SlashCommandSpec {
|
||||
description: "resume a saved chat",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Fork => SlashCommandSpec {
|
||||
description: "fork the current chat",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Init => SlashCommandSpec {
|
||||
description: "create an AGENTS.md file with instructions for Codex",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Compact => SlashCommandSpec {
|
||||
description: "summarize conversation to prevent hitting the context limit",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Plan => SlashCommandSpec {
|
||||
description: "switch to Plan mode",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Collab => SlashCommandSpec {
|
||||
description: "change collaboration mode (experimental)",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Agent => SlashCommandSpec {
|
||||
description: "switch the active agent thread",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Diff => SlashCommandSpec {
|
||||
description: "show git diff (including untracked files)",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Copy => SlashCommandSpec {
|
||||
description: "copy the latest Codex output to your clipboard",
|
||||
available_during_task: true,
|
||||
is_disabled: cfg!(target_os = "android"),
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Mention => SlashCommandSpec {
|
||||
description: "mention a file",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Status => SlashCommandSpec {
|
||||
description: "show current session configuration and token usage",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::DebugConfig => SlashCommandSpec {
|
||||
description: "show config layers and requirement sources for debugging",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Title => SlashCommandSpec {
|
||||
description: "configure which items appear in the terminal title",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Statusline => SlashCommandSpec {
|
||||
description: "configure which items appear in the status line",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Theme => SlashCommandSpec {
|
||||
description: "choose a syntax highlighting theme",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Mcp => SlashCommandSpec {
|
||||
description: "list configured MCP tools",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Apps => SlashCommandSpec {
|
||||
description: "manage apps",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Plugins => SlashCommandSpec {
|
||||
description: "browse plugins",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Logout => SlashCommandSpec {
|
||||
description: "log out of Codex",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Quit => SlashCommandSpec {
|
||||
description: "exit Codex",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: true,
|
||||
},
|
||||
SlashCommand::Exit => SlashCommandSpec {
|
||||
description: "exit Codex",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Feedback => SlashCommandSpec {
|
||||
description: "send logs to maintainers",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Rollout => SlashCommandSpec {
|
||||
description: "print the rollout file path",
|
||||
available_during_task: true,
|
||||
is_disabled: !cfg!(debug_assertions),
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Ps => SlashCommandSpec {
|
||||
description: "list background terminals",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Stop => SlashCommandSpec {
|
||||
description: "stop all background terminals",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Clear => SlashCommandSpec {
|
||||
description: "clear the terminal and start a new chat",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Personality => SlashCommandSpec {
|
||||
description: "choose a communication style for Codex",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Realtime => SlashCommandSpec {
|
||||
description: "toggle realtime voice mode (experimental)",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::Settings => SlashCommandSpec {
|
||||
description: "configure realtime microphone/speaker",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::TestApproval => SlashCommandSpec {
|
||||
description: "test approval request",
|
||||
available_during_task: true,
|
||||
is_disabled: !cfg!(debug_assertions),
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::MultiAgents => SlashCommandSpec {
|
||||
description: "switch the active agent thread",
|
||||
available_during_task: true,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::MemoryDrop => SlashCommandSpec {
|
||||
description: "DO NOT USE",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
SlashCommand::MemoryUpdate => SlashCommandSpec {
|
||||
description: "DO NOT USE",
|
||||
available_during_task: false,
|
||||
is_disabled: false,
|
||||
hide_in_command_popup: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// User-visible description shown in the popup.
|
||||
pub fn description(self) -> &'static str {
|
||||
match self {
|
||||
SlashCommand::Feedback => "send logs to maintainers",
|
||||
SlashCommand::New => "start a new chat during a conversation",
|
||||
SlashCommand::Init => "create an AGENTS.md file with instructions for Codex",
|
||||
SlashCommand::Compact => "summarize conversation to prevent hitting the context limit",
|
||||
SlashCommand::Review => "review my current changes and find issues",
|
||||
SlashCommand::Rename => "rename the current thread",
|
||||
SlashCommand::Resume => "resume a saved chat",
|
||||
SlashCommand::Clear => "clear the terminal and start a new chat",
|
||||
SlashCommand::Fork => "fork the current chat",
|
||||
// SlashCommand::Undo => "ask Codex to undo a turn",
|
||||
SlashCommand::Quit | SlashCommand::Exit => "exit Codex",
|
||||
SlashCommand::Diff => "show git diff (including untracked files)",
|
||||
SlashCommand::Copy => "copy the latest Codex output to your clipboard",
|
||||
SlashCommand::Mention => "mention a file",
|
||||
SlashCommand::Skills => "use skills to improve how Codex performs specific tasks",
|
||||
SlashCommand::Status => "show current session configuration and token usage",
|
||||
SlashCommand::DebugConfig => "show config layers and requirement sources for debugging",
|
||||
SlashCommand::Title => "configure which items appear in the terminal title",
|
||||
SlashCommand::Statusline => "configure which items appear in the status line",
|
||||
SlashCommand::Theme => "choose a syntax highlighting theme",
|
||||
SlashCommand::Ps => "list background terminals",
|
||||
SlashCommand::Stop => "stop all background terminals",
|
||||
SlashCommand::MemoryDrop => "DO NOT USE",
|
||||
SlashCommand::MemoryUpdate => "DO NOT USE",
|
||||
SlashCommand::Model => "choose what model and reasoning effort to use",
|
||||
SlashCommand::Fast => "toggle Fast mode to enable fastest inference at 2X plan usage",
|
||||
SlashCommand::Personality => "choose a communication style for Codex",
|
||||
SlashCommand::Realtime => "toggle realtime voice mode (experimental)",
|
||||
SlashCommand::Settings => "configure realtime microphone/speaker",
|
||||
SlashCommand::Plan => "switch to Plan mode",
|
||||
SlashCommand::Collab => "change collaboration mode (experimental)",
|
||||
SlashCommand::Agent | SlashCommand::MultiAgents => "switch the active agent thread",
|
||||
SlashCommand::Approvals => "choose what Codex is allowed to do",
|
||||
SlashCommand::Permissions => "choose what Codex is allowed to do",
|
||||
SlashCommand::ElevateSandbox => "set up elevated agent sandbox",
|
||||
SlashCommand::SandboxReadRoot => {
|
||||
"let sandbox read a directory: /sandbox-add-read-dir <absolute_path>"
|
||||
}
|
||||
SlashCommand::Experimental => "toggle experimental features",
|
||||
SlashCommand::Mcp => "list configured MCP tools",
|
||||
SlashCommand::Apps => "manage apps",
|
||||
SlashCommand::Plugins => "browse plugins",
|
||||
SlashCommand::Logout => "log out of Codex",
|
||||
SlashCommand::Rollout => "print the rollout file path",
|
||||
SlashCommand::TestApproval => "test approval request",
|
||||
}
|
||||
self.spec().description
|
||||
}
|
||||
|
||||
/// Command string without the leading '/'. Provided for compatibility with
|
||||
@@ -124,86 +347,89 @@ impl SlashCommand {
|
||||
self.into()
|
||||
}
|
||||
|
||||
/// Whether this command supports inline args (for example `/review ...`).
|
||||
pub fn supports_inline_args(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
SlashCommand::Review
|
||||
| SlashCommand::Rename
|
||||
| SlashCommand::Plan
|
||||
| SlashCommand::Fast
|
||||
| SlashCommand::SandboxReadRoot
|
||||
)
|
||||
/// User-visible usage forms for this command.
|
||||
pub(crate) fn usage_lines(self) -> &'static [&'static str] {
|
||||
match self {
|
||||
SlashCommand::Model => &["/model"],
|
||||
SlashCommand::Fast => &["/fast", "/fast [on|off|status]"],
|
||||
SlashCommand::Approvals => &["/approvals"],
|
||||
SlashCommand::Permissions => &["/permissions"],
|
||||
SlashCommand::ElevateSandbox => &["/setup-default-sandbox"],
|
||||
SlashCommand::SandboxReadRoot => &["/sandbox-add-read-dir <absolute-path>"],
|
||||
SlashCommand::Experimental => &["/experimental"],
|
||||
SlashCommand::Skills => &["/skills"],
|
||||
SlashCommand::Review => &["/review", "/review <instructions>"],
|
||||
SlashCommand::Rename => &["/rename", "/rename <title>"],
|
||||
SlashCommand::New => &["/new"],
|
||||
SlashCommand::Resume => &["/resume"],
|
||||
SlashCommand::Fork => &["/fork"],
|
||||
SlashCommand::Init => &["/init"],
|
||||
SlashCommand::Compact => &["/compact"],
|
||||
SlashCommand::Plan => &["/plan", "/plan <prompt>"],
|
||||
SlashCommand::Collab => &["/collab"],
|
||||
SlashCommand::Agent => &["/agent"],
|
||||
SlashCommand::Diff => &["/diff"],
|
||||
SlashCommand::Copy => &["/copy"],
|
||||
SlashCommand::Mention => &["/mention"],
|
||||
SlashCommand::Status => &["/status"],
|
||||
SlashCommand::DebugConfig => &["/debug-config"],
|
||||
SlashCommand::Title => &["/title"],
|
||||
SlashCommand::Statusline => &["/statusline"],
|
||||
SlashCommand::Theme => &["/theme"],
|
||||
SlashCommand::Mcp => &["/mcp"],
|
||||
SlashCommand::Apps => &["/apps"],
|
||||
SlashCommand::Plugins => &["/plugins"],
|
||||
SlashCommand::Logout => &["/logout"],
|
||||
SlashCommand::Quit => &["/quit"],
|
||||
SlashCommand::Exit => &["/exit"],
|
||||
SlashCommand::Feedback => &["/feedback"],
|
||||
SlashCommand::Rollout => &["/rollout"],
|
||||
SlashCommand::Ps => &["/ps"],
|
||||
SlashCommand::Stop => &["/stop"],
|
||||
SlashCommand::Clear => &["/clear"],
|
||||
SlashCommand::Personality => &["/personality"],
|
||||
SlashCommand::Realtime => &["/realtime"],
|
||||
SlashCommand::Settings => &["/settings"],
|
||||
SlashCommand::TestApproval => &["/test-approval"],
|
||||
SlashCommand::MultiAgents => &["/subagents"],
|
||||
SlashCommand::MemoryDrop => &["/debug-m-drop"],
|
||||
SlashCommand::MemoryUpdate => &["/debug-m-update"],
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this command can be run while a task is in progress.
|
||||
pub fn available_during_task(self) -> bool {
|
||||
match self {
|
||||
SlashCommand::New
|
||||
| SlashCommand::Resume
|
||||
| SlashCommand::Fork
|
||||
| SlashCommand::Init
|
||||
| SlashCommand::Compact
|
||||
// | SlashCommand::Undo
|
||||
| SlashCommand::Model
|
||||
| SlashCommand::Fast
|
||||
| SlashCommand::Personality
|
||||
| SlashCommand::Approvals
|
||||
| SlashCommand::Permissions
|
||||
| SlashCommand::ElevateSandbox
|
||||
| SlashCommand::SandboxReadRoot
|
||||
| SlashCommand::Experimental
|
||||
| SlashCommand::Review
|
||||
| SlashCommand::Plan
|
||||
| SlashCommand::Clear
|
||||
| SlashCommand::Logout
|
||||
| SlashCommand::MemoryDrop
|
||||
| SlashCommand::MemoryUpdate => false,
|
||||
SlashCommand::Diff
|
||||
| SlashCommand::Copy
|
||||
| SlashCommand::Rename
|
||||
| SlashCommand::Mention
|
||||
| SlashCommand::Skills
|
||||
| SlashCommand::Status
|
||||
| SlashCommand::DebugConfig
|
||||
| SlashCommand::Ps
|
||||
| SlashCommand::Stop
|
||||
| SlashCommand::Mcp
|
||||
| SlashCommand::Apps
|
||||
| SlashCommand::Plugins
|
||||
| SlashCommand::Feedback
|
||||
| SlashCommand::Quit
|
||||
| SlashCommand::Exit => true,
|
||||
SlashCommand::Rollout => true,
|
||||
SlashCommand::TestApproval => true,
|
||||
SlashCommand::Realtime => true,
|
||||
SlashCommand::Settings => true,
|
||||
SlashCommand::Collab => true,
|
||||
SlashCommand::Agent | SlashCommand::MultiAgents => true,
|
||||
SlashCommand::Statusline => false,
|
||||
SlashCommand::Theme => false,
|
||||
SlashCommand::Title => false,
|
||||
}
|
||||
self.spec().available_during_task
|
||||
}
|
||||
|
||||
fn is_visible(self) -> bool {
|
||||
match self {
|
||||
SlashCommand::SandboxReadRoot => cfg!(target_os = "windows"),
|
||||
SlashCommand::Copy => !cfg!(target_os = "android"),
|
||||
SlashCommand::Rollout | SlashCommand::TestApproval => cfg!(debug_assertions),
|
||||
_ => true,
|
||||
}
|
||||
pub(crate) fn hide_in_command_popup(self) -> bool {
|
||||
self.spec().hide_in_command_popup
|
||||
}
|
||||
|
||||
/// Whether this command is disabled for the current build target.
|
||||
///
|
||||
/// This is used for OS-specific or build-specific commands that still belong in the shared
|
||||
/// enum, such as Windows-only or debug-only slash commands.
|
||||
fn is_disabled(self) -> bool {
|
||||
self.spec().is_disabled
|
||||
}
|
||||
}
|
||||
|
||||
/// Return all built-in commands in a Vec paired with their command string.
|
||||
pub fn built_in_slash_commands() -> Vec<(&'static str, SlashCommand)> {
|
||||
SlashCommand::iter()
|
||||
.filter(|command| command.is_visible())
|
||||
.filter(|command| !command.is_disabled())
|
||||
.map(|c| (c.command(), c))
|
||||
.collect()
|
||||
}
|
||||
|
||||
struct SlashCommandSpec {
|
||||
description: &'static str,
|
||||
available_during_task: bool,
|
||||
is_disabled: bool,
|
||||
hide_in_command_popup: bool,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -212,12 +438,30 @@ mod tests {
|
||||
use super::SlashCommand;
|
||||
|
||||
#[test]
|
||||
fn stop_command_is_canonical_name() {
|
||||
assert_eq!(SlashCommand::Stop.command(), "stop");
|
||||
fn approvals_alias_is_hidden_from_command_popup() {
|
||||
assert!(SlashCommand::Approvals.hide_in_command_popup());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clean_alias_parses_to_stop_command() {
|
||||
assert_eq!(SlashCommand::from_str("clean"), Ok(SlashCommand::Stop));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stop_command_is_canonical_name() {
|
||||
assert_eq!(SlashCommand::Stop.command(), "stop");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fast_usage_lists_bare_and_arg_forms() {
|
||||
assert_eq!(
|
||||
SlashCommand::Fast.usage_lines(),
|
||||
["/fast", "/fast [on|off|status]"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_usage_is_bare_only() {
|
||||
assert_eq!(SlashCommand::Clear.usage_lines(), ["/clear"]);
|
||||
}
|
||||
}
|
||||
|
||||
40
codex-rs/tui/src/slash_command_invocation.rs
Normal file
40
codex-rs/tui/src/slash_command_invocation.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use crate::slash_command::SlashCommand;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct SlashCommandInvocation {
|
||||
command: SlashCommand,
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
impl SlashCommandInvocation {
|
||||
pub(crate) fn bare(command: SlashCommand) -> Self {
|
||||
Self {
|
||||
command,
|
||||
args: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_prefixed_string(self) -> String {
|
||||
let command = self.command.command();
|
||||
let joined = match shlex::try_join(
|
||||
std::iter::once(command).chain(self.args.iter().map(String::as_str)),
|
||||
) {
|
||||
Ok(joined) => joined,
|
||||
Err(err) => panic!("slash command invocation should serialize: {err}"),
|
||||
};
|
||||
format!("/{joined}")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
fn bare_invocation_serializes_with_leading_slash() {
|
||||
let invocation = SlashCommandInvocation::bare(SlashCommand::Model);
|
||||
|
||||
assert_eq!(invocation.into_prefixed_string(), "/model");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user