Skip to content

#1562 : Prevent autopair from inserting stray closers during copy/paste (multiline) #1563

Closed
Abdelilah-AIT-HAMOU wants to merge 1 commit intojline:masterfrom
Abdelilah-AIT-HAMOU:autopairMultilineFix
Closed

#1562 : Prevent autopair from inserting stray closers during copy/paste (multiline) #1563
Abdelilah-AIT-HAMOU wants to merge 1 commit intojline:masterfrom
Abdelilah-AIT-HAMOU:autopairMultilineFix

Conversation

@Abdelilah-AIT-HAMOU
Copy link
Copy Markdown

@Abdelilah-AIT-HAMOU Abdelilah-AIT-HAMOU commented Jan 21, 2026

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

  • 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’.

fixes #1562

…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’.
Copy link
Copy Markdown
Member

@gnodet gnodet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 sets regionActive = RegionType.PASTE
  • When paste ends, terminal sends ESC[201~ (BRACKETED_PASTE_END)
  • The paste region is automatically cleared after the next command
    See: LineReaderImpl.java lines 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

  1. Precise detection - Only disables during actual paste operations
  2. Uses existing infrastructure - Leverages JLine's bracketed paste mode
  3. Preserves functionality - Autopair still works for manual multiline editing
  4. Clean and simple - Single condition using the proper API
  5. Automatic cleanup - RegionType.PASTE is 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:

  1. Enable: JLine sends ESC[?2004h when starting (LineReaderImpl.java:687)
  2. Paste Start: Terminal sends ESC[200~ → triggers BEGIN_PASTE widget
  3. Paste Content: All pasted text is read at once
  4. Paste End: Terminal sends ESC[201~
  5. Disable: JLine sends ESC[?2004l when finishing (LineReaderImpl.java:2668)
    The BEGIN_PASTE widget (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.

gnodet added a commit that referenced this pull request Jan 29, 2026
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
gnodet added a commit that referenced this pull request Jan 29, 2026
…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
@gnodet gnodet marked this pull request as draft February 11, 2026 07:23
@gnodet
Copy link
Copy Markdown
Member

gnodet commented Feb 11, 2026

Closing this for now, we need to actually reproduce the problem first.

@gnodet gnodet closed this Feb 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

autopair widget : inserting stray closers during copy/paste (multiline)

2 participants