all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Bozhidar Batsov <bozhidar@batsov.com>
To: "João Távora" <joaotavora@gmail.com>
Cc: emacs-devel <emacs-devel@gnu.org>,
	Stefan Monnier <monnier@iro.umontreal.ca>,
	Dmitry Gutov <dgutov@yandex.ru>
Subject: Re: [patch] make electric-pair-mode smarter/more useful
Date: Tue, 24 Dec 2013 16:29:22 +0200	[thread overview]
Message-ID: <CAM9Zgm1ViiEAJccRppBiVx2LE+3fDgdu9iEQAvHJDg8ZSaJxfQ@mail.gmail.com> (raw)
In-Reply-To: <jjbd2knmxy8.fsf@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 58559 bytes --]

Not sure if Stefan saw your message here, but he mentioned in the "feature
freeze" thread that your patch is approved and can be installed. Have a
look at it.


On 23 December 2013 16:41, João Távora <joaotavora@gmail.com> wrote:

>
> Stefan,
>
> Don't want to be pushy, but is there anything holding up this patch?
>
> Here's yet another version that fixes some more details:
>
> - fixed default values for `electric-pair-inhibit-predicate' and
>   `electric-pair-skip-self'.
>
> - electric-pair-mode's docstring doesn't mention related customization
>   variables because there are now too many of them.
>
> - emacs-lisp/lisp-mode.el locally sets
>   `electric-pair-open-newline-between-pairs' to t.
>
> - the newline call in `newline-and-indent` now uses interactive set to
>   t
>
> Thanks
> João
>
> diff --git a/lisp/ChangeLog b/lisp/ChangeLog
> index 12889de..0564484 100644
> --- a/lisp/ChangeLog
> +++ b/lisp/ChangeLog
> @@ -1,3 +1,25 @@
> +2013-12-99  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-23  Chong Yidong  <cyd@gnu.org>
>
>         * subr.el (set-transient-map): Rename from
> diff --git a/lisp/electric.el b/lisp/electric.el
> index 91b99b4..ca4616b 100644
> --- a/lisp/electric.el
> +++ b/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,286 @@ 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))
> +             '(?\( ?\) ?\" ?\$))))
>
>  ;;;###autoload
>  (define-minor-mode electric-pair-mode
> @@ -438,29 +827,44 @@ 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)))
> +
> +(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'.")
>
>  ;;; 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)
>
> diff --git a/lisp/emacs-lisp/lisp-mode.el b/lisp/emacs-lisp/lisp-mode.el
> index b7bd33f..f1eae18 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."
>           (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."
> diff --git a/lisp/simple.el b/lisp/simple.el
> index a654351..624d87f 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
>  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.
> diff --git a/test/automated/electric-tests.el
> b/test/automated/electric-tests.el
> new file mode 100644
> index 0000000..aa4a063
> --- /dev/null
> +++ b/test/automated/electric-tests.el
> @@ -0,0 +1,509 @@
> +;;; 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:
> +
> +;;
> +
> +;;; 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-pair-tests)
> +;;; electric-pair-tests.el ends here
>
>

[-- Attachment #2: Type: text/html, Size: 71195 bytes --]

  reply	other threads:[~2013-12-24 14:29 UTC|newest]

Thread overview: 36+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-12-06 23:31 [patch] make electric-pair-mode smarter/more useful João Távora
2013-12-07  2:09 ` Leo Liu
2013-12-07  2:36 ` Dmitry Gutov
2013-12-07 21:01   ` João Távora
2013-12-07 23:16     ` Stefan Monnier
2013-12-12  3:05       ` João Távora
2013-12-12  4:29         ` Dmitry Gutov
2013-12-12 11:26           ` João Távora
2013-12-12 16:30           ` Stefan Monnier
2013-12-12 17:06             ` João Távora
2013-12-12 20:12               ` Stefan Monnier
2013-12-13  2:55               ` Dmitry Gutov
2013-12-14 15:18                 ` Stefan Monnier
2013-12-14 16:56                   ` Dmitry Gutov
2013-12-15  1:39                     ` Stefan Monnier
2013-12-16  0:35                       ` João Távora
2013-12-16  3:34                         ` Stefan Monnier
2013-12-16 19:26                           ` João Távora
2013-12-17  1:54                             ` Stefan Monnier
2013-12-18  2:43                               ` João Távora
2013-12-18 15:32                                 ` João Távora
2013-12-23 14:41                                   ` João Távora
2013-12-24 14:29                                     ` Bozhidar Batsov [this message]
2013-12-07 23:07 ` Stefan Monnier
2013-12-12  3:01   ` João Távora
2013-12-12 18:08     ` Stefan Monnier
2013-12-13  1:02       ` João Távora
2013-12-13  2:32         ` Stefan Monnier
2013-12-15 22:10           ` João Távora
2013-12-16  3:22             ` Stefan Monnier
2013-12-16 14:21               ` João Távora
2013-12-16 15:30                 ` Stefan Monnier
2013-12-16 18:40                   ` Stefan Monnier
2013-12-16 19:06                     ` João Távora
2013-12-17  1:42                       ` Stefan Monnier
     [not found]                   ` <CALDnm52AoShN891-L9=Cbng98UtYPEntzO+n_XDMmEL+UV0r-A@mail.gmail.com>
2013-12-16 19:02                     ` Fwd: " João Távora

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAM9Zgm1ViiEAJccRppBiVx2LE+3fDgdu9iEQAvHJDg8ZSaJxfQ@mail.gmail.com \
    --to=bozhidar@batsov.com \
    --cc=dgutov@yandex.ru \
    --cc=emacs-devel@gnu.org \
    --cc=joaotavora@gmail.com \
    --cc=monnier@iro.umontreal.ca \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.