file uu-decode.pbm was added on branch t-gnus-6_17 on 2005-05-01 23:25:48 +0000
[elisp/gnus.git-] / lisp / gnus-spec.el
1 ;;; gnus-spec.el --- format spec functions for Gnus
2 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003
3 ;;        Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;;      Katsumi Yamaoka <yamaoka@jpl.org>
7 ;; Keywords: news
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
25
26 ;;; Commentary:
27
28 ;;; Code:
29
30 (eval-when-compile (require 'cl))
31
32 (require 'alist)
33 (require 'gnus)
34
35 (defcustom gnus-use-correct-string-widths t
36   "*If non-nil, use correct functions for dealing with wide characters."
37   :group 'gnus-format
38   :type 'boolean)
39
40 (defcustom gnus-make-format-preserve-properties (featurep 'xemacs)
41   "*If non-nil, use a replacement `format' function which preserves
42 text properties. This is only needed on XEmacs, as FSF Emacs does this anyway."
43   :group 'gnus-format
44   :type 'boolean)
45
46 ;;; Internal variables.
47
48 (defvar gnus-summary-mark-positions nil)
49 (defvar gnus-group-mark-positions nil)
50 (defvar gnus-group-indentation "")
51
52 ;; Format specs.  The chunks below are the machine-generated forms
53 ;; that are to be evaled as the result of the default format strings.
54 ;; We write them in here to get them byte-compiled.  That way the
55 ;; default actions will be quite fast, while still retaining the full
56 ;; flexibility of the user-defined format specs.
57
58 ;; First we have lots of dummy defvars to let the compiler know these
59 ;; are really dynamic variables.
60
61 (defvar gnus-tmp-unread)
62 (defvar gnus-tmp-replied)
63 (defvar gnus-tmp-score-char)
64 (defvar gnus-tmp-indentation)
65 (defvar gnus-tmp-opening-bracket)
66 (defvar gnus-tmp-lines)
67 (defvar gnus-tmp-name)
68 (defvar gnus-tmp-closing-bracket)
69 (defvar gnus-tmp-subject-or-nil)
70 (defvar gnus-tmp-subject)
71 (defvar gnus-tmp-marked)
72 (defvar gnus-tmp-marked-mark)
73 (defvar gnus-tmp-subscribed)
74 (defvar gnus-tmp-process-marked)
75 (defvar gnus-tmp-number-of-unread)
76 (defvar gnus-tmp-group-name)
77 (defvar gnus-tmp-group)
78 (defvar gnus-tmp-article-number)
79 (defvar gnus-tmp-unread-and-unselected)
80 (defvar gnus-tmp-news-method)
81 (defvar gnus-tmp-news-server)
82 (defvar gnus-tmp-article-number)
83 (defvar gnus-mouse-face)
84 (defvar gnus-mouse-face-prop)
85 (defvar gnus-tmp-header)
86 (defvar gnus-tmp-from)
87
88 (defun gnus-summary-line-format-spec ()
89   (insert gnus-tmp-unread gnus-tmp-replied
90           gnus-tmp-score-char gnus-tmp-indentation)
91   (gnus-put-text-property
92    (point)
93    (progn
94      (insert
95       (format "%c%4s: %-23s%c" gnus-tmp-opening-bracket gnus-tmp-lines
96               (let ((val
97                      (inline
98                        (gnus-summary-from-or-to-or-newsgroups
99                         gnus-tmp-header gnus-tmp-from))))
100                 (if (> (length val) 23)
101                     (substring val 0 23)
102                   val))
103               gnus-tmp-closing-bracket))
104      (point))
105    gnus-mouse-face-prop gnus-mouse-face)
106   (insert " " gnus-tmp-subject-or-nil "\n"))
107
108 (defvar gnus-summary-line-format-spec
109   (gnus-byte-code 'gnus-summary-line-format-spec))
110
111 (defun gnus-summary-dummy-line-format-spec ()
112   (insert "*  ")
113   (gnus-put-text-property
114    (point)
115    (progn
116      (insert ":                          :")
117      (point))
118    gnus-mouse-face-prop gnus-mouse-face)
119   (insert " " gnus-tmp-subject "\n"))
120
121 (defvar gnus-summary-dummy-line-format-spec
122   (gnus-byte-code 'gnus-summary-dummy-line-format-spec))
123
124 (defun gnus-group-line-format-spec ()
125   (insert gnus-tmp-marked-mark gnus-tmp-subscribed
126           gnus-tmp-process-marked
127           gnus-group-indentation
128           (format "%5s: " gnus-tmp-number-of-unread))
129   (gnus-put-text-property
130    (point)
131    (progn
132      (insert gnus-tmp-group "\n")
133      (1- (point)))
134    gnus-mouse-face-prop gnus-mouse-face))
135 (defvar gnus-group-line-format-spec
136   (gnus-byte-code 'gnus-group-line-format-spec))
137
138 (defvar gnus-format-specs
139   `((group ("%M\%S\%p\%P\%5y: %(%g%)\n" ,gnus-group-line-format-spec))
140     (summary-dummy ("*  %(:                          :%) %S\n"
141                     ,gnus-summary-dummy-line-format-spec))
142     (summary ("%U%R%z%I%(%[%4L: %-23,23f%]%) %s\n"
143               ,gnus-summary-line-format-spec)))
144   "Alist of format specs.")
145
146 (defvar gnus-default-format-specs gnus-format-specs)
147
148 (defvar gnus-format-specs-compiled nil
149   "Alist of compiled format specs.  Each element should be the form:
150 \(TYPE (FORMAT-STRING-1 . COMPILED-FUNCTION-1)
151                  :
152        (FORMAT-STRING-n . COMPILED-FUNCTION-n)).")
153
154 (defvar gnus-article-mode-line-format-spec nil)
155 (defvar gnus-summary-mode-line-format-spec nil)
156 (defvar gnus-group-mode-line-format-spec nil)
157
158 ;;; Phew.  All that gruft is over with, fortunately.
159
160 ;;;###autoload
161 (defun gnus-update-format (var)
162   "Update the format specification near point."
163   (interactive
164    (list
165     (save-excursion
166       (eval-defun nil)
167       ;; Find the end of the current word.
168       (re-search-forward "[ \t\n]" nil t)
169       ;; Search backward.
170       (when (re-search-backward "\\(gnus-[-a-z]+-line-format\\)" nil t)
171         (match-string 1)))))
172   (let* ((type (intern (progn (string-match "gnus-\\([-a-z]+\\)-line" var)
173                               (match-string 1 var))))
174          (value (symbol-value (intern var)))
175          (spec (set
176                 (intern (format "%s-spec" var))
177                 (gnus-parse-format
178                  value (symbol-value (intern (format "%s-alist" var)))
179                  (not (string-match "mode" var)))))
180          (entry (assq type gnus-format-specs)))
181     (if entry
182         (let ((elem (assoc value entry)))
183           (if elem
184               (setcdr elem spec)
185             (setcdr entry (cons (cons value elem) (cdr entry)))))
186       (push (list type (cons value spec)) gnus-format-specs))
187     (gnus-product-variable-touch 'gnus-format-specs)
188
189     (pop-to-buffer "*Gnus Format*")
190     (erase-buffer)
191     (lisp-interaction-mode)
192     (insert (pp-to-string spec))))
193
194 (put 'gnus-search-or-regist-spec 'lisp-indent-function 1)
195 (defmacro gnus-search-or-regist-spec (mspec &rest body)
196   (let ((specs (nth 0 mspec)) (type (nth 1 mspec)) (format (nth 2 mspec))
197         (spec (nth 3 mspec)) (entry (nth 4 mspec)) (elem (nth 5 mspec)))
198     `(let* ((,entry (assq ,type ,specs))
199             (,elem (assoc ,format (cdr ,entry))))
200        (or (cdr ,elem)
201            (when (progn ,@body)
202              (if ,entry
203                  (if ,elem
204                      (setcdr ,elem ,spec)
205                    (setcdr ,entry (cons (cons ,format ,spec) (cdr ,entry))))
206                (push (list ,type (cons ,format ,spec)) ,specs))
207              (gnus-product-variable-touch (quote ,specs)))
208            ,spec))))
209
210 (defun gnus-update-format-specification-1 (type format val)
211   (set (intern (format "gnus-%s-line-format-spec" type))
212        (gnus-search-or-regist-spec (gnus-format-specs-compiled
213                                     type format val entry elem)
214          (when (and gnus-compile-user-specs val)
215            (setq val (prog1
216                          (progn
217                            (fset 'gnus-tmp-func `(lambda () ,val))
218                            (require 'bytecomp)
219                            (let (byte-compile-warnings)
220                              (byte-compile 'gnus-tmp-func))
221                            (gnus-byte-code 'gnus-tmp-func))
222                        (when (get-buffer "*Compile-Log*")
223                          (bury-buffer "*Compile-Log*"))
224                        (when (get-buffer "*Compile-Log-Show*")
225                          (bury-buffer "*Compile-Log-Show*"))))))))
226
227 (defun gnus-update-format-specifications (&optional force &rest types)
228   "Update all (necessary) format specifications."
229   ;; Make the indentation array.
230   ;; See whether all the stored info needs to be flushed.
231   (when force
232     (message "%s" "Force update format specs.")
233     (setq gnus-format-specs nil
234           gnus-format-specs-compiled nil)
235     (gnus-product-variable-touch 'gnus-format-specs
236                                  'gnus-format-specs-compiled))
237   ;; Flush the group format spec cache if there's the grouplens stuff.
238   (let ((spec (assq 'group gnus-format-specs)))
239     (when (and (memq 'group types)
240                (string-match " gnus-tmp-grouplens[ )]"
241                              (gnus-prin1-to-string (cdr spec))))
242       (setq gnus-format-specs (delq spec gnus-format-specs)
243             spec (assq 'group gnus-format-specs-compiled)
244             gnus-format-specs-compiled (delq spec gnus-format-specs-compiled))))
245
246   ;; Go through all the formats and see whether they need updating.
247   (let (type val)
248     (save-excursion
249       (while (setq type (pop types))
250         ;; Jump to the proper buffer to find out the value of the
251         ;; variable, if possible.  (It may be buffer-local.)
252         (let* ((new-format
253                 (let ((buffer (intern (format "gnus-%s-buffer" type))))
254                   (when (and (boundp buffer)
255                              (setq val (symbol-value buffer))
256                              (gnus-buffer-exists-p val))
257                     (set-buffer val))
258                   (symbol-value
259                    (intern (format "gnus-%s-line-format" type))))))
260           (or (gnus-update-format-specification-1 type new-format nil)
261               ;; This is a new format.
262               (gnus-update-format-specification-1
263                type new-format
264                (gnus-search-or-regist-spec (gnus-format-specs
265                                             type new-format val entry elem)
266                  (setq val (if (stringp new-format)
267                                ;; This is a "real" format.
268                                (gnus-parse-format
269                                 new-format
270                                 (symbol-value
271                                  (intern (format "gnus-%s-line-format-alist"
272                                                  type)))
273                                 (not (string-match "mode$"
274                                                    (symbol-name type))))
275                              ;; This is a function call or something.
276                              new-format))))))))))
277
278 (defvar gnus-mouse-face-0 'highlight)
279 (defvar gnus-mouse-face-1 'highlight)
280 (defvar gnus-mouse-face-2 'highlight)
281 (defvar gnus-mouse-face-3 'highlight)
282 (defvar gnus-mouse-face-4 'highlight)
283
284 (defun gnus-mouse-face-function (form type)
285   `(gnus-put-text-property
286     (point) (progn ,@form (point))
287     gnus-mouse-face-prop
288     ,(if (equal type 0)
289          'gnus-mouse-face
290        `(quote ,(symbol-value (intern (format "gnus-mouse-face-%d" type)))))))
291
292 (defvar gnus-face-0 'bold)
293 (defvar gnus-face-1 'italic)
294 (defvar gnus-face-2 'bold-italic)
295 (defvar gnus-face-3 'bold)
296 (defvar gnus-face-4 'bold)
297
298 (defun gnus-face-face-function (form type)
299   `(gnus-add-text-properties
300     (point) (progn ,@form (point))
301     '(gnus-face t face ,(symbol-value (intern (format "gnus-face-%d" type))))))
302
303 (defun gnus-balloon-face-function (form type)
304   `(gnus-put-text-property
305     (point) (progn ,@form (point))
306     ,(if (fboundp 'balloon-help-mode)
307          ''balloon-help
308        ''help-echo)
309     ,(intern (format "gnus-balloon-face-%d" type))))
310
311 (defun gnus-spec-tab (column)
312   (if (> column 0)
313       `(insert (make-string (max (- ,column (current-column)) 0) ? ))
314     (let ((column (abs column)))
315       (if gnus-use-correct-string-widths
316           `(progn
317              (if (> (current-column) ,column)
318                  (while (progn
319                           (delete-backward-char 1)
320                           (> (current-column) ,column))))
321              (insert (make-string (max (- ,column (current-column)) 0) ? )))
322         `(progn
323            (if (> (current-column) ,column)
324                (delete-region (point)
325                               (- (point) (- (current-column) ,column)))
326              (insert (make-string (max (- ,column (current-column)) 0)
327                                   ? ))))))))
328
329 (defun gnus-correct-length (string)
330   "Return the correct width of STRING."
331   (let ((length 0))
332     (mapcar (lambda (char) (incf length (char-width char))) string)
333     length))
334
335 (defun gnus-correct-substring (string start &optional end)
336   (let ((wstart 0)
337         (wend 0)
338         (wseek 0)
339         (seek 0)
340         (length (length string))
341         (string (concat string "\0")))
342     ;; Find the start position.
343     (while (and (< seek length)
344                 (< wseek start))
345       (incf wseek (char-width (aref string seek)))
346       (incf seek))
347     (setq wstart seek)
348     ;; Find the end position.
349     (while (and (<= seek length)
350                 (or (not end)
351                     (<= wseek end)))
352       (incf wseek (char-width (aref string seek)))
353       (incf seek))
354     (setq wend seek)
355     (substring string wstart (1- wend))))
356
357 (defun gnus-string-width-function ()
358   (cond
359    (gnus-use-correct-string-widths
360     'gnus-correct-length)
361    ((fboundp 'string-width)
362     'string-width)
363    (t
364     'length)))
365
366 (defun gnus-substring-function ()
367   (cond
368    (gnus-use-correct-string-widths
369     'gnus-correct-substring)
370    ((fboundp 'string-width)
371     'gnus-correct-substring)
372    (t
373     'substring)))
374
375 (defun gnus-tilde-max-form (el max-width)
376   "Return a form that limits EL to MAX-WIDTH."
377   (let ((max (abs max-width))
378         (length-fun (gnus-string-width-function))
379         (substring-fun (gnus-substring-function)))
380     (if (symbolp el)
381         `(if (> (,length-fun ,el) ,max)
382              ,(if (< max-width 0)
383                   `(,substring-fun ,el (- (,length-fun ,el) ,max))
384                 `(,substring-fun ,el 0 ,max))
385            ,el)
386       `(let ((val (eval ,el)))
387          (if (> (,length-fun val) ,max)
388              ,(if (< max-width 0)
389                   `(,substring-fun val (- (,length-fun val) ,max))
390                 `(,substring-fun val 0 ,max))
391            val)))))
392
393 (defun gnus-tilde-cut-form (el cut-width)
394   "Return a form that cuts CUT-WIDTH off of EL."
395   (let ((cut (abs cut-width))
396         (length-fun (gnus-string-width-function))
397         (substring-fun (gnus-substring-function)))
398     (if (symbolp el)
399         `(if (> (,length-fun ,el) ,cut)
400              ,(if (< cut-width 0)
401                   `(,substring-fun ,el 0 (- (,length-fun ,el) ,cut))
402                 `(,substring-fun ,el ,cut))
403            ,el)
404       `(let ((val (eval ,el)))
405          (if (> (,length-fun val) ,cut)
406              ,(if (< cut-width 0)
407                   `(,substring-fun val 0 (- (,length-fun val) ,cut))
408                 `(,substring-fun val ,cut))
409            val)))))
410
411 (defun gnus-tilde-ignore-form (el ignore-value)
412   "Return a form that is blank when EL is IGNORE-VALUE."
413   (if (symbolp el)
414       `(if (equal ,el ,ignore-value)
415            "" ,el)
416     `(let ((val (eval ,el)))
417        (if (equal val ,ignore-value)
418            "" val))))
419
420 (defun gnus-pad-form (el pad-width)
421   "Return a form that pads EL to PAD-WIDTH accounting for multi-column
422 characters correctly. This is because `format' may pad to columns or to
423 characters when given a pad value."
424   (let ((pad (abs pad-width))
425         (side (< 0 pad-width))
426         (length-fun (gnus-string-width-function)))
427     (if (symbolp el)
428         `(let ((need (- ,pad (,length-fun ,el))))
429            (if (> need 0)
430                (concat ,(when side '(make-string need ?\ ))
431                        ,el
432                        ,(when (not side) '(make-string need ?\ )))
433              ,el))
434       `(let* ((val (eval ,el))
435               (need (- ,pad (,length-fun val))))
436          (if (> need 0)
437              (concat ,(when side '(make-string need ?\ ))
438                      val
439                      ,(when (not side) '(make-string need ?\ )))
440            val)))))
441
442 (defun gnus-parse-format (format spec-alist &optional insert)
443   ;; This function parses the FORMAT string with the help of the
444   ;; SPEC-ALIST and returns a list that can be eval'ed to return the
445   ;; string.  If the FORMAT string contains the specifiers %( and %)
446   ;; the text between them will have the mouse-face text property.
447   ;; If the FORMAT string contains the specifiers %[ and %], the text between
448   ;; them will have the balloon-help text property.
449   (let ((case-fold-search nil))
450     (if (string-match
451          "\\`\\(.*\\)%[0-9]?[{(«]\\(.*\\)%[0-9]?[»})]\\(.*\n?\\)\\'\\|%[-0-9]*=\\|%[-0-9]*\\*"
452          format)
453         (gnus-parse-complex-format format spec-alist)
454       ;; This is a simple format.
455       (gnus-parse-simple-format format spec-alist insert))))
456
457 (defun gnus-parse-complex-format (format spec-alist)
458   (let ((cursor-spec nil))
459     (save-excursion
460       (gnus-set-work-buffer)
461       (insert format)
462       (goto-char (point-min))
463       (while (re-search-forward "\"" nil t)
464         (replace-match "\\\"" nil t))
465       (goto-char (point-min))
466       (insert "(\"")
467       ;; Convert all font specs into font spec lists.
468       (while (re-search-forward "%\\([0-9]+\\)?\\([«»{}()]\\)" nil t)
469         (let ((number (if (match-beginning 1)
470                           (match-string 1) "0"))
471               (delim (aref (match-string 2) 0)))
472           (if (or (= delim ?\()
473                   (= delim ?\{)
474                   (= delim ?\«))
475               (replace-match (concat "\"("
476                                      (cond ((= delim ?\() "mouse")
477                                            ((= delim ?\{) "face")
478                                            (t "balloon"))
479                                      " " number " \"")
480                              t t)
481             (replace-match "\")\""))))
482       (goto-char (point-max))
483       (insert "\")")
484       ;; Convert point position commands.
485       (goto-char (point-min))
486       (let ((case-fold-search nil))
487         (while (re-search-forward "%\\([-0-9]+\\)?\\*" nil t)
488           (replace-match "\"(point)\"" t t)
489           (setq cursor-spec t)))
490       ;; Convert TAB commands.
491       (goto-char (point-min))
492       (while (re-search-forward "%\\([-0-9]+\\)=" nil t)
493         (replace-match (format "\"(tab %s)\"" (match-string 1)) t t))
494       ;; Convert the buffer into the spec.
495       (goto-char (point-min))
496       (let ((form (read (current-buffer))))
497         (if cursor-spec
498             `(let (gnus-position)
499                ,@(gnus-complex-form-to-spec form spec-alist)
500                (if gnus-position
501                    (gnus-put-text-property gnus-position (1+ gnus-position)
502                                            'gnus-position t)))
503           `(progn
504              ,@(gnus-complex-form-to-spec form spec-alist)))))))
505
506 (defun gnus-complex-form-to-spec (form spec-alist)
507   (delq nil
508         (mapcar
509          (lambda (sform)
510            (cond
511             ((stringp sform)
512              (gnus-parse-simple-format sform spec-alist t))
513             ((eq (car sform) 'point)
514              '(setq gnus-position (point)))
515             ((eq (car sform) 'tab)
516              (gnus-spec-tab (cadr sform)))
517             (t
518              (funcall (intern (format "gnus-%s-face-function" (car sform)))
519                       (gnus-complex-form-to-spec (cddr sform) spec-alist)
520                       (nth 1 sform)))))
521          form)))
522
523
524 (defun gnus-xmas-format (fstring &rest args)
525   "A version of `format' which preserves text properties.
526
527 Required for XEmacs, where the built in `format' function strips all text
528 properties from both the format string and any inserted strings.
529
530 Only supports the format sequence %s, and %% for inserting
531 literal % characters. A pad width and an optional - (to right pad)
532 are supported for %s."
533   (let ((re "%%\\|%\\(-\\)?\\([1-9][0-9]*\\)?s")
534         (n (length args)))
535     (with-temp-buffer
536       (insert fstring)
537       (goto-char (point-min))
538       (while (re-search-forward re nil t)
539         (goto-char (match-end 0))
540         (cond
541          ((string= (match-string 0) "%%")
542           (delete-char -1))
543          (t
544           (if (null args)
545               (error 'wrong-number-of-arguments #'my-format n fstring))
546           (let* ((minlen (string-to-int (or (match-string 2) "")))
547                  (arg (car args))
548                  (str (if (stringp arg) arg (format "%s" arg)))
549                  (lpad (null (match-string 1)))
550                  (padlen (max 0 (- minlen (length str)))))
551             (replace-match "")
552             (if lpad (insert-char ?\  padlen))
553             (insert str)
554             (unless lpad (insert-char ?\  padlen))
555             (setq args (cdr args))))))
556       (buffer-string))))
557
558 (defun gnus-parse-simple-format (format spec-alist &optional insert)
559   ;; This function parses the FORMAT string with the help of the
560   ;; SPEC-ALIST and returns a list that can be eval'ed to return a
561   ;; string.
562   (let (max-width
563         spec flist fstring elem result dontinsert user-defined
564         type value pad-width spec-beg cut-width ignore-value
565         tilde-form tilde elem-type extended-spec)
566     (save-excursion
567       (gnus-set-work-buffer)
568       (insert format)
569       (goto-char (point-min))
570       (while (search-forward "%" nil t)
571         (setq user-defined nil
572               spec-beg nil
573               pad-width nil
574               max-width nil
575               cut-width nil
576               ignore-value nil
577               tilde-form nil
578               extended-spec nil)
579         (setq spec-beg (1- (point)))
580
581         ;; Parse this spec fully.
582         (while
583             (cond
584              ((looking-at "\\([-.0-9]+\\)\\(,[-0-9]+\\)?")
585               (setq pad-width (string-to-number (match-string 1)))
586               (when (match-beginning 2)
587                 (setq max-width (string-to-number (buffer-substring
588                                                    (1+ (match-beginning 2))
589                                                    (match-end 2)))))
590               (goto-char (match-end 0)))
591              ((looking-at "~")
592               (forward-char 1)
593               (setq tilde (read (current-buffer))
594                     type (car tilde)
595                     value (cadr tilde))
596               (cond
597                ((memq type '(pad pad-left))
598                 (setq pad-width value))
599                ((eq type 'pad-right)
600                 (setq pad-width (- value)))
601                ((memq type '(max-right max))
602                 (setq max-width value))
603                ((eq type 'max-left)
604                 (setq max-width (- value)))
605                ((memq type '(cut cut-left))
606                 (setq cut-width value))
607                ((eq type 'cut-right)
608                 (setq cut-width (- value)))
609                ((eq type 'ignore)
610                 (setq ignore-value
611                       (if (stringp value) value (format "%s" value))))
612                ((eq type 'form)
613                 (setq tilde-form value))
614                (t
615                 (error "Unknown tilde type: %s" tilde)))
616               t)
617              (t
618               nil)))
619         (cond
620          ;; User-defined spec -- find the spec name.
621          ((eq (setq spec (char-after)) ?u)
622           (forward-char 1)
623           (when (and (eq (setq user-defined (char-after)) ?&)
624                      (looking-at "&\\([^;]+\\);"))
625             (setq user-defined (match-string 1))
626             (goto-char (match-end 1))))
627          ;; extended spec
628          ((and (eq spec ?&) (looking-at "&\\([^;]+\\);"))
629           (setq extended-spec (intern (match-string 1)))
630           (goto-char (match-end 1))))
631         (forward-char 1)
632         (delete-region spec-beg (point))
633
634         ;; Now we have all the relevant data on this spec, so
635         ;; we start doing stuff.
636         (insert "%")
637         (if (eq spec ?%)
638             ;; "%%" just results in a "%".
639             (insert "%")
640           (cond
641            ;; Do tilde forms.
642            ((eq spec ?@)
643             (setq elem (list tilde-form ?s)))
644            ;; Treat user defined format specifiers specially.
645            (user-defined
646             (setq elem
647                   (list
648                    (list (intern (format
649                                   (if (stringp user-defined)
650                                       "gnus-user-format-function-%s"
651                                     "gnus-user-format-function-%c")
652                                   user-defined))
653                          'gnus-tmp-header)
654                    ?s)))
655            ;; Find the specification from `spec-alist'.
656            ((setq elem (cdr (assq (or extended-spec spec) spec-alist))))
657            ;; We used to use "%l" for displaying the grouplens score.
658            ((eq spec ?l)
659             (setq elem '("" ?s)))
660            (t
661             (setq elem '("*" ?s))))
662           (setq elem-type (cadr elem))
663           ;; Insert the new format elements.
664           (when (and pad-width
665                      (not (and (featurep 'xemacs)
666                                gnus-use-correct-string-widths)))
667             (insert (number-to-string pad-width)))
668           ;; Create the form to be evaled.
669           (if (or max-width cut-width ignore-value
670                   (and (featurep 'xemacs)
671                        gnus-use-correct-string-widths))
672               (progn
673                 (insert ?s)
674                 (let ((el (car elem)))
675                   (cond ((= (cadr elem) ?c)
676                          (setq el (list 'char-to-string el)))
677                         ((= (cadr elem) ?d)
678                          (setq el (list 'int-to-string el))))
679                   (when ignore-value
680                     (setq el (gnus-tilde-ignore-form el ignore-value)))
681                   (when cut-width
682                     (setq el (gnus-tilde-cut-form el cut-width)))
683                   (when max-width
684                     (setq el (gnus-tilde-max-form el max-width)))
685                   (when pad-width
686                     (setq el (gnus-pad-form el pad-width)))
687                   (push el flist)))
688             (insert elem-type)
689             (push (car elem) flist))))
690       (setq fstring (buffer-substring-no-properties (point-min) (point-max))))
691
692     ;; Do some postprocessing to increase efficiency.
693     (setq
694      result
695      (cond
696       ;; Emptiness.
697       ((string= fstring "")
698        nil)
699       ;; Not a format string.
700       ((not (string-match "%" fstring))
701        (list fstring))
702       ;; A format string with just a single string spec.
703       ((string= fstring "%s")
704        (list (car flist)))
705       ;; A single character.
706       ((string= fstring "%c")
707        (list (car flist)))
708       ;; A single number.
709       ((string= fstring "%d")
710        (setq dontinsert t)
711        (if insert
712            (list `(princ ,(car flist)))
713          (list `(int-to-string ,(car flist)))))
714       ;; Just lots of chars and strings.
715       ((string-match "\\`\\(%[cs]\\)+\\'" fstring)
716        (nreverse flist))
717       ;; A single string spec at the beginning of the spec.
718       ((string-match "\\`%[sc][^%]+\\'" fstring)
719        (list (car flist) (substring fstring 2)))
720       ;; A single string spec in the middle of the spec.
721       ((string-match "\\`\\([^%]+\\)%[sc]\\([^%]+\\)\\'" fstring)
722        (list (match-string 1 fstring) (car flist) (match-string 2 fstring)))
723       ;; A single string spec in the end of the spec.
724       ((string-match "\\`\\([^%]+\\)%[sc]\\'" fstring)
725        (list (match-string 1 fstring) (car flist)))
726       ;; Only string (and %) specs (XEmacs only!)
727       ((and (featurep 'xemacs)
728             gnus-make-format-preserve-properties
729             (string-match
730              "\\`\\([^%]*\\(%%\\|%-?\\([1-9][0-9]*\\)?s\\)\\)*[^%]*\\'"
731              fstring))
732        (list (cons 'gnus-xmas-format (cons fstring (nreverse flist)))))
733       ;; A more complex spec.
734       (t
735        (list (cons 'format (cons fstring (nreverse flist)))))))
736
737     (if insert
738         (when result
739           (if dontinsert
740               result
741             (cons 'insert result)))
742       (cond ((stringp result)
743              result)
744             ((consp result)
745              (cons 'concat result))
746             (t "")))))
747
748 (defun gnus-eval-format (format &optional alist props)
749   "Eval the format variable FORMAT, using ALIST.
750 If PROPS, insert the result."
751   (let ((form (gnus-parse-format format alist props)))
752     (if props
753         (gnus-add-text-properties (point) (progn (eval form) (point)) props)
754       (eval form))))
755
756 (defun gnus-compile ()
757   "Byte-compile the user-defined format specs."
758   (interactive)
759   (require 'bytecomp)
760   (let ((entries gnus-format-specs)
761         (byte-compile-warnings '(unresolved callargs redefine))
762         entry type compiled-function)
763     (save-excursion
764       (gnus-message 7 "Compiling format specs...")
765
766       (while entries
767         (setq entry (pop entries)
768               type (car entry))
769         (if (memq type '(gnus-version version))
770             (setq gnus-format-specs (delq entry gnus-format-specs))
771           (let ((form (caddr entry)))
772             (when (and (listp form)
773                        ;; Under GNU Emacs, it's (byte-code ...)
774                        (not (eq 'byte-code (car form)))
775                        ;; Under XEmacs, it's (funcall #<compiled-function ...>)
776                        (not (and (eq 'funcall (car form))
777                                  (byte-code-function-p (cadr form)))))
778               (defalias 'gnus-tmp-func `(lambda () ,form))
779               (byte-compile 'gnus-tmp-func)
780               (setq compiled-function (gnus-byte-code 'gnus-tmp-func))
781               (set (intern (format "gnus-%s-line-format-spec" type))
782                    compiled-function)
783               (let ((elem (cdr (assq type gnus-format-specs-compiled))))
784                 (if elem
785                     (set-alist 'elem (cadr entry) compiled-function)
786                   (setq elem (list (cadr entry) compiled-function)))
787                 (set-alist 'gnus-format-specs-compiled type elem))))))
788
789       (push (cons 'version emacs-version) gnus-format-specs)
790       (gnus-message 7 "Compiling user specs...done"))))
791
792 (defun gnus-set-format (type &optional insertable)
793   (set (intern (format "gnus-%s-line-format-spec" type))
794        (gnus-parse-format
795         (symbol-value (intern (format "gnus-%s-line-format" type)))
796         (symbol-value (intern (format "gnus-%s-line-format-alist" type)))
797         insertable)))
798
799 (gnus-ems-redefine)
800
801 (provide 'gnus-spec)
802
803 ;; Local Variables:
804 ;; coding: iso-8859-1
805 ;; End:
806
807 ;;; gnus-spec.el ends here