Subject: bug#33794: 26.1; electric-pair-mode breaks auto-newline minor mode of cc-mode

From: João Távora <[email-masked]>
Date: Fri, 21 Dec 2018 18:49:34 +0000

Beatrix Klebe <[email-masked]> writes:

> I believe I saw your Stack Overflow answer about this while searching
> for the solution. electric-layout-mode works with some quirks, such as
> that if you put a space after parens in a function definition, the
> space gets carried on to the newline with that method, which is a bit
> annoying. What would be ideal, and what I'm looking for, is to get
> auto-pairing of brackets with braces being placed where they should be
> automatically and the insertion point getting put in between them at
> the correct indent level, such as what happens with Visual Studio, or
> Visual Studio Code, or several other editors with this functionality.
> Perhaps it is not emacslike to have such behavior be totally
> automated, but I am used to it and finds it decreases my ordinary
> levels of frustration when working with verbose and imperative
> languages. I am currently trying to write some insert specifiers for
> smartparens to do this, but it is proving more difficult to find an
> elegant solution than I had expected.

It is quite emacslike (though maybe not activated by default): you just
have to report the bugs to the Emacs developers as efficiently as
possible.

1. Though Alan possibly has already, I still cannot understand the
   original problem.  Can you start by describing what the buffer looked
   like before, what you did, what it looked like afterwards, and what
   you expected it to look like?  If possible start with a clean Emacs
   -Q recpe.

2. I have experimented with nicer-playing like alternatives like
   electric-layout-mode.  I came across a few quirks myself (though I'm
   not sure if they are the same as yours). So I prepared a patch (in
   branch scratch/fix-33794-extend-electric-layout-mode) and attached
   it after the sig.

After loading this patch, in a simple Emacs -Q the configuration:

   (electric-pair-mode)
   (electric-layout-mode)
    
   (add-hook 'c-mode-hook
          (lambda ()
            (setq-local electric-layout-rules
                        '((?\{ . after)
                          (?\{ . after-stay)))))

And, when visiting a C file, if I press `{' I get the expected
pair+layout+indent behaviour.  Sor example opening a brace after
int main () gives me:

    int main () {
        <cursor here>
    }

I, like Stefan, think cc-mode could/should set electric-layout-rules
buffer-locally to reflect whatever c-style the user has selected.

Thanks,
João

PS: Also, can you link to the the relevant to the stack overflow answer you
mentioned?

commit ab036bdedbb49ecc96d550b5e883e43bb03eaccc
Author: JoÃ£o TÃ¡vora <[email-masked]>
Date:   Fri Dec 21 18:00:08 2018 +0000

    Extend electric-layout-mode to handle more complex layouts
    
    Also, have it play nice with electric-pair-mode.
    
    Multiple matching entries in `electric-layout-rules' are executed in
    order of appearance.  When inserting a newline in the 'after-stay
    rule, ensure electric-pair-open-newline-between-pairs is nil.
    
    Arguably the logic behind electric-pair-open-newline-between-pairs
    should be moved to electric-layout-mode, but the current rule-matching
    engine doesn't allow for it.  The current solution seems to be good
    enough for the situations reported in bug#33794.
    
    * lisp/electric.el (electric-layout-rules): Adjust docstring.
    (electric-layout-post-self-insert-function): Loop through rules.  Bind
    electric-pair-open-newline-between-pairs to nil when handling
    after-stay.

diff --git a/lisp/electric.el b/lisp/electric.el
index 6dbf46b80c..6a307a49b9 100644
--- a/lisp/electric.el
+++ b/lisp/electric.el
@@ -370,38 +370,43 @@ electric-layout-rules
 
 The symbols specify where in relation to CHAR the newline
 character(s) should be inserted. `after-stay' means insert a
-newline after CHAR but stay in the same place.")
+newline after CHAR but stay in the same place.
+
+If multiple rules match, they are all executed in order of
+appearance.")
 
 (defun electric-layout-post-self-insert-function ()
-  (let* ((rule (cdr (assq last-command-event electric-layout-rules)))
-         pos)
-    (when (and rule
-               (setq pos (electric--after-char-pos))
+  (let (pos)
+    (when (and (setq pos (electric--after-char-pos))
                ;; Not in a string or comment.
                (not (nth 8 (save-excursion (syntax-ppss pos)))))
-      (let ((end (point-marker))
-            (sym (if (functionp rule) (funcall rule) rule)))
-        (set-marker-insertion-type end (not (eq sym 'after-stay)))
-        (goto-char pos)
-        (pcase sym
-          ;; FIXME: we used `newline' down here which called
-          ;; self-insert-command and ran post-self-insert-hook recursively.
-          ;; It happened to make electric-indent-mode work automatically with
-          ;; electric-layout-mode (at the cost of re-indenting lines
-          ;; multiple times), but I'm not sure it's what we want.
-          ;;
-          ;; FIXME: check eolp before inserting \n?
-          ('before (goto-char (1- pos)) (skip-chars-backward " \t")
-                   (unless (bolp) (insert "\n")))
-          ('after  (insert "\n"))
-          ('after-stay (save-excursion
-                         (let ((electric-layout-rules nil))
-                           (newline 1 t))))
-          ('around (save-excursion
-                     (goto-char (1- pos)) (skip-chars-backward " \t")
-                     (unless (bolp) (insert "\n")))
-                   (insert "\n")))      ; FIXME: check eolp before inserting \n?
-        (goto-char end)))))
+      (goto-char pos)
+      (dolist (rule electric-layout-rules)
+        (when (eq last-command-event (car rule))
+          (let* ((end (point-marker))
+                 (rule (cdr rule))
+                 (sym (if (functionp rule) (funcall rule) rule)))
+            (set-marker-insertion-type end (not (eq sym 'after-stay)))
+            (pcase sym
+              ;; FIXME: we used `newline' down here which called
+              ;; self-insert-command and ran post-self-insert-hook recursively.
+              ;; It happened to make electric-indent-mode work automatically with
+              ;; electric-layout-mode (at the cost of re-indenting lines
+              ;; multiple times), but I'm not sure it's what we want.
+              ;;
+              ;; FIXME: check eolp before inserting \n?
+              ('before (goto-char (1- pos)) (skip-chars-backward " \t")
+                       (unless (bolp) (insert "\n")))
+              ('after  (insert "\n"))
+              ('after-stay (save-excursion
+                             (let ((electric-layout-rules nil)
+                                   (electric-pair-open-newline-between-pairs nil))
+                               (newline 1 t))))
+              ('around (save-excursion
+                         (goto-char (1- pos)) (skip-chars-backward " \t")
+                         (unless (bolp) (insert "\n")))
+                       (insert "\n")))      ; FIXME: check eolp before inserting \n?
+            (goto-char end)))))))
 
 (put 'electric-layout-post-self-insert-function 'priority  40)