Optional command

This commit is contained in:
jif-oai
2025-11-19 14:21:04 +00:00
parent 72af9e3092
commit b9f260057c
3 changed files with 32 additions and 19 deletions

View File

@@ -69,7 +69,10 @@ const LARGE_PASTE_CHAR_THRESHOLD: usize = 1000;
#[derive(Debug, PartialEq)]
pub enum InputResult {
Submitted(String),
Command { command: SlashCommand, args: String },
Command {
command: SlashCommand,
args: Option<String>,
},
None,
}
@@ -333,13 +336,17 @@ impl ChatComposer {
PasteBurst::recommended_flush_delay()
}
fn command_args_from_line(line: &str, command: SlashCommand) -> String {
fn command_args_from_line(line: &str, command: SlashCommand) -> Option<String> {
if let Some((name, rest)) = parse_slash_name(line)
&& name == command.command()
{
return rest.to_string();
let trimmed = rest.trim();
if trimmed.is_empty() {
return None;
}
return Some(trimmed.to_string());
}
String::new()
None
}
/// Integrate results from an asynchronous file search.
@@ -2410,7 +2417,7 @@ mod tests {
match result {
InputResult::Command { command: cmd, args } => {
assert_eq!(cmd.command(), "init");
assert!(args.is_empty());
assert!(args.is_none());
}
InputResult::Submitted(text) => {
panic!("expected command dispatch, but composer submitted literal text: {text}")
@@ -2486,7 +2493,7 @@ mod tests {
match result {
InputResult::Command { command: cmd, args } => {
assert_eq!(cmd.command(), "diff");
assert!(args.is_empty());
assert!(args.is_none());
}
InputResult::Submitted(text) => {
panic!("expected command dispatch after Tab completion, got literal submit: {text}")
@@ -2520,7 +2527,7 @@ mod tests {
match result {
InputResult::Command { command: cmd, args } => {
assert_eq!(cmd, SlashCommand::Save);
assert_eq!(args, "feature-one");
assert_eq!(args.as_deref(), Some("feature-one"));
}
InputResult::Submitted(text) => {
panic!(
@@ -2556,7 +2563,7 @@ mod tests {
match result {
InputResult::Command { command: cmd, args } => {
assert_eq!(cmd.command(), "mention");
assert!(args.is_empty());
assert!(args.is_none());
}
InputResult::Submitted(text) => {
panic!("expected command dispatch, but composer submitted literal text: {text}")

View File

@@ -1323,7 +1323,7 @@ impl ChatWidget {
self.request_redraw();
}
fn dispatch_command(&mut self, cmd: SlashCommand, args: String) {
fn dispatch_command(&mut self, cmd: SlashCommand, args: Option<String>) {
if !cmd.available_during_task() && self.bottom_pane.is_task_running() {
let message = format!(
"'/{}' is disabled while a task is in progress.",
@@ -1463,8 +1463,14 @@ impl ChatWidget {
}
}
fn handle_save_command(&mut self, args: String) {
let name = args.trim();
fn handle_save_command(&mut self, args: Option<String>) {
let Some(name_raw) = args else {
self.add_to_history(history_cell::new_error_event(
"Usage: /save <name>".to_string(),
));
return;
};
let name = name_raw.trim();
if name.is_empty() {
self.add_to_history(history_cell::new_error_event(
"Usage: /save <name>".to_string(),

View File

@@ -954,7 +954,7 @@ fn slash_init_skips_when_project_doc_exists() {
std::fs::write(&existing_path, "existing instructions").unwrap();
chat.config.cwd = tempdir.path().to_path_buf();
chat.dispatch_command(SlashCommand::Init, String::new());
chat.dispatch_command(SlashCommand::Init, None);
match op_rx.try_recv() {
Err(TryRecvError::Empty) => {}
@@ -982,7 +982,7 @@ fn slash_init_skips_when_project_doc_exists() {
fn slash_quit_requests_exit() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual();
chat.dispatch_command(SlashCommand::Quit, String::new());
chat.dispatch_command(SlashCommand::Quit, None);
assert_matches!(rx.try_recv(), Ok(AppEvent::ExitRequest));
}
@@ -991,7 +991,7 @@ fn slash_quit_requests_exit() {
fn slash_exit_requests_exit() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual();
chat.dispatch_command(SlashCommand::Exit, String::new());
chat.dispatch_command(SlashCommand::Exit, None);
assert_matches!(rx.try_recv(), Ok(AppEvent::ExitRequest));
}
@@ -1000,7 +1000,7 @@ fn slash_exit_requests_exit() {
fn slash_undo_sends_op() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual();
chat.dispatch_command(SlashCommand::Undo, String::new());
chat.dispatch_command(SlashCommand::Undo, None);
match rx.try_recv() {
Ok(AppEvent::CodexOp(Op::Undo)) => {}
@@ -1014,7 +1014,7 @@ fn slash_rollout_displays_current_path() {
let rollout_path = PathBuf::from("/tmp/codex-test-rollout.jsonl");
chat.current_rollout_path = Some(rollout_path.clone());
chat.dispatch_command(SlashCommand::Rollout, String::new());
chat.dispatch_command(SlashCommand::Rollout, None);
let cells = drain_insert_history(&mut rx);
assert_eq!(cells.len(), 1, "expected info message for rollout path");
@@ -1029,7 +1029,7 @@ fn slash_rollout_displays_current_path() {
fn slash_rollout_handles_missing_path() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual();
chat.dispatch_command(SlashCommand::Rollout, String::new());
chat.dispatch_command(SlashCommand::Rollout, None);
let cells = drain_insert_history(&mut rx);
assert_eq!(
@@ -1624,7 +1624,7 @@ fn feedback_selection_popup_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();
// Open the feedback category selection popup via slash command.
chat.dispatch_command(SlashCommand::Feedback, String::new());
chat.dispatch_command(SlashCommand::Feedback, None);
let popup = render_bottom_popup(&chat, 80);
assert_snapshot!("feedback_selection_popup", popup);
@@ -1702,7 +1702,7 @@ fn disabled_slash_command_while_task_running_snapshot() {
chat.bottom_pane.set_task_running(true);
// Dispatch a command that is unavailable while a task runs (e.g., /model)
chat.dispatch_command(SlashCommand::Model, String::new());
chat.dispatch_command(SlashCommand::Model, None);
// Drain history and snapshot the rendered error line(s)
let cells = drain_insert_history(&mut rx);