Compare commits

...

10 commits

Author SHA1 Message Date
João Távora
80a19b8b7a Another iteration for an improved electric-layout-mode
* lisp/electric.el (electric-layout-rules): Add nil back to
symbols list.
(electric-layout-post-self-insert-function-1): Call function in
correct position.
2018-12-29 18:34:53 +00:00
João Távora
ba97dad7c4 Tweak electric-layout-mode's API again after Stefan's comments
* lisp/electric.el (electric-layout-rules): Tweak docstring.
(electric-layout-post-self-insert-function-1): MATCHER is always a
char.  Call function with last-command-event.

* test/lisp/electric-tests.el (electric-pair-mode-newline-between-parens)
(electric-layout-mode-newline-between-parens-without-e-p-m): Don't
pass a name to the ERT test buffer.
(electric-layout-mode-newline-between-parens-without-e-p-m-2): New test.
2018-12-28 21:53:12 +00:00
João Távora
3e38509f9a Correctly implement two electric.el tests
* test/lisp/electric-tests.el (electric-pair-mode-newline-between-parens)
(electric-layout-mode-newline-between-parens-without-e-p-m):
Correctly code these two tests.
2018-12-24 12:42:50 +00:00
João Távora
1299027eb2 Clean up description of new semantics of electric-layout-rules
* lisp/electric.el (electric-layout-rules): Describe function
elements.
(electric-layout-post-self-insert-function-1): Fix handling of
function elements.  Describe that e-l-m is good enough to handle
electric-pair-open-newline-between-pairs.
2018-12-24 12:15:46 +00:00
João Távora
1173d5092d Slightly more powerful electric-layout-rules
* lisp/electric.el (electric-layout-rules): Expand slightly.
(electric-layout-post-self-insert-function-1): Accept new rules.

* test/lisp/electric-tests.el (electric-pair-mode-newline-between-parens)
(electric-layout-mode-newline-between-parens-without-e-p-m): New
(failing) tests.
2018-12-23 22:49:55 +00:00
João Távora
fae3e7c0d8 Test electric.el in plainer c-mode without c-electric-{brace,paren}
If cc-mode ever drops/changes its usage of c-electric-{brace,paren}
test that electric pairing, layout and indentation based on
electric.el keep working.

* test/lisp/electric-tests.el
(electric-layout-int-main-kernel-style)
(electric-layout-int-main-allman-style): Change order of after and
after-stay.
(plainer-c-mode): New test mode.
(electric-modes-in-c-mode-with-self-insert-command): New test.
2018-12-23 20:42:17 +00:00
João Távora
e4ef7a2e2a Handle entries of multiple symbols in electric-layout-rules
Instead of allowing multiple rules in electric-layout-rules, allow
rules to specify multiple symbols.  This should be entirely
backward-compatible to existing customizations of that variable.

* lisp/electric.el (electric-layout-rules):  a single entry can
specify multiple symbols.
(electric-layout-post-self-insert-function-1):  rework.

* test/lisp/electric-tests.el: Update tests to work with new semantics
  of electric-layout-rules.
2018-12-23 20:42:17 +00:00
João Távora
e02256cf27 Minor cleanup in new electric-layout-mode after Stefan's feedback
* lisp/electric.el (electric-layout-post-self-insert-function-1):
Update comments and function names.
2018-12-23 20:42:17 +00:00
João Távora
6deb668e72 Rework electric-layout-post-self-insert-function (bug#33794)
This should now fix more problems reported in (bug#33794) regarding
insertion of newlines before and after the opening brace.  Write two
automated tests.

Also provide a new electric-layout-local-mode for testing.

* lisp/electric.el (electric-layout-post-self-insert-function-1):
New function that does the work for
electric-layout-post-self-insert-function-1.
(electric-layout-local-mode): New minor mode.

* test/lisp/electric-tests.el (electric-layout-int-main-kernel-style)
(electric-layout-int-main-allman-style): Add two tests.
2018-12-23 20:42:17 +00:00
João Távora
cb1b4a8c01 Extend electric-layout-mode to handle more complex layouts
Also, have it play nice with electric-pair-mode.

Multiple matching entries in `electric-layout-rules' are executed in
order of appearance.  When inserting a newline in the 'after-stay
rule, ensure electric-pair-open-newline-between-pairs is nil.

Arguably the logic behind electric-pair-open-newline-between-pairs
should be moved to electric-layout-mode, but the current rule-matching
engine doesn't allow for it.  The current solution seems to be good
enough for the situations reported in bug#33794.

* lisp/electric.el (electric-layout-rules): Adjust docstring.
(electric-layout-post-self-insert-function): Loop through rules.  Bind
electric-pair-open-newline-between-pairs to nil when handling
after-stay.
2018-12-23 20:42:17 +00:00
2 changed files with 201 additions and 33 deletions

View file

@ -363,45 +363,96 @@ 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 (MATCHER . WHERE) where MATCHER examines
the state of the buffer after a certain character was inserted
and WHERE specifies where to insert newlines.
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.")
MATCHER is a character CHAR. The rule matches if the character
just inserted was CHAR.
WHERE can be:
* 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 (MATCHER . 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 +470,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

View file

@ -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