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