Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 67 additions & 7 deletions codex-rs/tui/src/bottom_pane/chat_composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -954,21 +954,17 @@ impl ChatComposer {
return (InputResult::None, true);
}
}
// If we have pending placeholder pastes, submit immediately to expand them.
// If we have pending placeholder pastes, replace them in the textarea text
// and continue to the normal submission flow to handle slash commands.
if !self.pending_pastes.is_empty() {
let mut text = self.textarea.text().to_string();
self.textarea.set_text("");
for (placeholder, actual) in &self.pending_pastes {
if text.contains(placeholder) {
text = text.replace(placeholder, actual);
}
}
self.textarea.set_text(&text);
self.pending_pastes.clear();
if text.is_empty() {
return (InputResult::None, true);
}
self.history.record_local_submission(&text);
return (InputResult::Submitted(text), true);
}

// During a paste-like burst, treat Enter as a newline instead of submit.
Expand Down Expand Up @@ -3030,6 +3026,70 @@ mod tests {
assert!(composer.textarea.is_empty());
}

#[test]
fn custom_prompt_with_large_paste_expands_correctly() {
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
use crossterm::event::KeyModifiers;

let (tx, _rx) = unbounded_channel::<AppEvent>();
let sender = AppEventSender::new(tx);
let mut composer = ChatComposer::new(
true,
sender,
false,
"Ask Codex to do anything".to_string(),
false,
);

// Create a custom prompt with positional args (no named args like $USER)
composer.set_custom_prompts(vec![CustomPrompt {
name: "code-review".to_string(),
path: "/tmp/code-review.md".to_string().into(),
content: "Please review the following code:\n\n$1".to_string(),
description: None,
argument_hint: None,
}]);

// Type the slash command
let command_text = "/prompts:code-review ";
composer.textarea.set_text(command_text);
composer.textarea.set_cursor(command_text.len());

// Paste large content (>3000 chars) to trigger placeholder
let large_content = "x".repeat(LARGE_PASTE_CHAR_THRESHOLD + 3000);
composer.handle_paste(large_content.clone());

// Verify placeholder was created
let placeholder = format!("[Pasted Content {} chars]", large_content.chars().count());
assert_eq!(
composer.textarea.text(),
format!("/prompts:code-review {}", placeholder)
);
assert_eq!(composer.pending_pastes.len(), 1);
assert_eq!(composer.pending_pastes[0].0, placeholder);
assert_eq!(composer.pending_pastes[0].1, large_content);

// Submit by pressing Enter
let (result, _needs_redraw) =
composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));

// Verify the custom prompt was expanded with the large content as positional arg
match result {
InputResult::Submitted(text) => {
// The prompt should be expanded, with the large content replacing $1
assert_eq!(
text,
format!("Please review the following code:\n\n{}", large_content),
"Expected prompt expansion with large content as $1"
);
}
_ => panic!("expected Submitted, got: {result:?}"),
}
assert!(composer.textarea.is_empty());
assert!(composer.pending_pastes.is_empty());
}

#[test]
fn slash_path_input_submits_without_command_error() {
use crossterm::event::KeyCode;
Expand Down
Loading