]> code.delx.au - gnu-emacs/blobdiff - lisp/elec-pair.el
Speed up CC Mode fontification with less accurate functions extending region
[gnu-emacs] / lisp / elec-pair.el
index f38320047512419d78e9116aa5d45cd3cc765b26..116292027cd614f74f84d03e9ac2564c4424935d 100644 (file)
@@ -1,6 +1,6 @@
 ;;; elec-pair.el --- Automatic parenthesis pairing  -*- lexical-binding:t -*-
 
-;; Copyright (C) 2013 Free Software Foundation, Inc.
+;; Copyright (C) 2013-2016 Free Software Foundation, Inc.
 
 ;; Author: João Távora <joaotavora@gmail.com>
 
@@ -92,8 +92,8 @@ closer."
 
 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
+predicates responsible for making decisions on whether to pair/skip some
+characters based on the actual state of the buffer's parentheses and
 quotes."
   :version "24.4"
   :group 'electricity
@@ -163,38 +163,9 @@ return value is considered instead."
   "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
@@ -213,9 +184,10 @@ 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))))))
+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))
@@ -250,58 +222,64 @@ inside a comment of string."
 (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 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 DIRECTIONS's sign.
+  "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 descritions (MATCHED-P . PAIR) for the
-innermost and outermost lists that enclose point. The outermost
+Return a cons of two descriptions (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.
+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.
@@ -350,7 +328,7 @@ point is not enclosed by any lists, return ((T) (T))."
           (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.
@@ -363,29 +341,39 @@ point is not enclosed by any lists, return ((T) (T))."
                   (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)
@@ -408,7 +396,7 @@ happened."
                           (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)
@@ -484,10 +472,13 @@ happened."
                            (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
@@ -508,12 +499,12 @@ happened."
                   (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))
+                  (< (1+ (point-min)) (point) (point-max))
                   (eq (save-excursion
                         (skip-chars-backward "\t\s")
                         (char-before (1- (point))))
@@ -527,14 +518,34 @@ happened."
        (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'.")
 
@@ -547,7 +558,8 @@ 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.)."
+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
@@ -561,6 +573,19 @@ closing parenthesis.  \(Likewise for brackets, etc.)."
     (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