diff options
| author | João Távora | 2019-01-22 15:46:56 +0000 |
|---|---|---|
| committer | João Távora | 2019-01-22 16:42:43 +0000 |
| commit | fd943124439b7644392919bca8bc2a77e6316d92 (patch) | |
| tree | 22fa8a1103239088dbd7dae9cd0c5974e5f72ebc | |
| parent | 6ca4626c9fe33f78df685b4df7aca88abb272118 (diff) | |
| download | emacs-fd943124439b7644392919bca8bc2a77e6316d92.tar.gz emacs-fd943124439b7644392919bca8bc2a77e6316d92.zip | |
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.
| -rw-r--r-- | lisp/elec-pair.el | 20 | ||||
| -rw-r--r-- | lisp/electric.el | 76 | ||||
| -rw-r--r-- | test/lisp/electric-tests.el | 57 |
3 files changed, 113 insertions, 40 deletions
diff --git a/lisp/elec-pair.el b/lisp/elec-pair.el index 2c5553e8dab..20581ad6573 100644 --- a/lisp/elec-pair.el +++ b/lisp/elec-pair.el | |||
| @@ -227,7 +227,8 @@ inside a comment or string." | |||
| 227 | (defun electric-pair--insert (char) | 227 | (defun electric-pair--insert (char) |
| 228 | (let ((last-command-event char) | 228 | (let ((last-command-event char) |
| 229 | (blink-matching-paren nil) | 229 | (blink-matching-paren nil) |
| 230 | (electric-pair-mode nil)) | 230 | (electric-pair-mode nil) |
| 231 | (electric-layout-allow-duplicate-newlines t)) | ||
| 231 | (self-insert-command 1))) | 232 | (self-insert-command 1))) |
| 232 | 233 | ||
| 233 | (cl-defmacro electric-pair--with-uncached-syntax ((table &optional start) &rest body) | 234 | (cl-defmacro electric-pair--with-uncached-syntax ((table &optional start) &rest body) |
| @@ -426,11 +427,10 @@ happened." | |||
| 426 | (eq (cdr outermost) pair))))) | 427 | (eq (cdr outermost) pair))))) |
| 427 | ((eq syntax ?\") | 428 | ((eq syntax ?\") |
| 428 | (electric-pair--unbalanced-strings-p char)))) | 429 | (electric-pair--unbalanced-strings-p char)))) |
| 429 | (insert-char char))))) | 430 | (insert-before-markers char))))) |
| 430 | 431 | ||
| 431 | (defun electric-pair-skip-if-helps-balance (char) | 432 | (defun electric-pair-skip-if-helps-balance (char) |
| 432 | "Return non-nil if skipping CHAR would benefit parentheses' balance. | 433 | "Return non-nil if skipping CHAR would benefit parentheses' balance. |
| 433 | |||
| 434 | Works by first removing the character from the buffer, then doing | 434 | Works by first removing the character from the buffer, then doing |
| 435 | some list calculations, finally restoring the situation as if nothing | 435 | some list calculations, finally restoring the situation as if nothing |
| 436 | happened." | 436 | happened." |
| @@ -452,7 +452,7 @@ happened." | |||
| 452 | (not (eq (cdr outermost) pair))))))) | 452 | (not (eq (cdr outermost) pair))))))) |
| 453 | ((eq syntax ?\") | 453 | ((eq syntax ?\") |
| 454 | (electric-pair--inside-string-p char)))) | 454 | (electric-pair--inside-string-p char)))) |
| 455 | (insert-char char))))) | 455 | (insert-before-markers char))))) |
| 456 | 456 | ||
| 457 | (defun electric-pair-default-skip-self (char) | 457 | (defun electric-pair-default-skip-self (char) |
| 458 | (if electric-pair-preserve-balance | 458 | (if electric-pair-preserve-balance |
| @@ -498,7 +498,9 @@ happened." | |||
| 498 | ((and (memq syntax '(?\) ?\" ?\$)) | 498 | ((and (memq syntax '(?\) ?\" ?\$)) |
| 499 | (and (or unconditional | 499 | (and (or unconditional |
| 500 | (if (functionp electric-pair-skip-self) | 500 | (if (functionp electric-pair-skip-self) |
| 501 | (funcall electric-pair-skip-self last-command-event) | 501 | (save-excursion |
| 502 | (goto-char pos) | ||
| 503 | (funcall electric-pair-skip-self last-command-event)) | ||
| 502 | electric-pair-skip-self)) | 504 | electric-pair-skip-self)) |
| 503 | (save-excursion | 505 | (save-excursion |
| 504 | (when (and (not (and unconditional | 506 | (when (and (not (and unconditional |
| @@ -525,8 +527,10 @@ happened." | |||
| 525 | ((and (memq syntax '(?\( ?\" ?\$)) | 527 | ((and (memq syntax '(?\( ?\" ?\$)) |
| 526 | (not overwrite-mode) | 528 | (not overwrite-mode) |
| 527 | (or unconditional | 529 | (or unconditional |
| 528 | (not (funcall electric-pair-inhibit-predicate | 530 | (not (save-excursion |
| 529 | last-command-event)))) | 531 | (goto-char pos) |
| 532 | (funcall electric-pair-inhibit-predicate | ||
| 533 | last-command-event))))) | ||
| 530 | (save-excursion (electric-pair--insert pair))))) | 534 | (save-excursion (electric-pair--insert pair))))) |
| 531 | (_ | 535 | (_ |
| 532 | (when (and (if (functionp electric-pair-open-newline-between-pairs) | 536 | (when (and (if (functionp electric-pair-open-newline-between-pairs) |
| @@ -540,7 +544,7 @@ happened." | |||
| 540 | (matching-paren (char-after)))) | 544 | (matching-paren (char-after)))) |
| 541 | (save-excursion (newline 1 t))))))) | 545 | (save-excursion (newline 1 t))))))) |
| 542 | 546 | ||
| 543 | (put 'electric-pair-post-self-insert-function 'priority 20) | 547 | (put 'electric-pair-post-self-insert-function 'priority 50) |
| 544 | 548 | ||
| 545 | (defun electric-pair-will-use-region () | 549 | (defun electric-pair-will-use-region () |
| 546 | (and (use-region-p) | 550 | (and (use-region-p) |
diff --git a/lisp/electric.el b/lisp/electric.el index ed968d7c65e..b9458776e3b 100644 --- a/lisp/electric.el +++ b/lisp/electric.el | |||
| @@ -270,28 +270,29 @@ or comment." | |||
| 270 | ;; hence copied). | 270 | ;; hence copied). |
| 271 | (let ((at-newline (<= pos (line-beginning-position)))) | 271 | (let ((at-newline (<= pos (line-beginning-position)))) |
| 272 | (when at-newline | 272 | (when at-newline |
| 273 | (let ((before (copy-marker (1- pos) t))) | 273 | (let ((before (copy-marker (1- pos) t)) |
| 274 | inhibit-reindentation) | ||
| 274 | (save-excursion | 275 | (save-excursion |
| 275 | (unless (or (memq indent-line-function | 276 | (unless |
| 276 | electric-indent-functions-without-reindent) | 277 | (setq inhibit-reindentation |
| 277 | electric-indent-inhibit) | 278 | (or (memq indent-line-function |
| 279 | electric-indent-functions-without-reindent) | ||
| 280 | electric-indent-inhibit)) | ||
| 278 | ;; Don't reindent the previous line if the | 281 | ;; Don't reindent the previous line if the |
| 279 | ;; indentation function is not a real one. | 282 | ;; indentation function is not a real one. |
| 280 | (goto-char before) | 283 | (goto-char before) |
| 281 | (condition-case-unless-debug () | 284 | (condition-case-unless-debug () |
| 282 | (indent-according-to-mode) | 285 | (indent-according-to-mode) |
| 283 | (error (throw 'indent-error nil)))) | 286 | (error (throw 'indent-error nil))) |
| 284 | ;; We are at EOL before the call to | 287 | ;; The goal here will be to remove the trailing |
| 285 | ;; `indent-according-to-mode', and after it we usually | 288 | ;; whitespace after reindentation of the previous line |
| 286 | ;; are as well, but not always. We tried to address | 289 | ;; because that may have (re)introduced it. |
| 287 | ;; it with `save-excursion' but that uses a normal | 290 | (goto-char before) |
| 288 | ;; marker whereas we need `move after insertion', so | 291 | ;; We were at EOL in marker `before' before the call |
| 289 | ;; we do the save/restore by hand. | 292 | ;; to `indent-according-to-mode' but after we may |
| 290 | (goto-char before) | 293 | ;; not be (Bug#15767). |
| 291 | (when (eolp) | 294 | (when (and (eolp)) |
| 292 | ;; Remove the trailing whitespace after indentation because | 295 | (delete-horizontal-space t)))))) |
| 293 | ;; indentation may (re)introduce the whitespace. | ||
| 294 | (delete-horizontal-space t))))) | ||
| 295 | (unless (and electric-indent-inhibit | 296 | (unless (and electric-indent-inhibit |
| 296 | (not at-newline)) | 297 | (not at-newline)) |
| 297 | (condition-case-unless-debug () | 298 | (condition-case-unless-debug () |
| @@ -388,6 +389,10 @@ WHERE if the rule matches, or nil if it doesn't match. | |||
| 388 | 389 | ||
| 389 | If multiple rules match, only first one is executed.") | 390 | If multiple rules match, only first one is executed.") |
| 390 | 391 | ||
| 392 | ;; TODO: Make this a defcustom? | ||
| 393 | (defvar electric-layout-allow-duplicate-newlines nil | ||
| 394 | "If non-nil, allow duplication of `before' newlines.") | ||
| 395 | |||
| 391 | (defun electric-layout-post-self-insert-function () | 396 | (defun electric-layout-post-self-insert-function () |
| 392 | (when electric-layout-mode | 397 | (when electric-layout-mode |
| 393 | (electric-layout-post-self-insert-function-1))) | 398 | (electric-layout-post-self-insert-function-1))) |
| @@ -420,11 +425,14 @@ If multiple rules match, only first one is executed.") | |||
| 420 | (lambda () | 425 | (lambda () |
| 421 | ;; FIXME: we use `newline', which calls | 426 | ;; FIXME: we use `newline', which calls |
| 422 | ;; `self-insert-command' and ran | 427 | ;; `self-insert-command' and ran |
| 423 | ;; `post-self-insert-hook' recursively. It | 428 | ;; `post-self-insert-hook' recursively. It happened |
| 424 | ;; happened to make `electric-indent-mode' work | 429 | ;; to make `electric-indent-mode' work automatically |
| 425 | ;; automatically with `electric-layout-mode' (at | 430 | ;; with `electric-layout-mode' (at the cost of |
| 426 | ;; the cost of re-indenting lines multiple times), | 431 | ;; re-indenting lines multiple times), but I'm not |
| 427 | ;; but I'm not sure it's what we want. | 432 | ;; sure it's what we want. |
| 433 | ;; | ||
| 434 | ;; JT@19/02/22: Indeed in the case of `before' | ||
| 435 | ;; newlines, re-indentation is prevented. | ||
| 428 | ;; | 436 | ;; |
| 429 | ;; FIXME: when `newline'ing, we exceptionally | 437 | ;; FIXME: when `newline'ing, we exceptionally |
| 430 | ;; prevent a specific behaviour of | 438 | ;; prevent a specific behaviour of |
| @@ -438,10 +446,28 @@ If multiple rules match, only first one is executed.") | |||
| 438 | (let ((electric-layout-mode nil) | 446 | (let ((electric-layout-mode nil) |
| 439 | (electric-pair-open-newline-between-pairs nil)) | 447 | (electric-pair-open-newline-between-pairs nil)) |
| 440 | (newline 1 t)))) | 448 | (newline 1 t)))) |
| 441 | (nl-before (lambda () | 449 | (nl-before |
| 442 | (save-excursion | 450 | (lambda () |
| 443 | (goto-char (1- pos)) (skip-chars-backward " \t") | 451 | (save-excursion |
| 444 | (unless (bolp) (funcall nl-after)))))) | 452 | (goto-char (1- pos)) |
| 453 | ;; Normally, we don't duplicate newlines, but when | ||
| 454 | ;; we're being called for i.e. a closer brace for | ||
| 455 | ;; `electric-pair-mode' generally make sense. So | ||
| 456 | ;; consult `electric-layout-allow-duplicate-newlines' | ||
| 457 | (unless (and (not electric-layout-allow-duplicate-newlines) | ||
| 458 | (progn (skip-chars-backward " \t") | ||
| 459 | (bolp))) | ||
| 460 | ;; FIXME: JT@19/03/22: Make sure the `before' | ||
| 461 | ;; newline being inserted here does not trigger | ||
| 462 | ;; reindentation. It doesn't seem to be our job | ||
| 463 | ;; to do so and it break with `cc-mode's | ||
| 464 | ;; indentation function. Later on we can add a | ||
| 465 | ;; before-and-maybe-indent, or if the user | ||
| 466 | ;; really wants to reindent, then | ||
| 467 | ;; `last-command-event' should be in | ||
| 468 | ;; `electric-indent-chars'. | ||
| 469 | (let ((electric-indent-inhibit t)) | ||
| 470 | (funcall nl-after))))))) | ||
| 445 | (pcase sym | 471 | (pcase sym |
| 446 | ('before (funcall nl-before)) | 472 | ('before (funcall nl-before)) |
| 447 | ('after (funcall nl-after)) | 473 | ('after (funcall nl-after)) |
diff --git a/test/lisp/electric-tests.el b/test/lisp/electric-tests.el index 0b076e4be01..4f1e5729be1 100644 --- a/test/lisp/electric-tests.el +++ b/test/lisp/electric-tests.el | |||
| @@ -391,11 +391,12 @@ baz\"\"" | |||
| 391 | :bindings '((electric-pair-skip-whitespace . chomp)) | 391 | :bindings '((electric-pair-skip-whitespace . chomp)) |
| 392 | :test-in-comments nil) | 392 | :test-in-comments nil) |
| 393 | 393 | ||
| 394 | (define-electric-pair-test whitespace-chomping-2 | 394 | (ert-deftest electric-pair-whitespace-chomping-2-at-point-4-in-c++-mode-in-strings nil |
| 395 | " ( \n\t\t\n ) " "--)------" :expected-string " () " :expected-point 4 | 395 | "Check if whitespace chomping works in `c++' unterminated strings." |
| 396 | :bindings '((electric-pair-skip-whitespace . chomp)) | 396 | (electric-pair-test-for |
| 397 | :modes '(c++-mode) | 397 | "\" ( \n \n ) \"" 4 41 "\" () \"" 5 'c++-mode |
| 398 | :test-in-comments nil) | 398 | '((electric-pair-skip-whitespace . chomp)) |
| 399 | (lambda () (electric-pair-mode 1)))) | ||
| 399 | ;; A test failure introduced by: | 400 | ;; A test failure introduced by: |
| 400 | ;; | 401 | ;; |
| 401 | ;; bb591f139f: Enhance CC Mode's fontification, etc., of unterminated strings. | 402 | ;; bb591f139f: Enhance CC Mode's fontification, etc., of unterminated strings. |
| @@ -513,6 +514,7 @@ baz\"\"" | |||
| 513 | :fixture-fn #'(lambda () | 514 | :fixture-fn #'(lambda () |
| 514 | (electric-pair-mode 1))) | 515 | (electric-pair-mode 1))) |
| 515 | 516 | ||
| 517 | |||
| 516 | (define-electric-pair-test js-mode-braces-with-layout | 518 | (define-electric-pair-test js-mode-braces-with-layout |
| 517 | "" "{" :expected-string "{\n\n}" :expected-point 3 | 519 | "" "{" :expected-string "{\n\n}" :expected-point 3 |
| 518 | :modes '(js-mode) | 520 | :modes '(js-mode) |
| @@ -532,6 +534,16 @@ baz\"\"" | |||
| 532 | (electric-indent-mode 1) | 534 | (electric-indent-mode 1) |
| 533 | (electric-layout-mode 1))) | 535 | (electric-layout-mode 1))) |
| 534 | 536 | ||
| 537 | (define-electric-pair-test js-mode-braces-with-layout-and-indent | ||
| 538 | "" "{" :expected-string "{\n \n}" :expected-point 7 | ||
| 539 | :modes '(js-mode) | ||
| 540 | :test-in-comments nil | ||
| 541 | :test-in-strings nil | ||
| 542 | :fixture-fn #'(lambda () | ||
| 543 | (electric-pair-mode 1) | ||
| 544 | (electric-indent-mode 1) | ||
| 545 | (electric-layout-mode 1))) | ||
| 546 | |||
| 535 | 547 | ||
| 536 | ;;; Backspacing | 548 | ;;; Backspacing |
| 537 | ;;; TODO: better tests | 549 | ;;; TODO: better tests |
| @@ -821,6 +833,35 @@ baz\"\"" | |||
| 821 | 833 | ||
| 822 | ;;; tests for `electric-layout-mode' | 834 | ;;; tests for `electric-layout-mode' |
| 823 | 835 | ||
| 836 | (define-derived-mode plainer-c-mode c-mode "pC" | ||
| 837 | "A plainer/saner C-mode with no internal electric machinery." | ||
| 838 | (c-toggle-electric-state -1) | ||
| 839 | (setq-local electric-indent-local-mode-hook nil) | ||
| 840 | (setq-local electric-indent-mode-hook nil) | ||
| 841 | (electric-indent-local-mode 1) | ||
| 842 | (dolist (key '(?\" ?\' ?\{ ?\} ?\( ?\) ?\[ ?\])) | ||
| 843 | (local-set-key (vector key) 'self-insert-command))) | ||
| 844 | |||
| 845 | (defun electric-layout-for-c-style-du-jour (inserted) | ||
| 846 | "A function to use in `electric-layout-rules'" | ||
| 847 | (when (memq inserted '(?{ ?})) | ||
| 848 | (save-excursion | ||
| 849 | (backward-char 2) (c-point-syntax) (forward-char) ; silly, but needed | ||
| 850 | (c-brace-newlines (c-point-syntax))))) | ||
| 851 | |||
| 852 | (ert-deftest electric-layout-plainer-c-mode-use-c-style () | ||
| 853 | (ert-with-test-buffer () | ||
| 854 | (plainer-c-mode) | ||
| 855 | (electric-layout-local-mode 1) | ||
| 856 | (electric-pair-local-mode 1) | ||
| 857 | (electric-indent-local-mode 1) | ||
| 858 | (setq-local electric-layout-rules | ||
| 859 | '(electric-layout-for-c-style-du-jour)) | ||
| 860 | (insert "int main () ") | ||
| 861 | (let ((last-command-event ?\{)) | ||
| 862 | (call-interactively (key-binding `[,last-command-event]))) | ||
| 863 | (should (equal (buffer-string) "int main ()\n{\n \n}\n")))) | ||
| 864 | |||
| 824 | (ert-deftest electric-layout-int-main-kernel-style () | 865 | (ert-deftest electric-layout-int-main-kernel-style () |
| 825 | (ert-with-test-buffer () | 866 | (ert-with-test-buffer () |
| 826 | (plainer-c-mode) | 867 | (plainer-c-mode) |
| @@ -828,7 +869,8 @@ baz\"\"" | |||
| 828 | (electric-pair-local-mode 1) | 869 | (electric-pair-local-mode 1) |
| 829 | (electric-indent-local-mode 1) | 870 | (electric-indent-local-mode 1) |
| 830 | (setq-local electric-layout-rules | 871 | (setq-local electric-layout-rules |
| 831 | '((?\{ . (after-stay after)))) | 872 | '((?\{ . (after)) |
| 873 | (?\} . (before)))) | ||
| 832 | (insert "int main () ") | 874 | (insert "int main () ") |
| 833 | (let ((last-command-event ?\{)) | 875 | (let ((last-command-event ?\{)) |
| 834 | (call-interactively (key-binding `[,last-command-event]))) | 876 | (call-interactively (key-binding `[,last-command-event]))) |
| @@ -850,7 +892,8 @@ baz\"\"" | |||
| 850 | (electric-pair-local-mode 1) | 892 | (electric-pair-local-mode 1) |
| 851 | (electric-indent-local-mode 1) | 893 | (electric-indent-local-mode 1) |
| 852 | (setq-local electric-layout-rules | 894 | (setq-local electric-layout-rules |
| 853 | '((?\{ . (before after-stay after)))) | 895 | '((?\{ . (before after)) |
| 896 | (?\} . (before)))) | ||
| 854 | (insert "int main () ") | 897 | (insert "int main () ") |
| 855 | (let ((last-command-event ?\{)) | 898 | (let ((last-command-event ?\{)) |
| 856 | (call-interactively (key-binding `[,last-command-event]))) | 899 | (call-interactively (key-binding `[,last-command-event]))) |