bug#50236: 27.2; electric-pair-mode is inconvenient in comint

João Távora <[email protected]>
Newsgroups gmane.emacs.bugs
Message-ID <[email protected]>
Andrew Hyatt <[email protected]> writes:

>  Maybe. But it's not now how I see responsible, maintainable
>  design. In my opinion there are even simpler ways.
>
> In the effort to get a fix out, I'd agree that we should make the
> simple change now (the one I have done), and we can later always
> substitute it with something more customizable. I don't think the
> customization is necessary for this bugfix.

Yes, indeed in the interest of getting this bug fix moving and hopefully
into emacs-31, lets:

1. Push the attached patch to emacs-31 (this is a bug fix after all).
Give it some testing.  I'll be testing it myself.
If there are no objections, I'll push in a few days time.

BTW I've measured the performance informally:

emacs -Q -nw -f electric-pair-mode src/xdisp.c
M-g M-g 500 RET ; goto line 500
C-m ; hold C-m for 10 seconds

Did this experiment three times withou and without the patch

with patch:    line reached: 733, 736, 736 %85 max cpu
without patch: line reached: 732, 738, 737 %83.2 max cpu

Even though these measurements are not concerning at all, I still opted
to set the the controlling variable to nil everwhere but in comint-mode,
where it is set to 1000.

Also I've credited the entire patch to Andrew since using fields is his
original idea and added myself as "Co-authored-by:" If there are
objections, let me know please before I push it.

2. Let's also keep this bug open (or open a new one) so that we can
continue discussing:

2.1 the merits of adding 'field' definitions to org-mode and markdown-mode
    and other modes

2.2 and/or the merits up upgrading the new controlling variable to
    something more generic that encompasses other parenthesis-matching
    modes

if this last point goes forward, as Augusto would like, it can be made
easily backward compatible to the current scheme, by using the existing
electric-pair-field-search-size to be a function that returns two
numbers.  Currently its semantics aref

nil for using the whole of the accessible buffer
a number to search that far for fields

If we ever do this upgrade, we can just rename the variable.

João
0001-Make-electric-pair-mode-respect-field-boundaries-bug.patch (text/x-patch, 5.7 KB)
From ca8bb2aaff67e3663628eb814800b81b0c1160b4 Mon Sep 17 00:00:00 2001
From: Andrew Hyatt <[email protected]>
Date: Sun, 16 Aug 2026 12:31:55 +0100
Subject: [PATCH] Make electric-pair-mode respect field boundaries (bug#50236)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Among other potential benefits, this makes e-p-m work correctly in all
comint-based modes.

* lisp/elec-pair.el (electric-pair-field-search-size): New variable.
(electric-pair-post-self-insert-function): Tweak.
(electric--pair-psif-1)

* etc/NEWS: Call out change.

* lisp/comint.el (electric-pair-field-search-size): forward-declare.
(comint-mode): Set electric-pair-field-search-size to 1000.

* test/lisp/electric-tests.el (preserve-balance)
(preserve-balance-not-across-fields): New tests.

Co-authored-by: João Távora <[email protected]>
---
 etc/NEWS                    |  7 +++++++
 lisp/comint.el              |  5 ++++-
 lisp/elec-pair.el           | 24 +++++++++++++++++++++++-
 test/lisp/electric-tests.el | 26 +++++++++++++++++++++++++-
 4 files changed, 59 insertions(+), 3 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 5f5f59d2a9f..fe662e4421d 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -972,6 +972,13 @@ To use this, add a list to both electric pair user options: '("/*" . "*/")'.
 You can also specify that an extra space should be inserted after the
 first string, like this: '("/*" " */" t)'.
 
+*** Electric pair mode can restrict operation to nearby fields
+This is particularly useful in comint-based modes (like Eshell) and
+other situations where process output may contain unpaired delimiters,
+while keeping delimiters you insert balanced. The
+''electric-pair-field-search-size'' variable controls the search
+distance for nearby fields.
+
 ---
 ** New user option 'electric-indent-actions'.
 This user option specifies a list of actions to reindent.  The possible
diff --git a/lisp/comint.el b/lisp/comint.el
index e5418f4ee67..3018717c937 100644
--- a/lisp/comint.el
+++ b/lisp/comint.el
@@ -702,6 +702,8 @@ comint-stored-incomplete-input
 
 (put 'comint-mode 'mode-class 'special)
 
+(defvar electric-pair-field-search-size)
+
 (define-derived-mode comint-mode fundamental-mode "Comint"
   "Major mode for interacting with an inferior interpreter.
 Interpreter name is same as buffer name, sans the asterisks.
@@ -792,7 +794,8 @@ comint-mode
   (add-hook 'isearch-mode-hook #'comint-history-isearch-setup nil t)
   (add-hook 'completion-at-point-functions #'comint-completion-at-point nil t)
   ;; This behavior is not useful in comint buffers, and is annoying
-  (setq-local next-line-add-newlines nil))
+  (setq-local next-line-add-newlines nil)
+  (setq electric-pair-field-search-size 1000))
 
 (defun comint-check-proc (buffer)
   "Return non-nil if there is a living process associated w/buffer BUFFER.
diff --git a/lisp/elec-pair.el b/lisp/elec-pair.el
index 54fbc960b0f..d7ae555e408 100644
--- a/lisp/elec-pair.el
+++ b/lisp/elec-pair.el
@@ -596,10 +596,32 @@ electric-pair-default-inhibit
       (electric-pair-inhibit-if-helps-balance char)
     (electric-pair-conservative-inhibit char)))
 
+(defvar electric-pair-field-search-size nil
+  "How far to look for nearby fields to restrict `electric-pair-mode'.
+
+If nil, do not look for nearby fields and use the accessible portion of
+the buffer.  If a integer number of characters, look at most that far
+backward and forward for a field boundary.  If one isn't found, use the
+accessible portion of the buffer in that direction.")
+
 (defun electric-pair-post-self-insert-function ()
   "Do main work for `electric-pair-mode'.
 This function is added to `post-self-insert-hook' when
-`electric-pair-mode' is enabled.
+`electric-pair-mode' is enabled."
+  ;; First, figure out whether to restrit the buffer (bug#50236)
+  (if (or (null electric-pair-field-search-size)
+          (use-region-p))
+      (electric--pair-psif-1)
+    (let* ((min (- (point) electric-pair-field-search-size))
+           (max (+ (point) electric-pair-field-search-size))
+           (beg (field-beginning nil nil (max (point-min) min)))
+           (end (field-end nil nil (min (point-max) max))))
+      (with-restriction
+          (if (= min beg) (point-min) beg) (if (= max end) (point-max) end)
+        (electric--pair-psif-1)))))
+
+(defun electric--pair-psif-1 ()
+  "Do main work for `electric-pair-post-self-insert-function.'
 
 If the newly inserted character C has delimiter syntax, this
 function may decide to insert additional paired delimiters, or
diff --git a/test/lisp/electric-tests.el b/test/lisp/electric-tests.el
index 9209e1739fd..d678279c8cc 100644
--- a/test/lisp/electric-tests.el
+++ b/test/lisp/electric-tests.el
@@ -703,7 +703,31 @@ autowrapping-multi-2
                 (goto-char (point-max))
                 (skip-chars-backward "\"")
                 (mark-sexp -1)))
-
+
+(define-electric-pair-test preserve-balance
+ "\"\n\n" "---\""
+ :expected-string "\"\n\n\""
+ :expected-point 5
+ :modes '(fundamental-mode)
+ :test-in-comments nil
+ :test-in-strings nil
+ :bindings '((electric-pair-preserve-balance . t))
+ :fixture-fn (lambda ()
+               (electric-pair-mode 1)))
+
+(define-electric-pair-test preserve-balance-not-across-fields
+ "\"\n\n" "---\""
+ :expected-string "\"\n\n\"\""
+ :expected-point 5
+ :modes '(fundamental-mode)
+ :test-in-comments nil
+ :test-in-strings nil
+ :bindings '((electric-pair-preserve-balance . t)
+             (electric-pair-field-search-size . 100))
+ :fixture-fn (lambda ()
+               (electric-pair-mode 1)
+               (add-text-properties (point-min) (+ (point-min) 2)
+                                    '(field t))))
 
 ;;; Electric quotes
 (define-electric-pair-test electric-quote-string
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.