Subject: bug#78053: [PATCH] Support strings for electric-pairs.

From: Elijah Gabe Pérez <[email-masked]>
Date: Sun, 25 May 2025 19:16:56 -0600

Stefan Monnier via "Bug reports for GNU Emacs, the Swiss army knife of
text editors" <[email-masked]> writes:

>> @@ -41,7 +41,9 @@ electric-pair-pairs
>>  See also the variable `electric-pair-text-pairs'."
>>    :version "24.1"
>>    :group 'electricity
>> -  :type '(repeat (cons character character)))
>> +  :type '(repeat
>> +          (choice (cons character character)
>> +                  (cons string string))))
>
> We want to do the same for `electric-pair-text-pairs`, no?
>

Yes, I forgot it.

>> @@ -258,7 +260,18 @@ electric-pair-syntax-info
>>       (direct (if (eq (car direct) (cdr direct))
>>                   (list ?\" command-event t string-or-comment)
>>                 (list ?\( (cdr direct) t string-or-comment)))
>> -     (reverse (list ?\) (car reverse) t string-or-comment)))))
>> +     (reverse (list ?\) (car reverse) t string-or-comment))
>> +     ;; The if-let here is for avoid unnecessary looking-back computation
>> +     ((if-let* ((str-pair
>> +		 (cl-loop for pairs in fallback
>> +			  do
>> +			  (if (and
>> +			       (stringp (car pairs))
>> +			       (looking-back
>> +				(regexp-quote (car pairs))
>> +				(- (point) (length (car pairs)))))
>> +			      (cl-return (cdr pairs))))))
>> +	  (list 'str str-pair nil nil))))))
>
> BTW, here you could use `compare-buffer-substrings`.

Thanks, Can you explain how to use it in this case?  AFAIK
`compare-buffer-substrings' needs 2 strings in the buffer for the
comparison.

>>  (defun electric-pair--insert (char times)
>>    (let ((last-command-event char)
>> @@ -591,6 +604,11 @@ electric-pair-post-self-insert-function
>>                                pos))
>>           (forward-char num))
>>          ;; Insert matching pair.
>> +        ;; String pairs
>> +        ((and (eq syntax 'str) (not overwrite-mode))
>> +         (insert " ")
>> +         (save-excursion (insert " " pair)))
>> +        ;; Char pairs
>>          ((and (memq syntax '(?\( ?\" ?\$))
>>                (not overwrite-mode)
>>                (or unconditional
>
> I can imagine some multi-char delimiters which don't call for spaces
> inside of them (e.g. LaTeX's ``...''), so maybe the entries in
> `electric-pair-pairs` should have some extra info about it.
> I suggest we replace the (STRING . STRING) format with
> (STRING STRING ...) so we can put extra info in the `...`.

Are you sure it's good idea to replace the cons cell form with a list?
I've added the list form, but it coexists with the cons cell to avoid
any future bug.

>From cf6971e3061926bd12705b40b43f59d290dbdd5b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?El=C3=ADas=20Gabriel=20P=C3=A9rez?= <[email-masked]>
Date: Thu, 24 Apr 2025 17:34:58 -0600
Subject: [PATCH] Support strings for electric-pairs.  (bug#78053)

This add support for string pairs in electric-pairs-mode.  For
example, typing `/*' in c-mode and derived it auto insert ` */',
or in sh-mode `case ' inserts `esac'.

* lisp/elec-pair.el (electric-pair-pairs): Add string :type
in defcustom.
(electric-pair-syntax-info)
(electric-pair-post-self-insert-function): Add support for
strings.
* etc/NEWS: Announce changes.
---
 etc/NEWS          |  4 ++++
 lisp/elec-pair.el | 37 ++++++++++++++++++++++++++++++++++---
 2 files changed, 38 insertions(+), 3 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index ade5abc43fc..bd514b110d8 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -527,6 +527,10 @@ You can now insert or wrap text with multiple sets of parentheses and
 other matching delimiters at once with Electric Pair mode, by providing
 a prefix argument when inserting one of the delimiters.
 
+---
+** Electric pair mode now supports multi-character paired delimiters.
+This is done by supporting strings in electric-pair-pairs.
+
 +++
 ** You can now use 'M-~' during 'C-x s' ('save-some-buffers').
 Typing 'M-~' while saving some buffers means not to save the buffer and
diff --git a/lisp/elec-pair.el b/lisp/elec-pair.el
index aa2577300fd..28459037699 100644
--- a/lisp/elec-pair.el
+++ b/lisp/elec-pair.el
@@ -41,7 +41,10 @@ electric-pair-pairs
 See also the variable `electric-pair-text-pairs'."
   :version "24.1"
   :group 'electricity
-  :type '(repeat (cons character character)))
+  :type '(repeat
+          (choice (cons :tag "Characters" character character)
+                  (cons :tag "Strings" string string)
+                  (list :tag "" string string boolean))))
 
 (defcustom electric-pair-text-pairs
   `((?\" . ?\")
@@ -54,7 +57,10 @@ electric-pair-text-pairs
 defined in `electric-pair-text-syntax-table'."
   :version "24.4"
   :group 'electricity
-  :type '(repeat (cons character character)))
+  :type '(repeat
+          (choice (cons :tag "Characters" character character)
+                  (cons :tag "Strings" string string)
+                  (list :tag "" string string boolean))))
 
 (defcustom electric-pair-skip-self #'electric-pair-default-skip-self
   "If non-nil, skip char instead of inserting a second closing paren.
@@ -252,6 +258,24 @@ electric-pair-syntax-info
          (direct (assq command-event fallback))
          (reverse (rassq command-event fallback)))
     (cond
+     ((cl-loop
+       for pairs in fallback
+       if (and
+	   (stringp (car pairs))
+	   (looking-back
+	    (car pairs)
+	    (- (point) (length (car pairs)))))
+         return (list
+                 'str
+                 ;; Get pair ender
+                 (if (proper-list-p pairs)
+                     (nth 1 pairs)
+                   (cdr pairs))
+                 ;; Get if pairs have to insert a space after
+                 ;; first pair was inserted.
+                 nil
+                 (if (proper-list-p pairs)
+                     (nth 2 pairs)))))
      ((memq (car table-syntax-and-pair)
             '(?\" ?\( ?\) ?\$))
       (append table-syntax-and-pair (list nil string-or-comment)))
@@ -533,7 +557,7 @@ electric-pair-post-self-insert-function
          (beg (when num (- pos num)))
          (skip-whitespace-info))
     (pcase (electric-pair-syntax-info last-command-event)
-      (`(,syntax ,pair ,unconditional ,_)
+      (`(,syntax ,pair ,unconditional ,space)
        (cond
         ((null pos) nil)
         ((zerop num) nil)
@@ -591,6 +615,12 @@ electric-pair-post-self-insert-function
                               pos))
          (forward-char num))
         ;; Insert matching pair.
+        ;; String pairs
+        ((and (eq syntax 'str) (not overwrite-mode))
+         (if space (insert " "))
+         (save-excursion
+           (insert pair)))
+        ;; Char pairs
         ((and (memq syntax '(?\( ?\" ?\$))
               (not overwrite-mode)
               (or unconditional
@@ -628,6 +658,7 @@ electric-pair-delete-pair
   (delete-char arg)
   (backward-delete-char-untabify arg killp))
 
+;; TODO: Add support for string pairs here.
 (defvar electric-pair-mode-map
   (let ((map (make-sparse-keymap)))
     (define-key map "\177"
-- 
2.49.0

-- 
                                          - E.G via GNU Emacs and Org.