;;; elec-pair.el --- Automatic parenthesis pairing -*- lexical-binding:t -*-
-;; Copyright (C) 2013-2014 Free Software Foundation, Inc.
+;; Copyright (C) 2013-2016 Free Software Foundation, Inc.
;; Author: João Távora <joaotavora@gmail.com>
"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
+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
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))))))
+inside a comment or string."
+ (let* ((pre-string-or-comment (or (bobp)
+ (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))
(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
+WHERE is a 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)))
+ (quick-ppss (syntax-ppss pos))
+ (in-string (and (nth 3 quick-ppss) (memq 'string where)))
+ (in-comment (and (nth 4 quick-ppss) (memq 'comment where)))
+ (s-or-c-start (cond (in-string
+ (1+ (nth 8 quick-ppss)))
+ (in-comment
+ (goto-char (nth 8 quick-ppss))
+ (forward-comment (- (point-max)))
+ (skip-syntax-forward " >!")
+ (point)))))
+ (if s-or-c-start
(with-syntax-table electric-pair-text-syntax-table
- (parse-partial-sexp (1+ (nth 8 quick-ppss)) pos))
+ (parse-partial-sexp s-or-c-start 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.
+ quick-ppss))))
+
+;; 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 DIRECTION's sign.
STRING-OR-COMMENT is info suitable for running `parse-partial-sexp'.
Return a cons of two descriptions (MATCHED-P . PAIR) for the
-innermost and outermost lists that enclose point. The outermost
+innermost and outermost lists that enclose point. The outermost
list enclosing point is either the first top-level or first
mismatched list found by listing up.
-If the outermost list is matched, don't rely on its PAIR. If
-point is not enclosed by any lists, return ((T) . (T))."
+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
+ ;; called when `scan-sexps' ran perfectly, 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
+ ;; 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.
(scan-error
(cond ((or
;; some error happened and it is not of the "ended
- ;; prematurely" kind"...
+ ;; prematurely" kind...
(not (string-match "ends prematurely" (nth 1 err)))
;; ... or we were in a comment and just came out of
;; it.
(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 nil
- (progn (forward-sexp) nil)
- (scan-error t)))))
+(defvar electric-pair-string-bound-function 'point-max
+ "Next buffer position where strings are syntactically unexpected.
+Value is a function called with no arguments and returning a
+buffer position. Major modes should set this variable
+buffer-locally if they experience slowness with
+`electric-pair-mode' when pairing quotes.")
+
+(defun electric-pair--unbalanced-strings-p (char)
+ "Return non-nil if there are unbalanced strings started by CHAR."
+ (let* ((selector-ppss (syntax-ppss))
+ (relevant-ppss (save-excursion
+ (if (nth 4 selector-ppss) ; comment
+ (electric-pair--syntax-ppss
+ (progn
+ (goto-char (nth 8 selector-ppss))
+ (forward-comment (point-max))
+ (skip-syntax-backward " >!")
+ (point)))
+ (syntax-ppss
+ (funcall electric-pair-string-bound-function)))))
+ (string-delim (nth 3 relevant-ppss)))
+ (or (eq t string-delim)
+ (eq char string-delim))))
(defun electric-pair--inside-string-p (char)
- "Say if point is inside a string started by CHAR.
+ "Return non-nil 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))))
+ (let ((ppss (electric-pair--syntax-ppss (point) '(comment))))
(memq (nth 3 ppss) (list t char))))
(defun electric-pair-inhibit-if-helps-balance (char)
(t
(eq (cdr outermost) pair)))))
((eq syntax ?\")
- (electric-pair--looking-at-unterminated-string-p char))))
+ (electric-pair--unbalanced-strings-p char))))
(insert-char char)))))
(defun electric-pair-skip-if-helps-balance (char)
(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))
+ (when (and (not (and unconditional
+ (eq syntax ?\")))
+ (setq skip-whitespace-info
+ (if (and (not (eq electric-pair-skip-whitespace 'chomp))
+ (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
(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)
(memq (car (electric-pair-syntax-info last-command-event))
'(?\( ?\) ?\" ?\$))))
+(defun electric-pair-delete-pair (arg &optional killp)
+ "When between adjacent paired delimiters, delete both of them.
+ARG and KILLP are passed directly to
+`backward-delete-char-untabify', which see."
+ (interactive "*p\nP")
+ (delete-char 1)
+ (backward-delete-char-untabify arg killp))
+
(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)
+ (define-key map "\177"
+ `(menu-item
+ "" electric-pair-delete-pair
+ :filter
+ ,(lambda (cmd)
+ (let* ((prev (char-before))
+ (next (char-after))
+ (syntax-info (and prev
+ (electric-pair-syntax-info prev)))
+ (syntax (car syntax-info))
+ (pair (cadr syntax-info)))
+ (and next pair
+ (memq syntax '(?\( ?\" ?\$))
+ (eq pair next)
+ (if (functionp electric-pair-delete-adjacent-pairs)
+ (funcall electric-pair-delete-adjacent-pairs)
+ electric-pair-delete-adjacent-pairs)
+ cmd)))))
map)
"Keymap used by `electric-pair-mode'.")
Electric Pair mode is a global minor mode. When enabled, typing
an open parenthesis automatically inserts the corresponding
-closing parenthesis. \(Likewise for brackets, etc.)."
+closing parenthesis. (Likewise for brackets, etc.). To toggle
+the mode in a single buffer, use `electric-pair-local-mode'."
:global t :group 'electricity
(if electric-pair-mode
(progn
(remove-hook 'self-insert-uses-region-functions
#'electric-pair-will-use-region)))
+;;;###autoload
+(define-minor-mode electric-pair-local-mode
+ "Toggle `electric-pair-mode' only in this buffer."
+ :variable (buffer-local-value 'electric-pair-mode (current-buffer))
+ (cond
+ ((eq electric-pair-mode (default-value 'electric-pair-mode))
+ (kill-local-variable 'electric-pair-mode))
+ ((not (default-value 'electric-pair-mode))
+ ;; Locally enabled, but globally disabled.
+ (electric-pair-mode 1) ; Setup the hooks.
+ (setq-default electric-pair-mode nil) ; But keep it globally disabled.
+ )))
+
(provide 'elec-pair)
;;; elec-pair.el ends here