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