Ensure duplicate-length paste placeholders stay distinct (#7431)
Fix issue #7430 Generate unique numbered placeholders for multiple large pastes of the same length so deleting one no longer removes the others. Signed-off-by: Joshua <joshua1s@protonmail.com>
This commit is contained in:
parent
6b5b9a687e
commit
ad9eeeb287
1 changed files with 91 additions and 1 deletions
|
|
@ -101,6 +101,7 @@ pub(crate) struct ChatComposer {
|
|||
dismissed_file_popup_token: Option<String>,
|
||||
current_file_query: Option<String>,
|
||||
pending_pastes: Vec<(String, String)>,
|
||||
large_paste_counters: HashMap<usize, usize>,
|
||||
has_focus: bool,
|
||||
attached_images: Vec<AttachedImage>,
|
||||
placeholder_text: String,
|
||||
|
|
@ -147,6 +148,7 @@ impl ChatComposer {
|
|||
dismissed_file_popup_token: None,
|
||||
current_file_query: None,
|
||||
pending_pastes: Vec::new(),
|
||||
large_paste_counters: HashMap::new(),
|
||||
has_focus: has_input_focus,
|
||||
attached_images: Vec::new(),
|
||||
placeholder_text,
|
||||
|
|
@ -222,7 +224,7 @@ impl ChatComposer {
|
|||
pub fn handle_paste(&mut self, pasted: String) -> bool {
|
||||
let char_count = pasted.chars().count();
|
||||
if char_count > LARGE_PASTE_CHAR_THRESHOLD {
|
||||
let placeholder = format!("[Pasted Content {char_count} chars]");
|
||||
let placeholder = self.next_large_paste_placeholder(char_count);
|
||||
self.textarea.insert_element(&placeholder);
|
||||
self.pending_pastes.push((placeholder, pasted));
|
||||
} else if char_count > 1 && self.handle_paste_image_path(pasted.clone()) {
|
||||
|
|
@ -362,6 +364,17 @@ impl ChatComposer {
|
|||
self.set_has_focus(has_focus);
|
||||
}
|
||||
|
||||
fn next_large_paste_placeholder(&mut self, char_count: usize) -> String {
|
||||
let base = format!("[Pasted Content {char_count} chars]");
|
||||
let next_suffix = self.large_paste_counters.entry(char_count).or_insert(0);
|
||||
*next_suffix += 1;
|
||||
if *next_suffix == 1 {
|
||||
base
|
||||
} else {
|
||||
format!("{base} #{next_suffix}")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert_str(&mut self, text: &str) {
|
||||
self.textarea.insert_str(text);
|
||||
self.sync_command_popup();
|
||||
|
|
@ -2671,6 +2684,83 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deleting_duplicate_length_pastes_removes_only_target() {
|
||||
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,
|
||||
);
|
||||
|
||||
let paste = "x".repeat(LARGE_PASTE_CHAR_THRESHOLD + 4);
|
||||
let placeholder_base = format!("[Pasted Content {} chars]", paste.chars().count());
|
||||
let placeholder_second = format!("{placeholder_base} #2");
|
||||
|
||||
composer.handle_paste(paste.clone());
|
||||
composer.handle_paste(paste.clone());
|
||||
assert_eq!(
|
||||
composer.textarea.text(),
|
||||
format!("{placeholder_base}{placeholder_second}")
|
||||
);
|
||||
assert_eq!(composer.pending_pastes.len(), 2);
|
||||
|
||||
composer.textarea.set_cursor(composer.textarea.text().len());
|
||||
composer.handle_key_event(KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE));
|
||||
|
||||
assert_eq!(composer.textarea.text(), placeholder_base);
|
||||
assert_eq!(composer.pending_pastes.len(), 1);
|
||||
assert_eq!(composer.pending_pastes[0].0, placeholder_base);
|
||||
assert_eq!(composer.pending_pastes[0].1, paste);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_paste_numbering_does_not_reuse_after_deletion() {
|
||||
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,
|
||||
);
|
||||
|
||||
let paste = "x".repeat(LARGE_PASTE_CHAR_THRESHOLD + 4);
|
||||
let base = format!("[Pasted Content {} chars]", paste.chars().count());
|
||||
let second = format!("{base} #2");
|
||||
let third = format!("{base} #3");
|
||||
|
||||
composer.handle_paste(paste.clone());
|
||||
composer.handle_paste(paste.clone());
|
||||
assert_eq!(composer.textarea.text(), format!("{base}{second}"));
|
||||
|
||||
composer.textarea.set_cursor(base.len());
|
||||
composer.handle_key_event(KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE));
|
||||
assert_eq!(composer.textarea.text(), second);
|
||||
assert_eq!(composer.pending_pastes.len(), 1);
|
||||
assert_eq!(composer.pending_pastes[0].0, second);
|
||||
|
||||
composer.textarea.set_cursor(composer.textarea.text().len());
|
||||
composer.handle_paste(paste);
|
||||
|
||||
assert_eq!(composer.textarea.text(), format!("{second}{third}"));
|
||||
assert_eq!(composer.pending_pastes.len(), 2);
|
||||
assert_eq!(composer.pending_pastes[0].0, second);
|
||||
assert_eq!(composer.pending_pastes[1].0, third);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_placeholder_deletion() {
|
||||
use crossterm::event::KeyCode;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue