aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoão Távora2019-01-22 15:46:56 +0000
committerJoão Távora2019-01-22 16:42:43 +0000
commitfd943124439b7644392919bca8bc2a77e6316d92 (patch)
tree22fa8a1103239088dbd7dae9cd0c5974e5f72ebc
parent6ca4626c9fe33f78df685b4df7aca88abb272118 (diff)
downloademacs-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.el20
-rw-r--r--lisp/electric.el76
-rw-r--r--test/lisp/electric-tests.el57
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
434Works by first removing the character from the buffer, then doing 434Works by first removing the character from the buffer, then doing
435some list calculations, finally restoring the situation as if nothing 435some list calculations, finally restoring the situation as if nothing
436happened." 436happened."
@@ -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
389If multiple rules match, only first one is executed.") 390If 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])))