(message-citation-line-format)
[gnus] / lisp / gnus-cite.el
index 1da62e9..7c60d76 100644 (file)
@@ -1,8 +1,9 @@
 ;;; gnus-cite.el --- parse citations in articles for Gnus
-;; Copyright (C) 1995,96 Free Software Foundation, Inc.
 
-;; Author: Per Abrahamsen <abraham@iesd.auc.dk>
-;; Keywords: news, mail
+;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
+;;   2004, 2005, 2006 Free Software Foundation, Inc.
+
+;; Author: Per Abhiddenware
 
 ;; This file is part of GNU Emacs.
 
 
 ;; You should have received a copy of the GNU General Public License
 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
 
 ;;; Commentary:
 
 ;;; Code:
 
+(eval-when-compile (require 'cl))
+
 (require 'gnus)
-(require 'gnus-art)
 (require 'gnus-range)
+(require 'gnus-art)
+(require 'message)     ; for message-cite-prefix-regexp
 
 ;;; Customization:
 
 (defgroup gnus-cite nil
   "Citation."
-  :group 'article)
-  
+  :prefix "gnus-cite-"
+  :link '(custom-manual "(gnus)Article Highlighting")
+  :group 'gnus-article)
 
-(defcustom gnus-cited-text-button-line-format "%(%{[...]%}%)\n"
-  "Format of cited text buttons."
+(defcustom gnus-cited-opened-text-button-line-format "%(%{[-]%}%)\n"
+  "Format of opened cited text buttons."
+  :group 'gnus-cite
+  :type 'string)
+
+(defcustom gnus-cited-closed-text-button-line-format "%(%{[+]%}%)\n"
+  "Format of closed cited text buttons."
   :group 'gnus-cite
   :type 'string)
 
 (defcustom gnus-cited-lines-visible nil
-  "The number of lines of hidden cited text to remain visible."
+  "The number of lines of hidden cited text to remain visible.
+Or a pair (cons) of numbers which are the number of lines at the top
+and bottom of the text, respectively, to remain visible."
   :group 'gnus-cite
   :type '(choice (const :tag "none" nil)
-                integer))
+                integer
+                (cons :tag "Top and Bottom" integer integer)))
 
 (defcustom gnus-cite-parse-max-size 25000
   "Maximum article size (in bytes) where parsing citations is allowed.
@@ -54,21 +67,15 @@ Set it to nil to parse all articles."
   :type '(choice (const :tag "all" nil)
                 integer))
 
-(defcustom gnus-cite-prefix-regexp 
-    "^[]>|:}+ ]*[]>|:}+]\\(.*>\\)?\\|^.*>"
-  "Regexp matching the longest possible citation prefix on a line."
-  :group 'gnus-cite
-  :type 'regexp)
-
 (defcustom gnus-cite-max-prefix 20
   "Maximum possible length for a citation prefix."
   :group 'gnus-cite
   :type 'integer)
 
-(defcustom gnus-supercite-regexp 
-  (concat "^\\(" gnus-cite-prefix-regexp "\\)? *"
+(defcustom gnus-supercite-regexp
+  (concat "^\\(" message-cite-prefix-regexp "\\)? *"
          ">>>>> +\"\\([^\"\n]+\\)\" +==")
-  "Regexp matching normal Supercite attribution lines.
+  "*Regexp matching normal Supercite attribution lines.
 The first grouping must match prefixes added by other packages."
   :group 'gnus-cite
   :type 'regexp)
@@ -84,143 +91,211 @@ The first regexp group should match the Supercite attribution."
   :group 'gnus-cite
   :type 'integer)
 
-(defcustom gnus-cite-attribution-prefix "in article\\|in <"
-  "Regexp matching the beginning of an attribution line."
+;; Some Microsoft products put in a citation that extends to the
+;; remainder of the message:
+;;
+;;     -----Original Message-----
+;;     From: ...
+;;     To: ...
+;;     Sent: ...   [date, in non-RFC-2822 format]
+;;     Subject: ...
+;;
+;;     Cited message, with no prefixes
+;;
+;; The four headers are always the same.  But note they are prone to
+;; folding without additional indentation.
+;;
+;; Others use "----- Original Message -----" instead, and properly quote
+;; the body using "> ".  This style is handled without special cases.
+
+(defcustom gnus-cite-attribution-prefix
+  "In article\\|in <\\|On \\(Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\|Sun\\),\\|----- ?Original Message ?-----"
+  "*Regexp matching the beginning of an attribution line."
   :group 'gnus-cite
   :type 'regexp)
 
 (defcustom gnus-cite-attribution-suffix
-  "\\(wrote\\|writes\\|said\\|says\\):[ \t]*$"
-  "Regexp matching the end of an attribution line.
+  "\\(\\(wrote\\|writes\\|said\\|says\\|>\\)\\(:\\|\\.\\.\\.\\)\\|----- ?Original Message ?-----\\)[ \t]*$"
+  "*Regexp matching the end of an attribution line.
 The text matching the first grouping will be used as a button."
   :group 'gnus-cite
   :type 'regexp)
 
-(defface gnus-cite-attribution-face '((t 
-                                      (:underline t)))
-  "Face used for attribution lines.")
+(defcustom gnus-cite-unsightly-citation-regexp
+  "^-----Original Message-----\nFrom: \\(.+\n\\)+\n"
+  "Regexp matching Microsoft-type rest-of-message citations."
+  :version "22.1"
+  :group 'gnus-cite
+  :type 'regexp)
+
+(defcustom gnus-cite-ignore-quoted-from t
+  "Non-nil means don't regard lines beginning with \">From \" as cited text.
+Those lines may have been quoted by MTAs in order not to mix up with
+the envelope From line."
+  :version "22.1"
+  :group 'gnus-cite
+  :type 'boolean)
+
+(defface gnus-cite-attribution '((t (:italic t)))
+  "Face used for attribution lines."
+  :group 'gnus-cite)
+;; backward-compatibility alias
+(put 'gnus-cite-attribution-face 'face-alias 'gnus-cite-attribution)
 
-(defcustom gnus-cite-attribution-face 'gnus-cite-attribution-face
+(defcustom gnus-cite-attribution-face 'gnus-cite-attribution
   "Face used for attribution lines.
 It is merged with the face for the cited text belonging to the attribution."
+  :version "22.1"
   :group 'gnus-cite
   :type 'face)
 
-(defface gnus-cite-face-1 '((((class color)
-                             (background dark))
-                            (:foreground "light blue"))
-                           (((class color)
-                             (background light))
-                            (:foreground "MidnightBlue"))
-                           (t 
-                            (:italic t)))
-  "Citation face.")
-
-(defface gnus-cite-face-2 '((((class color)
-                             (background dark))
-                            (:foreground "light cyan"))
-                           (((class color)
-                             (background light))
-                            (:foreground "firebrick"))
-                           (t 
-                            (:italic t)))
-  "Citation face.")
-
-(defface gnus-cite-face-3 '((((class color)
-                             (background dark))
-                            (:foreground "light yellow"))
-                           (((class color)
-                             (background light))
-                            (:foreground "dark green"))
-                           (t 
-                            (:italic t)))
-  "Citation face.")
-
-(defface gnus-cite-face-4 '((((class color)
-                             (background dark))
-                            (:foreground "light pink"))
-                           (((class color)
-                             (background light))
-                            (:foreground "OrangeRed"))
-                           (t 
-                            (:italic t)))
-  "Citation face.")
-
-(defface gnus-cite-face-5 '((((class color)
-                             (background dark))
-                            (:foreground "pale green"))
-                           (((class color)
-                             (background light))
-                            (:foreground "dark khaki"))
-                           (t 
-                            (:italic t)))
-  "Citation face.")
-
-(defface gnus-cite-face-6 '((((class color)
-                             (background dark))
-                            (:foreground "beige"))
-                           (((class color)
-                             (background light))
-                            (:foreground "dark violet"))
-                           (t 
-                            (:italic t)))
-  "Citation face.")
-
-(defface gnus-cite-face-7 '((((class color)
-                             (background dark))
-                            (:foreground "orange"))
-                           (((class color)
-                             (background light))
-                            (:foreground "SteelBlue4"))
-                           (t 
-                            (:italic t)))
-  "Citation face.")
-
-(defface gnus-cite-face-8 '((((class color)
-                             (background dark))
-                            (:foreground "magenta"))
-                           (((class color)
-                             (background light))
-                            (:foreground "magenta"))
-                           (t 
-                            (:italic t)))
-  "Citation face.")
-
-(defface gnus-cite-face-9 '((((class color)
-                             (background dark))
-                            (:foreground "violet"))
-                           (((class color)
-                             (background light))
-                            (:foreground "violet"))
-                           (t 
-                            (:italic t)))
-  "Citation face.")
-
-(defface gnus-cite-face-10 '((((class color)
-                              (background dark))
-                             (:foreground "medium purple"))
-                            (((class color)
-                              (background light))
-                             (:foreground "medium purple"))
-                            (t 
-                             (:italic t)))
-  "Citation face.")
-
-(defface gnus-cite-face-11 '((((class color)
-                              (background dark))
-                             (:foreground "turquoise"))
-                            (((class color)
-                              (background light))
-                             (:foreground "turquoise"))
-                            (t 
-                             (:italic t)))
-  "Citation face.")
-
-(defcustom gnus-cite-face-list 
-  '(gnus-cite-face-1 gnus-cite-face-2 gnus-cite-face-3 gnus-cite-face-4 
-    gnus-cite-face-5 gnus-cite-face-6 gnus-cite-face-7 gnus-cite-face-8 
-    gnus-cite-face-9 gnus-cite-face-10 gnus-cite-face-11)
-  "List of faces used for highlighting citations. 
+(defface gnus-cite-1 '((((class color)
+                        (background dark))
+                       (:foreground "light blue"))
+                      (((class color)
+                        (background light))
+                       (:foreground "MidnightBlue"))
+                      (t
+                       (:italic t)))
+  "Citation face."
+  :group 'gnus-cite)
+;; backward-compatibility alias
+(put 'gnus-cite-face-1 'face-alias 'gnus-cite-1)
+
+(defface gnus-cite-2 '((((class color)
+                        (background dark))
+                       (:foreground "light cyan"))
+                      (((class color)
+                        (background light))
+                       (:foreground "firebrick"))
+                      (t
+                       (:italic t)))
+  "Citation face."
+  :group 'gnus-cite)
+;; backward-compatibility alias
+(put 'gnus-cite-face-2 'face-alias 'gnus-cite-2)
+
+(defface gnus-cite-3 '((((class color)
+                        (background dark))
+                       (:foreground "light yellow"))
+                      (((class color)
+                        (background light))
+                       (:foreground "dark green"))
+                      (t
+                       (:italic t)))
+  "Citation face."
+  :group 'gnus-cite)
+;; backward-compatibility alias
+(put 'gnus-cite-face-3 'face-alias 'gnus-cite-3)
+
+(defface gnus-cite-4 '((((class color)
+                        (background dark))
+                       (:foreground "light pink"))
+                      (((class color)
+                        (background light))
+                       (:foreground "OrangeRed"))
+                      (t
+                       (:italic t)))
+  "Citation face."
+  :group 'gnus-cite)
+;; backward-compatibility alias
+(put 'gnus-cite-face-4 'face-alias 'gnus-cite-4)
+
+(defface gnus-cite-5 '((((class color)
+                        (background dark))
+                       (:foreground "pale green"))
+                      (((class color)
+                        (background light))
+                       (:foreground "dark khaki"))
+                      (t
+                       (:italic t)))
+  "Citation face."
+  :group 'gnus-cite)
+;; backward-compatibility alias
+(put 'gnus-cite-face-5 'face-alias 'gnus-cite-5)
+
+(defface gnus-cite-6 '((((class color)
+                        (background dark))
+                       (:foreground "beige"))
+                      (((class color)
+                        (background light))
+                       (:foreground "dark violet"))
+                      (t
+                       (:italic t)))
+  "Citation face."
+  :group 'gnus-cite)
+;; backward-compatibility alias
+(put 'gnus-cite-face-6 'face-alias 'gnus-cite-6)
+
+(defface gnus-cite-7 '((((class color)
+                        (background dark))
+                       (:foreground "orange"))
+                      (((class color)
+                        (background light))
+                       (:foreground "SteelBlue4"))
+                      (t
+                       (:italic t)))
+  "Citation face."
+  :group 'gnus-cite)
+;; backward-compatibility alias
+(put 'gnus-cite-face-7 'face-alias 'gnus-cite-7)
+
+(defface gnus-cite-8 '((((class color)
+                        (background dark))
+                       (:foreground "magenta"))
+                      (((class color)
+                        (background light))
+                       (:foreground "magenta"))
+                      (t
+                       (:italic t)))
+  "Citation face."
+  :group 'gnus-cite)
+;; backward-compatibility alias
+(put 'gnus-cite-face-8 'face-alias 'gnus-cite-8)
+
+(defface gnus-cite-9 '((((class color)
+                        (background dark))
+                       (:foreground "violet"))
+                      (((class color)
+                        (background light))
+                       (:foreground "violet"))
+                      (t
+                       (:italic t)))
+  "Citation face."
+  :group 'gnus-cite)
+;; backward-compatibility alias
+(put 'gnus-cite-face-9 'face-alias 'gnus-cite-9)
+
+(defface gnus-cite-10 '((((class color)
+                         (background dark))
+                        (:foreground "medium purple"))
+                       (((class color)
+                         (background light))
+                        (:foreground "medium purple"))
+                       (t
+                        (:italic t)))
+  "Citation face."
+  :group 'gnus-cite)
+;; backward-compatibility alias
+(put 'gnus-cite-face-10 'face-alias 'gnus-cite-10)
+
+(defface gnus-cite-11 '((((class color)
+                         (background dark))
+                        (:foreground "turquoise"))
+                       (((class color)
+                         (background light))
+                        (:foreground "turquoise"))
+                       (t
+                        (:italic t)))
+  "Citation face."
+  :group 'gnus-cite)
+;; backward-compatibility alias
+(put 'gnus-cite-face-11 'face-alias 'gnus-cite-11)
+
+(defcustom gnus-cite-face-list
+  '(gnus-cite-1 gnus-cite-2 gnus-cite-3 gnus-cite-4 gnus-cite-5 gnus-cite-6
+    gnus-cite-7 gnus-cite-8 gnus-cite-9 gnus-cite-10 gnus-cite-11)
+  "*List of faces used for highlighting citations.
 
 When there are citations from multiple articles in the same message,
 Gnus will try to give each citation from each article its own face.
@@ -238,12 +313,28 @@ This should make it easier to see who wrote what."
   :group 'gnus-cite
   :type 'integer)
 
+(defcustom gnus-cite-blank-line-after-header t
+  "If non-nil, put a blank line between the citation header and the button."
+  :group 'gnus-cite
+  :type 'boolean)
+
+;; This has to go here because its default value depends on
+;; gnus-cite-face-list.
+(defcustom gnus-article-boring-faces (cons 'gnus-signature gnus-cite-face-list)
+  "List of faces that are not worth reading.
+If an article has more pages below the one you are looking at, but
+nothing on those pages is a word of at least three letters that is not
+in a boring face, then the pages will be skipped."
+  :type '(repeat face)
+  :group 'gnus-article-hiding)
+
 ;;; Internal Variables:
 
 (defvar gnus-cite-article nil)
+(defvar gnus-cite-overlay-list nil)
 
 (defvar gnus-cite-prefix-alist nil)
-;; Alist of citation prefixes.  
+;; Alist of citation prefixes.
 ;; The cdr is a list of lines with that prefix.
 
 (defvar gnus-cite-attribution-alist nil)
@@ -263,11 +354,16 @@ This should make it easier to see who wrote what."
 ;; PREFIX: Is the citation prefix of the attribution line(s), and
 ;; TAG: Is a Supercite tag, if any.
 
-(defvar gnus-cited-text-button-line-format-alist 
+(defvar gnus-cited-opened-text-button-line-format-alist
   `((?b (marker-position beg) ?d)
     (?e (marker-position end) ?d)
+    (?n (count-lines beg end) ?d)
     (?l (- end beg) ?d)))
-(defvar gnus-cited-text-button-line-format-spec nil)
+(defvar gnus-cited-opened-text-button-line-format-spec nil)
+(defvar gnus-cited-closed-text-button-line-format-alist
+  gnus-cited-opened-text-button-line-format-alist)
+(defvar gnus-cited-closed-text-button-line-format-spec nil)
+
 
 ;;; Commands:
 
@@ -276,10 +372,10 @@ This should make it easier to see who wrote what."
 Each citation in the article will be highlighted with a different face.
 The faces are taken from `gnus-cite-face-list'.
 Attribution lines are highlighted with the same face as the
-corresponding citation merged with `gnus-cite-attribution-face'.
+corresponding citation merged with the face `gnus-cite-attribution'.
 
 Text is considered cited if at least `gnus-cite-minimum-match-count'
-lines matches `gnus-cite-prefix-regexp' with the same prefix.  
+lines matches `message-cite-prefix-regexp' with the same prefix.
 
 Lines matching `gnus-cite-attribution-suffix' and perhaps
 `gnus-cite-attribution-prefix' are considered attribution lines."
@@ -317,9 +413,10 @@ Lines matching `gnus-cite-attribution-suffix' and perhaps
              skip (gnus-cite-find-prefix number)
              face (cdr (assoc prefix face-alist)))
        ;; Add attribution button.
-       (goto-line number)
-       (when (re-search-forward gnus-cite-attribution-suffix 
-                                (save-excursion (end-of-line 1) (point))
+       (goto-char (point-min))
+       (forward-line (1- number))
+       (when (re-search-forward gnus-cite-attribution-suffix
+                                (point-at-eol)
                                 t)
          (gnus-article-add-button (match-beginning 1) (match-end 1)
                                   'gnus-cite-toggle prefix))
@@ -339,7 +436,7 @@ Lines matching `gnus-cite-attribution-suffix' and perhaps
   "Dissect the article buffer looking for cited text."
   (save-excursion
     (set-buffer gnus-article-buffer)
-    (gnus-cite-parse-maybe)
+    (gnus-cite-parse-maybe nil t)
     (let ((alist gnus-cite-prefix-alist)
          prefix numbers number marks m)
       ;; Loop through citation prefixes.
@@ -357,14 +454,16 @@ Lines matching `gnus-cite-attribution-suffix' and perhaps
          (goto-char (point-min))
          (forward-line (1- number))
          (push (cons (point-marker) prefix) marks)))
-      (goto-char (point-min))
-      (search-forward "\n\n" nil t)
+      ;; Skip to the beginning of the body.
+      (article-goto-body)
       (push (cons (point-marker) "") marks)
+      ;; Find the end of the body.
       (goto-char (point-max))
-      (article-search-signature)
+      (gnus-article-search-signature)
       (push (cons (point-marker) "") marks)
-      (setq marks (sort marks (lambda (m1 m2) (< (car m1) (car m2)))))
-      (let* ((omarks marks))
+      ;; Sort the marks.
+      (setq marks (sort marks 'car-less-than-car))
+      (let ((omarks marks))
        (setq marks nil)
        (while (cdr omarks)
          (if (= (caar omarks) (caadr omarks))
@@ -373,7 +472,10 @@ Lines matching `gnus-cite-attribution-suffix' and perhaps
                  (push (car omarks) marks))
                (unless (equal (cdadr omarks) "")
                  (push (cadr omarks) marks))
-               (setq omarks (cdr omarks)))
+               (unless (and (equal (cdar omarks) "")
+                            (equal (cdadr omarks) "")
+                            (not (cddr omarks)))
+                 (setq omarks (cdr omarks))))
            (push (car omarks) marks))
          (setq omarks (cdr omarks)))
        (when (car omarks)
@@ -388,23 +490,28 @@ Lines matching `gnus-cite-attribution-suffix' and perhaps
              (setcdr m (cdddr m))
            (setq m (cdr m))))
        marks))))
-           
-(defun gnus-article-fill-cited-article (&optional force)
-  "Do word wrapping in the current article."
-  (interactive (list t))
+
+(defun gnus-article-fill-cited-article (&optional force width)
+  "Do word wrapping in the current article.
+If WIDTH (the numerical prefix), use that text width when filling."
+  (interactive (list t current-prefix-arg))
   (save-excursion
     (set-buffer gnus-article-buffer)
     (let ((buffer-read-only nil)
          (inhibit-point-motion-hooks t)
          (marks (gnus-dissect-cited-text))
-         (adaptive-fill-mode nil))
+         (adaptive-fill-mode nil)
+         (filladapt-mode nil)
+         (fill-column (if width (prefix-numeric-value width) fill-column)))
       (save-restriction
        (while (cdr marks)
-         (widen)
          (narrow-to-region (caar marks) (caadr marks))
          (let ((adaptive-fill-regexp
                 (concat "^" (regexp-quote (cdar marks)) " *"))
-               (fill-prefix (cdar marks)))
+               (fill-prefix
+                (if (string= (cdar marks) "") ""
+                  (concat (cdar marks) " ")))
+               use-hard-newlines)
            (fill-region (point-min) (point-max)))
          (set-marker (caar marks) nil)
          (setq marks (cdr marks)))
@@ -414,75 +521,136 @@ Lines matching `gnus-cite-attribution-suffix' and perhaps
        (setq gnus-cite-prefix-alist nil
              gnus-cite-attribution-alist nil
              gnus-cite-loose-prefix-alist nil
-             gnus-cite-loose-attribution-alist nil)))))
+             gnus-cite-loose-attribution-alist nil
+             gnus-cite-article nil)))))
 
 (defun gnus-article-hide-citation (&optional arg force)
   "Toggle hiding of all cited text except attribution lines.
 See the documentation for `gnus-article-highlight-citation'.
 If given a negative prefix, always show; if given a positive prefix,
 always hide."
-  (interactive (append (article-hidden-arg) (list 'force)))
-  (setq gnus-cited-text-button-line-format-spec 
-       (gnus-parse-format gnus-cited-text-button-line-format 
-                          gnus-cited-text-button-line-format-alist t))
+  (interactive (append (gnus-article-hidden-arg) (list 'force)))
+  (gnus-set-format 'cited-opened-text-button t)
+  (gnus-set-format 'cited-closed-text-button t)
   (save-excursion
     (set-buffer gnus-article-buffer)
-    (cond
-     ((article-check-hidden-text 'cite arg)
-      t)
-     ((article-text-type-exists-p 'cite)
-      (let ((buffer-read-only nil))
-       (article-hide-text-of-type 'cite)))
-     (t
       (let ((buffer-read-only nil)
-           (marks (gnus-dissect-cited-text))
+           marks
            (inhibit-point-motion-hooks t)
            (props (nconc (list 'article-type 'cite)
                          gnus-hidden-properties))
-           beg end)
-       (while marks
-         (setq beg nil
-               end nil)
-         (while (and marks (string= (cdar marks) ""))
-           (setq marks (cdr marks)))
-         (when marks 
-           (setq beg (caar marks)))
-         (while (and marks (not (string= (cdar marks) "")))
-           (setq marks (cdr marks)))
-         (when marks
+           (point (point-min))
+           found beg end start)
+       (while (setq point
+                    (text-property-any point (point-max)
+                                       'gnus-callback
+                                       'gnus-article-toggle-cited-text))
+         (setq found t)
+         (goto-char point)
+         (gnus-article-toggle-cited-text
+          (get-text-property point 'gnus-data) arg)
+         (forward-line 1)
+         (setq point (point)))
+       (unless found
+         (setq marks (gnus-dissect-cited-text))
+         (while marks
+           (setq beg nil
+                 end nil)
+           (while (and marks (string= (cdar marks) ""))
+             (setq marks (cdr marks)))
+           (when marks
+             (setq beg (caar marks)))
+           (while (and marks (not (string= (cdar marks) "")))
+             (setq marks (cdr marks)))
+           (when marks
            (setq end (caar marks)))
-         ;; Skip past lines we want to leave visible.
-         (when (and beg end gnus-cited-lines-visible)
-           (goto-char beg)
-           (forward-line gnus-cited-lines-visible)
-           (if (>= (point) end)
-               (setq beg nil)
-             (setq beg (point-marker))))
-         (when (and beg end)
-           (gnus-add-text-properties beg end props)
-           (goto-char beg)
-           (unless (save-excursion (search-backward "\n\n" nil t))
-             (insert "\n"))
-           (put-text-property
-            (point)
-            (progn
+           ;; Skip past lines we want to leave visible.
+           (when (and beg end gnus-cited-lines-visible)
+             (goto-char beg)
+             (forward-line (if (consp gnus-cited-lines-visible)
+                               (car gnus-cited-lines-visible)
+                             gnus-cited-lines-visible))
+             (if (>= (point) end)
+                 (setq beg nil)
+               (setq beg (point-marker))
+               (when (consp gnus-cited-lines-visible)
+                 (goto-char end)
+                 (forward-line (- (cdr gnus-cited-lines-visible)))
+                 (if (<= (point) beg)
+                     (setq beg nil)
+                 (setq end (point-marker))))))
+           (when (and beg end)
+             (gnus-add-wash-type 'cite)
+             ;; We use markers for the end-points to facilitate later
+             ;; wrapping and mangling of text.
+             (setq beg (set-marker (make-marker) beg)
+                   end (set-marker (make-marker) end))
+             (gnus-add-text-properties-when 'article-type nil beg end props)
+             (goto-char beg)
+             (when (and gnus-cite-blank-line-after-header
+                        (not (save-excursion (search-backward "\n\n" nil t))))
+               (insert "\n"))
+             (put-text-property
+              (setq start (point-marker))
+              (progn
               (gnus-article-add-button
                (point)
-               (progn (eval gnus-cited-text-button-line-format-spec) (point))
-               `gnus-article-toggle-cited-text (cons beg end))
+               (progn (eval gnus-cited-closed-text-button-line-format-spec)
+                      (point))
+               `gnus-article-toggle-cited-text
+               (list (cons beg end) start))
               (point))
-            'article-type 'annotation)
-           (set-marker beg (point)))))))))
-
-(defun gnus-article-toggle-cited-text (region)
-  "Toggle hiding the text in REGION."
-  (let (buffer-read-only)
-    (funcall
-     (if (text-property-any
-         (car region) (1- (cdr region))
-         (car gnus-hidden-properties) (cadr gnus-hidden-properties))
-        'remove-text-properties 'gnus-add-text-properties)
-     (car region) (cdr region) gnus-hidden-properties)))
+              'article-type 'annotation)
+             (set-marker beg (point))))))))
+
+(defun gnus-article-toggle-cited-text (args &optional arg)
+  "Toggle hiding the text in REGION.
+ARG can be nil or a number.  Positive means hide, negative
+means show, nil means toggle."
+  (let* ((region (car args))
+        (beg (car region))
+        (end (cdr region))
+        (start (cadr args))
+        (hidden
+         (text-property-any beg (1- end) 'article-type 'cite))
+        (inhibit-point-motion-hooks t)
+        buffer-read-only)
+    (when (or (null arg)
+             (zerop arg)
+             (and (> arg 0) (not hidden))
+             (and (< arg 0) hidden))
+      (if hidden
+         (progn
+           ;; Can't remove 'cite from g-a-wash-types here because
+           ;; multiple citations may be hidden -jas
+           (gnus-remove-text-properties-when
+            'article-type 'cite beg end
+            (cons 'article-type (cons 'cite
+                                      gnus-hidden-properties))))
+       (gnus-add-wash-type 'cite)
+       (gnus-add-text-properties-when
+        'article-type nil beg end
+        (cons 'article-type (cons 'cite
+                                  gnus-hidden-properties))))
+      (let ((gnus-article-mime-handle-alist-1 gnus-article-mime-handle-alist))
+       (gnus-set-mode-line 'article))
+      (save-excursion
+       (goto-char start)
+       (gnus-delete-line)
+       (put-text-property
+        (point)
+        (progn
+          (gnus-article-add-button
+          &n