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.
This commit is contained in:
parent
0515b223c2
commit
949295ae1a
2 changed files with 196 additions and 33 deletions
125
lisp/electric.el
125
lisp/electric.el
|
@ -363,45 +363,91 @@ use `electric-indent-local-mode'."
|
|||
(defvar electric-layout-rules nil
|
||||
"List of rules saying where to automatically insert newlines.
|
||||
|
||||
Each rule has the form (CHAR . WHERE) where CHAR is the char that
|
||||
was just inserted and WHERE specifies where to insert newlines
|
||||
and can be: nil, `before', `after', `around', `after-stay', or a
|
||||
function of no arguments that returns one of those symbols.
|
||||
Each rule has the form (CHAR . WHERE), the rule matching if the
|
||||
character just inserted was CHAR. WHERE specifies where to
|
||||
insert newlines, and can be:
|
||||
|
||||
The symbols specify where in relation to CHAR the newline
|
||||
character(s) should be inserted. `after-stay' means insert a
|
||||
newline after CHAR but stay in the same place.")
|
||||
* one of the symbols `before', `after', `around', `after-stay',
|
||||
or nil.
|
||||
|
||||
* a list of the preceding symbols, processed in order of
|
||||
appearance to insert multiple newlines;
|
||||
|
||||
* a function of no arguments that returns one of the previous
|
||||
values.
|
||||
|
||||
Each symbol specifies where, in relation to the position POS of
|
||||
the character inserted, the newline character(s) should be
|
||||
inserted. `after-stay' means insert a newline after POS but stay
|
||||
in the same place.
|
||||
|
||||
Instead of the (CHAR . WHERE) form, a rule can also be just a
|
||||
function of a single argument, the character just inserted. It
|
||||
should return a value compatible with WHERE if the rule matches,
|
||||
or nil if it doesn't match.
|
||||
|
||||
If multiple rules match, only first one is executed.")
|
||||
|
||||
(defun electric-layout-post-self-insert-function ()
|
||||
(let* ((rule (cdr (assq last-command-event electric-layout-rules)))
|
||||
pos)
|
||||
(when electric-layout-mode
|
||||
(electric-layout-post-self-insert-function-1)))
|
||||
|
||||
;; for edebug's sake, a separate function
|
||||
(defun electric-layout-post-self-insert-function-1 ()
|
||||
(let* (pos
|
||||
probe
|
||||
(rules electric-layout-rules)
|
||||
(rule
|
||||
(catch 'done
|
||||
(while (setq probe (pop rules))
|
||||
(cond ((and (consp probe)
|
||||
(eq (car probe) last-command-event))
|
||||
(throw 'done (cdr probe)))
|
||||
((functionp probe)
|
||||
(let ((res
|
||||
(save-excursion
|
||||
(goto-char
|
||||
(or pos (setq pos (electric--after-char-pos))))
|
||||
(funcall probe last-command-event))))
|
||||
(when res (throw 'done res)))))))))
|
||||
(when (and rule
|
||||
(setq pos (electric--after-char-pos))
|
||||
(or pos (setq pos (electric--after-char-pos)))
|
||||
;; Not in a string or comment.
|
||||
(not (nth 8 (save-excursion (syntax-ppss pos)))))
|
||||
(let ((end (point-marker))
|
||||
(sym (if (functionp rule) (funcall rule) rule)))
|
||||
(set-marker-insertion-type end (not (eq sym 'after-stay)))
|
||||
(goto-char pos)
|
||||
(pcase sym
|
||||
;; FIXME: we used `newline' down here which called
|
||||
;; self-insert-command and ran post-self-insert-hook recursively.
|
||||
;; It happened to make electric-indent-mode work automatically with
|
||||
;; electric-layout-mode (at the cost of re-indenting lines
|
||||
;; multiple times), but I'm not sure it's what we want.
|
||||
;;
|
||||
;; FIXME: check eolp before inserting \n?
|
||||
('before (goto-char (1- pos)) (skip-chars-backward " \t")
|
||||
(unless (bolp) (insert "\n")))
|
||||
('after (insert "\n"))
|
||||
('after-stay (save-excursion
|
||||
(let ((electric-layout-rules nil))
|
||||
(newline 1 t))))
|
||||
('around (save-excursion
|
||||
(goto-char (1- pos)) (skip-chars-backward " \t")
|
||||
(unless (bolp) (insert "\n")))
|
||||
(insert "\n"))) ; FIXME: check eolp before inserting \n?
|
||||
(goto-char end)))))
|
||||
(goto-char pos)
|
||||
(when (functionp rule) (setq rule (funcall rule)))
|
||||
(dolist (sym (if (symbolp rule) (list rule) rule))
|
||||
(let* ((nl-after
|
||||
(lambda ()
|
||||
;; FIXME: we use `newline', which calls
|
||||
;; `self-insert-command' and ran
|
||||
;; `post-self-insert-hook' recursively. It
|
||||
;; happened to make `electric-indent-mode' work
|
||||
;; automatically with `electric-layout-mode' (at
|
||||
;; the cost of re-indenting lines multiple times),
|
||||
;; but I'm not sure it's what we want.
|
||||
;;
|
||||
;; FIXME: when `newline'ing, we exceptionally
|
||||
;; prevent a specific behaviour of
|
||||
;; `eletric-pair-mode', that of opening an extra
|
||||
;; newline between newly inserted matching paris.
|
||||
;; In theory that behaviour should be provided by
|
||||
;; `electric-layout-mode' instead, which should be
|
||||
;; possible given the current API.
|
||||
;;
|
||||
;; FIXME: check eolp before inserting \n?
|
||||
(let ((electric-layout-mode nil)
|
||||
(electric-pair-open-newline-between-pairs nil))
|
||||
(newline 1 t))))
|
||||
(nl-before (lambda ()
|
||||
(save-excursion
|
||||
(goto-char (1- pos)) (skip-chars-backward " \t")
|
||||
(unless (bolp) (funcall nl-after))))))
|
||||
(pcase sym
|
||||
('before (funcall nl-before))
|
||||
('after (funcall nl-after))
|
||||
('after-stay (save-excursion (funcall nl-after)))
|
||||
('around (funcall nl-before) (funcall nl-after))))))))
|
||||
|
||||
(put 'electric-layout-post-self-insert-function 'priority 40)
|
||||
|
||||
|
@ -419,6 +465,19 @@ The variable `electric-layout-rules' says when and how to insert newlines."
|
|||
(remove-hook 'post-self-insert-hook
|
||||
#'electric-layout-post-self-insert-function))))
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode electric-layout-local-mode
|
||||
"Toggle `electric-layout-mode' only in this buffer."
|
||||
:variable (buffer-local-value 'electric-layout-mode (current-buffer))
|
||||
(cond
|
||||
((eq electric-layout-mode (default-value 'electric-layout-mode))
|
||||
(kill-local-variable 'electric-layout-mode))
|
||||
((not (default-value 'electric-layout-mode))
|
||||
;; Locally enabled, but globally disabled.
|
||||
(electric-layout-mode 1) ; Setup the hooks.
|
||||
(setq-default electric-layout-mode nil) ; But keep it globally disabled.
|
||||
)))
|
||||
|
||||
;;; Electric quoting.
|
||||
|
||||
(defcustom electric-quote-comment t
|
||||
|
|
|
@ -812,5 +812,109 @@ baz\"\""
|
|||
:bindings '((comment-start . "<!--") (comment-use-syntax . t))
|
||||
:test-in-comments nil :test-in-strings nil)
|
||||
|
||||
|
||||
;;; tests for `electric-layout-mode'
|
||||
|
||||
(ert-deftest electric-layout-int-main-kernel-style ()
|
||||
(ert-with-test-buffer ()
|
||||
(c-mode)
|
||||
(electric-layout-local-mode 1)
|
||||
(electric-pair-local-mode 1)
|
||||
(electric-indent-local-mode 1)
|
||||
(setq-local electric-layout-rules
|
||||
'((?\{ . (after-stay after))))
|
||||
(insert "int main () ")
|
||||
(let ((last-command-event ?\{))
|
||||
(call-interactively (key-binding `[,last-command-event])))
|
||||
(should (equal (buffer-string) "int main () {\n \n}"))))
|
||||
|
||||
(ert-deftest electric-layout-int-main-allman-style ()
|
||||
(ert-with-test-buffer ()
|
||||
(c-mode)
|
||||
(electric-layout-local-mode 1)
|
||||
(electric-pair-local-mode 1)
|
||||
(electric-indent-local-mode 1)
|
||||
(setq-local electric-layout-rules
|
||||
'((?\{ . (before after-stay after))))
|
||||
(insert "int main () ")
|
||||
(let ((last-command-event ?\{))
|
||||
(call-interactively (key-binding `[,last-command-event])))
|
||||
(should (equal (buffer-string) "int main ()\n{\n \n}"))))
|
||||
|
||||
(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)))
|
||||
|
||||
(ert-deftest electric-modes-in-c-mode-with-self-insert-command ()
|
||||
(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
|
||||
'((?\{ . (before after-stay after))))
|
||||
(insert "int main () ")
|
||||
(let ((last-command-event ?\{))
|
||||
(call-interactively (key-binding `[,last-command-event])))
|
||||
(should (equal (buffer-string) "int main ()\n{\n \n}"))))
|
||||
|
||||
(ert-deftest electric-pair-mode-newline-between-parens ()
|
||||
(ert-with-test-buffer ()
|
||||
(plainer-c-mode)
|
||||
(electric-layout-local-mode -1) ;; ensure e-l-m mode is off
|
||||
(electric-pair-local-mode 1)
|
||||
(insert-before-markers "int main () {}")
|
||||
(backward-char 1)
|
||||
(let ((last-command-event ?
))
|
||||
(call-interactively (key-binding `[,last-command-event])))
|
||||
(should (equal (buffer-string) "int main () {\n \n}"))))
|
||||
|
||||
(ert-deftest electric-layout-mode-newline-between-parens-without-e-p-m ()
|
||||
(ert-with-test-buffer ()
|
||||
(plainer-c-mode)
|
||||
(electric-layout-local-mode 1)
|
||||
(electric-pair-local-mode -1) ;; ensure e-p-m mode is off
|
||||
(electric-indent-local-mode 1)
|
||||
(setq-local electric-layout-rules
|
||||
'((?\n
|
||||
.
|
||||
(lambda ()
|
||||
(when (eq (save-excursion
|
||||
(skip-chars-backward "\t\s")
|
||||
(char-before (1- (point))))
|
||||
(matching-paren (char-after)))
|
||||
'(after-stay))))))
|
||||
(insert "int main () {}")
|
||||
(backward-char 1)
|
||||
(let ((last-command-event ?
))
|
||||
(call-interactively (key-binding `[,last-command-event])))
|
||||
(should (equal (buffer-string) "int main () {\n \n}"))))
|
||||
|
||||
(ert-deftest electric-layout-mode-newline-between-parens-without-e-p-m-2 ()
|
||||
(ert-with-test-buffer ()
|
||||
(plainer-c-mode)
|
||||
(electric-layout-local-mode 1)
|
||||
(electric-pair-local-mode -1) ;; ensure e-p-m mode is off
|
||||
(electric-indent-local-mode 1)
|
||||
(setq-local electric-layout-rules
|
||||
'((lambda (char)
|
||||
(when (and
|
||||
(eq char ?\n)
|
||||
(eq (save-excursion
|
||||
(skip-chars-backward "\t\s")
|
||||
(char-before (1- (point))))
|
||||
(matching-paren (char-after))))
|
||||
'(after-stay)))))
|
||||
(insert "int main () {}")
|
||||
(backward-char 1)
|
||||
(let ((last-command-event ?
))
|
||||
(call-interactively (key-binding `[,last-command-event])))
|
||||
(should (equal (buffer-string) "int main () {\n \n}"))))
|
||||
|
||||
(provide 'electric-tests)
|
||||
;;; electric-tests.el ends here
|
||||
|
|
Loading…
Add table
Reference in a new issue