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:
parent
fbcc63a317
commit
3b8d5131a3
10 changed files with 1126 additions and 97 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
36
etc/NEWS
36
etc/NEWS
|
@ -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'.
|
||||
|
|
|
@ -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.
|
||||
|
|
599
lisp/electric.el
599
lisp/electric.el
|
@ -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)
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
510
test/automated/electric-tests.el
Normal file
510
test/automated/electric-tests.el
Normal 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
|
Loading…
Add table
Reference in a new issue