Merge from emacs--devo--0
[gnus] / lisp / gnus-util.el
index 7d5da22..a327522 100644 (file)
@@ -1,27 +1,25 @@
 ;;; gnus-util.el --- utility functions for Gnus
 
 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
-;;   2005, 2006 Free Software Foundation, Inc.
+;;   2005, 2006, 2007, 2008 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: news
 
 ;; This file is part of GNU Emacs.
 
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs 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.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
 
 ;; GNU Emacs 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
+;; 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 GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
-;; Boston, MA 02110-1301, USA.
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Commentary:
 
 
 ;;; Code:
 
+;; For Emacs < 22.2.
+(eval-and-compile
+  (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
 (eval-when-compile
-  (require 'cl)
-  ;; Fixme: this should be a gnus variable, not nnmail-.
-  (defvar nnmail-pathname-coding-system)
-  (defvar nnmail-active-file-coding-system)
-
-  ;; Inappropriate references to other parts of Gnus.
-  (defvar gnus-emphasize-whitespace-regexp)
-  (defvar gnus-original-article-buffer)
-  (defvar gnus-user-agent)
-  )
+  (require 'cl))
+;; Fixme: this should be a gnus variable, not nnmail-.
+(defvar nnmail-pathname-coding-system)
+(defvar nnmail-active-file-coding-system)
+
+;; Inappropriate references to other parts of Gnus.
+(defvar gnus-emphasize-whitespace-regexp)
+(defvar gnus-original-article-buffer)
+(defvar gnus-user-agent)
+
 (require 'time-date)
 (require 'netrc)
 
@@ -74,20 +75,7 @@ string containing the replacements.
 This is a compatibility function for different Emacsen."
       (replace-regexp-in-string regexp newtext string nil literal)))
    ((fboundp 'replace-in-string)
-    (defalias 'gnus-replace-in-string 'replace-in-string))
-   (t
-    (defun gnus-replace-in-string (string regexp newtext &optional literal)
-      "Replace all matches for REGEXP with NEWTEXT in STRING.
-If LITERAL is non-nil, insert NEWTEXT literally.  Return a new
-string containing the replacements.
-
-This is a compatibility function for different Emacsen."
-      (let ((start 0) tail)
-       (while (string-match regexp string start)
-         (setq tail (- (length string) (match-end 0)))
-         (setq string (replace-match newtext nil literal string))
-         (setq start (- (length string) tail))))
-      string))))
+    (defalias 'gnus-replace-in-string 'replace-in-string))))
 
 (defun gnus-boundp (variable)
   "Return non-nil if VARIABLE is bound and non-nil."
@@ -116,21 +104,12 @@ This is a compatibility function for different Emacsen."
 (put 'gnus-eval-in-buffer-window 'edebug-form-spec '(form body))
 
 (defmacro gnus-intern-safe (string hashtable)
-  "Set hash value.  Arguments are STRING, VALUE, and HASHTABLE."
+  "Get hash value.  Arguments are STRING and HASHTABLE."
   `(let ((symbol (intern ,string ,hashtable)))
      (or (boundp symbol)
         (set symbol nil))
      symbol))
 
-;; Added by Geoffrey T. Dairiki <dairiki@u.washington.edu>.  A safe way
-;; to limit the length of a string.  This function is necessary since
-;; `(substr "abc" 0 30)' pukes with "Args out of range".
-;; Fixme: Why not `truncate-string-to-width'?
-(defsubst gnus-limit-string (str width)
-  (if (> (length str) width)
-      (substring str 0 width)
-    str))
-
 (defsubst gnus-goto-char (point)
   (and point (goto-char point)))
 
@@ -186,8 +165,13 @@ is slower."
     ;; First find the address - the thing with the @ in it.  This may
     ;; not be accurate in mail addresses, but does the trick most of
     ;; the time in news messages.
-    (when (string-match "\\b[^@ \t<>]+[!@][^@ \t<>]+\\b" from)
-      (setq address (substring from (match-beginning 0) (match-end 0))))
+    (cond (;; Check ``<foo@bar>'' first in order to handle the quite common
+          ;; form ``"abc@xyz" <foo@bar>'' (i.e. ``@'' as part of a comment)
+          ;; correctly.
+          (string-match "<\\([^@ \t<>]+[!@][^@ \t<>]+\\)>" from)
+          (setq address (substring from (match-beginning 1) (match-end 1))))
+         ((string-match "\\b[^@ \t<>]+[!@][^@ \t<>]+\\b" from)
+          (setq address (substring from (match-beginning 0) (match-end 0)))))
     ;; Then we check whether the "name <address>" format is used.
     (and address
         ;; Linear white space is not required.
@@ -209,6 +193,15 @@ is slower."
                                   (match-end 0)))))
     (list (if (string= name "") nil name) (or address from))))
 
+(defun gnus-extract-address-component-name (from)
+  "Extract name from a From header.
+Uses `gnus-extract-address-components'."
+  (nth 0 (gnus-extract-address-components from)))
+
+(defun gnus-extract-address-component-email (from)
+  "Extract e-mail address from a From header.
+Uses `gnus-extract-address-components'."
+  (nth 1 (gnus-extract-address-components from)))
 
 (defun gnus-fetch-field (field)
   "Return the value of the header FIELD of current article."
@@ -231,6 +224,13 @@ is slower."
                   (search-forward ":" eol t)
                   (point)))))
 
+(declare-function gnus-find-method-for-group "gnus" (group &optional info))
+(autoload 'gnus-group-name-decode "gnus-group")
+(declare-function gnus-group-name-charset "gnus-group" (method group))
+;; gnus-group requires gnus-int which requires message.
+(declare-function message-tokenize-header "message"
+                  (header &optional separator))
+
 (defun gnus-decode-newsgroups (newsgroups group &optional method)
   (let ((method (or method (gnus-find-method-for-group group))))
     (mapconcat (lambda (group)
@@ -271,6 +271,15 @@ is slower."
   (not (or (string< s1 s2)
           (string= s1 s2))))
 
+(defun gnus-string< (s1 s2)
+  "Return t if first arg string is less than second in lexicographic order.
+Case is significant if and only if `case-fold-search' is nil.
+Symbols are also allowed; their print names are used instead."
+  (if case-fold-search
+      (string-lessp (downcase (if (symbolp s1) (symbol-name s1) s1))
+                   (downcase (if (symbolp s2) (symbol-name s2) s2)))
+    (string-lessp s1 s2)))
+
 ;;; Time functions.
 
 (defun gnus-file-newer-than (file date)
@@ -335,15 +344,55 @@ is slower."
 
 ;; Two silly functions to ensure that all `y-or-n-p' questions clear
 ;; the echo area.
-(defun gnus-y-or-n-p (prompt)
-  (prog1
-      (y-or-n-p prompt)
-    (message "")))
-
-(defun gnus-yes-or-no-p (prompt)
-  (prog1
-      (yes-or-no-p prompt)
-    (message "")))
+;;
+;; Do we really need these functions?  Workarounds for bugs in the corresponding
+;; Emacs functions?  Maybe these bugs are no longer present in any supported
+;; (X)Emacs version?  Alias them to the original functions and see if anyone
+;; reports a problem.  If not, replace with original functions.  --rsteib,
+;; 2007-12-14
+;;
+;; All supported Emacsen clear the echo area after `yes-or-no-p', so we can
+;; remove `yes-or-no-p'.  RMS says that not clearing after `y-or-n-p' is
+;; intentional (see below), so we could remove `gnus-y-or-n-p' too.
+;; Objections?  --rsteib, 2008-02-16
+;;
+;; ,----[ http://thread.gmane.org/gmane.emacs.gnus.general/65099/focus=66070 ]
+;; | From: Richard Stallman
+;; | Subject: Re: Do we need gnus-yes-or-no-p and gnus-y-or-n-p?
+;; | To: Katsumi Yamaoka [...]
+;; | Cc: emacs-devel@[...], xemacs-beta@[...], ding@[...]
+;; | Date: Mon, 07 Jan 2008 12:16:05 -0500
+;; | Message-ID: <E1JBva1-000528-VY@fencepost.gnu.org>
+;; |
+;; |     The behavior of `y-or-n-p' that it doesn't clear the question
+;; |     and the answer is not serious of course, but I feel it is not
+;; |     cool.
+;; |
+;; | It is intentional.
+;; |
+;; |     Currently, it is commented out in the trunk by Reiner Steib.  He
+;; |     also wrote the benefit of leaving the question and the answer in
+;; |     the echo area as follows:
+;; |
+;; |     (http://article.gmane.org/gmane.emacs.gnus.general/66061)
+;; |     > In contrast to yes-or-no-p it is much easier to type y, n,
+;; |     > SPC, DEL, etc accidentally, so it might be useful for the user
+;; |     > to see what he has typed.
+;; |
+;; | Yes, that is the reason.
+;; `----
+
+;; (defun gnus-y-or-n-p (prompt)
+;;   (prog1
+;;       (y-or-n-p prompt)
+;;     (message "")))
+;; (defun gnus-yes-or-no-p (prompt)
+;;   (prog1
+;;       (yes-or-no-p prompt)
+;;     (message "")))
+
+(defalias 'gnus-y-or-n-p 'y-or-n-p)
+(defalias 'gnus-yes-or-no-p 'yes-or-no-p)
 
 ;; By Frank Schmitt <ich@Frank-Schmitt.net>. Allows to have
 ;; age-depending date representations. (e.g. just the time if it's
@@ -473,6 +522,79 @@ jabbering all the time."
   :group 'gnus-start
   :type 'integer)
 
+(defcustom gnus-add-timestamp-to-message nil
+  "Non-nil means add timestamps to messages that Gnus issues.
+If it is `log', add timestamps to only the messages that go into the
+\"*Messages*\" buffer (in XEmacs, it is the \" *Message-Log*\" buffer).
+If it is neither nil nor `log', add timestamps not only to log messages
+but also to the ones displayed in the echo area."
+  :version "23.1" ;; No Gnus
+  :group  'gnus-various
+  :type '(choice :format "%{%t%}:\n %[Value Menu%] %v"
+                (const :tag "Logged messages only" log)
+                (sexp :tag "All messages"
+                      :match (lambda (widget value) value)
+                      :value t)
+                (const :tag "No timestamp" nil)))
+
+(eval-when-compile
+  (defmacro gnus-message-with-timestamp-1 (format-string args)
+    (let ((timestamp '((format-time-string "%Y%m%dT%H%M%S" time)
+                      "." (format "%03d" (/ (nth 2 time) 1000)) "> ")))
+      (if (featurep 'xemacs)
+         `(let (str time)
+            (if (or (and (null ,format-string) (null ,args))
+                    (progn
+                      (setq str (apply 'format ,format-string ,args))
+                      (zerop (length str))))
+                (prog1
+                    (and ,format-string str)
+                  (clear-message nil))
+              (cond ((eq gnus-add-timestamp-to-message 'log)
+                     (setq time (current-time))
+                     (display-message 'no-log str)
+                     (log-message 'message (concat ,@timestamp str)))
+                    (gnus-add-timestamp-to-message
+                     (setq time (current-time))
+                     (display-message 'message (concat ,@timestamp str)))
+                    (t
+                     (display-message 'message str))))
+            str)
+       `(let (str time)
+          (cond ((eq gnus-add-timestamp-to-message 'log)
+                 (setq str (let (message-log-max)
+                             (apply 'message ,format-string ,args)))
+                 (when (and message-log-max
+                            (> message-log-max 0)
+                            (/= (length str) 0))
+                   (setq time (current-time))
+                   (with-current-buffer (get-buffer-create "*Messages*")
+                     (goto-char (point-max))
+                     (insert ,@timestamp str "\n")
+                     (forward-line (- message-log-max))
+                     (delete-region (point-min) (point))
+                     (goto-char (point-max))))
+                 str)
+                (gnus-add-timestamp-to-message
+                 (if (or (and (null ,format-string) (null ,args))
+                         (progn
+                           (setq str (apply 'format ,format-string ,args))
+                           (zerop (length str))))
+                     (prog1
+                         (and ,format-string str)
+                       (message nil))
+                   (setq time (current-time))
+                   (message "%s" (concat ,@timestamp str))
+                   str))
+                (t
+                 (apply 'message ,format-string ,args))))))))
+
+(defun gnus-message-with-timestamp (format-string &rest args)
+  "Display message with timestamp.  Arguments are the same as `message'.
+The `gnus-add-timestamp-to-message' variable controls how to add
+timestamp to message."
+  (gnus-message-with-timestamp-1 format-string args))
+
 (defun gnus-message (level &rest args)
   "If LEVEL is lower than `gnus-verbose' print ARGS using `message'.
 
@@ -481,7 +603,9 @@ Guideline for numbers:
 that take a long time, 7 - not very important messages on stuff, 9 - messages
 inside loops."
   (if (<= level gnus-verbose)
-      (apply 'message args)
+      (if gnus-add-timestamp-to-message
+         (apply 'gnus-message-with-timestamp args)
+       (apply 'message args))
     ;; We have to do this format thingy here even if the result isn't
     ;; shown - the return value has to be the same as the return value
     ;; from `message'.
@@ -512,7 +636,7 @@ ARGS are passed to `message'."
 (defun gnus-extract-references (references)
   "Return a list of Message-IDs in REFERENCES (in In-Reply-To
   format), trimmed to only contain the Message-IDs."
-  (let ((ids (gnus-split-references references)) 
+  (let ((ids (gnus-split-references references))
        refs)
     (dolist (id ids)
       (when (string-match "<[^<>]+>" id)
@@ -587,6 +711,10 @@ If N, return the Nth ancestor instead."
 
 (defvar gnus-work-buffer " *gnus work*")
 
+(declare-function gnus-get-buffer-create "gnus" (name))
+;; gnus.el requires mm-util.
+(declare-function mm-enable-multibyte "mm-util")
+
 (defun gnus-set-work-buffer ()
   "Put point in the empty Gnus work buffer."
   (if (get-buffer gnus-work-buffer)
@@ -609,8 +737,10 @@ If N, return the Nth ancestor instead."
 For example, (gnus-group-server \"nnimap+yxa:INBOX.foo\") would
 yield \"nnimap:yxa\"."
   `(let ((gname ,group))
-     (if (string-match "^\\([^+]+\\).\\([^:]+\\):" gname)
-        (format "%s:%s" (match-string 1 gname) (match-string 2 gname))
+     (if (string-match "^\\([^:+]+\\)\\(?:\\+\\([^:]*\\)\\)?:" gname)
+        (format "%s:%s" (match-string 1 gname) (or
+                                                (match-string 2 gname)
+                                                ""))
        (format "%s:%s" (car gnus-select-method) (cadr gnus-select-method)))))
 
 (defun gnus-make-sort-function (funs)
@@ -713,9 +843,9 @@ Bind `print-quoted' and `print-readably' to t, and `print-length' and
 
 (defun gnus-write-buffer (file)
   "Write the current buffer's contents to FILE."
-  ;; Make sure the directory exists.
-  (gnus-make-directory (file-name-directory file))
   (let ((file-name-coding-system nnmail-pathname-coding-system))
+    ;; Make sure the directory exists.
+    (gnus-make-directory (file-name-directory file))
     ;; Write the buffer.
     (write-region (point-min) (point-max) file nil 'quietly)))
 
@@ -769,6 +899,9 @@ If there's no subdirectory, delete DIRECTORY as well."
     (setq string (replace-match "" t t string)))
   string)
 
+(declare-function gnus-put-text-property "gnus"
+                  (start end property value &optional object))
+
 (defsubst gnus-put-text-property-excluding-newlines (beg end prop val)
   "The same as `put-text-property', but don't put this prop on any newlines in the region."
   (save-match-data
@@ -780,6 +913,10 @@ If there's no subdirectory, delete DIRECTORY as well."
          (setq beg (point)))
        (gnus-put-text-property beg (point) prop val)))))
 
+(declare-function gnus-overlay-put  "gnus" (overlay prop value))
+(declare-function gnus-make-overlay "gnus"
+                  (beg end &optional buffer front-advance rear-advance))
+
 (defsubst gnus-put-overlay-excluding-newlines (beg end prop val)
   "The same as `put-text-property', but don't put this prop on any newlines in the region."
   (save-match-data
@@ -913,9 +1050,13 @@ with potentially long computations."
      ;; version fails halfway, however it provides the rmail-select-summary
      ;; macro which uses the following functions:
      (autoload 'rmail-summary-displayed "rmail")
-     (autoload 'rmail-maybe-display-summary "rmail")))
-  (defvar rmail-default-rmail-file)
-  (defvar mm-text-coding-system))
+     (autoload 'rmail-maybe-display-summary "rmail"))))
+
+(defvar rmail-default-rmail-file)
+(defvar mm-text-coding-system)
+
+(declare-function mm-append-to-file "mm-util"
+                  (start end filename &optional codesys inhibit))
 
 (defun gnus-output-to-rmail (filename &optional ask)
   "Append the current article to an Rmail file named FILENAME."
@@ -1098,7 +1239,7 @@ Return the modified alist."
     `(setq ,alist (delq (,fun ,key ,alist) ,alist))))
 
 (defun gnus-globalify-regexp (re)
-  "Return a regexp that matches a whole line, iff RE matches a part of it."
+  "Return a regexp that matches a whole line, if RE matches a part of it."
   (concat (unless (string-match "^\\^" re) "^.*")
          re
          (unless (string-match "\\$$" re) ".*$")))
@@ -1129,9 +1270,16 @@ Return the modified alist."
        (throw 'found nil)))
     t))
 
+;; gnus.el requires mm-util.
+(declare-function mm-disable-multibyte "mm-util")
+
 (defun gnus-write-active-file (file hashtb &optional full-names)
+  ;; `coding-system-for-write' should be `raw-text' or equivalent.
   (let ((coding-system-for-write nnmail-active-file-coding-system))
     (with-temp-file file
+      ;; The buffer should be in the unibyte mode because group names
+      ;; are ASCII text or encoded non-ASCII text (i.e., unibyte).
+      (mm-disable-multibyte)
       (mapatoms
        (lambda (sym)
         (when (and sym
@@ -1192,6 +1340,9 @@ Return the modified alist."
             (pop l2))
           l1))))
 
+(declare-function gnus-add-text-properties "gnus"
+                  (start end properties &optional object))
+
 (defun gnus-add-text-properties-when
   (property value start end properties &optional object)
   "Like `gnus-add-text-properties', only applied on where PROPERTY is VALUE."
@@ -1217,6 +1368,13 @@ Return the modified alist."
        (remove-text-properties start end properties object))
     t))
 
+(defun gnus-string-remove-all-properties (string)
+  (condition-case ()
+      (let ((s string))
+       (set-text-properties 0 (length string) nil string)
+       s)
+    (error string)))
+
 ;; This might use `compare-strings' to reduce consing in the
 ;; case-insensitive case, but it has to cope with null args.
 ;; (`string-equal' uses symbol print names.)
@@ -1434,6 +1592,8 @@ CHOICE is a list of the choice char and help message at IDX."
        (kill-buffer buf))
     tchar))
 
+(declare-function w32-focus-frame "../term/w32-win" (frame))
+
 (defun gnus-select-frame-set-input-focus (frame)
   "Select FRAME, raise it, and set input focus, if possible."
   (cond ((featurep 'xemacs)
@@ -1471,8 +1631,7 @@ Return nil otherwise."
                                 display))
              display)))))
 
-(eval-when-compile
-  (defvar tool-bar-mode))
+(defvar tool-bar-mode)
 
 (defun gnus-tool-bar-update (&rest ignore)
   "Update the tool bar."
@@ -1541,10 +1700,9 @@ predicate on the elements."
          (push (pop list1) res)))
       (nconc (nreverse res) list1 list2))))
 
-(eval-when-compile
-  (defvar xemacs-codename)
-  (defvar sxemacs-codename)
-  (defvar emacs-program-version))
+(defvar xemacs-codename)
+(defvar sxemacs-codename)
+(defvar emacs-program-version)
 
 (defun gnus-emacs-version ()
   "Stringified Emacs version."
@@ -1577,13 +1735,16 @@ predicate on the elements."
      ((or (featurep 'sxemacs) (featurep 'xemacs))
       ;; XEmacs or SXEmacs:
       (concat emacsname "/" emacs-program-version
-             " ("
-             (when (and (memq 'codename lst)
-                        codename)
-               (concat codename
-                       (when system-v ", ")))
-             (when system-v system-v)
-             ")"))
+             (let (plst)
+               (when (memq 'codename lst)
+                 (push codename plst))
+               (when system-v
+                 (push system-v plst))
+               (unless (featurep 'mule)
+                 (push "no MULE" plst))
+               (when (> (length plst) 0)
+                 (concat
+                  " (" (mapconcat 'identity (reverse plst) ", ") ")")))))
      (t emacs-version))))
 
 (defun gnus-rename-file (old-path new-path &optional trim)
@@ -1618,7 +1779,26 @@ empty directories from OLD-PATH."
   (defalias 'gnus-set-process-query-on-exit-flag
     'process-kill-without-query))
 
+(if (fboundp 'with-local-quit)
+    (defalias 'gnus-with-local-quit 'with-local-quit)
+  (defmacro gnus-with-local-quit (&rest body)
+    "Execute BODY, allowing quits to terminate BODY but not escape further.
+When a quit terminates BODY, `gnus-with-local-quit' returns nil but
+requests another quit.  That quit will be processed as soon as quitting
+is allowed once again.  (Immediately, if `inhibit-quit' is nil.)"
+    ;;(declare (debug t) (indent 0))
+    `(condition-case nil
+        (let ((inhibit-quit nil))
+          ,@body)
+       (quit (setq quit-flag t)
+            ;; This call is to give a chance to handle quit-flag
+            ;; in case inhibit-quit is nil.
+            ;; Without this, it will not be handled until the next function
+            ;; call, and that might allow it to exit thru a condition-case
+            ;; that intends to handle the quit signal next time.
+            (eval '(ignore nil))))))
+
 (provide 'gnus-util)
 
-;;; arch-tag: f94991af-d32b-4c97-8c26-ca12a934de49
+;; arch-tag: f94991af-d32b-4c97-8c26-ca12a934de49
 ;;; gnus-util.el ends here