#1562 : Prevent autopair from inserting stray closers during copy/paste (multiline) #1563
#1562 : Prevent autopair from inserting stray closers during copy/paste (multiline) #1563Abdelilah-AIT-HAMOU wants to merge 1 commit intojline:masterfrom
Conversation
…iline)
Changes
- console/src/main/java/org/jline/widget/AutopairWidgets.java
- autopairInsert: Skip autopairing entirely when the buffer is multiline (new isMultiline()).
- canPair: Block pairing at end-of-buffer/newline and when the next char is whitespace; also bail out in multiline buffers.
- New helper isMultiline(): returns true if the buffer contains ‘\n’.
gnodet
left a comment
There was a problem hiding this comment.
The PR attempts to fix autopair widgets inserting stray closing delimiters during copy/paste operations by detecting multiline buffers. While the problem is real, the approach is incorrect.
Problems with the Current Approach
1. Wrong Detection Mechanism
The PR uses isMultiline() to detect paste operations by checking for \n characters:
private boolean isMultiline() {
Buffer buf = buffer();
return buf.toString().indexOf('\n') >= 0;
}
This is fundamentally flawed because:
- ❌ Multiline buffer ≠ Paste operation
- ❌ Disables autopair for legitimate multiline editing (e.g., typing multi-line commands manually)
- ❌ Ignores JLine's existing paste detection infrastructure
2. Ignores Bracketed Paste Mode
JLine already has built-in paste detection via Bracketed Paste Mode:
- When paste starts, terminal sends
ESC[200~(BRACKETED_PASTE_BEGIN) - JLine's
beginPaste()widget setsregionActive = RegionType.PASTE - When paste ends, terminal sends
ESC[201~(BRACKETED_PASTE_END) - The paste region is automatically cleared after the next command
See:LineReaderImpl.javalines 6054-6060 and 730-732
3. Too Broad Impact
The current fix disables autopair whenever there's a newline in the buffer, which breaks:
- Multi-line command editing
- Multi-line string literals
- Any legitimate use case where users manually create multi-line input
The Correct Approach
Use RegionType.PASTE Detection
The autopair widgets should check if we're currently in a paste operation using the existing API:
public boolean autopairInsert() {
// Skip autopair logic during bracketed paste operations
if (reader.getRegionActive() == LineReader.RegionType.PASTE) {
callWidget(LineReader.SELF_INSERT);
return true;
}
// ... existing autopair logic
}
Apply the same check to autopairClose() and autopairDelete().
Why This is Better
- ✅ Precise detection - Only disables during actual paste operations
- ✅ Uses existing infrastructure - Leverages JLine's bracketed paste mode
- ✅ Preserves functionality - Autopair still works for manual multiline editing
- ✅ Clean and simple - Single condition using the proper API
- ✅ Automatic cleanup -
RegionType.PASTEis cleared automatically
Complete Fix
diff --git a/console/src/main/java/org/jline/widget/AutopairWidgets.java b/console/src/main/java/org/jline/widget/AutopairWidgets.java
index xxx..yyy 100644
--- a/console/src/main/java/org/jline/widget/AutopairWidgets.java
+++ b/console/src/main/java/org/jline/widget/AutopairWidgets.java
@@ -119,6 +119,11 @@ public class AutopairWidgets extends Widgets {
• Widgets
*/
public boolean autopairInsert() {
+ // Skip autopair logic during bracketed paste operations
+ if (reader.getRegionActive() == LineReader.RegionType.PASTE) {
+ callWidget(LineReader.SELF_INSERT);
+ return true;
+ }
+
if (pairs.containsKey(lastBinding())) {
if (canSkip(lastBinding())) {
callWidget(LineReader.FORWARD_CHAR);
@@ -138,6 +143,11 @@ public class AutopairWidgets extends Widgets {
}
public boolean autopairClose() {
+ // Skip autopair logic during bracketed paste operations
+ if (reader.getRegionActive() == LineReader.RegionType.PASTE) {
+ callWidget(LineReader.SELF_INSERT);
+ return true;
+ }
+
if (pairs.containsValue(lastBinding()) && currChar().equals(lastBinding())) {
callWidget(LineReader.FORWARD_CHAR);
} else {
@@ -147,6 +157,11 @@ public class AutopairWidgets extends Widgets {
}
public boolean autopairDelete() {
+ // Skip autopair logic during bracketed paste operations
+ if (reader.getRegionActive() == LineReader.RegionType.PASTE) {
+ callWidget(LineReader.BACKWARD_DELETE_CHAR);
+ return true;
+ }
+
if (pairs.containsKey(prevChar()) && pairs.get(prevChar()).equals(currChar()) && canDelete(prevChar())) {
callWidget(LineReader.DELETE_CHAR);
}Background: How Bracketed Paste Mode Works
Bracketed Paste Mode is a standard terminal feature that JLine already supports:
- Enable: JLine sends
ESC[?2004hwhen starting (LineReaderImpl.java:687) - Paste Start: Terminal sends
ESC[200~→ triggersBEGIN_PASTEwidget - Paste Content: All pasted text is read at once
- Paste End: Terminal sends
ESC[201~ - Disable: JLine sends
ESC[?2004lwhen finishing (LineReaderImpl.java:2668)
TheBEGIN_PASTEwidget (LineReaderImpl.java:6054-6060) already:
- Sets
regionActive = RegionType.PASTE - Reads all content until BRACKETED_PASTE_END
- Writes the pasted content to the buffer
This state is automatically cleared after the next command (LineReaderImpl.java:730-732).
Recommendation
Please update the PR to use reader.getRegionActive() == LineReader.RegionType.PASTE instead of the isMultiline() check. This will
properly fix the issue without breaking legitimate multiline editing scenarios.
This change prevents autopair from inserting matching closing delimiters during bracketed paste operations by checking the paste region state. ## Problem When pasting multiline input (especially with quotes/brackets), autopair inserts matching closing delimiters. Those auto-inserted closers end up being printed after execution (e.g., "'' } ] )"). ## Solution Use JLine's existing bracketed paste detection (RegionType.PASTE) to disable autopairing during paste operations. This is more precise than detecting multiline buffers and preserves autopair functionality for legitimate multiline editing. ## Changes - **autopairInsert**: Skip autopairing during bracketed paste - **autopairClose**: Skip autopairing during bracketed paste - **autopairDelete**: Skip autopairing during bracketed paste Fixes #1562 Based on PR #1563 by @Abdelilah-AIT-HAMOU
…te (multiline) This issue occurs in copy/paste scenario with multiline. When pasting multiline input (especially with quotes/brackets), autopair inserts matching closing delimiters across line breaks. Those auto-inserted closers end up on a separate line and are printed after execution (e.g., "' } ] )"). This change disables autopairing in multiline buffers and adds stricter EOL/whitespace guards, eliminating the stray output while preserving single-line behavior. Changes: - autopairInsert: Skip autopairing entirely when the buffer is multiline (new isMultiline()). - canPair: Block pairing at end-of-buffer/newline and when the next char is whitespace; also bail out in multiline buffers. - New helper isMultiline(): returns true if the buffer contains '\n'. Fixes #1562 Based on PR #1563 by @Abdelilah-AIT-HAMOU
|
Closing this for now, we need to actually reproduce the problem first. |
This issue occurs in copy/paste scenario with multiline . When pasting multiline input (especially with quotes/brackets), autopair inserts matching closing delimiters across line breaks. Those auto-inserted closers end up on a separate line and are printed after execution (e.g., “* ' } ] )”).
This change disables autopairing in multiline buffers and adds stricter EOL/whitespace guards, eliminating the stray output while preserving single-line behavior.
Changes
fixes #1562