Make Electric Pair mode smarter/more useful:

* lisp/electric.el: Pairing/skipping helps preserve
  balance. Autobackspacing behaviour. Opens extra newlines between
  pairs. Skip whitespace before closing delimiters.

* lisp/emacs-lisp/lisp-mode.el (lisp-mode-variables): Use new
  features.

* test/automated/electric-tests.lisp: New file.

* doc/emacs/programs.texi: Describe new features.

* lisp/simple.el: Pass non-nil interactive arg to newline call inside
  newline-and-indent.
This commit is contained in:
João Távora 2013-12-26 22:02:49 +00:00
parent fbcc63a317
commit 3b8d5131a3
10 changed files with 1126 additions and 97 deletions

View file

@ -1,3 +1,7 @@
2013-12-26 João Távora <joaotavora@gmail.com>
* emacs.texi (Matching): Describe new features of Electric Pair
mode.
2013-12-25 Chong Yidong <cyd@gnu.org>
* glossary.texi (Glossary): Define MULE in modern terms.

View file

@ -844,8 +844,34 @@ show-paren-mode}.
Electric Pair mode, a global minor mode, provides a way to easily
insert matching delimiters. Whenever you insert an opening delimiter,
the matching closing delimiter is automatically inserted as well,
leaving point between the two. To toggle Electric Pair mode, type
@kbd{M-x electric-pair-mode}.
leaving point between the two. Conversely, when you insert a closing
delimiter over an existing one, no inserting takes places and that
position is simply skipped over. These variables control additional
features of Electric Pair mode:
@itemize @bullet
@item
@code{electric-pair-preserve-balance}, when non-@code{nil}, makes the
default pairing logic balance out the number of opening and closing
delimiters.
@item
@code{electric-pair-delete-adjacent-pairs}, when non-@code{nil}, makes
backspacing between two adjacent delimiters also automatically delete
the closing delimiter.
@item
@code{electric-pair-open-newline-between-pairs}, when non-@code{nil},
makes inserting inserting a newline between two adjacent pairs also
automatically open and extra newline after point.
@item
@code{electric-skip-whitespace}, when non-@code{nil}, causes the minor
mode to skip whitespace forward before deciding whether to skip over
the closing delimiter.
@end itemize
To toggle Electric Pair mode, type @kbd{M-x electric-pair-mode}.
@node Comments
@section Manipulating Comments

View file

@ -1,3 +1,6 @@
2013-12-26 João Távora <joaotavora@gmail.com>
* NEWS: Describe new features of Electric Pair mode.
2013-12-23 Teodor Zlatanov <tzz@lifelogs.com>
* NEWS: Updated for `gnutls-verify-error', cfengine-mode, and

View file

@ -439,6 +439,42 @@ and `desktop-restore-forces-onscreen' offer further customization.
** Eldoc Mode works properly in the minibuffer.
** Electric Pair mode
*** New `electric-pair-preserve-balance' enabled by default.
Pairing/skipping only kicks in when that help the balance of
parentheses and quotes, i.e. the buffer should end up at least as
balanced as before.
You can further control this behaviour by adjusting the predicates
stored in `electric-pair-inhibit-predicate' and
`electric-pair-skip-self'.
*** New `electric-pair-delete-adjacent-pairs' enabled by default.
In `electric-pair-mode', the commands `backward-delete-char' and
`backward-delete-char-untabify' are now bound to electric variants
that delete the closer when invoked between adjacent pairs.
*** New `electric-pair-open-newline-between-pairs' enabled by default.
In `electric-pair-mode', inserting a newline between adjacent pairs
opens an extra newline after point, which is indented if
`electric-indent-mode' is also set.
*** New `electric-pair-skip-whitespace' enabled by default.
Controls if skipping over closing delimiters should jump over any
whitespace slack. Setting it to `chomp' makes it delete this
whitespace. See also the variable
`electric-pair-skip-whitespace-chars'.
*** New variables control the pairing in strings and comments.
You can customize `electric-pair-text-pairs' and
`electric-pair-text-syntax-table' to tweak pairing behaviour inside
strings and comments.
** EPA
*** New option `epa-mail-aliases'.

View file

@ -1,3 +1,25 @@
2013-12-26 João Távora <joaotavora@gmail.com>
* electric.el (electric-pair-mode): More flexible engine for skip-
and inhibit predicates, new options for pairing-related
functionality.
(electric-pair-preserve-balance): Pair/skip parentheses and quotes
if that keeps or improves their balance in buffers.
(electric-pair-delete-adjacent-pairs): Delete the pair when
backspacing over adjacent matched delimiters.
(electric-pair-open-extra-newline): Open extra newline when
inserting newlines between adjacent matched delimiters.
(electric--sort-post-self-insertion-hook): Sort
post-self-insert-hook according to priority values when
minor-modes are activated.
* simple.el (newline-and-indent): Call newline with interactive
set to t.
(blink-paren-post-self-insert-function): Set priority to 100.
* emacs-lisp/lisp-mode.el (lisp-mode-variables): Use
electric-pair-text-pairs to pair backtick-and-quote in strings and
comments. Locally set electric-pair-skip-whitespace to 'chomp and
electric-pair-open-newline-between-pairs to nil.
2013-12-26 Fabián Ezequiel Gallina <fgallina@gnu.org>
* progmodes/python.el: Use lexical-binding.

View file

@ -187,6 +187,17 @@ Returns nil when we can't find this char."
(eq (char-before) last-command-event)))))
pos)))
(defun electric--sort-post-self-insertion-hook ()
"Ensure order of electric functions in `post-self-insertion-hook'.
Hooks in this variable interact in non-trivial ways, so a
relative order must be maintained within it."
(setq-default post-self-insert-hook
(sort (default-value 'post-self-insert-hook)
#'(lambda (fn1 fn2)
(< (or (get fn1 'priority) 0)
(or (get fn2 'priority) 0))))))
;;; Electric indentation.
;; Autoloading variables is generally undesirable, but major modes
@ -267,6 +278,8 @@ mode set `electric-indent-inhibit', but this can be used as a workaround.")
(> pos (line-beginning-position)))
(indent-according-to-mode)))))
(put 'electric-indent-post-self-insert-function 'priority 60)
(defun electric-indent-just-newline (arg)
"Insert just a newline, without any auto-indentation."
(interactive "*P")
@ -295,20 +308,9 @@ insert a character from `electric-indent-chars'."
#'electric-indent-post-self-insert-function))
(when (eq (lookup-key global-map [?\C-j]) 'newline-and-indent)
(define-key global-map [?\C-j] 'electric-indent-just-newline))
;; post-self-insert-hooks interact in non-trivial ways.
;; It turns out that electric-indent-mode generally works better if run
;; late, but still before blink-paren.
(add-hook 'post-self-insert-hook
#'electric-indent-post-self-insert-function
'append)
;; FIXME: Ugly!
(let ((bp (memq #'blink-paren-post-self-insert-function
(default-value 'post-self-insert-hook))))
(when (memq #'electric-indent-post-self-insert-function bp)
(setcar bp #'electric-indent-post-self-insert-function)
(setcdr bp (cons #'blink-paren-post-self-insert-function
(delq #'electric-indent-post-self-insert-function
(cdr bp))))))))
#'electric-indent-post-self-insert-function)
(electric--sort-post-self-insertion-hook)))
;;;###autoload
(define-minor-mode electric-indent-local-mode
@ -327,32 +329,163 @@ insert a character from `electric-indent-chars'."
(defcustom electric-pair-pairs
'((?\" . ?\"))
"Alist of pairs that should be used regardless of major mode."
"Alist of pairs that should be used regardless of major mode.
Pairs of delimiters in this list are a fallback in case they have
no syntax relevant to `electric-pair-mode' in the mode's syntax
table.
See also the variable `electric-pair-text-pairs'."
:version "24.1"
:type '(repeat (cons character character)))
(defcustom electric-pair-skip-self t
(defcustom electric-pair-text-pairs
'((?\" . ?\" ))
"Alist of pairs that should always be used in comments and strings.
Pairs of delimiters in this list are a fallback in case they have
no syntax relevant to `electric-pair-mode' in the syntax table
defined in `electric-pair-text-syntax-table'"
:version "24.4"
:type '(repeat (cons character character)))
(defcustom electric-pair-skip-self #'electric-pair-default-skip-self
"If non-nil, skip char instead of inserting a second closing paren.
When inserting a closing paren character right before the same character,
just skip that character instead, so that hitting ( followed by ) results
in \"()\" rather than \"())\".
This can be convenient for people who find it easier to hit ) than C-f."
This can be convenient for people who find it easier to hit ) than C-f.
Can also be a function of one argument (the closer char just
inserted), in which case that function's return value is
considered instead."
:version "24.1"
:type 'boolean)
:type '(choice
(const :tag "Never skip" nil)
(const :tag "Help balance" electric-pair-default-skip-self)
(const :tag "Always skip" t)
function))
(defcustom electric-pair-inhibit-predicate
#'electric-pair-default-inhibit
"Predicate to prevent insertion of a matching pair.
The function is called with a single char (the opening char just inserted).
If it returns non-nil, then `electric-pair-mode' will not insert a matching
closer."
:version "24.4"
:type '(choice
(const :tag "Default" electric-pair-default-inhibit)
(const :tag "Conservative" electric-pair-conservative-inhibit)
(const :tag "Help balance" electric-pair-default-inhibit)
(const :tag "Always pair" ignore)
function))
(defun electric-pair-default-inhibit (char)
(defcustom electric-pair-preserve-balance t
"Non-nil if default pairing and skipping should help balance parentheses.
The default values of `electric-pair-inhibit-predicate' and
`electric-pair-skip-self' check this variable before delegating to other
predicates reponsible for making decisions on whether to pair/skip some
characters based on the actual state of the buffer's parenthesis and
quotes."
:version "24.4"
:type 'boolean)
(defcustom electric-pair-delete-adjacent-pairs t
"If non-nil, backspacing an open paren also deletes adjacent closer.
Can also be a function of no arguments, in which case that function's
return value is considered instead."
:version "24.4"
:type '(choice
(const :tag "Yes" t)
(const :tag "No" nil)
function))
(defcustom electric-pair-open-newline-between-pairs t
"If non-nil, a newline between adjacent parentheses opens an extra one.
Can also be a function of no arguments, in which case that function's
return value is considered instead."
:version "24.4"
:type '(choice
(const :tag "Yes" t)
(const :tag "No" nil)
function))
(defcustom electric-pair-skip-whitespace t
"If non-nil skip whitespace when skipping over closing parens.
The specific kind of whitespace skipped is given by the variable
`electric-pair-skip-whitespace-chars'.
The symbol `chomp' specifies that the skipped-over whitespace
should be deleted.
Can also be a function of no arguments, in which case that function's
return value is considered instead."
:version "24.4"
:type '(choice
(const :tag "Yes, jump over whitespace" t)
(const :tag "Yes, and delete whitespace" 'chomp)
(const :tag "No, no whitespace skipping" nil)
function))
(defcustom electric-pair-skip-whitespace-chars (list ?\t ?\s ?\n)
"Whitespace characters considered by `electric-pair-skip-whitespace'."
:version "24.4"
:type '(choice (set (const :tag "Space" ?\s)
(const :tag "Tab" ?\t)
(const :tag "Newline" ?\n))
(list character)))
(defun electric-pair--skip-whitespace ()
"Skip whitespace forward, not crossing comment or string boundaries."
(let ((saved (point))
(string-or-comment (nth 8 (syntax-ppss))))
(skip-chars-forward (apply #'string electric-pair-skip-whitespace-chars))
(unless (eq string-or-comment (nth 8 (syntax-ppss)))
(goto-char saved))))
(defvar electric-pair-text-syntax-table prog-mode-syntax-table
"Syntax table used when pairing inside comments and strings.
`electric-pair-mode' considers this syntax table only when point in inside
quotes or comments. If lookup fails here, `electric-pair-text-pairs' will
be considered.")
(defun electric-pair-backward-delete-char (n &optional killflag untabify)
"Delete characters backward, and maybe also two adjacent paired delimiters.
Remaining behaviour is given by `backward-delete-char' or, if UNTABIFY is
non-nil, `backward-delete-char-untabify'."
(interactive "*p\nP")
(let* ((prev (char-before))
(next (char-after))
(syntax-info (electric-pair-syntax-info prev))
(syntax (car syntax-info))
(pair (cadr syntax-info)))
(when (and (if (functionp electric-pair-delete-adjacent-pairs)
(funcall electric-pair-delete-adjacent-pairs)
electric-pair-delete-adjacent-pairs)
next
(memq syntax '(?\( ?\" ?\$))
(eq pair next))
(delete-char 1 killflag))
(if untabify
(backward-delete-char-untabify n killflag)
(backward-delete-char n killflag))))
(defun electric-pair-backward-delete-char-untabify (n &optional killflag)
"Delete characters backward, and maybe also two adjacent paired delimiters.
Remaining behaviour is given by `backward-delete-char-untabify'."
(interactive "*p\nP")
(electric-pair-backward-delete-char n killflag t))
(defun electric-pair-conservative-inhibit (char)
(or
;; I find it more often preferable not to pair when the
;; same char is next.
@ -363,14 +496,40 @@ closer."
;; I also find it often preferable not to pair next to a word.
(eq (char-syntax (following-char)) ?w)))
(defun electric-pair-syntax (command-event)
(let ((x (assq command-event electric-pair-pairs)))
(defun electric-pair-syntax-info (command-event)
"Calculate a list (SYNTAX PAIR UNCONDITIONAL STRING-OR-COMMENT-START).
SYNTAX is COMMAND-EVENT's syntax character. PAIR is
COMMAND-EVENT's pair. UNCONDITIONAL indicates the variables
`electric-pair-pairs' or `electric-pair-text-pairs' were used to
lookup syntax. STRING-OR-COMMENT-START indicates that point is
inside a comment of string."
(let* ((pre-string-or-comment (nth 8 (save-excursion
(syntax-ppss (1- (point))))))
(post-string-or-comment (nth 8 (syntax-ppss (point))))
(string-or-comment (and post-string-or-comment
pre-string-or-comment))
(table (if string-or-comment
electric-pair-text-syntax-table
(syntax-table)))
(table-syntax-and-pair (with-syntax-table table
(list (char-syntax command-event)
(or (matching-paren command-event)
command-event))))
(fallback (if string-or-comment
(append electric-pair-text-pairs
electric-pair-pairs)
electric-pair-pairs))
(direct (assq command-event fallback))
(reverse (rassq command-event fallback)))
(cond
(x (if (eq (car x) (cdr x)) ?\" ?\())
((rassq command-event electric-pair-pairs) ?\))
((nth 8 (syntax-ppss))
(with-syntax-table text-mode-syntax-table (char-syntax command-event)))
(t (char-syntax command-event)))))
((memq (car table-syntax-and-pair)
'(?\" ?\( ?\) ?\$))
(append table-syntax-and-pair (list nil string-or-comment)))
(direct (if (eq (car direct) (cdr direct))
(list ?\" command-event t string-or-comment)
(list ?\( (cdr direct) t string-or-comment)))
(reverse (list ?\) (car reverse) t string-or-comment)))))
(defun electric-pair--insert (char)
(let ((last-command-event char)
@ -378,56 +537,297 @@ closer."
(electric-pair-mode nil))
(self-insert-command 1)))
(defun electric-pair--syntax-ppss (&optional pos where)
"Like `syntax-ppss', but sometimes fallback to `parse-partial-sexp'.
WHERE is list defaulting to '(string comment) and indicates
when to fallback to `parse-partial-sexp'."
(let* ((pos (or pos (point)))
(where (or where '(string comment)))
(quick-ppss (syntax-ppss))
(quick-ppss-at-pos (syntax-ppss pos)))
(if (or (and (nth 3 quick-ppss) (memq 'string where))
(and (nth 4 quick-ppss) (memq 'comment where)))
(with-syntax-table electric-pair-text-syntax-table
(parse-partial-sexp (1+ (nth 8 quick-ppss)) pos))
;; HACK! cc-mode apparently has some `syntax-ppss' bugs
(if (memq major-mode '(c-mode c++ mode))
(parse-partial-sexp (point-min) pos)
quick-ppss-at-pos))))
;; Balancing means controlling pairing and skipping of parentheses so
;; that, if possible, the buffer ends up at least as balanced as
;; before, if not more. The algorithm is slightly complex because some
;; situations like "()))" need pairing to occur at the end but not at
;; the beginning. Balancing should also happen independently for
;; different types of parentheses, so that having your {}'s unbalanced
;; doesn't keep `electric-pair-mode' from balancing your ()'s and your
;; []'s.
(defun electric-pair--balance-info (direction string-or-comment)
"Examine lists forward or backward according to DIRECTIONS's sign.
STRING-OR-COMMENT is info suitable for running `parse-partial-sexp'.
Return a cons of two descritions (MATCHED-P . PAIR) for the
innermost and outermost lists that enclose point. The outermost
list enclosing point is either the first top-level or first
mismatched list found by uplisting.
If the outermost list is matched, don't rely on its PAIR. If
point is not enclosed by any lists, return ((T) (T))."
(let* (innermost
outermost
(table (if string-or-comment
electric-pair-text-syntax-table
(syntax-table)))
(at-top-level-or-equivalent-fn
;; called when `scan-sexps' ran perfectly, when when it
;; found a parenthesis pointing in the direction of
;; travel. Also when travel started inside a comment and
;; exited it
#'(lambda ()
(setq outermost (list t))
(unless innermost
(setq innermost (list t)))))
(ended-prematurely-fn
;; called when `scan-sexps' crashed against a parenthesis
;; pointing opposite the direction of travel. After
;; traversing that character, the idea is to travel one sexp
;; in the opposite direction looking for a matching
;; delimiter.
#'(lambda ()
(let* ((pos (point))
(matched
(save-excursion
(cond ((< direction 0)
(condition-case nil
(eq (char-after pos)
(with-syntax-table table
(matching-paren
(char-before
(scan-sexps (point) 1)))))
(scan-error nil)))
(t
;; In this case, no need to use
;; `scan-sexps', we can use some
;; `electric-pair--syntax-ppss' in this
;; case (which uses the quicker
;; `syntax-ppss' in some cases)
(let* ((ppss (electric-pair--syntax-ppss
(1- (point))))
(start (car (last (nth 9 ppss))))
(opener (char-after start)))
(and start
(eq (char-before pos)
(or (with-syntax-table table
(matching-paren opener))
opener))))))))
(actual-pair (if (> direction 0)
(char-before (point))
(char-after (point)))))
(unless innermost
(setq innermost (cons matched actual-pair)))
(unless matched
(setq outermost (cons matched actual-pair)))))))
(save-excursion
(while (not outermost)
(condition-case err
(with-syntax-table table
(scan-sexps (point) (if (> direction 0)
(point-max)
(- (point-max))))
(funcall at-top-level-or-equivalent-fn))
(scan-error
(cond ((or
;; some error happened and it is not of the "ended
;; prematurely" kind"...
(not (string-match "ends prematurely" (nth 1 err)))
;; ... or we were in a comment and just came out of
;; it.
(and string-or-comment
(not (nth 8 (syntax-ppss)))))
(funcall at-top-level-or-equivalent-fn))
(t
;; exit the sexp
(goto-char (nth 3 err))
(funcall ended-prematurely-fn)))))))
(cons innermost outermost)))
(defun electric-pair--looking-at-unterminated-string-p (char)
"Say if following string starts with CHAR and is unterminated."
;; FIXME: ugly/naive
(save-excursion
(skip-chars-forward (format "^%c" char))
(while (not (zerop (% (save-excursion (skip-syntax-backward "\\")) 2)))
(unless (eobp)
(forward-char 1)
(skip-chars-forward (format "^%c" char))))
(and (not (eobp))
(condition-case err
(progn (forward-sexp) nil)
(scan-error t)))))
(defun electric-pair--inside-string-p (char)
"Say if point is inside a string started by CHAR.
A comments text is parsed with `electric-pair-text-syntax-table'.
Also consider strings within comments, but not strings within
strings."
;; FIXME: could also consider strings within strings by examining
;; delimiters.
(let* ((ppss (electric-pair--syntax-ppss (point) '(comment))))
(memq (nth 3 ppss) (list t char))))
(defun electric-pair-inhibit-if-helps-balance (char)
"Return non-nil if auto-pairing of CHAR would hurt parentheses' balance.
Works by first removing the character from the buffer, then doing
some list calculations, finally restoring the situation as if nothing
happened."
(pcase (electric-pair-syntax-info char)
(`(,syntax ,pair ,_ ,s-or-c)
(unwind-protect
(progn
(delete-char -1)
(cond ((eq ?\( syntax)
(let* ((pair-data
(electric-pair--balance-info 1 s-or-c))
(innermost (car pair-data))
(outermost (cdr pair-data)))
(cond ((car outermost)
nil)
(t
(eq (cdr outermost) pair)))))
((eq syntax ?\")
(electric-pair--looking-at-unterminated-string-p char))))
(insert-char char)))))
(defun electric-pair-skip-if-helps-balance (char)
"Return non-nil if skipping CHAR would benefit parentheses' balance.
Works by first removing the character from the buffer, then doing
some list calculations, finally restoring the situation as if nothing
happened."
(pcase (electric-pair-syntax-info char)
(`(,syntax ,pair ,_ ,s-or-c)
(unwind-protect
(progn
(delete-char -1)
(cond ((eq syntax ?\))
(let* ((pair-data
(electric-pair--balance-info
-1 s-or-c))
(innermost (car pair-data))
(outermost (cdr pair-data)))
(and
(cond ((car outermost)
(car innermost))
((car innermost)
(not (eq (cdr outermost) pair)))))))
((eq syntax ?\")
(electric-pair--inside-string-p char))))
(insert-char char)))))
(defun electric-pair-default-skip-self (char)
(if electric-pair-preserve-balance
(electric-pair-skip-if-helps-balance char)
t))
(defun electric-pair-default-inhibit (char)
(if electric-pair-preserve-balance
(electric-pair-inhibit-if-helps-balance char)
(electric-pair-conservative-inhibit char)))
(defun electric-pair-post-self-insert-function ()
(let* ((pos (and electric-pair-mode (electric--after-char-pos)))
(syntax (and pos (electric-pair-syntax last-command-event)))
(closer (if (eq syntax ?\()
(cdr (or (assq last-command-event electric-pair-pairs)
(aref (syntax-table) last-command-event)))
last-command-event)))
(cond
((null pos) nil)
;; Wrap a pair around the active region.
((and (memq syntax '(?\( ?\" ?\$)) (use-region-p))
;; FIXME: To do this right, we'd need a post-self-insert-function
;; so we could add-function around it and insert the closer after
;; all the rest of the hook has run.
(if (>= (mark) (point))
(goto-char (mark))
;; We already inserted the open-paren but at the end of the
;; region, so we have to remove it and start over.
(delete-region (1- pos) (point))
(save-excursion
(goto-char (mark))
(electric-pair--insert last-command-event)))
;; Since we're right after the closer now, we could tell the rest of
;; post-self-insert-hook that we inserted `closer', but then we'd get
;; blink-paren to kick in, which is annoying.
;;(setq last-command-event closer)
(insert closer))
;; Backslash-escaped: no pairing, no skipping.
((save-excursion
(goto-char (1- pos))
(not (zerop (% (skip-syntax-backward "\\") 2))))
nil)
;; Skip self.
((and (memq syntax '(?\) ?\" ?\$))
electric-pair-skip-self
(eq (char-after pos) last-command-event))
;; This is too late: rather than insert&delete we'd want to only skip (or
;; insert in overwrite mode). The difference is in what goes in the
;; undo-log and in the intermediate state which might be visible to other
;; post-self-insert-hook. We'll just have to live with it for now.
(delete-char 1))
;; Insert matching pair.
((not (or (not (memq syntax `(?\( ?\" ?\$)))
overwrite-mode
(funcall electric-pair-inhibit-predicate last-command-event)))
(save-excursion (electric-pair--insert closer))))))
(skip-whitespace-info))
(pcase (electric-pair-syntax-info last-command-event)
(`(,syntax ,pair ,unconditional ,_)
(cond
((null pos) nil)
;; Wrap a pair around the active region.
;;
((and (memq syntax '(?\( ?\) ?\" ?\$)) (use-region-p))
;; FIXME: To do this right, we'd need a post-self-insert-function
;; so we could add-function around it and insert the closer after
;; all the rest of the hook has run.
(if (or (eq syntax ?\")
(and (eq syntax ?\))
(>= (point) (mark)))
(and (not (eq syntax ?\)))
(>= (mark) (point))))
(save-excursion
(goto-char (mark))
(electric-pair--insert pair))
(delete-region pos (1- pos))
(electric-pair--insert pair)
(goto-char (mark))
(electric-pair--insert last-command-event)))
;; Backslash-escaped: no pairing, no skipping.
((save-excursion
(goto-char (1- pos))
(not (zerop (% (skip-syntax-backward "\\") 2))))
nil)
;; Skip self.
((and (memq syntax '(?\) ?\" ?\$))
(and (or unconditional
(if (functionp electric-pair-skip-self)
(funcall electric-pair-skip-self last-command-event)
electric-pair-skip-self))
(save-excursion
(when (setq skip-whitespace-info
(if (functionp electric-pair-skip-whitespace)
(funcall electric-pair-skip-whitespace)
electric-pair-skip-whitespace))
(electric-pair--skip-whitespace))
(eq (char-after) last-command-event))))
;; This is too late: rather than insert&delete we'd want to only
;; skip (or insert in overwrite mode). The difference is in what
;; goes in the undo-log and in the intermediate state which might
;; be visible to other post-self-insert-hook. We'll just have to
;; live with it for now.
(when skip-whitespace-info
(electric-pair--skip-whitespace))
(delete-region (1- pos) (if (eq skip-whitespace-info 'chomp)
(point)
pos))
(forward-char))
;; Insert matching pair.
((and (memq syntax `(?\( ?\" ?\$))
(not overwrite-mode)
(or unconditional
(not (funcall electric-pair-inhibit-predicate
last-command-event))))
(save-excursion (electric-pair--insert pair)))))
(t
(when (and (if (functionp electric-pair-open-newline-between-pairs)
(funcall electric-pair-open-newline-between-pairs)
electric-pair-open-newline-between-pairs)
(eq last-command-event ?\n)
(not (eobp))
(eq (save-excursion
(skip-chars-backward "\t\s")
(char-before (1- (point))))
(matching-paren (char-after))))
(save-excursion (newline 1 t)))))))
(put 'electric-pair-post-self-insert-function 'priority 20)
(defun electric-pair-will-use-region ()
(and (use-region-p)
(memq (electric-pair-syntax last-command-event) '(?\( ?\" ?\$))))
(memq (car (electric-pair-syntax-info last-command-event))
'(?\( ?\) ?\" ?\$))))
(defvar electric-pair-mode-map
(let ((map (make-sparse-keymap)))
(define-key map [remap backward-delete-char-untabify]
'electric-pair-backward-delete-char-untabify)
(define-key map [remap backward-delete-char]
'electric-pair-backward-delete-char)
(define-key map [remap delete-backward-char]
'electric-pair-backward-delete-char)
map)
"Keymap used by `electric-pair-mode'.")
;;;###autoload
(define-minor-mode electric-pair-mode
@ -438,29 +838,33 @@ the mode if ARG is omitted or nil.
Electric Pair mode is a global minor mode. When enabled, typing
an open parenthesis automatically inserts the corresponding
closing parenthesis. \(Likewise for brackets, etc.)
See options `electric-pair-pairs' and `electric-pair-skip-self'."
closing parenthesis. \(Likewise for brackets, etc.)."
:global t :group 'electricity
(if electric-pair-mode
(progn
(add-hook 'post-self-insert-hook
#'electric-pair-post-self-insert-function)
(electric--sort-post-self-insertion-hook)
(add-hook 'self-insert-uses-region-functions
#'electric-pair-will-use-region))
(remove-hook 'post-self-insert-hook
#'electric-pair-post-self-insert-function)
(remove-hook 'self-insert-uses-region-functions
#'electric-pair-will-use-region)))
#'electric-pair-will-use-region)))
;;; Electric newlines after/before/around some chars.
(defvar electric-layout-rules '()
(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', or a function of no
arguments that returns one of those symbols.")
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.
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.")
(defun electric-layout-post-self-insert-function ()
(let* ((rule (cdr (assq last-command-event electric-layout-rules)))
@ -469,23 +873,32 @@ arguments that returns one of those symbols.")
(setq pos (electric--after-char-pos))
;; Not in a string or comment.
(not (nth 8 (save-excursion (syntax-ppss pos)))))
(let ((end (copy-marker (point) t)))
(let ((end (copy-marker (point)))
(sym (if (functionp rule) (funcall rule) rule)))
(set-marker-insertion-type end (not (eq sym 'after-stay)))
(goto-char pos)
(pcase (if (functionp rule) (funcall rule) rule)
(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")) ; FIXME: check eolp before inserting \n?
(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 (1- pos)) (skip-chars-backward " \t")
(unless (bolp) (insert "\n")))
(insert "\n"))) ; FIXME: check eolp before inserting \n?
(goto-char end)))))
(put 'electric-layout-post-self-insert-function 'priority 40)
;;;###autoload
(define-minor-mode electric-layout-mode
"Automatically insert newlines around some chars.
@ -494,11 +907,13 @@ positive, and disable it otherwise. If called from Lisp, enable
the mode if ARG is omitted or nil.
The variable `electric-layout-rules' says when and how to insert newlines."
:global t :group 'electricity
(if electric-layout-mode
(add-hook 'post-self-insert-hook
#'electric-layout-post-self-insert-function)
(remove-hook 'post-self-insert-hook
#'electric-layout-post-self-insert-function)))
(cond (electric-layout-mode
(add-hook 'post-self-insert-hook
#'electric-layout-post-self-insert-function)
(electric--sort-post-self-insertion-hook))
(t
(remove-hook 'post-self-insert-hook
#'electric-layout-post-self-insert-function))))
(provide 'electric)

View file

@ -472,7 +472,13 @@ font-lock keywords will not be case sensitive."
(font-lock-mark-block-function . mark-defun)
(font-lock-syntactic-face-function
. lisp-font-lock-syntactic-face-function)))
(setq-local prettify-symbols-alist lisp--prettify-symbols-alist))
(setq-local prettify-symbols-alist lisp--prettify-symbols-alist)
;; electric
(when elisp
(setq-local electric-pair-text-pairs
(cons '(?\` . ?\') electric-pair-text-pairs)))
(setq-local electric-pair-skip-whitespace 'chomp)
(setq-local electric-pair-open-newline-between-pairs nil))
(defun lisp-outline-level ()
"Lisp mode `outline-level' function."

View file

@ -610,7 +610,7 @@ In some text modes, where TAB inserts a tab, this command indents to the
column specified by the function `current-left-margin'."
(interactive "*")
(delete-horizontal-space t)
(newline)
(newline 1 t)
(indent-according-to-mode))
(defun reindent-then-newline-and-indent ()
@ -6448,10 +6448,14 @@ More precisely, a char with closeparen syntax is self-inserted.")
(point))))))
(funcall blink-paren-function)))
(put 'blink-paren-post-self-insert-function 'priority 100)
(add-hook 'post-self-insert-hook #'blink-paren-post-self-insert-function
;; Most likely, this hook is nil, so this arg doesn't matter,
;; but I use it as a reminder that this function usually
;; likes to be run after others since it does `sit-for'.
;; likes to be run after others since it does
;; `sit-for'. That's also the reason it get a `priority' prop
;; of 100.
'append)
;; This executes C-g typed while Emacs is waiting for a command.

View file

@ -1,3 +1,6 @@
2013-12-26 João Távora <joaotavora@gmail.com>
* automated/electric-tests.el: Add tests for Electric Pair mode.
2013-12-25 Fabián Ezequiel Gallina <fgallina@gnu.org>
* automated/python-tests.el

View file

@ -0,0 +1,510 @@
;;; electric-tests.el --- tests for electric.el
;; Copyright (C) 2013 João Távora
;; Author: João Távora <joaotavora@gmail.com>
;; Keywords:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary: Tests for Electric Pair mode.
;;; TODO: Add tests for other Electric-* functionality
;;
;;; Code:
(require 'ert)
(require 'ert-x)
(require 'electric)
(require 'cl-lib)
(defun call-with-saved-electric-modes (fn)
(let ((saved-electric (if electric-pair-mode 1 -1))
(saved-layout (if electric-layout-mode 1 -1))
(saved-indent (if electric-indent-mode 1 -1)))
(electric-pair-mode -1)
(electric-layout-mode -1)
(electric-indent-mode -1)
(unwind-protect
(funcall fn)
(electric-pair-mode saved-electric)
(electric-indent-mode saved-indent)
(electric-layout-mode saved-layout))))
(defmacro save-electric-modes (&rest body)
(declare (indent defun) (debug t))
`(call-with-saved-electric-modes #'(lambda () ,@body)))
(defun electric-pair-test-for (fixture where char expected-string
expected-point mode bindings fixture-fn)
(with-temp-buffer
(funcall mode)
(insert fixture)
(save-electric-modes
(let ((last-command-event char))
(goto-char where)
(funcall fixture-fn)
(cl-progv
(mapcar #'car bindings)
(mapcar #'cdr bindings)
(self-insert-command 1))))
(should (equal (buffer-substring-no-properties (point-min) (point-max))
expected-string))
(should (equal (point)
expected-point))))
(eval-when-compile
(defun electric-pair-define-test-form (name fixture
char
pos
expected-string
expected-point
skip-pair-string
prefix
suffix
extra-desc
mode
bindings
fixture-fn)
(let* ((expected-string-and-point
(if skip-pair-string
(with-temp-buffer
(cl-progv
;; FIXME: avoid `eval'
(mapcar #'car (eval bindings))
(mapcar #'cdr (eval bindings))
(funcall mode)
(insert fixture)
(goto-char (1+ pos))
(insert char)
(cond ((eq (aref skip-pair-string pos)
?p)
(insert (cadr (electric-pair-syntax-info char)))
(backward-char 1))
((eq (aref skip-pair-string pos)
?s)
(delete-char -1)
(forward-char 1)))
(list
(buffer-substring-no-properties (point-min) (point-max))
(point))))
(list expected-string expected-point)))
(expected-string (car expected-string-and-point))
(expected-point (cadr expected-string-and-point))
(fixture (format "%s%s%s" prefix fixture suffix))
(expected-string (format "%s%s%s" prefix expected-string suffix))
(expected-point (+ (length prefix) expected-point))
(pos (+ (length prefix) pos)))
`(ert-deftest ,(intern (format "electric-pair-%s-at-point-%s-in-%s%s"
name
(1+ pos)
mode
extra-desc))
()
,(format "With \"%s\", try input %c at point %d. \
Should %s \"%s\" and point at %d"
fixture
char
(1+ pos)
(if (string= fixture expected-string)
"stay"
"become")
(replace-regexp-in-string "\n" "\\\\n" expected-string)
expected-point)
(electric-pair-test-for ,fixture
,(1+ pos)
,char
,expected-string
,expected-point
',mode
,bindings
,fixture-fn)))))
(cl-defmacro define-electric-pair-test
(name fixture
input
&key
skip-pair-string
expected-string
expected-point
bindings
(modes '(quote (emacs-lisp-mode ruby-mode c++-mode)))
(test-in-comments t)
(test-in-strings t)
(test-in-code t)
(fixture-fn #'(lambda ()
(electric-pair-mode 1))))
`(progn
,@(cl-loop
for mode in (eval modes) ;FIXME: avoid `eval'
append
(cl-loop
for (prefix suffix extra-desc) in
(append (if test-in-comments
`((,(with-temp-buffer
(funcall mode)
(insert "z")
(comment-region (point-min) (point-max))
(buffer-substring-no-properties (point-min)
(1- (point-max))))
""
"-in-comments")))
(if test-in-strings
`(("\"" "\"" "-in-strings")))
(if test-in-code
`(("" "" ""))))
append
(cl-loop
for char across input
for pos from 0
unless (eq char ?-)
collect (electric-pair-define-test-form
name
fixture
(aref input pos)
pos
expected-string
expected-point
skip-pair-string
prefix
suffix
extra-desc
mode
bindings
fixture-fn))))))
;;; Basic pairings and skippings
;;;
(define-electric-pair-test balanced-situation
" (()) " "(((((((" :skip-pair-string "ppppppp"
:modes '(ruby-mode))
(define-electric-pair-test too-many-openings
" ((()) " "(((((((" :skip-pair-string "ppppppp")
(define-electric-pair-test too-many-closings
" (())) " "(((((((" :skip-pair-string "------p")
(define-electric-pair-test too-many-closings-2
"() ) " "---(---" :skip-pair-string "-------")
(define-electric-pair-test too-many-closings-3
")() " "(------" :skip-pair-string "-------")
(define-electric-pair-test balanced-autoskipping
" (()) " "---))--" :skip-pair-string "---ss--")
(define-electric-pair-test too-many-openings-autoskipping
" ((()) " "----))-" :skip-pair-string "-------")
(define-electric-pair-test too-many-closings-autoskipping
" (())) " "---)))-" :skip-pair-string "---sss-")
;;; Mixed parens
;;;
(define-electric-pair-test mixed-paren-1
" ()] " "-(-(---" :skip-pair-string "-p-p---")
(define-electric-pair-test mixed-paren-2
" [() " "-(-()--" :skip-pair-string "-p-ps--")
(define-electric-pair-test mixed-paren-3
" (]) " "-(-()--" :skip-pair-string "---ps--")
(define-electric-pair-test mixed-paren-4
" ()] " "---)]--" :skip-pair-string "---ss--")
(define-electric-pair-test mixed-paren-5
" [() " "----(--" :skip-pair-string "----p--")
(define-electric-pair-test find-matching-different-paren-type
" ()] " "-[-----" :skip-pair-string "-------")
(define-electric-pair-test find-matching-different-paren-type-inside-list
"( ()]) " "-[-----" :skip-pair-string "-------")
(define-electric-pair-test ignore-different-unmatching-paren-type
"( ()]) " "-(-----" :skip-pair-string "-p-----")
(define-electric-pair-test autopair-keep-least-amount-of-mixed-unbalance
"( ()] " "-(-----" :skip-pair-string "-p-----")
(define-electric-pair-test dont-autopair-to-resolve-mixed-unbalance
"( ()] " "-[-----" :skip-pair-string "-------")
(define-electric-pair-test autopair-so-as-not-to-worsen-unbalance-situation
"( (]) " "-[-----" :skip-pair-string "-p-----")
(define-electric-pair-test skip-over-partially-balanced
" [([]) " "-----)---" :skip-pair-string "-----s---")
(define-electric-pair-test only-skip-over-at-least-partially-balanced-stuff
" [([()) " "-----))--" :skip-pair-string "-----s---")
;;; Quotes
;;;
(define-electric-pair-test pair-some-quotes-skip-others
" \"\" " "-\"\"-----" :skip-pair-string "-ps------"
:test-in-strings nil
:bindings `((electric-pair-text-syntax-table
. ,prog-mode-syntax-table)))
(define-electric-pair-test skip-single-quotes-in-ruby-mode
" '' " "--'-" :skip-pair-string "--s-"
:modes '(ruby-mode)
:test-in-comments nil
:test-in-strings nil
:bindings `((electric-pair-text-syntax-table
. ,prog-mode-syntax-table)))
(define-electric-pair-test leave-unbalanced-quotes-alone
" \"' " "-\"'-" :skip-pair-string "----"
:modes '(ruby-mode)
:test-in-strings nil
:bindings `((electric-pair-text-syntax-table
. ,prog-mode-syntax-table)))
(define-electric-pair-test leave-unbalanced-quotes-alone-2
" \"\\\"' " "-\"--'-" :skip-pair-string "------"
:modes '(ruby-mode)
:test-in-strings nil
:bindings `((electric-pair-text-syntax-table
. ,prog-mode-syntax-table)))
(define-electric-pair-test leave-unbalanced-quotes-alone-3
" foo\\''" "'------" :skip-pair-string "-------"
:modes '(ruby-mode)
:test-in-strings nil
:bindings `((electric-pair-text-syntax-table
. ,prog-mode-syntax-table)))
(define-electric-pair-test inhibit-only-if-next-is-mismatched
"\"foo\"\"bar" "\""
:expected-string "\"\"\"foo\"\"bar"
:expected-point 2
:test-in-strings nil
:bindings `((electric-pair-text-syntax-table
. ,prog-mode-syntax-table)))
;;; More quotes, but now don't bind `electric-pair-text-syntax-table'
;;; to `prog-mode-syntax-table'. Use the defaults for
;;; `electric-pair-pairs' and `electric-pair-text-pairs'.
;;;
(define-electric-pair-test pairing-skipping-quotes-in-code
" \"\" " "-\"\"-----" :skip-pair-string "-ps------"
:test-in-strings nil
:test-in-comments nil)
(define-electric-pair-test skipping-quotes-in-comments
" \"\" " "--\"-----" :skip-pair-string "--s------"
:test-in-strings nil)
;;; Skipping over whitespace
;;;
(define-electric-pair-test whitespace-jumping
" ( ) " "--))))---" :expected-string " ( ) " :expected-point 8
:bindings '((electric-pair-skip-whitespace . t)))
(define-electric-pair-test whitespace-chomping
" ( ) " "--)------" :expected-string " () " :expected-point 4
:bindings '((electric-pair-skip-whitespace . chomp)))
(define-electric-pair-test whitespace-chomping-2
" ( \n\t\t\n ) " "--)------" :expected-string " () " :expected-point 4
:bindings '((electric-pair-skip-whitespace . chomp))
:test-in-comments nil)
(define-electric-pair-test whitespace-chomping-dont-cross-comments
" ( \n\t\t\n ) " "--)------" :expected-string " () \n\t\t\n ) "
:expected-point 4
:bindings '((electric-pair-skip-whitespace . chomp))
:test-in-strings nil
:test-in-code nil
:test-in-comments t)
;;; Pairing arbitrary characters
;;;
(define-electric-pair-test angle-brackets-everywhere
"<>" "<>" :skip-pair-string "ps"
:bindings '((electric-pair-pairs . ((?\< . ?\>)))))
(define-electric-pair-test angle-brackets-everywhere-2
"(<>" "-<>" :skip-pair-string "-ps"
:bindings '((electric-pair-pairs . ((?\< . ?\>)))))
(defvar electric-pair-test-angle-brackets-table
(let ((table (make-syntax-table prog-mode-syntax-table)))
(modify-syntax-entry ?\< "(>" table)
(modify-syntax-entry ?\> ")<`" table)
table))
(define-electric-pair-test angle-brackets-pair
"<>" "<" :expected-string "<><>" :expected-point 2
:test-in-code nil
:bindings `((electric-pair-text-syntax-table
. ,electric-pair-test-angle-brackets-table)))
(define-electric-pair-test angle-brackets-skip
"<>" "->" :expected-string "<>" :expected-point 3
:test-in-code nil
:bindings `((electric-pair-text-syntax-table
. ,electric-pair-test-angle-brackets-table)))
(define-electric-pair-test pair-backtick-and-quote-in-comments
";; " "---`" :expected-string ";; `'" :expected-point 5
:test-in-comments nil
:test-in-strings nil
:modes '(emacs-lisp-mode)
:bindings '((electric-pair-text-pairs . ((?\` . ?\')))))
(define-electric-pair-test skip-backtick-and-quote-in-comments
";; `foo'" "-------'" :expected-string ";; `foo'" :expected-point 9
:test-in-comments nil
:test-in-strings nil
:modes '(emacs-lisp-mode)
:bindings '((electric-pair-text-pairs . ((?\` . ?\')))))
(define-electric-pair-test pair-backtick-and-quote-in-strings
"\"\"" "-`" :expected-string "\"`'\"" :expected-point 3
:test-in-comments nil
:test-in-strings nil
:modes '(emacs-lisp-mode)
:bindings '((electric-pair-text-pairs . ((?\` . ?\')))))
(define-electric-pair-test skip-backtick-and-quote-in-strings
"\"`'\"" "--'" :expected-string "\"`'\"" :expected-point 4
:test-in-comments nil
:test-in-strings nil
:modes '(emacs-lisp-mode)
:bindings '((electric-pair-text-pairs . ((?\` . ?\')))))
(define-electric-pair-test skip-backtick-and-quote-in-strings-2
" \"`'\"" "----'" :expected-string " \"`'\"" :expected-point 6
:test-in-comments nil
:test-in-strings nil
:modes '(emacs-lisp-mode)
:bindings '((electric-pair-text-pairs . ((?\` . ?\')))))
;;; `js-mode' has `electric-layout-rules' for '{ and '}
;;;
(define-electric-pair-test js-mode-braces
"" "{" :expected-string "{}" :expected-point 2
:modes '(js-mode)
:fixture-fn #'(lambda ()
(electric-pair-mode 1)))
(define-electric-pair-test js-mode-braces-with-layout
"" "{" :expected-string "{\n\n}" :expected-point 3
:modes '(js-mode)
:test-in-comments nil
:test-in-strings nil
:fixture-fn #'(lambda ()
(electric-layout-mode 1)
(electric-pair-mode 1)))
(define-electric-pair-test js-mode-braces-with-layout-and-indent
"" "{" :expected-string "{\n \n}" :expected-point 7
:modes '(js-mode)
:test-in-comments nil
:test-in-strings nil
:fixture-fn #'(lambda ()
(electric-pair-mode 1)
(electric-indent-mode 1)
(electric-layout-mode 1)))
;;; Backspacing
;;; TODO: better tests
;;;
(ert-deftest electric-pair-backspace-1 ()
(save-electric-modes
(with-temp-buffer
(insert "()")
(goto-char 2)
(electric-pair-backward-delete-char 1)
(should (equal "" (buffer-string))))))
;;; Electric newlines between pairs
;;; TODO: better tests
(ert-deftest electric-pair-open-extra-newline ()
(save-electric-modes
(with-temp-buffer
(c-mode)
(electric-pair-mode 1)
(electric-indent-mode 1)
(insert "int main {}")
(backward-char 1)
(let ((c-basic-offset 4))
(newline 1 t)
(should (equal "int main {\n \n}"
(buffer-string)))
(should (equal (point) (- (point-max) 2)))))))
;;; Autowrapping
;;;
(define-electric-pair-test autowrapping-1
"foo" "(" :expected-string "(foo)" :expected-point 2
:fixture-fn #'(lambda ()
(electric-pair-mode 1)
(mark-sexp 1)))
(define-electric-pair-test autowrapping-2
"foo" ")" :expected-string "(foo)" :expected-point 6
:fixture-fn #'(lambda ()
(electric-pair-mode 1)
(mark-sexp 1)))
(define-electric-pair-test autowrapping-3
"foo" ")" :expected-string "(foo)" :expected-point 6
:fixture-fn #'(lambda ()
(electric-pair-mode 1)
(goto-char (point-max))
(skip-chars-backward "\"")
(mark-sexp -1)))
(define-electric-pair-test autowrapping-4
"foo" "(" :expected-string "(foo)" :expected-point 2
:fixture-fn #'(lambda ()
(electric-pair-mode 1)
(goto-char (point-max))
(skip-chars-backward "\"")
(mark-sexp -1)))
(define-electric-pair-test autowrapping-5
"foo" "\"" :expected-string "\"foo\"" :expected-point 2
:fixture-fn #'(lambda ()
(electric-pair-mode 1)
(mark-sexp 1)))
(define-electric-pair-test autowrapping-6
"foo" "\"" :expected-string "\"foo\"" :expected-point 6
:fixture-fn #'(lambda ()
(electric-pair-mode 1)
(goto-char (point-max))
(skip-chars-backward "\"")
(mark-sexp -1)))
(provide 'electric-tests)
;;; electric-tests.el ends here