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

From: Elijah Gabe Pérez <[email-masked]>
Date: Mon, 16 Jun 2025 18:56:33 -0600

Eli Zaretskii <[email-masked]> writes:

[...]

>> 
>> After the pair start was typed it expands to this:
>> /* | */
>>    ^ cursor
>
> OK, but this should be first described in the doc string, which should
> include the full documentation of the supported forms of the value.
>
> As for the :tag text how about the below?
>
>   "Strings, plus insert SPC after first string"

Thanks, It sounds better.

I've now updated the patch (plus added some new ert tests).

>From 2d9fdca2c5eabbd5352a16987ff4d63fb7fe4a02 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 insert string pairs in 'electric-pairs-mode'.

* etc/NEWS: Announce changes.
* lisp/elec-pair.el (electric-pair-pairs)
(electric-pair-text-pairs): Add new defcustom types.
(electric-pair-syntax-info)
(electric-pair-post-self-insert-function): Add support for
strings.
* test/lisp/electric-tests.el: Add new tests.
---
 etc/NEWS                    | 10 +++++
 lisp/elec-pair.el           | 80 +++++++++++++++++++++++++++++++++++--
 test/lisp/electric-tests.el | 27 +++++++++++++
 3 files changed, 113 insertions(+), 4 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 0c66ac38105..4b34e20b584 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -563,6 +563,16 @@ 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.
+'electric-pair-pairs' and 'electric-pair-text-pairs' now allows using
+strings for multi-character paired delimiters.
+
+To use this add a list to both electric pair variables: ("/*" . "*/").
+
+You can also specify to insert an extra space after the first string
+pair: ("/*" " */" t).
+
 +++
 ** 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 3173160370e..7c32e2521c5 100644
--- a/lisp/elec-pair.el
+++ b/lisp/elec-pair.el
@@ -43,10 +43,34 @@ electric-pair-pairs
 no syntax relevant to `electric-pair-mode' in the mode's syntax
 table.
 
+Each list element should be in one of these forms:
+ (CHAR . CHAR)
+Where CHAR is character to be used as pair.
+
+ (STRING STRING SPC)
+Where STRING is a string to be used as pair and SPC a non-nil value
+which specifies to insert an extra space after first STRING.
+
+ (STRING . STRING)
+This is similar to (STRING STRING SPC) form, except that SPC (space) is
+ignored and will not be inserted.
+
+In both string pairs forms, the first string pair must be a regular
+expression.
+
+In comparation to character pairs, string pairs does not support
+inserting pairs in regions and can not be deleted with
+`electric-pair-delete-pair', thus string pairs should be used only for
+multi-character 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 "Strings, plus insert SPC after first string"
+                        string string boolean))))
 
 (defcustom electric-pair-text-pairs
   `((?\" . ?\")
@@ -56,10 +80,36 @@ electric-pair-text-pairs
 
 Pairs of delimiters in this list are a fallback in case they have
 no syntax relevant to `electric-pair-mode' in the syntax table
-defined in `electric-pair-text-syntax-table'."
+defined in `electric-pair-text-syntax-table'.
+
+Each list element should be in one of these forms:
+ (CHAR . CHAR)
+Where CHAR is character to be used as pair.
+
+ (STRING STRING SPC)
+Where STRING is a string to be used as pair and SPC a non-nil value
+which specifies to insert an extra space after first STRING.
+
+ (STRING . STRING)
+This is similar to (STRING STRING SPC) form, except that SPC (space) is
+ignored and will not be inserted.
+
+In both string pairs forms, the first string pair must be a regular
+expression.
+
+In comparation to character pairs, string pairs does not support
+inserting pairs in regions and can not be deleted with
+`electric-pair-delete-pair', thus string pairs should be used only for
+multi-character pairs.
+
+See also the variable `electric-pair-pairs'."
   :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 "Strings, plus insert SPC after first string"
+                        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.
@@ -276,6 +326,22 @@ 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) (pos-bol)))
+         return (list
+                 'str
+                 ;; Get pair ender
+                 (if (proper-list-p pairs)
+                     (nth 1 pairs)
+                   (cdr pairs))
+                 nil
+                 ;; Check if pairs have to insert a space after
+                 ;; first pair was inserted.
+                 (if (proper-list-p pairs)
+                     (nth 2 pairs)))))
      ((memq (car table-syntax-and-pair)
             '(?\" ?\( ?\) ?\$))
       (append table-syntax-and-pair (list nil string-or-comment)))
@@ -560,7 +626,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)
@@ -622,6 +688,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
diff --git a/test/lisp/electric-tests.el b/test/lisp/electric-tests.el
index ad7506f68ff..4468559cf38 100644
--- a/test/lisp/electric-tests.el
+++ b/test/lisp/electric-tests.el
@@ -549,6 +549,33 @@ js-mode-braces-with-layout-and-indent
                 (electric-pair-mode 1)
                 (electric-indent-mode 1)
                 (electric-layout-mode 1)))
+
+;;; String pairs
+;;; TODO: add more tests
+;;;
+
+;; NOTE: Currently string pairs do not support insert pairs in region
+;;       or delete them with electric-pair-delete-pair
+
+(ert-deftest electric-pair-strings-pairs ()
+  (save-electric-modes
+    (with-temp-buffer
+      (setq-local electric-pair-pairs `((,(regexp-quote "/*") . "*/")))
+      (electric-pair-local-mode)
+      (insert "/")
+      (let ((last-command-event ?\*))
+        (ert-simulate-command '(self-insert-command 1)))
+      (should (equal "/**/" (buffer-string))))))
+
+(ert-deftest electric-pair-strings-pairs-with-space ()
+  (save-electric-modes
+    (with-temp-buffer
+      (setq-local electric-pair-pairs `((,(regexp-quote "/*") " */" t)))
+      (electric-pair-local-mode)
+      (insert "/")
+      (let ((last-command-event ?\*))
+        (ert-simulate-command '(self-insert-command 1)))
+      (should (equal "/*  */" (buffer-string))))))
 
 
 ;;; Backspacing
-- 
2.49.0

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