1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-06 06:20:55 -08:00

electric-layout-mode kicks in before electric-pair-mode

This aims to solve problems with indentation.  Previously in, say, a
js-mode buffer with electric-layout-rules set to

   (?\{ before after)
   (?\} before)

would produce an intended:

   function ()
   {
     <indented point>
   }

The initial state

  function () {

Would go immediately to the following by e-p-m

  function () {}

Only then would e-l-m be applied to } first, and then again to {.
This makes lines indent in the wrong order, which can be a problem in
some modes.

The way we fix this is by reversing the order of e-p-m and e-l-m in
the post-self-insert-hook (and also fixing a number of details that
this uncovered).  In the end this changes the sequence from

  function () {

By way of e-l-m becomes:

  function () <newline>
  {
  <newline>

The e-p-m inserts the pair

  function () <newline>
  {
  <newline>}

And then e-l-m kicks in for the pair again, yielding the desired result

  function () <newline>
  {
  <indented point>
  }

* lisp/elec-pair.el (electric-pair--insert): Bind
electric-layout-no-duplicate-newlines.
(electric-pair-inhibit-if-helps-balance)
(electric-pair-skip-if-helps-balance): Use insert-before-markers,
playing nice with save-excurion.
(electric-pair-post-self-insert-function): Go to correct position
before checking electric-pair-inhibit-predicate and
electric-pair-skip-self predicate.
(electric-pair-post-self-insert-function): Increase priority to
50.

* lisp/electric.el (electric-indent-post-self-insert-function):
Delete trailing space in reindented line only if line was
really reindented.  Rewrite comment.
(electric-layout-allow-duplicate-newlines): New variable.
(electric-layout-post-self-insert-function-1): Rewrite comments.
Honours electric-layout-allow-duplicate-newlines.  Don't reindent
previous line because racecar.

* test/lisp/electric-tests.el: New test.
(plainer-c-mode): Move up.
(electric-modes-int-main-allman-style)
(electric-layout-int-main-kernel-style): Simplify
electric-layout-rules.
(electric-layout-for-c-style-du-jour): New helper.
(electric-layout-plainer-c-mode-use-c-style): New test.
This commit is contained in:
João Távora 2019-01-22 15:46:56 +00:00
parent 6ca4626c9f
commit fd94312443
3 changed files with 113 additions and 40 deletions

View file

@ -227,7 +227,8 @@ inside a comment or string."
(defun electric-pair--insert (char) (defun electric-pair--insert (char)
(let ((last-command-event char) (let ((last-command-event char)
(blink-matching-paren nil) (blink-matching-paren nil)
(electric-pair-mode nil)) (electric-pair-mode nil)
(electric-layout-allow-duplicate-newlines t))
(self-insert-command 1))) (self-insert-command 1)))
(cl-defmacro electric-pair--with-uncached-syntax ((table &optional start) &rest body) (cl-defmacro electric-pair--with-uncached-syntax ((table &optional start) &rest body)
@ -426,11 +427,10 @@ happened."
(eq (cdr outermost) pair))))) (eq (cdr outermost) pair)))))
((eq syntax ?\") ((eq syntax ?\")
(electric-pair--unbalanced-strings-p char)))) (electric-pair--unbalanced-strings-p char))))
(insert-char char))))) (insert-before-markers char)))))
(defun electric-pair-skip-if-helps-balance (char) (defun electric-pair-skip-if-helps-balance (char)
"Return non-nil if skipping CHAR would benefit parentheses' balance. "Return non-nil if skipping CHAR would benefit parentheses' balance.
Works by first removing the character from the buffer, then doing Works by first removing the character from the buffer, then doing
some list calculations, finally restoring the situation as if nothing some list calculations, finally restoring the situation as if nothing
happened." happened."
@ -452,7 +452,7 @@ happened."
(not (eq (cdr outermost) pair))))))) (not (eq (cdr outermost) pair)))))))
((eq syntax ?\") ((eq syntax ?\")
(electric-pair--inside-string-p char)))) (electric-pair--inside-string-p char))))
(insert-char char))))) (insert-before-markers char)))))
(defun electric-pair-default-skip-self (char) (defun electric-pair-default-skip-self (char)
(if electric-pair-preserve-balance (if electric-pair-preserve-balance
@ -498,7 +498,9 @@ happened."
((and (memq syntax '(?\) ?\" ?\$)) ((and (memq syntax '(?\) ?\" ?\$))
(and (or unconditional (and (or unconditional
(if (functionp electric-pair-skip-self) (if (functionp electric-pair-skip-self)
(funcall electric-pair-skip-self last-command-event) (save-excursion
(goto-char pos)
(funcall electric-pair-skip-self last-command-event))
electric-pair-skip-self)) electric-pair-skip-self))
(save-excursion (save-excursion
(when (and (not (and unconditional (when (and (not (and unconditional
@ -525,8 +527,10 @@ happened."
((and (memq syntax '(?\( ?\" ?\$)) ((and (memq syntax '(?\( ?\" ?\$))
(not overwrite-mode) (not overwrite-mode)
(or unconditional (or unconditional
(not (funcall electric-pair-inhibit-predicate (not (save-excursion
last-command-event)))) (goto-char pos)
(funcall electric-pair-inhibit-predicate
last-command-event)))))
(save-excursion (electric-pair--insert pair))))) (save-excursion (electric-pair--insert pair)))))
(_ (_
(when (and (if (functionp electric-pair-open-newline-between-pairs) (when (and (if (functionp electric-pair-open-newline-between-pairs)
@ -540,7 +544,7 @@ happened."
(matching-paren (char-after)))) (matching-paren (char-after))))
(save-excursion (newline 1 t))))))) (save-excursion (newline 1 t)))))))
(put 'electric-pair-post-self-insert-function 'priority 20) (put 'electric-pair-post-self-insert-function 'priority 50)
(defun electric-pair-will-use-region () (defun electric-pair-will-use-region ()
(and (use-region-p) (and (use-region-p)

View file

@ -270,28 +270,29 @@ or comment."
;; hence copied). ;; hence copied).
(let ((at-newline (<= pos (line-beginning-position)))) (let ((at-newline (<= pos (line-beginning-position))))
(when at-newline (when at-newline
(let ((before (copy-marker (1- pos) t))) (let ((before (copy-marker (1- pos) t))
inhibit-reindentation)
(save-excursion (save-excursion
(unless (or (memq indent-line-function (unless
electric-indent-functions-without-reindent) (setq inhibit-reindentation
electric-indent-inhibit) (or (memq indent-line-function
electric-indent-functions-without-reindent)
electric-indent-inhibit))
;; Don't reindent the previous line if the ;; Don't reindent the previous line if the
;; indentation function is not a real one. ;; indentation function is not a real one.
(goto-char before) (goto-char before)
(condition-case-unless-debug () (condition-case-unless-debug ()
(indent-according-to-mode) (indent-according-to-mode)
(error (throw 'indent-error nil)))) (error (throw 'indent-error nil)))
;; We are at EOL before the call to ;; The goal here will be to remove the trailing
;; `indent-according-to-mode', and after it we usually ;; whitespace after reindentation of the previous line
;; are as well, but not always. We tried to address ;; because that may have (re)introduced it.
;; it with `save-excursion' but that uses a normal (goto-char before)
;; marker whereas we need `move after insertion', so ;; We were at EOL in marker `before' before the call
;; we do the save/restore by hand. ;; to `indent-according-to-mode' but after we may
(goto-char before) ;; not be (Bug#15767).
(when (eolp) (when (and (eolp))
;; Remove the trailing whitespace after indentation because (delete-horizontal-space t))))))
;; indentation may (re)introduce the whitespace.
(delete-horizontal-space t)))))
(unless (and electric-indent-inhibit (unless (and electric-indent-inhibit
(not at-newline)) (not at-newline))
(condition-case-unless-debug () (condition-case-unless-debug ()
@ -388,6 +389,10 @@ WHERE if the rule matches, or nil if it doesn't match.
If multiple rules match, only first one is executed.") If multiple rules match, only first one is executed.")
;; TODO: Make this a defcustom?
(defvar electric-layout-allow-duplicate-newlines nil
"If non-nil, allow duplication of `before' newlines.")
(defun electric-layout-post-self-insert-function () (defun electric-layout-post-self-insert-function ()
(when electric-layout-mode (when electric-layout-mode
(electric-layout-post-self-insert-function-1))) (electric-layout-post-self-insert-function-1)))
@ -420,11 +425,14 @@ If multiple rules match, only first one is executed.")
(lambda () (lambda ()
;; FIXME: we use `newline', which calls ;; FIXME: we use `newline', which calls
;; `self-insert-command' and ran ;; `self-insert-command' and ran
;; `post-self-insert-hook' recursively. It ;; `post-self-insert-hook' recursively. It happened
;; happened to make `electric-indent-mode' work ;; to make `electric-indent-mode' work automatically
;; automatically with `electric-layout-mode' (at ;; with `electric-layout-mode' (at the cost of
;; the cost of re-indenting lines multiple times), ;; re-indenting lines multiple times), but I'm not
;; but I'm not sure it's what we want. ;; sure it's what we want.
;;
;; JT@19/02/22: Indeed in the case of `before'
;; newlines, re-indentation is prevented.
;; ;;
;; FIXME: when `newline'ing, we exceptionally ;; FIXME: when `newline'ing, we exceptionally
;; prevent a specific behaviour of ;; prevent a specific behaviour of
@ -438,10 +446,28 @@ If multiple rules match, only first one is executed.")
(let ((electric-layout-mode nil) (let ((electric-layout-mode nil)
(electric-pair-open-newline-between-pairs nil)) (electric-pair-open-newline-between-pairs nil))
(newline 1 t)))) (newline 1 t))))
(nl-before (lambda () (nl-before
(save-excursion (lambda ()
(goto-char (1- pos)) (skip-chars-backward " \t") (save-excursion
(unless (bolp) (funcall nl-after)))))) (goto-char (1- pos))
;; Normally, we don't duplicate newlines, but when
;; we're being called for i.e. a closer brace for
;; `electric-pair-mode' generally make sense. So
;; consult `electric-layout-allow-duplicate-newlines'
(unless (and (not electric-layout-allow-duplicate-newlines)
(progn (skip-chars-backward " \t")
(bolp)))
;; FIXME: JT@19/03/22: Make sure the `before'
;; newline being inserted here does not trigger
;; reindentation. It doesn't seem to be our job
;; to do so and it break with `cc-mode's
;; indentation function. Later on we can add a
;; before-and-maybe-indent, or if the user
;; really wants to reindent, then
;; `last-command-event' should be in
;; `electric-indent-chars'.
(let ((electric-indent-inhibit t))
(funcall nl-after)))))))
(pcase sym (pcase sym
('before (funcall nl-before)) ('before (funcall nl-before))
('after (funcall nl-after)) ('after (funcall nl-after))

View file

@ -391,11 +391,12 @@ baz\"\""
:bindings '((electric-pair-skip-whitespace . chomp)) :bindings '((electric-pair-skip-whitespace . chomp))
:test-in-comments nil) :test-in-comments nil)
(define-electric-pair-test whitespace-chomping-2 (ert-deftest electric-pair-whitespace-chomping-2-at-point-4-in-c++-mode-in-strings nil
" ( \n\t\t\n ) " "--)------" :expected-string " () " :expected-point 4 "Check if whitespace chomping works in `c++' unterminated strings."
:bindings '((electric-pair-skip-whitespace . chomp)) (electric-pair-test-for
:modes '(c++-mode) "\" ( \n \n ) \"" 4 41 "\" () \"" 5 'c++-mode
:test-in-comments nil) '((electric-pair-skip-whitespace . chomp))
(lambda () (electric-pair-mode 1))))
;; A test failure introduced by: ;; A test failure introduced by:
;; ;;
;; bb591f139f: Enhance CC Mode's fontification, etc., of unterminated strings. ;; bb591f139f: Enhance CC Mode's fontification, etc., of unterminated strings.
@ -513,6 +514,7 @@ baz\"\""
:fixture-fn #'(lambda () :fixture-fn #'(lambda ()
(electric-pair-mode 1))) (electric-pair-mode 1)))
(define-electric-pair-test js-mode-braces-with-layout (define-electric-pair-test js-mode-braces-with-layout
"" "{" :expected-string "{\n\n}" :expected-point 3 "" "{" :expected-string "{\n\n}" :expected-point 3
:modes '(js-mode) :modes '(js-mode)
@ -532,6 +534,16 @@ baz\"\""
(electric-indent-mode 1) (electric-indent-mode 1)
(electric-layout-mode 1))) (electric-layout-mode 1)))
(define-electric-pair-test js-mode-braces-with-layout-and-indent
"" "{" :expected-string "{\n \n}" :expected-point 7
:modes '(js-mode)
:test-in-comments nil
:test-in-strings nil
:fixture-fn #'(lambda ()
(electric-pair-mode 1)
(electric-indent-mode 1)
(electric-layout-mode 1)))
;;; Backspacing ;;; Backspacing
;;; TODO: better tests ;;; TODO: better tests
@ -821,6 +833,35 @@ baz\"\""
;;; tests for `electric-layout-mode' ;;; tests for `electric-layout-mode'
(define-derived-mode plainer-c-mode c-mode "pC"
"A plainer/saner C-mode with no internal electric machinery."
(c-toggle-electric-state -1)
(setq-local electric-indent-local-mode-hook nil)
(setq-local electric-indent-mode-hook nil)
(electric-indent-local-mode 1)
(dolist (key '(?\" ?\' ?\{ ?\} ?\( ?\) ?\[ ?\]))
(local-set-key (vector key) 'self-insert-command)))
(defun electric-layout-for-c-style-du-jour (inserted)
"A function to use in `electric-layout-rules'"
(when (memq inserted '(?{ ?}))
(save-excursion
(backward-char 2) (c-point-syntax) (forward-char) ; silly, but needed
(c-brace-newlines (c-point-syntax)))))
(ert-deftest electric-layout-plainer-c-mode-use-c-style ()
(ert-with-test-buffer ()
(plainer-c-mode)
(electric-layout-local-mode 1)
(electric-pair-local-mode 1)
(electric-indent-local-mode 1)
(setq-local electric-layout-rules
'(electric-layout-for-c-style-du-jour))
(insert "int main () ")
(let ((last-command-event ?\{))
(call-interactively (key-binding `[,last-command-event])))
(should (equal (buffer-string) "int main ()\n{\n \n}\n"))))
(ert-deftest electric-layout-int-main-kernel-style () (ert-deftest electric-layout-int-main-kernel-style ()
(ert-with-test-buffer () (ert-with-test-buffer ()
(plainer-c-mode) (plainer-c-mode)
@ -828,7 +869,8 @@ baz\"\""
(electric-pair-local-mode 1) (electric-pair-local-mode 1)
(electric-indent-local-mode 1) (electric-indent-local-mode 1)
(setq-local electric-layout-rules (setq-local electric-layout-rules
'((?\{ . (after-stay after)))) '((?\{ . (after))
(?\} . (before))))
(insert "int main () ") (insert "int main () ")
(let ((last-command-event ?\{)) (let ((last-command-event ?\{))
(call-interactively (key-binding `[,last-command-event]))) (call-interactively (key-binding `[,last-command-event])))
@ -850,7 +892,8 @@ baz\"\""
(electric-pair-local-mode 1) (electric-pair-local-mode 1)
(electric-indent-local-mode 1) (electric-indent-local-mode 1)
(setq-local electric-layout-rules (setq-local electric-layout-rules
'((?\{ . (before after-stay after)))) '((?\{ . (before after))
(?\} . (before))))
(insert "int main () ") (insert "int main () ")
(let ((last-command-event ?\{)) (let ((last-command-event ?\{))
(call-interactively (key-binding `[,last-command-event]))) (call-interactively (key-binding `[,last-command-event])))