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

From: Beatrix Klebe <[email-masked]>
Date: Fri, 21 Dec 2018 14:43:41 -0500

I know how to do hotpaches but that doesn't appear to solve the
problem I'm having here, unless I've missed something. The problem is
with moving the opening bracket, not the insertion point.

On Fri, Dec 21, 2018 at 2:20 PM João Távora <[email-masked]> wrote:
>
> Hi Beatrix,
>
> The solution I propose involves introducing the hotpatch I attached to fix electric-layout-mode in your emacs, so I wouldn't expect it to work if you haven't done that.
>
> Do you know how to do it?
>
> Though Alan will probably suggest otherwise, I'd also steer away from c-specific functionality and keep to the triad electric-indent-mode, electric-pair-mode and electric-indent-mode, at least while we try to extend/fix these modes to accommodate your needs.
>
> After such a solution is evaluated, you can select to keep it or move to something else.
>
> João
>
> On Fri, Dec 21, 2018, 19:06 Beatrix Klebe <[email-masked] wrote:
>>
>> Here's the link, I believe it was Stefan that answered it:
>> https://emacs.stackexchange.com/questions/2837/automatically-formatting-brackets/2853#2853
>>
>> I have tried this with emacs -Q and it does not fix the issue, which
>> is as follows.
>>
>> Ordinarily in cc-mode when you have auto-newline-mode activated, and
>> as far as I can tell, a cc-mode configuration that supports it, (which
>> csharp-mode contains), the following happens when opening a block
>> (pipe is the cursor):
>>
>> void Main() {| // opening bracket is typed
>>
>> becomes
>>
>> void Main
>> {
>>     |
>>
>> when c-toggle-auto-newline is activated. However, if you also want
>> your braces automatically paired, with electric-pair-mode, instead the
>> following occurs:
>>
>> void Main() {| // opening bracket is typed
>>
>> void Main() {|} // electric-pair-mode closes the open bracket, but
>> auto-newline-mode does not appear to do anything.
>>
>> void Main() {
>>     |
>> } // user hits return, inserting the cursor at the correct indent
>> level, but leaving the opening brace where it is.
>>
>> The ideal/desired behavior is:
>>
>> void Main() {| // opening bracket is typed
>>
>> void Main()
>> {
>>     |
>> } // user hits return key, electric-pair-mode pairs up the brackets,
>> and auto-newline-mode formats the braces correctly
>>
>> It would also probably suffice to format with the newline before
>> hitting enter as well, although I think I prefer hitting enter to open
>> the block. I'm quite curious as to the internals of these formatting
>> systems and would be happy to help with a fix/feature if that would be
>> desired, I am mostly an OCaml programmer but C# is my day job and I've
>> just recently gotten deeper into Emacs Lisp.
>>
>> On Fri, Dec 21, 2018 at 1:49 PM João Távora <[email-masked]> wrote:
>> >
>> > 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)
>> >