comparison lisp/gnus/gnus-util.el @ 88155:d7ddb3e565de

sync with trunk
author Henrik Enberg <henrik.enberg@telia.com>
date Mon, 16 Jan 2006 00:03:54 +0000
parents 52d99cc2e9e3
children
comparison
equal deleted inserted replaced
88154:8ce476d3ba36 88155:d7ddb3e565de
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
3 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
3 ;; Free Software Foundation, Inc. 4 ;; Free Software Foundation, Inc.
4 5
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org> 6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; Keywords: news 7 ;; Keywords: news
7 8
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details. 19 ;; GNU General Public License for more details.
19 20
20 ;; You should have received a copy of the GNU General Public License 21 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the 22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, 23 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 ;; Boston, MA 02111-1307, USA. 24 ;; Boston, MA 02110-1301, USA.
24 25
25 ;;; Commentary: 26 ;;; Commentary:
26 27
27 ;; Nothing in this file depends on any other parts of Gnus -- all 28 ;; Nothing in this file depends on any other parts of Gnus -- all
28 ;; functions and macros in this file are utility functions that are 29 ;; 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 30 ;; used by Gnus and may be used by any other package without loading
30 ;; Gnus first. 31 ;; Gnus first.
31 32
33 ;; [Unfortunately, it does depend on other parts of Gnus, e.g. the
34 ;; autoloads below...]
35
32 ;;; Code: 36 ;;; Code:
33 37
34 (require 'custom) 38 (require 'custom)
35 (eval-when-compile 39 (eval-when-compile
36 (require 'cl) 40 (require 'cl)
37 ;; Fixme: this should be a gnus variable, not nnmail-. 41 ;; Fixme: this should be a gnus variable, not nnmail-.
38 (defvar nnmail-pathname-coding-system)) 42 (defvar nnmail-pathname-coding-system)
39 (require 'nnheader) 43 (defvar nnmail-active-file-coding-system)
44
45 ;; Inappropriate references to other parts of Gnus.
46 (defvar gnus-emphasize-whitespace-regexp)
47 (defvar gnus-original-article-buffer)
48 (defvar gnus-user-agent)
49 )
40 (require 'time-date) 50 (require 'time-date)
51 (require 'netrc)
41 52
42 (eval-and-compile 53 (eval-and-compile
43 (autoload 'message-fetch-field "message") 54 (autoload 'message-fetch-field "message")
55 (autoload 'gnus-get-buffer-window "gnus-win")
44 (autoload 'rmail-insert-rmail-file-header "rmail") 56 (autoload 'rmail-insert-rmail-file-header "rmail")
45 (autoload 'rmail-count-new-messages "rmail") 57 (autoload 'rmail-count-new-messages "rmail")
46 (autoload 'rmail-show-message "rmail")) 58 (autoload 'rmail-show-message "rmail")
59 (autoload 'nnheader-narrow-to-headers "nnheader")
60 (autoload 'nnheader-replace-chars-in-string "nnheader"))
61
62 (eval-and-compile
63 (cond
64 ((fboundp 'replace-in-string)
65 (defalias 'gnus-replace-in-string 'replace-in-string))
66 ((fboundp 'replace-regexp-in-string)
67 (defun gnus-replace-in-string (string regexp newtext &optional literal)
68 "Replace all matches for REGEXP with NEWTEXT in STRING.
69 If LITERAL is non-nil, insert NEWTEXT literally. Return a new
70 string containing the replacements.
71
72 This is a compatibility function for different Emacsen."
73 (replace-regexp-in-string regexp newtext string nil literal)))
74 (t
75 (defun gnus-replace-in-string (string regexp newtext &optional literal)
76 "Replace all matches for REGEXP with NEWTEXT in STRING.
77 If LITERAL is non-nil, insert NEWTEXT literally. Return a new
78 string containing the replacements.
79
80 This is a compatibility function for different Emacsen."
81 (let ((start 0) tail)
82 (while (string-match regexp string start)
83 (setq tail (- (length string) (match-end 0)))
84 (setq string (replace-match newtext nil literal string))
85 (setq start (- (length string) tail))))
86 string))))
87
88 ;;; bring in the netrc functions as aliases
89 (defalias 'gnus-netrc-get 'netrc-get)
90 (defalias 'gnus-netrc-machine 'netrc-machine)
91 (defalias 'gnus-parse-netrc 'netrc-parse)
47 92
48 (defun gnus-boundp (variable) 93 (defun gnus-boundp (variable)
49 "Return non-nil if VARIABLE is bound and non-nil." 94 "Return non-nil if VARIABLE is bound and non-nil."
50 (and (boundp variable) 95 (and (boundp variable)
51 (symbol-value variable))) 96 (symbol-value variable)))
52 97
53 (defmacro gnus-eval-in-buffer-window (buffer &rest forms) 98 (defmacro gnus-eval-in-buffer-window (buffer &rest forms)
54 "Pop to BUFFER, evaluate FORMS, and then return to the original window." 99 "Pop to BUFFER, evaluate FORMS, and then return to the original window."
55 (let ((tempvar (make-symbol "GnusStartBufferWindow")) 100 (let ((tempvar (make-symbol "GnusStartBufferWindow"))
56 (w (make-symbol "w")) 101 (w (make-symbol "w"))
57 (buf (make-symbol "buf"))) 102 (buf (make-symbol "buf")))
58 `(let* ((,tempvar (selected-window)) 103 `(let* ((,tempvar (selected-window))
59 (,buf ,buffer) 104 (,buf ,buffer)
60 (,w (get-buffer-window ,buf 'visible))) 105 (,w (gnus-get-buffer-window ,buf 'visible)))
61 (unwind-protect 106 (unwind-protect
62 (progn 107 (progn
63 (if ,w 108 (if ,w
64 (progn 109 (progn
65 (select-window ,w) 110 (select-window ,w)
66 (set-buffer (window-buffer ,w))) 111 (set-buffer (window-buffer ,w)))
67 (pop-to-buffer ,buf)) 112 (pop-to-buffer ,buf))
68 ,@forms) 113 ,@forms)
69 (select-window ,tempvar))))) 114 (select-window ,tempvar)))))
70 115
71 (put 'gnus-eval-in-buffer-window 'lisp-indent-function 1) 116 (put 'gnus-eval-in-buffer-window 'lisp-indent-function 1)
72 (put 'gnus-eval-in-buffer-window 'edebug-form-spec '(form body)) 117 (put 'gnus-eval-in-buffer-window 'edebug-form-spec '(form body))
73 118
74 (defmacro gnus-intern-safe (string hashtable) 119 (defmacro gnus-intern-safe (string hashtable)
79 symbol)) 124 symbol))
80 125
81 ;; Added by Geoffrey T. Dairiki <dairiki@u.washington.edu>. A safe way 126 ;; 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 127 ;; to limit the length of a string. This function is necessary since
83 ;; `(substr "abc" 0 30)' pukes with "Args out of range". 128 ;; `(substr "abc" 0 30)' pukes with "Args out of range".
129 ;; Fixme: Why not `truncate-string-to-width'?
84 (defsubst gnus-limit-string (str width) 130 (defsubst gnus-limit-string (str width)
85 (if (> (length str) width) 131 (if (> (length str) width)
86 (substring str 0 width) 132 (substring str 0 width)
87 str)) 133 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 134
95 (defsubst gnus-goto-char (point) 135 (defsubst gnus-goto-char (point)
96 (and point (goto-char point))) 136 (and point (goto-char point)))
97 137
98 (defmacro gnus-buffer-exists-p (buffer) 138 (defmacro gnus-buffer-exists-p (buffer)
99 `(let ((buffer ,buffer)) 139 `(let ((buffer ,buffer))
100 (when buffer 140 (when buffer
101 (funcall (if (stringp buffer) 'get-buffer 'buffer-name) 141 (funcall (if (stringp buffer) 'get-buffer 'buffer-name)
102 buffer)))) 142 buffer))))
103 143
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 144 (defalias 'gnus-point-at-bol
110 (if (fboundp 'point-at-bol) 145 (if (fboundp 'point-at-bol)
111 'point-at-bol 146 'point-at-bol
112 'line-beginning-position)) 147 'line-beginning-position))
113 148
114 (defalias 'gnus-point-at-eol 149 (defalias 'gnus-point-at-eol
115 (if (fboundp 'point-at-eol) 150 (if (fboundp 'point-at-eol)
116 'point-at-eol 151 'point-at-eol
117 'line-end-position)) 152 'line-end-position))
153
154 ;; The LOCAL arg to `add-hook' is interpreted differently in Emacs and
155 ;; XEmacs. In Emacs we don't need to call `make-local-hook' first.
156 ;; It's harmless, though, so the main purpose of this alias is to shut
157 ;; up the byte compiler.
158 (defalias 'gnus-make-local-hook
159 (if (eq (get 'make-local-hook 'byte-compile)
160 'byte-compile-obsolete)
161 'ignore ; Emacs
162 'make-local-hook)) ; XEmacs
118 163
119 (defun gnus-delete-first (elt list) 164 (defun gnus-delete-first (elt list)
120 "Delete by side effect the first occurrence of ELT as a member of LIST." 165 "Delete by side effect the first occurrence of ELT as a member of LIST."
121 (if (equal (car list) elt) 166 (if (equal (car list) elt)
122 (cdr list) 167 (cdr list)
128 (setcdr list (cddr list))) 173 (setcdr list (cddr list)))
129 total))) 174 total)))
130 175
131 ;; Delete the current line (and the next N lines). 176 ;; Delete the current line (and the next N lines).
132 (defmacro gnus-delete-line (&optional n) 177 (defmacro gnus-delete-line (&optional n)
133 `(delete-region (progn (beginning-of-line) (point)) 178 `(delete-region (gnus-point-at-bol)
134 (progn (forward-line ,(or n 1)) (point)))) 179 (progn (forward-line ,(or n 1)) (point))))
135 180
136 (defun gnus-byte-code (func) 181 (defun gnus-byte-code (func)
137 "Return a form that can be `eval'ed based on FUNC." 182 "Return a form that can be `eval'ed based on FUNC."
138 (let ((fval (indirect-function func))) 183 (let ((fval (indirect-function func)))
141 (setcar flist 'byte-code) 186 (setcar flist 'byte-code)
142 flist) 187 flist)
143 (cons 'progn (cddr fval))))) 188 (cons 'progn (cddr fval)))))
144 189
145 (defun gnus-extract-address-components (from) 190 (defun gnus-extract-address-components (from)
191 "Extract address components from a From header.
192 Given an RFC-822 address FROM, extract full name and canonical address.
193 Returns a list of the form (FULL-NAME CANONICAL-ADDRESS). Much more simple
194 solution than `mail-extract-address-components', which works much better, but
195 is slower."
146 (let (name address) 196 (let (name address)
147 ;; First find the address - the thing with the @ in it. This may 197 ;; 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 198 ;; not be accurate in mail addresses, but does the trick most of
149 ;; the time in news messages. 199 ;; the time in news messages.
150 (when (string-match "\\b[^@ \t<>]+[!@][^@ \t<>]+\\b" from) 200 (when (string-match "\\b[^@ \t<>]+[!@][^@ \t<>]+\\b" from)
153 (and address 203 (and address
154 ;; Linear white space is not required. 204 ;; Linear white space is not required.
155 (string-match (concat "[ \t]*<" (regexp-quote address) ">") from) 205 (string-match (concat "[ \t]*<" (regexp-quote address) ">") from)
156 (and (setq name (substring from 0 (match-beginning 0))) 206 (and (setq name (substring from 0 (match-beginning 0)))
157 ;; Strip any quotes from the name. 207 ;; Strip any quotes from the name.
158 (string-match "\".*\"" name) 208 (string-match "^\".*\"$" name)
159 (setq name (substring name 1 (1- (match-end 0)))))) 209 (setq name (substring name 1 (1- (match-end 0))))))
160 ;; If not, then "address (name)" is used. 210 ;; If not, then "address (name)" is used.
161 (or name 211 (or name
162 (and (string-match "(.+)" from) 212 (and (string-match "(.+)" from)
163 (setq name (substring from (1+ (match-beginning 0)) 213 (setq name (substring from (1+ (match-beginning 0))
178 (let ((case-fold-search t) 228 (let ((case-fold-search t)
179 (inhibit-point-motion-hooks t)) 229 (inhibit-point-motion-hooks t))
180 (nnheader-narrow-to-headers) 230 (nnheader-narrow-to-headers)
181 (message-fetch-field field))))) 231 (message-fetch-field field)))))
182 232
233 (defun gnus-fetch-original-field (field)
234 "Fetch FIELD from the original version of the current article."
235 (with-current-buffer gnus-original-article-buffer
236 (gnus-fetch-field field)))
237
238
183 (defun gnus-goto-colon () 239 (defun gnus-goto-colon ()
184 (beginning-of-line) 240 (beginning-of-line)
185 (search-forward ":" (gnus-point-at-eol) t)) 241 (let ((eol (gnus-point-at-eol)))
242 (goto-char (or (text-property-any (point) eol 'gnus-position t)
243 (search-forward ":" eol t)
244 (point)))))
245
246 (defun gnus-decode-newsgroups (newsgroups group &optional method)
247 (let ((method (or method (gnus-find-method-for-group group))))
248 (mapconcat (lambda (group)
249 (gnus-group-name-decode group (gnus-group-name-charset
250 method group)))
251 (message-tokenize-header newsgroups)
252 ",")))
186 253
187 (defun gnus-remove-text-with-property (prop) 254 (defun gnus-remove-text-with-property (prop)
188 "Delete all text in the current buffer with text property PROP." 255 "Delete all text in the current buffer with text property PROP."
189 (save-excursion 256 (save-excursion
190 (goto-char (point-min)) 257 (goto-char (point-min))
193 (delete-char 1)) 260 (delete-char 1))
194 (goto-char (next-single-property-change (point) prop nil (point-max)))))) 261 (goto-char (next-single-property-change (point) prop nil (point-max))))))
195 262
196 (defun gnus-newsgroup-directory-form (newsgroup) 263 (defun gnus-newsgroup-directory-form (newsgroup)
197 "Make hierarchical directory name from NEWSGROUP name." 264 "Make hierarchical directory name from NEWSGROUP name."
198 (let ((newsgroup (gnus-newsgroup-savable-name newsgroup)) 265 (let* ((newsgroup (gnus-newsgroup-savable-name newsgroup))
199 (len (length newsgroup)) 266 (idx (string-match ":" newsgroup)))
200 idx) 267 (concat
201 ;; If this is a foreign group, we don't want to translate the 268 (if idx (substring newsgroup 0 idx))
202 ;; entire name. 269 (if idx "/")
203 (if (setq idx (string-match ":" newsgroup)) 270 (nnheader-replace-chars-in-string
204 (aset newsgroup idx ?/) 271 (if idx (substring newsgroup (1+ idx)) newsgroup)
205 (setq idx 0)) 272 ?. ?/))))
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 273
213 (defun gnus-newsgroup-savable-name (group) 274 (defun gnus-newsgroup-savable-name (group)
214 ;; Replace any slashes in a group name (eg. an ange-ftp nndoc group) 275 ;; Replace any slashes in a group name (eg. an ange-ftp nndoc group)
215 ;; with dots. 276 ;; with dots.
216 (nnheader-replace-chars-in-string group ?/ ?.)) 277 (nnheader-replace-chars-in-string group ?/ ?.))
269 (if (or (not safe) 330 (if (or (not safe)
270 (eq (lookup-key keymap key) 'undefined)) 331 (eq (lookup-key keymap key) 'undefined))
271 (define-key keymap key (pop plist)) 332 (define-key keymap key (pop plist))
272 (pop plist))))) 333 (pop plist)))))
273 334
274 (defun gnus-completing-read (default prompt &rest args) 335 (defun gnus-completing-read-with-default (default prompt &rest args)
275 ;; Like `completing-read', except that DEFAULT is the default argument. 336 ;; Like `completing-read', except that DEFAULT is the default argument.
276 (let* ((prompt (if default 337 (let* ((prompt (if default
277 (concat prompt " (default " default ") ") 338 (concat prompt " (default " default "): ")
278 (concat prompt " "))) 339 (concat prompt ": ")))
279 (answer (apply 'completing-read prompt args))) 340 (answer (apply 'completing-read prompt args)))
280 (if (or (null answer) (zerop (length answer))) 341 (if (or (null answer) (zerop (length answer)))
281 default 342 default
282 answer))) 343 answer)))
283 344
290 351
291 (defun gnus-yes-or-no-p (prompt) 352 (defun gnus-yes-or-no-p (prompt)
292 (prog1 353 (prog1
293 (yes-or-no-p prompt) 354 (yes-or-no-p prompt)
294 (message ""))) 355 (message "")))
356
357 ;; By Frank Schmitt <ich@Frank-Schmitt.net>. Allows to have
358 ;; age-depending date representations. (e.g. just the time if it's
359 ;; from today, the day of the week if it's within the last 7 days and
360 ;; the full date if it's older)
361
362 (defun gnus-seconds-today ()
363 "Return the number of seconds passed today."
364 (let ((now (decode-time (current-time))))
365 (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600))))
366
367 (defun gnus-seconds-month ()
368 "Return the number of seconds passed this month."
369 (let ((now (decode-time (current-time))))
370 (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600)
371 (* (- (car (nthcdr 3 now)) 1) 3600 24))))
372
373 (defun gnus-seconds-year ()
374 "Return the number of seconds passed this year."
375 (let ((now (decode-time (current-time)))
376 (days (format-time-string "%j" (current-time))))
377 (+ (car now) (* (car (cdr now)) 60) (* (car (nthcdr 2 now)) 3600)
378 (* (- (string-to-number days) 1) 3600 24))))
379
380 (defvar gnus-user-date-format-alist
381 '(((gnus-seconds-today) . "%k:%M")
382 (604800 . "%a %k:%M") ;;that's one week
383 ((gnus-seconds-month) . "%a %d")
384 ((gnus-seconds-year) . "%b %d")
385 (t . "%b %d '%y")) ;;this one is used when no
386 ;;other does match
387 "Specifies date format depending on age of article.
388 This is an alist of items (AGE . FORMAT). AGE can be a number (of
389 seconds) or a Lisp expression evaluating to a number. When the age of
390 the article is less than this number, then use `format-time-string'
391 with the corresponding FORMAT for displaying the date of the article.
392 If AGE is not a number or a Lisp expression evaluating to a
393 non-number, then the corresponding FORMAT is used as a default value.
394
395 Note that the list is processed from the beginning, so it should be
396 sorted by ascending AGE. Also note that items following the first
397 non-number AGE will be ignored.
398
399 You can use the functions `gnus-seconds-today', `gnus-seconds-month'
400 and `gnus-seconds-year' in the AGE spec. They return the number of
401 seconds passed since the start of today, of this month, of this year,
402 respectively.")
403
404 (defun gnus-user-date (messy-date)
405 "Format the messy-date according to gnus-user-date-format-alist.
406 Returns \" ? \" if there's bad input or if an other error occurs.
407 Input should look like this: \"Sun, 14 Oct 2001 13:34:39 +0200\"."
408 (condition-case ()
409 (let* ((messy-date (time-to-seconds (safe-date-to-time messy-date)))
410 (now (time-to-seconds (current-time)))
411 ;;If we don't find something suitable we'll use this one
412 (my-format "%b %d '%y"))
413 (let* ((difference (- now messy-date))
414 (templist gnus-user-date-format-alist)
415 (top (eval (caar templist))))
416 (while (if (numberp top) (< top difference) (not top))
417 (progn
418 (setq templist (cdr templist))
419 (setq top (eval (caar templist)))))
420 (if (stringp (cdr (car templist)))
421 (setq my-format (cdr (car templist)))))
422 (format-time-string (eval my-format) (seconds-to-time messy-date)))
423 (error " ? ")))
295 424
296 (defun gnus-dd-mmm (messy-date) 425 (defun gnus-dd-mmm (messy-date)
297 "Return a string like DD-MMM from a big messy string." 426 "Return a string like DD-MMM from a big messy string."
298 (condition-case () 427 (condition-case ()
299 (format-time-string "%d-%b" (safe-date-to-time messy-date)) 428 (format-time-string "%d-%b" (safe-date-to-time messy-date))
323 (gnus-time-iso8601 (gnus-date-get-time date)) 452 (gnus-time-iso8601 (gnus-date-get-time date))
324 (error ""))) 453 (error "")))
325 454
326 (defun gnus-mode-string-quote (string) 455 (defun gnus-mode-string-quote (string)
327 "Quote all \"%\"'s in STRING." 456 "Quote all \"%\"'s in STRING."
328 (save-excursion 457 (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 458
336 ;; Make a hash table (default and minimum size is 256). 459 ;; Make a hash table (default and minimum size is 256).
337 ;; Optional argument HASHSIZE specifies the table size. 460 ;; Optional argument HASHSIZE specifies the table size.
338 (defun gnus-make-hashtable (&optional hashsize) 461 (defun gnus-make-hashtable (&optional hashsize)
339 (make-vector (if hashsize (max (gnus-create-hash-size hashsize) 256) 256) 0)) 462 (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 480 display most important messages; and at ten, Gnus will keep on
358 jabbering all the time." 481 jabbering all the time."
359 :group 'gnus-start 482 :group 'gnus-start
360 :type 'integer) 483 :type 'integer)
361 484
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) 485 (defun gnus-message (level &rest args)
486 "If LEVEL is lower than `gnus-verbose' print ARGS using `message'.
487
488 Guideline for numbers:
489 1 - error messages, 3 - non-serious error messages, 5 - messages for things
490 that take a long time, 7 - not very important messages on stuff, 9 - messages
491 inside loops."
368 (if (<= level gnus-verbose) 492 (if (<= level gnus-verbose)
369 (apply 'message args) 493 (apply 'message args)
370 ;; We have to do this format thingy here even if the result isn't 494 ;; 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 495 ;; shown - the return value has to be the same as the return value
372 ;; from `message'. 496 ;; from `message'.
385 509
386 (defun gnus-split-references (references) 510 (defun gnus-split-references (references)
387 "Return a list of Message-IDs in REFERENCES." 511 "Return a list of Message-IDs in REFERENCES."
388 (let ((beg 0) 512 (let ((beg 0)
389 ids) 513 ids)
390 (while (string-match "<[^>]+>" references beg) 514 (while (string-match "<[^<]+[^< \t]" references beg)
391 (push (substring references (match-beginning 0) (setq beg (match-end 0))) 515 (push (substring references (match-beginning 0) (setq beg (match-end 0)))
392 ids)) 516 ids))
393 (nreverse ids))) 517 (nreverse ids)))
394 518
395 (defsubst gnus-parent-id (references &optional n) 519 (defsubst gnus-parent-id (references &optional n)
396 "Return the last Message-ID in REFERENCES. 520 "Return the last Message-ID in REFERENCES.
397 If N, return the Nth ancestor instead." 521 If N, return the Nth ancestor instead."
398 (when references 522 (when (and references
399 (let ((ids (inline (gnus-split-references references)))) 523 (not (zerop (length references))))
400 (while (nthcdr (or n 1) ids) 524 (if n
401 (setq ids (cdr ids))) 525 (let ((ids (inline (gnus-split-references references))))
402 (car ids)))) 526 (while (nthcdr n ids)
403 527 (setq ids (cdr ids)))
404 (defsubst gnus-buffer-live-p (buffer) 528 (car ids))
529 (when (string-match "\\(<[^<]+>\\)[ \t]*\\'" references)
530 (match-string 1 references)))))
531
532 (defun gnus-buffer-live-p (buffer)
405 "Say whether BUFFER is alive or not." 533 "Say whether BUFFER is alive or not."
406 (and buffer 534 (and buffer
407 (get-buffer buffer) 535 (get-buffer buffer)
408 (buffer-name (get-buffer buffer)))) 536 (buffer-name (get-buffer buffer))))
409 537
410 (defun gnus-horizontal-recenter () 538 (defun gnus-horizontal-recenter ()
411 "Recenter the current buffer horizontally." 539 "Recenter the current buffer horizontally."
412 (if (< (current-column) (/ (window-width) 2)) 540 (if (< (current-column) (/ (window-width) 2))
413 (set-window-hscroll (get-buffer-window (current-buffer) t) 0) 541 (set-window-hscroll (gnus-get-buffer-window (current-buffer) t) 0)
414 (let* ((orig (point)) 542 (let* ((orig (point))
415 (end (window-end (get-buffer-window (current-buffer) t))) 543 (end (window-end (gnus-get-buffer-window (current-buffer) t)))
416 (max 0)) 544 (max 0))
417 (when end 545 (when end
418 ;; Find the longest line currently displayed in the window. 546 ;; Find the longest line currently displayed in the window.
419 (goto-char (window-start)) 547 (goto-char (window-start))
420 (while (and (not (eobp)) 548 (while (and (not (eobp))
424 (forward-line 1)) 552 (forward-line 1))
425 (goto-char orig) 553 (goto-char orig)
426 ;; Scroll horizontally to center (sort of) the point. 554 ;; Scroll horizontally to center (sort of) the point.
427 (if (> max (window-width)) 555 (if (> max (window-width))
428 (set-window-hscroll 556 (set-window-hscroll
429 (get-buffer-window (current-buffer) t) 557 (gnus-get-buffer-window (current-buffer) t)
430 (min (- (current-column) (/ (window-width) 3)) 558 (min (- (current-column) (/ (window-width) 3))
431 (+ 2 (- max (window-width))))) 559 (+ 2 (- max (window-width)))))
432 (set-window-hscroll (get-buffer-window (current-buffer) t) 0)) 560 (set-window-hscroll (gnus-get-buffer-window (current-buffer) t) 0))
433 max)))) 561 max))))
434 562
435 (defun gnus-read-event-char () 563 (defun gnus-read-event-char (&optional prompt)
436 "Get the next event." 564 "Get the next event."
437 (let ((event (read-event))) 565 (let ((event (read-event prompt)))
438 ;; should be gnus-characterp, but this can't be called in XEmacs anyway 566 ;; should be gnus-characterp, but this can't be called in XEmacs anyway
439 (cons (and (numberp event) event) event))) 567 (cons (and (numberp event) event) event)))
440 568
441 (defun gnus-sortable-date (date) 569 (defun gnus-sortable-date (date)
442 "Make string suitable for sorting from DATE." 570 "Make string suitable for sorting from DATE."
472 (if (string-match "^[^:]+:" gname) 600 (if (string-match "^[^:]+:" gname)
473 (substring gname (match-end 0)) 601 (substring gname (match-end 0))
474 gname))) 602 gname)))
475 603
476 (defun gnus-make-sort-function (funs) 604 (defun gnus-make-sort-function (funs)
477 "Return a composite sort condition based on the functions in FUNC." 605 "Return a composite sort condition based on the functions in FUNS."
478 (cond 606 (cond
479 ;; Just a simple function. 607 ;; Just a simple function.
480 ((gnus-functionp funs) funs) 608 ((functionp funs) funs)
481 ;; No functions at all. 609 ;; No functions at all.
482 ((null funs) funs) 610 ((null funs) funs)
483 ;; A list of functions. 611 ;; A list of functions.
484 ((or (cdr funs) 612 ((or (cdr funs)
485 (listp (car funs))) 613 (listp (car funs)))
486 `(lambda (t1 t2) 614 (gnus-byte-compile
487 ,(gnus-make-sort-function-1 (reverse funs)))) 615 `(lambda (t1 t2)
616 ,(gnus-make-sort-function-1 (reverse funs)))))
488 ;; A list containing just one function. 617 ;; A list containing just one function.
489 (t 618 (t
490 (car funs)))) 619 (car funs))))
491 620
492 (defun gnus-make-sort-function-1 (funs) 621 (defun gnus-make-sort-function-1 (funs)
493 "Return a composite sort condition based on the functions in FUNC." 622 "Return a composite sort condition based on the functions in FUNS."
494 (let ((function (car funs)) 623 (let ((function (car funs))
495 (first 't1) 624 (first 't1)
496 (last 't2)) 625 (last 't2))
497 (when (consp function) 626 (when (consp function)
498 (cond 627 (cond
499 ;; Reversed spec. 628 ;; Reversed spec.
500 ((eq (car function) 'not) 629 ((eq (car function) 'not)
501 (setq function (cadr function) 630 (setq function (cadr function)
502 first 't2 631 first 't2
503 last 't1)) 632 last 't1))
504 ((gnus-functionp function) 633 ((functionp function)
505 ;; Do nothing. 634 ;; Do nothing.
506 ) 635 )
507 (t 636 (t
508 (error "Invalid sort spec: %s" function)))) 637 (error "Invalid sort spec: %s" function))))
509 (if (cdr funs) 638 (if (cdr funs)
515 (defun gnus-turn-off-edit-menu (type) 644 (defun gnus-turn-off-edit-menu (type)
516 "Turn off edit menu in `gnus-TYPE-mode-map'." 645 "Turn off edit menu in `gnus-TYPE-mode-map'."
517 (define-key (symbol-value (intern (format "gnus-%s-mode-map" type))) 646 (define-key (symbol-value (intern (format "gnus-%s-mode-map" type)))
518 [menu-bar edit] 'undefined)) 647 [menu-bar edit] 'undefined))
519 648
649 (defmacro gnus-bind-print-variables (&rest forms)
650 "Bind print-* variables and evaluate FORMS.
651 This macro is used with `prin1', `pp', etc. in order to ensure printed
652 Lisp objects are loadable. Bind `print-quoted' and `print-readably'
653 to t, and `print-escape-multibyte', `print-escape-newlines',
654 `print-escape-nonascii', `print-length', `print-level' and
655 `print-string-length' to nil."
656 `(let ((print-quoted t)
657 (print-readably t)
658 ;;print-circle
659 ;;print-continuous-numbering
660 print-escape-multibyte
661 print-escape-newlines
662 print-escape-nonascii
663 ;;print-gensym
664 print-length
665 print-level
666 print-string-length)
667 ,@forms))
668
520 (defun gnus-prin1 (form) 669 (defun gnus-prin1 (form)
521 "Use `prin1' on FORM in the current buffer. 670 "Use `prin1' on FORM in the current buffer.
522 Bind `print-quoted' and `print-readably' to t while printing." 671 Bind `print-quoted' and `print-readably' to t, and `print-length' and
523 (let ((print-quoted t) 672 `print-level' to nil. See also `gnus-bind-print-variables'."
524 (print-readably t) 673 (gnus-bind-print-variables (prin1 form (current-buffer))))
525 (print-escape-multibyte nil)
526 print-level print-length)
527 (prin1 form (current-buffer))))
528 674
529 (defun gnus-prin1-to-string (form) 675 (defun gnus-prin1-to-string (form)
530 "The same as `prin1', but bind `print-quoted' and `print-readably' to t." 676 "The same as `prin1'.
531 (let ((print-quoted t) 677 Bind `print-quoted' and `print-readably' to t, and `print-length' and
532 (print-readably t)) 678 `print-level' to nil. See also `gnus-bind-print-variables'."
533 (prin1-to-string form))) 679 (gnus-bind-print-variables (prin1-to-string form)))
680
681 (defun gnus-pp (form)
682 "Use `pp' on FORM in the current buffer.
683 Bind `print-quoted' and `print-readably' to t, and `print-length' and
684 `print-level' to nil. See also `gnus-bind-print-variables'."
685 (gnus-bind-print-variables (pp form (current-buffer))))
686
687 (defun gnus-pp-to-string (form)
688 "The same as `pp-to-string'.
689 Bind `print-quoted' and `print-readably' to t, and `print-length' and
690 `print-level' to nil. See also `gnus-bind-print-variables'."
691 (gnus-bind-print-variables (pp-to-string form)))
534 692
535 (defun gnus-make-directory (directory) 693 (defun gnus-make-directory (directory)
536 "Make DIRECTORY (and all its parents) if it doesn't exist." 694 "Make DIRECTORY (and all its parents) if it doesn't exist."
537 (require 'nnmail) 695 (require 'nnmail)
538 (let ((file-name-coding-system nnmail-pathname-coding-system)) 696 (let ((file-name-coding-system nnmail-pathname-coding-system))
551 709
552 (defun gnus-delete-file (file) 710 (defun gnus-delete-file (file)
553 "Delete FILE if it exists." 711 "Delete FILE if it exists."
554 (when (file-exists-p file) 712 (when (file-exists-p file)
555 (delete-file file))) 713 (delete-file file)))
714
715 (defun gnus-delete-directory (directory)
716 "Delete files in DIRECTORY. Subdirectories remain.
717 If there's no subdirectory, delete DIRECTORY as well."
718 (when (file-directory-p directory)
719 (let ((files (directory-files
720 directory t "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*"))
721 file dir)
722 (while files
723 (setq file (pop files))
724 (if (eq t (car (file-attributes file)))
725 ;; `file' is a subdirectory.
726 (setq dir t)
727 ;; `file' is a file or a symlink.
728 (delete-file file)))
729 (unless dir
730 (delete-directory directory)))))
556 731
557 (defun gnus-strip-whitespace (string) 732 (defun gnus-strip-whitespace (string)
558 "Return STRING stripped of all whitespace." 733 "Return STRING stripped of all whitespace."
559 (while (string-match "[\r\n\t ]+" string) 734 (while (string-match "[\r\n\t ]+" string)
560 (setq string (replace-match "" t t string))) 735 (setq string (replace-match "" t t string)))
569 (while (re-search-forward gnus-emphasize-whitespace-regexp end 'move) 744 (while (re-search-forward gnus-emphasize-whitespace-regexp end 'move)
570 (gnus-put-text-property beg (match-beginning 0) prop val) 745 (gnus-put-text-property beg (match-beginning 0) prop val)
571 (setq beg (point))) 746 (setq beg (point)))
572 (gnus-put-text-property beg (point) prop val))))) 747 (gnus-put-text-property beg (point) prop val)))))
573 748
749 (defsubst gnus-put-overlay-excluding-newlines (beg end prop val)
750 "The same as `put-text-property', but don't put this prop on any newlines in the region."
751 (save-match-data
752 (save-excursion
753 (save-restriction
754 (goto-char beg)
755 (while (re-search-forward gnus-emphasize-whitespace-regexp end 'move)
756 (gnus-overlay-put
757 (gnus-make-overlay beg (match-beginning 0))
758 prop val)
759 (setq beg (point)))
760 (gnus-overlay-put (gnus-make-overlay beg (point)) prop val)))))
761
574 (defun gnus-put-text-property-excluding-characters-with-faces (beg end 762 (defun gnus-put-text-property-excluding-characters-with-faces (beg end
575 prop val) 763 prop val)
576 "The same as `put-text-property', but don't put props on characters with the `gnus-face' property." 764 "The same as `put-text-property', but don't put props on characters with the `gnus-face' property."
577 (let ((b beg)) 765 (let ((b beg))
578 (while (/= b end) 766 (while (/= b end)
579 (when (get-text-property b 'gnus-face) 767 (when (get-text-property b 'gnus-face)
580 (setq b (next-single-property-change b 'gnus-face nil end))) 768 (setq b (next-single-property-change b 'gnus-face nil end)))
581 (when (/= b end) 769 (when (/= b end)
582 (gnus-put-text-property 770 (inline
583 b (setq b (next-single-property-change b 'gnus-face nil end)) 771 (gnus-put-text-property
584 prop val))))) 772 b (setq b (next-single-property-change b 'gnus-face nil end))
773 prop val))))))
774
775 (defmacro gnus-faces-at (position)
776 "Return a list of faces at POSITION."
777 (if (featurep 'xemacs)
778 `(let ((pos ,position))
779 (mapcar-extents 'extent-face
780 nil (current-buffer) pos pos nil 'face))
781 `(let ((pos ,position))
782 (delq nil (cons (get-text-property pos 'face)
783 (mapcar
784 (lambda (overlay)
785 (overlay-get overlay 'face))
786 (overlays-at pos)))))))
585 787
586 ;;; Protected and atomic operations. dmoore@ucsd.edu 21.11.1996 788 ;;; Protected and atomic operations. dmoore@ucsd.edu 21.11.1996
587 ;;; The primary idea here is to try to protect internal datastructures 789 ;;; 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 790 ;;; 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 791 ;;; similar blows up. Often in Gnus multiple tables/lists need to be
658 ;(put 'gnus-atomic-setq 'edebug-form-spec '(body)) 860 ;(put 'gnus-atomic-setq 'edebug-form-spec '(body))
659 861
660 862
661 ;;; Functions for saving to babyl/mail files. 863 ;;; Functions for saving to babyl/mail files.
662 864
663 (defvar rmail-default-rmail-file) 865 (eval-when-compile
866 (condition-case nil
867 (progn
868 (require 'rmail)
869 (autoload 'rmail-update-summary "rmailsum"))
870 (error
871 (define-compiler-macro rmail-select-summary (&rest body)
872 ;; Rmail of the XEmacs version is supplied by the package, and
873 ;; requires tm and apel packages. However, there may be those
874 ;; who haven't installed those packages. This macro helps such
875 ;; people even if they install those packages later.
876 `(eval '(rmail-select-summary ,@body)))
877 ;; If there's rmail but there's no tm (or there's apel of the
878 ;; mainstream, not the XEmacs version), loading rmail of the XEmacs
879 ;; version fails halfway, however it provides the rmail-select-summary
880 ;; macro which uses the following functions:
881 (autoload 'rmail-summary-displayed "rmail")
882 (autoload 'rmail-maybe-display-summary "rmail")))
883 (defvar rmail-default-rmail-file)
884 (defvar mm-text-coding-system))
885
664 (defun gnus-output-to-rmail (filename &optional ask) 886 (defun gnus-output-to-rmail (filename &optional ask)
665 "Append the current article to an Rmail file named FILENAME." 887 "Append the current article to an Rmail file named FILENAME."
666 (require 'rmail) 888 (require 'rmail)
889 (require 'mm-util)
667 ;; Most of these codes are borrowed from rmailout.el. 890 ;; Most of these codes are borrowed from rmailout.el.
668 (setq filename (expand-file-name filename)) 891 (setq filename (expand-file-name filename))
669 (setq rmail-default-rmail-file filename) 892 (setq rmail-default-rmail-file filename)
670 (let ((artbuf (current-buffer)) 893 (let ((artbuf (current-buffer))
671 (tmpbuf (get-buffer-create " *Gnus-output*"))) 894 (tmpbuf (get-buffer-create " *Gnus-output*")))
704 (narrow-to-region (point-max) (point-max))) 927 (narrow-to-region (point-max) (point-max)))
705 (insert-buffer-substring tmpbuf) 928 (insert-buffer-substring tmpbuf)
706 (when msg 929 (when msg
707 (goto-char (point-min)) 930 (goto-char (point-min))
708 (widen) 931 (widen)
709 (search-backward "\n\^_") 932 (search-backward "\n\^_")
710 (narrow-to-region (point) (point-max)) 933 (narrow-to-region (point) (point-max))
711 (rmail-count-new-messages t) 934 (rmail-count-new-messages t)
712 (when (rmail-summary-exists) 935 (when (rmail-summary-exists)
713 (rmail-select-summary 936 (rmail-select-summary
714 (rmail-update-summary))) 937 (rmail-update-summary)))
715 (rmail-count-new-messages t) 938 (rmail-count-new-messages t)
716 (rmail-show-message msg)) 939 (rmail-show-message msg))
717 (save-buffer))))) 940 (save-buffer)))))
783 (replace-match "\n^_" t t)) ;2 chars: "^" and "_" 1006 (replace-match "\n^_" t t)) ;2 chars: "^" and "_"
784 (goto-char (point-max)) 1007 (goto-char (point-max))
785 (insert "\^_"))) 1008 (insert "\^_")))
786 1009
787 (defun gnus-map-function (funs arg) 1010 (defun gnus-map-function (funs arg)
788 "Applies the result of the first function in FUNS to the second, and so on. 1011 "Apply the result of the first function in FUNS to the second, and so on.
789 ARG is passed to the first function." 1012 ARG is passed to the first function."
790 (let ((myfuns funs)) 1013 (while funs
791 (while myfuns 1014 (setq arg (funcall (pop funs) arg)))
792 (setq arg (funcall (pop myfuns) arg))) 1015 arg)
793 arg))
794 1016
795 (defun gnus-run-hooks (&rest funcs) 1017 (defun gnus-run-hooks (&rest funcs)
796 "Does the same as `run-hooks', but saves excursion." 1018 "Does the same as `run-hooks', but saves the current buffer."
797 (let ((buf (current-buffer))) 1019 (save-current-buffer
798 (unwind-protect 1020 (apply 'run-hooks funcs)))
799 (apply 'run-hooks funcs) 1021
800 (set-buffer buf)))) 1022 (defun gnus-run-mode-hooks (&rest funcs)
801 1023 "Run `run-mode-hooks' if it is available, otherwise `run-hooks'.
802 ;;; 1024 This function saves the current buffer."
803 ;;; .netrc and .authinforc parsing 1025 (if (fboundp 'run-mode-hooks)
804 ;;; 1026 (save-current-buffer (apply 'run-mode-hooks funcs))
805 1027 (save-current-buffer (apply 'run-hooks funcs))))
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 1028
889 ;;; Various 1029 ;;; Various
890 1030
891 (defvar gnus-group-buffer) ; Compiler directive 1031 (defvar gnus-group-buffer) ; Compiler directive
892 (defun gnus-alive-p () 1032 (defun gnus-alive-p ()
895 (get-buffer gnus-group-buffer) 1035 (get-buffer gnus-group-buffer)
896 (save-excursion 1036 (save-excursion
897 (set-buffer gnus-group-buffer) 1037 (set-buffer gnus-group-buffer)
898 (eq major-mode 'gnus-group-mode)))) 1038 (eq major-mode 'gnus-group-mode))))
899 1039
900 (defun gnus-remove-duplicates (list) 1040 (defun gnus-remove-if (predicate list)
901 (let (new (tail list)) 1041 "Return a copy of LIST with all items satisfying PREDICATE removed."
902 (while tail
903 (or (member (car tail) new)
904 (setq new (cons (car tail) new)))
905 (setq tail (cdr tail)))
906 (nreverse new)))
907
908 (defun gnus-delete-if (predicate list)
909 "Delete elements from LIST that satisfy PREDICATE."
910 (let (out) 1042 (let (out)
911 (while list 1043 (while list
912 (unless (funcall predicate (car list)) 1044 (unless (funcall predicate (car list))
913 (push (car list) out)) 1045 (push (car list) out))
914 (pop list)) 1046 (setq list (cdr list)))
915 (nreverse out))) 1047 (nreverse out)))
916 1048
917 (defun gnus-delete-alist (key alist) 1049 (if (fboundp 'assq-delete-all)
918 "Delete all entries in ALIST that have a key eq to KEY." 1050 (defalias 'gnus-delete-alist 'assq-delete-all)
919 (let (entry) 1051 (defun gnus-delete-alist (key alist)
920 (while (setq entry (assq key alist)) 1052 "Delete from ALIST all elements whose car is KEY.
921 (setq alist (delq entry alist))) 1053 Return the modified alist."
922 alist)) 1054 (let (entry)
1055 (while (setq entry (assq key alist))
1056 (setq alist (delq entry alist)))
1057 alist)))
923 1058
924 (defmacro gnus-pull (key alist &optional assoc-p) 1059 (defmacro gnus-pull (key alist &optional assoc-p)
925 "Modify ALIST to be without KEY." 1060 "Modify ALIST to be without KEY."
926 (unless (symbolp alist) 1061 (unless (symbolp alist)
927 (error "Not a symbol: %s" alist)) 1062 (error "Not a symbol: %s" alist))
928 (let ((fun (if assoc-p 'assoc 'assq))) 1063 (let ((fun (if assoc-p 'assoc 'assq)))
929 `(setq ,alist (delq (,fun ,key ,alist) ,alist)))) 1064 `(setq ,alist (delq (,fun ,key ,alist) ,alist))))
930 1065
931 (defun gnus-globalify-regexp (re) 1066 (defun gnus-globalify-regexp (re)
932 "Returns a regexp that matches a whole line, iff RE matches a part of it." 1067 "Return a regexp that matches a whole line, iff RE matches a part of it."
933 (concat (unless (string-match "^\\^" re) "^.*") 1068 (concat (unless (string-match "^\\^" re) "^.*")
934 re 1069 re
935 (unless (string-match "\\$$" re) ".*$"))) 1070 (unless (string-match "\\$$" re) ".*$")))
936 1071
937 (defun gnus-set-window-start (&optional point) 1072 (defun gnus-set-window-start (&optional point)
938 "Set the window start to POINT, or (point) if nil." 1073 "Set the window start to POINT, or (point) if nil."
939 (let ((win (get-buffer-window (current-buffer) t))) 1074 (let ((win (gnus-get-buffer-window (current-buffer) t)))
940 (when win 1075 (when win
941 (set-window-start win (or point (point)))))) 1076 (set-window-start win (or point (point))))))
942 1077
943 (defun gnus-annotation-in-region-p (b e) 1078 (defun gnus-annotation-in-region-p (b e)
944 (if (= b e) 1079 (if (= b e)
978 hashtb) 1113 hashtb)
979 (goto-char (point-max)) 1114 (goto-char (point-max))
980 (while (search-backward "\\." nil t) 1115 (while (search-backward "\\." nil t)
981 (delete-char 1))))) 1116 (delete-char 1)))))
982 1117
1118 ;; Fixme: Why not use `with-output-to-temp-buffer'?
1119 (defmacro gnus-with-output-to-file (file &rest body)
1120 (let ((buffer (make-symbol "output-buffer"))
1121 (size (make-symbol "output-buffer-size"))
1122 (leng (make-symbol "output-buffer-length"))
1123 (append (make-symbol "output-buffer-append")))
1124 `(let* ((,size 131072)
1125 (,buffer (make-string ,size 0))
1126 (,leng 0)
1127 (,append nil)
1128 (standard-output
1129 (lambda (c)
1130 (aset ,buffer ,leng c)
1131
1132 (if (= ,size (setq ,leng (1+ ,leng)))
1133 (progn (write-region ,buffer nil ,file ,append 'no-msg)
1134 (setq ,leng 0
1135 ,append t))))))
1136 ,@body
1137 (when (> ,leng 0)
1138 (let ((coding-system-for-write 'no-conversion))
1139 (write-region (substring ,buffer 0 ,leng) nil ,file
1140 ,append 'no-msg))))))
1141
1142 (put 'gnus-with-output-to-file 'lisp-indent-function 1)
1143 (put 'gnus-with-output-to-file 'edebug-form-spec '(form body))
1144
1145 (if (fboundp 'union)
1146 (defalias 'gnus-union 'union)
1147 (defun gnus-union (l1 l2)
1148 "Set union of lists L1 and L2."
1149 (cond ((null l1) l2)
1150 ((null l2) l1)
1151 ((equal l1 l2) l1)
1152 (t
1153 (or (>= (length l1) (length l2))
1154 (setq l1 (prog1 l2 (setq l2 l1))))
1155 (while l2
1156 (or (member (car l2) l1)
1157 (push (car l2) l1))
1158 (pop l2))
1159 l1))))
1160
983 (defun gnus-add-text-properties-when 1161 (defun gnus-add-text-properties-when
984 (property value start end properties &optional object) 1162 (property value start end properties &optional object)
985 "Like `gnus-add-text-properties', only applied on where PROPERTY is VALUE." 1163 "Like `gnus-add-text-properties', only applied on where PROPERTY is VALUE."
986 (let (point) 1164 (let (point)
987 (while (and start 1165 (while (and start
1166 (< start end) ;; XEmacs will loop for every when start=end.
988 (setq point (text-property-not-all start end property value))) 1167 (setq point (text-property-not-all start end property value)))
989 (gnus-add-text-properties start point properties object) 1168 (gnus-add-text-properties start point properties object)
990 (setq start (text-property-any point end property value))) 1169 (setq start (text-property-any point end property value)))
991 (if start 1170 (if start
992 (gnus-add-text-properties start end properties object)))) 1171 (gnus-add-text-properties start end properties object))))
994 (defun gnus-remove-text-properties-when 1173 (defun gnus-remove-text-properties-when
995 (property value start end properties &optional object) 1174 (property value start end properties &optional object)
996 "Like `remove-text-properties', only applied on where PROPERTY is VALUE." 1175 "Like `remove-text-properties', only applied on where PROPERTY is VALUE."
997 (let (point) 1176 (let (point)
998 (while (and start 1177 (while (and start
1178 (< start end)
999 (setq point (text-property-not-all start end property value))) 1179 (setq point (text-property-not-all start end property value)))
1000 (remove-text-properties start point properties object) 1180 (remove-text-properties start point properties object)
1001 (setq start (text-property-any point end property value))) 1181 (setq start (text-property-any point end property value)))
1002 (if start 1182 (if start
1003 (remove-text-properties start end properties object)) 1183 (remove-text-properties start end properties object))
1004 t)) 1184 t))
1005 1185
1186 ;; This might use `compare-strings' to reduce consing in the
1187 ;; case-insensitive case, but it has to cope with null args.
1188 ;; (`string-equal' uses symbol print names.)
1189 (defun gnus-string-equal (x y)
1190 "Like `string-equal', except it compares case-insensitively."
1191 (and (= (length x) (length y))
1192 (or (string-equal x y)
1193 (string-equal (downcase x) (downcase y)))))
1194
1195 (defcustom gnus-use-byte-compile t
1196 "If non-nil, byte-compile crucial run-time code.
1197 Setting it to nil has no effect after the first time `gnus-byte-compile'
1198 is run."
1199 :type 'boolean
1200 :version "22.1"
1201 :group 'gnus-various)
1202
1203 (defun gnus-byte-compile (form)
1204 "Byte-compile FORM if `gnus-use-byte-compile' is non-nil."
1205 (if gnus-use-byte-compile
1206 (progn
1207 (condition-case nil
1208 ;; Work around a bug in XEmacs 21.4
1209 (require 'byte-optimize)
1210 (error))
1211 (require 'bytecomp)
1212 (defalias 'gnus-byte-compile
1213 (lambda (form)
1214 (let ((byte-compile-warnings '(unresolved callargs redefine)))
1215 (byte-compile form))))
1216 (gnus-byte-compile form))
1217 form))
1218
1219 (defun gnus-remassoc (key alist)
1220 "Delete by side effect any elements of LIST whose car is `equal' to KEY.
1221 The modified LIST is returned. If the first member
1222 of LIST has a car that is `equal' to KEY, there is no way to remove it
1223 by side effect; therefore, write `(setq foo (gnus-remassoc key foo))' to be
1224 sure of changing the value of `foo'."
1225 (when alist
1226 (if (equal key (caar alist))
1227 (cdr alist)
1228 (setcdr alist (gnus-remassoc key (cdr alist)))
1229 alist)))
1230
1231 (defun gnus-update-alist-soft (key value alist)
1232 (if value
1233 (cons (cons key value) (gnus-remassoc key alist))
1234 (gnus-remassoc key alist)))
1235
1236 (defun gnus-create-info-command (node)
1237 "Create a command that will go to info NODE."
1238 `(lambda ()
1239 (interactive)
1240 ,(concat "Enter the info system at node " node)
1241 (Info-goto-node ,node)
1242 (setq gnus-info-buffer (current-buffer))
1243 (gnus-configure-windows 'info)))
1244
1245 (defun gnus-not-ignore (&rest args)
1246 t)
1247
1006 (defvar gnus-directory-sep-char-regexp "/" 1248 (defvar gnus-directory-sep-char-regexp "/"
1007 "The regexp of directory separator character. 1249 "The regexp of directory separator character.
1008 If you find some problem with the directory separator character, try 1250 If you find some problem with the directory separator character, try
1009 \"[/\\\\\]\" for some systems.") 1251 \"[/\\\\\]\" for some systems.")
1010 1252
1253 (defun gnus-url-unhex (x)
1254 (if (> x ?9)
1255 (if (>= x ?a)
1256 (+ 10 (- x ?a))
1257 (+ 10 (- x ?A)))
1258 (- x ?0)))
1259
1260 ;; Fixme: Do it like QP.
1261 (defun gnus-url-unhex-string (str &optional allow-newlines)
1262 "Remove %XX, embedded spaces, etc in a url.
1263 If optional second argument ALLOW-NEWLINES is non-nil, then allow the
1264 decoding of carriage returns and line feeds in the string, which is normally
1265 forbidden in URL encoding."
1266 (let ((tmp "")
1267 (case-fold-search t))
1268 (while (string-match "%[0-9a-f][0-9a-f]" str)
1269 (let* ((start (match-beginning 0))
1270 (ch1 (gnus-url-unhex (elt str (+ start 1))))
1271 (code (+ (* 16 ch1)
1272 (gnus-url-unhex (elt str (+ start 2))))))
1273 (setq tmp (concat
1274 tmp (substring str 0 start)
1275 (cond
1276 (allow-newlines
1277 (char-to-string code))
1278 ((or (= code ?\n) (= code ?\r))
1279 " ")
1280 (t (char-to-string code))))
1281 str (substring str (match-end 0)))))
1282 (setq tmp (concat tmp str))
1283 tmp))
1284
1285 (defun gnus-make-predicate (spec)
1286 "Transform SPEC into a function that can be called.
1287 SPEC is a predicate specifier that contains stuff like `or', `and',
1288 `not', lists and functions. The functions all take one parameter."
1289 `(lambda (elem) ,(gnus-make-predicate-1 spec)))
1290
1291 (defun gnus-make-predicate-1 (spec)
1292 (cond
1293 ((symbolp spec)
1294 `(,spec elem))
1295 ((listp spec)
1296 (if (memq (car spec) '(or and not))
1297 `(,(car spec) ,@(mapcar 'gnus-make-predicate-1 (cdr spec)))
1298 (error "Invalid predicate specifier: %s" spec)))))
1299
1300 (defun gnus-local-map-property (map)
1301 "Return a list suitable for a text property list specifying keymap MAP."
1302 (cond
1303 ((featurep 'xemacs)
1304 (list 'keymap map))
1305 ((>= emacs-major-version 21)
1306 (list 'keymap map))
1307 (t
1308 (list 'local-map map))))
1309
1310 (defmacro gnus-completing-read-maybe-default (prompt table &optional predicate
1311 require-match initial-contents
1312 history default)
1313 "Like `completing-read', allowing for non-existent 7th arg in older XEmacsen."
1314 `(completing-read ,prompt ,table ,predicate ,require-match
1315 ,initial-contents ,history
1316 ,@(if (and (featurep 'xemacs) (< emacs-minor-version 2))
1317 ()
1318 (list default))))
1319
1320 (defun gnus-completing-read (prompt table &optional predicate require-match
1321 history)
1322 (when (and history
1323 (not (boundp history)))
1324 (set history nil))
1325 (gnus-completing-read-maybe-default
1326 (if (symbol-value history)
1327 (concat prompt " (" (car (symbol-value history)) "): ")
1328 (concat prompt ": "))
1329 table
1330 predicate
1331 require-match
1332 nil
1333 history
1334 (car (symbol-value history))))
1335
1336 (defun gnus-graphic-display-p ()
1337 (or (and (fboundp 'display-graphic-p)
1338 (display-graphic-p))
1339 ;;;!!!This is bogus. Fixme!
1340 (and (featurep 'xemacs)
1341 t)))
1342
1343 (put 'gnus-parse-without-error 'lisp-indent-function 0)
1344 (put 'gnus-parse-without-error 'edebug-form-spec '(body))
1345
1346 (defmacro gnus-parse-without-error (&rest body)
1347 "Allow continuing onto the next line even if an error occurs."
1348 `(while (not (eobp))
1349 (condition-case ()
1350 (progn
1351 ,@body
1352 (goto-char (point-max)))
1353 (error
1354 (gnus-error 4 "Invalid data on line %d"
1355 (count-lines (point-min) (point)))
1356 (forward-line 1)))))
1357
1358 (defun gnus-cache-file-contents (file variable function)
1359 "Cache the contents of FILE in VARIABLE. The contents come from FUNCTION."
1360 (let ((time (nth 5 (file-attributes file)))
1361 contents value)
1362 (if (or (null (setq value (symbol-value variable)))
1363 (not (equal (car value) file))
1364 (not (equal (nth 1 value) time)))
1365 (progn
1366 (setq contents (funcall function file))
1367 (set variable (list file time contents))
1368 contents)
1369 (nth 2 value))))
1370
1371 (defun gnus-multiple-choice (prompt choice &optional idx)
1372 "Ask user a multiple choice question.
1373 CHOICE is a list of the choice char and help message at IDX."
1374 (let (tchar buf)
1375 (save-window-excursion
1376 (save-excursion
1377 (while (not tchar)
1378 (message "%s (%s): "
1379 prompt
1380 (concat
1381 (mapconcat (lambda (s) (char-to-string (car s)))
1382 choice ", ") ", ?"))
1383 (setq tchar (read-char))
1384 (when (not (assq tchar choice))
1385 (setq tchar nil)
1386 (setq buf (get-buffer-create "*Gnus Help*"))
1387 (pop-to-buffer buf)
1388 (fundamental-mode) ; for Emacs 20.4+
1389 (buffer-disable-undo)
1390 (erase-buffer)
1391 (insert prompt ":\n\n")
1392 (let ((max -1)
1393 (list choice)
1394 (alist choice)
1395 (idx (or idx 1))
1396 (i 0)
1397 n width pad format)
1398 ;; find the longest string to display
1399 (while list
1400 (setq n (length (nth idx (car list))))
1401 (unless (> max n)
1402 (setq max n))
1403 (setq list (cdr list)))
1404 (setq max (+ max 4)) ; %c, `:', SPACE, a SPACE at end
1405 (setq n (/ (1- (window-width)) max)) ; items per line
1406 (setq width (/ (1- (window-width)) n)) ; width of each item
1407 ;; insert `n' items, each in a field of width `width'
1408 (while alist
1409 (if (< i n)
1410 ()
1411 (setq i 0)
1412 (delete-char -1) ; the `\n' takes a char
1413 (insert "\n"))
1414 (setq pad (- width 3))
1415 (setq format (concat "%c: %-" (int-to-string pad) "s"))
1416 (insert (format format (caar alist) (nth idx (car alist))))
1417 (setq alist (cdr alist))
1418 (setq i (1+ i))))))))
1419 (if (buffer-live-p buf)
1420 (kill-buffer buf))
1421 tchar))
1422
1423 (defun gnus-select-frame-set-input-focus (frame)
1424 "Select FRAME, raise it, and set input focus, if possible."
1425 (cond ((featurep 'xemacs)
1426 (raise-frame frame)
1427 (select-frame frame)
1428 (focus-frame frame))
1429 ;; The function `select-frame-set-input-focus' won't set
1430 ;; the input focus under Emacs 21.2 and X window system.
1431 ;;((fboundp 'select-frame-set-input-focus)
1432 ;; (defalias 'gnus-select-frame-set-input-focus
1433 ;; 'select-frame-set-input-focus)
1434 ;; (select-frame-set-input-focus frame))
1435 (t
1436 (raise-frame frame)
1437 (select-frame frame)
1438 (cond ((and (eq window-system 'x)
1439 (fboundp 'x-focus-frame))
1440 (x-focus-frame frame))
1441 ((eq window-system 'w32)
1442 (w32-focus-frame frame)))
1443 (when focus-follows-mouse
1444 (set-mouse-position frame (1- (frame-width frame)) 0)))))
1445
1446 (defun gnus-frame-or-window-display-name (object)
1447 "Given a frame or window, return the associated display name.
1448 Return nil otherwise."
1449 (if (featurep 'xemacs)
1450 (device-connection (dfw-device object))
1451 (if (or (framep object)
1452 (and (windowp object)
1453 (setq object (window-frame object))))
1454 (let ((display (frame-parameter object 'display)))
1455 (if (and (stringp display)
1456 ;; Exclude invalid display names.
1457 (string-match "\\`[^:]*:[0-9]+\\(\\.[0-9]+\\)?\\'"
1458 display))
1459 display)))))
1460
1461 ;; Fixme: This has only one use (in gnus-agent), which isn't worthwhile.
1462 (defmacro gnus-mapcar (function seq1 &rest seqs2_n)
1463 "Apply FUNCTION to each element of the sequences, and make a list of the results.
1464 If there are several sequences, FUNCTION is called with that many arguments,
1465 and mapping stops as soon as the shortest sequence runs out. With just one
1466 sequence, this is like `mapcar'. With several, it is like the Common Lisp
1467 `mapcar' function extended to arbitrary sequence types."
1468
1469 (if seqs2_n
1470 (let* ((seqs (cons seq1 seqs2_n))
1471 (cnt 0)
1472 (heads (mapcar (lambda (seq)
1473 (make-symbol (concat "head"
1474 (int-to-string
1475 (setq cnt (1+ cnt))))))
1476 seqs))
1477 (result (make-symbol "result"))
1478 (result-tail (make-symbol "result-tail")))
1479 `(let* ,(let* ((bindings (cons nil nil))
1480 (heads heads))
1481 (nconc bindings (list (list result '(cons nil nil))))
1482 (nconc bindings (list (list result-tail result)))
1483 (while heads
1484 (nconc bindings (list (list (pop heads) (pop seqs)))))
1485 (cdr bindings))
1486 (while (and ,@heads)
1487 (setcdr ,result-tail (cons (funcall ,function
1488 ,@(mapcar (lambda (h) (list 'car h))
1489 heads))
1490 nil))
1491 (setq ,result-tail (cdr ,result-tail)
1492 ,@(apply 'nconc (mapcar (lambda (h) (list h (list 'cdr h))) heads))))
1493 (cdr ,result)))
1494 `(mapcar ,function ,seq1)))
1495
1496 (if (fboundp 'merge)
1497 (defalias 'gnus-merge 'merge)
1498 ;; Adapted from cl-seq.el
1499 (defun gnus-merge (type list1 list2 pred)
1500 "Destructively merge lists LIST1 and LIST2 to produce a new list.
1501 Argument TYPE is for compatibility and ignored.
1502 Ordering of the elements is preserved according to PRED, a `less-than'
1503 predicate on the elements."
1504 (let ((res nil))
1505 (while (and list1 list2)
1506 (if (funcall pred (car list2) (car list1))
1507 (push (pop list2) res)
1508 (push (pop list1) res)))
1509 (nconc (nreverse res) list1 list2))))
1510
1511 (eval-when-compile
1512 (defvar xemacs-codename)
1513 (defvar sxemacs-codename)
1514 (defvar emacs-program-version))
1515
1516 (defun gnus-emacs-version ()
1517 "Stringified Emacs version."
1518 (let* ((lst (if (listp gnus-user-agent)
1519 gnus-user-agent
1520 '(gnus emacs type)))
1521 (system-v (cond ((memq 'config lst)
1522 system-configuration)
1523 ((memq 'type lst)
1524 (symbol-name system-type))
1525 (t nil)))
1526 codename emacsname)
1527 (cond ((featurep 'sxemacs)
1528 (setq emacsname "SXEmacs"
1529 codename sxemacs-codename))
1530 ((featurep 'xemacs)
1531 (setq emacsname "XEmacs"
1532 codename xemacs-codename))
1533 (t
1534 (setq emacsname "Emacs")))
1535 (cond
1536 ((not (memq 'emacs lst))
1537 nil)
1538 ((string-match "^\\(\\([.0-9]+\\)*\\)\\.[0-9]+$" emacs-version)
1539 ;; Emacs:
1540 (concat "Emacs/" (match-string 1 emacs-version)
1541 (if system-v
1542 (concat " (" system-v ")")
1543 "")))
1544 ((or (featurep 'sxemacs) (featurep 'xemacs))
1545 ;; XEmacs or SXEmacs:
1546 (concat emacsname "/" emacs-program-version
1547 " ("
1548 (when (and (memq 'codename lst)
1549 codename)
1550 (concat codename
1551 (when system-v ", ")))
1552 (when system-v system-v)
1553 ")"))
1554 (t emacs-version))))
1555
1556 (defun gnus-rename-file (old-path new-path &optional trim)
1557 "Rename OLD-PATH as NEW-PATH. If TRIM, recursively delete
1558 empty directories from OLD-PATH."
1559 (when (file-exists-p old-path)
1560 (let* ((old-dir (file-name-directory old-path))
1561 (old-name (file-name-nondirectory old-path))
1562 (new-dir (file-name-directory new-path))
1563 (new-name (file-name-nondirectory new-path))
1564 temp)
1565 (gnus-make-directory new-dir)
1566 (rename-file old-path new-path t)
1567 (when trim
1568 (while (progn (setq temp (directory-files old-dir))
1569 (while (member (car temp) '("." ".."))
1570 (setq temp (cdr temp)))
1571 (= (length temp) 0))
1572 (delete-directory old-dir)
1573 (setq old-dir (file-name-as-directory
1574 (file-truename
1575 (concat old-dir "..")))))))))
1576
1577 (if (fboundp 'set-process-query-on-exit-flag)
1578 (defalias 'gnus-set-process-query-on-exit-flag
1579 'set-process-query-on-exit-flag)
1580 (defalias 'gnus-set-process-query-on-exit-flag
1581 'process-kill-without-query))
1582
1011 (provide 'gnus-util) 1583 (provide 'gnus-util)
1012 1584
1585 ;;; arch-tag: f94991af-d32b-4c97-8c26-ca12a934de49
1013 ;;; gnus-util.el ends here 1586 ;;; gnus-util.el ends here