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