aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoão Távora2013-12-26 22:02:49 +0000
committerJoão Távora2013-12-26 22:02:49 +0000
commit3b8d5131a316ad2fdc206744cec489a11f0bf1d3 (patch)
tree7a717cd2152141fdd7e5abe20926c7c0d4092869
parentfbcc63a3176389f39cb06f5a56f2abb29b06eaab (diff)
downloademacs-3b8d5131a316ad2fdc206744cec489a11f0bf1d3.tar.gz
emacs-3b8d5131a316ad2fdc206744cec489a11f0bf1d3.zip
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.
-rw-r--r--doc/emacs/ChangeLog4
-rw-r--r--doc/emacs/programs.texi30
-rw-r--r--etc/ChangeLog3
-rw-r--r--etc/NEWS36
-rw-r--r--lisp/ChangeLog22
-rw-r--r--lisp/electric.el599
-rw-r--r--lisp/emacs-lisp/lisp-mode.el8
-rw-r--r--lisp/simple.el8
-rw-r--r--test/ChangeLog3
-rw-r--r--test/automated/electric-tests.el510
10 files changed, 1126 insertions, 97 deletions
diff --git a/doc/emacs/ChangeLog b/doc/emacs/ChangeLog
index 96008f63e93..c9e6682aeaa 100644
--- a/doc/emacs/ChangeLog
+++ b/doc/emacs/ChangeLog
@@ -1,3 +1,7 @@
12013-12-26 João Távora <joaotavora@gmail.com>
2 * emacs.texi (Matching): Describe new features of Electric Pair
3 mode.
4
12013-12-25 Chong Yidong <cyd@gnu.org> 52013-12-25 Chong Yidong <cyd@gnu.org>
2 6
3 * glossary.texi (Glossary): Define MULE in modern terms. 7 * glossary.texi (Glossary): Define MULE in modern terms.
diff --git a/doc/emacs/programs.texi b/doc/emacs/programs.texi
index 8bb851e75a4..ff61ba8b1d7 100644
--- a/doc/emacs/programs.texi
+++ b/doc/emacs/programs.texi
@@ -844,8 +844,34 @@ show-paren-mode}.
844 Electric Pair mode, a global minor mode, provides a way to easily 844 Electric Pair mode, a global minor mode, provides a way to easily
845insert matching delimiters. Whenever you insert an opening delimiter, 845insert matching delimiters. Whenever you insert an opening delimiter,
846the matching closing delimiter is automatically inserted as well, 846the matching closing delimiter is automatically inserted as well,
847leaving point between the two. To toggle Electric Pair mode, type 847leaving point between the two. Conversely, when you insert a closing
848@kbd{M-x electric-pair-mode}. 848delimiter over an existing one, no inserting takes places and that
849position is simply skipped over. These variables control additional
850features of Electric Pair mode:
851
852@itemize @bullet
853@item
854@code{electric-pair-preserve-balance}, when non-@code{nil}, makes the
855default pairing logic balance out the number of opening and closing
856delimiters.
857
858@item
859@code{electric-pair-delete-adjacent-pairs}, when non-@code{nil}, makes
860backspacing between two adjacent delimiters also automatically delete
861the closing delimiter.
862
863@item
864@code{electric-pair-open-newline-between-pairs}, when non-@code{nil},
865makes inserting inserting a newline between two adjacent pairs also
866automatically open and extra newline after point.
867
868@item
869@code{electric-skip-whitespace}, when non-@code{nil}, causes the minor
870mode to skip whitespace forward before deciding whether to skip over
871the closing delimiter.
872@end itemize
873
874To toggle Electric Pair mode, type @kbd{M-x electric-pair-mode}.
849 875
850@node Comments 876@node Comments
851@section Manipulating Comments 877@section Manipulating Comments
diff --git a/etc/ChangeLog b/etc/ChangeLog
index 34f40ae6e24..d3ce75f5368 100644
--- a/etc/ChangeLog
+++ b/etc/ChangeLog
@@ -1,3 +1,6 @@
12013-12-26 João Távora <joaotavora@gmail.com>
2 * NEWS: Describe new features of Electric Pair mode.
3
12013-12-23 Teodor Zlatanov <tzz@lifelogs.com> 42013-12-23 Teodor Zlatanov <tzz@lifelogs.com>
2 5
3 * NEWS: Updated for `gnutls-verify-error', cfengine-mode, and 6 * NEWS: Updated for `gnutls-verify-error', cfengine-mode, and
diff --git a/etc/NEWS b/etc/NEWS
index 5aedab61c20..79004d091f1 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -439,6 +439,42 @@ and `desktop-restore-forces-onscreen' offer further customization.
439 439
440** Eldoc Mode works properly in the minibuffer. 440** Eldoc Mode works properly in the minibuffer.
441 441
442** Electric Pair mode
443*** New `electric-pair-preserve-balance' enabled by default.
444
445Pairing/skipping only kicks in when that help the balance of
446parentheses and quotes, i.e. the buffer should end up at least as
447balanced as before.
448
449You can further control this behaviour by adjusting the predicates
450stored in `electric-pair-inhibit-predicate' and
451`electric-pair-skip-self'.
452
453*** New `electric-pair-delete-adjacent-pairs' enabled by default.
454
455In `electric-pair-mode', the commands `backward-delete-char' and
456`backward-delete-char-untabify' are now bound to electric variants
457that delete the closer when invoked between adjacent pairs.
458
459*** New `electric-pair-open-newline-between-pairs' enabled by default.
460
461In `electric-pair-mode', inserting a newline between adjacent pairs
462opens an extra newline after point, which is indented if
463`electric-indent-mode' is also set.
464
465*** New `electric-pair-skip-whitespace' enabled by default.
466
467Controls if skipping over closing delimiters should jump over any
468whitespace slack. Setting it to `chomp' makes it delete this
469whitespace. See also the variable
470`electric-pair-skip-whitespace-chars'.
471
472*** New variables control the pairing in strings and comments.
473
474You can customize `electric-pair-text-pairs' and
475`electric-pair-text-syntax-table' to tweak pairing behaviour inside
476strings and comments.
477
442** EPA 478** EPA
443 479
444*** New option `epa-mail-aliases'. 480*** New option `epa-mail-aliases'.
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 972106bd26f..2831988efd9 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,25 @@
12013-12-26 João Távora <joaotavora@gmail.com>
2
3 * electric.el (electric-pair-mode): More flexible engine for skip-
4 and inhibit predicates, new options for pairing-related
5 functionality.
6 (electric-pair-preserve-balance): Pair/skip parentheses and quotes
7 if that keeps or improves their balance in buffers.
8 (electric-pair-delete-adjacent-pairs): Delete the pair when
9 backspacing over adjacent matched delimiters.
10 (electric-pair-open-extra-newline): Open extra newline when
11 inserting newlines between adjacent matched delimiters.
12 (electric--sort-post-self-insertion-hook): Sort
13 post-self-insert-hook according to priority values when
14 minor-modes are activated.
15 * simple.el (newline-and-indent): Call newline with interactive
16 set to t.
17 (blink-paren-post-self-insert-function): Set priority to 100.
18 * emacs-lisp/lisp-mode.el (lisp-mode-variables): Use
19 electric-pair-text-pairs to pair backtick-and-quote in strings and
20 comments. Locally set electric-pair-skip-whitespace to 'chomp and
21 electric-pair-open-newline-between-pairs to nil.
22
12013-12-26 Fabián Ezequiel Gallina <fgallina@gnu.org> 232013-12-26 Fabián Ezequiel Gallina <fgallina@gnu.org>
2 24
3 * progmodes/python.el: Use lexical-binding. 25 * progmodes/python.el: Use lexical-binding.
diff --git a/lisp/electric.el b/lisp/electric.el
index 91b99b4bfe7..fc5e63f90bb 100644
--- a/lisp/electric.el
+++ b/lisp/electric.el
@@ -187,6 +187,17 @@ Returns nil when we can't find this char."
187 (eq (char-before) last-command-event))))) 187 (eq (char-before) last-command-event)))))
188 pos))) 188 pos)))
189 189
190(defun electric--sort-post-self-insertion-hook ()
191 "Ensure order of electric functions in `post-self-insertion-hook'.
192
193Hooks in this variable interact in non-trivial ways, so a
194relative order must be maintained within it."
195 (setq-default post-self-insert-hook
196 (sort (default-value 'post-self-insert-hook)
197 #'(lambda (fn1 fn2)
198 (< (or (get fn1 'priority) 0)
199 (or (get fn2 'priority) 0))))))
200
190;;; Electric indentation. 201;;; Electric indentation.
191 202
192;; Autoloading variables is generally undesirable, but major modes 203;; 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.")
267 (> pos (line-beginning-position))) 278 (> pos (line-beginning-position)))
268 (indent-according-to-mode))))) 279 (indent-according-to-mode)))))
269 280
281(put 'electric-indent-post-self-insert-function 'priority 60)
282
270(defun electric-indent-just-newline (arg) 283(defun electric-indent-just-newline (arg)
271 "Insert just a newline, without any auto-indentation." 284 "Insert just a newline, without any auto-indentation."
272 (interactive "*P") 285 (interactive "*P")
@@ -295,20 +308,9 @@ insert a character from `electric-indent-chars'."
295 #'electric-indent-post-self-insert-function)) 308 #'electric-indent-post-self-insert-function))
296 (when (eq (lookup-key global-map [?\C-j]) 'newline-and-indent) 309 (when (eq (lookup-key global-map [?\C-j]) 'newline-and-indent)
297 (define-key global-map [?\C-j] 'electric-indent-just-newline)) 310 (define-key global-map [?\C-j] 'electric-indent-just-newline))
298 ;; post-self-insert-hooks interact in non-trivial ways.
299 ;; It turns out that electric-indent-mode generally works better if run
300 ;; late, but still before blink-paren.
301 (add-hook 'post-self-insert-hook 311 (add-hook 'post-self-insert-hook
302 #'electric-indent-post-self-insert-function 312 #'electric-indent-post-self-insert-function)
303 'append) 313 (electric--sort-post-self-insertion-hook)))
304 ;; FIXME: Ugly!
305 (let ((bp (memq #'blink-paren-post-self-insert-function
306 (default-value 'post-self-insert-hook))))
307 (when (memq #'electric-indent-post-self-insert-function bp)
308 (setcar bp #'electric-indent-post-self-insert-function)
309 (setcdr bp (cons #'blink-paren-post-self-insert-function
310 (delq #'electric-indent-post-self-insert-function
311 (cdr bp))))))))
312 314
313;;;###autoload 315;;;###autoload
314(define-minor-mode electric-indent-local-mode 316(define-minor-mode electric-indent-local-mode
@@ -327,32 +329,163 @@ insert a character from `electric-indent-chars'."
327 329
328(defcustom electric-pair-pairs 330(defcustom electric-pair-pairs
329 '((?\" . ?\")) 331 '((?\" . ?\"))
330 "Alist of pairs that should be used regardless of major mode." 332 "Alist of pairs that should be used regardless of major mode.
333
334Pairs of delimiters in this list are a fallback in case they have
335no syntax relevant to `electric-pair-mode' in the mode's syntax
336table.
337
338See also the variable `electric-pair-text-pairs'."
331 :version "24.1" 339 :version "24.1"
332 :type '(repeat (cons character character))) 340 :type '(repeat (cons character character)))
333 341
334(defcustom electric-pair-skip-self t 342(defcustom electric-pair-text-pairs
343 '((?\" . ?\" ))
344 "Alist of pairs that should always be used in comments and strings.
345
346Pairs of delimiters in this list are a fallback in case they have
347no syntax relevant to `electric-pair-mode' in the syntax table
348defined in `electric-pair-text-syntax-table'"
349 :version "24.4"
350 :type '(repeat (cons character character)))
351
352(defcustom electric-pair-skip-self #'electric-pair-default-skip-self
335 "If non-nil, skip char instead of inserting a second closing paren. 353 "If non-nil, skip char instead of inserting a second closing paren.
354
336When inserting a closing paren character right before the same character, 355When inserting a closing paren character right before the same character,
337just skip that character instead, so that hitting ( followed by ) results 356just skip that character instead, so that hitting ( followed by ) results
338in \"()\" rather than \"())\". 357in \"()\" rather than \"())\".
339This can be convenient for people who find it easier to hit ) than C-f." 358
359This can be convenient for people who find it easier to hit ) than C-f.
360
361Can also be a function of one argument (the closer char just
362inserted), in which case that function's return value is
363considered instead."
340 :version "24.1" 364 :version "24.1"
341 :type 'boolean) 365 :type '(choice
366 (const :tag "Never skip" nil)
367 (const :tag "Help balance" electric-pair-default-skip-self)
368 (const :tag "Always skip" t)
369 function))
342 370
343(defcustom electric-pair-inhibit-predicate 371(defcustom electric-pair-inhibit-predicate
344 #'electric-pair-default-inhibit 372 #'electric-pair-default-inhibit
345 "Predicate to prevent insertion of a matching pair. 373 "Predicate to prevent insertion of a matching pair.
374
346The function is called with a single char (the opening char just inserted). 375The function is called with a single char (the opening char just inserted).
347If it returns non-nil, then `electric-pair-mode' will not insert a matching 376If it returns non-nil, then `electric-pair-mode' will not insert a matching
348closer." 377closer."
349 :version "24.4" 378 :version "24.4"
350 :type '(choice 379 :type '(choice
351 (const :tag "Default" electric-pair-default-inhibit) 380 (const :tag "Conservative" electric-pair-conservative-inhibit)
381 (const :tag "Help balance" electric-pair-default-inhibit)
352 (const :tag "Always pair" ignore) 382 (const :tag "Always pair" ignore)
353 function)) 383 function))
354 384
355(defun electric-pair-default-inhibit (char) 385(defcustom electric-pair-preserve-balance t
386 "Non-nil if default pairing and skipping should help balance parentheses.
387
388The default values of `electric-pair-inhibit-predicate' and
389`electric-pair-skip-self' check this variable before delegating to other
390predicates reponsible for making decisions on whether to pair/skip some
391characters based on the actual state of the buffer's parenthesis and
392quotes."
393 :version "24.4"
394 :type 'boolean)
395
396(defcustom electric-pair-delete-adjacent-pairs t
397 "If non-nil, backspacing an open paren also deletes adjacent closer.
398
399Can also be a function of no arguments, in which case that function's
400return value is considered instead."
401 :version "24.4"
402 :type '(choice
403 (const :tag "Yes" t)
404 (const :tag "No" nil)
405 function))
406
407(defcustom electric-pair-open-newline-between-pairs t
408 "If non-nil, a newline between adjacent parentheses opens an extra one.
409
410Can also be a function of no arguments, in which case that function's
411return value is considered instead."
412 :version "24.4"
413 :type '(choice
414 (const :tag "Yes" t)
415 (const :tag "No" nil)
416 function))
417
418(defcustom electric-pair-skip-whitespace t
419 "If non-nil skip whitespace when skipping over closing parens.
420
421The specific kind of whitespace skipped is given by the variable
422`electric-pair-skip-whitespace-chars'.
423
424The symbol `chomp' specifies that the skipped-over whitespace
425should be deleted.
426
427Can also be a function of no arguments, in which case that function's
428return value is considered instead."
429 :version "24.4"
430 :type '(choice
431 (const :tag "Yes, jump over whitespace" t)
432 (const :tag "Yes, and delete whitespace" 'chomp)
433 (const :tag "No, no whitespace skipping" nil)
434 function))
435
436(defcustom electric-pair-skip-whitespace-chars (list ?\t ?\s ?\n)
437 "Whitespace characters considered by `electric-pair-skip-whitespace'."
438 :version "24.4"
439 :type '(choice (set (const :tag "Space" ?\s)
440 (const :tag "Tab" ?\t)
441 (const :tag "Newline" ?\n))
442 (list character)))
443
444(defun electric-pair--skip-whitespace ()
445 "Skip whitespace forward, not crossing comment or string boundaries."
446 (let ((saved (point))
447 (string-or-comment (nth 8 (syntax-ppss))))
448 (skip-chars-forward (apply #'string electric-pair-skip-whitespace-chars))
449 (unless (eq string-or-comment (nth 8 (syntax-ppss)))
450 (goto-char saved))))
451
452(defvar electric-pair-text-syntax-table prog-mode-syntax-table
453 "Syntax table used when pairing inside comments and strings.
454
455`electric-pair-mode' considers this syntax table only when point in inside
456quotes or comments. If lookup fails here, `electric-pair-text-pairs' will
457be considered.")
458
459(defun electric-pair-backward-delete-char (n &optional killflag untabify)
460 "Delete characters backward, and maybe also two adjacent paired delimiters.
461
462Remaining behaviour is given by `backward-delete-char' or, if UNTABIFY is
463non-nil, `backward-delete-char-untabify'."
464 (interactive "*p\nP")
465 (let* ((prev (char-before))
466 (next (char-after))
467 (syntax-info (electric-pair-syntax-info prev))
468 (syntax (car syntax-info))
469 (pair (cadr syntax-info)))
470 (when (and (if (functionp electric-pair-delete-adjacent-pairs)
471 (funcall electric-pair-delete-adjacent-pairs)
472 electric-pair-delete-adjacent-pairs)
473 next
474 (memq syntax '(?\( ?\" ?\$))
475 (eq pair next))
476 (delete-char 1 killflag))
477 (if untabify
478 (backward-delete-char-untabify n killflag)
479 (backward-delete-char n killflag))))
480
481(defun electric-pair-backward-delete-char-untabify (n &optional killflag)
482 "Delete characters backward, and maybe also two adjacent paired delimiters.
483
484Remaining behaviour is given by `backward-delete-char-untabify'."
485 (interactive "*p\nP")
486 (electric-pair-backward-delete-char n killflag t))
487
488(defun electric-pair-conservative-inhibit (char)
356 (or 489 (or
357 ;; I find it more often preferable not to pair when the 490 ;; I find it more often preferable not to pair when the
358 ;; same char is next. 491 ;; same char is next.
@@ -363,14 +496,40 @@ closer."
363 ;; I also find it often preferable not to pair next to a word. 496 ;; I also find it often preferable not to pair next to a word.
364 (eq (char-syntax (following-char)) ?w))) 497 (eq (char-syntax (following-char)) ?w)))
365 498
366(defun electric-pair-syntax (command-event) 499(defun electric-pair-syntax-info (command-event)
367 (let ((x (assq command-event electric-pair-pairs))) 500 "Calculate a list (SYNTAX PAIR UNCONDITIONAL STRING-OR-COMMENT-START).
501
502SYNTAX is COMMAND-EVENT's syntax character. PAIR is
503COMMAND-EVENT's pair. UNCONDITIONAL indicates the variables
504`electric-pair-pairs' or `electric-pair-text-pairs' were used to
505lookup syntax. STRING-OR-COMMENT-START indicates that point is
506inside a comment of string."
507 (let* ((pre-string-or-comment (nth 8 (save-excursion
508 (syntax-ppss (1- (point))))))
509 (post-string-or-comment (nth 8 (syntax-ppss (point))))
510 (string-or-comment (and post-string-or-comment
511 pre-string-or-comment))
512 (table (if string-or-comment
513 electric-pair-text-syntax-table
514 (syntax-table)))
515 (table-syntax-and-pair (with-syntax-table table
516 (list (char-syntax command-event)
517 (or (matching-paren command-event)
518 command-event))))
519 (fallback (if string-or-comment
520 (append electric-pair-text-pairs
521 electric-pair-pairs)
522 electric-pair-pairs))
523 (direct (assq command-event fallback))
524 (reverse (rassq command-event fallback)))
368 (cond 525 (cond
369 (x (if (eq (car x) (cdr x)) ?\" ?\()) 526 ((memq (car table-syntax-and-pair)
370 ((rassq command-event electric-pair-pairs) ?\)) 527 '(?\" ?\( ?\) ?\$))
371 ((nth 8 (syntax-ppss)) 528 (append table-syntax-and-pair (list nil string-or-comment)))
372 (with-syntax-table text-mode-syntax-table (char-syntax command-event))) 529 (direct (if (eq (car direct) (cdr direct))
373 (t (char-syntax command-event))))) 530 (list ?\" command-event t string-or-comment)
531 (list ?\( (cdr direct) t string-or-comment)))
532 (reverse (list ?\) (car reverse) t string-or-comment)))))
374 533
375(defun electric-pair--insert (char) 534(defun electric-pair--insert (char)
376 (let ((last-command-event char) 535 (let ((last-command-event char)
@@ -378,56 +537,297 @@ closer."
378 (electric-pair-mode nil)) 537 (electric-pair-mode nil))
379 (self-insert-command 1))) 538 (self-insert-command 1)))
380 539
540(defun electric-pair--syntax-ppss (&optional pos where)
541 "Like `syntax-ppss', but sometimes fallback to `parse-partial-sexp'.
542
543WHERE is list defaulting to '(string comment) and indicates
544when to fallback to `parse-partial-sexp'."
545 (let* ((pos (or pos (point)))
546 (where (or where '(string comment)))
547 (quick-ppss (syntax-ppss))
548 (quick-ppss-at-pos (syntax-ppss pos)))
549 (if (or (and (nth 3 quick-ppss) (memq 'string where))
550 (and (nth 4 quick-ppss) (memq 'comment where)))
551 (with-syntax-table electric-pair-text-syntax-table
552 (parse-partial-sexp (1+ (nth 8 quick-ppss)) pos))
553 ;; HACK! cc-mode apparently has some `syntax-ppss' bugs
554 (if (memq major-mode '(c-mode c++ mode))
555 (parse-partial-sexp (point-min) pos)
556 quick-ppss-at-pos))))
557
558;; Balancing means controlling pairing and skipping of parentheses so
559;; that, if possible, the buffer ends up at least as balanced as
560;; before, if not more. The algorithm is slightly complex because some
561;; situations like "()))" need pairing to occur at the end but not at
562;; the beginning. Balancing should also happen independently for
563;; different types of parentheses, so that having your {}'s unbalanced
564;; doesn't keep `electric-pair-mode' from balancing your ()'s and your
565;; []'s.
566(defun electric-pair--balance-info (direction string-or-comment)
567 "Examine lists forward or backward according to DIRECTIONS's sign.
568
569STRING-OR-COMMENT is info suitable for running `parse-partial-sexp'.
570
571Return a cons of two descritions (MATCHED-P . PAIR) for the
572innermost and outermost lists that enclose point. The outermost
573list enclosing point is either the first top-level or first
574mismatched list found by uplisting.
575
576If the outermost list is matched, don't rely on its PAIR. If
577point is not enclosed by any lists, return ((T) (T))."
578 (let* (innermost
579 outermost
580 (table (if string-or-comment
581 electric-pair-text-syntax-table
582 (syntax-table)))
583 (at-top-level-or-equivalent-fn
584 ;; called when `scan-sexps' ran perfectly, when when it
585 ;; found a parenthesis pointing in the direction of
586 ;; travel. Also when travel started inside a comment and
587 ;; exited it
588 #'(lambda ()
589 (setq outermost (list t))
590 (unless innermost
591 (setq innermost (list t)))))
592 (ended-prematurely-fn
593 ;; called when `scan-sexps' crashed against a parenthesis
594 ;; pointing opposite the direction of travel. After
595 ;; traversing that character, the idea is to travel one sexp
596 ;; in the opposite direction looking for a matching
597 ;; delimiter.
598 #'(lambda ()
599 (let* ((pos (point))
600 (matched
601 (save-excursion
602 (cond ((< direction 0)
603 (condition-case nil
604 (eq (char-after pos)
605 (with-syntax-table table
606 (matching-paren
607 (char-before
608 (scan-sexps (point) 1)))))
609 (scan-error nil)))
610 (t
611 ;; In this case, no need to use
612 ;; `scan-sexps', we can use some
613 ;; `electric-pair--syntax-ppss' in this
614 ;; case (which uses the quicker
615 ;; `syntax-ppss' in some cases)
616 (let* ((ppss (electric-pair--syntax-ppss
617 (1- (point))))
618 (start (car (last (nth 9 ppss))))
619 (opener (char-after start)))
620 (and start
621 (eq (char-before pos)
622 (or (with-syntax-table table
623 (matching-paren opener))
624 opener))))))))
625 (actual-pair (if (> direction 0)
626 (char-before (point))
627 (char-after (point)))))
628 (unless innermost
629 (setq innermost (cons matched actual-pair)))
630 (unless matched
631 (setq outermost (cons matched actual-pair)))))))
632 (save-excursion
633 (while (not outermost)
634 (condition-case err
635 (with-syntax-table table
636 (scan-sexps (point) (if (> direction 0)
637 (point-max)
638 (- (point-max))))
639 (funcall at-top-level-or-equivalent-fn))
640 (scan-error
641 (cond ((or
642 ;; some error happened and it is not of the "ended
643 ;; prematurely" kind"...
644 (not (string-match "ends prematurely" (nth 1 err)))
645 ;; ... or we were in a comment and just came out of
646 ;; it.
647 (and string-or-comment
648 (not (nth 8 (syntax-ppss)))))
649 (funcall at-top-level-or-equivalent-fn))
650 (t
651 ;; exit the sexp
652 (goto-char (nth 3 err))
653 (funcall ended-prematurely-fn)))))))
654 (cons innermost outermost)))
655
656(defun electric-pair--looking-at-unterminated-string-p (char)
657 "Say if following string starts with CHAR and is unterminated."
658 ;; FIXME: ugly/naive
659 (save-excursion
660 (skip-chars-forward (format "^%c" char))
661 (while (not (zerop (% (save-excursion (skip-syntax-backward "\\")) 2)))
662 (unless (eobp)
663 (forward-char 1)
664 (skip-chars-forward (format "^%c" char))))
665 (and (not (eobp))
666 (condition-case err
667 (progn (forward-sexp) nil)
668 (scan-error t)))))
669
670(defun electric-pair--inside-string-p (char)
671 "Say if point is inside a string started by CHAR.
672
673A comments text is parsed with `electric-pair-text-syntax-table'.
674Also consider strings within comments, but not strings within
675strings."
676 ;; FIXME: could also consider strings within strings by examining
677 ;; delimiters.
678 (let* ((ppss (electric-pair--syntax-ppss (point) '(comment))))
679 (memq (nth 3 ppss) (list t char))))
680
681(defun electric-pair-inhibit-if-helps-balance (char)
682 "Return non-nil if auto-pairing of CHAR would hurt parentheses' balance.
683
684Works by first removing the character from the buffer, then doing
685some list calculations, finally restoring the situation as if nothing
686happened."
687 (pcase (electric-pair-syntax-info char)
688 (`(,syntax ,pair ,_ ,s-or-c)
689 (unwind-protect
690 (progn
691 (delete-char -1)
692 (cond ((eq ?\( syntax)
693 (let* ((pair-data
694 (electric-pair--balance-info 1 s-or-c))
695 (innermost (car pair-data))
696 (outermost (cdr pair-data)))
697 (cond ((car outermost)
698 nil)
699 (t
700 (eq (cdr outermost) pair)))))
701 ((eq syntax ?\")
702 (electric-pair--looking-at-unterminated-string-p char))))
703 (insert-char char)))))
704
705(defun electric-pair-skip-if-helps-balance (char)
706 "Return non-nil if skipping CHAR would benefit parentheses' balance.
707
708Works by first removing the character from the buffer, then doing
709some list calculations, finally restoring the situation as if nothing
710happened."
711 (pcase (electric-pair-syntax-info char)
712 (`(,syntax ,pair ,_ ,s-or-c)
713 (unwind-protect
714 (progn
715 (delete-char -1)
716 (cond ((eq syntax ?\))
717 (let* ((pair-data
718 (electric-pair--balance-info
719 -1 s-or-c))
720 (innermost (car pair-data))
721 (outermost (cdr pair-data)))
722 (and
723 (cond ((car outermost)
724 (car innermost))
725 ((car innermost)
726 (not (eq (cdr outermost) pair)))))))
727 ((eq syntax ?\")
728 (electric-pair--inside-string-p char))))
729 (insert-char char)))))
730
731(defun electric-pair-default-skip-self (char)
732 (if electric-pair-preserve-balance
733 (electric-pair-skip-if-helps-balance char)
734 t))
735
736(defun electric-pair-default-inhibit (char)
737 (if electric-pair-preserve-balance
738 (electric-pair-inhibit-if-helps-balance char)
739 (electric-pair-conservative-inhibit char)))
740
381(defun electric-pair-post-self-insert-function () 741(defun electric-pair-post-self-insert-function ()
382 (let* ((pos (and electric-pair-mode (electric--after-char-pos))) 742 (let* ((pos (and electric-pair-mode (electric--after-char-pos)))
383 (syntax (and pos (electric-pair-syntax last-command-event))) 743 (skip-whitespace-info))
384 (closer (if (eq syntax ?\() 744 (pcase (electric-pair-syntax-info last-command-event)
385 (cdr (or (assq last-command-event electric-pair-pairs) 745 (`(,syntax ,pair ,unconditional ,_)
386 (aref (syntax-table) last-command-event))) 746 (cond
387 last-command-event))) 747 ((null pos) nil)
388 (cond 748 ;; Wrap a pair around the active region.
389 ((null pos) nil) 749 ;;
390 ;; Wrap a pair around the active region. 750 ((and (memq syntax '(?\( ?\) ?\" ?\$)) (use-region-p))
391 ((and (memq syntax '(?\( ?\" ?\$)) (use-region-p)) 751 ;; FIXME: To do this right, we'd need a post-self-insert-function
392 ;; FIXME: To do this right, we'd need a post-self-insert-function 752 ;; so we could add-function around it and insert the closer after
393 ;; so we could add-function around it and insert the closer after 753 ;; all the rest of the hook has run.
394 ;; all the rest of the hook has run. 754 (if (or (eq syntax ?\")
395 (if (>= (mark) (point)) 755 (and (eq syntax ?\))
396 (goto-char (mark)) 756 (>= (point) (mark)))
397 ;; We already inserted the open-paren but at the end of the 757 (and (not (eq syntax ?\)))
398 ;; region, so we have to remove it and start over. 758 (>= (mark) (point))))
399 (delete-region (1- pos) (point)) 759 (save-excursion
400 (save-excursion 760 (goto-char (mark))
401 (goto-char (mark)) 761 (electric-pair--insert pair))
402 (electric-pair--insert last-command-event))) 762 (delete-region pos (1- pos))
403 ;; Since we're right after the closer now, we could tell the rest of 763 (electric-pair--insert pair)
404 ;; post-self-insert-hook that we inserted `closer', but then we'd get 764 (goto-char (mark))
405 ;; blink-paren to kick in, which is annoying. 765 (electric-pair--insert last-command-event)))
406 ;;(setq last-command-event closer) 766 ;; Backslash-escaped: no pairing, no skipping.
407 (insert closer)) 767 ((save-excursion
408 ;; Backslash-escaped: no pairing, no skipping. 768 (goto-char (1- pos))
409 ((save-excursion 769 (not (zerop (% (skip-syntax-backward "\\") 2))))
410 (goto-char (1- pos)) 770 nil)
411 (not (zerop (% (skip-syntax-backward "\\") 2)))) 771 ;; Skip self.
412 nil) 772 ((and (memq syntax '(?\) ?\" ?\$))
413 ;; Skip self. 773 (and (or unconditional
414 ((and (memq syntax '(?\) ?\" ?\$)) 774 (if (functionp electric-pair-skip-self)
415 electric-pair-skip-self 775 (funcall electric-pair-skip-self last-command-event)
416 (eq (char-after pos) last-command-event)) 776 electric-pair-skip-self))
417 ;; This is too late: rather than insert&delete we'd want to only skip (or 777 (save-excursion
418 ;; insert in overwrite mode). The difference is in what goes in the 778 (when (setq skip-whitespace-info
419 ;; undo-log and in the intermediate state which might be visible to other 779 (if (functionp electric-pair-skip-whitespace)
420 ;; post-self-insert-hook. We'll just have to live with it for now. 780 (funcall electric-pair-skip-whitespace)
421 (delete-char 1)) 781 electric-pair-skip-whitespace))
422 ;; Insert matching pair. 782 (electric-pair--skip-whitespace))
423 ((not (or (not (memq syntax `(?\( ?\" ?\$))) 783 (eq (char-after) last-command-event))))
424 overwrite-mode 784 ;; This is too late: rather than insert&delete we'd want to only
425 (funcall electric-pair-inhibit-predicate last-command-event))) 785 ;; skip (or insert in overwrite mode). The difference is in what
426 (save-excursion (electric-pair--insert closer)))))) 786 ;; goes in the undo-log and in the intermediate state which might
787 ;; be visible to other post-self-insert-hook. We'll just have to
788 ;; live with it for now.
789 (when skip-whitespace-info
790 (electric-pair--skip-whitespace))
791 (delete-region (1- pos) (if (eq skip-whitespace-info 'chomp)
792 (point)
793 pos))
794 (forward-char))
795 ;; Insert matching pair.
796 ((and (memq syntax `(?\( ?\" ?\$))
797 (not overwrite-mode)
798 (or unconditional
799 (not (funcall electric-pair-inhibit-predicate
800 last-command-event))))
801 (save-excursion (electric-pair--insert pair)))))
802 (t
803 (when (and (if (functionp electric-pair-open-newline-between-pairs)
804 (funcall electric-pair-open-newline-between-pairs)
805 electric-pair-open-newline-between-pairs)
806 (eq last-command-event ?\n)
807 (not (eobp))
808 (eq (save-excursion
809 (skip-chars-backward "\t\s")
810 (char-before (1- (point))))
811 (matching-paren (char-after))))
812 (save-excursion (newline 1 t)))))))
813
814(put 'electric-pair-post-self-insert-function 'priority 20)
427 815
428(defun electric-pair-will-use-region () 816(defun electric-pair-will-use-region ()
429 (and (use-region-p) 817 (and (use-region-p)
430 (memq (electric-pair-syntax last-command-event) '(?\( ?\" ?\$)))) 818 (memq (car (electric-pair-syntax-info last-command-event))
819 '(?\( ?\) ?\" ?\$))))
820
821(defvar electric-pair-mode-map
822 (let ((map (make-sparse-keymap)))
823 (define-key map [remap backward-delete-char-untabify]
824 'electric-pair-backward-delete-char-untabify)
825 (define-key map [remap backward-delete-char]
826 'electric-pair-backward-delete-char)
827 (define-key map [remap delete-backward-char]
828 'electric-pair-backward-delete-char)
829 map)
830 "Keymap used by `electric-pair-mode'.")
431 831
432;;;###autoload 832;;;###autoload
433(define-minor-mode electric-pair-mode 833(define-minor-mode electric-pair-mode
@@ -438,29 +838,33 @@ the mode if ARG is omitted or nil.
438 838
439Electric Pair mode is a global minor mode. When enabled, typing 839Electric Pair mode is a global minor mode. When enabled, typing
440an open parenthesis automatically inserts the corresponding 840an open parenthesis automatically inserts the corresponding
441closing parenthesis. \(Likewise for brackets, etc.) 841closing parenthesis. \(Likewise for brackets, etc.)."
442
443See options `electric-pair-pairs' and `electric-pair-skip-self'."
444 :global t :group 'electricity 842 :global t :group 'electricity
445 (if electric-pair-mode 843 (if electric-pair-mode
446 (progn 844 (progn
447 (add-hook 'post-self-insert-hook 845 (add-hook 'post-self-insert-hook
448 #'electric-pair-post-self-insert-function) 846 #'electric-pair-post-self-insert-function)
847 (electric--sort-post-self-insertion-hook)
449 (add-hook 'self-insert-uses-region-functions 848 (add-hook 'self-insert-uses-region-functions
450 #'electric-pair-will-use-region)) 849 #'electric-pair-will-use-region))
451 (remove-hook 'post-self-insert-hook 850 (remove-hook 'post-self-insert-hook
452 #'electric-pair-post-self-insert-function) 851 #'electric-pair-post-self-insert-function)
453 (remove-hook 'self-insert-uses-region-functions 852 (remove-hook 'self-insert-uses-region-functions
454 #'electric-pair-will-use-region))) 853 #'electric-pair-will-use-region)))
455 854
456;;; Electric newlines after/before/around some chars. 855;;; Electric newlines after/before/around some chars.
457 856
458(defvar electric-layout-rules '() 857(defvar electric-layout-rules nil
459 "List of rules saying where to automatically insert newlines. 858 "List of rules saying where to automatically insert newlines.
460Each rule has the form (CHAR . WHERE) where CHAR is the char 859
461that was just inserted and WHERE specifies where to insert newlines 860Each rule has the form (CHAR . WHERE) where CHAR is the char that
462and can be: nil, `before', `after', `around', or a function of no 861was just inserted and WHERE specifies where to insert newlines
463arguments that returns one of those symbols.") 862and can be: nil, `before', `after', `around', `after-stay', or a
863function of no arguments that returns one of those symbols.
864
865The symbols specify where in relation to CHAR the newline
866character(s) should be inserted. `after-stay' means insert a
867newline after CHAR but stay in the same place.")
464 868
465(defun electric-layout-post-self-insert-function () 869(defun electric-layout-post-self-insert-function ()
466 (let* ((rule (cdr (assq last-command-event electric-layout-rules))) 870 (let* ((rule (cdr (assq last-command-event electric-layout-rules)))
@@ -469,23 +873,32 @@ arguments that returns one of those symbols.")
469 (setq pos (electric--after-char-pos)) 873 (setq pos (electric--after-char-pos))
470 ;; Not in a string or comment. 874 ;; Not in a string or comment.
471 (not (nth 8 (save-excursion (syntax-ppss pos))))) 875 (not (nth 8 (save-excursion (syntax-ppss pos)))))
472 (let ((end (copy-marker (point) t))) 876 (let ((end (copy-marker (point)))
877 (sym (if (functionp rule) (funcall rule) rule)))
878 (set-marker-insertion-type end (not (eq sym 'after-stay)))
473 (goto-char pos) 879 (goto-char pos)
474 (pcase (if (functionp rule) (funcall rule) rule) 880 (pcase sym
475 ;; FIXME: we used `newline' down here which called 881 ;; FIXME: we used `newline' down here which called
476 ;; self-insert-command and ran post-self-insert-hook recursively. 882 ;; self-insert-command and ran post-self-insert-hook recursively.
477 ;; It happened to make electric-indent-mode work automatically with 883 ;; It happened to make electric-indent-mode work automatically with
478 ;; electric-layout-mode (at the cost of re-indenting lines 884 ;; electric-layout-mode (at the cost of re-indenting lines
479 ;; multiple times), but I'm not sure it's what we want. 885 ;; multiple times), but I'm not sure it's what we want.
886 ;;
887 ;; FIXME: check eolp before inserting \n?
480 (`before (goto-char (1- pos)) (skip-chars-backward " \t") 888 (`before (goto-char (1- pos)) (skip-chars-backward " \t")
481 (unless (bolp) (insert "\n"))) 889 (unless (bolp) (insert "\n")))
482 (`after (insert "\n")) ; FIXME: check eolp before inserting \n? 890 (`after (insert "\n"))
891 (`after-stay (save-excursion
892 (let ((electric-layout-rules nil))
893 (newline 1 t))))
483 (`around (save-excursion 894 (`around (save-excursion
484 (goto-char (1- pos)) (skip-chars-backward " \t") 895 (goto-char (1- pos)) (skip-chars-backward " \t")
485 (unless (bolp) (insert "\n"))) 896 (unless (bolp) (insert "\n")))
486 (insert "\n"))) ; FIXME: check eolp before inserting \n? 897 (insert "\n"))) ; FIXME: check eolp before inserting \n?
487 (goto-char end))))) 898 (goto-char end)))))
488 899
900(put 'electric-layout-post-self-insert-function 'priority 40)
901
489;;;###autoload 902;;;###autoload
490(define-minor-mode electric-layout-mode 903(define-minor-mode electric-layout-mode
491 "Automatically insert newlines around some chars. 904 "Automatically insert newlines around some chars.
@@ -494,11 +907,13 @@ positive, and disable it otherwise. If called from Lisp, enable
494the mode if ARG is omitted or nil. 907the mode if ARG is omitted or nil.
495The variable `electric-layout-rules' says when and how to insert newlines." 908The variable `electric-layout-rules' says when and how to insert newlines."
496 :global t :group 'electricity 909 :global t :group 'electricity
497 (if electric-layout-mode 910 (cond (electric-layout-mode
498 (add-hook 'post-self-insert-hook 911 (add-hook 'post-self-insert-hook
499 #'electric-layout-post-self-insert-function) 912 #'electric-layout-post-self-insert-function)
500 (remove-hook 'post-self-insert-hook 913 (electric--sort-post-self-insertion-hook))
501 #'electric-layout-post-self-insert-function))) 914 (t
915 (remove-hook 'post-self-insert-hook
916 #'electric-layout-post-self-insert-function))))
502 917
503(provide 'electric) 918(provide 'electric)
504 919
diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el
index b7bd33f628f..f1eae18c507 100644
--- a/lisp/emacs-lisp/lisp-mode.el
+++ b/lisp/emacs-lisp/lisp-mode.el
@@ -472,7 +472,13 @@ font-lock keywords will not be case sensitive."
472 (font-lock-mark-block-function . mark-defun) 472 (font-lock-mark-block-function . mark-defun)
473 (font-lock-syntactic-face-function 473 (font-lock-syntactic-face-function
474 . lisp-font-lock-syntactic-face-function))) 474 . lisp-font-lock-syntactic-face-function)))
475 (setq-local prettify-symbols-alist lisp--prettify-symbols-alist)) 475 (setq-local prettify-symbols-alist lisp--prettify-symbols-alist)
476 ;; electric
477 (when elisp
478 (setq-local electric-pair-text-pairs
479 (cons '(?\` . ?\') electric-pair-text-pairs)))
480 (setq-local electric-pair-skip-whitespace 'chomp)
481 (setq-local electric-pair-open-newline-between-pairs nil))
476 482
477(defun lisp-outline-level () 483(defun lisp-outline-level ()
478 "Lisp mode `outline-level' function." 484 "Lisp mode `outline-level' function."
diff --git a/lisp/simple.el b/lisp/simple.el
index a6543516a9c..624d87fd655 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -610,7 +610,7 @@ In some text modes, where TAB inserts a tab, this command indents to the
610column specified by the function `current-left-margin'." 610column specified by the function `current-left-margin'."
611 (interactive "*") 611 (interactive "*")
612 (delete-horizontal-space t) 612 (delete-horizontal-space t)
613 (newline) 613 (newline 1 t)
614 (indent-according-to-mode)) 614 (indent-according-to-mode))
615 615
616(defun reindent-then-newline-and-indent () 616(defun reindent-then-newline-and-indent ()
@@ -6448,10 +6448,14 @@ More precisely, a char with closeparen syntax is self-inserted.")
6448 (point)))))) 6448 (point))))))
6449 (funcall blink-paren-function))) 6449 (funcall blink-paren-function)))
6450 6450
6451(put 'blink-paren-post-self-insert-function 'priority 100)
6452
6451(add-hook 'post-self-insert-hook #'blink-paren-post-self-insert-function 6453(add-hook 'post-self-insert-hook #'blink-paren-post-self-insert-function
6452 ;; Most likely, this hook is nil, so this arg doesn't matter, 6454 ;; Most likely, this hook is nil, so this arg doesn't matter,
6453 ;; but I use it as a reminder that this function usually 6455 ;; but I use it as a reminder that this function usually
6454 ;; likes to be run after others since it does `sit-for'. 6456 ;; likes to be run after others since it does
6457 ;; `sit-for'. That's also the reason it get a `priority' prop
6458 ;; of 100.
6455 'append) 6459 'append)
6456 6460
6457;; This executes C-g typed while Emacs is waiting for a command. 6461;; This executes C-g typed while Emacs is waiting for a command.
diff --git a/test/ChangeLog b/test/ChangeLog
index 1f4d5164e37..f45e4de11f5 100644
--- a/test/ChangeLog
+++ b/test/ChangeLog
@@ -1,3 +1,6 @@
12013-12-26 João Távora <joaotavora@gmail.com>
2 * automated/electric-tests.el: Add tests for Electric Pair mode.
3
12013-12-25 Fabián Ezequiel Gallina <fgallina@gnu.org> 42013-12-25 Fabián Ezequiel Gallina <fgallina@gnu.org>
2 5
3 * automated/python-tests.el 6 * automated/python-tests.el
diff --git a/test/automated/electric-tests.el b/test/automated/electric-tests.el
new file mode 100644
index 00000000000..28a34752ecf
--- /dev/null
+++ b/test/automated/electric-tests.el
@@ -0,0 +1,510 @@
1;;; electric-tests.el --- tests for electric.el
2
3;; Copyright (C) 2013 João Távora
4
5;; Author: João Távora <joaotavora@gmail.com>
6;; Keywords:
7
8;; This program is free software; you can redistribute it and/or modify
9;; it under the terms of the GNU General Public License as published by
10;; the Free Software Foundation, either version 3 of the License, or
11;; (at your option) any later version.
12
13;; This program is distributed in the hope that it will be useful,
14;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;; GNU General Public License for more details.
17
18;; You should have received a copy of the GNU General Public License
19;; along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21;;; Commentary: Tests for Electric Pair mode.
22;;; TODO: Add tests for other Electric-* functionality
23
24;;
25
26;;; Code:
27(require 'ert)
28(require 'ert-x)
29(require 'electric)
30(require 'cl-lib)
31
32(defun call-with-saved-electric-modes (fn)
33 (let ((saved-electric (if electric-pair-mode 1 -1))
34 (saved-layout (if electric-layout-mode 1 -1))
35 (saved-indent (if electric-indent-mode 1 -1)))
36 (electric-pair-mode -1)
37 (electric-layout-mode -1)
38 (electric-indent-mode -1)
39 (unwind-protect
40 (funcall fn)
41 (electric-pair-mode saved-electric)
42 (electric-indent-mode saved-indent)
43 (electric-layout-mode saved-layout))))
44
45(defmacro save-electric-modes (&rest body)
46 (declare (indent defun) (debug t))
47 `(call-with-saved-electric-modes #'(lambda () ,@body)))
48
49(defun electric-pair-test-for (fixture where char expected-string
50 expected-point mode bindings fixture-fn)
51 (with-temp-buffer
52 (funcall mode)
53 (insert fixture)
54 (save-electric-modes
55 (let ((last-command-event char))
56 (goto-char where)
57 (funcall fixture-fn)
58 (cl-progv
59 (mapcar #'car bindings)
60 (mapcar #'cdr bindings)
61 (self-insert-command 1))))
62 (should (equal (buffer-substring-no-properties (point-min) (point-max))
63 expected-string))
64 (should (equal (point)
65 expected-point))))
66
67(eval-when-compile
68 (defun electric-pair-define-test-form (name fixture
69 char
70 pos
71 expected-string
72 expected-point
73 skip-pair-string
74 prefix
75 suffix
76 extra-desc
77 mode
78 bindings
79 fixture-fn)
80 (let* ((expected-string-and-point
81 (if skip-pair-string
82 (with-temp-buffer
83 (cl-progv
84 ;; FIXME: avoid `eval'
85 (mapcar #'car (eval bindings))
86 (mapcar #'cdr (eval bindings))
87 (funcall mode)
88 (insert fixture)
89 (goto-char (1+ pos))
90 (insert char)
91 (cond ((eq (aref skip-pair-string pos)
92 ?p)
93 (insert (cadr (electric-pair-syntax-info char)))
94 (backward-char 1))
95 ((eq (aref skip-pair-string pos)
96 ?s)
97 (delete-char -1)
98 (forward-char 1)))
99 (list
100 (buffer-substring-no-properties (point-min) (point-max))
101 (point))))
102 (list expected-string expected-point)))
103 (expected-string (car expected-string-and-point))
104 (expected-point (cadr expected-string-and-point))
105 (fixture (format "%s%s%s" prefix fixture suffix))
106 (expected-string (format "%s%s%s" prefix expected-string suffix))
107 (expected-point (+ (length prefix) expected-point))
108 (pos (+ (length prefix) pos)))
109 `(ert-deftest ,(intern (format "electric-pair-%s-at-point-%s-in-%s%s"
110 name
111 (1+ pos)
112 mode
113 extra-desc))
114 ()
115 ,(format "With \"%s\", try input %c at point %d. \
116Should %s \"%s\" and point at %d"
117 fixture
118 char
119 (1+ pos)
120 (if (string= fixture expected-string)
121 "stay"
122 "become")
123 (replace-regexp-in-string "\n" "\\\\n" expected-string)
124 expected-point)
125 (electric-pair-test-for ,fixture
126 ,(1+ pos)
127 ,char
128 ,expected-string
129 ,expected-point
130 ',mode
131 ,bindings
132 ,fixture-fn)))))
133
134(cl-defmacro define-electric-pair-test
135 (name fixture
136 input
137 &key
138 skip-pair-string
139 expected-string
140 expected-point
141 bindings
142 (modes '(quote (emacs-lisp-mode ruby-mode c++-mode)))
143 (test-in-comments t)
144 (test-in-strings t)
145 (test-in-code t)
146 (fixture-fn #'(lambda ()
147 (electric-pair-mode 1))))
148 `(progn
149 ,@(cl-loop
150 for mode in (eval modes) ;FIXME: avoid `eval'
151 append
152 (cl-loop
153 for (prefix suffix extra-desc) in
154 (append (if test-in-comments
155 `((,(with-temp-buffer
156 (funcall mode)
157 (insert "z")
158 (comment-region (point-min) (point-max))
159 (buffer-substring-no-properties (point-min)
160 (1- (point-max))))
161 ""
162 "-in-comments")))
163 (if test-in-strings
164 `(("\"" "\"" "-in-strings")))
165 (if test-in-code
166 `(("" "" ""))))
167 append
168 (cl-loop
169 for char across input
170 for pos from 0
171 unless (eq char ?-)
172 collect (electric-pair-define-test-form
173 name
174 fixture
175 (aref input pos)
176 pos
177 expected-string
178 expected-point
179 skip-pair-string
180 prefix
181 suffix
182 extra-desc
183 mode
184 bindings
185 fixture-fn))))))
186
187;;; Basic pairings and skippings
188;;;
189(define-electric-pair-test balanced-situation
190 " (()) " "(((((((" :skip-pair-string "ppppppp"
191 :modes '(ruby-mode))
192
193(define-electric-pair-test too-many-openings
194 " ((()) " "(((((((" :skip-pair-string "ppppppp")
195
196(define-electric-pair-test too-many-closings
197 " (())) " "(((((((" :skip-pair-string "------p")
198
199(define-electric-pair-test too-many-closings-2
200 "() ) " "---(---" :skip-pair-string "-------")
201
202(define-electric-pair-test too-many-closings-3
203 ")() " "(------" :skip-pair-string "-------")
204
205(define-electric-pair-test balanced-autoskipping
206 " (()) " "---))--" :skip-pair-string "---ss--")
207
208(define-electric-pair-test too-many-openings-autoskipping
209 " ((()) " "----))-" :skip-pair-string "-------")
210
211(define-electric-pair-test too-many-closings-autoskipping
212 " (())) " "---)))-" :skip-pair-string "---sss-")
213
214
215;;; Mixed parens
216;;;
217(define-electric-pair-test mixed-paren-1
218 " ()] " "-(-(---" :skip-pair-string "-p-p---")
219
220(define-electric-pair-test mixed-paren-2
221 " [() " "-(-()--" :skip-pair-string "-p-ps--")
222
223(define-electric-pair-test mixed-paren-3
224 " (]) " "-(-()--" :skip-pair-string "---ps--")
225
226(define-electric-pair-test mixed-paren-4
227 " ()] " "---)]--" :skip-pair-string "---ss--")
228
229(define-electric-pair-test mixed-paren-5
230 " [() " "----(--" :skip-pair-string "----p--")
231
232(define-electric-pair-test find-matching-different-paren-type
233 " ()] " "-[-----" :skip-pair-string "-------")
234
235(define-electric-pair-test find-matching-different-paren-type-inside-list
236 "( ()]) " "-[-----" :skip-pair-string "-------")
237
238(define-electric-pair-test ignore-different-unmatching-paren-type
239 "( ()]) " "-(-----" :skip-pair-string "-p-----")
240
241(define-electric-pair-test autopair-keep-least-amount-of-mixed-unbalance
242 "( ()] " "-(-----" :skip-pair-string "-p-----")
243
244(define-electric-pair-test dont-autopair-to-resolve-mixed-unbalance
245 "( ()] " "-[-----" :skip-pair-string "-------")
246
247(define-electric-pair-test autopair-so-as-not-to-worsen-unbalance-situation
248 "( (]) " "-[-----" :skip-pair-string "-p-----")
249
250(define-electric-pair-test skip-over-partially-balanced
251 " [([]) " "-----)---" :skip-pair-string "-----s---")
252
253(define-electric-pair-test only-skip-over-at-least-partially-balanced-stuff
254 " [([()) " "-----))--" :skip-pair-string "-----s---")
255
256
257
258
259;;; Quotes
260;;;
261(define-electric-pair-test pair-some-quotes-skip-others
262 " \"\" " "-\"\"-----" :skip-pair-string "-ps------"
263 :test-in-strings nil
264 :bindings `((electric-pair-text-syntax-table
265 . ,prog-mode-syntax-table)))
266
267(define-electric-pair-test skip-single-quotes-in-ruby-mode
268 " '' " "--'-" :skip-pair-string "--s-"
269 :modes '(ruby-mode)
270 :test-in-comments nil
271 :test-in-strings nil
272 :bindings `((electric-pair-text-syntax-table
273 . ,prog-mode-syntax-table)))
274
275(define-electric-pair-test leave-unbalanced-quotes-alone
276 " \"' " "-\"'-" :skip-pair-string "----"
277 :modes '(ruby-mode)
278 :test-in-strings nil
279 :bindings `((electric-pair-text-syntax-table
280 . ,prog-mode-syntax-table)))
281
282(define-electric-pair-test leave-unbalanced-quotes-alone-2
283 " \"\\\"' " "-\"--'-" :skip-pair-string "------"
284 :modes '(ruby-mode)
285 :test-in-strings nil
286 :bindings `((electric-pair-text-syntax-table
287 . ,prog-mode-syntax-table)))
288
289(define-electric-pair-test leave-unbalanced-quotes-alone-3
290 " foo\\''" "'------" :skip-pair-string "-------"
291 :modes '(ruby-mode)
292 :test-in-strings nil
293 :bindings `((electric-pair-text-syntax-table
294 . ,prog-mode-syntax-table)))
295
296(define-electric-pair-test inhibit-only-if-next-is-mismatched
297 "\"foo\"\"bar" "\""
298 :expected-string "\"\"\"foo\"\"bar"
299 :expected-point 2
300 :test-in-strings nil
301 :bindings `((electric-pair-text-syntax-table
302 . ,prog-mode-syntax-table)))
303
304
305;;; More quotes, but now don't bind `electric-pair-text-syntax-table'
306;;; to `prog-mode-syntax-table'. Use the defaults for
307;;; `electric-pair-pairs' and `electric-pair-text-pairs'.
308;;;
309(define-electric-pair-test pairing-skipping-quotes-in-code
310 " \"\" " "-\"\"-----" :skip-pair-string "-ps------"
311 :test-in-strings nil
312 :test-in-comments nil)
313
314(define-electric-pair-test skipping-quotes-in-comments
315 " \"\" " "--\"-----" :skip-pair-string "--s------"
316 :test-in-strings nil)
317
318
319;;; Skipping over whitespace
320;;;
321(define-electric-pair-test whitespace-jumping
322 " ( ) " "--))))---" :expected-string " ( ) " :expected-point 8
323 :bindings '((electric-pair-skip-whitespace . t)))
324
325(define-electric-pair-test whitespace-chomping
326 " ( ) " "--)------" :expected-string " () " :expected-point 4
327 :bindings '((electric-pair-skip-whitespace . chomp)))
328
329(define-electric-pair-test whitespace-chomping-2
330 " ( \n\t\t\n ) " "--)------" :expected-string " () " :expected-point 4
331 :bindings '((electric-pair-skip-whitespace . chomp))
332 :test-in-comments nil)
333
334(define-electric-pair-test whitespace-chomping-dont-cross-comments
335 " ( \n\t\t\n ) " "--)------" :expected-string " () \n\t\t\n ) "
336 :expected-point 4
337 :bindings '((electric-pair-skip-whitespace . chomp))
338 :test-in-strings nil
339 :test-in-code nil
340 :test-in-comments t)
341
342
343;;; Pairing arbitrary characters
344;;;
345(define-electric-pair-test angle-brackets-everywhere
346 "<>" "<>" :skip-pair-string "ps"
347 :bindings '((electric-pair-pairs . ((?\< . ?\>)))))
348
349(define-electric-pair-test angle-brackets-everywhere-2
350 "(<>" "-<>" :skip-pair-string "-ps"
351 :bindings '((electric-pair-pairs . ((?\< . ?\>)))))
352
353(defvar electric-pair-test-angle-brackets-table
354 (let ((table (make-syntax-table prog-mode-syntax-table)))
355 (modify-syntax-entry ?\< "(>" table)
356 (modify-syntax-entry ?\> ")<`" table)
357 table))
358
359(define-electric-pair-test angle-brackets-pair
360 "<>" "<" :expected-string "<><>" :expected-point 2
361 :test-in-code nil
362 :bindings `((electric-pair-text-syntax-table
363 . ,electric-pair-test-angle-brackets-table)))
364
365(define-electric-pair-test angle-brackets-skip
366 "<>" "->" :expected-string "<>" :expected-point 3
367 :test-in-code nil
368 :bindings `((electric-pair-text-syntax-table
369 . ,electric-pair-test-angle-brackets-table)))
370
371(define-electric-pair-test pair-backtick-and-quote-in-comments
372 ";; " "---`" :expected-string ";; `'" :expected-point 5
373 :test-in-comments nil
374 :test-in-strings nil
375 :modes '(emacs-lisp-mode)
376 :bindings '((electric-pair-text-pairs . ((?\` . ?\')))))
377
378(define-electric-pair-test skip-backtick-and-quote-in-comments
379 ";; `foo'" "-------'" :expected-string ";; `foo'" :expected-point 9
380 :test-in-comments nil
381 :test-in-strings nil
382 :modes '(emacs-lisp-mode)
383 :bindings '((electric-pair-text-pairs . ((?\` . ?\')))))
384
385(define-electric-pair-test pair-backtick-and-quote-in-strings
386 "\"\"" "-`" :expected-string "\"`'\"" :expected-point 3
387 :test-in-comments nil
388 :test-in-strings nil
389 :modes '(emacs-lisp-mode)
390 :bindings '((electric-pair-text-pairs . ((?\` . ?\')))))
391
392(define-electric-pair-test skip-backtick-and-quote-in-strings
393 "\"`'\"" "--'" :expected-string "\"`'\"" :expected-point 4
394 :test-in-comments nil
395 :test-in-strings nil
396 :modes '(emacs-lisp-mode)
397 :bindings '((electric-pair-text-pairs . ((?\` . ?\')))))
398
399(define-electric-pair-test skip-backtick-and-quote-in-strings-2
400 " \"`'\"" "----'" :expected-string " \"`'\"" :expected-point 6
401 :test-in-comments nil
402 :test-in-strings nil
403 :modes '(emacs-lisp-mode)
404 :bindings '((electric-pair-text-pairs . ((?\` . ?\')))))
405
406
407;;; `js-mode' has `electric-layout-rules' for '{ and '}
408;;;
409(define-electric-pair-test js-mode-braces
410 "" "{" :expected-string "{}" :expected-point 2
411 :modes '(js-mode)
412 :fixture-fn #'(lambda ()
413 (electric-pair-mode 1)))
414
415(define-electric-pair-test js-mode-braces-with-layout
416 "" "{" :expected-string "{\n\n}" :expected-point 3
417 :modes '(js-mode)
418 :test-in-comments nil
419 :test-in-strings nil
420 :fixture-fn #'(lambda ()
421 (electric-layout-mode 1)
422 (electric-pair-mode 1)))
423
424(define-electric-pair-test js-mode-braces-with-layout-and-indent
425 "" "{" :expected-string "{\n \n}" :expected-point 7
426 :modes '(js-mode)
427 :test-in-comments nil
428 :test-in-strings nil
429 :fixture-fn #'(lambda ()
430 (electric-pair-mode 1)
431 (electric-indent-mode 1)
432 (electric-layout-mode 1)))
433
434
435;;; Backspacing
436;;; TODO: better tests
437;;;
438(ert-deftest electric-pair-backspace-1 ()
439 (save-electric-modes
440 (with-temp-buffer
441 (insert "()")
442 (goto-char 2)
443 (electric-pair-backward-delete-char 1)
444 (should (equal "" (buffer-string))))))
445
446
447;;; Electric newlines between pairs
448;;; TODO: better tests
449(ert-deftest electric-pair-open-extra-newline ()
450 (save-electric-modes
451 (with-temp-buffer
452 (c-mode)
453 (electric-pair-mode 1)
454 (electric-indent-mode 1)
455 (insert "int main {}")
456 (backward-char 1)
457 (let ((c-basic-offset 4))
458 (newline 1 t)
459 (should (equal "int main {\n \n}"
460 (buffer-string)))
461 (should (equal (point) (- (point-max) 2)))))))
462
463
464
465;;; Autowrapping
466;;;
467(define-electric-pair-test autowrapping-1
468 "foo" "(" :expected-string "(foo)" :expected-point 2
469 :fixture-fn #'(lambda ()
470 (electric-pair-mode 1)
471 (mark-sexp 1)))
472
473(define-electric-pair-test autowrapping-2
474 "foo" ")" :expected-string "(foo)" :expected-point 6
475 :fixture-fn #'(lambda ()
476 (electric-pair-mode 1)
477 (mark-sexp 1)))
478
479(define-electric-pair-test autowrapping-3
480 "foo" ")" :expected-string "(foo)" :expected-point 6
481 :fixture-fn #'(lambda ()
482 (electric-pair-mode 1)
483 (goto-char (point-max))
484 (skip-chars-backward "\"")
485 (mark-sexp -1)))
486
487(define-electric-pair-test autowrapping-4
488 "foo" "(" :expected-string "(foo)" :expected-point 2
489 :fixture-fn #'(lambda ()
490 (electric-pair-mode 1)
491 (goto-char (point-max))
492 (skip-chars-backward "\"")
493 (mark-sexp -1)))
494
495(define-electric-pair-test autowrapping-5
496 "foo" "\"" :expected-string "\"foo\"" :expected-point 2
497 :fixture-fn #'(lambda ()
498 (electric-pair-mode 1)
499 (mark-sexp 1)))
500
501(define-electric-pair-test autowrapping-6
502 "foo" "\"" :expected-string "\"foo\"" :expected-point 6
503 :fixture-fn #'(lambda ()
504 (electric-pair-mode 1)
505 (goto-char (point-max))
506 (skip-chars-backward "\"")
507 (mark-sexp -1)))
508
509(provide 'electric-tests)
510;;; electric-tests.el ends here