diff lisp/utils/highlight-headers.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children ac2d302a0011
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/utils/highlight-headers.el	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,579 @@
+;;; highlight-headers.el --- highlighting message headers.
+
+;; Copyright (C) 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
+;; Copyright (C) 1995 Tinker Systems
+
+;; Keywords: mail, news
+
+;; 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, 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; see the file COPYING.  If not, write to the Free
+;; Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; Synched up with: Not in FSF.
+
+;; This code is shared by RMAIL, VM, and GNUS.
+;;
+;; Faces:
+;;
+;; message-headers			the part before the colon
+;; message-header-contents		the part after the colon
+;; message-highlighted-header-contents	contents of "special" headers
+;; message-cited-text			quoted text from other messages
+;;
+;; Variables:
+;;
+;; highlight-headers-regexp			what makes a "special" header
+;; highlight-headers-citation-regexp		matches lines of quoted text
+;; highlight-headers-citation-header-regexp	matches headers for quoted text
+
+(if (find-face 'message-headers)
+    nil
+  (make-face 'message-headers)
+  (or (face-differs-from-default-p 'message-headers)
+      (copy-face 'bold 'message-headers)))
+
+(if (find-face 'message-header-contents)
+    nil
+  (make-face 'message-header-contents)
+  (or (face-differs-from-default-p 'message-header-contents)
+      (copy-face 'italic 'message-header-contents)))
+
+(if (find-face 'message-highlighted-header-contents)
+    nil
+  (make-face 'message-highlighted-header-contents)
+  (or (face-differs-from-default-p 'message-highlighted-header-contents)
+      (progn
+	(copy-face 'message-header-contents
+		   'message-highlighted-header-contents)
+	;; Most people seem not to like underlining, so change
+	;; the font instead.
+	;; (set-face-underline-p 'message-highlighted-header-contents t)
+	(or (make-face-bold 'message-highlighted-header-contents)
+	    (make-face-unbold 'message-highlighted-header-contents)
+	    (make-face-italic 'message-highlighted-header-contents)
+	    (make-face-unitalic 'message-highlighted-header-contents))
+	)))
+
+(if (find-face 'message-cited-text)
+    nil
+  (make-face 'message-cited-text)
+  (or (face-differs-from-default-p 'message-cited-text)
+      (copy-face 'italic 'message-cited-text)))
+
+(if (find-face 'x-face)
+    nil
+  (make-face 'x-face)
+  (or (face-differs-from-default-p 'x-face)
+      (progn 
+	(copy-face 'message-highlighted-header-contents 'x-face)
+	(set-face-background 'x-face "white")
+	(set-face-foreground 'x-face "black"))))
+
+;;(condition-case nil
+;;    (face-name 'message-addresses)
+;;  (wrong-type-argument
+;;   (make-face 'message-addresses)
+;;   (or (face-differs-from-default-p 'message-addresses)
+;;       (progn
+;;	 (copy-face 'bold-italic 'message-addresses)
+;;	 (set-face-underline-p 'message-addresses
+;;			       (face-underline-p
+;;				'message-highlighted-header-contents))))))
+
+(defvar highlight-headers-regexp "Subject[ \t]*:"
+  "*The headers whose contents should be emphasized more.
+The contents of these headers will be displayed in the face 
+`message-highlighted-header-contents' instead of `message-header-contents'.")
+
+(defvar highlight-headers-citation-regexp
+  (concat "^\\("
+	  (mapconcat 'identity
+	   '("[ \t]*[a-zA-Z0-9_]+>+"	; supercite
+	     "[ \t]*[>]+"		; ">" with leading spaces
+	     "[]}<>|:]+[ \t]*"		; other chars, no leading space
+	     )
+	   "\\|")
+	  "\\)[ \t]*")
+  "*The pattern to match cited text.
+Text in the body of a message which matches this will be displayed in
+the face `message-cited-text'.")
+
+(defvar highlight-headers-citation-header-regexp
+  (concat "^In article\\|^In message\\|"
+	  "^[^ \t].*\\(writes\\|wrote\\|said\\):\n"
+	  (substring highlight-headers-citation-regexp 1))
+  "*The pattern to match the prolog of a cited block.
+Text in the body of a message which matches this will be displayed in
+the `message-headers' face.")
+
+(defvar highlight-headers-highlight-citation-too nil
+  "*Whether the whole citation line should go in the `mesage-cited-text' face.
+If nil, the text matched by `highlight-headers-citation-regexp' is in the
+default face, and the remainder of the line is in the message-cited-text face.")
+
+(defvar highlight-headers-max-message-size 10000
+  "*If the message body is larger than this many chars, don't highlight it.
+This is to prevent us from wasting time trying to fontify things like
+uuencoded files and large digests.  If this is nil, all messages will
+be highlighted.")
+
+(defvar highlight-headers-hack-x-face-p (featurep 'xface)
+  "*If true, then the bitmap in an X-Face header will be displayed
+in the buffer.  This assumes you have the `uncompface' and `icontopbm'
+programs on your path.")
+
+(defvar highlight-headers-convert-quietly nil
+  "*Non-nil inhibits the message that is normally displayed when external
+filters are used to convert an X-Face header.  This has no affect if
+XEmacs is compiled with internal support for x-faces.")
+
+(defvar highlight-headers-invert-x-face-data nil 
+  "*If true, causes the foreground and background bits in an X-Face
+header to be flipped before the image is displayed. If you use a
+light foreground color on a dark background color, you probably want
+to set this to t. This assumes that you have the `pnminvert' program
+on your path.  This doesn't presently work with internal xface support.")
+
+
+;;;###autoload
+(defun highlight-headers (start end hack-sig)
+  "Highlight message headers between start and end.
+Faces used:
+  message-headers			the part before the colon
+  message-header-contents		the part after the colon
+  message-highlighted-header-contents	contents of \"special\" headers
+  message-cited-text			quoted text from other messages
+
+Variables used:
+
+  highlight-headers-regexp			what makes a \"special\" header
+  highlight-headers-citation-regexp		matches lines of quoted text
+  highlight-headers-citation-header-regexp	matches headers for quoted text
+
+If HACK-SIG is true,then we search backward from END for something that
+looks like the beginning of a signature block, and don't consider that a
+part of the message (this is because signatures are often incorrectly
+interpreted as cited text.)"
+  (if (< end start)
+      (let ((s start)) (setq start end end s)))
+  (let* ((too-big (and highlight-headers-max-message-size
+		       (> (- end start)
+			  highlight-headers-max-message-size)))
+	 (real-end end)
+	 e p hend)
+    ;; delete previous highlighting
+    (map-extents (function (lambda (extent ignore)
+			     (if (extent-property extent 'headers)
+				 (delete-extent extent))
+			     nil))
+		 (current-buffer) start end)
+    (save-excursion
+      (save-restriction
+	(widen)
+	;; take off signature
+	(if (and hack-sig (not too-big))
+	    (save-excursion
+	      (goto-char end)
+	      (if (re-search-backward "\n--+ *\n" start t)
+		  (if (eq (char-after (point)) ?\n)
+		      (setq end (1+ (point)))
+		    (setq end (point))))))
+	(narrow-to-region start end)
+
+	(save-restriction
+	  ;; narrow down to just the headers...
+	  (goto-char start)
+	  ;; If this search fails then the narrowing performed above
+	  ;; is sufficient
+	  (if (re-search-forward "^$" nil t)
+	      (narrow-to-region (point-min) (point)))
+	  (goto-char start)
+	  (while (not (eobp))
+	    (cond
+	     ((looking-at "^\\([^ \t\n:]+[ \t]*:\\) *\\(.*\\(\n[ \t].*\\)*\n\\)")
+	      (setq hend (match-end 0))
+	      (setq e (make-extent (match-beginning 1) (match-end 1)))
+	      (set-extent-face e 'message-headers)
+	      (set-extent-property e 'headers t)
+	      (setq p (match-end 1))
+	      (cond
+	       ((and highlight-headers-hack-x-face-p
+		     (save-match-data (looking-at "^X-Face: *")))
+		;; make the whole header invisible
+		(setq e (make-extent (match-beginning 0) (match-end 0)))
+		(set-extent-property e 'invisible t)
+		(set-extent-property e 'headers t)
+		;; now extract the xface and put it somewhere interesting
+		(let ((xface (highlight-headers-x-face-to-pixmap
+			      (match-beginning 2)
+			      (match-end 2))))
+		  (if (not xface)
+		      nil		; just leave the header invisible if
+					; we can't convert the face for some
+					; reason 
+		    (cond ((save-excursion
+			     (goto-char (point-min))
+			     (save-excursion (re-search-forward "^From: *"
+								nil t)))
+			   (setq e (make-extent (match-end 0)
+						(match-end 0))))
+			  (t
+			   ;; okay, make the beginning of the the invisible
+			   ;; move forward to only hide the modem noise...
+			   (set-extent-endpoints e
+						 (match-beginning 2)
+						 (1- (match-end 2)))
+			   ;; kludge: if a zero-length extent exists at the
+			   ;; starting point of an invisible extent, then
+			   ;; it's invisible... even if the invisible extent
+			   ;; is start-open.  
+			   (setq e (make-extent (1- (match-beginning 2))
+						(match-beginning 2)))
+			   ))
+		    (set-extent-property e 'headers t)
+		    (set-extent-end-glyph e xface))
+		  ))
+;;; I don't think this is worth the effort
+;;;           ((looking-at "\\(From\\|Resent-From\\)[ \t]*:")
+;;;            (setq current 'message-highlighted-header-contents)
+;;;            (goto-char (match-end 0))
+;;;            (or (looking-at ".*(\\(.*\\))")
+;;;                (looking-at "\\(.*\\)<")
+;;;                (looking-at "\\(.*\\)[@%]")
+;;;                (looking-at "\\(.*\\)"))
+;;;            (end-of-line)
+;;;            (setq e (make-extent p (match-beginning 1)))
+;;;            (set-extent-face e current)
+;;;            (set-extent-property e 'headers t)
+;;;            (setq e (make-extent (match-beginning 1) (match-end 1)))
+;;;            (set-extent-face e 'message-addresses)
+;;;            (set-extent-property e 'headers t)
+;;;            (setq e (make-extent (match-end 1) (point)))
+;;;            (set-extent-face e current)
+;;;            (set-extent-property e 'headers t)
+;;;            )
+	       ((and highlight-headers-regexp
+		     (save-match-data (looking-at highlight-headers-regexp)))
+		(setq e (make-extent (match-beginning 2) (match-end 2)))
+		(set-extent-face e 'message-highlighted-header-contents)
+		(set-extent-property e 'headers t))
+	       (t
+		(setq e (make-extent (match-beginning 2) (match-end 2)))
+		(set-extent-face e 'message-header-contents)
+		(set-extent-property e 'headers t))
+ 	       )
+ 	      (goto-char hend))
+ 	     ;; ignore non-header field name lines
+ 	     (t (forward-line 1)))))
+
+	;; now do the body, unless it's too big....
+	(if too-big
+	    nil
+	  (while (not (eobp))
+	    (cond ((null highlight-headers-citation-regexp)
+		   nil)
+		  ((looking-at highlight-headers-citation-regexp)
+		   (or highlight-headers-highlight-citation-too
+		       (goto-char (match-end 0)))
+		   (or (save-excursion
+			 (beginning-of-line)
+			 (let ((case-fold-search nil)) ; aaaaah, unix...
+			   (looking-at "^>From ")))
+		       (setq current 'message-cited-text)))
+;;;                ((or (looking-at "^In article\\|^In message")
+;;;                     (looking-at
+;;;            "^[^ \t].*\\(writes\\|wrote\\|said\\):\n[ \t]+[A-Z]*[]}<>|]"))
+;;;                 (setq current 'message-headers))
+		  ((null highlight-headers-citation-header-regexp)
+		   nil)
+		  ((looking-at highlight-headers-citation-header-regexp)
+		   (setq current 'message-headers))
+		  (t (setq current nil)))
+	    (cond (current
+		   (setq p (point))
+		   (forward-line 1) ; this is to put the \n in the face too
+		   (setq e (make-extent p (point)))
+		   (forward-char -1)
+		   (set-extent-face e current)
+		   (set-extent-property e 'headers t)
+		   ))
+	    (forward-line 1)))
+	))
+    (save-excursion
+      (save-restriction
+	(widen)
+	(narrow-to-region start real-end)
+	(highlight-headers-mark-urls start real-end)))
+    ))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;
+;;; X-Face header conversion:
+
+;;; This cache is only used if x-face conversion is done with external
+;;; filters.  If XEmacs is compiled --with-xface, then it's better to
+;;; convert it twice than to suck up memory for a potentially large cache of
+;;; stuff that's not difficult to recreate.
+(defvar highlight-headers-x-face-to-pixmap-cache nil)
+
+(defun highlight-headers-x-face-to-pixmap (start end)
+  (let* ((string (if (stringp start) start (buffer-substring start end)))
+	 (data (assoc string highlight-headers-x-face-to-pixmap-cache)))
+    (if (featurep 'xface)
+	(let ((new-face (make-glyph (concat "X-Face: " string))))
+	  (set-glyph-face new-face 'x-face)
+	  new-face)
+      ;; YUCK this is the old two-external-filters-plus-a-bunch-of-lisp method
+      (if data
+	  (cdr data)
+	(setq data (cons string
+			 (condition-case c
+			     (highlight-headers-parse-x-face-data start end)
+			   (error
+			    (display-error c nil)
+			    (sit-for 2)
+			    nil))))
+	(setq highlight-headers-x-face-to-pixmap-cache
+	      (cons data highlight-headers-x-face-to-pixmap-cache))
+	(cdr data)))
+    ))
+
+;;; Kludge kludge kludge for displaying the bitmap in the X-Face header.
+
+;;; This depends on the following programs: icontopbm, from the pbmplus
+;;; toolkit (available everywhere) and uncompface, which comes with
+;;; several faces-related packages, and can also be had at ftp.clark.net
+;;; in /pub/liebman/compface.tar.Z.  See also xfaces 3.*.  Not needed
+;;; for this, but a very nice xbiff replacment.
+
+(defconst highlight-headers-x-face-bitrev
+  (purecopy
+   (eval-when-compile
+     (let* ((v (make-string 256 0))
+	    (i (1- (length v))))
+       (while (>= i 0)
+	 (let ((j 7)
+	       (k 0))
+	   (while (>= j 0)
+	     (if (/= 0 (logand i (lsh 1 (- 7 j))))
+		 (setq k (logior k (lsh 1 j))))
+	     (setq j (1- j)))
+	   (aset v i k))
+	 (setq i (1- i)))
+       v))))
+
+(defun highlight-headers-parse-x-face-data (start end)
+  (save-excursion
+    (let ((b (current-buffer))
+	  (lines 0)
+	  p)
+      (or highlight-headers-convert-quietly
+	  (message "Converting X-Face header to pixmap ..."))
+      (set-buffer (get-buffer-create " *x-face-tmp*"))
+      (buffer-disable-undo (current-buffer))
+      (erase-buffer)
+      (if (stringp start)
+	  (insert start)
+	(insert-buffer-substring b start end))
+      (while (search-forward "\n" nil t)
+	(skip-chars-backward " \t\n")
+	(setq p (point))
+	(skip-chars-forward " \t\n")
+	(delete-region p (point)))
+      (call-process-region (point-min) (point-max) "uncompface" t t nil)
+      (goto-char (point-min))
+      (while (not (eobp))
+	(or (looking-at "0x....,0x....,0x...., *$")
+	    (error "unexpected uncompface output"))
+	(forward-line 1)
+	(setq lines (1+ lines))
+	(delete-char -1))
+      (goto-char (point-min))
+      (insert (format "/* Format_version=1, Width=%d, Height=%d" lines lines))
+      (insert ", Depth=1, Valid_bits_per_item=16\n */\n")
+      (while (not (eobp))
+	(insert ?\t)
+	(forward-char 56) ; 7 groups per line
+	(insert ?\n))
+      (forward-char -1)
+      (delete-char -1)  ; take off last comma
+      ;;
+      ;; Ok, now we've got the format that "icontopbm" knows about.
+      (call-process-region (point-min) (point-max) "icontopbm" t t nil)
+      ;; Invert the image if the user wants us to...
+      (if highlight-headers-invert-x-face-data
+	  (call-process-region (point-min) (point-max) "pnminvert" t t nil))
+      ;;
+      ;; If PBM is using binary mode, we're winning.
+      (goto-char (point-min))
+      (let ((new-face))
+	(cond ((looking-at "P4\n")
+	       (forward-line 2)
+	       (delete-region (point-min) (point))
+	       (while (not (eobp))
+		 (insert (aref highlight-headers-x-face-bitrev
+			       (following-char)))
+		 (delete-char 1))
+	       (setq new-face (make-glyph
+			       (vector 'xbm :data
+				       (list lines lines (prog1 (buffer-string)
+							   (erase-buffer))))))
+	       (set-glyph-image new-face "[xface]" 'global 'tty)
+	       (set-glyph-face new-face 'x-face))
+	      (t ; fix me
+	       (error "I only understand binary-format PBM...")))
+	(or highlight-headers-convert-quietly
+	    (message "Converting X-Face header to pixmap ... done."))
+	new-face)
+      )))
+
+
+;;; "The Internet's new BBS!" -Boardwatch Magazine
+;;; URL support by jwz@netscape.com
+
+(defvar highlight-headers-mark-urls (string-match "XEmacs" emacs-version)
+  "*Whether to make URLs clickable in message bodies.")
+
+(defvar highlight-headers-follow-url-function 'w3-fetch
+  "The function to invoke to follow a URL.
+Possible values that work out of the box are:
+
+'w3-fetch                                == Use emacs-w3
+'highlight-headers-follow-url-netscape   == Use Netscape 1.1
+'highlight-headers-follow-url-mosaic     == Use Mosaic")
+
+(defvar highlight-headers-follow-url-netscape-auto-raise t
+  "*Whether to make Netscape auto-raise when a URL is sent to it.")
+
+(defvar highlight-headers-follow-url-netscape-new-window nil
+  "*Whether to make Netscape create a new window when a URL is sent to it.")
+
+;;;###autoload
+(defun highlight-headers-follow-url-netscape (url)
+  (message "Sending URL to Netscape...")
+  (save-excursion
+    (set-buffer (get-buffer-create "*Shell Command Output*"))
+    (erase-buffer)
+    (if (equal
+	 0
+	 (apply 'call-process "netscape" nil t nil
+		"-remote"
+		(nconc
+		 (and (not highlight-headers-follow-url-netscape-auto-raise)
+		      (list "-noraise"))
+		 (list
+		  (concat "openURL(" url
+			  (if highlight-headers-follow-url-netscape-new-window
+			      ",new-window)" ")"))))))
+	;; it worked
+	nil
+      ;; it didn't work, so start a new Netscape process.
+      (call-process "netscape" nil 0 nil url)))
+  (message "Sending URL to Netscape... done"))
+
+;;;###autoload
+(defun highlight-headers-follow-url-mosaic (url)
+  (message "Sending URL to Mosaic...")
+  (let ((pid-file "~/.mosaicpid")
+	(work-buffer " *mosaic work*")
+	(pid nil))
+    (cond ((file-readable-p pid-file)
+	   (set-buffer (get-buffer-create work-buffer))
+	   (erase-buffer)
+	   (insert-file-contents pid-file)
+	   (setq pid (int-to-string (string-to-int (buffer-string))))
+	   (erase-buffer)
+	   (insert "goto" ?\n)
+	   (insert url ?\n)
+	   (write-region (point-min) (point-max)
+			 (concat "/tmp/Mosaic." pid)
+			 nil 0)
+	   (set-buffer-modified-p nil)
+	   (kill-buffer work-buffer)))
+    (cond ((or (null pid)
+	       (not (equal 0 (call-process "kill" nil nil nil "-USR1" pid))))
+	   (call-process "Mosaic" nil 0 nil url))))
+  (message "Sending URL to Mosaic... done"))
+
+(defvar highlight-headers-url-keymap
+  (let ((m (make-sparse-keymap)))
+    (set-keymap-name m 'highlight-headers-url-keymap)
+    (if (string-match "XEmacs" emacs-version)
+	(progn
+	  (define-key m 'button2 'highlight-headers-follow-url)
+	  ))
+    m))
+
+;;;###autoload
+(defun highlight-headers-follow-url (event)
+  (interactive "e")
+  (let* ((p (event-point event))
+	 (buffer (window-buffer (event-window event)))
+	 (extent (and p (extent-at p buffer 'highlight)))
+	 (url (and extent
+		   (save-excursion
+		     (set-buffer buffer)
+		     (buffer-substring (extent-start-position extent)
+				       (extent-end-position extent))))))
+    (if (and url (string-match "\\`<\\([^>]+\\)>\\'" url))
+	(setq url (concat "news:"
+			  (substring url (match-beginning 1) (match-end 1)))))
+    (if url
+	(funcall highlight-headers-follow-url-function url)
+      (beep))))
+
+
+(defconst highlight-headers-url-pattern
+  (concat
+   "\\b\\(s?https?\\|ftp\\|file\\|gopher\\|s?news\\|telnet\\|mailbox\\):"
+	  "\\(//[-a-zA-Z0-9_.]+:[0-9]*\\)?"
+	  "[-a-zA-Z0-9_=?#$@~`%&*+|\\/.,]+"
+	  ))
+
+(defun highlight-headers-mark-urls (start end)
+  (cond
+   (highlight-headers-mark-urls
+    (save-excursion
+      (goto-char start)
+      (while (re-search-forward highlight-headers-url-pattern nil t)
+	(let ((s (match-beginning 0))
+	      e
+	      extent)
+	  (goto-char (match-end 0))
+	  ;(skip-chars-forward "^ \t\n\r")
+	  (skip-chars-backward ".?#!*()")
+	  (setq e (point))
+	  (setq extent (make-extent s e))
+	  (set-extent-face extent 'bold)
+	  (set-extent-property extent 'highlight t)
+	  (set-extent-property extent 'headers t)
+	  (set-extent-property extent 'keymap highlight-headers-url-keymap)
+	  ))
+
+      (goto-char start)
+      (while (re-search-forward "^Message-ID: \\(<[^>\n]+>\\)" nil t)
+	(let ((s (match-beginning 1))
+	      (e (match-end 1))
+	      extent)
+	  (setq extent (make-extent s e))
+	  (set-extent-face extent 'bold)
+	  (set-extent-property extent 'highlight t)
+	  (set-extent-property extent 'headers t)
+	  (set-extent-property extent 'keymap highlight-headers-url-keymap)))
+      ))))
+
+
+(provide 'highlight-headers)