pub const LARGE_PASTE_CHAR_THRESHOLD: usize = 1000;
pub const MAX_PASTE_ATTACHMENT_BYTES: usize = 512 * 1024;
pub fn normalize_pasted_text(pasted: &str) -> String {
pasted.replace("\r\n", "\n").replace('\r', "\n")
}
pub fn is_large_paste(text: &str) -> bool {
text.chars().count() > LARGE_PASTE_CHAR_THRESHOLD
}
pub fn next_large_paste_placeholder(
char_count: usize,
pending_pastes: &[(String, String)],
) -> String {
let base = format!("[Pasted Content {char_count} chars]");
let prefix = format!("{base} #");
let mut max_suffix = 0usize;
for (placeholder, _) in pending_pastes {
if placeholder == &base {
max_suffix = max_suffix.max(1);
continue;
}
if let Some(suffix) = placeholder.strip_prefix(&prefix)
&& let Ok(value) = suffix.parse::<usize>()
{
max_suffix = max_suffix.max(value);
}
}
if max_suffix == 0 {
base
} else {
format!("{base} #{}", max_suffix + 1)
}
}
pub fn prune_pending_pastes(text: &str, pending_pastes: &mut Vec<(String, String)>) {
pending_pastes.retain(|(placeholder, _)| text.contains(placeholder));
}
pub fn expand_pending_pastes(text: &str, pending_pastes: &[(String, String)]) -> String {
let mut expanded = text.to_string();
for (placeholder, payload) in pending_pastes {
expanded = expanded.replace(placeholder, payload);
}
expanded
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize_pasted_text_unifies_line_endings() {
assert_eq!(normalize_pasted_text("a\r\nb\rc"), "a\nb\nc");
}
#[test]
fn is_large_paste_uses_char_count() {
let exact = "x".repeat(LARGE_PASTE_CHAR_THRESHOLD);
assert!(!is_large_paste(&exact));
assert!(is_large_paste(&format!("{exact}x")));
}
#[test]
fn next_large_paste_placeholder_adds_suffix_for_duplicates() {
let pending = vec![
(
"[Pasted Content 1200 chars]".to_string(),
"first".to_string(),
),
(
"[Pasted Content 1200 chars] #2".to_string(),
"second".to_string(),
),
];
assert_eq!(
next_large_paste_placeholder(1200, &pending),
"[Pasted Content 1200 chars] #3"
);
}
#[test]
fn expand_pending_pastes_replaces_placeholders_in_order() {
let first = "a".repeat(LARGE_PASTE_CHAR_THRESHOLD + 1);
let second = "b".repeat(LARGE_PASTE_CHAR_THRESHOLD + 2);
let first_ph = next_large_paste_placeholder(first.chars().count(), &[]);
let pending = vec![
(first_ph.clone(), first.clone()),
(
next_large_paste_placeholder(second.chars().count(), &[(first_ph, first.clone())]),
second.clone(),
),
];
let composed = format!("before {} middle {} after", pending[0].0, pending[1].0);
let expanded = expand_pending_pastes(&composed, &pending);
assert_eq!(expanded, format!("before {first} middle {second} after"));
}
#[test]
fn prune_pending_pastes_drops_removed_placeholders() {
let placeholder = "[Pasted Content 1500 chars]";
let mut pending = vec![(placeholder.to_string(), "payload".to_string())];
prune_pending_pastes("typed only", &mut pending);
assert!(pending.is_empty());
}
}