Reviewed code and tests related to paste handling.
UnderstandAnalyzed terminal bracketed paste mode behavior.
UnderstandSummarized root cause of multiline paste bug.
UnderstandConsidered edge cases and brittleness concerns.
UnderstandAnalyze GitHub issue(s): https://github.com/badlogic/pi-mono/issues/767 read full comments
For each issue:
Read the issue in full, including all comments and linked issues/PRs.
For bugs:
For feature requests:
Do NOT implement unless explicitly asked. Analyze and propose only.
The user wants me to analyze GitHub issue #767. Let me fetch the issue details first.
>gh issue view 767 --repo badlogic/pi-mono --json title,body,comments,labels,stateThis is a bug report about multiline paste on Windows triggering prompt submission. The issue describes:
The user's test shows:
6f 6e 65 0d 74 77 6f (one\rtwo - no bracketed paste markers)1b 5b 32 30 30 7e 6f 6e 65 0d 74 77 6f 1b 5b 32 30 31 7e (has bracketed paste markers ESC[200~ ... ESC[201~)The owner (badlogic) couldn't reproduce this on the same setup.
Let me look at the input handling code in the TUI package to understand how multiline paste is handled and how bracketed paste mode works.
Let me look at the input handling code in the TUI package to understand how bracketed paste mode works:
>find /Users/badlogic/workspaces/pi-mono/packages/tui -name "*.ts" | xargs grep -l -i "paste\|bracket\|2004" 2>/dev/null>find /Users/badlogic/workspaces/pi-mono/packages/coding-agent -name "*.ts" | xargs grep -l -i "paste\|bracket\|2004" 2>/dev/nullLet me read the key files related to input handling, especially stdin-buffer.ts, keys.ts, and editor components.
Now I need to look at how the editor handles input and specifically how it handles paste events vs regular enter key presses. Let me look at the editor component.
Now let me look at the editor component to see how it handles paste events and enter key:
Now I understand the code flow. Let me also look at the terminal.ts file to see how bracketed paste mode is enabled:
Let me look at how the terminal handles bracketed paste mode:
Now I understand the issue. Here's what's happening:
\x1b[?2004h when starting.\x1b[200~ (start) and \x1b[201~ (end) markers.StdinBuffer class detects these markers and emits a paste event with the content.The user's diagnosis is correct:
In WSL (which supports bracketed paste): 1b 5b 32 30 30 7e 6f 6e 65 0d 74 77 6f 1b 5b 32 30 31 7e
1b 5b 32 30 30 7e = \x1b[200~ (bracketed paste start)6f 6e 65 = "one"0d = CR (carriage return)74 77 6f = "two"1b 5b 32 30 31 7e = \x1b[201~ (bracketed paste end)In Git Bash for Windows: 6f 6e 65 0d 74 77 6f
6f 6e 65 = "one"0d = CR (carriage return)74 77 6f = "two"So when "one\rtwo" is pasted without bracketed paste markers, the editor sees:
\r which matches the "enter" key (submit action)The matchesKey function in keys.ts shows that \r matches "enter":
case "enter":case "return": if (modifier === 0) { return ( data === "\r" || (!_kittyProtocolActive && data === "\n") || ... ); }Without bracketed paste mode, the carriage return in the pasted text gets interpreted as an actual key press instead of being treated as part of the paste content, causing the form to submit prematurely.
The owner couldn't reproduce this issue, suggesting their environment might have different terminal settings or a version of mintty/Git for Windows that handles this differently. The root problem is that Git for Windows bash (mintty) doesn't consistently support bracketed paste mode across all configurations.
A few potential solutions exist: implementing a heuristic that detects rapid character sequences within a short time window and treats them as paste events, or using timing-based detection where multiple characters including newlines arriving in a single stdin event or within ~50ms get classified as pasted content rather than individual keystrokes.
Another angle is checking the size of stdin data events—when a single event contains multiple characters with CR/LF, it's almost certainly a paste, since normal typing typically sends one character at a time. The StdinBuffer already has some timing logic for escape sequences that could potentially be adapted for paste detection. Looking at how the data flows through terminal.ts into stdinBuffer.process(), the original data event might contain multiple characters before being processed individually, which could be leveraged for detection.
The most practical approach would be to identify when multiple characters arrive in a single stdin data event alongside carriage returns or newlines—this pattern strongly indicates a paste even without bracketed paste mode support. This detection could happen in StdinBuffer.process() by checking the incoming data length and content before character-by-character processing begins.
The implementation needs careful handling to avoid false positives: distinguishing between a user typing a single newline (which arrives as just \r) versus actual multi-character pastes, and ensuring escape sequences containing \r or \n don't trigger the paste detection incorrectly.
Now let me look at the tests for paste handling:
Now I have a complete picture of the issue. Let me summarize:
Bug: Multiline paste on Windows (Git for Windows bash) triggers prompt submission instead of inserting the pasted content.
User Setup: Windows 11, Git for Windows bash in Windows Terminal, pi 0.47.0
Root Cause:
Git for Windows bash (mintty) doesn't support or enable bracketed paste mode (\x1b[?2004h) in some configurations. When bracketed paste is not supported:
\x1b[?2004h to stdout\x1b[200~ ... \x1b[201~ markers\r) charactersStdinBuffer splits the input into individual characters, and CR is emitted as a single \reditor.ts, the handleInput method matches \r as "enter" (submit action)Why Owner Can't Reproduce: The owner's setup might have different:
Some newer versions of Windows Terminal + Git Bash may support bracketed paste via ConPTY.
The most robust approach is to detect "implicit paste" in StdinBuffer when:
data event contains multiple characters\r) or LF (\n)When these conditions are met, emit a paste event instead of splitting into individual characters.
Implementation changes in stdin-buffer.ts:
In the process() method, I need to add detection logic before character-by-character processing. When multiple characters arrive in a single data event containing line breaks but lacking bracketed paste markers, treat it as an implicit paste and emit the full content as a paste event rather than processing it character by character. This prevents CR characters from being interpreted as individual enter commands.
The approach needs to account for edge cases: escape sequences that legitimately contain newlines are rare, and single newline characters from user input should continue flowing through normal processing. The real distinction is that pasted content typically arrives batched in one data event, while typed input comes character by character. So the detection should trigger when a data event has multiple characters with line breaks and isn't a complete escape sequence, then emit that as a paste event.
Now I have a complete picture. Let me summarize the analysis:
When using pi on Git for Windows bash in Windows Terminal, pasting multiline text triggers prompt submission instead of inserting all lines.
Git for Windows bash (mintty) doesn't support or properly enable bracketed paste mode in some configurations.
How bracketed paste works:
\x1b[?2004h to stdout (in terminal.ts:start())\x1b[200~ (start) + pasted content + \x1b[201~ (end)StdinBuffer detects these markers and emits a paste eventEditor handles paste events specially, inserting all content without treating newlines as submitWhat fails without bracketed paste support:
StdinBuffer splits input into individual sequences\r (carriage return) is emitted as a single characterhandleInput() matches \r as "enter" (submit action):
// In keys.ts matchesKey()case "enter": if (modifier === 0) { return data === "\r" || ... }The user's test confirms this:
6f 6e 65 0d 74 77 6f ("one\rtwo" with no markers)1b 5b 32 30 30 7e 6f 6e 65 0d 74 77 6f 1b 5b 32 30 31 7e (same content wrapped in bracketed paste markers)Likely differences in:
Newer versions may properly support bracketed paste via ConPTY.
Detect "implicit paste" in StdinBuffer.process() when:
\r or \nChanges to packages/tui/src/stdin-buffer.ts:
public process(data: string | Buffer): void { // ... existing timeout clearing and string conversion ...
// After converting to string, before existing logic: // Detect implicit paste: multi-character input with newlines but no bracketed paste markers // This handles terminals that don't support bracketed paste mode (e.g., some Git Bash configurations) if (!this.pasteMode && str.length > 1 && (str.includes('\r') || str.includes('\n')) && !str.includes(BRACKETED_PASTE_START) && !str.includes(BRACKETED_PASTE_END)) { // Emit as paste event to avoid treating newlines as submit this.emit("paste", str); return; }
// ... rest of existing logic ...}Edge cases to consider:
\r from Enter key: length === 1, so won't trigger\r: rare, and typically don't arrive with other content in the same data event\r or \n in the payloadpackages/tui/src/stdin-buffer.ts (primary fix)packages/tui/test/stdin-buffer.test.ts (add test case)packages/tui/CHANGELOG.md (document fix)The proposed fix is the most robust and least intrusive.
isn't that brittle? think of edge cases
The user is right to push back. Let me think through the edge cases more carefully.
Edge cases with the proposed "implicit paste" detection:
SSH/slow connections: Keystrokes can batch together in a single data event. If user types "hello\r" quickly over SSH, it might arrive as a single chunk and be misinterpreted as a paste.
Escape sequences with modifiers: Some terminal sequences include multiple bytes that could theoretically batch with a newline.
Terminal echo/response sequences: Terminals sometimes send responses that contain multiple characters.
Rapid typing followed by Enter: If user types fast and presses Enter, "abc\r" could arrive together.
Applications that send data programmatically: If something writes to stdin (like a test or automation), multi-character input with newlines is normal.
The real problem: We can't reliably distinguish between:
If they arrive in the same data event, they look identical.
The user is right - this is brittle.
Better approaches:
Document the limitation: This is a terminal limitation. Git Bash's mintty historically has spotty bracketed paste support. The correct fix is for the terminal to support bracketed paste, not for us to guess.
Detect bracketed paste support: We could query terminal capabilities, but even then we can't fix the terminal not supporting it.
Alternative submit key: Some tools use Ctrl+Enter or Ctrl+D to submit, with Enter always inserting newlines. But this changes the UX for everyone.
Configuration option: Add a flag like --no-enter-submit or similar that makes Enter always insert newlines, requiring a different key to submit.
Accept the limitation: The owner can't even reproduce it. This might be a very specific terminal configuration issue.
The owner tested the same setup and couldn't reproduce the issue, which points to version differences or specific terminal settings on the user's end. Since it's not happening consistently across the same claimed environment, I should ask for more details before implementing something potentially fragile. I need the user's exact Git for Windows and Windows Terminal versions, their terminal profile settings, and any custom configurations that might affect bracketed paste behavior.
If a fix is necessary, I'd keep it conservative—only treating input as a paste when it's substantial (more than 10 characters) and contains newlines, which minimizes false positives from rapid typing. Though this approach might miss smaller multi-line pastes. Another option is documenting Shift+Enter or Alt+Enter as the workaround for terminals without bracketed paste support, keeping the current Enter-to-submit behavior.
Looking at the existing editor code, there's already logic checking for newline conditions like data.charCodeAt(0) === 10 && data.length > 1, which suggests the foundation for detecting multi-line input is already there.
Since reproducing the issue has been difficult and heuristic detection is inherently fragile, the most practical path forward is to first get more context from the user about why they can't reproduce it, then document the Shift+Enter workaround as the immediate solution, and only pursue a conservative heuristic implementation if absolutely necessary.
You're right, let me think through the edge cases:
False positives (would incorrectly treat as paste):
False negatives (would miss actual pastes):
The fundamental problem: We cannot reliably distinguish between:
"one\rtwo""one" then pressing EnterIf they arrive in the same data event, they're byte-identical.
Better assessment:
The owner cannot reproduce on the same claimed setup (Win 11, Git Bash, Windows Terminal). This suggests:
Recommended approach:
Request more information on the issue:
git --version)echo $TERM and echo $TERM_PROGRAM.bashrc or .minttyrc configurationDocument the workaround: Users can use Shift+Enter or Alt+Enter to insert newlines manually, which works regardless of bracketed paste support.
Document as known limitation: Some terminal configurations don't support bracketed paste. Recommend WSL or native Windows Terminal with PowerShell as alternatives.
Implementing a heuristic that could break normal Enter-to-submit behavior for everyone seems worse than the original issue affecting a subset of users with unusual terminal configurations.
case "enter":case "return": if (modifier === 0) { return ( data === "\r" || (!_kittyProtocolActive && data === "\n") || ... ); }// In keys.ts matchesKey()case "enter": if (modifier === 0) { return data === "\r" || ... }public process(data: string | Buffer): void { // ... existing timeout clearing and string conversion ...
// After converting to string, before existing logic: // Detect implicit paste: multi-character input with newlines but no bracketed paste markers // This handles terminals that don't support bracketed paste mode (e.g., some Git Bash configurations) if (!this.pasteMode && str.length > 1 && (str.includes('\r') || str.includes('\n')) && !str.includes(BRACKETED_PASTE_START) && !str.includes(BRACKETED_PASTE_END)) { // Emit as paste event to avoid treating newlines as submit this.emit("paste", str); return; }
// ... rest of existing logic ...}