Mercurial > hg > xemacs-beta
diff lisp/packages/paren.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | 4103f0995bd7 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/packages/paren.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,369 @@ +;;; paren.el --- highlight (un)matching parens and whole expressions + +;; Copyright (C) 1993 Free Software Foundation, Inc. +;; Copyright (C) 1993, 1994, 1995 Tinker Systems +;; +;; Author: Jonathan Stigelman <Stig@hackvan.com> +;; Note: (some code scammed from simple.el and blink-paren.el) +;; Maintainer: Jonathan Stigelman <Stig@hackvan.com> +;; Keywords: languages, faces + +;;; This file is part of XEmacs. +;;; +;;; XEmacs is free software; you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 2 of the License, or +;;; (at your option) any later version. +;;; +;;; XEmacs is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with XEmacs; if not, write to the Free Software +;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +;;; Synched up with: Not synched with FSF. +;;; Way different from FSF. + +;;; Commentary: + +;; Purpose of this package: +;; +;; This package highlights matching parens (or whole sexps) for easier +;; editing of source code, particularly lisp source code. +;; +;; The `paren-highlight' hook function runs after each command and +;; checks to see if the cursor is at a parenthesis. If so, then it +;; highlights, in one of several ways, the matching parenthesis. +;; +;; Priority is given to matching parentheses right before the cursor because +;; that's what makes sense when you're typing a lot of closed parentheses. +;; +;; This is especially intuitive if you frequently use forward-sexp (M-C-f) +;; and backward-sexp (M-C-b) to maneuver around in source code. +;; +;; Different faces are used for matching and mismatching parens so that it +;; is easier to see mistakes as you type them. Audible feedback is optional. +;; +;; If a (mis)matching paren is offscreen, then a message is sent to the modeline. +;; +;; If paren-mode is `sexp', entire S-expressions are highlighted instead of +;; just matching parens. + +;;; Code: + +(defvar paren-message-offscreen t + "*Display message if matching open paren is offscreen.") + +(defvar paren-ding-unmatched nil + "*Make noise if the cursor is at an unmatched paren. + +If T, then typing or passing over an unmatched paren will ring the bell +using the `paren' sound. If NIL, then the bell will not ring even if an +unmatched paren is typed. If neither T or NIL, then the bell will not ring +when the cursor moves over unmatched parens but will ring if one is typed.") + +;;;###autoload +(defvar paren-mode nil + "*Sets the style of parenthesis highlighting. +Valid values are nil, `blink-paren', `paren', and `sexp'. + nil no parenthesis highlighting. + blink-paren causes the matching paren to blink. + paren causes the matching paren to be highlighted but not to blink. + sexp whole expression enclosed by the local paren at its mate. + nested (not yet implemented) use variable shading to see the + nesting of an expression. Also groks regular expressions + and shell quoting. + +This variable is global by default, but you can make it buffer-local and +highlight parentheses differrently in different major modes.") + +(make-face 'paren-match) +(or (face-differs-from-default-p 'paren-match) + (copy-face 'highlight 'paren-match)) + +(make-face 'paren-mismatch) +(cond ((face-differs-from-default-p 'paren-mismatch) nil) + (t (let ((color-tag (list 'x 'color)) + (mono-tag (list 'x 'mono)) + (gray-tag (list 'x 'grayscale))) + (set-face-background 'paren-mismatch "DeepPink" 'global color-tag) + (set-face-reverse-p 'paren-mismatch t 'global 'tty) + (set-face-background 'paren-mismatch [modeline background] 'global + mono-tag) + (set-face-foreground 'paren-mismatch [modeline foreground] 'global + mono-tag) + (set-face-background 'paren-mismatch [modeline background] 'global + gray-tag) + (set-face-foreground 'paren-mismatch [modeline foreground] 'global + gray-tag)))) + +(make-face 'paren-blink-off) +(or (face-differs-from-default-p 'paren-blink-off) + (set-face-foreground 'paren-blink-off (face-background 'default))) + +;; this is either paren-match or paren-mismatch... +(defvar paren-blink-on-face nil) + +(defvar paren-blink-interval 0.2 + "*If the cursor is on a parenthesis, the matching parenthesis will blink. +This variable controls how long each phase of the blink lasts in seconds. +This should be a fractional part of a second (a float.)") + +(defvar paren-max-blinks (* 5 60 5) ; 5 minutes is plenty... + ;; idea from Eric Eide <eeide@jaguar.cs.utah.edu> + "*Maximum number of times that a matching parenthesis will blink. +Set this to NIL if you want indefinite blinking.") + +;; timeout to blink the face +(defvar paren-timeout-id nil) + +;; Code: + +(defvar paren-n-blinks) +(defvar paren-extent nil) + +;; used to suppress messages from the same position so that other messages +;; can be seen in the modeline. +(make-variable-buffer-local + (defvar paren-message-suppress nil)) + +(defsubst pos-visible-in-window-safe (pos) + "safe version of pos-visible-in-window-p" + (condition-case nil + ;; #### - is this needed in XEmacs??? + (pos-visible-in-window-p pos) + (args-out-of-range nil))) + +;; called before a new command is executed in the pre-command-hook +;; cleanup by removing the extent and the time-out +(defun paren-nuke-extent () + (condition-case c ; don't ever signal an error in pre-command-hook! + (let ((inhibit-quit t)) + (if paren-timeout-id + (disable-timeout (prog1 paren-timeout-id + (setq paren-timeout-id nil)))) + (if paren-extent + (delete-extent (prog1 paren-extent + (setq paren-extent nil))))) + (error + (message "paren-nuke-extent error! %s" c)))) + +;; callback for the timeout +;; swap the face of the extent on the matching paren +(defun paren-blink-timeout (arg) + ;; The extent could have been deleted for some reason and not point to a + ;; buffer anymore. So catch any error to remove the timeout. + (condition-case () + (if (and paren-max-blinks + (> (setq paren-n-blinks (1+ paren-n-blinks)) paren-max-blinks)) + (paren-nuke-extent) + (set-extent-face paren-extent + (if (eq (extent-face paren-extent) + paren-blink-on-face) + 'paren-blink-off + paren-blink-on-face))) + (error (paren-nuke-extent)))) + + +(defun paren-describe-match (pos mismatch) + (or (window-minibuffer-p (selected-window)) + (save-excursion + (goto-char pos) + (message "%s %s" + (if mismatch "MISMATCH:" "Matches") + ;; if there's stuff on this line preceding the paren, then + ;; display text from beginning of line to paren. + ;; + ;; If, however, the paren is at the beginning of a line, then + ;; skip whitespace forward and display text from paren to end + ;; of the next line containing nonspace text. + ;; + ;; If paren-backwards-message gravity were implemented, then + ;; perhaps it would reverse this behavior and look to the + ;; previous line for meaningful context. + (if (save-excursion + (skip-chars-backward " \t") + (not (bolp))) + (concat (buffer-substring + (progn (beginning-of-line) (point)) + (1+ pos)) "...") + (buffer-substring + pos (progn + (forward-char 1) + (skip-chars-forward "\n \t") + (end-of-line) + (point)))))))) + +(defun paren-maybe-ding () + (and (or (eq paren-ding-unmatched t) + (and paren-ding-unmatched + (eq this-command 'self-insert-command))) + (progn + (message "Unmatched parenthesis.") + (ding nil 'paren)))) + +;; Find the place to show, if there is one, +;; and show it until input arrives. +(defun paren-highlight () + "This highlights matching parentheses. + +See the variables: + paren-message-offscreen use modeline when matchingparen is offscreen? + paren-ding-unmatched make noise when passing over mismatched parens? + paren-mode 'blink-paren, 'paren, or 'sexp + blink-matching-paren-distance maximum distance to search for parens. + +and the following faces: + paren-match, paren-mismatch, paren-blink-off" + + ;; I suppose I could check here to see if a keyboard macro is executing, + ;; but I did a quick empirical check and couldn't tell that there was any + ;; difference in performance + + (let ((oldpos (point)) + (pface nil) ; face for paren...nil kills the overlay + (dir (and paren-mode + (not (input-pending-p)) + (not executing-kbd-macro) + (cond ((eq (char-syntax (preceding-char)) ?\)) + -1) + ((eq (char-syntax (following-char)) ?\() + 1)))) + pos mismatch) + + (save-excursion + (if (or (not dir) + (not (save-restriction + ;; Determine the range within which to look for a match. + (if blink-matching-paren-distance + (narrow-to-region + (max (point-min) + (- (point) blink-matching-paren-distance)) + (min (point-max) + (+ (point) blink-matching-paren-distance)))) + + ;; Scan across one sexp within that range. + (condition-case nil + (setq pos (scan-sexps (point) dir)) + ;; NOTE - if blink-matching-paren-distance is set, + ;; then we can have spurious unmatched parens. + (error (paren-maybe-ding) + nil))))) + + ;; do nothing if we didn't find a matching paren... + nil + + ;; See if the "matching" paren is the right kind of paren + ;; to match the one we started at. + (let ((beg (min pos oldpos)) (end (max pos oldpos))) + (setq mismatch + (and (/= (char-syntax (char-after beg)) ?\\) + (/= (char-syntax (char-after beg)) ?\$) + (/= (char-after (1- end)) + (logand (lsh (aref (syntax-table) + (char-after beg)) + -8) + 255)))) + (if (eq paren-mode 'sexp) + (setq paren-extent (make-extent beg end)))) + (and mismatch + (paren-maybe-ding)) + (setq pface (if mismatch + 'paren-mismatch + 'paren-match)) + (and (memq paren-mode '(blink-paren paren)) + (setq paren-extent (make-extent (- pos dir) pos))) + + (if (and paren-message-offscreen + (eq dir -1) + (not (eq paren-message-suppress (point))) + (not (window-minibuffer-p (selected-window))) + (not (pos-visible-in-window-safe pos))) + (progn + (setq paren-message-suppress (point)) + (paren-describe-match pos mismatch)) + (setq paren-message-suppress nil)) + + ;; put the right face on the extent + (cond (pface + (set-extent-face paren-extent pface) + (set-extent-priority paren-extent 100) ; want this to be high + (and (eq paren-mode 'blink-paren) + (setq paren-blink-on-face pface + paren-n-blinks 0 + paren-timeout-id + (and paren-blink-interval + (add-timeout paren-blink-interval + 'paren-blink-timeout + nil + paren-blink-interval)))))) + )))) + +;; kill off the competition, er, uh, eliminate redundancy... +(setq post-command-hook (delq 'show-paren-command-hook post-command-hook)) +(setq pre-command-hook (delq 'blink-paren-pre-command pre-command-hook)) +(setq post-command-hook (delq 'blink-paren-post-command post-command-hook)) + +;;;###autoload +(defun paren-set-mode (arg &optional quiet) + "Cycles through possible values for `paren-mode', force off with negative arg. +When called from lisp, a symbolic value for `paren-mode' can be pased directly. +See also `paren-mode' and `paren-highlight'." + (interactive "P") + (let* ((paren-modes '(blink-paren paren sexp)) + (paren-next-modes (cons nil (append paren-modes (list nil))))) + (setq paren-mode (if (and (numberp arg) (< arg 0)) + nil ; turn paren highlighting off + (cond ((and arg (symbolp arg)) arg) + ((and (numberp arg) (> arg 0)) + (nth (1- arg) paren-modes)) + ((numberp arg) nil) + (t (car (cdr (memq paren-mode + paren-next-modes))))) + ))) + (cond (paren-mode + (add-hook 'post-command-hook 'paren-highlight) + (add-hook 'pre-command-hook 'paren-nuke-extent) + (setq blink-matching-paren nil)) + ((not (local-variable-p 'paren-mode (current-buffer))) + (remove-hook 'post-command-hook 'paren-highlight) + (remove-hook 'pre-command-hook 'paren-nuke-extent) + (paren-nuke-extent) ; overkill + (setq blink-matching-paren t) + )) + (or quiet (message "Paren mode is %s" (or paren-mode "OFF")))) + +(eval-when-compile + ;; suppress compiler warning. + (defvar highlight-paren-expression)) + +(paren-set-mode (if (and (boundp 'highlight-paren-expression) + ;; bletcherous blink-paren no-naming-convention + highlight-paren-expression) + 'sexp + (if (eq 'x (device-type (selected-device))) + 'blink-paren + 'paren)) + t) + +;;;###autoload +(make-obsolete 'blink-paren 'paren-set-mode) + +;;;###autoload +(defun blink-paren (&optional arg) + "Obsolete. Use `paren-set-mode' instead." + (interactive "P") + (paren-set-mode (if (and (numberp arg) (> arg 0)) + 'blink-paren -1) t)) + +(provide 'blink-paren) +(provide 'paren) + +;; Local Variables: +;; byte-optimize: t +;; End: + +;;; paren.el ends here