(gnus-float-time): Make TIME optional, defaulting to current-time.
[bpt/emacs.git] / lisp / gnus / gnus-util.el
CommitLineData
eec82323 1;;; gnus-util.el --- utility functions for Gnus
e84b4b86 2
88e6695f 3;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
ae940284 4;; 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
eec82323 5
6748645f 6;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
eec82323
LMI
7;; Keywords: news
8
9;; This file is part of GNU Emacs.
10
5e809f55 11;; GNU Emacs is free software: you can redistribute it and/or modify
eec82323 12;; it under the terms of the GNU General Public License as published by
5e809f55
GM
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
eec82323
LMI
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
5e809f55 18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
eec82323
LMI
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
5e809f55 22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
eec82323
LMI
23
24;;; Commentary:
25
26;; Nothing in this file depends on any other parts of Gnus -- all
27;; functions and macros in this file are utility functions that are
28;; used by Gnus and may be used by any other package without loading
29;; Gnus first.
30
23f87bed 31;; [Unfortunately, it does depend on other parts of Gnus, e.g. the
01c52d31 32;; autoloads and defvars below...]
23f87bed 33
eec82323
LMI
34;;; Code:
35
88bfa2e4
GM
36;; For Emacs < 22.2.
37(eval-and-compile
38 (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
5eee36fa 39(eval-when-compile
9efa445f
DN
40 (require 'cl))
41;; Fixme: this should be a gnus variable, not nnmail-.
42(defvar nnmail-pathname-coding-system)
43(defvar nnmail-active-file-coding-system)
44
45;; Inappropriate references to other parts of Gnus.
46(defvar gnus-emphasize-whitespace-regexp)
47(defvar gnus-original-article-buffer)
48(defvar gnus-user-agent)
49
16409b0b 50(require 'time-date)
23f87bed 51(require 'netrc)
eec82323 52
dbb6c370
GM
53(autoload 'message-fetch-field "message")
54(autoload 'gnus-get-buffer-window "gnus-win")
dbb6c370
GM
55(autoload 'nnheader-narrow-to-headers "nnheader")
56(autoload 'nnheader-replace-chars-in-string "nnheader")
09aece0b 57(autoload 'mail-header-remove-comments "mail-parse")
23f87bed
MB
58
59(eval-and-compile
60 (cond
f67d6742 61 ;; Prefer `replace-regexp-in-string' (present in Emacs, XEmacs 21.5,
4b4f6dc8 62 ;; SXEmacs 22.1.4) over `replace-in-string'. The latter leads to inf-loops
f67d6742
MB
63 ;; on empty matches:
64 ;; (replace-in-string "foo" "/*$" "/")
65 ;; (replace-in-string "xe" "\\(x\\)?" "")
23f87bed 66 ((fboundp 'replace-regexp-in-string)
01c52d31 67 (defun gnus-replace-in-string (string regexp newtext &optional literal)
ad136a7c
MB
68 "Replace all matches for REGEXP with NEWTEXT in STRING.
69If LITERAL is non-nil, insert NEWTEXT literally. Return a new
70string containing the replacements.
71
72This is a compatibility function for different Emacsen."
23f87bed 73 (replace-regexp-in-string regexp newtext string nil literal)))
f67d6742 74 ((fboundp 'replace-in-string)
01c52d31 75 (defalias 'gnus-replace-in-string 'replace-in-string))))
eec82323
LMI
76
77(defun gnus-boundp (variable)
78 "Return non-nil if VARIABLE is bound and non-nil."
79 (and (boundp variable)
80 (symbol-value variable)))
81
82(defmacro gnus-eval-in-buffer-window (buffer &rest forms)
83 "Pop to BUFFER, evaluate FORMS, and then return to the original window."
84 (let ((tempvar (make-symbol "GnusStartBufferWindow"))
23f87bed
MB
85 (w (make-symbol "w"))
86 (buf (make-symbol "buf")))
eec82323 87 `(let* ((,tempvar (selected-window))
23f87bed
MB
88 (,buf ,buffer)
89 (,w (gnus-get-buffer-window ,buf 'visible)))
eec82323 90 (unwind-protect
23f87bed
MB
91 (progn
92 (if ,w
93 (progn
94 (select-window ,w)
95 (set-buffer (window-buffer ,w)))
96 (pop-to-buffer ,buf))
97 ,@forms)
98 (select-window ,tempvar)))))
eec82323
LMI
99
100(put 'gnus-eval-in-buffer-window 'lisp-indent-function 1)
101(put 'gnus-eval-in-buffer-window 'edebug-form-spec '(form body))
102
103(defmacro gnus-intern-safe (string hashtable)
815b81c8 104 "Get hash value. Arguments are STRING and HASHTABLE."
eec82323
LMI
105 `(let ((symbol (intern ,string ,hashtable)))
106 (or (boundp symbol)
107 (set symbol nil))
108 symbol))
109
eec82323
LMI
110(defsubst gnus-goto-char (point)
111 (and point (goto-char point)))
112
113(defmacro gnus-buffer-exists-p (buffer)
114 `(let ((buffer ,buffer))
115 (when buffer
116 (funcall (if (stringp buffer) 'get-buffer 'buffer-name)
117 buffer))))
118
23f87bed
MB
119;; The LOCAL arg to `add-hook' is interpreted differently in Emacs and
120;; XEmacs. In Emacs we don't need to call `make-local-hook' first.
121;; It's harmless, though, so the main purpose of this alias is to shut
122;; up the byte compiler.
123(defalias 'gnus-make-local-hook
124 (if (eq (get 'make-local-hook 'byte-compile)
125 'byte-compile-obsolete)
126 'ignore ; Emacs
127 'make-local-hook)) ; XEmacs
128
eec82323
LMI
129(defun gnus-delete-first (elt list)
130 "Delete by side effect the first occurrence of ELT as a member of LIST."
131 (if (equal (car list) elt)
132 (cdr list)
133 (let ((total list))
134 (while (and (cdr list)
135 (not (equal (cadr list) elt)))
136 (setq list (cdr list)))
137 (when (cdr list)
138 (setcdr list (cddr list)))
139 total)))
140
141;; Delete the current line (and the next N lines).
142(defmacro gnus-delete-line (&optional n)
01c52d31 143 `(delete-region (point-at-bol)
eec82323
LMI
144 (progn (forward-line ,(or n 1)) (point))))
145
146(defun gnus-byte-code (func)
147 "Return a form that can be `eval'ed based on FUNC."
6748645f
LMI
148 (let ((fval (indirect-function func)))
149 (if (byte-code-function-p fval)
eec82323
LMI
150 (let ((flist (append fval nil)))
151 (setcar flist 'byte-code)
152 flist)
153 (cons 'progn (cddr fval)))))
154
155(defun gnus-extract-address-components (from)
23f87bed
MB
156 "Extract address components from a From header.
157Given an RFC-822 address FROM, extract full name and canonical address.
158Returns a list of the form (FULL-NAME CANONICAL-ADDRESS). Much more simple
159solution than `mail-extract-address-components', which works much better, but
160is slower."
eec82323
LMI
161 (let (name address)
162 ;; First find the address - the thing with the @ in it. This may
163 ;; not be accurate in mail addresses, but does the trick most of
164 ;; the time in news messages.
4573e0df
MB
165 (cond (;; Check ``<foo@bar>'' first in order to handle the quite common
166 ;; form ``"abc@xyz" <foo@bar>'' (i.e. ``@'' as part of a comment)
167 ;; correctly.
168 (string-match "<\\([^@ \t<>]+[!@][^@ \t<>]+\\)>" from)
169 (setq address (substring from (match-beginning 1) (match-end 1))))
170 ((string-match "\\b[^@ \t<>]+[!@][^@ \t<>]+\\b" from)
171 (setq address (substring from (match-beginning 0) (match-end 0)))))
eec82323
LMI
172 ;; Then we check whether the "name <address>" format is used.
173 (and address
eec82323
LMI
174 ;; Linear white space is not required.
175 (string-match (concat "[ \t]*<" (regexp-quote address) ">") from)
176 (and (setq name (substring from 0 (match-beginning 0)))
177 ;; Strip any quotes from the name.
23f87bed 178 (string-match "^\".*\"$" name)
eec82323
LMI
179 (setq name (substring name 1 (1- (match-end 0))))))
180 ;; If not, then "address (name)" is used.
181 (or name
182 (and (string-match "(.+)" from)
183 (setq name (substring from (1+ (match-beginning 0))
184 (1- (match-end 0)))))
185 (and (string-match "()" from)
186 (setq name address))
eec82323
LMI
187 ;; XOVER might not support folded From headers.
188 (and (string-match "(.*" from)
189 (setq name (substring from (1+ (match-beginning 0))
190 (match-end 0)))))
16409b0b
GM
191 (list (if (string= name "") nil name) (or address from))))
192
0ab5c2be
MB
193(defun gnus-extract-address-component-name (from)
194 "Extract name from a From header.
195Uses `gnus-extract-address-components'."
196 (nth 0 (gnus-extract-address-components from)))
197
198(defun gnus-extract-address-component-email (from)
199 "Extract e-mail address from a From header.
200Uses `gnus-extract-address-components'."
201 (nth 1 (gnus-extract-address-components from)))
eec82323
LMI
202
203(defun gnus-fetch-field (field)
204 "Return the value of the header FIELD of current article."
205 (save-excursion
206 (save-restriction
01c52d31 207 (let ((inhibit-point-motion-hooks t))
eec82323
LMI
208 (nnheader-narrow-to-headers)
209 (message-fetch-field field)))))
210
23f87bed
MB
211(defun gnus-fetch-original-field (field)
212 "Fetch FIELD from the original version of the current article."
213 (with-current-buffer gnus-original-article-buffer
214 (gnus-fetch-field field)))
215
216
eec82323
LMI
217(defun gnus-goto-colon ()
218 (beginning-of-line)
01c52d31 219 (let ((eol (point-at-eol)))
23f87bed
MB
220 (goto-char (or (text-property-any (point) eol 'gnus-position t)
221 (search-forward ":" eol t)
222 (point)))))
223
5ec7fe1b
GM
224(declare-function gnus-find-method-for-group "gnus" (group &optional info))
225(autoload 'gnus-group-name-decode "gnus-group")
226(declare-function gnus-group-name-charset "gnus-group" (method group))
227;; gnus-group requires gnus-int which requires message.
228(declare-function message-tokenize-header "message"
229 (header &optional separator))
230
23f87bed
MB
231(defun gnus-decode-newsgroups (newsgroups group &optional method)
232 (let ((method (or method (gnus-find-method-for-group group))))
233 (mapconcat (lambda (group)
234 (gnus-group-name-decode group (gnus-group-name-charset
235 method group)))
236 (message-tokenize-header newsgroups)
237 ",")))
eec82323
LMI
238
239(defun gnus-remove-text-with-property (prop)
240 "Delete all text in the current buffer with text property PROP."
01c52d31
MB
241 (let ((start (point-min))
242 end)
243 (unless (get-text-property start prop)
244 (setq start (next-single-property-change start prop)))
245 (while start
246 (setq end (text-property-any start (point-max) prop nil))
247 (delete-region start (or end (point-max)))
248 (setq start (when end
249 (next-single-property-change start prop))))))
eec82323
LMI
250
251(defun gnus-newsgroup-directory-form (newsgroup)
252 "Make hierarchical directory name from NEWSGROUP name."
23f87bed
MB
253 (let* ((newsgroup (gnus-newsgroup-savable-name newsgroup))
254 (idx (string-match ":" newsgroup)))
255 (concat
256 (if idx (substring newsgroup 0 idx))
257 (if idx "/")
258 (nnheader-replace-chars-in-string
259 (if idx (substring newsgroup (1+ idx)) newsgroup)
260 ?. ?/))))
eec82323
LMI
261
262(defun gnus-newsgroup-savable-name (group)
263 ;; Replace any slashes in a group name (eg. an ange-ftp nndoc group)
264 ;; with dots.
265 (nnheader-replace-chars-in-string group ?/ ?.))
266
267(defun gnus-string> (s1 s2)
268 (not (or (string< s1 s2)
269 (string= s1 s2))))
270
b4fde39f
MB
271(defun gnus-string< (s1 s2)
272 "Return t if first arg string is less than second in lexicographic order.
273Case is significant if and only if `case-fold-search' is nil.
274Symbols are also allowed; their print names are used instead."
275 (if case-fold-search
276 (string-lessp (downcase (if (symbolp s1) (symbol-name s1) s1))
277 (downcase (if (symbolp s2) (symbol-name s2) s2)))
278 (string-lessp s1 s2)))
279
eec82323
LMI
280;;; Time functions.
281
eec82323
LMI
282(defun gnus-file-newer-than (file date)
283 (let ((fdate (nth 5 (file-attributes file))))
284 (or (> (car fdate) (car date))
285 (and (= (car fdate) (car date))
286 (> (nth 1 fdate) (nth 1 date))))))
287
c506adde
GM
288(defun gnus-float-time (&optional time)
289 "Convert time value TIME to a floating point number.
290TIME defaults to the current time."
feefd9f3 291 (if (featurep 'xemacs)
c506adde 292 (time-to-seconds (or time (current-time)))
feefd9f3
GM
293 (float-time time)))
294
eec82323
LMI
295;;; Keymap macros.
296
297(defmacro gnus-local-set-keys (&rest plist)
298 "Set the keys in PLIST in the current keymap."
299 `(gnus-define-keys-1 (current-local-map) ',plist))
300
301(defmacro gnus-define-keys (keymap &rest plist)
302 "Define all keys in PLIST in KEYMAP."
303 `(gnus-define-keys-1 (quote ,keymap) (quote ,plist)))
304
305(defmacro gnus-define-keys-safe (keymap &rest plist)
306 "Define all keys in PLIST in KEYMAP without overwriting previous definitions."
307 `(gnus-define-keys-1 (quote ,keymap) (quote ,plist) t))
308
309(put 'gnus-define-keys 'lisp-indent-function 1)
310(put 'gnus-define-keys-safe 'lisp-indent-function 1)
311(put 'gnus-local-set-keys 'lisp-indent-function 1)
312
313(defmacro gnus-define-keymap (keymap &rest plist)
314 "Define all keys in PLIST in KEYMAP."
315 `(gnus-define-keys-1 ,keymap (quote ,plist)))
316
317(put 'gnus-define-keymap 'lisp-indent-function 1)
318
319(defun gnus-define-keys-1 (keymap plist &optional safe)
320 (when (null keymap)
321 (error "Can't set keys in a null keymap"))
322 (cond ((symbolp keymap)
323 (setq keymap (symbol-value keymap)))
324 ((keymapp keymap))
325 ((listp keymap)
326 (set (car keymap) nil)
327 (define-prefix-command (car keymap))
328 (define-key (symbol-value (caddr keymap)) (cadr keymap) (car keymap))
329 (setq keymap (symbol-value (car keymap)))))
330 (let (key)
331 (while plist
332 (when (symbolp (setq key (pop plist)))
333 (setq key (symbol-value key)))
334 (if (or (not safe)
335 (eq (lookup-key keymap key) 'undefined))
336 (define-key keymap key (pop plist))
337 (pop plist)))))
338
23f87bed 339(defun gnus-completing-read-with-default (default prompt &rest args)
eec82323
LMI
340 ;; Like `completing-read', except that DEFAULT is the default argument.
341 (let* ((prompt (if default
81df110a
RF
342 (concat prompt " (default " default "): ")
343 (concat prompt ": ")))
eec82323
LMI
344 (answer (apply 'completing-read prompt args)))
345 (if (or (null answer) (zerop (length answer)))
346 default
347 answer)))
348
349;; Two silly functions to ensure that all `y-or-n-p' questions clear
350;; the echo area.
bbbe940b 351;;
52bec650
MB
352;; Do we really need these functions? Workarounds for bugs in the corresponding
353;; Emacs functions? Maybe these bugs are no longer present in any supported
bbbe940b 354;; (X)Emacs version? Alias them to the original functions and see if anyone
52bec650
MB
355;; reports a problem. If not, replace with original functions. --rsteib,
356;; 2007-12-14
bbbe940b 357;;
52bec650
MB
358;; All supported Emacsen clear the echo area after `yes-or-no-p', so we can
359;; remove `yes-or-no-p'. RMS says that not clearing after `y-or-n-p' is
360;; intentional (see below), so we could remove `gnus-y-or-n-p' too.
361;; Objections? --rsteib, 2008-02-16
362;;
363;; ,----[ http://thread.gmane.org/gmane.emacs.gnus.general/65099/focus=66070 ]
364;; | From: Richard Stallman
365;; | Subject: Re: Do we need gnus-yes-or-no-p and gnus-y-or-n-p?
366;; | To: Katsumi Yamaoka [...]
367;; | Cc: emacs-devel@[...], xemacs-beta@[...], ding@[...]
368;; | Date: Mon, 07 Jan 2008 12:16:05 -0500
369;; | Message-ID: <E1JBva1-000528-VY@fencepost.gnu.org>
370;; |
371;; | The behavior of `y-or-n-p' that it doesn't clear the question
372;; | and the answer is not serious of course, but I feel it is not
373;; | cool.
374;; |
375;; | It is intentional.
376;; |
377;; | Currently, it is commented out in the trunk by Reiner Steib. He
378;; | also wrote the benefit of leaving the question and the answer in
379;; | the echo area as follows:
380;; |
381;; | (http://article.gmane.org/gmane.emacs.gnus.general/66061)
382;; | > In contrast to yes-or-no-p it is much easier to type y, n,
383;; | > SPC, DEL, etc accidentally, so it might be useful for the user
384;; | > to see what he has typed.
385;; |
386;; | Yes, that is the reason.
387;; `----
388
bbbe940b
MB
389;; (defun gnus-y-or-n-p (prompt)
390;; (prog1
391;; (y-or-n-p prompt)
392;; (message "")))
393;; (defun gnus-yes-or-no-p (prompt)
394;; (prog1
395;; (yes-or-no-p prompt)
396;; (message "")))
397
398(defalias 'gnus-y-or-n-p 'y-or-n-p)
399(defalias 'gnus-yes-or-no-p 'yes-or-no-p)
eec82323 400
23f87bed
MB
401;; By Frank Schmitt <ich@Frank-Schmitt.net>. Allows to have
402;; age-depending date representations. (e.g. just the time if it's
403;; from today, the day of the week if it's within the last 7 days and
404;; the full date if it's older)
405
406(defun gnus-seconds-today ()
407 "Return the number of seconds passed today."
408 (let ((now (decode-time (current-time))))
409 (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600))))
410
411(defun gnus-seconds-month ()
412 "Return the number of seconds passed this month."
413 (let ((now (decode-time (current-time))))
414 (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600)
415 (* (- (car (nthcdr 3 now)) 1) 3600 24))))
416
417(defun gnus-seconds-year ()
418 "Return the number of seconds passed this year."
419 (let ((now (decode-time (current-time)))
420 (days (format-time-string "%j" (current-time))))
421 (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600)
422 (* (- (string-to-number days) 1) 3600 24))))
423
424(defvar gnus-user-date-format-alist
425 '(((gnus-seconds-today) . "%k:%M")
426 (604800 . "%a %k:%M") ;;that's one week
427 ((gnus-seconds-month) . "%a %d")
428 ((gnus-seconds-year) . "%b %d")
429 (t . "%b %d '%y")) ;;this one is used when no
430 ;;other does match
431 "Specifies date format depending on age of article.
432This is an alist of items (AGE . FORMAT). AGE can be a number (of
433seconds) or a Lisp expression evaluating to a number. When the age of
434the article is less than this number, then use `format-time-string'
435with the corresponding FORMAT for displaying the date of the article.
436If AGE is not a number or a Lisp expression evaluating to a
437non-number, then the corresponding FORMAT is used as a default value.
438
439Note that the list is processed from the beginning, so it should be
440sorted by ascending AGE. Also note that items following the first
441non-number AGE will be ignored.
442
443You can use the functions `gnus-seconds-today', `gnus-seconds-month'
444and `gnus-seconds-year' in the AGE spec. They return the number of
445seconds passed since the start of today, of this month, of this year,
446respectively.")
447
448(defun gnus-user-date (messy-date)
449 "Format the messy-date according to gnus-user-date-format-alist.
450Returns \" ? \" if there's bad input or if an other error occurs.
451Input should look like this: \"Sun, 14 Oct 2001 13:34:39 +0200\"."
452 (condition-case ()
feefd9f3
GM
453 (let* ((messy-date (gnus-float-time (safe-date-to-time messy-date)))
454 (now (gnus-float-time (current-time)))
23f87bed
MB
455 ;;If we don't find something suitable we'll use this one
456 (my-format "%b %d '%y"))
457 (let* ((difference (- now messy-date))
458 (templist gnus-user-date-format-alist)
459 (top (eval (caar templist))))
460 (while (if (numberp top) (< top difference) (not top))
461 (progn
462 (setq templist (cdr templist))
463 (setq top (eval (caar templist)))))
464 (if (stringp (cdr (car templist)))
465 (setq my-format (cdr (car templist)))))
466 (format-time-string (eval my-format) (seconds-to-time messy-date)))
467 (error " ? ")))
468
eec82323 469(defun gnus-dd-mmm (messy-date)
6748645f 470 "Return a string like DD-MMM from a big messy string."
16409b0b
GM
471 (condition-case ()
472 (format-time-string "%d-%b" (safe-date-to-time messy-date))
473 (error " - ")))
eec82323
LMI
474
475(defmacro gnus-date-get-time (date)
476 "Convert DATE string to Emacs time.
477Cache the result as a text property stored in DATE."
478 ;; Either return the cached value...
479 `(let ((d ,date))
480 (if (equal "" d)
481 '(0 0)
482 (or (get-text-property 0 'gnus-time d)
483 ;; or compute the value...
16409b0b 484 (let ((time (safe-date-to-time d)))
eec82323
LMI
485 ;; and store it back in the string.
486 (put-text-property 0 1 'gnus-time time d)
487 time)))))
488
489(defsubst gnus-time-iso8601 (time)
8b93df01 490 "Return a string of TIME in YYYYMMDDTHHMMSS format."
eec82323
LMI
491 (format-time-string "%Y%m%dT%H%M%S" time))
492
6748645f 493(defun gnus-date-iso8601 (date)
8b93df01 494 "Convert the DATE to YYYYMMDDTHHMMSS."
eec82323 495 (condition-case ()
6748645f 496 (gnus-time-iso8601 (gnus-date-get-time date))
eec82323
LMI
497 (error "")))
498
499(defun gnus-mode-string-quote (string)
500 "Quote all \"%\"'s in STRING."
23f87bed 501 (gnus-replace-in-string string "%" "%%"))
eec82323
LMI
502
503;; Make a hash table (default and minimum size is 256).
504;; Optional argument HASHSIZE specifies the table size.
505(defun gnus-make-hashtable (&optional hashsize)
506 (make-vector (if hashsize (max (gnus-create-hash-size hashsize) 256) 256) 0))
507
508;; Make a number that is suitable for hashing; bigger than MIN and
509;; equal to some 2^x. Many machines (such as sparcs) do not have a
510;; hardware modulo operation, so they implement it in software. On
511;; many sparcs over 50% of the time to intern is spent in the modulo.
512;; Yes, it's slower than actually computing the hash from the string!
513;; So we use powers of 2 so people can optimize the modulo to a mask.
514(defun gnus-create-hash-size (min)
515 (let ((i 1))
516 (while (< i min)
517 (setq i (* 2 i)))
518 i))
519
520(defcustom gnus-verbose 7
521 "*Integer that says how verbose Gnus should be.
522The higher the number, the more messages Gnus will flash to say what
523it's doing. At zero, Gnus will be totally mute; at five, Gnus will
524display most important messages; and at ten, Gnus will keep on
525jabbering all the time."
526 :group 'gnus-start
527 :type 'integer)
528
01c52d31
MB
529(defcustom gnus-add-timestamp-to-message nil
530 "Non-nil means add timestamps to messages that Gnus issues.
531If it is `log', add timestamps to only the messages that go into the
532\"*Messages*\" buffer (in XEmacs, it is the \" *Message-Log*\" buffer).
533If it is neither nil nor `log', add timestamps not only to log messages
534but also to the ones displayed in the echo area."
330f707b 535 :version "23.1" ;; No Gnus
01c52d31
MB
536 :group 'gnus-various
537 :type '(choice :format "%{%t%}:\n %[Value Menu%] %v"
538 (const :tag "Logged messages only" log)
539 (sexp :tag "All messages"
540 :match (lambda (widget value) value)
541 :value t)
542 (const :tag "No timestamp" nil)))
543
544(eval-when-compile
545 (defmacro gnus-message-with-timestamp-1 (format-string args)
546 (let ((timestamp '((format-time-string "%Y%m%dT%H%M%S" time)
547 "." (format "%03d" (/ (nth 2 time) 1000)) "> ")))
548 (if (featurep 'xemacs)
549 `(let (str time)
550 (if (or (and (null ,format-string) (null ,args))
551 (progn
552 (setq str (apply 'format ,format-string ,args))
553 (zerop (length str))))
554 (prog1
555 (and ,format-string str)
556 (clear-message nil))
557 (cond ((eq gnus-add-timestamp-to-message 'log)
558 (setq time (current-time))
559 (display-message 'no-log str)
560 (log-message 'message (concat ,@timestamp str)))
561 (gnus-add-timestamp-to-message
562 (setq time (current-time))
563 (display-message 'message (concat ,@timestamp str)))
564 (t
565 (display-message 'message str))))
566 str)
567 `(let (str time)
568 (cond ((eq gnus-add-timestamp-to-message 'log)
569 (setq str (let (message-log-max)
570 (apply 'message ,format-string ,args)))
571 (when (and message-log-max
572 (> message-log-max 0)
573 (/= (length str) 0))
574 (setq time (current-time))
575 (with-current-buffer (get-buffer-create "*Messages*")
576 (goto-char (point-max))
577 (insert ,@timestamp str "\n")
578 (forward-line (- message-log-max))
579 (delete-region (point-min) (point))
580 (goto-char (point-max))))
581 str)
582 (gnus-add-timestamp-to-message
583 (if (or (and (null ,format-string) (null ,args))
584 (progn
585 (setq str (apply 'format ,format-string ,args))
586 (zerop (length str))))
587 (prog1
588 (and ,format-string str)
589 (message nil))
590 (setq time (current-time))
591 (message "%s" (concat ,@timestamp str))
592 str))
593 (t
594 (apply 'message ,format-string ,args))))))))
595
596(defun gnus-message-with-timestamp (format-string &rest args)
597 "Display message with timestamp. Arguments are the same as `message'.
598The `gnus-add-timestamp-to-message' variable controls how to add
599timestamp to message."
600 (gnus-message-with-timestamp-1 format-string args))
601
eec82323 602(defun gnus-message (level &rest args)
23f87bed
MB
603 "If LEVEL is lower than `gnus-verbose' print ARGS using `message'.
604
605Guideline for numbers:
6061 - error messages, 3 - non-serious error messages, 5 - messages for things
607that take a long time, 7 - not very important messages on stuff, 9 - messages
608inside loops."
eec82323 609 (if (<= level gnus-verbose)
01c52d31
MB
610 (if gnus-add-timestamp-to-message
611 (apply 'gnus-message-with-timestamp args)
612 (apply 'message args))
eec82323
LMI
613 ;; We have to do this format thingy here even if the result isn't
614 ;; shown - the return value has to be the same as the return value
615 ;; from `message'.
616 (apply 'format args)))
617
618(defun gnus-error (level &rest args)
6203370b
MB
619 "Beep an error if LEVEL is equal to or less than `gnus-verbose'.
620ARGS are passed to `message'."
eec82323
LMI
621 (when (<= (floor level) gnus-verbose)
622 (apply 'message args)
623 (ding)
624 (let (duration)
625 (when (and (floatp level)
626 (not (zerop (setq duration (* 10 (- level (floor level)))))))
627 (sit-for duration))))
628 nil)
629
630(defun gnus-split-references (references)
631 "Return a list of Message-IDs in REFERENCES."
632 (let ((beg 0)
521c4a23 633 (references (mail-header-remove-comments (or references "")))
eec82323 634 ids)
23f87bed 635 (while (string-match "<[^<]+[^< \t]" references beg)
eec82323
LMI
636 (push (substring references (match-beginning 0) (setq beg (match-end 0)))
637 ids))
638 (nreverse ids)))
639
01c52d31
MB
640(defun gnus-extract-references (references)
641 "Return a list of Message-IDs in REFERENCES (in In-Reply-To
642 format), trimmed to only contain the Message-IDs."
643 (let ((ids (gnus-split-references references))
644 refs)
645 (dolist (id ids)
646 (when (string-match "<[^<>]+>" id)
647 (push (match-string 0 id) refs)))
648 refs))
649
16409b0b 650(defsubst gnus-parent-id (references &optional n)
eec82323
LMI
651 "Return the last Message-ID in REFERENCES.
652If N, return the Nth ancestor instead."
23f87bed
MB
653 (when (and references
654 (not (zerop (length references))))
655 (if n
656 (let ((ids (inline (gnus-split-references references))))
657 (while (nthcdr n ids)
658 (setq ids (cdr ids)))
659 (car ids))
521c4a23
AS
660 (let ((references (mail-header-remove-comments references)))
661 (when (string-match "\\(<[^<]+>\\)[ \t]*\\'" references)
662 (match-string 1 references))))))
23f87bed
MB
663
664(defun gnus-buffer-live-p (buffer)
eec82323
LMI
665 "Say whether BUFFER is alive or not."
666 (and buffer
667 (get-buffer buffer)
668 (buffer-name (get-buffer buffer))))
669
670(defun gnus-horizontal-recenter ()
671 "Recenter the current buffer horizontally."
672 (if (< (current-column) (/ (window-width) 2))
23f87bed 673 (set-window-hscroll (gnus-get-buffer-window (current-buffer) t) 0)
eec82323 674 (let* ((orig (point))
23f87bed 675 (end (window-end (gnus-get-buffer-window (current-buffer) t)))
eec82323 676 (max 0))
6748645f
LMI
677 (when end
678 ;; Find the longest line currently displayed in the window.
679 (goto-char (window-start))
680 (while (and (not (eobp))
681 (< (point) end))
682 (end-of-line)
683 (setq max (max max (current-column)))
684 (forward-line 1))
685 (goto-char orig)
686 ;; Scroll horizontally to center (sort of) the point.
687 (if (> max (window-width))
688 (set-window-hscroll
23f87bed 689 (gnus-get-buffer-window (current-buffer) t)
6748645f
LMI
690 (min (- (current-column) (/ (window-width) 3))
691 (+ 2 (- max (window-width)))))
23f87bed 692 (set-window-hscroll (gnus-get-buffer-window (current-buffer) t) 0))
6748645f 693 max))))
eec82323 694
23f87bed 695(defun gnus-read-event-char (&optional prompt)
eec82323 696 "Get the next event."
23f87bed 697 (let ((event (read-event prompt)))
eec82323
LMI
698 ;; should be gnus-characterp, but this can't be called in XEmacs anyway
699 (cons (and (numberp event) event) event)))
700
701(defun gnus-sortable-date (date)
16409b0b
GM
702 "Make string suitable for sorting from DATE."
703 (gnus-time-iso8601 (date-to-time date)))
eec82323
LMI
704
705(defun gnus-copy-file (file &optional to)
706 "Copy FILE to TO."
707 (interactive
708 (list (read-file-name "Copy file: " default-directory)
709 (read-file-name "Copy file to: " default-directory)))
710 (unless to
711 (setq to (read-file-name "Copy file to: " default-directory)))
712 (when (file-directory-p to)
713 (setq to (concat (file-name-as-directory to)
714 (file-name-nondirectory file))))
715 (copy-file file to))
716
eec82323
LMI
717(defvar gnus-work-buffer " *gnus work*")
718
5ec7fe1b
GM
719(declare-function gnus-get-buffer-create "gnus" (name))
720;; gnus.el requires mm-util.
721(declare-function mm-enable-multibyte "mm-util")
722
eec82323
LMI
723(defun gnus-set-work-buffer ()
724 "Put point in the empty Gnus work buffer."
725 (if (get-buffer gnus-work-buffer)
726 (progn
727 (set-buffer gnus-work-buffer)
728 (erase-buffer))
6748645f 729 (set-buffer (gnus-get-buffer-create gnus-work-buffer))
eec82323 730 (kill-all-local-variables)
16409b0b 731 (mm-enable-multibyte)))
eec82323
LMI
732
733(defmacro gnus-group-real-name (group)
734 "Find the real name of a foreign newsgroup."
735 `(let ((gname ,group))
736 (if (string-match "^[^:]+:" gname)
737 (substring gname (match-end 0))
738 gname)))
739
6c5d6b6c
MB
740(defmacro gnus-group-server (group)
741 "Find the server name of a foreign newsgroup.
742For example, (gnus-group-server \"nnimap+yxa:INBOX.foo\") would
743yield \"nnimap:yxa\"."
744 `(let ((gname ,group))
745 (if (string-match "^\\([^:+]+\\)\\(?:\\+\\([^:]*\\)\\)?:" gname)
746 (format "%s:%s" (match-string 1 gname) (or
747 (match-string 2 gname)
748 ""))
749 (format "%s:%s" (car gnus-select-method) (cadr gnus-select-method)))))
750
eec82323 751(defun gnus-make-sort-function (funs)
23f87bed 752 "Return a composite sort condition based on the functions in FUNS."
eec82323 753 (cond
16409b0b 754 ;; Just a simple function.
23f87bed 755 ((functionp funs) funs)
16409b0b 756 ;; No functions at all.
eec82323 757 ((null funs) funs)
16409b0b
GM
758 ;; A list of functions.
759 ((or (cdr funs)
760 (listp (car funs)))
23f87bed
MB
761 (gnus-byte-compile
762 `(lambda (t1 t2)
763 ,(gnus-make-sort-function-1 (reverse funs)))))
16409b0b 764 ;; A list containing just one function.
eec82323
LMI
765 (t
766 (car funs))))
767
768(defun gnus-make-sort-function-1 (funs)
23f87bed 769 "Return a composite sort condition based on the functions in FUNS."
16409b0b
GM
770 (let ((function (car funs))
771 (first 't1)
772 (last 't2))
773 (when (consp function)
774 (cond
775 ;; Reversed spec.
776 ((eq (car function) 'not)
777 (setq function (cadr function)
778 first 't2
779 last 't1))
23f87bed 780 ((functionp function)
16409b0b
GM
781 ;; Do nothing.
782 )
783 (t
784 (error "Invalid sort spec: %s" function))))
785 (if (cdr funs)
786 `(or (,function ,first ,last)
787 (and (not (,function ,last ,first))
788 ,(gnus-make-sort-function-1 (cdr funs))))
789 `(,function ,first ,last))))
eec82323
LMI
790
791(defun gnus-turn-off-edit-menu (type)
792 "Turn off edit menu in `gnus-TYPE-mode-map'."
793 (define-key (symbol-value (intern (format "gnus-%s-mode-map" type)))
794 [menu-bar edit] 'undefined))
795
23f87bed
MB
796(defmacro gnus-bind-print-variables (&rest forms)
797 "Bind print-* variables and evaluate FORMS.
798This macro is used with `prin1', `pp', etc. in order to ensure printed
799Lisp objects are loadable. Bind `print-quoted' and `print-readably'
800to t, and `print-escape-multibyte', `print-escape-newlines',
801`print-escape-nonascii', `print-length', `print-level' and
802`print-string-length' to nil."
803 `(let ((print-quoted t)
804 (print-readably t)
805 ;;print-circle
806 ;;print-continuous-numbering
807 print-escape-multibyte
808 print-escape-newlines
809 print-escape-nonascii
810 ;;print-gensym
811 print-length
812 print-level
813 print-string-length)
814 ,@forms))
815
eec82323
LMI
816(defun gnus-prin1 (form)
817 "Use `prin1' on FORM in the current buffer.
23f87bed
MB
818Bind `print-quoted' and `print-readably' to t, and `print-length' and
819`print-level' to nil. See also `gnus-bind-print-variables'."
820 (gnus-bind-print-variables (prin1 form (current-buffer))))
eec82323
LMI
821
822(defun gnus-prin1-to-string (form)
23f87bed
MB
823 "The same as `prin1'.
824Bind `print-quoted' and `print-readably' to t, and `print-length' and
825`print-level' to nil. See also `gnus-bind-print-variables'."
826 (gnus-bind-print-variables (prin1-to-string form)))
827
01c52d31 828(defun gnus-pp (form &optional stream)
23f87bed
MB
829 "Use `pp' on FORM in the current buffer.
830Bind `print-quoted' and `print-readably' to t, and `print-length' and
831`print-level' to nil. See also `gnus-bind-print-variables'."
01c52d31 832 (gnus-bind-print-variables (pp form (or stream (current-buffer)))))
23f87bed
MB
833
834(defun gnus-pp-to-string (form)
835 "The same as `pp-to-string'.
836Bind `print-quoted' and `print-readably' to t, and `print-length' and
837`print-level' to nil. See also `gnus-bind-print-variables'."
838 (gnus-bind-print-variables (pp-to-string form)))
eec82323
LMI
839
840(defun gnus-make-directory (directory)
841 "Make DIRECTORY (and all its parents) if it doesn't exist."
5eee36fa 842 (require 'nnmail)
16409b0b
GM
843 (let ((file-name-coding-system nnmail-pathname-coding-system))
844 (when (and directory
845 (not (file-exists-p directory)))
846 (make-directory directory t)))
eec82323
LMI
847 t)
848
849(defun gnus-write-buffer (file)
850 "Write the current buffer's contents to FILE."
16409b0b 851 (let ((file-name-coding-system nnmail-pathname-coding-system))
01c52d31
MB
852 ;; Make sure the directory exists.
853 (gnus-make-directory (file-name-directory file))
16409b0b
GM
854 ;; Write the buffer.
855 (write-region (point-min) (point-max) file nil 'quietly)))
eec82323 856
eec82323
LMI
857(defun gnus-delete-file (file)
858 "Delete FILE if it exists."
859 (when (file-exists-p file)
860 (delete-file file)))
861
aa0a8561
MB
862(defun gnus-delete-directory (directory)
863 "Delete files in DIRECTORY. Subdirectories remain.
864If there's no subdirectory, delete DIRECTORY as well."
865 (when (file-directory-p directory)
866 (let ((files (directory-files
867 directory t "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*"))
868 file dir)
869 (while files
870 (setq file (pop files))
871 (if (eq t (car (file-attributes file)))
872 ;; `file' is a subdirectory.
873 (setq dir t)
874 ;; `file' is a file or a symlink.
875 (delete-file file)))
876 (unless dir
877 (delete-directory directory)))))
878
996aa8c1
MB
879;; The following two functions are used in gnus-registry.
880;; They were contributed by Andreas Fuchs <asf@void.at>.
881(defun gnus-alist-to-hashtable (alist)
882 "Build a hashtable from the values in ALIST."
883 (let ((ht (make-hash-table
884 :size 4096
885 :test 'equal)))
886 (mapc
887 (lambda (kv-pair)
888 (puthash (car kv-pair) (cdr kv-pair) ht))
889 alist)
890 ht))
891
892(defun gnus-hashtable-to-alist (hash)
893 "Build an alist from the values in HASH."
894 (let ((list nil))
895 (maphash
896 (lambda (key value)
897 (setq list (cons (cons key value) list)))
898 hash)
899 list))
900
eec82323
LMI
901(defun gnus-strip-whitespace (string)
902 "Return STRING stripped of all whitespace."
903 (while (string-match "[\r\n\t ]+" string)
904 (setq string (replace-match "" t t string)))
905 string)
906
5ec7fe1b
GM
907(declare-function gnus-put-text-property "gnus"
908 (start end property value &optional object))
909
16409b0b 910(defsubst gnus-put-text-property-excluding-newlines (beg end prop val)
eec82323
LMI
911 "The same as `put-text-property', but don't put this prop on any newlines in the region."
912 (save-match-data
913 (save-excursion
914 (save-restriction
915 (goto-char beg)
16409b0b 916 (while (re-search-forward gnus-emphasize-whitespace-regexp end 'move)
6748645f 917 (gnus-put-text-property beg (match-beginning 0) prop val)
eec82323 918 (setq beg (point)))
6748645f
LMI
919 (gnus-put-text-property beg (point) prop val)))))
920
5ec7fe1b
GM
921(declare-function gnus-overlay-put "gnus" (overlay prop value))
922(declare-function gnus-make-overlay "gnus"
923 (beg end &optional buffer front-advance rear-advance))
924
23f87bed
MB
925(defsubst gnus-put-overlay-excluding-newlines (beg end prop val)
926 "The same as `put-text-property', but don't put this prop on any newlines in the region."
927 (save-match-data
928 (save-excursion
929 (save-restriction
930 (goto-char beg)
931 (while (re-search-forward gnus-emphasize-whitespace-regexp end 'move)
932 (gnus-overlay-put
933 (gnus-make-overlay beg (match-beginning 0))
934 prop val)
935 (setq beg (point)))
936 (gnus-overlay-put (gnus-make-overlay beg (point)) prop val)))))
937
6748645f
LMI
938(defun gnus-put-text-property-excluding-characters-with-faces (beg end
939 prop val)
940 "The same as `put-text-property', but don't put props on characters with the `gnus-face' property."
941 (let ((b beg))
942 (while (/= b end)
943 (when (get-text-property b 'gnus-face)
944 (setq b (next-single-property-change b 'gnus-face nil end)))
945 (when (/= b end)
23f87bed
MB
946 (inline
947 (gnus-put-text-property
948 b (setq b (next-single-property-change b 'gnus-face nil end))
949 prop val))))))
950
951(defmacro gnus-faces-at (position)
952 "Return a list of faces at POSITION."
953 (if (featurep 'xemacs)
954 `(let ((pos ,position))
955 (mapcar-extents 'extent-face
956 nil (current-buffer) pos pos nil 'face))
957 `(let ((pos ,position))
958 (delq nil (cons (get-text-property pos 'face)
959 (mapcar
960 (lambda (overlay)
961 (overlay-get overlay 'face))
962 (overlays-at pos)))))))
eec82323
LMI
963
964;;; Protected and atomic operations. dmoore@ucsd.edu 21.11.1996
d346bf7e
SM
965;; The primary idea here is to try to protect internal datastructures
966;; from becoming corrupted when the user hits C-g, or if a hook or
967;; similar blows up. Often in Gnus multiple tables/lists need to be
968;; updated at the same time, or information can be lost.
eec82323
LMI
969
970(defvar gnus-atomic-be-safe t
971 "If t, certain operations will be protected from interruption by C-g.")
972
973(defmacro gnus-atomic-progn (&rest forms)
974 "Evaluate FORMS atomically, which means to protect the evaluation
975from being interrupted by the user. An error from the forms themselves
976will return without finishing the operation. Since interrupts from
977the user are disabled, it is recommended that only the most minimal
978operations are performed by FORMS. If you wish to assign many
979complicated values atomically, compute the results into temporary
980variables and then do only the assignment atomically."
981 `(let ((inhibit-quit gnus-atomic-be-safe))
982 ,@forms))
983
984(put 'gnus-atomic-progn 'lisp-indent-function 0)
985
986(defmacro gnus-atomic-progn-assign (protect &rest forms)
d346bf7e 987 "Evaluate FORMS, but ensure that the variables listed in PROTECT
eec82323
LMI
988are not changed if anything in FORMS signals an error or otherwise
989non-locally exits. The variables listed in PROTECT are updated atomically.
990It is safe to use gnus-atomic-progn-assign with long computations.
991
992Note that if any of the symbols in PROTECT were unbound, they will be
8f688cb0 993set to nil on a successful assignment. In case of an error or other
eec82323
LMI
994non-local exit, it will still be unbound."
995 (let* ((temp-sym-map (mapcar (lambda (x) (list (make-symbol
996 (concat (symbol-name x)
997 "-tmp"))
998 x))
999 protect))
1000 (sym-temp-map (mapcar (lambda (x) (list (cadr x) (car x)))
1001 temp-sym-map))
1002 (temp-sym-let (mapcar (lambda (x) (list (car x)
1003 `(and (boundp ',(cadr x))
1004 ,(cadr x))))
1005 temp-sym-map))
1006 (sym-temp-let sym-temp-map)
1007 (temp-sym-assign (apply 'append temp-sym-map))
1008 (sym-temp-assign (apply 'append sym-temp-map))
1009 (result (make-symbol "result-tmp")))
1010 `(let (,@temp-sym-let
1011 ,result)
1012 (let ,sym-temp-let
1013 (setq ,result (progn ,@forms))
1014 (setq ,@temp-sym-assign))
1015 (let ((inhibit-quit gnus-atomic-be-safe))
1016 (setq ,@sym-temp-assign))
1017 ,result)))
1018
1019(put 'gnus-atomic-progn-assign 'lisp-indent-function 1)
1020;(put 'gnus-atomic-progn-assign 'edebug-form-spec '(sexp body))
1021
1022(defmacro gnus-atomic-setq (&rest pairs)
1023 "Similar to setq, except that the real symbols are only assigned when
1024there are no errors. And when the real symbols are assigned, they are
1025done so atomically. If other variables might be changed via side-effect,
1026see gnus-atomic-progn-assign. It is safe to use gnus-atomic-setq
1027with potentially long computations."
1028 (let ((tpairs pairs)
1029 syms)
1030 (while tpairs
1031 (push (car tpairs) syms)
1032 (setq tpairs (cddr tpairs)))
1033 `(gnus-atomic-progn-assign ,syms
1034 (setq ,@pairs))))
1035
1036;(put 'gnus-atomic-setq 'edebug-form-spec '(body))
1037
1038
1039;;; Functions for saving to babyl/mail files.
1040
23f87bed
MB
1041(eval-when-compile
1042 (condition-case nil
1043 (progn
1044 (require 'rmail)
1045 (autoload 'rmail-update-summary "rmailsum"))
1046 (error
1047 (define-compiler-macro rmail-select-summary (&rest body)
1048 ;; Rmail of the XEmacs version is supplied by the package, and
1049 ;; requires tm and apel packages. However, there may be those
1050 ;; who haven't installed those packages. This macro helps such
1051 ;; people even if they install those packages later.
1052 `(eval '(rmail-select-summary ,@body)))
1053 ;; If there's rmail but there's no tm (or there's apel of the
1054 ;; mainstream, not the XEmacs version), loading rmail of the XEmacs
1055 ;; version fails halfway, however it provides the rmail-select-summary
1056 ;; macro which uses the following functions:
1057 (autoload 'rmail-summary-displayed "rmail")
9efa445f
DN
1058 (autoload 'rmail-maybe-display-summary "rmail"))))
1059
9efa445f 1060(defvar mm-text-coding-system)
23f87bed 1061
88bfa2e4
GM
1062(declare-function mm-append-to-file "mm-util"
1063 (start end filename &optional codesys inhibit))
1064
eec82323 1065(defun gnus-output-to-rmail (filename &optional ask)
e38658c4
GM
1066 "Append the current article to an Rmail file named FILENAME.
1067In Emacs 22 this writes Babyl format; in Emacs 23 it writes mbox unless
1068FILENAME exists and is Babyl format."
eec82323 1069 (require 'rmail)
23f87bed 1070 (require 'mm-util)
e38658c4 1071 ;; Some of this codes is borrowed from rmailout.el.
eec82323 1072 (setq filename (expand-file-name filename))
e38658c4
GM
1073 ;; FIXME should we really be messing with this defcustom?
1074 ;; It is not needed for the operation of this function.
1075 (if (boundp 'rmail-default-rmail-file)
1076 (setq rmail-default-rmail-file filename) ; 22
1077 (setq rmail-default-file filename)) ; 23
eec82323 1078 (let ((artbuf (current-buffer))
e38658c4
GM
1079 (tmpbuf (get-buffer-create " *Gnus-output*"))
1080 ;; Babyl rmail.el defines this, mbox does not.
1081 (babyl (fboundp 'rmail-insert-rmail-file-header)))
eec82323 1082 (save-excursion
e38658c4
GM
1083 ;; Note that we ignore the possibility of visiting a Babyl
1084 ;; format buffer in Emacs 23, since Rmail no longer supports that.
1085 (or (get-file-buffer filename)
1086 (progn
1087 ;; In case someone wants to write to a Babyl file from Emacs 23.
1088 (when (file-exists-p filename)
1089 (setq babyl (mail-file-babyl-p filename))
1090 t))
eec82323
LMI
1091 (if (or (not ask)
1092 (gnus-yes-or-no-p
1093 (concat "\"" filename "\" does not exist, create it? ")))
1094 (let ((file-buffer (create-file-buffer filename)))
1095 (save-excursion
1096 (set-buffer file-buffer)
e38658c4
GM
1097 (if (fboundp 'rmail-insert-rmail-file-header)
1098 (rmail-insert-rmail-file-header))
16409b0b
GM
1099 (let ((require-final-newline nil)
1100 (coding-system-for-write mm-text-coding-system))
eec82323
LMI
1101 (gnus-write-buffer filename)))
1102 (kill-buffer file-buffer))
1103 (error "Output file does not exist")))
1104 (set-buffer tmpbuf)
1105 (erase-buffer)
1106 (insert-buffer-substring artbuf)
e38658c4
GM
1107 (if babyl
1108 (gnus-convert-article-to-rmail)
1109 ;; Non-Babyl case copied from gnus-output-to-mail.
1110 (goto-char (point-min))
1111 (if (looking-at "From ")
1112 (forward-line 1)
1113 (insert "From nobody " (current-time-string) "\n"))
1114 (let (case-fold-search)
1115 (while (re-search-forward "^From " nil t)
1116 (beginning-of-line)
1117 (insert ">"))))
eec82323
LMI
1118 ;; Decide whether to append to a file or to an Emacs buffer.
1119 (let ((outbuf (get-file-buffer filename)))
1120 (if (not outbuf)
e38658c4
GM
1121 (progn
1122 (unless babyl ; from gnus-output-to-mail
1123 (let ((buffer-read-only nil))
1124 (goto-char (point-max))
1125 (forward-char -2)
1126 (unless (looking-at "\n\n")
1127 (goto-char (point-max))
1128 (unless (bolp)
1129 (insert "\n"))
1130 (insert "\n"))))
1131 (let ((file-name-coding-system nnmail-pathname-coding-system))
1132 (mm-append-to-file (point-min) (point-max) filename)))
eec82323
LMI
1133 ;; File has been visited, in buffer OUTBUF.
1134 (set-buffer outbuf)
1135 (let ((buffer-read-only nil)
1136 (msg (and (boundp 'rmail-current-message)
1137 (symbol-value 'rmail-current-message))))
1138 ;; If MSG is non-nil, buffer is in RMAIL mode.
e38658c4 1139 ;; Compare this with rmail-output-to-rmail-buffer in Emacs 23.
eec82323 1140 (when msg
e38658c4
GM
1141 (unless babyl
1142 (rmail-swap-buffers-maybe)
1143 (rmail-maybe-set-message-counters))
1144 (widen)
1145 (narrow-to-region (point-max) (point-max)))
eec82323
LMI
1146 (insert-buffer-substring tmpbuf)
1147 (when msg
e38658c4
GM
1148 (when babyl
1149 (goto-char (point-min))
1150 (widen)
1151 (search-backward "\n\^_")
1152 (narrow-to-region (point) (point-max)))
23f87bed
MB
1153 (rmail-count-new-messages t)
1154 (when (rmail-summary-exists)
6748645f
LMI
1155 (rmail-select-summary
1156 (rmail-update-summary)))
6748645f
LMI
1157 (rmail-show-message msg))
1158 (save-buffer)))))
eec82323
LMI
1159 (kill-buffer tmpbuf)))
1160
1161(defun gnus-output-to-mail (filename &optional ask)
1162 "Append the current article to a mail file named FILENAME."
1163 (setq filename (expand-file-name filename))
1164 (let ((artbuf (current-buffer))
1165 (tmpbuf (get-buffer-create " *Gnus-output*")))
1166 (save-excursion
1167 ;; Create the file, if it doesn't exist.
1168 (when (and (not (get-file-buffer filename))
1169 (not (file-exists-p filename)))
1170 (if (or (not ask)
1171 (gnus-y-or-n-p
1172 (concat "\"" filename "\" does not exist, create it? ")))
1173 (let ((file-buffer (create-file-buffer filename)))
1174 (save-excursion
1175 (set-buffer file-buffer)
16409b0b
GM
1176 (let ((require-final-newline nil)
1177 (coding-system-for-write mm-text-coding-system))
eec82323
LMI
1178 (gnus-write-buffer filename)))
1179 (kill-buffer file-buffer))
1180 (error "Output file does not exist")))
1181 (set-buffer tmpbuf)
1182 (erase-buffer)
1183 (insert-buffer-substring artbuf)
1184 (goto-char (point-min))
1185 (if (looking-at "From ")
1186 (forward-line 1)
1187 (insert "From nobody " (current-time-string) "\n"))
1188 (let (case-fold-search)
1189 (while (re-search-forward "^From " nil t)
1190 (beginning-of-line)
1191 (insert ">")))
1192 ;; Decide whether to append to a file or to an Emacs buffer.
1193 (let ((outbuf (get-file-buffer filename)))
1194 (if (not outbuf)
1195 (let ((buffer-read-only nil))
1196 (save-excursion
1197 (goto-char (point-max))
1198 (forward-char -2)
1199 (unless (looking-at "\n\n")
1200 (goto-char (point-max))
1201 (unless (bolp)
1202 (insert "\n"))
1203 (insert "\n"))
1204 (goto-char (point-max))
47e77e9f
SZ
1205 (let ((file-name-coding-system nnmail-pathname-coding-system))
1206 (mm-append-to-file (point-min) (point-max) filename))))
eec82323
LMI
1207 ;; File has been visited, in buffer OUTBUF.
1208 (set-buffer outbuf)
1209 (let ((buffer-read-only nil))
1210 (goto-char (point-max))
1211 (unless (eobp)
1212 (insert "\n"))
1213 (insert "\n")
1214 (insert-buffer-substring tmpbuf)))))
1215 (kill-buffer tmpbuf)))
1216
1217(defun gnus-convert-article-to-rmail ()
1218 "Convert article in current buffer to Rmail message format."
1219 (let ((buffer-read-only nil))
1220 ;; Convert article directly into Babyl format.
1221 (goto-char (point-min))
1222 (insert "\^L\n0, unseen,,\n*** EOOH ***\n")
1223 (while (search-forward "\n\^_" nil t) ;single char
1224 (replace-match "\n^_" t t)) ;2 chars: "^" and "_"
1225 (goto-char (point-max))
1226 (insert "\^_")))
1227
6748645f 1228(defun gnus-map-function (funs arg)
23f87bed 1229 "Apply the result of the first function in FUNS to the second, and so on.
6748645f 1230ARG is passed to the first function."
23f87bed
MB
1231 (while funs
1232 (setq arg (funcall (pop funs) arg)))
1233 arg)
6748645f
LMI
1234
1235(defun gnus-run-hooks (&rest funcs)
23f87bed
MB
1236 "Does the same as `run-hooks', but saves the current buffer."
1237 (save-current-buffer
1238 (apply 'run-hooks funcs)))
6748645f 1239
b4e8a25b 1240(defun gnus-run-mode-hooks (&rest funcs)
cfcd5c91
MB
1241 "Run `run-mode-hooks' if it is available, otherwise `run-hooks'.
1242This function saves the current buffer."
b4e8a25b 1243 (if (fboundp 'run-mode-hooks)
cfcd5c91
MB
1244 (save-current-buffer (apply 'run-mode-hooks funcs))
1245 (save-current-buffer (apply 'run-hooks funcs))))
b4e8a25b 1246
6748645f
LMI
1247;;; Various
1248
16409b0b 1249(defvar gnus-group-buffer) ; Compiler directive
6748645f
LMI
1250(defun gnus-alive-p ()
1251 "Say whether Gnus is running or not."
1252 (and (boundp 'gnus-group-buffer)
1253 (get-buffer gnus-group-buffer)
1254 (save-excursion
1255 (set-buffer gnus-group-buffer)
1256 (eq major-mode 'gnus-group-mode))))
1257
23f87bed
MB
1258(defun gnus-remove-if (predicate list)
1259 "Return a copy of LIST with all items satisfying PREDICATE removed."
6748645f
LMI
1260 (let (out)
1261 (while list
1262 (unless (funcall predicate (car list))
1263 (push (car list) out))
23f87bed 1264 (setq list (cdr list)))
6748645f
LMI
1265 (nreverse out)))
1266
23f87bed
MB
1267(if (fboundp 'assq-delete-all)
1268 (defalias 'gnus-delete-alist 'assq-delete-all)
1269 (defun gnus-delete-alist (key alist)
1270 "Delete from ALIST all elements whose car is KEY.
1271Return the modified alist."
1272 (let (entry)
1273 (while (setq entry (assq key alist))
1274 (setq alist (delq entry alist)))
1275 alist)))
6748645f 1276
16409b0b 1277(defmacro gnus-pull (key alist &optional assoc-p)
6748645f
LMI
1278 "Modify ALIST to be without KEY."
1279 (unless (symbolp alist)
1280 (error "Not a symbol: %s" alist))
16409b0b
GM
1281 (let ((fun (if assoc-p 'assoc 'assq)))
1282 `(setq ,alist (delq (,fun ,key ,alist) ,alist))))
6748645f
LMI
1283
1284(defun gnus-globalify-regexp (re)
e7f767c2 1285 "Return a regexp that matches a whole line, if RE matches a part of it."
6748645f
LMI
1286 (concat (unless (string-match "^\\^" re) "^.*")
1287 re
1288 (unless (string-match "\\$$" re) ".*$")))
1289
16409b0b
GM
1290(defun gnus-set-window-start (&optional point)
1291 "Set the window start to POINT, or (point) if nil."
23f87bed 1292 (let ((win (gnus-get-buffer-window (current-buffer) t)))
16409b0b
GM
1293 (when win
1294 (set-window-start win (or point (point))))))
1295
1296(defun gnus-annotation-in-region-p (b e)
1297 (if (= b e)
1298 (eq (cadr (memq 'gnus-undeletable (text-properties-at b))) t)
1299 (text-property-any b e 'gnus-undeletable t)))
1300
1301(defun gnus-or (&rest elems)
1302 "Return non-nil if any of the elements are non-nil."
1303 (catch 'found
1304 (while elems
1305 (when (pop elems)
1306 (throw 'found t)))))
1307
1308(defun gnus-and (&rest elems)
1309 "Return non-nil if all of the elements are non-nil."
1310 (catch 'found
1311 (while elems
1312 (unless (pop elems)
1313 (throw 'found nil)))
1314 t))
1315
5ec7fe1b
GM
1316;; gnus.el requires mm-util.
1317(declare-function mm-disable-multibyte "mm-util")
1318
16409b0b 1319(defun gnus-write-active-file (file hashtb &optional full-names)
01c52d31 1320 ;; `coding-system-for-write' should be `raw-text' or equivalent.
16409b0b
GM
1321 (let ((coding-system-for-write nnmail-active-file-coding-system))
1322 (with-temp-file file
01c52d31
MB
1323 ;; The buffer should be in the unibyte mode because group names
1324 ;; are ASCII text or encoded non-ASCII text (i.e., unibyte).
1325 (mm-disable-multibyte)
16409b0b
GM
1326 (mapatoms
1327 (lambda (sym)
1328 (when (and sym
1329 (boundp sym)
1330 (symbol-value sym))
1331 (insert (format "%S %d %d y\n"
1332 (if full-names
1333 sym
1334 (intern (gnus-group-real-name (symbol-name sym))))
1335 (or (cdr (symbol-value sym))
1336 (car (symbol-value sym)))
1337 (car (symbol-value sym))))))
1338 hashtb)
1339 (goto-char (point-max))
1340 (while (search-backward "\\." nil t)
1341 (delete-char 1)))))
1342
23f87bed
MB
1343;; Fixme: Why not use `with-output-to-temp-buffer'?
1344(defmacro gnus-with-output-to-file (file &rest body)
1345 (let ((buffer (make-symbol "output-buffer"))
1346 (size (make-symbol "output-buffer-size"))
1347 (leng (make-symbol "output-buffer-length"))
1348 (append (make-symbol "output-buffer-append")))
1349 `(let* ((,size 131072)
1350 (,buffer (make-string ,size 0))
1351 (,leng 0)
1352 (,append nil)
1353 (standard-output
1354 (lambda (c)
1355 (aset ,buffer ,leng c)
bf247b6e 1356
23f87bed
MB
1357 (if (= ,size (setq ,leng (1+ ,leng)))
1358 (progn (write-region ,buffer nil ,file ,append 'no-msg)
1359 (setq ,leng 0
1360 ,append t))))))
1361 ,@body
1362 (when (> ,leng 0)
1363 (let ((coding-system-for-write 'no-conversion))
1364 (write-region (substring ,buffer 0 ,leng) nil ,file
1365 ,append 'no-msg))))))
1366
1367(put 'gnus-with-output-to-file 'lisp-indent-function 1)
1368(put 'gnus-with-output-to-file 'edebug-form-spec '(form body))
1369
1370(if (fboundp 'union)
1371 (defalias 'gnus-union 'union)
1372 (defun gnus-union (l1 l2)
1373 "Set union of lists L1 and L2."
1374 (cond ((null l1) l2)
1375 ((null l2) l1)
1376 ((equal l1 l2) l1)
1377 (t
1378 (or (>= (length l1) (length l2))
1379 (setq l1 (prog1 l2 (setq l2 l1))))
1380 (while l2
1381 (or (member (car l2) l1)
1382 (push (car l2) l1))
1383 (pop l2))
1384 l1))))
1385
5ec7fe1b
GM
1386(declare-function gnus-add-text-properties "gnus"
1387 (start end properties &optional object))
1388
520aa572
SZ
1389(defun gnus-add-text-properties-when
1390 (property value start end properties &optional object)
1391 "Like `gnus-add-text-properties', only applied on where PROPERTY is VALUE."
1392 (let (point)
3d8c35d3 1393 (while (and start
23f87bed 1394 (< start end) ;; XEmacs will loop for every when start=end.
520aa572
SZ
1395 (setq point (text-property-not-all start end property value)))
1396 (gnus-add-text-properties start point properties object)
1397 (setq start (text-property-any point end property value)))
1398 (if start
1399 (gnus-add-text-properties start end properties object))))
1400
1401(defun gnus-remove-text-properties-when
1402 (property value start end properties &optional object)
1403 "Like `remove-text-properties', only applied on where PROPERTY is VALUE."
1404 (let (point)
3d8c35d3 1405 (while (and start
23f87bed 1406 (< start end)
520aa572
SZ
1407 (setq point (text-property-not-all start end property value)))
1408 (remove-text-properties start point properties object)
1409 (setq start (text-property-any point end property value)))
1410 (if start
4481aa98
SZ
1411 (remove-text-properties start end properties object))
1412 t))
520aa572 1413
01c52d31
MB
1414(defun gnus-string-remove-all-properties (string)
1415 (condition-case ()
1416 (let ((s string))
1417 (set-text-properties 0 (length string) nil string)
1418 s)
1419 (error string)))
1420
23f87bed
MB
1421;; This might use `compare-strings' to reduce consing in the
1422;; case-insensitive case, but it has to cope with null args.
1423;; (`string-equal' uses symbol print names.)
1424(defun gnus-string-equal (x y)
1425 "Like `string-equal', except it compares case-insensitively."
1426 (and (= (length x) (length y))
1427 (or (string-equal x y)
1428 (string-equal (downcase x) (downcase y)))))
1429
1430(defcustom gnus-use-byte-compile t
1431 "If non-nil, byte-compile crucial run-time code.
1432Setting it to nil has no effect after the first time `gnus-byte-compile'
1433is run."
1434 :type 'boolean
bf247b6e 1435 :version "22.1"
23f87bed
MB
1436 :group 'gnus-various)
1437
1438(defun gnus-byte-compile (form)
1439 "Byte-compile FORM if `gnus-use-byte-compile' is non-nil."
1440 (if gnus-use-byte-compile
1441 (progn
1442 (condition-case nil
1443 ;; Work around a bug in XEmacs 21.4
1444 (require 'byte-optimize)
1445 (error))
1446 (require 'bytecomp)
1447 (defalias 'gnus-byte-compile
1448 (lambda (form)
1449 (let ((byte-compile-warnings '(unresolved callargs redefine)))
1450 (byte-compile form))))
1451 (gnus-byte-compile form))
1452 form))
1453
1454(defun gnus-remassoc (key alist)
1455 "Delete by side effect any elements of LIST whose car is `equal' to KEY.
1456The modified LIST is returned. If the first member
1457of LIST has a car that is `equal' to KEY, there is no way to remove it
54506618 1458by side effect; therefore, write `(setq foo (gnus-remassoc key foo))' to be
23f87bed
MB
1459sure of changing the value of `foo'."
1460 (when alist
1461 (if (equal key (caar alist))
1462 (cdr alist)
1463 (setcdr alist (gnus-remassoc key (cdr alist)))
1464 alist)))
1465
1466(defun gnus-update-alist-soft (key value alist)
1467 (if value
1468 (cons (cons key value) (gnus-remassoc key alist))
1469 (gnus-remassoc key alist)))
1470
1471(defun gnus-create-info-command (node)
1472 "Create a command that will go to info NODE."
1473 `(lambda ()
1474 (interactive)
1475 ,(concat "Enter the info system at node " node)
1476 (Info-goto-node ,node)
1477 (setq gnus-info-buffer (current-buffer))
1478 (gnus-configure-windows 'info)))
1479
1480(defun gnus-not-ignore (&rest args)
1481 t)
1482
47b63dfa
SZ
1483(defvar gnus-directory-sep-char-regexp "/"
1484 "The regexp of directory separator character.
1485If you find some problem with the directory separator character, try
1486\"[/\\\\\]\" for some systems.")
1487
23f87bed
MB
1488(defun gnus-url-unhex (x)
1489 (if (> x ?9)
1490 (if (>= x ?a)
1491 (+ 10 (- x ?a))
1492 (+ 10 (- x ?A)))
1493 (- x ?0)))
1494
1495;; Fixme: Do it like QP.
1496(defun gnus-url-unhex-string (str &optional allow-newlines)
1497 "Remove %XX, embedded spaces, etc in a url.
1498If optional second argument ALLOW-NEWLINES is non-nil, then allow the
1499decoding of carriage returns and line feeds in the string, which is normally
1500forbidden in URL encoding."
1501 (let ((tmp "")
1502 (case-fold-search t))
1503 (while (string-match "%[0-9a-f][0-9a-f]" str)
1504 (let* ((start (match-beginning 0))
1505 (ch1 (gnus-url-unhex (elt str (+ start 1))))
1506 (code (+ (* 16 ch1)
1507 (gnus-url-unhex (elt str (+ start 2))))))
1508 (setq tmp (concat
1509 tmp (substring str 0 start)
1510 (cond
1511 (allow-newlines
1512 (char-to-string code))
1513 ((or (= code ?\n) (= code ?\r))
1514 " ")
1515 (t (char-to-string code))))
1516 str (substring str (match-end 0)))))
1517 (setq tmp (concat tmp str))
1518 tmp))
1519
1520(defun gnus-make-predicate (spec)
1521 "Transform SPEC into a function that can be called.
1522SPEC is a predicate specifier that contains stuff like `or', `and',
1523`not', lists and functions. The functions all take one parameter."
1524 `(lambda (elem) ,(gnus-make-predicate-1 spec)))
1525
1526(defun gnus-make-predicate-1 (spec)
1527 (cond
1528 ((symbolp spec)
1529 `(,spec elem))
1530 ((listp spec)
1531 (if (memq (car spec) '(or and not))
1532 `(,(car spec) ,@(mapcar 'gnus-make-predicate-1 (cdr spec)))
1533 (error "Invalid predicate specifier: %s" spec)))))
1534
23f87bed
MB
1535(defun gnus-completing-read (prompt table &optional predicate require-match
1536 history)
1537 (when (and history
1538 (not (boundp history)))
1539 (set history nil))
01c52d31 1540 (completing-read
23f87bed
MB
1541 (if (symbol-value history)
1542 (concat prompt " (" (car (symbol-value history)) "): ")
1543 (concat prompt ": "))
1544 table
1545 predicate
1546 require-match
1547 nil
1548 history
1549 (car (symbol-value history))))
1550
1551(defun gnus-graphic-display-p ()
1552 (or (and (fboundp 'display-graphic-p)
1553 (display-graphic-p))
1554 ;;;!!!This is bogus. Fixme!
1555 (and (featurep 'xemacs)
1556 t)))
1557
1558(put 'gnus-parse-without-error 'lisp-indent-function 0)
1559(put 'gnus-parse-without-error 'edebug-form-spec '(body))
1560
1561(defmacro gnus-parse-without-error (&rest body)
1562 "Allow continuing onto the next line even if an error occurs."
1563 `(while (not (eobp))
1564 (condition-case ()
1565 (progn
1566 ,@body
1567 (goto-char (point-max)))
1568 (error
1569 (gnus-error 4 "Invalid data on line %d"
1570 (count-lines (point-min) (point)))
1571 (forward-line 1)))))
1572
1573(defun gnus-cache-file-contents (file variable function)
1574 "Cache the contents of FILE in VARIABLE. The contents come from FUNCTION."
1575 (let ((time (nth 5 (file-attributes file)))
1576 contents value)
1577 (if (or (null (setq value (symbol-value variable)))
1578 (not (equal (car value) file))
1579 (not (equal (nth 1 value) time)))
1580 (progn
1581 (setq contents (funcall function file))
1582 (set variable (list file time contents))
1583 contents)
1584 (nth 2 value))))
1585
1586(defun gnus-multiple-choice (prompt choice &optional idx)
1587 "Ask user a multiple choice question.
1588CHOICE is a list of the choice char and help message at IDX."
1589 (let (tchar buf)
1590 (save-window-excursion
1591 (save-excursion
1592 (while (not tchar)
1593 (message "%s (%s): "
1594 prompt
1595 (concat
1596 (mapconcat (lambda (s) (char-to-string (car s)))
1597 choice ", ") ", ?"))
1598 (setq tchar (read-char))
1599 (when (not (assq tchar choice))
1600 (setq tchar nil)
1601 (setq buf (get-buffer-create "*Gnus Help*"))
1602 (pop-to-buffer buf)
1603 (fundamental-mode) ; for Emacs 20.4+
1604 (buffer-disable-undo)
1605 (erase-buffer)
1606 (insert prompt ":\n\n")
1607 (let ((max -1)
1608 (list choice)
1609 (alist choice)
1610 (idx (or idx 1))
1611 (i 0)
1612 n width pad format)
1613 ;; find the longest string to display
1614 (while list
1615 (setq n (length (nth idx (car list))))
1616 (unless (> max n)
1617 (setq max n))
1618 (setq list (cdr list)))
1619 (setq max (+ max 4)) ; %c, `:', SPACE, a SPACE at end
1620 (setq n (/ (1- (window-width)) max)) ; items per line
1621 (setq width (/ (1- (window-width)) n)) ; width of each item
1622 ;; insert `n' items, each in a field of width `width'
1623 (while alist
1624 (if (< i n)
1625 ()
1626 (setq i 0)
1627 (delete-char -1) ; the `\n' takes a char
1628 (insert "\n"))
1629 (setq pad (- width 3))
1630 (setq format (concat "%c: %-" (int-to-string pad) "s"))
1631 (insert (format format (caar alist) (nth idx (car alist))))
1632 (setq alist (cdr alist))
1633 (setq i (1+ i))))))))
1634 (if (buffer-live-p buf)
1635 (kill-buffer buf))
1636 tchar))
1637
dbb6c370 1638(declare-function x-focus-frame "xfns.c" (frame))
88bfa2e4
GM
1639(declare-function w32-focus-frame "../term/w32-win" (frame))
1640
23f87bed
MB
1641(defun gnus-select-frame-set-input-focus (frame)
1642 "Select FRAME, raise it, and set input focus, if possible."
1643 (cond ((featurep 'xemacs)
f67d6742
MB
1644 (if (fboundp 'select-frame-set-input-focus)
1645 (select-frame-set-input-focus frame)
1646 (raise-frame frame)
1647 (select-frame frame)
1648 (focus-frame frame)))
1649 ;; `select-frame-set-input-focus' defined in Emacs 21 will not
1650 ;; set the input focus.
1651 ((>= emacs-major-version 22)
1652 (select-frame-set-input-focus frame))
23f87bed
MB
1653 (t
1654 (raise-frame frame)
1655 (select-frame frame)
d221466a 1656 (cond ((memq window-system '(x ns mac))
23f87bed
MB
1657 (x-focus-frame frame))
1658 ((eq window-system 'w32)
d221466a 1659 (w32-focus-frame frame)))
23f87bed
MB
1660 (when focus-follows-mouse
1661 (set-mouse-position frame (1- (frame-width frame)) 0)))))
1662
1663(defun gnus-frame-or-window-display-name (object)
1664 "Given a frame or window, return the associated display name.
1665Return nil otherwise."
1666 (if (featurep 'xemacs)
1667 (device-connection (dfw-device object))
1668 (if (or (framep object)
1669 (and (windowp object)
1670 (setq object (window-frame object))))
1671 (let ((display (frame-parameter object 'display)))
1672 (if (and (stringp display)
1673 ;; Exclude invalid display names.
1674 (string-match "\\`[^:]*:[0-9]+\\(\\.[0-9]+\\)?\\'"
1675 display))
1676 display)))))
1677
9efa445f 1678(defvar tool-bar-mode)
531bedc3 1679
85fd8002
RS
1680(defun gnus-tool-bar-update (&rest ignore)
1681 "Update the tool bar."
1682 (when (and (boundp 'tool-bar-mode)
1683 tool-bar-mode)
1684 (let* ((args nil)
1685 (func (cond ((featurep 'xemacs)
1686 'ignore)
1687 ((fboundp 'tool-bar-update)
1688 'tool-bar-update)
1689 ((fboundp 'force-window-update)
1690 'force-window-update)
1691 ((fboundp 'redraw-frame)
1692 (setq args (list (selected-frame)))
1693 'redraw-frame)
1694 (t 'ignore))))
1695 (apply func args))))
1696
23f87bed
MB
1697;; Fixme: This has only one use (in gnus-agent), which isn't worthwhile.
1698(defmacro gnus-mapcar (function seq1 &rest seqs2_n)
1699 "Apply FUNCTION to each element of the sequences, and make a list of the results.
1700If there are several sequences, FUNCTION is called with that many arguments,
1701and mapping stops as soon as the shortest sequence runs out. With just one
1702sequence, this is like `mapcar'. With several, it is like the Common Lisp
1703`mapcar' function extended to arbitrary sequence types."
1704
1705 (if seqs2_n
1706 (let* ((seqs (cons seq1 seqs2_n))
1707 (cnt 0)
1708 (heads (mapcar (lambda (seq)
1709 (make-symbol (concat "head"
1710 (int-to-string
1711 (setq cnt (1+ cnt))))))
1712 seqs))
1713 (result (make-symbol "result"))
1714 (result-tail (make-symbol "result-tail")))
1715 `(let* ,(let* ((bindings (cons nil nil))
1716 (heads heads))
1717 (nconc bindings (list (list result '(cons nil nil))))
1718 (nconc bindings (list (list result-tail result)))
1719 (while heads
1720 (nconc bindings (list (list (pop heads) (pop seqs)))))
1721 (cdr bindings))
1722 (while (and ,@heads)
1723 (setcdr ,result-tail (cons (funcall ,function
1724 ,@(mapcar (lambda (h) (list 'car h))
1725 heads))
1726 nil))
1727 (setq ,result-tail (cdr ,result-tail)
1728 ,@(apply 'nconc (mapcar (lambda (h) (list h (list 'cdr h))) heads))))
1729 (cdr ,result)))
1730 `(mapcar ,function ,seq1)))
1731
1732(if (fboundp 'merge)
1733 (defalias 'gnus-merge 'merge)
1734 ;; Adapted from cl-seq.el
1735 (defun gnus-merge (type list1 list2 pred)
1736 "Destructively merge lists LIST1 and LIST2 to produce a new list.
1737Argument TYPE is for compatibility and ignored.
1738Ordering of the elements is preserved according to PRED, a `less-than'
1739predicate on the elements."
1740 (let ((res nil))
1741 (while (and list1 list2)
1742 (if (funcall pred (car list2) (car list1))
1743 (push (pop list2) res)
1744 (push (pop list1) res)))
1745 (nconc (nreverse res) list1 list2))))
1746
9efa445f
DN
1747(defvar xemacs-codename)
1748(defvar sxemacs-codename)
1749(defvar emacs-program-version)
23f87bed
MB
1750
1751(defun gnus-emacs-version ()
1752 "Stringified Emacs version."
4a2358e9
MB
1753 (let* ((lst (if (listp gnus-user-agent)
1754 gnus-user-agent
1755 '(gnus emacs type)))
1756 (system-v (cond ((memq 'config lst)
1757 system-configuration)
1758 ((memq 'type lst)
1759 (symbol-name system-type))
1760 (t nil)))
1761 codename emacsname)
1762 (cond ((featurep 'sxemacs)
1763 (setq emacsname "SXEmacs"
1764 codename sxemacs-codename))
1765 ((featurep 'xemacs)
1766 (setq emacsname "XEmacs"
1767 codename xemacs-codename))
1768 (t
1769 (setq emacsname "Emacs")))
23f87bed 1770 (cond
4a2358e9 1771 ((not (memq 'emacs lst))
23f87bed
MB
1772 nil)
1773 ((string-match "^\\(\\([.0-9]+\\)*\\)\\.[0-9]+$" emacs-version)
4a2358e9 1774 ;; Emacs:
23f87bed
MB
1775 (concat "Emacs/" (match-string 1 emacs-version)
1776 (if system-v
1777 (concat " (" system-v ")")
1778 "")))
4a2358e9
MB
1779 ((or (featurep 'sxemacs) (featurep 'xemacs))
1780 ;; XEmacs or SXEmacs:
1781 (concat emacsname "/" emacs-program-version
01c52d31
MB
1782 (let (plst)
1783 (when (memq 'codename lst)
1784 (push codename plst))
1785 (when system-v
1786 (push system-v plst))
1787 (unless (featurep 'mule)
1788 (push "no MULE" plst))
1789 (when (> (length plst) 0)
1790 (concat
1791 " (" (mapconcat 'identity (reverse plst) ", ") ")")))))
23f87bed
MB
1792 (t emacs-version))))
1793
54506618
MB
1794(defun gnus-rename-file (old-path new-path &optional trim)
1795 "Rename OLD-PATH as NEW-PATH. If TRIM, recursively delete
1796empty directories from OLD-PATH."
1797 (when (file-exists-p old-path)
1798 (let* ((old-dir (file-name-directory old-path))
1799 (old-name (file-name-nondirectory old-path))
1800 (new-dir (file-name-directory new-path))
1801 (new-name (file-name-nondirectory new-path))
1802 temp)
1803 (gnus-make-directory new-dir)
1804 (rename-file old-path new-path t)
1805 (when trim
1806 (while (progn (setq temp (directory-files old-dir))
1807 (while (member (car temp) '("." ".."))
1808 (setq temp (cdr temp)))
1809 (= (length temp) 0))
1810 (delete-directory old-dir)
bf247b6e
KS
1811 (setq old-dir (file-name-as-directory
1812 (file-truename
54506618
MB
1813 (concat old-dir "..")))))))))
1814
01c52d31
MB
1815(defun gnus-set-file-modes (filename mode)
1816 "Wrapper for set-file-modes."
1817 (ignore-errors
1818 (set-file-modes filename mode)))
1819
4a43ee9b
MB
1820(if (fboundp 'set-process-query-on-exit-flag)
1821 (defalias 'gnus-set-process-query-on-exit-flag
1822 'set-process-query-on-exit-flag)
1823 (defalias 'gnus-set-process-query-on-exit-flag
1824 'process-kill-without-query))
54506618 1825
996aa8c1
MB
1826(if (fboundp 'with-local-quit)
1827 (defalias 'gnus-with-local-quit 'with-local-quit)
1828 (defmacro gnus-with-local-quit (&rest body)
1829 "Execute BODY, allowing quits to terminate BODY but not escape further.
1830When a quit terminates BODY, `gnus-with-local-quit' returns nil but
1831requests another quit. That quit will be processed as soon as quitting
1832is allowed once again. (Immediately, if `inhibit-quit' is nil.)"
1833 ;;(declare (debug t) (indent 0))
1834 `(condition-case nil
1835 (let ((inhibit-quit nil))
1836 ,@body)
1837 (quit (setq quit-flag t)
1838 ;; This call is to give a chance to handle quit-flag
1839 ;; in case inhibit-quit is nil.
1840 ;; Without this, it will not be handled until the next function
1841 ;; call, and that might allow it to exit thru a condition-case
1842 ;; that intends to handle the quit signal next time.
1843 (eval '(ignore nil))))))
1844
d346bf7e
SM
1845(defalias 'gnus-read-shell-command
1846 (if (fboundp 'read-shell-command) 'read-shell-command 'read-string))
1847
2b968687
MB
1848(defmacro gnus-put-display-table (range value display-table)
1849 "Set the value for char RANGE to VALUE in DISPLAY-TABLE. "
1850 (if (featurep 'xemacs)
1851 (progn
1852 `(if (fboundp 'put-display-table)
1853 (put-display-table ,range ,value ,display-table)
1854 (if (sequencep ,display-table)
1855 (aset ,display-table ,range ,value)
1856 (put-char-table ,range ,value ,display-table))))
1857 `(aset ,display-table ,range ,value)))
1858
1859(defmacro gnus-get-display-table (character display-table)
1860 "Find value for CHARACTER in DISPLAY-TABLE. "
1861 (if (featurep 'xemacs)
1862 `(if (fboundp 'get-display-table)
1863 (get-display-table ,character ,display-table)
1864 (if (sequencep ,display-table)
1865 (aref ,display-table ,character)
1866 (get-char-table ,character ,display-table)))
1867 `(aref ,display-table ,character)))
1868
eec82323
LMI
1869(provide 'gnus-util)
1870
cbee283d 1871;; arch-tag: f94991af-d32b-4c97-8c26-ca12a934de49
eec82323 1872;;; gnus-util.el ends here