comparison lisp/gnus/gnus-util.el @ 56927:55fd4f77387a after-merge-gnus-5_10

Revision: miles@gnu.org--gnu-2004/emacs--cvs-trunk--0--patch-523 Merge from emacs--gnus--5.10, gnus--rel--5.10 Patches applied: * miles@gnu.org--gnu-2004/emacs--gnus--5.10--base-0 tag of miles@gnu.org--gnu-2004/emacs--cvs-trunk--0--patch-464 * miles@gnu.org--gnu-2004/emacs--gnus--5.10--patch-1 Import from CVS branch gnus-5_10-branch * miles@gnu.org--gnu-2004/emacs--gnus--5.10--patch-2 Merge from lorentey@elte.hu--2004/emacs--multi-tty--0, emacs--cvs-trunk--0 * miles@gnu.org--gnu-2004/emacs--gnus--5.10--patch-3 Merge from gnus--rel--5.10 * miles@gnu.org--gnu-2004/emacs--gnus--5.10--patch-4 Merge from gnus--rel--5.10 * miles@gnu.org--gnu-2004/gnus--rel--5.10--patch-18 Update from CVS * miles@gnu.org--gnu-2004/gnus--rel--5.10--patch-19 Remove autoconf-generated files from archive * miles@gnu.org--gnu-2004/gnus--rel--5.10--patch-20 Update from CVS
author Miles Bader <miles@gnu.org>
date Sat, 04 Sep 2004 13:13:48 +0000
parents 695cf19ef79e
children 497f0d2ca551 cce1c0ee76ee
comparison
equal deleted inserted replaced
56926:f8e248e9a717 56927:55fd4f77387a
1 ;;; gnus-util.el --- utility functions for Gnus 1 ;;; gnus-util.el --- utility functions for Gnus
2 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2002 2 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004
3 ;; Free Software Foundation, Inc. 3 ;; Free Software Foundation, Inc.
4 4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org> 5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; Keywords: news 6 ;; Keywords: news
7 7
26 26
27 ;; Nothing in this file depends on any other parts of Gnus -- all 27 ;; Nothing in this file depends on any other parts of Gnus -- all
28 ;; functions and macros in this file are utility functions that are 28 ;; functions and macros in this file are utility functions that are
29 ;; used by Gnus and may be used by any other package without loading 29 ;; used by Gnus and may be used by any other package without loading
30 ;; Gnus first. 30 ;; Gnus first.
31
32 ;; [Unfortunately, it does depend on other parts of Gnus, e.g. the
33 ;; autoloads below...]
31 34
32 ;;; Code: 35 ;;; Code:
33 36
34 (require 'custom) 37 (require 'custom)
35 (eval-when-compile 38 (eval-when-compile
36 (require 'cl) 39 (require 'cl)
37 ;; Fixme: this should be a gnus variable, not nnmail-. 40 ;; Fixme: this should be a gnus variable, not nnmail-.
38 (defvar nnmail-pathname-coding-system)) 41 (defvar nnmail-pathname-coding-system))
39 (require 'nnheader)
40 (require 'time-date) 42 (require 'time-date)
43 (require 'netrc)
41 44
42 (eval-and-compile 45 (eval-and-compile
43 (autoload 'message-fetch-field "message") 46 (autoload 'message-fetch-field "message")
47 (autoload 'gnus-get-buffer-window "gnus-win")
44 (autoload 'rmail-insert-rmail-file-header "rmail") 48 (autoload 'rmail-insert-rmail-file-header "rmail")
45 (autoload 'rmail-count-new-messages "rmail") 49 (autoload 'rmail-count-new-messages "rmail")
46 (autoload 'rmail-show-message "rmail")) 50 (autoload 'rmail-show-message "rmail")
51 (autoload 'nnheader-narrow-to-headers "nnheader")
52 (autoload 'nnheader-replace-chars-in-string "nnheader"))
53
54 (eval-and-compile
55 (cond
56 ((fboundp 'replace-in-string)
57 (defalias 'gnus-replace-in-string 'replace-in-string))
58 ((fboundp 'replace-regexp-in-string)
59 (defun gnus-replace-in-string (string regexp newtext &optional literal)
60 (replace-regexp-in-string regexp newtext string nil literal)))
61 (t
62 (defun gnus-replace-in-string (string regexp newtext &optional literal)
63 (let ((start 0) tail)
64 (while (string-match regexp string start)
65 (setq tail (- (length string) (match-end 0)))
66 (setq string (replace-match newtext nil literal string))
67 (setq start (- (length string) tail))))
68 string))))
69
70 ;;; bring in the netrc functions as aliases
71 (defalias 'gnus-netrc-get 'netrc-get)
72 (defalias 'gnus-netrc-machine 'netrc-machine)
73 (defalias 'gnus-parse-netrc 'netrc-parse)
47 74
48 (defun gnus-boundp (variable) 75 (defun gnus-boundp (variable)
49 "Return non-nil if VARIABLE is bound and non-nil." 76 "Return non-nil if VARIABLE is bound and non-nil."
50 (and (boundp variable) 77 (and (boundp variable)
51 (symbol-value variable))) 78 (symbol-value variable)))
52 79
53 (defmacro gnus-eval-in-buffer-window (buffer &rest forms) 80 (defmacro gnus-eval-in-buffer-window (buffer &rest forms)
54 "Pop to BUFFER, evaluate FORMS, and then return to the original window." 81 "Pop to BUFFER, evaluate FORMS, and then return to the original window."
55 (let ((tempvar (make-symbol "GnusStartBufferWindow")) 82 (let ((tempvar (make-symbol "GnusStartBufferWindow"))
56 (w (make-symbol "w")) 83 (w (make-symbol "w"))
57 (buf (make-symbol "buf"))) 84 (buf (make-symbol "buf")))
58 `(let* ((,tempvar (selected-window)) 85 `(let* ((,tempvar (selected-window))
59 (,buf ,buffer) 86 (,buf ,buffer)
60 (,w (get-buffer-window ,buf 'visible))) 87 (,w (gnus-get-buffer-window ,buf 'visible)))
61 (unwind-protect 88 (unwind-protect
62 (progn 89 (progn
63 (if ,w 90 (if ,w
64 (progn 91 (progn
65 (select-window ,w) 92 (select-window ,w)
66 (set-buffer (window-buffer ,w))) 93 (set-buffer (window-buffer ,w)))
67 (pop-to-buffer ,buf)) 94 (pop-to-buffer ,buf))
68 ,@forms) 95 ,@forms)
69 (select-window ,tempvar))))) 96 (select-window ,tempvar)))))
70 97
71 (put 'gnus-eval-in-buffer-window 'lisp-indent-function 1) 98 (put 'gnus-eval-in-buffer-window 'lisp-indent-function 1)
72 (put 'gnus-eval-in-buffer-window 'edebug-form-spec '(form body)) 99 (put 'gnus-eval-in-buffer-window 'edebug-form-spec '(form body))
73 100
74 (defmacro gnus-intern-safe (string hashtable) 101 (defmacro gnus-intern-safe (string hashtable)
79 symbol)) 106 symbol))
80 107
81 ;; Added by Geoffrey T. Dairiki <dairiki@u.washington.edu>. A safe way 108 ;; Added by Geoffrey T. Dairiki <dairiki@u.washington.edu>. A safe way
82 ;; to limit the length of a string. This function is necessary since 109 ;; to limit the length of a string. This function is necessary since
83 ;; `(substr "abc" 0 30)' pukes with "Args out of range". 110 ;; `(substr "abc" 0 30)' pukes with "Args out of range".
111 ;; Fixme: Why not `truncate-string-to-width'?
84 (defsubst gnus-limit-string (str width) 112 (defsubst gnus-limit-string (str width)
85 (if (> (length str) width) 113 (if (> (length str) width)
86 (substring str 0 width) 114 (substring str 0 width)
87 str)) 115 str))
88
89 (defsubst gnus-functionp (form)
90 "Return non-nil if FORM is funcallable."
91 (or (and (symbolp form) (fboundp form))
92 (and (listp form) (eq (car form) 'lambda))
93 (byte-code-function-p form)))
94 116
95 (defsubst gnus-goto-char (point) 117 (defsubst gnus-goto-char (point)
96 (and point (goto-char point))) 118 (and point (goto-char point)))
97 119
98 (defmacro gnus-buffer-exists-p (buffer) 120 (defmacro gnus-buffer-exists-p (buffer)
99 `(let ((buffer ,buffer)) 121 `(let ((buffer ,buffer))
100 (when buffer 122 (when buffer
101 (funcall (if (stringp buffer) 'get-buffer 'buffer-name) 123 (funcall (if (stringp buffer) 'get-buffer 'buffer-name)
102 buffer)))) 124 buffer))))
103 125
104 (defmacro gnus-kill-buffer (buffer)
105 `(let ((buf ,buffer))
106 (when (gnus-buffer-exists-p buf)
107 (kill-buffer buf))))
108
109 (defalias 'gnus-point-at-bol 126 (defalias 'gnus-point-at-bol
110 (if (fboundp 'point-at-bol) 127 (if (fboundp 'point-at-bol)
111 'point-at-bol 128 'point-at-bol
112 'line-beginning-position)) 129 'line-beginning-position))
113 130
114 (defalias 'gnus-point-at-eol 131 (defalias 'gnus-point-at-eol
115 (if (fboundp 'point-at-eol) 132 (if (fboundp 'point-at-eol)
116 'point-at-eol 133 'point-at-eol
117 'line-end-position)) 134 'line-end-position))
135
136 ;; The LOCAL arg to `add-hook' is interpreted differently in Emacs and
137 ;; XEmacs. In Emacs we don't need to call `make-local-hook' first.
138 ;; It's harmless, though, so the main purpose of this alias is to shut
139 ;; up the byte compiler.
140 (defalias 'gnus-make-local-hook
141 (if (eq (get 'make-local-hook 'byte-compile)
142 'byte-compile-obsolete)
143 'ignore ; Emacs
144 'make-local-hook)) ; XEmacs
118 145
119 (defun gnus-delete-first (elt list) 146 (defun gnus-delete-first (elt list)
120 "Delete by side effect the first occurrence of ELT as a member of LIST." 147 "Delete by side effect the first occurrence of ELT as a member of LIST."
121 (if (equal (car list) elt) 148 (if (equal (car list) elt)
122 (cdr list) 149 (cdr list)
128 (setcdr list (cddr list))) 155 (setcdr list (cddr list)))
129 total))) 156 total)))
130 157
131 ;; Delete the current line (and the next N lines). 158 ;; Delete the current line (and the next N lines).
132 (defmacro gnus-delete-line (&optional n) 159 (defmacro gnus-delete-line (&optional n)
133 `(delete-region (progn (beginning-of-line) (point)) 160 `(delete-region (gnus-point-at-bol)
134 (progn (forward-line ,(or n 1)) (point)))) 161 (progn (forward-line ,(or n 1)) (point))))
135 162
136 (defun gnus-byte-code (func) 163 (defun gnus-byte-code (func)
137 "Return a form that can be `eval'ed based on FUNC." 164 "Return a form that can be `eval'ed based on FUNC."
138 (let ((fval (indirect-function func))) 165 (let ((fval (indirect-function func)))
141 (setcar flist 'byte-code) 168 (setcar flist 'byte-code)
142 flist) 169 flist)
143 (cons 'progn (cddr fval))))) 170 (cons 'progn (cddr fval)))))
144 171
145 (defun gnus-extract-address-components (from) 172 (defun gnus-extract-address-components (from)
173 "Extract address components from a From header.
174 Given an RFC-822 address FROM, extract full name and canonical address.
175 Returns a list of the form (FULL-NAME CANONICAL-ADDRESS). Much more simple
176 solution than `mail-extract-address-components', which works much better, but
177 is slower."
146 (let (name address) 178 (let (name address)
147 ;; First find the address - the thing with the @ in it. This may 179 ;; First find the address - the thing with the @ in it. This may
148 ;; not be accurate in mail addresses, but does the trick most of 180 ;; not be accurate in mail addresses, but does the trick most of
149 ;; the time in news messages. 181 ;; the time in news messages.
150 (when (string-match "\\b[^@ \t<>]+[!@][^@ \t<>]+\\b" from) 182 (when (string-match "\\b[^@ \t<>]+[!@][^@ \t<>]+\\b" from)
153 (and address 185 (and address
154 ;; Linear white space is not required. 186 ;; Linear white space is not required.
155 (string-match (concat "[ \t]*<" (regexp-quote address) ">") from) 187 (string-match (concat "[ \t]*<" (regexp-quote address) ">") from)
156 (and (setq name (substring from 0 (match-beginning 0))) 188 (and (setq name (substring from 0 (match-beginning 0)))
157 ;; Strip any quotes from the name. 189 ;; Strip any quotes from the name.
158 (string-match "\".*\"" name) 190 (string-match "^\".*\"$" name)
159 (setq name (substring name 1 (1- (match-end 0)))))) 191 (setq name (substring name 1 (1- (match-end 0))))))
160 ;; If not, then "address (name)" is used. 192 ;; If not, then "address (name)" is used.
161 (or name 193 (or name
162 (and (string-match "(.+)" from) 194 (and (string-match "(.+)" from)
163 (setq name (substring from (1+ (match-beginning 0)) 195 (setq name (substring from (1+ (match-beginning 0))
178 (let ((case-fold-search t) 210 (let ((case-fold-search t)
179 (inhibit-point-motion-hooks t)) 211 (inhibit-point-motion-hooks t))
180 (nnheader-narrow-to-headers) 212 (nnheader-narrow-to-headers)
181 (message-fetch-field field))))) 213 (message-fetch-field field)))))
182 214
215 (defun gnus-fetch-original-field (field)
216 "Fetch FIELD from the original version of the current article."
217 (with-current-buffer gnus-original-article-buffer
218 (gnus-fetch-field field)))
219
220
183 (defun gnus-goto-colon () 221 (defun gnus-goto-colon ()
184 (beginning-of-line) 222 (beginning-of-line)
185 (search-forward ":" (gnus-point-at-eol) t)) 223 (let ((eol (gnus-point-at-eol)))
224 (goto-char (or (text-property-any (point) eol 'gnus-position t)
225 (search-forward ":" eol t)
226 (point)))))
227
228 (defun gnus-decode-newsgroups (newsgroups group &optional method)
229 (let ((method (or method (gnus-find-method-for-group group))))
230 (mapconcat (lambda (group)
231 (gnus-group-name-decode group (gnus-group-name-charset
232 method group)))
233 (message-tokenize-header newsgroups)
234 ",")))
186 235
187 (defun gnus-remove-text-with-property (prop) 236 (defun gnus-remove-text-with-property (prop)
188 "Delete all text in the current buffer with text property PROP." 237 "Delete all text in the current buffer with text property PROP."
189 (save-excursion 238 (save-excursion
190 (goto-char (point-min)) 239 (goto-char (point-min))
193 (delete-char 1)) 242 (delete-char 1))
194 (goto-char (next-single-property-change (point) prop nil (point-max)))))) 243 (goto-char (next-single-property-change (point) prop nil (point-max))))))
195 244
196 (defun gnus-newsgroup-directory-form (newsgroup) 245 (defun gnus-newsgroup-directory-form (newsgroup)
197 "Make hierarchical directory name from NEWSGROUP name." 246 "Make hierarchical directory name from NEWSGROUP name."
198 (let ((newsgroup (gnus-newsgroup-savable-name newsgroup)) 247 (let* ((newsgroup (gnus-newsgroup-savable-name newsgroup))
199 (len (length newsgroup)) 248 (idx (string-match ":" newsgroup)))
200 idx) 249 (concat
201 ;; If this is a foreign group, we don't want to translate the 250 (if idx (substring newsgroup 0 idx))
202 ;; entire name. 251 (if idx "/")
203 (if (setq idx (string-match ":" newsgroup)) 252 (nnheader-replace-chars-in-string
204 (aset newsgroup idx ?/) 253 (if idx (substring newsgroup (1+ idx)) newsgroup)
205 (setq idx 0)) 254 ?. ?/))))
206 ;; Replace all occurrences of `.' with `/'.
207 (while (< idx len)
208 (when (= (aref newsgroup idx) ?.)
209 (aset newsgroup idx ?/))
210 (setq idx (1+ idx)))
211 newsgroup))
212 255
213 (defun gnus-newsgroup-savable-name (group) 256 (defun gnus-newsgroup-savable-name (group)
214 ;; Replace any slashes in a group name (eg. an ange-ftp nndoc group) 257 ;; Replace any slashes in a group name (eg. an ange-ftp nndoc group)
215 ;; with dots. 258 ;; with dots.
216 (nnheader-replace-chars-in-string group ?/ ?.)) 259 (nnheader-replace-chars-in-string group ?/ ?.))
269 (if (or (not safe) 312 (if (or (not safe)
270 (eq (lookup-key keymap key) 'undefined)) 313 (eq (lookup-key keymap key) 'undefined))
271 (define-key keymap key (pop plist)) 314 (define-key keymap key (pop plist))
272 (pop plist))))) 315 (pop plist)))))
273 316
274 (defun gnus-completing-read (default prompt &rest args) 317 (defun gnus-completing-read-with-default (default prompt &rest args)
275 ;; Like `completing-read', except that DEFAULT is the default argument. 318 ;; Like `completing-read', except that DEFAULT is the default argument.
276 (let* ((prompt (if default 319 (let* ((prompt (if default
277 (concat prompt " (default " default ") ") 320 (concat prompt " (default " default ") ")
278 (concat prompt " "))) 321 (concat prompt " ")))
279 (answer (apply 'completing-read prompt args))) 322 (answer (apply 'completing-read prompt args)))
290 333
291 (defun gnus-yes-or-no-p (prompt) 334 (defun gnus-yes-or-no-p (prompt)
292 (prog1 335 (prog1
293 (yes-or-no-p prompt) 336 (yes-or-no-p prompt)
294 (message ""))) 337 (message "")))
338
339 ;; By Frank Schmitt <ich@Frank-Schmitt.net>. Allows to have
340 ;; age-depending date representations. (e.g. just the time if it's
341 ;; from today, the day of the week if it's within the last 7 days and
342 ;; the full date if it's older)
343
344 (defun gnus-seconds-today ()
345 "Return the number of seconds passed today."
346 (let ((now (decode-time (current-time))))
347 (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600))))
348
349 (defun gnus-seconds-month ()
350 "Return the number of seconds passed this month."
351 (let ((now (decode-time (current-time))))
352 (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600)
353 (* (- (car (nthcdr 3 now)) 1) 3600 24))))
354
355 (defun gnus-seconds-year ()
356 "Return the number of seconds passed this year."
357 (let ((now (decode-time (current-time)))
358 (days (format-time-string "%j" (current-time))))
359 (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600)
360 (* (- (string-to-number days) 1) 3600 24))))
361
362 (defvar gnus-user-date-format-alist
363 '(((gnus-seconds-today) . "%k:%M")
364 (604800 . "%a %k:%M") ;;that's one week
365 ((gnus-seconds-month) . "%a %d")
366 ((gnus-seconds-year) . "%b %d")
367 (t . "%b %d '%y")) ;;this one is used when no
368 ;;other does match
369 "Specifies date format depending on age of article.
370 This is an alist of items (AGE . FORMAT). AGE can be a number (of
371 seconds) or a Lisp expression evaluating to a number. When the age of
372 the article is less than this number, then use `format-time-string'
373 with the corresponding FORMAT for displaying the date of the article.
374 If AGE is not a number or a Lisp expression evaluating to a
375 non-number, then the corresponding FORMAT is used as a default value.
376
377 Note that the list is processed from the beginning, so it should be
378 sorted by ascending AGE. Also note that items following the first
379 non-number AGE will be ignored.
380
381 You can use the functions `gnus-seconds-today', `gnus-seconds-month'
382 and `gnus-seconds-year' in the AGE spec. They return the number of
383 seconds passed since the start of today, of this month, of this year,
384 respectively.")
385
386 (defun gnus-user-date (messy-date)
387 "Format the messy-date according to gnus-user-date-format-alist.
388 Returns \" ? \" if there's bad input or if an other error occurs.
389 Input should look like this: \"Sun, 14 Oct 2001 13:34:39 +0200\"."
390 (condition-case ()
391 (let* ((messy-date (time-to-seconds (safe-date-to-time messy-date)))
392 (now (time-to-seconds (current-time)))
393 ;;If we don't find something suitable we'll use this one
394 (my-format "%b %d '%y"))
395 (let* ((difference (- now messy-date))
396 (templist gnus-user-date-format-alist)
397 (top (eval (caar templist))))
398 (while (if (numberp top) (< top difference) (not top))
399 (progn
400 (setq templist (cdr templist))
401 (setq top (eval (caar templist)))))
402 (if (stringp (cdr (car templist)))
403 (setq my-format (cdr (car templist)))))
404 (format-time-string (eval my-format) (seconds-to-time messy-date)))
405 (error " ? ")))
295 406
296 (defun gnus-dd-mmm (messy-date) 407 (defun gnus-dd-mmm (messy-date)
297 "Return a string like DD-MMM from a big messy string." 408 "Return a string like DD-MMM from a big messy string."
298 (condition-case () 409 (condition-case ()
299 (format-time-string "%d-%b" (safe-date-to-time messy-date)) 410 (format-time-string "%d-%b" (safe-date-to-time messy-date))
323 (gnus-time-iso8601 (gnus-date-get-time date)) 434 (gnus-time-iso8601 (gnus-date-get-time date))
324 (error ""))) 435 (error "")))
325 436
326 (defun gnus-mode-string-quote (string) 437 (defun gnus-mode-string-quote (string)
327 "Quote all \"%\"'s in STRING." 438 "Quote all \"%\"'s in STRING."
328 (save-excursion 439 (gnus-replace-in-string string "%" "%%"))
329 (gnus-set-work-buffer)
330 (insert string)
331 (goto-char (point-min))
332 (while (search-forward "%" nil t)
333 (insert "%"))
334 (buffer-string)))
335 440
336 ;; Make a hash table (default and minimum size is 256). 441 ;; Make a hash table (default and minimum size is 256).
337 ;; Optional argument HASHSIZE specifies the table size. 442 ;; Optional argument HASHSIZE specifies the table size.
338 (defun gnus-make-hashtable (&optional hashsize) 443 (defun gnus-make-hashtable (&optional hashsize)
339 (make-vector (if hashsize (max (gnus-create-hash-size hashsize) 256) 256) 0)) 444 (make-vector (if hashsize (max (gnus-create-hash-size hashsize) 256) 256) 0))
357 display most important messages; and at ten, Gnus will keep on 462 display most important messages; and at ten, Gnus will keep on
358 jabbering all the time." 463 jabbering all the time."
359 :group 'gnus-start 464 :group 'gnus-start
360 :type 'integer) 465 :type 'integer)
361 466
362 ;; Show message if message has a lower level than `gnus-verbose'.
363 ;; Guideline for numbers:
364 ;; 1 - error messages, 3 - non-serious error messages, 5 - messages
365 ;; for things that take a long time, 7 - not very important messages
366 ;; on stuff, 9 - messages inside loops.
367 (defun gnus-message (level &rest args) 467 (defun gnus-message (level &rest args)
468 "If LEVEL is lower than `gnus-verbose' print ARGS using `message'.
469
470 Guideline for numbers:
471 1 - error messages, 3 - non-serious error messages, 5 - messages for things
472 that take a long time, 7 - not very important messages on stuff, 9 - messages
473 inside loops."
368 (if (<= level gnus-verbose) 474 (if (<= level gnus-verbose)
369 (apply 'message args) 475 (apply 'message args)
370 ;; We have to do this format thingy here even if the result isn't 476 ;; We have to do this format thingy here even if the result isn't
371 ;; shown - the return value has to be the same as the return value 477 ;; shown - the return value has to be the same as the return value
372 ;; from `message'. 478 ;; from `message'.
385 491
386 (defun gnus-split-references (references) 492 (defun gnus-split-references (references)
387 "Return a list of Message-IDs in REFERENCES." 493 "Return a list of Message-IDs in REFERENCES."
388 (let ((beg 0) 494 (let ((beg 0)
389 ids) 495 ids)
390 (while (string-match "<[^>]+>" references beg) 496 (while (string-match "<[^<]+[^< \t]" references beg)
391 (push (substring references (match-beginning 0) (setq beg (match-end 0))) 497 (push (substring references (match-beginning 0) (setq beg (match-end 0)))
392 ids)) 498 ids))
393 (nreverse ids))) 499 (nreverse ids)))
394 500
395 (defsubst gnus-parent-id (references &optional n) 501 (defsubst gnus-parent-id (references &optional n)
396 "Return the last Message-ID in REFERENCES. 502 "Return the last Message-ID in REFERENCES.
397 If N, return the Nth ancestor instead." 503 If N, return the Nth ancestor instead."
398 (when references 504 (when (and references
399 (let ((ids (inline (gnus-split-references references)))) 505 (not (zerop (length references))))
400 (while (nthcdr (or n 1) ids) 506 (if n
401 (setq ids (cdr ids))) 507 (let ((ids (inline (gnus-split-references references))))
402 (car ids)))) 508 (while (nthcdr n ids)
403 509 (setq ids (cdr ids)))
404 (defsubst gnus-buffer-live-p (buffer) 510 (car ids))
511 (when (string-match "\\(<[^<]+>\\)[ \t]*\\'" references)
512 (match-string 1 references)))))
513
514 (defun gnus-buffer-live-p (buffer)
405 "Say whether BUFFER is alive or not." 515 "Say whether BUFFER is alive or not."
406 (and buffer 516 (and buffer
407 (get-buffer buffer) 517 (get-buffer buffer)
408 (buffer-name (get-buffer buffer)))) 518 (buffer-name (get-buffer buffer))))
409 519
410 (defun gnus-horizontal-recenter () 520 (defun gnus-horizontal-recenter ()
411 "Recenter the current buffer horizontally." 521 "Recenter the current buffer horizontally."
412 (if (< (current-column) (/ (window-width) 2)) 522 (if (< (current-column) (/ (window-width) 2))
413 (set-window-hscroll (get-buffer-window (current-buffer) t) 0) 523 (set-window-hscroll (gnus-get-buffer-window (current-buffer) t) 0)
414 (let* ((orig (point)) 524 (let* ((orig (point))
415 (end (window-end (get-buffer-window (current-buffer) t))) 525 (end (window-end (gnus-get-buffer-window (current-buffer) t)))
416 (max 0)) 526 (max 0))
417 (when end 527 (when end
418 ;; Find the longest line currently displayed in the window. 528 ;; Find the longest line currently displayed in the window.
419 (goto-char (window-start)) 529 (goto-char (window-start))
420 (while (and (not (eobp)) 530 (while (and (not (eobp))
424 (forward-line 1)) 534 (forward-line 1))
425 (goto-char orig) 535 (goto-char orig)
426 ;; Scroll horizontally to center (sort of) the point. 536 ;; Scroll horizontally to center (sort of) the point.
427 (if (> max (window-width)) 537 (if (> max (window-width))
428 (set-window-hscroll 538 (set-window-hscroll
429 (get-buffer-window (current-buffer) t) 539 (gnus-get-buffer-window (current-buffer) t)
430 (min (- (current-column) (/ (window-width) 3)) 540 (min (- (current-column) (/ (window-width) 3))
431 (+ 2 (- max (window-width))))) 541 (+ 2 (- max (window-width)))))
432 (set-window-hscroll (get-buffer-window (current-buffer) t) 0)) 542 (set-window-hscroll (gnus-get-buffer-window (current-buffer) t) 0))
433 max)))) 543 max))))
434 544
435 (defun gnus-read-event-char () 545 (defun gnus-read-event-char (&optional prompt)
436 "Get the next event." 546 "Get the next event."
437 (let ((event (read-event))) 547 (let ((event (read-event prompt)))
438 ;; should be gnus-characterp, but this can't be called in XEmacs anyway 548 ;; should be gnus-characterp, but this can't be called in XEmacs anyway
439 (cons (and (numberp event) event) event))) 549 (cons (and (numberp event) event) event)))
440 550
441 (defun gnus-sortable-date (date) 551 (defun gnus-sortable-date (date)
442 "Make string suitable for sorting from DATE." 552 "Make string suitable for sorting from DATE."
472 (if (string-match "^[^:]+:" gname) 582 (if (string-match "^[^:]+:" gname)
473 (substring gname (match-end 0)) 583 (substring gname (match-end 0))
474 gname))) 584 gname)))
475 585
476 (defun gnus-make-sort-function (funs) 586 (defun gnus-make-sort-function (funs)
477 "Return a composite sort condition based on the functions in FUNC." 587 "Return a composite sort condition based on the functions in FUNS."
478 (cond 588 (cond
479 ;; Just a simple function. 589 ;; Just a simple function.
480 ((gnus-functionp funs) funs) 590 ((functionp funs) funs)
481 ;; No functions at all. 591 ;; No functions at all.
482 ((null funs) funs) 592 ((null funs) funs)
483 ;; A list of functions. 593 ;; A list of functions.
484 ((or (cdr funs) 594 ((or (cdr funs)
485 (listp (car funs))) 595 (listp (car funs)))
486 `(lambda (t1 t2) 596 (gnus-byte-compile
487 ,(gnus-make-sort-function-1 (reverse funs)))) 597 `(lambda (t1 t2)
598 ,(gnus-make-sort-function-1 (reverse funs)))))
488 ;; A list containing just one function. 599 ;; A list containing just one function.
489 (t 600 (t
490 (car funs)))) 601 (car funs))))
491 602
492 (defun gnus-make-sort-function-1 (funs) 603 (defun gnus-make-sort-function-1 (funs)
493 "Return a composite sort condition based on the functions in FUNC." 604 "Return a composite sort condition based on the functions in FUNS."
494 (let ((function (car funs)) 605 (let ((function (car funs))
495 (first 't1) 606 (first 't1)
496 (last 't2)) 607 (last 't2))
497 (when (consp function) 608 (when (consp function)
498 (cond 609 (cond
499 ;; Reversed spec. 610 ;; Reversed spec.
500 ((eq (car function) 'not) 611 ((eq (car function) 'not)
501 (setq function (cadr function) 612 (setq function (cadr function)
502 first 't2 613 first 't2
503 last 't1)) 614 last 't1))
504 ((gnus-functionp function) 615 ((functionp function)
505 ;; Do nothing. 616 ;; Do nothing.
506 ) 617 )
507 (t 618 (t
508 (error "Invalid sort spec: %s" function)))) 619 (error "Invalid sort spec: %s" function))))
509 (if (cdr funs) 620 (if (cdr funs)
515 (defun gnus-turn-off-edit-menu (type) 626 (defun gnus-turn-off-edit-menu (type)
516 "Turn off edit menu in `gnus-TYPE-mode-map'." 627 "Turn off edit menu in `gnus-TYPE-mode-map'."
517 (define-key (symbol-value (intern (format "gnus-%s-mode-map" type))) 628 (define-key (symbol-value (intern (format "gnus-%s-mode-map" type)))
518 [menu-bar edit] 'undefined)) 629 [menu-bar edit] 'undefined))
519 630
631 (defmacro gnus-bind-print-variables (&rest forms)
632 "Bind print-* variables and evaluate FORMS.
633 This macro is used with `prin1', `pp', etc. in order to ensure printed
634 Lisp objects are loadable. Bind `print-quoted' and `print-readably'
635 to t, and `print-escape-multibyte', `print-escape-newlines',
636 `print-escape-nonascii', `print-length', `print-level' and
637 `print-string-length' to nil."
638 `(let ((print-quoted t)
639 (print-readably t)
640 ;;print-circle
641 ;;print-continuous-numbering
642 print-escape-multibyte
643 print-escape-newlines
644 print-escape-nonascii
645 ;;print-gensym
646 print-length
647 print-level
648 print-string-length)
649 ,@forms))
650
520 (defun gnus-prin1 (form) 651 (defun gnus-prin1 (form)
521 "Use `prin1' on FORM in the current buffer. 652 "Use `prin1' on FORM in the current buffer.
522 Bind `print-quoted' and `print-readably' to t while printing." 653 Bind `print-quoted' and `print-readably' to t, and `print-length' and
523 (let ((print-quoted t) 654 `print-level' to nil. See also `gnus-bind-print-variables'."
524 (print-readably t) 655 (gnus-bind-print-variables (prin1 form (current-buffer))))
525 (print-escape-multibyte nil)
526 print-level print-length)
527 (prin1 form (current-buffer))))
528 656
529 (defun gnus-prin1-to-string (form) 657 (defun gnus-prin1-to-string (form)
530 "The same as `prin1', but bind `print-quoted' and `print-readably' to t." 658 "The same as `prin1'.
531 (let ((print-quoted t) 659 Bind `print-quoted' and `print-readably' to t, and `print-length' and
532 (print-readably t)) 660 `print-level' to nil. See also `gnus-bind-print-variables'."
533 (prin1-to-string form))) 661 (gnus-bind-print-variables (prin1-to-string form)))
662
663 (defun gnus-pp (form)
664 "Use `pp' on FORM in the current buffer.
665 Bind `print-quoted' and `print-readably' to t, and `print-length' and
666 `print-level' to nil. See also `gnus-bind-print-variables'."
667 (gnus-bind-print-variables (pp form (current-buffer))))
668
669 (defun gnus-pp-to-string (form)
670 "The same as `pp-to-string'.
671 Bind `print-quoted' and `print-readably' to t, and `print-length' and
672 `print-level' to nil. See also `gnus-bind-print-variables'."
673 (gnus-bind-print-variables (pp-to-string form)))
534 674
535 (defun gnus-make-directory (directory) 675 (defun gnus-make-directory (directory)
536 "Make DIRECTORY (and all its parents) if it doesn't exist." 676 "Make DIRECTORY (and all its parents) if it doesn't exist."
537 (require 'nnmail) 677 (require 'nnmail)
538 (let ((file-name-coding-system nnmail-pathname-coding-system)) 678 (let ((file-name-coding-system nnmail-pathname-coding-system))
569 (while (re-search-forward gnus-emphasize-whitespace-regexp end 'move) 709 (while (re-search-forward gnus-emphasize-whitespace-regexp end 'move)
570 (gnus-put-text-property beg (match-beginning 0) prop val) 710 (gnus-put-text-property beg (match-beginning 0) prop val)
571 (setq beg (point))) 711 (setq beg (point)))
572 (gnus-put-text-property beg (point) prop val))))) 712 (gnus-put-text-property beg (point) prop val)))))
573 713
714 (defsubst gnus-put-overlay-excluding-newlines (beg end prop val)
715 "The same as `put-text-property', but don't put this prop on any newlines in the region."
716 (save-match-data
717 (save-excursion
718 (save-restriction
719 (goto-char beg)
720 (while (re-search-forward gnus-emphasize-whitespace-regexp end 'move)
721 (gnus-overlay-put
722 (gnus-make-overlay beg (match-beginning 0))
723 prop val)
724 (setq beg (point)))
725 (gnus-overlay-put (gnus-make-overlay beg (point)) prop val)))))
726
574 (defun gnus-put-text-property-excluding-characters-with-faces (beg end 727 (defun gnus-put-text-property-excluding-characters-with-faces (beg end
575 prop val) 728 prop val)
576 "The same as `put-text-property', but don't put props on characters with the `gnus-face' property." 729 "The same as `put-text-property', but don't put props on characters with the `gnus-face' property."
577 (let ((b beg)) 730 (let ((b beg))
578 (while (/= b end) 731 (while (/= b end)
579 (when (get-text-property b 'gnus-face) 732 (when (get-text-property b 'gnus-face)
580 (setq b (next-single-property-change b 'gnus-face nil end))) 733 (setq b (next-single-property-change b 'gnus-face nil end)))
581 (when (/= b end) 734 (when (/= b end)
582 (gnus-put-text-property 735 (inline
583 b (setq b (next-single-property-change b 'gnus-face nil end)) 736 (gnus-put-text-property
584 prop val))))) 737 b (setq b (next-single-property-change b 'gnus-face nil end))
738 prop val))))))
739
740 (defmacro gnus-faces-at (position)
741 "Return a list of faces at POSITION."
742 (if (featurep 'xemacs)
743 `(let ((pos ,position))
744 (mapcar-extents 'extent-face
745 nil (current-buffer) pos pos nil 'face))
746 `(let ((pos ,position))
747 (delq nil (cons (get-text-property pos 'face)
748 (mapcar
749 (lambda (overlay)
750 (overlay-get overlay 'face))
751 (overlays-at pos)))))))
585 752
586 ;;; Protected and atomic operations. dmoore@ucsd.edu 21.11.1996 753 ;;; Protected and atomic operations. dmoore@ucsd.edu 21.11.1996
587 ;;; The primary idea here is to try to protect internal datastructures 754 ;;; The primary idea here is to try to protect internal datastructures
588 ;;; from becoming corrupted when the user hits C-g, or if a hook or 755 ;;; from becoming corrupted when the user hits C-g, or if a hook or
589 ;;; similar blows up. Often in Gnus multiple tables/lists need to be 756 ;;; similar blows up. Often in Gnus multiple tables/lists need to be
658 ;(put 'gnus-atomic-setq 'edebug-form-spec '(body)) 825 ;(put 'gnus-atomic-setq 'edebug-form-spec '(body))
659 826
660 827
661 ;;; Functions for saving to babyl/mail files. 828 ;;; Functions for saving to babyl/mail files.
662 829
663 (defvar rmail-default-rmail-file) 830 (eval-when-compile
831 (condition-case nil
832 (progn
833 (require 'rmail)
834 (autoload 'rmail-update-summary "rmailsum"))
835 (error
836 (define-compiler-macro rmail-select-summary (&rest body)
837 ;; Rmail of the XEmacs version is supplied by the package, and
838 ;; requires tm and apel packages. However, there may be those
839 ;; who haven't installed those packages. This macro helps such
840 ;; people even if they install those packages later.
841 `(eval '(rmail-select-summary ,@body)))
842 ;; If there's rmail but there's no tm (or there's apel of the
843 ;; mainstream, not the XEmacs version), loading rmail of the XEmacs
844 ;; version fails halfway, however it provides the rmail-select-summary
845 ;; macro which uses the following functions:
846 (autoload 'rmail-summary-displayed "rmail")
847 (autoload 'rmail-maybe-display-summary "rmail")))
848 (defvar rmail-default-rmail-file)
849 (defvar mm-text-coding-system))
850
664 (defun gnus-output-to-rmail (filename &optional ask) 851 (defun gnus-output-to-rmail (filename &optional ask)
665 "Append the current article to an Rmail file named FILENAME." 852 "Append the current article to an Rmail file named FILENAME."
666 (require 'rmail) 853 (require 'rmail)
854 (require 'mm-util)
667 ;; Most of these codes are borrowed from rmailout.el. 855 ;; Most of these codes are borrowed from rmailout.el.
668 (setq filename (expand-file-name filename)) 856 (setq filename (expand-file-name filename))
669 (setq rmail-default-rmail-file filename) 857 (setq rmail-default-rmail-file filename)
670 (let ((artbuf (current-buffer)) 858 (let ((artbuf (current-buffer))
671 (tmpbuf (get-buffer-create " *Gnus-output*"))) 859 (tmpbuf (get-buffer-create " *Gnus-output*")))
704 (narrow-to-region (point-max) (point-max))) 892 (narrow-to-region (point-max) (point-max)))
705 (insert-buffer-substring tmpbuf) 893 (insert-buffer-substring tmpbuf)
706 (when msg 894 (when msg
707 (goto-char (point-min)) 895 (goto-char (point-min))
708 (widen) 896 (widen)
709 (search-backward "\n\^_") 897 (search-backward "\n\^_")
710 (narrow-to-region (point) (point-max)) 898 (narrow-to-region (point) (point-max))
711 (rmail-count-new-messages t) 899 (rmail-count-new-messages t)
712 (when (rmail-summary-exists) 900 (when (rmail-summary-exists)
713 (rmail-select-summary 901 (rmail-select-summary
714 (rmail-update-summary))) 902 (rmail-update-summary)))
715 (rmail-count-new-messages t) 903 (rmail-count-new-messages t)
716 (rmail-show-message msg)) 904 (rmail-show-message msg))
717 (save-buffer))))) 905 (save-buffer)))))
783 (replace-match "\n^_" t t)) ;2 chars: "^" and "_" 971 (replace-match "\n^_" t t)) ;2 chars: "^" and "_"
784 (goto-char (point-max)) 972 (goto-char (point-max))
785 (insert "\^_"))) 973 (insert "\^_")))
786 974
787 (defun gnus-map-function (funs arg) 975 (defun gnus-map-function (funs arg)
788 "Applies the result of the first function in FUNS to the second, and so on. 976 "Apply the result of the first function in FUNS to the second, and so on.
789 ARG is passed to the first function." 977 ARG is passed to the first function."
790 (let ((myfuns funs)) 978 (while funs
791 (while myfuns 979 (setq arg (funcall (pop funs) arg)))
792 (setq arg (funcall (pop myfuns) arg))) 980 arg)
793 arg))
794 981
795 (defun gnus-run-hooks (&rest funcs) 982 (defun gnus-run-hooks (&rest funcs)
796 "Does the same as `run-hooks', but saves excursion." 983 "Does the same as `run-hooks', but saves the current buffer."
797 (let ((buf (current-buffer))) 984 (save-current-buffer
798 (unwind-protect 985 (apply 'run-hooks funcs)))
799 (apply 'run-hooks funcs)
800 (set-buffer buf))))
801
802 ;;;
803 ;;; .netrc and .authinforc parsing
804 ;;;
805
806 (defun gnus-parse-netrc (file)
807 "Parse FILE and return a list of all entries in the file."
808 (when (file-exists-p file)
809 (with-temp-buffer
810 (let ((tokens '("machine" "default" "login"
811 "password" "account" "macdef" "force"
812 "port"))
813 alist elem result pair)
814 (insert-file-contents file)
815 (goto-char (point-min))
816 ;; Go through the file, line by line.
817 (while (not (eobp))
818 (narrow-to-region (point) (gnus-point-at-eol))
819 ;; For each line, get the tokens and values.
820 (while (not (eobp))
821 (skip-chars-forward "\t ")
822 ;; Skip lines that begin with a "#".
823 (if (eq (char-after) ?#)
824 (goto-char (point-max))
825 (unless (eobp)
826 (setq elem
827 (if (= (following-char) ?\")
828 (read (current-buffer))
829 (buffer-substring
830 (point) (progn (skip-chars-forward "^\t ")
831 (point)))))
832 (cond
833 ((equal elem "macdef")
834 ;; We skip past the macro definition.
835 (widen)
836 (while (and (zerop (forward-line 1))
837 (looking-at "$")))
838 (narrow-to-region (point) (point)))
839 ((member elem tokens)
840 ;; Tokens that don't have a following value are ignored,
841 ;; except "default".
842 (when (and pair (or (cdr pair)
843 (equal (car pair) "default")))
844 (push pair alist))
845 (setq pair (list elem)))
846 (t
847 ;; Values that haven't got a preceding token are ignored.
848 (when pair
849 (setcdr pair elem)
850 (push pair alist)
851 (setq pair nil)))))))
852 (when alist
853 (push (nreverse alist) result))
854 (setq alist nil
855 pair nil)
856 (widen)
857 (forward-line 1))
858 (nreverse result)))))
859
860 (defun gnus-netrc-machine (list machine &optional port defaultport)
861 "Return the netrc values from LIST for MACHINE or for the default entry.
862 If PORT specified, only return entries with matching port tokens.
863 Entries without port tokens default to DEFAULTPORT."
864 (let ((rest list)
865 result)
866 (while list
867 (when (equal (cdr (assoc "machine" (car list))) machine)
868 (push (car list) result))
869 (pop list))
870 (unless result
871 ;; No machine name matches, so we look for default entries.
872 (while rest
873 (when (assoc "default" (car rest))
874 (push (car rest) result))
875 (pop rest)))
876 (when result
877 (setq result (nreverse result))
878 (while (and result
879 (not (equal (or port defaultport "nntp")
880 (or (gnus-netrc-get (car result) "port")
881 defaultport "nntp"))))
882 (pop result))
883 (car result))))
884
885 (defun gnus-netrc-get (alist type)
886 "Return the value of token TYPE from ALIST."
887 (cdr (assoc type alist)))
888 986
889 ;;; Various 987 ;;; Various
890 988
891 (defvar gnus-group-buffer) ; Compiler directive 989 (defvar gnus-group-buffer) ; Compiler directive
892 (defun gnus-alive-p () 990 (defun gnus-alive-p ()
896 (save-excursion 994 (save-excursion
897 (set-buffer gnus-group-buffer) 995 (set-buffer gnus-group-buffer)
898 (eq major-mode 'gnus-group-mode)))) 996 (eq major-mode 'gnus-group-mode))))
899 997
900 (defun gnus-remove-duplicates (list) 998 (defun gnus-remove-duplicates (list)
901 (let (new (tail list)) 999 (let (new)
902 (while tail 1000 (while list
903 (or (member (car tail) new) 1001 (or (member (car list) new)
904 (setq new (cons (car tail) new))) 1002 (setq new (cons (car list) new)))
905 (setq tail (cdr tail))) 1003 (setq list (cdr list)))
906 (nreverse new))) 1004 (nreverse new)))
907 1005
908 (defun gnus-delete-if (predicate list) 1006 (defun gnus-remove-if (predicate list)
909 "Delete elements from LIST that satisfy PREDICATE." 1007 "Return a copy of LIST with all items satisfying PREDICATE removed."
910 (let (out) 1008 (let (out)
911 (while list 1009 (while list
912 (unless (funcall predicate (car list)) 1010 (unless (funcall predicate (car list))
913 (push (car list) out)) 1011 (push (car list) out))
914 (pop list)) 1012 (setq list (cdr list)))
915 (nreverse out))) 1013 (nreverse out)))
916 1014
917 (defun gnus-delete-alist (key alist) 1015 (if (fboundp 'assq-delete-all)
918 "Delete all entries in ALIST that have a key eq to KEY." 1016 (defalias 'gnus-delete-alist 'assq-delete-all)
919 (let (entry) 1017 (defun gnus-delete-alist (key alist)
920 (while (setq entry (assq key alist)) 1018 "Delete from ALIST all elements whose car is KEY.
921 (setq alist (delq entry alist))) 1019 Return the modified alist."
922 alist)) 1020 (let (entry)
1021 (while (setq entry (assq key alist))
1022 (setq alist (delq entry alist)))
1023 alist)))
923 1024
924 (defmacro gnus-pull (key alist &optional assoc-p) 1025 (defmacro gnus-pull (key alist &optional assoc-p)
925 "Modify ALIST to be without KEY." 1026 "Modify ALIST to be without KEY."
926 (unless (symbolp alist) 1027 (unless (symbolp alist)
927 (error "Not a symbol: %s" alist)) 1028 (error "Not a symbol: %s" alist))
928 (let ((fun (if assoc-p 'assoc 'assq))) 1029 (let ((fun (if assoc-p 'assoc 'assq)))
929 `(setq ,alist (delq (,fun ,key ,alist) ,alist)))) 1030 `(setq ,alist (delq (,fun ,key ,alist) ,alist))))
930 1031
931 (defun gnus-globalify-regexp (re) 1032 (defun gnus-globalify-regexp (re)
932 "Returns a regexp that matches a whole line, iff RE matches a part of it." 1033 "Return a regexp that matches a whole line, iff RE matches a part of it."
933 (concat (unless (string-match "^\\^" re) "^.*") 1034 (concat (unless (string-match "^\\^" re) "^.*")
934 re 1035 re
935 (unless (string-match "\\$$" re) ".*$"))) 1036 (unless (string-match "\\$$" re) ".*$")))
936 1037
937 (defun gnus-set-window-start (&optional point) 1038 (defun gnus-set-window-start (&optional point)
938 "Set the window start to POINT, or (point) if nil." 1039 "Set the window start to POINT, or (point) if nil."
939 (let ((win (get-buffer-window (current-buffer) t))) 1040 (let ((win (gnus-get-buffer-window (current-buffer) t)))
940 (when win 1041 (when win
941 (set-window-start win (or point (point)))))) 1042 (set-window-start win (or point (point))))))
942 1043
943 (defun gnus-annotation-in-region-p (b e) 1044 (defun gnus-annotation-in-region-p (b e)
944 (if (= b e) 1045 (if (= b e)
978 hashtb) 1079 hashtb)
979 (goto-char (point-max)) 1080 (goto-char (point-max))
980 (while (search-backward "\\." nil t) 1081 (while (search-backward "\\." nil t)
981 (delete-char 1))))) 1082 (delete-char 1)))))
982 1083
1084 ;; Fixme: Why not use `with-output-to-temp-buffer'?
1085 (defmacro gnus-with-output-to-file (file &rest body)
1086 (let ((buffer (make-symbol "output-buffer"))
1087 (size (make-symbol "output-buffer-size"))
1088 (leng (make-symbol "output-buffer-length"))
1089 (append (make-symbol "output-buffer-append")))
1090 `(let* ((,size 131072)
1091 (,buffer (make-string ,size 0))
1092 (,leng 0)
1093 (,append nil)
1094 (standard-output
1095 (lambda (c)
1096 (aset ,buffer ,leng c)
1097
1098 (if (= ,size (setq ,leng (1+ ,leng)))
1099 (progn (write-region ,buffer nil ,file ,append 'no-msg)
1100 (setq ,leng 0
1101 ,append t))))))
1102 ,@body
1103 (when (> ,leng 0)
1104 (let ((coding-system-for-write 'no-conversion))
1105 (write-region (substring ,buffer 0 ,leng) nil ,file
1106 ,append 'no-msg))))))
1107
1108 (put 'gnus-with-output-to-file 'lisp-indent-function 1)
1109 (put 'gnus-with-output-to-file 'edebug-form-spec '(form body))
1110
1111 (if (fboundp 'union)
1112 (defalias 'gnus-union 'union)
1113 (defun gnus-union (l1 l2)
1114 "Set union of lists L1 and L2."
1115 (cond ((null l1) l2)
1116 ((null l2) l1)
1117 ((equal l1 l2) l1)
1118 (t
1119 (or (>= (length l1) (length l2))
1120 (setq l1 (prog1 l2 (setq l2 l1))))
1121 (while l2
1122 (or (member (car l2) l1)
1123 (push (car l2) l1))
1124 (pop l2))
1125 l1))))
1126
983 (defun gnus-add-text-properties-when 1127 (defun gnus-add-text-properties-when
984 (property value start end properties &optional object) 1128 (property value start end properties &optional object)
985 "Like `gnus-add-text-properties', only applied on where PROPERTY is VALUE." 1129 "Like `gnus-add-text-properties', only applied on where PROPERTY is VALUE."
986 (let (point) 1130 (let (point)
987 (while (and start 1131 (while (and start
1132 (< start end) ;; XEmacs will loop for every when start=end.
988 (setq point (text-property-not-all start end property value))) 1133 (setq point (text-property-not-all start end property value)))
989 (gnus-add-text-properties start point properties object) 1134 (gnus-add-text-properties start point properties object)
990 (setq start (text-property-any point end property value))) 1135 (setq start (text-property-any point end property value)))
991 (if start 1136 (if start
992 (gnus-add-text-properties start end properties object)))) 1137 (gnus-add-text-properties start end properties object))))
994 (defun gnus-remove-text-properties-when 1139 (defun gnus-remove-text-properties-when
995 (property value start end properties &optional object) 1140 (property value start end properties &optional object)
996 "Like `remove-text-properties', only applied on where PROPERTY is VALUE." 1141 "Like `remove-text-properties', only applied on where PROPERTY is VALUE."
997 (let (point) 1142 (let (point)
998 (while (and start 1143 (while (and start
1144 (< start end)
999 (setq point (text-property-not-all start end property value))) 1145 (setq point (text-property-not-all start end property value)))
1000 (remove-text-properties start point properties object) 1146 (remove-text-properties start point properties object)
1001 (setq start (text-property-any point end property value))) 1147 (setq start (text-property-any point end property value)))
1002 (if start 1148 (if start
1003 (remove-text-properties start end properties object)) 1149 (remove-text-properties start end properties object))
1004 t)) 1150 t))
1005 1151
1152 ;; This might use `compare-strings' to reduce consing in the
1153 ;; case-insensitive case, but it has to cope with null args.
1154 ;; (`string-equal' uses symbol print names.)
1155 (defun gnus-string-equal (x y)
1156 "Like `string-equal', except it compares case-insensitively."
1157 (and (= (length x) (length y))
1158 (or (string-equal x y)
1159 (string-equal (downcase x) (downcase y)))))
1160
1161 (defcustom gnus-use-byte-compile t
1162 "If non-nil, byte-compile crucial run-time code.
1163 Setting it to nil has no effect after the first time `gnus-byte-compile'
1164 is run."
1165 :type 'boolean
1166 :version "21.1"
1167 :group 'gnus-various)
1168
1169 (defun gnus-byte-compile (form)
1170 "Byte-compile FORM if `gnus-use-byte-compile' is non-nil."
1171 (if gnus-use-byte-compile
1172 (progn
1173 (condition-case nil
1174 ;; Work around a bug in XEmacs 21.4
1175 (require 'byte-optimize)
1176 (error))
1177 (require 'bytecomp)
1178 (defalias 'gnus-byte-compile
1179 (lambda (form)
1180 (let ((byte-compile-warnings '(unresolved callargs redefine)))
1181 (byte-compile form))))
1182 (gnus-byte-compile form))
1183 form))
1184
1185 (defun gnus-remassoc (key alist)
1186 "Delete by side effect any elements of LIST whose car is `equal' to KEY.
1187 The modified LIST is returned. If the first member
1188 of LIST has a car that is `equal' to KEY, there is no way to remove it
1189 by side effect; therefore, write `(setq foo (remassoc key foo))' to be
1190 sure of changing the value of `foo'."
1191 (when alist
1192 (if (equal key (caar alist))
1193 (cdr alist)
1194 (setcdr alist (gnus-remassoc key (cdr alist)))
1195 alist)))
1196
1197 (defun gnus-update-alist-soft (key value alist)
1198 (if value
1199 (cons (cons key value) (gnus-remassoc key alist))
1200 (gnus-remassoc key alist)))
1201
1202 (defun gnus-create-info-command (node)
1203 "Create a command that will go to info NODE."
1204 `(lambda ()
1205 (interactive)
1206 ,(concat "Enter the info system at node " node)
1207 (Info-goto-node ,node)
1208 (setq gnus-info-buffer (current-buffer))
1209 (gnus-configure-windows 'info)))
1210
1211 (defun gnus-not-ignore (&rest args)
1212 t)
1213
1006 (defvar gnus-directory-sep-char-regexp "/" 1214 (defvar gnus-directory-sep-char-regexp "/"
1007 "The regexp of directory separator character. 1215 "The regexp of directory separator character.
1008 If you find some problem with the directory separator character, try 1216 If you find some problem with the directory separator character, try
1009 \"[/\\\\\]\" for some systems.") 1217 \"[/\\\\\]\" for some systems.")
1010 1218
1219 (defun gnus-url-unhex (x)
1220 (if (> x ?9)
1221 (if (>= x ?a)
1222 (+ 10 (- x ?a))
1223 (+ 10 (- x ?A)))
1224 (- x ?0)))
1225
1226 ;; Fixme: Do it like QP.
1227 (defun gnus-url-unhex-string (str &optional allow-newlines)
1228 "Remove %XX, embedded spaces, etc in a url.
1229 If optional second argument ALLOW-NEWLINES is non-nil, then allow the
1230 decoding of carriage returns and line feeds in the string, which is normally
1231 forbidden in URL encoding."
1232 (let ((tmp "")
1233 (case-fold-search t))
1234 (while (string-match "%[0-9a-f][0-9a-f]" str)
1235 (let* ((start (match-beginning 0))
1236 (ch1 (gnus-url-unhex (elt str (+ start 1))))
1237 (code (+ (* 16 ch1)
1238 (gnus-url-unhex (elt str (+ start 2))))))
1239 (setq tmp (concat
1240 tmp (substring str 0 start)
1241 (cond
1242 (allow-newlines
1243 (char-to-string code))
1244 ((or (= code ?\n) (= code ?\r))
1245 " ")
1246 (t (char-to-string code))))
1247 str (substring str (match-end 0)))))
1248 (setq tmp (concat tmp str))
1249 tmp))
1250
1251 (defun gnus-make-predicate (spec)
1252 "Transform SPEC into a function that can be called.
1253 SPEC is a predicate specifier that contains stuff like `or', `and',
1254 `not', lists and functions. The functions all take one parameter."
1255 `(lambda (elem) ,(gnus-make-predicate-1 spec)))
1256
1257 (defun gnus-make-predicate-1 (spec)
1258 (cond
1259 ((symbolp spec)
1260 `(,spec elem))
1261 ((listp spec)
1262 (if (memq (car spec) '(or and not))
1263 `(,(car spec) ,@(mapcar 'gnus-make-predicate-1 (cdr spec)))
1264 (error "Invalid predicate specifier: %s" spec)))))
1265
1266 (defun gnus-local-map-property (map)
1267 "Return a list suitable for a text property list specifying keymap MAP."
1268 (cond
1269 ((featurep 'xemacs)
1270 (list 'keymap map))
1271 ((>= emacs-major-version 21)
1272 (list 'keymap map))
1273 (t
1274 (list 'local-map map))))
1275
1276 (defmacro gnus-completing-read-maybe-default (prompt table &optional predicate
1277 require-match initial-contents
1278 history default)
1279 "Like `completing-read', allowing for non-existent 7th arg in older XEmacsen."
1280 `(completing-read ,prompt ,table ,predicate ,require-match
1281 ,initial-contents ,history
1282 ,@(if (and (featurep 'xemacs) (< emacs-minor-version 2))
1283 ()
1284 (list default))))
1285
1286 (defun gnus-completing-read (prompt table &optional predicate require-match
1287 history)
1288 (when (and history
1289 (not (boundp history)))
1290 (set history nil))
1291 (gnus-completing-read-maybe-default
1292 (if (symbol-value history)
1293 (concat prompt " (" (car (symbol-value history)) "): ")
1294 (concat prompt ": "))
1295 table
1296 predicate
1297 require-match
1298 nil
1299 history
1300 (car (symbol-value history))))
1301
1302 (defun gnus-graphic-display-p ()
1303 (or (and (fboundp 'display-graphic-p)
1304 (display-graphic-p))
1305 ;;;!!!This is bogus. Fixme!
1306 (and (featurep 'xemacs)
1307 t)))
1308
1309 (put 'gnus-parse-without-error 'lisp-indent-function 0)
1310 (put 'gnus-parse-without-error 'edebug-form-spec '(body))
1311
1312 (defmacro gnus-parse-without-error (&rest body)
1313 "Allow continuing onto the next line even if an error occurs."
1314 `(while (not (eobp))
1315 (condition-case ()
1316 (progn
1317 ,@body
1318 (goto-char (point-max)))
1319 (error
1320 (gnus-error 4 "Invalid data on line %d"
1321 (count-lines (point-min) (point)))
1322 (forward-line 1)))))
1323
1324 (defun gnus-cache-file-contents (file variable function)
1325 "Cache the contents of FILE in VARIABLE. The contents come from FUNCTION."
1326 (let ((time (nth 5 (file-attributes file)))
1327 contents value)
1328 (if (or (null (setq value (symbol-value variable)))
1329 (not (equal (car value) file))
1330 (not (equal (nth 1 value) time)))
1331 (progn
1332 (setq contents (funcall function file))
1333 (set variable (list file time contents))
1334 contents)
1335 (nth 2 value))))
1336
1337 (defun gnus-multiple-choice (prompt choice &optional idx)
1338 "Ask user a multiple choice question.
1339 CHOICE is a list of the choice char and help message at IDX."
1340 (let (tchar buf)
1341 (save-window-excursion
1342 (save-excursion
1343 (while (not tchar)
1344 (message "%s (%s): "
1345 prompt
1346 (concat
1347 (mapconcat (lambda (s) (char-to-string (car s)))
1348 choice ", ") ", ?"))
1349 (setq tchar (read-char))
1350 (when (not (assq tchar choice))
1351 (setq tchar nil)
1352 (setq buf (get-buffer-create "*Gnus Help*"))
1353 (pop-to-buffer buf)
1354 (fundamental-mode) ; for Emacs 20.4+
1355 (buffer-disable-undo)
1356 (erase-buffer)
1357 (insert prompt ":\n\n")
1358 (let ((max -1)
1359 (list choice)
1360 (alist choice)
1361 (idx (or idx 1))
1362 (i 0)
1363 n width pad format)
1364 ;; find the longest string to display
1365 (while list
1366 (setq n (length (nth idx (car list))))
1367 (unless (> max n)
1368 (setq max n))
1369 (setq list (cdr list)))
1370 (setq max (+ max 4)) ; %c, `:', SPACE, a SPACE at end
1371 (setq n (/ (1- (window-width)) max)) ; items per line
1372 (setq width (/ (1- (window-width)) n)) ; width of each item
1373 ;; insert `n' items, each in a field of width `width'
1374 (while alist
1375 (if (< i n)
1376 ()
1377 (setq i 0)
1378 (delete-char -1) ; the `\n' takes a char
1379 (insert "\n"))
1380 (setq pad (- width 3))
1381 (setq format (concat "%c: %-" (int-to-string pad) "s"))
1382 (insert (format format (caar alist) (nth idx (car alist))))
1383 (setq alist (cdr alist))
1384 (setq i (1+ i))))))))
1385 (if (buffer-live-p buf)
1386 (kill-buffer buf))
1387 tchar))
1388
1389 (defun gnus-select-frame-set-input-focus (frame)
1390 "Select FRAME, raise it, and set input focus, if possible."
1391 (cond ((featurep 'xemacs)
1392 (raise-frame frame)
1393 (select-frame frame)
1394 (focus-frame frame))
1395 ;; The function `select-frame-set-input-focus' won't set
1396 ;; the input focus under Emacs 21.2 and X window system.
1397 ;;((fboundp 'select-frame-set-input-focus)
1398 ;; (defalias 'gnus-select-frame-set-input-focus
1399 ;; 'select-frame-set-input-focus)
1400 ;; (select-frame-set-input-focus frame))
1401 (t
1402 (raise-frame frame)
1403 (select-frame frame)
1404 (cond ((and (eq window-system 'x)
1405 (fboundp 'x-focus-frame))
1406 (x-focus-frame frame))
1407 ((eq window-system 'w32)
1408 (w32-focus-frame frame)))
1409 (when focus-follows-mouse
1410 (set-mouse-position frame (1- (frame-width frame)) 0)))))
1411
1412 (defun gnus-frame-or-window-display-name (object)
1413 "Given a frame or window, return the associated display name.
1414 Return nil otherwise."
1415 (if (featurep 'xemacs)
1416 (device-connection (dfw-device object))
1417 (if (or (framep object)
1418 (and (windowp object)
1419 (setq object (window-frame object))))
1420 (let ((display (frame-parameter object 'display)))
1421 (if (and (stringp display)
1422 ;; Exclude invalid display names.
1423 (string-match "\\`[^:]*:[0-9]+\\(\\.[0-9]+\\)?\\'"
1424 display))
1425 display)))))
1426
1427 ;; Fixme: This has only one use (in gnus-agent), which isn't worthwhile.
1428 (defmacro gnus-mapcar (function seq1 &rest seqs2_n)
1429 "Apply FUNCTION to each element of the sequences, and make a list of the results.
1430 If there are several sequences, FUNCTION is called with that many arguments,
1431 and mapping stops as soon as the shortest sequence runs out. With just one
1432 sequence, this is like `mapcar'. With several, it is like the Common Lisp
1433 `mapcar' function extended to arbitrary sequence types."
1434
1435 (if seqs2_n
1436 (let* ((seqs (cons seq1 seqs2_n))
1437 (cnt 0)
1438 (heads (mapcar (lambda (seq)
1439 (make-symbol (concat "head"
1440 (int-to-string
1441 (setq cnt (1+ cnt))))))
1442 seqs))
1443 (result (make-symbol "result"))
1444 (result-tail (make-symbol "result-tail")))
1445 `(let* ,(let* ((bindings (cons nil nil))
1446 (heads heads))
1447 (nconc bindings (list (list result '(cons nil nil))))
1448 (nconc bindings (list (list result-tail result)))
1449 (while heads
1450 (nconc bindings (list (list (pop heads) (pop seqs)))))
1451 (cdr bindings))
1452 (while (and ,@heads)
1453 (setcdr ,result-tail (cons (funcall ,function
1454 ,@(mapcar (lambda (h) (list 'car h))
1455 heads))
1456 nil))
1457 (setq ,result-tail (cdr ,result-tail)
1458 ,@(apply 'nconc (mapcar (lambda (h) (list h (list 'cdr h))) heads))))
1459 (cdr ,result)))
1460 `(mapcar ,function ,seq1)))
1461
1462 (if (fboundp 'merge)
1463 (defalias 'gnus-merge 'merge)
1464 ;; Adapted from cl-seq.el
1465 (defun gnus-merge (type list1 list2 pred)
1466 "Destructively merge lists LIST1 and LIST2 to produce a new list.
1467 Argument TYPE is for compatibility and ignored.
1468 Ordering of the elements is preserved according to PRED, a `less-than'
1469 predicate on the elements."
1470 (let ((res nil))
1471 (while (and list1 list2)
1472 (if (funcall pred (car list2) (car list1))
1473 (push (pop list2) res)
1474 (push (pop list1) res)))
1475 (nconc (nreverse res) list1 list2))))
1476
1477 (eval-when-compile
1478 (defvar xemacs-codename))
1479
1480 (defun gnus-emacs-version ()
1481 "Stringified Emacs version."
1482 (let ((system-v
1483 (cond
1484 ((eq gnus-user-agent 'emacs-gnus-config)
1485 system-configuration)
1486 ((eq gnus-user-agent 'emacs-gnus-type)
1487 (symbol-name system-type))
1488 (t nil))))
1489 (cond
1490 ((eq gnus-user-agent 'gnus)
1491 nil)
1492 ((string-match "^\\(\\([.0-9]+\\)*\\)\\.[0-9]+$" emacs-version)
1493 (concat "Emacs/" (match-string 1 emacs-version)
1494 (if system-v
1495 (concat " (" system-v ")")
1496 "")))
1497 ((string-match
1498 "\\([A-Z]*[Mm][Aa][Cc][Ss]\\)[^(]*\\(\\((beta.*)\\|'\\)\\)?"
1499 emacs-version)
1500 (concat
1501 (match-string 1 emacs-version)
1502 (format "/%d.%d" emacs-major-version emacs-minor-version)
1503 (if (match-beginning 3)
1504 (match-string 3 emacs-version)
1505 "")
1506 (if (boundp 'xemacs-codename)
1507 (concat
1508 " (" xemacs-codename
1509 (if system-v
1510 (concat ", " system-v ")")
1511 ")"))
1512 "")))
1513 (t emacs-version))))
1514
1011 (provide 'gnus-util) 1515 (provide 'gnus-util)
1012 1516
1013 ;;; arch-tag: f94991af-d32b-4c97-8c26-ca12a934de49 1517 ;;; arch-tag: f94991af-d32b-4c97-8c26-ca12a934de49
1014 ;;; gnus-util.el ends here 1518 ;;; gnus-util.el ends here