diff options
| -rw-r--r-- | lisp/electric.el | 125 | ||||
| -rw-r--r-- | test/lisp/electric-tests.el | 104 |
2 files changed, 196 insertions, 33 deletions
diff --git a/lisp/electric.el b/lisp/electric.el index 36841bc6ccd..e7ebdf5739d 100644 --- a/lisp/electric.el +++ b/lisp/electric.el | |||
| @@ -363,45 +363,91 @@ use `electric-indent-local-mode'." | |||
| 363 | (defvar electric-layout-rules nil | 363 | (defvar electric-layout-rules nil |
| 364 | "List of rules saying where to automatically insert newlines. | 364 | "List of rules saying where to automatically insert newlines. |
| 365 | 365 | ||
| 366 | Each rule has the form (CHAR . WHERE) where CHAR is the char that | 366 | Each rule has the form (CHAR . WHERE), the rule matching if the |
| 367 | was just inserted and WHERE specifies where to insert newlines | 367 | character just inserted was CHAR. WHERE specifies where to |
| 368 | and can be: nil, `before', `after', `around', `after-stay', or a | 368 | insert newlines, and can be: |
| 369 | function of no arguments that returns one of those symbols. | ||
| 370 | 369 | ||
| 371 | The symbols specify where in relation to CHAR the newline | 370 | * one of the symbols `before', `after', `around', `after-stay', |
| 372 | character(s) should be inserted. `after-stay' means insert a | 371 | or nil. |
| 373 | newline after CHAR but stay in the same place.") | 372 | |
| 373 | * a list of the preceding symbols, processed in order of | ||
| 374 | appearance to insert multiple newlines; | ||
| 375 | |||
| 376 | * a function of no arguments that returns one of the previous | ||
| 377 | values. | ||
| 378 | |||
| 379 | Each symbol specifies where, in relation to the position POS of | ||
| 380 | the character inserted, the newline character(s) should be | ||
| 381 | inserted. `after-stay' means insert a newline after POS but stay | ||
| 382 | in the same place. | ||
| 383 | |||
| 384 | Instead of the (CHAR . WHERE) form, a rule can also be just a | ||
| 385 | function of a single argument, the character just inserted. It | ||
| 386 | should return a value compatible with WHERE if the rule matches, | ||
| 387 | or nil if it doesn't match. | ||
| 388 | |||
| 389 | If multiple rules match, only first one is executed.") | ||
| 374 | 390 | ||
| 375 | (defun electric-layout-post-self-insert-function () | 391 | (defun electric-layout-post-self-insert-function () |
| 376 | (let* ((rule (cdr (assq last-command-event electric-layout-rules))) | 392 | (when electric-layout-mode |
| 377 | pos) | 393 | (electric-layout-post-self-insert-function-1))) |
| 394 | |||
| 395 | ;; for edebug's sake, a separate function | ||
| 396 | (defun electric-layout-post-self-insert-function-1 () | ||
| 397 | (let* (pos | ||
| 398 | probe | ||
| 399 | (rules electric-layout-rules) | ||
| 400 | (rule | ||
| 401 | (catch 'done | ||
| 402 | (while (setq probe (pop rules)) | ||
| 403 | (cond ((and (consp probe) | ||
| 404 | (eq (car probe) last-command-event)) | ||
| 405 | (throw 'done (cdr probe))) | ||
| 406 | ((functionp probe) | ||
| 407 | (let ((res | ||
| 408 | (save-excursion | ||
| 409 | (goto-char | ||
| 410 | (or pos (setq pos (electric--after-char-pos)))) | ||
| 411 | (funcall probe last-command-event)))) | ||
| 412 | (when res (throw 'done res))))))))) | ||
| 378 | (when (and rule | 413 | (when (and rule |
| 379 | (setq pos (electric--after-char-pos)) | 414 | (or pos (setq pos (electric--after-char-pos))) |
| 380 | ;; Not in a string or comment. | 415 | ;; Not in a string or comment. |
| 381 | (not (nth 8 (save-excursion (syntax-ppss pos))))) | 416 | (not (nth 8 (save-excursion (syntax-ppss pos))))) |
| 382 | (let ((end (point-marker)) | 417 | (goto-char pos) |
| 383 | (sym (if (functionp rule) (funcall rule) rule))) | 418 | (when (functionp rule) (setq rule (funcall rule))) |
| 384 | (set-marker-insertion-type end (not (eq sym 'after-stay))) | 419 | (dolist (sym (if (symbolp rule) (list rule) rule)) |
| 385 | (goto-char pos) | 420 | (let* ((nl-after |
| 386 | (pcase sym | 421 | (lambda () |
| 387 | ;; FIXME: we used `newline' down here which called | 422 | ;; FIXME: we use `newline', which calls |
| 388 | ;; self-insert-command and ran post-self-insert-hook recursively. | 423 | ;; `self-insert-command' and ran |
| 389 | ;; It happened to make electric-indent-mode work automatically with | 424 | ;; `post-self-insert-hook' recursively. It |
| 390 | ;; electric-layout-mode (at the cost of re-indenting lines | 425 | ;; happened to make `electric-indent-mode' work |
| 391 | ;; multiple times), but I'm not sure it's what we want. | 426 | ;; automatically with `electric-layout-mode' (at |
| 392 | ;; | 427 | ;; the cost of re-indenting lines multiple times), |
| 393 | ;; FIXME: check eolp before inserting \n? | 428 | ;; but I'm not sure it's what we want. |
| 394 | ('before (goto-char (1- pos)) (skip-chars-backward " \t") | 429 | ;; |
| 395 | (unless (bolp) (insert "\n"))) | 430 | ;; FIXME: when `newline'ing, we exceptionally |
| 396 | ('after (insert "\n")) | 431 | ;; prevent a specific behaviour of |
| 397 | ('after-stay (save-excursion | 432 | ;; `eletric-pair-mode', that of opening an extra |
| 398 | (let ((electric-layout-rules nil)) | 433 | ;; newline between newly inserted matching paris. |
| 399 | (newline 1 t)))) | 434 | ;; In theory that behaviour should be provided by |
| 400 | ('around (save-excursion | 435 | ;; `electric-layout-mode' instead, which should be |
| 401 | (goto-char (1- pos)) (skip-chars-backward " \t") | 436 | ;; possible given the current API. |
| 402 | (unless (bolp) (insert "\n"))) | 437 | ;; |
| 403 | (insert "\n"))) ; FIXME: check eolp before inserting \n? | 438 | ;; FIXME: check eolp before inserting \n? |
| 404 | (goto-char end))))) | 439 | (let ((electric-layout-mode nil) |
| 440 | (electric-pair-open-newline-between-pairs nil)) | ||
| 441 | (newline 1 t)))) | ||
| 442 | (nl-before (lambda () | ||
| 443 | (save-excursion | ||
| 444 | (goto-char (1- pos)) (skip-chars-backward " \t") | ||
| 445 | (unless (bolp) (funcall nl-after)))))) | ||
| 446 | (pcase sym | ||
| 447 | ('before (funcall nl-before)) | ||
| 448 | ('after (funcall nl-after)) | ||
| 449 | ('after-stay (save-excursion (funcall nl-after))) | ||
| 450 | ('around (funcall nl-before) (funcall nl-after)))))))) | ||
| 405 | 451 | ||
| 406 | (put 'electric-layout-post-self-insert-function 'priority 40) | 452 | (put 'electric-layout-post-self-insert-function 'priority 40) |
| 407 | 453 | ||
| @@ -419,6 +465,19 @@ The variable `electric-layout-rules' says when and how to insert newlines." | |||
| 419 | (remove-hook 'post-self-insert-hook | 465 | (remove-hook 'post-self-insert-hook |
| 420 | #'electric-layout-post-self-insert-function)))) | 466 | #'electric-layout-post-self-insert-function)))) |
| 421 | 467 | ||
| 468 | ;;;###autoload | ||
| 469 | (define-minor-mode electric-layout-local-mode | ||
| 470 | "Toggle `electric-layout-mode' only in this buffer." | ||
| 471 | :variable (buffer-local-value 'electric-layout-mode (current-buffer)) | ||
| 472 | (cond | ||
| 473 | ((eq electric-layout-mode (default-value 'electric-layout-mode)) | ||
| 474 | (kill-local-variable 'electric-layout-mode)) | ||
| 475 | ((not (default-value 'electric-layout-mode)) | ||
| 476 | ;; Locally enabled, but globally disabled. | ||
| 477 | (electric-layout-mode 1) ; Setup the hooks. | ||
| 478 | (setq-default electric-layout-mode nil) ; But keep it globally disabled. | ||
| 479 | ))) | ||
| 480 | |||
| 422 | ;;; Electric quoting. | 481 | ;;; Electric quoting. |
| 423 | 482 | ||
| 424 | (defcustom electric-quote-comment t | 483 | (defcustom electric-quote-comment t |
diff --git a/test/lisp/electric-tests.el b/test/lisp/electric-tests.el index 467abd1f274..5a4b20ed04e 100644 --- a/test/lisp/electric-tests.el +++ b/test/lisp/electric-tests.el | |||
| @@ -812,5 +812,109 @@ baz\"\"" | |||
| 812 | :bindings '((comment-start . "<!--") (comment-use-syntax . t)) | 812 | :bindings '((comment-start . "<!--") (comment-use-syntax . t)) |
| 813 | :test-in-comments nil :test-in-strings nil) | 813 | :test-in-comments nil :test-in-strings nil) |
| 814 | 814 | ||
| 815 | |||
| 816 | ;;; tests for `electric-layout-mode' | ||
| 817 | |||
| 818 | (ert-deftest electric-layout-int-main-kernel-style () | ||
| 819 | (ert-with-test-buffer () | ||
| 820 | (c-mode) | ||
| 821 | (electric-layout-local-mode 1) | ||
| 822 | (electric-pair-local-mode 1) | ||
| 823 | (electric-indent-local-mode 1) | ||
| 824 | (setq-local electric-layout-rules | ||
| 825 | '((?\{ . (after-stay after)))) | ||
| 826 | (insert "int main () ") | ||
| 827 | (let ((last-command-event ?\{)) | ||
| 828 | (call-interactively (key-binding `[,last-command-event]))) | ||
| 829 | (should (equal (buffer-string) "int main () {\n \n}")))) | ||
| 830 | |||
| 831 | (ert-deftest electric-layout-int-main-allman-style () | ||
| 832 | (ert-with-test-buffer () | ||
| 833 | (c-mode) | ||
| 834 | (electric-layout-local-mode 1) | ||
| 835 | (electric-pair-local-mode 1) | ||
| 836 | (electric-indent-local-mode 1) | ||
| 837 | (setq-local electric-layout-rules | ||
| 838 | '((?\{ . (before after-stay after)))) | ||
| 839 | (insert "int main () ") | ||
| 840 | (let ((last-command-event ?\{)) | ||
| 841 | (call-interactively (key-binding `[,last-command-event]))) | ||
| 842 | (should (equal (buffer-string) "int main ()\n{\n \n}")))) | ||
| 843 | |||
| 844 | (define-derived-mode plainer-c-mode c-mode "pC" | ||
| 845 | "A plainer/saner C-mode with no internal electric machinery." | ||
| 846 | (c-toggle-electric-state -1) | ||
| 847 | (setq-local electric-indent-local-mode-hook nil) | ||
| 848 | (setq-local electric-indent-mode-hook nil) | ||
| 849 | (electric-indent-local-mode 1) | ||
| 850 | (dolist (key '(?\" ?\' ?\{ ?\} ?\( ?\) ?\[ ?\])) | ||
| 851 | (local-set-key (vector key) 'self-insert-command))) | ||
| 852 | |||
| 853 | (ert-deftest electric-modes-in-c-mode-with-self-insert-command () | ||
| 854 | (ert-with-test-buffer () | ||
| 855 | (plainer-c-mode) | ||
| 856 | (electric-layout-local-mode 1) | ||
| 857 | (electric-pair-local-mode 1) | ||
| 858 | (electric-indent-local-mode 1) | ||
| 859 | (setq-local electric-layout-rules | ||
| 860 | '((?\{ . (before after-stay after)))) | ||
| 861 | (insert "int main () ") | ||
| 862 | (let ((last-command-event ?\{)) | ||
| 863 | (call-interactively (key-binding `[,last-command-event]))) | ||
| 864 | (should (equal (buffer-string) "int main ()\n{\n \n}")))) | ||
| 865 | |||
| 866 | (ert-deftest electric-pair-mode-newline-between-parens () | ||
| 867 | (ert-with-test-buffer () | ||
| 868 | (plainer-c-mode) | ||
| 869 | (electric-layout-local-mode -1) ;; ensure e-l-m mode is off | ||
| 870 | (electric-pair-local-mode 1) | ||
| 871 | (insert-before-markers "int main () {}") | ||
| 872 | (backward-char 1) | ||
| 873 | (let ((last-command-event ? )) | ||
| 874 | (call-interactively (key-binding `[,last-command-event]))) | ||
| 875 | (should (equal (buffer-string) "int main () {\n \n}")))) | ||
| 876 | |||
| 877 | (ert-deftest electric-layout-mode-newline-between-parens-without-e-p-m () | ||
| 878 | (ert-with-test-buffer () | ||
| 879 | (plainer-c-mode) | ||
| 880 | (electric-layout-local-mode 1) | ||
| 881 | (electric-pair-local-mode -1) ;; ensure e-p-m mode is off | ||
| 882 | (electric-indent-local-mode 1) | ||
| 883 | (setq-local electric-layout-rules | ||
| 884 | '((?\n | ||
| 885 | . | ||
| 886 | (lambda () | ||
| 887 | (when (eq (save-excursion | ||
| 888 | (skip-chars-backward "\t\s") | ||
| 889 | (char-before (1- (point)))) | ||
| 890 | (matching-paren (char-after))) | ||
| 891 | '(after-stay)))))) | ||
| 892 | (insert "int main () {}") | ||
| 893 | (backward-char 1) | ||
| 894 | (let ((last-command-event ? )) | ||
| 895 | (call-interactively (key-binding `[,last-command-event]))) | ||
| 896 | (should (equal (buffer-string) "int main () {\n \n}")))) | ||
| 897 | |||
| 898 | (ert-deftest electric-layout-mode-newline-between-parens-without-e-p-m-2 () | ||
| 899 | (ert-with-test-buffer () | ||
| 900 | (plainer-c-mode) | ||
| 901 | (electric-layout-local-mode 1) | ||
| 902 | (electric-pair-local-mode -1) ;; ensure e-p-m mode is off | ||
| 903 | (electric-indent-local-mode 1) | ||
| 904 | (setq-local electric-layout-rules | ||
| 905 | '((lambda (char) | ||
| 906 | (when (and | ||
| 907 | (eq char ?\n) | ||
| 908 | (eq (save-excursion | ||
| 909 | (skip-chars-backward "\t\s") | ||
| 910 | (char-before (1- (point)))) | ||
| 911 | (matching-paren (char-after)))) | ||
| 912 | '(after-stay))))) | ||
| 913 | (insert "int main () {}") | ||
| 914 | (backward-char 1) | ||
| 915 | (let ((last-command-event ? )) | ||
| 916 | (call-interactively (key-binding `[,last-command-event]))) | ||
| 917 | (should (equal (buffer-string) "int main () {\n \n}")))) | ||
| 918 | |||
| 815 | (provide 'electric-tests) | 919 | (provide 'electric-tests) |
| 816 | ;;; electric-tests.el ends here | 920 | ;;; electric-tests.el ends here |