docs(comments): trim paste-fix commentary to the crucial constraints
This commit is contained in:
@@ -148,7 +148,6 @@ mod linux {
|
|||||||
}
|
}
|
||||||
let err = std::io::Error::last_os_error();
|
let err = std::io::Error::last_os_error();
|
||||||
match err.kind() {
|
match err.kind() {
|
||||||
// The fd is non-blocking: the queue is empty.
|
|
||||||
std::io::ErrorKind::WouldBlock => break,
|
std::io::ErrorKind::WouldBlock => break,
|
||||||
std::io::ErrorKind::Interrupted => continue,
|
std::io::ErrorKind::Interrupted => continue,
|
||||||
_ => return Err(err),
|
_ => return Err(err),
|
||||||
|
|||||||
@@ -2333,11 +2333,7 @@ async fn drain_and_process(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// On terminals without bracketed paste, try to capture more events
|
// On terminals without bracketed paste, try to capture more events
|
||||||
// that may still be in transit from the input reader thread. A batch
|
// that may still be in transit from the input reader thread.
|
||||||
// that already holds a key burst skips the short detection window:
|
|
||||||
// its 2 ms budget is below one Windows scheduler quantum, so a
|
|
||||||
// mid-paste gap would end collection before it starts and the paste
|
|
||||||
// tail would arrive as a separate fragment.
|
|
||||||
if should_extend_for_paste(&raw_events)
|
if should_extend_for_paste(&raw_events)
|
||||||
&& (is_paste_burst(&raw_events) || detect_paste(&mut raw_events, input_rx).await)
|
&& (is_paste_burst(&raw_events) || detect_paste(&mut raw_events, input_rx).await)
|
||||||
{
|
{
|
||||||
@@ -2542,18 +2538,13 @@ async fn drain_and_process(
|
|||||||
const PASTE_DETECT_TIMEOUT: Duration = Duration::from_millis(2);
|
const PASTE_DETECT_TIMEOUT: Duration = Duration::from_millis(2);
|
||||||
|
|
||||||
/// Timeout for subsequent rounds once paste has been detected. Must exceed
|
/// Timeout for subsequent rounds once paste has been detected. Must exceed
|
||||||
/// one Windows scheduler quantum (~15.6 ms): ConPTY delivers a paste as a
|
/// one Windows scheduler quantum (~15.6 ms), or a routine mid-burst gap
|
||||||
/// per-character key burst, and a mid-burst gap of one quantum is routine.
|
/// splits the paste and repaste-to-expand duplicates instead of expanding.
|
||||||
/// A smaller window ends collection mid-paste; the tail then arrives as a
|
|
||||||
/// second synthetic paste, whose content can never match the paste chip, so
|
|
||||||
/// repaste-to-expand inserts a duplicate instead of expanding.
|
|
||||||
const PASTE_CONTINUE_TIMEOUT: Duration = Duration::from_millis(25);
|
const PASTE_CONTINUE_TIMEOUT: Duration = Duration::from_millis(25);
|
||||||
|
|
||||||
/// Safety cap on events accumulated in one extension pass. On the
|
/// Safety cap on events accumulated in one extension pass. Pastes arrive
|
||||||
/// no-bracketed-paste path a paste is one key event per character, so the
|
/// one key event per character here, so the cap must exceed any real paste;
|
||||||
/// cap must exceed any real paste (~200 KB of text); hitting it splits the
|
/// hitting it splits the paste like a timeout miss would.
|
||||||
/// paste with the same duplicate-on-repaste effect as a timeout split. The
|
|
||||||
/// idle timeout above, not this cap, is the normal terminator.
|
|
||||||
const PASTE_EXTEND_MAX_EVENTS: usize = 200_000;
|
const PASTE_EXTEND_MAX_EVENTS: usize = 200_000;
|
||||||
|
|
||||||
/// Returns `true` when the batch contains pasteable key events but no
|
/// Returns `true` when the batch contains pasteable key events but no
|
||||||
@@ -2564,12 +2555,11 @@ fn should_extend_for_paste(events: &[Event]) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Minimum pasteable key events already in one batch to classify it as a
|
/// Minimum pasteable key events already in one batch to classify it as a
|
||||||
/// paste burst in progress. Human typing and key auto-repeat deliver one
|
/// paste burst; typing and auto-repeat deliver only a couple per batch.
|
||||||
/// or two events per batch; only a paste chunk lands more at once.
|
|
||||||
const PASTE_BURST_BATCH_EVENTS: usize = 8;
|
const PASTE_BURST_BATCH_EVENTS: usize = 8;
|
||||||
|
|
||||||
/// True when the batch alone proves a paste is in progress, without
|
/// True when the batch alone proves a paste is in progress, so collection
|
||||||
/// waiting on [`PASTE_DETECT_TIMEOUT`].
|
/// can start without the [`PASTE_DETECT_TIMEOUT`] round-trip.
|
||||||
fn is_paste_burst(events: &[Event]) -> bool {
|
fn is_paste_burst(events: &[Event]) -> bool {
|
||||||
events.iter().filter(|e| is_pasteable_key_event(e)).count() >= PASTE_BURST_BATCH_EVENTS
|
events.iter().filter(|e| is_pasteable_key_event(e)).count() >= PASTE_BURST_BATCH_EVENTS
|
||||||
}
|
}
|
||||||
@@ -3188,11 +3178,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn typing_and_release_storms_are_not_a_paste_burst() {
|
fn typing_and_release_storms_are_not_a_paste_burst() {
|
||||||
// A short typed run stays below the burst threshold.
|
|
||||||
let typed = vec![press(KeyCode::Char('h')), press(KeyCode::Char('i'))];
|
let typed = vec![press(KeyCode::Char('h')), press(KeyCode::Char('i'))];
|
||||||
assert!(!is_paste_burst(&typed));
|
assert!(!is_paste_burst(&typed));
|
||||||
|
|
||||||
// Release events carry no content and must not count toward it.
|
|
||||||
let releases: Vec<Event> = "releases only!"
|
let releases: Vec<Event> = "releases only!"
|
||||||
.chars()
|
.chars()
|
||||||
.map(|c| release(KeyCode::Char(c)))
|
.map(|c| release(KeyCode::Char(c)))
|
||||||
@@ -3200,10 +3188,8 @@ mod tests {
|
|||||||
assert!(!is_paste_burst(&releases));
|
assert!(!is_paste_burst(&releases));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A key-burst paste whose tail trickles in with sub-window gaps must
|
/// A burst tail trickling in with one-quantum gaps must reassemble into
|
||||||
/// reassemble into ONE synthetic paste. A split here is what made
|
/// ONE paste — a split is what duplicated chips on Windows.
|
||||||
/// repaste-to-expand insert a duplicate chip on Windows: the fragment
|
|
||||||
/// could never byte-equal the chip content.
|
|
||||||
#[tokio::test(start_paused = true)]
|
#[tokio::test(start_paused = true)]
|
||||||
async fn burst_tail_with_scheduler_gaps_reassembles_into_one_paste() {
|
async fn burst_tail_with_scheduler_gaps_reassembles_into_one_paste() {
|
||||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
@@ -3213,8 +3199,6 @@ mod tests {
|
|||||||
let mut batch: Vec<Event> = head.chars().map(char_or_enter).collect();
|
let mut batch: Vec<Event> = head.chars().map(char_or_enter).collect();
|
||||||
assert!(is_paste_burst(&batch), "head chunk must classify as burst");
|
assert!(is_paste_burst(&batch), "head chunk must classify as burst");
|
||||||
|
|
||||||
// Trickle the tail with 15 ms gaps — one Windows scheduler quantum,
|
|
||||||
// larger than the old 10 ms window, smaller than the current one.
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
for c in tail.chars() {
|
for c in tail.chars() {
|
||||||
tokio::time::sleep(Duration::from_millis(15)).await;
|
tokio::time::sleep(Duration::from_millis(15)).await;
|
||||||
|
|||||||
Reference in New Issue
Block a user