changeset 110163:c9b982e5aefd

* lisp/electric.el (electricity): New group. (electric-indent-chars): New var. (electric-indent-post-self-insert-function): New fun. (electric-indent-mode): New minor mode. (electric-pair-skip-self): New custom. (electric-pair-post-self-insert-function): New function. (electric-pair-mode): New minor mode.
author Stefan Monnier <monnier@iro.umontreal.ca>
date Fri, 03 Sep 2010 15:06:51 +0200
parents 6ea012a6203d
children da442f29c7b6
files etc/NEWS lisp/ChangeLog lisp/electric.el
diffstat 3 files changed, 149 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/etc/NEWS	Fri Sep 03 13:26:08 2010 +0200
+++ b/etc/NEWS	Fri Sep 03 15:06:51 2010 +0200
@@ -427,6 +427,8 @@
 
 * New Modes and Packages in Emacs 24.1
 
+** New global minor modes electric-pair-mode and electric-indent-mode.
+
 ** pcase.el provides the ML-style pattern matching macro `pcase'.
 
 ** smie.el is a package providing a simple generic indentation engine.
--- a/lisp/ChangeLog	Fri Sep 03 13:26:08 2010 +0200
+++ b/lisp/ChangeLog	Fri Sep 03 15:06:51 2010 +0200
@@ -1,5 +1,13 @@
 2010-09-03  Stefan Monnier  <monnier@iro.umontreal.ca>
 
+	* electric.el (electricity): New group.
+	(electric-indent-chars): New var.
+	(electric-indent-post-self-insert-function): New fun.
+	(electric-indent-mode): New minor mode.
+	(electric-pair-skip-self): New custom.
+	(electric-pair-post-self-insert-function): New function.
+	(electric-pair-mode): New minor mode.
+
 	* calc/calc-aent.el (calcAlg-blink-matching-check): New fun, to replace
 	calcAlg-blink-matching-open.
 	(calc-alg-ent-map, calc-alg-ent-esc-map): Initialize in the declaration.
--- a/lisp/electric.el	Fri Sep 03 13:26:08 2010 +0200
+++ b/lisp/electric.el	Fri Sep 03 15:06:51 2010 +0200
@@ -24,10 +24,23 @@
 
 ;;; Commentary:
 
-; zaaaaaaap
+;; "Electric" has been used in Emacs to refer to different things.
+;; Among them:
+;;
+;; - electric modes and buffers: modes that typically pop-up in a modal kind of
+;;   way a transient buffer that automatically disappears as soon as the user
+;;   is done with it.
+;;
+;; - electric keys: self inserting keys which additionally perform some side
+;;   operation which happens to be often convenient at that time.  Examples of
+;;   such side operations are: reindenting code, inserting a newline,
+;;   ... auto-fill-mode and abbrev-mode can be considered as built-in forms of
+;;   electric key behavior.
 
 ;;; 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
@@ -157,6 +170,131 @@
 	(fit-window-to-buffer win max-height))
       win)))
 
+;;; Electric keys.
+
+(defgroup electricity ()
+  "Electric behavior for self inserting keys."
+  :group 'editing)
+
+;; Electric indentation.
+
+(defvar electric-indent-chars '(?\n)
+  "Characters that should cause automatic reindentation.")
+
+(defun electric-indent-post-self-insert-function ()
+  ;; 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
+  ;; ad-hoc fashion, but there are still missing cases such as the case of
+  ;; electric-pair-mode wrapping a region with a pair of parens.
+  ;; There might be a way to get it working by analyzing buffer-undo-list, but
+  ;; it looks challenging.
+  (when (and (memq last-command-event electric-indent-chars)
+             ;; Don't reindent while inserting spaces at beginning of line.
+             (or (not (memq last-command-event '(?\s ?\t)))
+                 (save-excursion (skip-chars-backward " \t") (not (bolp))))
+             ;; Not in a string or comment.
+             (not (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 (and (eq last-command-event ?\n)
+               ;; Sanity check.
+               (eq (char-before) last-command-event))
+      (let ((pos (copy-marker (1- (point)) t)))
+        (save-excursion
+          (goto-char pos)
+          (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 pos)
+          ;; Remove the trailing whitespace after indentation because
+          ;; indentation may (re)introduce the whitespace.
+          (delete-horizontal-space t))))
+    (indent-according-to-mode)))
+
+;;;###autoload
+(define-minor-mode electric-indent-mode
+  "Automatically reindent lines of code when inserting particular chars.
+`electric-indent-chars' specifies the set of chars that should cause reindentation."
+  :global t
+  :group 'electricity
+  (if electric-indent-mode
+      (add-hook 'post-self-insert-hook
+                #'electric-indent-post-self-insert-function)
+    (remove-hook 'post-self-insert-hook
+                 #'electric-indent-post-self-insert-function)))
+
+;; Electric pairing.
+
+(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."
+  :type 'boolean)
+
+(defun electric-pair-post-self-insert-function ()
+  (let* ((syntax (and (eq (char-before) last-command-event) ; Sanity check.
+                      (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 (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))))))
+
+;;;###autoload
+(define-minor-mode electric-pair-mode
+  "Automatically pair-up parens when inserting an open paren."
+  :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)))
+        
 (provide 'electric)
 
 ;; arch-tag: dae045eb-dc2d-4fb7-9f27-9cc2ce277be8