From: joaotavora@gmail.com (João Távora)
To: Stefan Monnier <monnier@IRO.UMontreal.CA>
Cc: emacs-devel@gnu.org
Subject: Re: [patch] make electric-pair-mode smarter/more useful
Date: Sun, 15 Dec 2013 22:10:08 +0000 [thread overview]
Message-ID: <87ppoxepfj.fsf@gmail.com> (raw)
In-Reply-To: <jwvd2l15wja.fsf-monnier+emacs@gnu.org> (Stefan Monnier's message of "Thu, 12 Dec 2013 21:32:45 -0500")
Hi,
Here is a new patch. I integrated most your suggestions and I hope it'll
be in time for the feature freeze.
Also the `electric-pair-newline-between-pairs-rule' rule in
`electric-layout-rules'. Which I think should be global, but I'll reply
to those mails separately.
João
Stefan Monnier <monnier@IRO.UMontreal.CA> writes:
>>> I think this change is for the worst. I often rely on ".." pairing in
>>> things like text-mode where they don't have `string' syntax.
>> But if you "rely" on those in text-mode, isn't the correct thing to give
>> them that syntax there?
> Why would it be a problem to keep them there?
It's not, not anymore.
> Still: you changed the default on the grounds that "it doesn't work
> well", but it's worked well enough so far.
It didn't work well when you try to make the new default be inhibit- and
skip predicates that attempt to balance quotes (among other things). But
now that the order of lookup has been inverted and I can do that.
>>> I suggest you use "text" rather than "non-code" in the variable names
>>> (and indeed, the behavior in strings and comments should largely be the
>>> same as in text-mode).
>> I disagree, but won't insist.
> On which part (the quoted text has at least 2 separate arguments)?
Sorry, the second one. I like comments and strings to have prog-mode
syntax, so I can get half-assed quote balancing there. So in my config
I'll set `electric-pair-text-syntax-table'to `prog-mode-syntax-table'.
>>> (save-excursion (car (syntax-ppss (point-max))))
>> This works... but only for one kind of parenthesis. It incorrectly
>> reports 0 for a buffer with '[)',
> Ah, I guess that indeed introduces some complications, indeed.
> Thanks for clearing things up.
I've spent a substantial amount of time analysing the balancing problem
more thoroughly. Turns out, the mixed-parens situation is not the only
counter-example to your extreme simplification
(save-excursion (car (syntax-ppss (point-max))))
The buffer situation "())", for example, has too many closers. But, to
help balance, you want it to *not* autopair at the beginning and *do*
autopair at the end. There is a converse "too-many-openers" situation
for skipping. And then there are the mixed-parens situations.
I don't have a formalism for it (though it might be interesting to
create one, if one is so inclined), but do have a lots of unit tests,
that I invite you to inspect (especially their docstrings, which say
things like)
With " (]) ", try input ) at point 5. Should stay " (]) " and
point at 6
With " (())) ", try input ( at point 1. Should become "( (())) " and
point at 2
So, to summarize, it appears one does need an algorithm a little bit
more complicated and one that performs a kind of local search of
successive uplisting. Since the last patch, I've discovered and fixed
some balancing bugs and condensed such an algorithm in the function
`electric-pair--balance-info'. I've integrated your suggestions, using
`syntax-ppss' whenever possible.
It can probably be made simpler, but passes all the tests and seems now
as usable as autopair.el, if not more.
Your simplification for `electric-pair--looking-at-mismatched-string-p'
was also found to fail some tests, so I kept my previous naive
version. But it's not too many tests and I would not mind if you revert
it.
> You might give this obvious example somewhere in a comment.
I added a section there in the middle explaining the gist of it.
>>> So I think we need a "electric-pair-preserve-balance" flag to control
>>> those features.
>> OK, but I would set it to t.
> Yes, it should default to t (unless too many people complain).
Here too, I've kind of changed my mind :-) Balancing can already be
turned off by customizing two variables:
electric-pair-inhibit-predicate
electric-pair-skip-self
To disable balancing, we can set these to t and nil respectively. If not
we could also add function `electric-pair-toggle-balancing' that sets
the two variables locally for the buffers.
Lastly, if you insist, then do create `electric-pair-preserve-balance'
variable and set the above two vars to 2 new "default" functions that
check it and delegate to the "balance" functions appropriately. However
i think that defeats the kind simplicity of the defcustom (though I
don't much use `custom').
>>> The priorities can be added as symbol properties, so the "sort" function
Done.
>>>> +(defvar electric-pair-non-code-syntax-table prog-mode-syntax-table
>>> Why prog-mode-syntax-table, rather than (say) text-mode-syntax-table?
>> Explained above, but I don't object to text-mode-syntax-table.
> Can you give more concrete examples?
Explained above again. Using prog-mode-syntax-table allows me to get
some quote balancing in comments and strings.
>>> BTW, syntax-ppss will get majorly confused if you call it while
>>> a different syntax-table is temporarily installed.
>> Never bit me, but thanks for the heads-up.
> It rarely bites, because in most cases syntax-ppss is pre-computed
Anyway, I took the advice seriously and should have gotten rid of such
potential problems.
I created a `electric-pair--syntax-ppss' function that calls
`parse-partial-sexp' when it detects it's inside a comment. It also does
when it detects it's in c-mode or c++-mode, since in my testing
syntax-ppss is sometimes broken there (tried in in src/syntax.c)
>> shoudn't newline-and-indent also call the post-self-insertion hooks?
> I guess so, yes.
OK. And is the `(not (or executing-kbd-macro noninteractive))' valid?
OTOH if `newline-and-indent' is somehow unfavoured over
`electric-indent-mode', we could leave it as is to encourage people to
move.
diff --git a/lisp/electric.el b/lisp/electric.el
index 91b99b4..c2bbbb5 100644
--- a/lisp/electric.el
+++ b/lisp/electric.el
@@ -187,6 +187,22 @@ Returns nil when we can't find this char."
(eq (char-before) last-command-event)))))
pos)))
+(put 'electric-pair-post-self-insert-function 'priority 20)
+(put 'electric-layout-post-self-insert-function 'priority 40)
+(put 'electric-indent-post-self-insert-function 'priority 60)
+(put 'blink-paren-post-self-insert-function 'priority 100)
+
+(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
@@ -295,20 +311,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 +332,122 @@ 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-skip-if-helps-balance
"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-skip-if-helps-balance)
+ (const :tag "Always skip" t)
+ function))
(defcustom electric-pair-inhibit-predicate
- #'electric-pair-default-inhibit
+ #'electric-pair-inhibit-if-helps-balance
"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-inhibit-if-helps-balance)
(const :tag "Always pair" ignore)
function))
-(defun electric-pair-default-inhibit (char)
+(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-skip-whitespace t
+ "If non-nil skip whitespace when skipping over closing parens.
+
+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))
+
+(defvar electric-pair-text-syntax-table text-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 +458,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 &optional offset)
+ "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 +499,262 @@ 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-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 -1)
+ (`(,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))
+ (skip-chars-forward "\n\t\s"))
+ (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
+ (skip-chars-forward "\n\t\s"))
+ (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))))))))
(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
@@ -442,10 +769,19 @@ closing parenthesis. \(Likewise for brackets, etc.)
See options `electric-pair-pairs' and `electric-pair-skip-self'."
:global t :group 'electricity
+ :keymap (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)
(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
@@ -455,12 +791,18 @@ See options `electric-pair-pairs' and `electric-pair-skip-self'."
;;; Electric newlines after/before/around some chars.
-(defvar electric-layout-rules '()
+(defvar electric-layout-rules
+ `((?\n . ,#'electric-pair-newline-between-pairs-rule))
"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 +811,39 @@ 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)
+ (case 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)))))
+(defun electric-pair-newline-between-pairs-rule ()
+ (when (and electric-pair-mode
+ (not (eobp))
+ (eq (save-excursion
+ (skip-chars-backward "\t\s")
+ (char-before (1- (point))))
+ (matching-paren (char-after))))
+ 'after-stay))
+
;;;###autoload
(define-minor-mode electric-layout-mode
"Automatically insert newlines around some chars.
@@ -494,11 +852,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 f4e9b31..5194e73 100644
--- a/lisp/emacs-lisp/lisp-mode.el
+++ b/lisp/emacs-lisp/lisp-mode.el
@@ -472,7 +472,12 @@ 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))
(defun lisp-outline-level ()
"Lisp mode `outline-level' function."
diff --git a/lisp/simple.el b/lisp/simple.el
index 260c170..c591cee 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -607,7 +607,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 (not (or executing-kbd-macro noninteractive)))
(indent-according-to-mode))
(defun reindent-then-newline-and-indent ()
diff --git a/test/automated/electric-tests.el b/test/automated/electric-tests.el
new file mode 100644
index 0000000..c31808e
--- /dev/null
+++ b/test/automated/electric-tests.el
@@ -0,0 +1,482 @@
+;;; electric-tests.el --- tests for electric.el -*- lexical-binding: t; -*-
+
+;; 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)
+ (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
+ (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))))))
+\f
+;;; 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-")
+
+\f
+;;; 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---")
+
+
+
+\f
+;;; 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)))
+
+\f
+;;; 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)
+
+\f
+;;; 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)))
+
+\f
+;;; 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 . ((?\` . ?\')))))
+
+\f
+;;; `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)))
+
+\f
+;;; 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))))))
+
+\f
+;;; 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
next prev parent reply other threads:[~2013-12-15 22:10 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
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 [this message]
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=87ppoxepfj.fsf@gmail.com \
--to=joaotavora@gmail.com \
--cc=emacs-devel@gnu.org \
--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.