diff --git a/etc/NEWS b/etc/NEWS index be1c4aac946..72075945f62 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -427,6 +427,8 @@ system or session bus. * 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. diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 1c83ea2ad61..65beeb6e531 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,5 +1,13 @@ 2010-09-03 Stefan Monnier + * 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. diff --git a/lisp/electric.el b/lisp/electric.el index fb3e462efba..48d865d8957 100644 --- a/lisp/electric.el +++ b/lisp/electric.el @@ -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