aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoão Távora2018-12-21 18:00:08 +0000
committerJoão Távora2019-01-02 12:55:16 +0000
commit949295ae1a8a79a181b2bf614b9c69849f2fd667 (patch)
tree6c1570504f7f43a2c47f1651dae18323b229cdc7
parent0515b223c2158984e135e84be97c01d5b8d0ae75 (diff)
downloademacs-949295ae1a8a79a181b2bf614b9c69849f2fd667.tar.gz
emacs-949295ae1a8a79a181b2bf614b9c69849f2fd667.zip
Extend electric-layout-mode to handle more complex layouts (bug#33794)
Entries in electric-layout-rules can specify multiple newline-related actions which are executed in order of appearance. Also, have it play nice with electric-pair-mode when inserting a newlines, particularly with electric-pair-open-newline-between-pairs. Entries in electric-layout-rules can also be functions. Among other things, the logic behind electric-pair-open-newline-between-pairs could now be moved to electric-layout-mode, but this commit doesn't do that yet. This change was motivated by bug#33794 and is an alternative solution to the problem reported in that bug. * lisp/electric.el (electric-layout-rules): Adjust docstring. (electric-layout-post-self-insert-function): Call electric-layout-post-self-insert-function-1. (electric-layout-post-self-insert-function-1): Rename from electric-layout-post-self-insert-function. Redesign. (electric-layout-local-mode): New minor mode. * test/lisp/electric-tests.el (electric-layout-int-main-kernel-style) (electric-layout-int-main-allman-style) (electric-modes-in-c-mode-with-self-insert-command) (electric-pair-mode-newline-between-parens) (electric-layout-mode-newline-between-parens-without-e-p-m) (electric-layout-mode-newline-between-parens-without-e-p-m-2): New tests. (plainer-c-mode): New helper.
-rw-r--r--lisp/electric.el125
-rw-r--r--test/lisp/electric-tests.el104
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
366Each rule has the form (CHAR . WHERE) where CHAR is the char that 366Each rule has the form (CHAR . WHERE), the rule matching if the
367was just inserted and WHERE specifies where to insert newlines 367character just inserted was CHAR. WHERE specifies where to
368and can be: nil, `before', `after', `around', `after-stay', or a 368insert newlines, and can be:
369function of no arguments that returns one of those symbols.
370 369
371The symbols specify where in relation to CHAR the newline 370* one of the symbols `before', `after', `around', `after-stay',
372character(s) should be inserted. `after-stay' means insert a 371 or nil.
373newline 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
379Each symbol specifies where, in relation to the position POS of
380the character inserted, the newline character(s) should be
381inserted. `after-stay' means insert a newline after POS but stay
382in the same place.
383
384Instead of the (CHAR . WHERE) form, a rule can also be just a
385function of a single argument, the character just inserted. It
386should return a value compatible with WHERE if the rule matches,
387or nil if it doesn't match.
388
389If 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