Importing qgnus-0.20
[elisp/gnus.git-] / lisp / gnus-art.el
index db13b3d..f1b4b6d 100644 (file)
@@ -95,7 +95,7 @@
     "^Date-Received:" "^References:" "^Control:" "^Xref:" "^Lines:"
     "^Posted:" "^Relay-Version:" "^Message-ID:" "^Nf-ID:" "^Nf-From:"
     "^Approved:" "^Sender:" "^Received:" "^Mail-from:")
-  "All headers that match this regexp will be hidden.
+  "All headers that start with this regexp will be hidden.
 This variable can also be a list of regexps of headers to be ignored.
 If `gnus-visible-headers' is non-nil, this variable will be ignored."
   :type '(choice :custom-show nil
@@ -104,7 +104,7 @@ If `gnus-visible-headers' is non-nil, this variable will be ignored."
   :group 'gnus-article-hiding)
 
 (defcustom gnus-visible-headers
-  "^From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-From"
+  "^From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-From:\\|X-Sent:"
   "All headers that do not match this regexp will be hidden.
 This variable can also be a list of regexp of headers to remain visible.
 If this variable is non-nil, `gnus-ignored-headers' will be ignored."
@@ -129,13 +129,14 @@ this list."
 (defcustom gnus-boring-article-headers '(empty followup-to reply-to)
   "Headers that are only to be displayed if they have interesting data.
 Possible values in this list are `empty', `newsgroups', `followup-to',
-`reply-to', and `date'."
+`reply-to', `date', `long-to', and `many-to'."
   :type '(set (const :tag "Headers with no content." empty)
              (const :tag "Newsgroups with only one group." newsgroups)
              (const :tag "Followup-to identical to newsgroups." followup-to)
              (const :tag "Reply-to identical to from." reply-to)
              (const :tag "Date less than four days old." date)
-             (const :tag "Very long To header." long-to))
+             (const :tag "Very long To header." long-to)
+             (const :tag "Multiple To headers." many-to))
   :group 'gnus-article-hiding)
 
 (defcustom gnus-signature-separator '("^-- $" "^-- *$")
@@ -154,7 +155,10 @@ longer (in lines) than that number.  If it is a function, the function
 will be called without any parameters, and if it returns nil, there is
 no signature in the buffer.  If it is a string, it will be used as a
 regexp.  If it matches, the text in question is not a signature."
-  :type '(choice integer number function regexp)
+  :type '(choice (integer :value 200)
+                (number :value 4.0)
+                (function :value fun)
+                (regexp :value ".*"))
   :group 'gnus-article-signature)
 
 (defcustom gnus-hidden-properties '(invisible t intangible t)
@@ -269,7 +273,7 @@ each invocation of the saving commands."
   :group 'gnus-article-saving
   :type '(choice (item always)
                 (item :tag "never" nil)
-                (sexp :tag "once" :format "%t")))
+                (sexp :tag "once" :format "%t\n" :value t)))
 
 (defcustom gnus-saved-headers gnus-visible-headers
   "Headers to keep if `gnus-save-all-headers' is nil.
@@ -348,9 +352,9 @@ If this form or function returns a string, this string will be used as
 a possible file name; and if it returns a non-nil list, that list will
 be used as possible file names."
   :group 'gnus-article-saving
-  :type '(repeat (choice (list function)
-                        (cons regexp (repeat string))
-                        sexp)))
+  :type '(repeat (choice (list :value (fun) function)
+                        (cons :value ("" "") regexp (repeat string))
+                        (sexp :value nil))))
 
 (defcustom gnus-strict-mime t
   "*If nil, MIME-decode even if there is no Mime-Version header."
@@ -525,6 +529,8 @@ displayed by the first non-nil matching CONTENT face."
 
 ;;; Internal variables
 
+(defvar article-lapsed-timer nil)
+
 (defvar gnus-article-mode-syntax-table
   (let ((table (copy-syntax-table text-mode-syntax-table)))
     (modify-syntax-entry ?- "w" table)
@@ -618,6 +624,7 @@ Initialized from `text-mode-syntax-table.")
 If given a negative prefix, always show; if given a positive prefix,
 always hide."
   (interactive (gnus-article-hidden-arg))
+  (current-buffer)
   (if (gnus-article-check-hidden-text 'headers arg)
       ;; Show boring headers as well.
       (gnus-article-show-hidden-text 'boring-headers)
@@ -747,7 +754,21 @@ always hide."
             ((eq elem 'long-to)
              (let ((to (message-fetch-field "to")))
                (when (> (length to) 1024)
-                 (gnus-article-hide-header "to")))))))))))
+                 (gnus-article-hide-header "to"))))
+            ((eq elem 'many-to)
+             (let ((to-count 0))
+               (goto-char (point-min))
+               (while (re-search-forward "^to:" nil t)
+                 (setq to-count (1+ to-count)))
+               (when (> to-count 1)
+                 (while (> to-count 0)
+                   (goto-char (point-min))
+                   (save-restriction
+                     (re-search-forward "^to:" nil nil to-count)
+                     (forward-line -1)
+                     (narrow-to-region (point) (point-max))
+                     (gnus-article-hide-header "to"))
+                   (setq to-count (1- to-count)))))))))))))
 
 (defun gnus-article-hide-header (header)
   (save-excursion
@@ -762,7 +783,29 @@ always hide."
           (point-max)))
        'boring-headers))))
 
-;; Written by Per Abrahamsen <amanda@iesd.auc.dk>.
+(defun article-treat-dumbquotes ()
+  "Translate M******** sm*rtq**t*s into proper text."
+  (interactive)
+  (article-translate-characters "\221\222\223\223" "`'\"\""))
+
+(defun article-translate-characters (from to)
+  "Translate all characters in the body of the article according to FROM and TO.
+FROM is a string of characters to translate from; to is a string of
+characters to translate to."
+  (save-excursion
+    (goto-char (point-min))
+    (when (search-forward "\n\n" nil t)
+      (let ((buffer-read-only nil)
+           (x (make-string 225 ?x))
+           (i -1))
+       (while (< (incf i) (length x))
+         (aset x i i))
+       (setq i 0)
+       (while (< i (length from))
+         (aset x (aref from i) (aref to i))
+         (incf i))
+       (translate-region (point) (point-max) x)))))
+
 (defun article-treat-overstrike ()
   "Translate overstrikes into bold text."
   (interactive)
@@ -840,7 +883,7 @@ always hide."
     (when (process-status "article-x-face")
       (delete-process "article-x-face"))
     (let ((inhibit-point-motion-hooks t)
-         (case-fold-search nil)
+         (case-fold-search t)
          from)
       (save-restriction
        (nnheader-narrow-to-headers)
@@ -1242,7 +1285,7 @@ how much time has lapsed since DATE."
                 header))
         (date-regexp "^Date:[ \t]\\|^X-Sent:[ \t]")
         (inhibit-point-motion-hooks t)
-        bface eface)
+        bface eface newline)
     (when (and date (not (string= date "")))
       (save-excursion
        (save-restriction
@@ -1254,17 +1297,22 @@ how much time has lapsed since DATE."
                  (setq bface (get-text-property (gnus-point-at-bol) 'face)
                        eface (get-text-property (1- (gnus-point-at-eol))
                                                 'face))
-                 (message-remove-header date-regexp t)
+                 (delete-region (progn (beginning-of-line) (point))
+                                (progn (end-of-line) (point)))
                  (beginning-of-line))
-             (goto-char (point-max)))
+             (goto-char (point-max))
+             (setq newline t))
            (insert (article-make-date-line date type))
            ;; Do highlighting.
-           (forward-line -1)
+           (beginning-of-line)
            (when (looking-at "\\([^:]+\\): *\\(.*\\)$")
-             (put-text-property (match-beginning 1) (match-end 1)
+             (put-text-property (match-beginning 1) (1+ (match-end 1))
                                 'face bface)
              (put-text-property (match-beginning 2) (match-end 2)
-                                'face eface))))))))
+                                'face eface))
+           (when newline
+             (end-of-line)
+             (insert "\n"))))))))
 
 (defun article-make-date-line (date type)
   "Return a DATE line of TYPE."
@@ -1276,18 +1324,16 @@ how much time has lapsed since DATE."
    ((eq type 'local)
     (concat "Date: " (condition-case ()
                         (timezone-make-date-arpa-standard date)
-                      (error date))
-           "\n"))
+                      (error date))))
    ;; Convert to Universal Time.
    ((eq type 'ut)
     (concat "Date: "
            (condition-case ()
                (timezone-make-date-arpa-standard date nil "UT")
-             (error date))
-           "\n"))
+             (error date))))
    ;; Get the original date from the article.
    ((eq type 'original)
-    (concat "Date: " date "\n"))
+    (concat "Date: " date))
    ;; Let the user define the format.
    ((eq type 'user)
     (concat
@@ -1296,8 +1342,7 @@ how much time has lapsed since DATE."
                         (ignore-errors
                           (gnus-encode-date
                            (timezone-make-date-arpa-standard
-                            date nil "UT"))))
-     "\n"))
+                            date nil "UT"))))))
    ;; Do an X-Sent lapsed format.
    ((eq type 'lapsed)
     ;; If the date is seriously mangled, the timezone functions are
@@ -1320,9 +1365,9 @@ how much time has lapsed since DATE."
           num prev)
       (cond
        ((null real-time)
-       "X-Sent: Unknown\n")
+       "X-Sent: Unknown")
        ((zerop sec)
-       "X-Sent: Now\n")
+       "X-Sent: Now")
        (t
        (concat
         "X-Sent: "
@@ -1348,8 +1393,8 @@ how much time has lapsed since DATE."
         ;; If dates are odd, then it might appear like the
         ;; article was sent in the future.
         (if (> real-sec 0)
-            " ago\n"
-          " in the future\n"))))))
+            " ago"
+          " in the future"))))))
    (t
     (error "Unknown conversion type: %s" type))))
 
@@ -1370,6 +1415,34 @@ function and want to see what the date was before converting."
   (interactive (list t))
   (article-date-ut 'lapsed highlight))
 
+(defun article-update-date-lapsed ()
+  "Function to be run from a timer to update the lapsed time line."
+  (save-excursion
+    (ignore-errors
+      (when (gnus-buffer-live-p gnus-article-buffer)
+       (set-buffer gnus-article-buffer)
+       (goto-char (point-min))
+       (when (re-search-forward "^X-Sent:" nil t)
+         (article-date-lapsed t))))))
+
+(defun gnus-start-date-timer (&optional n)
+  "Start a timer to update the X-Sent header in the article buffers.
+The numerical prefix says how frequently (in seconds) the function
+is to run."
+  (interactive "p")
+  (unless n
+    (setq n 1))
+  (gnus-stop-date-timer)
+  (setq article-lapsed-timer 
+       (nnheader-run-at-time 1 n 'article-update-date-lapsed)))
+
+(defun gnus-stop-date-timer ()
+  "Stop the X-Sent timer."
+  (interactive)
+  (when article-lapsed-timer
+    (nnheader-cancel-timer article-lapsed-timer)
+    (setq article-lapsed-timer nil)))
+
 (defun article-date-user (&optional highlight)
   "Convert the current article date to the user-defined format.
 This format is defined by the `gnus-article-time-format' variable."
@@ -1424,7 +1497,9 @@ This format is defined by the `gnus-article-time-format' variable."
     (let ((gnus-visible-headers
           (or gnus-saved-headers gnus-visible-headers))
          (gnus-article-buffer save-buffer))
-      (gnus-article-hide-headers 1 t)))
+      (save-excursion
+       (set-buffer save-buffer)
+       (article-hide-headers 1 t))))
   (save-window-excursion
     (if (not gnus-default-article-saver)
        (error "No default saver is defined")
@@ -1539,7 +1614,6 @@ This format is defined by the `gnus-article-time-format' variable."
 Optional argument FILENAME specifies file name.
 Directory to save to is default to `gnus-article-save-directory'."
   (interactive)
-  (gnus-set-global-variables)
   (setq filename (gnus-read-save-file-name
                  "Save %s in rmail file:" filename
                  gnus-rmail-save-name gnus-newsgroup-name
@@ -1548,14 +1622,14 @@ Directory to save to is default to `gnus-article-save-directory'."
     (save-excursion
       (save-restriction
        (widen)
-       (gnus-output-to-rmail filename)))))
+       (gnus-output-to-rmail filename))))
+  filename)
 
 (defun gnus-summary-save-in-mail (&optional filename)
   "Append this article to Unix mail file.
 Optional argument FILENAME specifies file name.
 Directory to save to is default to `gnus-article-save-directory'."
   (interactive)
-  (gnus-set-global-variables)
   (setq filename (gnus-read-save-file-name
                  "Save %s in Unix mail file:" filename
                  gnus-mail-save-name gnus-newsgroup-name
@@ -1567,14 +1641,14 @@ Directory to save to is default to `gnus-article-save-directory'."
        (if (and (file-readable-p filename)
                 (mail-file-babyl-p filename))
            (gnus-output-to-rmail filename t)
-         (gnus-output-to-mail filename))))))
+         (gnus-output-to-mail filename)))))
+  filename)
 
 (defun gnus-summary-save-in-file (&optional filename overwrite)
   "Append this article to file.
 Optional argument FILENAME specifies file name.
 Directory to save to is default to `gnus-article-save-directory'."
   (interactive)
-  (gnus-set-global-variables)
   (setq filename (gnus-read-save-file-name
                  "Save %s in file:" filename
                  gnus-file-save-name gnus-newsgroup-name
@@ -1586,7 +1660,8 @@ Directory to save to is default to `gnus-article-save-directory'."
        (when (and overwrite
                   (file-exists-p filename))
          (delete-file filename))
-       (gnus-output-to-file filename)))))
+       (gnus-output-to-file filename))))
+  filename)
 
 (defun gnus-summary-write-to-file (&optional filename)
   "Write this article to a file.
@@ -1600,7 +1675,6 @@ The directory to save in defaults to `gnus-article-save-directory'."
 Optional argument FILENAME specifies file name.
 The directory to save in defaults to `gnus-article-save-directory'."
   (interactive)
-  (gnus-set-global-variables)
   (setq filename (gnus-read-save-file-name
                  "Save %s body in file:" filename
                  gnus-file-save-name gnus-newsgroup-name
@@ -1612,12 +1686,12 @@ The directory to save in defaults to `gnus-article-save-directory'."
        (goto-char (point-min))
        (when (search-forward "\n\n" nil t)
          (narrow-to-region (point) (point-max)))
-       (gnus-output-to-file filename)))))
+       (gnus-output-to-file filename))))
+  filename)
 
 (defun gnus-summary-save-in-pipe (&optional command)
   "Pipe this article to subprocess."
   (interactive)
-  (gnus-set-global-variables)
   (setq command
        (cond ((eq command 'default)
               gnus-last-shell-command)
@@ -1747,6 +1821,7 @@ If variable `gnus-use-long-file-name' is non-nil, it is
      article-date-user
      article-date-lapsed
      article-emphasize
+     article-treat-dumbquotes
      (article-show-all . gnus-article-show-all-headers))))
 \f
 ;;;
@@ -1953,9 +2028,8 @@ If ALL-HEADERS is non-nil, no headers are hidden."
              (progn
                (save-excursion
                  (set-buffer summary-buffer)
+                 (push article gnus-newsgroup-history)
                  (setq gnus-last-article gnus-current-article
-                       gnus-newsgroup-history (cons gnus-current-article
-                                                    gnus-newsgroup-history)
                        gnus-current-article 0
                        gnus-current-headers nil
                        gnus-article-current nil)
@@ -1973,9 +2047,8 @@ If ALL-HEADERS is non-nil, no headers are hidden."
              ;; `gnus-current-article' must be an article number.
              (save-excursion
                (set-buffer summary-buffer)
+               (push article gnus-newsgroup-history)
                (setq gnus-last-article gnus-current-article
-                     gnus-newsgroup-history (cons gnus-current-article
-                                                  gnus-newsgroup-history)
                      gnus-current-article article
                      gnus-current-headers
                      (gnus-summary-article-header gnus-current-article)
@@ -2502,6 +2575,28 @@ groups."
 (defun gnus-article-edit-done (&optional arg)
   "Update the article edits and exit."
   (interactive "P")
+  (save-excursion
+    (save-restriction
+      (widen)
+      (goto-char (point-min))
+      (when (search-forward "\n\n" nil 1)
+       (let ((lines (count-lines (point) (point-max)))
+             (length (- (point-max) (point)))
+             (case-fold-search t)
+             (body (copy-marker (point))))
+         (goto-char (point-min))
+         (when (re-search-forward "^content-length:[ \t]\\([0-9]+\\)" body t)
+           (delete-region (match-beginning 1) (match-end 1))
+           (insert (number-to-string length)))
+         (goto-char (point-min))
+         (when (re-search-forward
+                "^x-content-length:[ \t]\\([0-9]+\\)" body t)
+           (delete-region (match-beginning 1) (match-end 1))
+           (insert (number-to-string length)))
+         (goto-char (point-min))
+         (when (re-search-forward "^lines:[ \t]\\([0-9]+\\)" body t)
+           (delete-region (match-beginning 1) (match-end 1))
+           (insert (number-to-string lines)))))))
   (let ((func gnus-article-edit-done-function)
        (buf (current-buffer))
        (start (window-start)))
@@ -2574,8 +2669,8 @@ groups."
     ("\\bnews:\\([^>\n\t ]*@[^>\n\t ]*\\)" 0 t gnus-button-message-id 1)
     ("\\(\\b<\\(url: ?\\)?news:\\(//\\)?\\([^>\n\t ]*\\)>\\)" 1 t
      gnus-button-fetch-group 4)
-    ("\\bnews:\\(//\\)?\\([^>\n\t ]+\\)" 0 t gnus-button-fetch-group 2)
-    ("\\bin\\( +article\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)" 2
+    ("\\bnews:\\(//\\)?\\([^'\">\n\t ]+\\)" 0 t gnus-button-fetch-group 2)
+    ("\\bin\\( +article\\| +message\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)" 2
      t gnus-button-message-id 3)
     ("\\(<URL: *\\)mailto: *\\([^> \n\t]+\\)>" 0 t gnus-url-mailto 2)
     ("\\bmailto:\\([^ \n\t]+\\)" 0 t gnus-url-mailto 1)