pub const DEFAULT_BYTE_LIMIT: usize = 64 * 1024 * 1024;
const PASTE_START_MARKER: &[u8] = b"\x1b[200~";
const PASTE_END_MARKER: &[u8] = b"\x1b[201~";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PasteEvent {
NotPaste,
Started,
Chunk(String),
Completed(String),
}
#[derive(Debug)]
pub struct PasteBuffer {
buffer: String,
active: bool,
lookahead: Vec<u8>,
byte_limit: usize,
}
impl Default for PasteBuffer {
fn default() -> Self {
Self::new(DEFAULT_BYTE_LIMIT)
}
}
impl PasteBuffer {
#[must_use]
pub fn new(byte_limit: usize) -> Self {
Self {
buffer: String::new(),
active: false,
lookahead: Vec::new(),
byte_limit,
}
}
#[must_use]
pub fn is_active(&self) -> bool {
self.active
}
#[must_use]
pub fn buffered_len(&self) -> usize {
self.buffer.len()
}
pub fn feed(&mut self, data: &[u8]) -> PasteEvent {
if self.active {
self.feed_active_tail(data, false)
} else {
self.feed_idle(data)
}
}
fn feed_idle(&mut self, data: &[u8]) -> PasteEvent {
let mut combined: Vec<u8> = Vec::with_capacity(data.len());
let prev = std::mem::take(&mut self.lookahead);
combined.extend_from_slice(&prev);
combined.extend_from_slice(data);
if let Some(start_idx) = find_subsequence(&combined, PASTE_START_MARKER) {
self.active = true;
self.buffer.clear();
let after_start = start_idx + PASTE_START_MARKER.len();
let tail = &combined[after_start..];
return self.feed_active_tail(tail, true);
}
self.lookahead = partial_prefix(&combined, PASTE_START_MARKER);
PasteEvent::NotPaste
}
fn feed_active_tail(&mut self, data: &[u8], started_now: bool) -> PasteEvent {
let prev = std::mem::take(&mut self.lookahead);
let mut combined: Vec<u8> = Vec::with_capacity(data.len() + prev.len());
combined.extend_from_slice(&prev);
combined.extend_from_slice(data);
if let Some(end_idx) = find_subsequence(&combined, PASTE_END_MARKER) {
let content = &combined[..end_idx];
self.append_bytes(content);
let payload = std::mem::take(&mut self.buffer);
self.active = false;
self.lookahead.clear();
return PasteEvent::Completed(payload);
}
let new_lookahead = partial_prefix(&combined, PASTE_END_MARKER);
let content_end = combined.len() - new_lookahead.len();
let content = &combined[..content_end];
self.append_bytes(content);
if self.buffer.len() > self.byte_limit {
let payload = std::mem::take(&mut self.buffer);
self.active = false;
self.lookahead.clear();
return PasteEvent::Completed(payload);
}
self.lookahead = new_lookahead;
if started_now {
PasteEvent::Started
} else {
PasteEvent::Chunk(String::from_utf8_lossy(data).into_owned())
}
}
fn append_bytes(&mut self, bytes: &[u8]) {
self.buffer.push_str(&String::from_utf8_lossy(bytes));
}
}
fn partial_prefix(haystack: &[u8], needle: &[u8]) -> Vec<u8> {
let max = needle.len().saturating_sub(1);
let n = haystack.len().min(max);
for len in (1..=n).rev() {
let start = haystack.len() - len;
if haystack[start..].starts_with(&needle[..len]) {
return haystack[start..].to_vec();
}
}
Vec::new()
}
fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
if needle.is_empty() {
return None;
}
haystack.windows(needle.len()).position(|w| w == needle)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn start_marker_only_returns_started() {
let mut buf = PasteBuffer::new(1024);
assert_eq!(buf.feed(b"\x1b[200~"), PasteEvent::Started);
assert!(buf.is_active());
}
#[test]
fn chunks_emit_chunk_events() {
let mut buf = PasteBuffer::new(1024);
assert_eq!(buf.feed(b"\x1b[200~"), PasteEvent::Started);
assert_eq!(buf.feed(b"hello "), PasteEvent::Chunk("hello ".into()));
assert_eq!(buf.feed(b"world"), PasteEvent::Chunk("world".into()));
assert_eq!(
buf.feed(b"\x1b[201~"),
PasteEvent::Completed("hello world".into())
);
assert!(!buf.is_active());
}
#[test]
fn start_and_content_in_one_call_emits_started() {
let mut buf = PasteBuffer::new(1024);
assert_eq!(buf.feed(b"\x1b[200~abc"), PasteEvent::Started);
assert!(buf.is_active());
assert_eq!(buf.buffered_len(), 3);
assert_eq!(buf.feed(b"\x1b[201~"), PasteEvent::Completed("abc".into()));
}
#[test]
fn start_and_end_in_one_call_completes_immediately() {
let mut buf = PasteBuffer::new(1024);
let event = buf.feed(b"\x1b[200~\x1b[201~");
assert_eq!(event, PasteEvent::Completed(String::new()));
assert!(!buf.is_active());
}
#[test]
fn non_paste_input_returns_not_paste() {
let mut buf = PasteBuffer::new(1024);
assert_eq!(buf.feed(b"hello world"), PasteEvent::NotPaste);
assert_eq!(buf.feed(b"\x1b[A"), PasteEvent::NotPaste);
assert!(!buf.is_active());
}
#[test]
fn partial_start_marker_buffered() {
let mut buf = PasteBuffer::new(1024);
let first_half = &b"\x1b[20"[..];
assert_eq!(buf.feed(first_half), PasteEvent::NotPaste);
assert!(!buf.is_active());
let second_half = &b"0~payload\x1b[201~"[..];
assert_eq!(
buf.feed(second_half),
PasteEvent::Completed("payload".into())
);
assert!(!buf.is_active());
}
#[test]
fn partial_end_marker_buffered() {
let mut buf = PasteBuffer::new(1024);
assert_eq!(buf.feed(b"\x1b[200~hello"), PasteEvent::Started);
assert!(buf.is_active());
assert_eq!(buf.feed(b"\x1b[20"), PasteEvent::Chunk("\x1b[20".into()));
assert!(buf.is_active());
assert_eq!(
buf.feed(b"1~trailing"),
PasteEvent::Completed("hello".into())
);
}
#[test]
fn byte_limit_force_completes() {
let mut buf = PasteBuffer::new(16);
assert_eq!(buf.feed(b"\x1b[200~"), PasteEvent::Started);
let first = b"abcdefghijkl";
assert_eq!(
buf.feed(first),
PasteEvent::Chunk(String::from_utf8_lossy(first).into_owned())
);
let second = b"mnopqrstuvwxyz0123";
let event = buf.feed(second);
assert!(matches!(event, PasteEvent::Completed(_)));
assert!(!buf.is_active());
}
#[test]
fn default_uses_64_mib_cap() {
let buf = PasteBuffer::default();
assert_eq!(buf.byte_limit, DEFAULT_BYTE_LIMIT);
}
#[test]
fn partial_prefix_helper() {
assert_eq!(
partial_prefix(b"\x1b[20", PASTE_START_MARKER),
b"\x1b[20".to_vec()
);
let empty: Vec<u8> = Vec::new();
assert_eq!(partial_prefix(b"0~", PASTE_START_MARKER), empty);
assert_eq!(partial_prefix(b"~", PASTE_START_MARKER), empty);
assert_eq!(partial_prefix(b"hello", PASTE_START_MARKER), empty);
let empty: Vec<u8> = Vec::new();
assert_eq!(partial_prefix(b"\x1b[200~", PASTE_START_MARKER), empty);
}
}