declare smobs in alloc.c
[bpt/emacs.git] / lisp / electric.el
index 6a31ba1..4bf5963 100644 (file)
@@ -1,9 +1,10 @@
 ;;; electric.el --- window maker and Command loop for `electric' modes
 
 ;;; electric.el --- window maker and Command loop for `electric' modes
 
-;; Copyright (C) 1985-1986, 1995, 2001-2012 Free Software Foundation, Inc.
+;; Copyright (C) 1985-1986, 1995, 2001-2014 Free Software Foundation,
+;; Inc.
 
 ;; Author: K. Shane Hartman
 
 ;; Author: K. Shane Hartman
-;; Maintainer: FSF
+;; Maintainer: emacs-devel@gnu.org
 ;; Keywords: extensions
 
 ;; This file is part of GNU Emacs.
 ;; Keywords: extensions
 
 ;; This file is part of GNU Emacs.
@@ -38,8 +39,6 @@
 
 ;;; Code:
 
 
 ;;; Code:
 
-(eval-when-compile (require 'cl))
-
 ;; This loop is the guts for non-standard modes which retain control
 ;; until some event occurs.  It is a `do-forever', the only way out is
 ;; to throw.  It assumes that you have set up the keymap, window, and
 ;; This loop is the guts for non-standard modes which retain control
 ;; until some event occurs.  It is a `do-forever', the only way out is
 ;; to throw.  It assumes that you have set up the keymap, window, and
@@ -79,8 +78,6 @@
       (setq last-command-event (aref cmd (1- (length cmd)))
            this-command (key-binding cmd t)
            cmd this-command)
       (setq last-command-event (aref cmd (1- (length cmd)))
            this-command (key-binding cmd t)
            cmd this-command)
-      ;; This makes universal-argument-other-key work.
-      (setq universal-argument-num-events 0)
       (if (or (prog1 quit-flag (setq quit-flag nil))
              (eq last-input-event ?\C-g))
          (progn (setq unread-command-events nil
       (if (or (prog1 quit-flag (setq quit-flag nil))
              (eq last-input-event ?\C-g))
          (progn (setq unread-command-events nil
@@ -190,7 +187,18 @@ Returns nil when we can't find this char."
                            (eq (char-before) last-command-event)))))
       pos)))
 
                            (eq (char-before) last-command-event)))))
       pos)))
 
-;; Electric indentation.
+(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
 ;; should usually set this variable by adding elements to the default
 
 ;; Autoloading variables is generally undesirable, but major modes
 ;; should usually set this variable by adding elements to the default
@@ -205,7 +213,26 @@ Each function is called with one argument (the inserted char), with
 point right after that char, and it should return t to cause indentation,
 `no-indent' to prevent indentation or nil to let other functions decide.")
 
 point right after that char, and it should return t to cause indentation,
 `no-indent' to prevent indentation or nil to let other functions decide.")
 
+(defvar-local electric-indent-inhibit nil
+  "If non-nil, reindentation is not appropriate for this buffer.
+This should be set by major modes such as `python-mode' since
+Python does not lend itself to fully automatic indentation.")
+
+(defvar electric-indent-functions-without-reindent
+  '(indent-relative indent-to-left-margin indent-relative-maybe
+    py-indent-line coffee-indent-line org-indent-line yaml-indent-line
+    haskell-indentation-indent-line haskell-indent-cycle haskell-simple-indent
+    yaml-indent-line)
+  "List of indent functions that can't reindent.
+If `line-indent-function' is one of those, then `electric-indent-mode' will
+not try to reindent lines.  It is normally better to make the major
+mode set `electric-indent-inhibit', but this can be used as a workaround.")
+
 (defun electric-indent-post-self-insert-function ()
 (defun electric-indent-post-self-insert-function ()
+  "Function that `electric-indent-mode' adds to `post-self-insert-hook'.
+This indents if the hook `electric-indent-functions' returns non-nil,
+or if a member of `electric-indent-chars' was typed; but not in a string
+or comment."
   ;; FIXME: This reindents the current line, but what we really want instead is
   ;; to reindent the whole affected text.  That's the current line for simple
   ;; cases, but not all cases.  We do take care of the newline case in an
   ;; FIXME: This reindents the current line, but what we really want instead is
   ;; to reindent the whole affected text.  That's the current line for simple
   ;; cases, but not all cases.  We do take care of the newline case in an
@@ -232,27 +259,54 @@ point right after that char, and it should return t to cause indentation,
                     (unless (eq act 'do-indent) (nth 8 (syntax-ppss))))))))
       ;; For newline, we want to reindent both lines and basically behave like
       ;; reindent-then-newline-and-indent (whose code we hence copied).
                     (unless (eq act 'do-indent) (nth 8 (syntax-ppss))))))))
       ;; For newline, we want to reindent both lines and basically behave like
       ;; reindent-then-newline-and-indent (whose code we hence copied).
-      (when (< (1- pos) (line-beginning-position))
-        (let ((before (copy-marker (1- pos) t)))
-          (save-excursion
-            (unless (memq indent-line-function
-                          '(indent-relative indent-to-left-margin
-                                            indent-relative-maybe))
-              ;; Don't reindent the previous line if the indentation function
-              ;; is not a real one.
+      (let ((at-newline (<= pos (line-beginning-position))))
+        (when at-newline
+          (let ((before (copy-marker (1- pos) t)))
+            (save-excursion
+              (unless (or (memq indent-line-function
+                                electric-indent-functions-without-reindent)
+                          electric-indent-inhibit)
+                ;; Don't reindent the previous line if the indentation function
+                ;; is not a real one.
+                (goto-char before)
+                (indent-according-to-mode))
+              ;; We are at EOL before the call to indent-according-to-mode, and
+              ;; after it we usually are as well, but not always.  We tried to
+              ;; address it with `save-excursion' but that uses a normal marker
+              ;; whereas we need `move after insertion', so we do the
+              ;; save/restore by hand.
               (goto-char before)
               (goto-char before)
-              (indent-according-to-mode))
-            ;; We are at EOL before the call to indent-according-to-mode, and
-            ;; after it we usually are as well, but not always.  We tried to
-            ;; address it with `save-excursion' but that uses a normal marker
-            ;; whereas we need `move after insertion', so we do the
-            ;; save/restore by hand.
-            (goto-char before)
-            ;; Remove the trailing whitespace after indentation because
-            ;; indentation may (re)introduce the whitespace.
-            (delete-horizontal-space t))))
-      (unless (memq indent-line-function '(indent-to-left-margin))
-        (indent-according-to-mode)))))
+              (when (eolp)
+                ;; Remove the trailing whitespace after indentation because
+                ;; indentation may (re)introduce the whitespace.
+                (delete-horizontal-space t)))))
+        (unless (and electric-indent-inhibit
+                     (not at-newline))
+          (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")
+  (let ((electric-indent-mode nil))
+    (newline arg 'interactive)))
+
+;;;###autoload
+(define-key global-map "\C-j" 'electric-newline-and-maybe-indent)
+;;;###autoload
+(defun electric-newline-and-maybe-indent ()
+  "Insert a newline.
+If `electric-indent-mode' is enabled, that's that, but if it
+is *disabled* then additionally indent according to major mode.
+Indentation is done using the value of `indent-line-function'.
+In programming language modes, this is the same as TAB.
+In some text modes, where TAB inserts a tab, this command indents to the
+column specified by the function `current-left-margin'."
+  (interactive "*")
+  (if electric-indent-mode
+      (electric-indent-just-newline nil)
+    (newline-and-indent)))
 
 ;;;###autoload
 (define-minor-mode electric-indent-mode
 
 ;;;###autoload
 (define-minor-mode electric-indent-mode
@@ -261,129 +315,51 @@ With a prefix argument ARG, enable Electric Indent mode if ARG is
 positive, and disable it otherwise.  If called from Lisp, enable
 the mode if ARG is omitted or nil.
 
 positive, and disable it otherwise.  If called from Lisp, enable
 the mode if ARG is omitted or nil.
 
-This is a global minor mode.  When enabled, it reindents whenever
-the hook `electric-indent-functions' returns non-nil, or you
-insert a character from `electric-indent-chars'."
-  :global t
-  :group 'electricity
+When enabled, this reindents whenever the hook `electric-indent-functions'
+returns non-nil, or if you insert a character from `electric-indent-chars'.
+
+This is a global minor mode.  To toggle the mode in a single buffer,
+use `electric-indent-local-mode'."
+  :global t :group 'electricity
+  :initialize 'custom-initialize-delay
+  :init-value t
   (if (not electric-indent-mode)
   (if (not electric-indent-mode)
-      (remove-hook 'post-self-insert-hook
-                   #'electric-indent-post-self-insert-function)
-    ;; 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.
+      (unless (catch 'found
+                (dolist (buf (buffer-list))
+                  (with-current-buffer buf
+                    (if electric-indent-mode (throw 'found t)))))
+        (remove-hook 'post-self-insert-hook
+                     #'electric-indent-post-self-insert-function))
     (add-hook 'post-self-insert-hook
     (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 pairing.
-
-(defcustom electric-pair-pairs
-  '((?\" . ?\"))
-  "Alist of pairs that should be used regardless of major mode."
-  :group 'electricity
-  :version "24.1"
-  :type '(repeat (cons character character)))
-
-(defcustom electric-pair-skip-self t
-  "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."
-  :group 'electricity
-  :version "24.1"
-  :type 'boolean)
-
-(defun electric-pair-post-self-insert-function ()
-  (let* ((syntax (and (eq (char-before) last-command-event) ; Sanity check.
-                      electric-pair-mode
-                      (let ((x (assq last-command-event electric-pair-pairs)))
-                        (cond
-                         (x (if (eq (car x) (cdr x)) ?\" ?\())
-                         ((rassq last-command-event electric-pair-pairs) ?\))
-                         (t (char-syntax last-command-event))))))
-         ;; FIXME: when inserting the closer, we should maybe use
-         ;; self-insert-command, although it may prove tricky running
-         ;; post-self-insert-hook recursively, and we wouldn't want to trigger
-         ;; blink-matching-open.
-         (closer (if (eq syntax ?\()
-                     (cdr (or (assq last-command-event electric-pair-pairs)
-                              (aref (syntax-table) last-command-event)))
-                   last-command-event)))
-    (cond
-     ;; Wrap a pair around the active region.
-     ((and (memq syntax '(?\( ?\" ?\$)) (use-region-p))
-      (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-char -1)
-        (save-excursion
-          (goto-char (mark))
-          (insert last-command-event)))
-      (insert closer))
-     ;; Backslash-escaped: no pairing, no skipping.
-     ((save-excursion
-        (goto-char (1- (point)))
-        (not (zerop (% (skip-syntax-backward "\\") 2))))
-      nil)
-     ;; Skip self.
-     ((and (memq syntax '(?\) ?\" ?\$))
-           electric-pair-skip-self
-           (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.
-      (delete-char 1))
-     ;; Insert matching pair.
-     ((not (or (not (memq syntax `(?\( ?\" ?\$)))
-               overwrite-mode
-               ;; I find it more often preferable not to pair when the
-               ;; same char is next.
-               (eq last-command-event (char-after))
-               (eq last-command-event (char-before (1- (point))))
-               ;; I also find it often preferable not to pair next to a word.
-               (eq (char-syntax (following-char)) ?w)))
-      (save-excursion (insert closer))))))
+              #'electric-indent-post-self-insert-function)
+    (electric--sort-post-self-insertion-hook)))
 
 ;;;###autoload
 
 ;;;###autoload
-(define-minor-mode electric-pair-mode
-  "Toggle automatic parens pairing (Electric Pair mode).
-With a prefix argument ARG, enable Electric Pair mode if ARG is
-positive, and disable it otherwise.  If called from Lisp, enable
-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'."
-  :global t
-  :group 'electricity
-  (if electric-pair-mode
-      (add-hook 'post-self-insert-hook
-                #'electric-pair-post-self-insert-function)
-    (remove-hook 'post-self-insert-hook
-                 #'electric-pair-post-self-insert-function)))
+(define-minor-mode electric-indent-local-mode
+  "Toggle `electric-indent-mode' only in this buffer."
+  :variable (buffer-local-value 'electric-indent-mode (current-buffer))
+  (cond
+   ((eq electric-indent-mode (default-value 'electric-indent-mode))
+    (kill-local-variable 'electric-indent-mode))
+   ((not (default-value 'electric-indent-mode))
+    ;; Locally enabled, but globally disabled.
+    (electric-indent-mode 1)                ; Setup the hooks.
+    (setq-default electric-indent-mode nil) ; But keep it globally disabled.
+    )))
+
+;;; Electric newlines after/before/around some chars.
+
+(defvar electric-layout-rules nil
+  "List of rules saying where to automatically insert newlines.
 
 
-;; Automatically add newlines after/before/around some chars.
+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.
 
 
-(defvar electric-layout-rules '()
-  "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.")
+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)))
 
 (defun electric-layout-post-self-insert-function ()
   (let* ((rule (cdr (assq last-command-event electric-layout-rules)))
@@ -392,23 +368,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)))))
                (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 (point-marker))
+            (sym (if (functionp rule) (funcall rule) rule)))
+        (set-marker-insertion-type end (not (eq sym 'after-stay)))
         (goto-char pos)
         (goto-char pos)
-        (case (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: 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.
-          (before (goto-char (1- pos)) (skip-chars-backward " \t")
-                  (unless (bolp) (insert "\n")))
-          (after  (insert "\n"))       ; FIXME: check eolp before inserting \n?
-          (around (save-excursion
-                    (goto-char (1- pos)) (skip-chars-backward " \t")
-                    (unless (bolp) (insert "\n")))
-                  (insert "\n")))      ; FIXME: check eolp before inserting \n?
+          ;;
+          ;; FIXME: check eolp before inserting \n?
+          (`before (goto-char (1- pos)) (skip-chars-backward " \t")
+                   (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 end)))))
 
         (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.
 ;;;###autoload
 (define-minor-mode electric-layout-mode
   "Automatically insert newlines around some chars.
@@ -416,13 +401,14 @@ With a prefix argument ARG, enable Electric Layout mode if ARG is
 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."
 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)))
+  :global t :group 'electricity
+  (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)
 
 
 (provide 'electric)