Fixed typo.
[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, 2004
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 (gnus-pp-to-string spec))))
193
194 (eval-when-compile (defvar unchanged))
195
196 (put 'gnus-search-or-regist-spec 'lisp-indent-function 4)
197 (defmacro gnus-search-or-regist-spec (specs type format val &rest body)
198   `(let* ((entry (assq ,type ,specs))
199           (elem (assoc ,format (cdr entry))))
200      ;; That `(cdr elem)' returns non-nil means the spec for `type'
201      ;; doesn't need to be updated.
202      (or (cdr elem)
203          ;; This variable is set beforehand.
204          (setq unchanged nil)
205          ;; Update the spec.  Where `body' will modify `val'.  This
206          ;; section will be skipped if compiling the spec is disabled.
207          (when (progn ,@body)
208            (if entry
209                (if elem
210                    (setcdr elem ,val)
211                  (setcdr entry (cons (cons ,format ,val) (cdr entry))))
212              (push (list ,type (cons ,format ,val)) ,specs))
213            (gnus-product-variable-touch (quote ,specs)))
214          ;; Return the new spec without compiling.
215          ,val)))
216
217 (defun gnus-update-format-specification-1 (type format val)
218   (set (intern (format "gnus-%s-line-format-spec" type))
219        (gnus-search-or-regist-spec
220            gnus-format-specs-compiled type format val
221          (when (and gnus-compile-user-specs val)
222            (setq val (prog1
223                          (progn
224                            (fset 'gnus-tmp-func `(lambda () ,val))
225                            (require 'bytecomp)
226                            (let (byte-compile-warnings)
227                              (byte-compile 'gnus-tmp-func))
228                            (gnus-byte-code 'gnus-tmp-func))
229                        (when (get-buffer "*Compile-Log*")
230                          (bury-buffer "*Compile-Log*"))
231                        (when (get-buffer "*Compile-Log-Show*")
232                          (bury-buffer "*Compile-Log-Show*"))))))))
233
234 (defun gnus-update-format-specifications (&optional force &rest types)
235   "Update all (necessary) format specifications.
236 Return a list of updated types."
237   ;; Make the indentation array.
238   ;; See whether all the stored info needs to be flushed.
239   (when force
240     (message "%s" "Force update format specs.")
241     (setq gnus-format-specs nil
242           gnus-format-specs-compiled nil)
243     (gnus-product-variable-touch 'gnus-format-specs
244                                  'gnus-format-specs-compiled))
245   ;; Flush the group format spec cache if there's the grouplens stuff.
246   (let ((spec (assq 'group gnus-format-specs)))
247     (when (and (memq 'group types)
248                (string-match " gnus-tmp-grouplens[ )]"
249                              (gnus-prin1-to-string (cdr spec))))
250       (setq gnus-format-specs (delq spec gnus-format-specs)
251             spec (assq 'group gnus-format-specs-compiled)
252             gnus-format-specs-compiled (delq spec gnus-format-specs-compiled))))
253
254   ;; Go through all the formats and see whether they need updating.
255   (let (new-format type val unchanged updated)
256     (save-excursion
257       (while (setq type (pop types))
258         ;; Jump to the proper buffer to find out the value of the
259         ;; variable, if possible.  (It may be buffer-local.)
260         (let ((buffer (intern (format "gnus-%s-buffer" type))))
261           (when (and (boundp buffer)
262                      (setq val (symbol-value buffer))
263                      (gnus-buffer-exists-p val))
264             (set-buffer val))
265           (setq new-format (symbol-value
266                             (intern (format "gnus-%s-line-format" type)))))
267         (setq unchanged t)
268         (or (gnus-update-format-specification-1 type new-format nil)
269             ;; This is a new format.
270             (gnus-update-format-specification-1
271              type new-format
272              (gnus-search-or-regist-spec
273                  gnus-format-specs type new-format val
274                (setq val (if (stringp new-format)
275                              ;; This is a "real" format.
276                              (gnus-parse-format
277                               new-format
278                               (symbol-value
279                                (intern (format "gnus-%s-line-format-alist"
280                                                type)))
281                               (not (string-match "mode$"
282                                                  (symbol-name type))))
283                            ;; This is a function call or something.
284                            new-format)))))
285         (unless unchanged
286           (push type updated))))
287     updated))
288
289 (defvar gnus-mouse-face-0 'highlight)
290 (defvar gnus-mouse-face-1 'highlight)
291 (defvar gnus-mouse-face-2 'highlight)
292 (defvar gnus-mouse-face-3 'highlight)
293 (defvar gnus-mouse-face-4 'highlight)
294
295 (defun gnus-mouse-face-function (form type)
296   `(gnus-put-text-property
297     (point) (progn ,@form (point))
298     gnus-mouse-face-prop
299     ,(if (equal type 0)
300          'gnus-mouse-face
301        `(quote ,(symbol-value (intern (format "gnus-mouse-face-%d" type)))))))
302
303 (defvar gnus-face-0 'bold)
304 (defvar gnus-face-1 'italic)
305 (defvar gnus-face-2 'bold-italic)
306 (defvar gnus-face-3 'bold)
307 (defvar gnus-face-4 'bold)
308
309 (defun gnus-face-face-function (form type)
310   `(gnus-add-text-properties
311     (point) (progn ,@form (point))
312     '(gnus-face t face ,(symbol-value (intern (format "gnus-face-%d" type))))))
313
314 (defun gnus-balloon-face-function (form type)
315   `(gnus-put-text-property
316     (point) (progn ,@form (point))
317     ,(if (fboundp 'balloon-help-mode)
318          ''balloon-help
319        ''help-echo)
320     ,(intern (format "gnus-balloon-face-%d" type))))
321
322 (defun gnus-spec-tab (column)
323   (if (> column 0)
324       `(insert (make-string (max (- ,column (current-column)) 0) ? ))
325     (let ((column (abs column)))
326       (if gnus-use-correct-string-widths
327           `(progn
328              (if (> (current-column) ,column)
329                  (while (progn
330                           (delete-backward-char 1)
331                           (> (current-column) ,column))))
332              (insert (make-string (max (- ,column (current-column)) 0) ? )))
333         `(progn
334            (if (> (current-column) ,column)
335                (delete-region (point)
336                               (- (point) (- (current-column) ,column)))
337              (insert (make-string (max (- ,column (current-column)) 0)
338                                   ? ))))))))
339
340 (defun gnus-correct-length (string)
341   "Return the correct width of STRING."
342   (let ((length 0))
343     (mapcar (lambda (char) (incf length (char-width char))) string)
344     length))
345
346 (defun gnus-correct-substring (string start &optional end)
347   (let ((wstart 0)
348         (wend 0)
349         (wseek 0)
350         (seek 0)
351         (length (length string))
352         (string (concat string "\0")))
353     ;; Find the start position.
354     (while (and (< seek length)
355                 (< wseek start))
356       (incf wseek (char-width (aref string seek)))
357       (incf seek))
358     (setq wstart seek)
359     ;; Find the end position.
360     (while (and (<= seek length)
361                 (or (not end)
362                     (<= wseek end)))
363       (incf wseek (char-width (aref string seek)))
364       (incf seek))
365     (setq wend seek)
366     (substring string wstart (1- wend))))
367
368 (defun gnus-string-width-function ()
369   (cond
370    (gnus-use-correct-string-widths
371     'gnus-correct-length)
372    ((fboundp 'string-width)
373     'string-width)
374    (t
375     'length)))
376
377 (defun gnus-substring-function ()
378   (cond
379    (gnus-use-correct-string-widths
380     'gnus-correct-substring)
381    ((fboundp 'string-width)
382     'gnus-correct-substring)
383    (t
384     'substring)))
385
386 (defun gnus-tilde-max-form (el max-width)
387   "Return a form that limits EL to MAX-WIDTH."
388   (let ((max (abs max-width))
389         (length-fun (gnus-string-width-function))
390         (substring-fun (gnus-substring-function)))
391     (if (symbolp el)
392         `(if (> (,length-fun ,el) ,max)
393              ,(if (< max-width 0)
394                   `(,substring-fun ,el (- (,length-fun ,el) ,max))
395                 `(,substring-fun ,el 0 ,max))
396            ,el)
397       `(let ((val (eval ,el)))
398          (if (> (,length-fun val) ,max)
399              ,(if (< max-width 0)
400                   `(,substring-fun val (- (,length-fun val) ,max))
401                 `(,substring-fun val 0 ,max))
402            val)))))
403
404 (defun gnus-tilde-cut-form (el cut-width)
405   "Return a form that cuts CUT-WIDTH off of EL."
406   (let ((cut (abs cut-width))
407         (length-fun (gnus-string-width-function))
408         (substring-fun (gnus-substring-function)))
409     (if (symbolp el)
410         `(if (> (,length-fun ,el) ,cut)
411              ,(if (< cut-width 0)
412                   `(,substring-fun ,el 0 (- (,length-fun ,el) ,cut))
413                 `(,substring-fun ,el ,cut))
414            ,el)
415       `(let ((val (eval ,el)))
416          (if (> (,length-fun val) ,cut)
417              ,(if (< cut-width 0)
418                   `(,substring-fun val 0 (- (,length-fun val) ,cut))
419                 `(,substring-fun val ,cut))
420            val)))))
421
422 (defun gnus-tilde-ignore-form (el ignore-value)
423   "Return a form that is blank when EL is IGNORE-VALUE."
424   (if (symbolp el)
425       `(if (equal ,el ,ignore-value)
426            "" ,el)
427     `(let ((val (eval ,el)))
428        (if (equal val ,ignore-value)
429            "" val))))
430
431 (defun gnus-pad-form (el pad-width)
432   "Return a form that pads EL to PAD-WIDTH accounting for multi-column
433 characters correctly. This is because `format' may pad to columns or to
434 characters when given a pad value."
435   (let ((pad (abs pad-width))
436         (side (< 0 pad-width))
437         (length-fun (gnus-string-width-function)))
438     (if (symbolp el)
439         `(let ((need (- ,pad (,length-fun ,el))))
440            (if (> need 0)
441                (concat ,(when side '(make-string need ?\ ))
442                        ,el
443                        ,(when (not side) '(make-string need ?\ )))
444              ,el))
445       `(let* ((val (eval ,el))
446               (need (- ,pad (,length-fun val))))
447          (if (> need 0)
448              (concat ,(when side '(make-string need ?\ ))
449                      val
450                      ,(when (not side) '(make-string need ?\ )))
451            val)))))
452
453 (defun gnus-parse-format (format spec-alist &optional insert)
454   ;; This function parses the FORMAT string with the help of the
455   ;; SPEC-ALIST and returns a list that can be eval'ed to return the
456   ;; string.  If the FORMAT string contains the specifiers %( and %)
457   ;; the text between them will have the mouse-face text property.
458   ;; If the FORMAT string contains the specifiers %[ and %], the text between
459   ;; them will have the balloon-help text property.
460   (let ((case-fold-search nil))
461     (if (string-match
462          "\\`\\(.*\\)%[0-9]?[{(«]\\(.*\\)%[0-9]?[»})]\\(.*\n?\\)\\'\\|%[-0-9]*=\\|%[-0-9]*\\*"
463          format)
464         (gnus-parse-complex-format format spec-alist)
465       ;; This is a simple format.
466       (gnus-parse-simple-format format spec-alist insert))))
467
468 (defun gnus-parse-complex-format (format spec-alist)
469   (let ((cursor-spec nil))
470     (save-excursion
471       (gnus-set-work-buffer)
472       (insert format)
473       (goto-char (point-min))
474       (while (re-search-forward "\"" nil t)
475         (replace-match "\\\"" nil t))
476       (goto-char (point-min))
477       (insert "(\"")
478       ;; Convert all font specs into font spec lists.
479       (while (re-search-forward "%\\([0-9]+\\)?\\([«»{}()]\\)" nil t)
480         (let ((number (if (match-beginning 1)
481                           (match-string 1) "0"))
482               (delim (aref (match-string 2) 0)))
483           (if (or (= delim ?\()
484                   (= delim ?\{)
485                   (= delim ?\«))
486               (replace-match (concat "\"("
487                                      (cond ((= delim ?\() "mouse")
488                                            ((= delim ?\{) "face")
489                                            (t "balloon"))
490                                      " " number " \"")
491                              t t)
492             (replace-match "\")\""))))
493       (goto-char (point-max))
494       (insert "\")")
495       ;; Convert point position commands.
496       (goto-char (point-min))
497       (let ((case-fold-search nil))
498         (while (re-search-forward "%\\([-0-9]+\\)?\\*" nil t)
499           (replace-match "\"(point)\"" t t)
500           (setq cursor-spec t)))
501       ;; Convert TAB commands.
502       (goto-char (point-min))
503       (while (re-search-forward "%\\([-0-9]+\\)=" nil t)
504         (replace-match (format "\"(tab %s)\"" (match-string 1)) t t))
505       ;; Convert the buffer into the spec.
506       (goto-char (point-min))
507       (let ((form (read (current-buffer))))
508         (if cursor-spec
509             `(let (gnus-position)
510                ,@(gnus-complex-form-to-spec form spec-alist)
511                (if gnus-position
512                    (gnus-put-text-property gnus-position (1+ gnus-position)
513                                            'gnus-position t)))
514           `(progn
515              ,@(gnus-complex-form-to-spec form spec-alist)))))))
516
517 (defun gnus-complex-form-to-spec (form spec-alist)
518   (delq nil
519         (mapcar
520          (lambda (sform)
521            (cond
522             ((stringp sform)
523              (gnus-parse-simple-format sform spec-alist t))
524             ((eq (car sform) 'point)
525              '(setq gnus-position (point)))
526             ((eq (car sform) 'tab)
527              (gnus-spec-tab (cadr sform)))
528             (t
529              (funcall (intern (format "gnus-%s-face-function" (car sform)))
530                       (gnus-complex-form-to-spec (cddr sform) spec-alist)
531                       (nth 1 sform)))))
532          form)))
533
534
535 (defun gnus-xmas-format (fstring &rest args)
536   "A version of `format' which preserves text properties.
537
538 Required for XEmacs, where the built in `format' function strips all text
539 properties from both the format string and any inserted strings.
540
541 Only supports the format sequence %s, and %% for inserting
542 literal % characters. A pad width and an optional - (to right pad)
543 are supported for %s."
544   (let ((re "%%\\|%\\(-\\)?\\([1-9][0-9]*\\)?s")
545         (n (length args)))
546     (with-temp-buffer
547       (insert fstring)
548       (goto-char (point-min))
549       (while (re-search-forward re nil t)
550         (goto-char (match-end 0))
551         (cond
552          ((string= (match-string 0) "%%")
553           (delete-char -1))
554          (t
555           (if (null args)
556               (error 'wrong-number-of-arguments #'my-format n fstring))
557           (let* ((minlen (string-to-int (or (match-string 2) "")))
558                  (arg (car args))
559                  (str (if (stringp arg) arg (format "%s" arg)))
560                  (lpad (null (match-string 1)))
561                  (padlen (max 0 (- minlen (length str)))))
562             (replace-match "")
563             (if lpad (insert-char ?\  padlen))
564             (insert str)
565             (unless lpad (insert-char ?\  padlen))
566             (setq args (cdr args))))))
567       (buffer-string))))
568
569 (defun gnus-parse-simple-format (format spec-alist &optional insert)
570   ;; This function parses the FORMAT string with the help of the
571   ;; SPEC-ALIST and returns a list that can be eval'ed to return a
572   ;; string.
573   (let (max-width
574         spec flist fstring elem result dontinsert user-defined
575         type value pad-width spec-beg cut-width ignore-value
576         tilde-form tilde elem-type extended-spec)
577     (save-excursion
578       (gnus-set-work-buffer)
579       (insert format)
580       (goto-char (point-min))
581       (while (search-forward "%" nil t)
582         (setq user-defined nil
583               spec-beg nil
584               pad-width nil
585               max-width nil
586               cut-width nil
587               ignore-value nil
588               tilde-form nil
589               extended-spec nil)
590         (setq spec-beg (1- (point)))
591
592         ;; Parse this spec fully.
593         (while
594             (cond
595              ((looking-at "\\([-.0-9]+\\)\\(,[-0-9]+\\)?")
596               (setq pad-width (string-to-number (match-string 1)))
597               (when (match-beginning 2)
598                 (setq max-width (string-to-number (buffer-substring
599                                                    (1+ (match-beginning 2))
600                                                    (match-end 2)))))
601               (goto-char (match-end 0)))
602              ((looking-at "~")
603               (forward-char 1)
604               (setq tilde (read (current-buffer))
605                     type (car tilde)
606                     value (cadr tilde))
607               (cond
608                ((memq type '(pad pad-left))
609                 (setq pad-width value))
610                ((eq type 'pad-right)
611                 (setq pad-width (- value)))
612                ((memq type '(max-right max))
613                 (setq max-width value))
614                ((eq type 'max-left)
615                 (setq max-width (- value)))
616                ((memq type '(cut cut-left))
617                 (setq cut-width value))
618                ((eq type 'cut-right)
619                 (setq cut-width (- value)))
620                ((eq type 'ignore)
621                 (setq ignore-value
622                       (if (stringp value) value (format "%s" value))))
623                ((eq type 'form)
624                 (setq tilde-form value))
625                (t
626                 (error "Unknown tilde type: %s" tilde)))
627               t)
628              (t
629               nil)))
630         (cond
631          ;; User-defined spec -- find the spec name.
632          ((eq (setq spec (char-after)) ?u)
633           (forward-char 1)
634           (when (and (eq (setq user-defined (char-after)) ?&)
635                      (looking-at "&\\([^;]+\\);"))
636             (setq user-defined (match-string 1))
637             (goto-char (match-end 1))))
638          ;; extended spec
639          ((and (eq spec ?&) (looking-at "&\\([^;]+\\);"))
640           (setq extended-spec (intern (match-string 1)))
641           (goto-char (match-end 1))))
642         (forward-char 1)
643         (delete-region spec-beg (point))
644
645         ;; Now we have all the relevant data on this spec, so
646         ;; we start doing stuff.
647         (insert "%")
648         (if (eq spec ?%)
649             ;; "%%" just results in a "%".
650             (insert "%")
651           (cond
652            ;; Do tilde forms.
653            ((eq spec ?@)
654             (setq elem (list tilde-form ?s)))
655            ;; Treat user defined format specifiers specially.
656            (user-defined
657             (setq elem
658                   (list
659                    (list (intern (format
660                                   (if (stringp user-defined)
661                                       "gnus-user-format-function-%s"
662                                     "gnus-user-format-function-%c")
663                                   user-defined))
664                          'gnus-tmp-header)
665                    ?s)))
666            ;; Find the specification from `spec-alist'.
667            ((setq elem (cdr (assq (or extended-spec spec) spec-alist))))
668            ;; We used to use "%l" for displaying the grouplens score.
669            ((eq spec ?l)
670             (setq elem '("" ?s)))
671            (t
672             (setq elem '("*" ?s))))
673           (setq elem-type (cadr elem))
674           ;; Insert the new format elements.
675           (when (and pad-width
676                      (not (and (featurep 'xemacs)
677                                gnus-use-correct-string-widths)))
678             (insert (number-to-string pad-width)))
679           ;; Create the form to be evaled.
680           (if (or max-width cut-width ignore-value
681                   (and (featurep 'xemacs)
682                        gnus-use-correct-string-widths))
683               (progn
684                 (insert ?s)
685                 (let ((el (car elem)))
686                   (cond ((= (cadr elem) ?c)
687                          (setq el (list 'char-to-string el)))
688                         ((= (cadr elem) ?d)
689                          (setq el (list 'int-to-string el))))
690                   (when ignore-value
691                     (setq el (gnus-tilde-ignore-form el ignore-value)))
692                   (when cut-width
693                     (setq el (gnus-tilde-cut-form el cut-width)))
694                   (when max-width
695                     (setq el (gnus-tilde-max-form el max-width)))
696                   (when pad-width
697                     (setq el (gnus-pad-form el pad-width)))
698                   (push el flist)))
699             (insert elem-type)
700             (push (car elem) flist))))
701       (setq fstring (buffer-substring-no-properties (point-min) (point-max))))
702
703     ;; Do some postprocessing to increase efficiency.
704     (setq
705      result
706      (cond
707       ;; Emptiness.
708       ((string= fstring "")
709        nil)
710       ;; Not a format string.
711       ((not (string-match "%" fstring))
712        (list fstring))
713       ;; A format string with just a single string spec.
714       ((string= fstring "%s")
715        (list (car flist)))
716       ;; A single character.
717       ((string= fstring "%c")
718        (list (car flist)))
719       ;; A single number.
720       ((string= fstring "%d")
721        (setq dontinsert t)
722        (if insert
723            (list `(princ ,(car flist)))
724          (list `(int-to-string ,(car flist)))))
725       ;; Just lots of chars and strings.
726       ((string-match "\\`\\(%[cs]\\)+\\'" fstring)
727        (nreverse flist))
728       ;; A single string spec at the beginning of the spec.
729       ((string-match "\\`%[sc][^%]+\\'" fstring)
730        (list (car flist) (substring fstring 2)))
731       ;; A single string spec in the middle of the spec.
732       ((string-match "\\`\\([^%]+\\)%[sc]\\([^%]+\\)\\'" fstring)
733        (list (match-string 1 fstring) (car flist) (match-string 2 fstring)))
734       ;; A single string spec in the end of the spec.
735       ((string-match "\\`\\([^%]+\\)%[sc]\\'" fstring)
736        (list (match-string 1 fstring) (car flist)))
737       ;; Only string (and %) specs (XEmacs only!)
738       ((and (featurep 'xemacs)
739             gnus-make-format-preserve-properties
740             (string-match
741              "\\`\\([^%]*\\(%%\\|%-?\\([1-9][0-9]*\\)?s\\)\\)*[^%]*\\'"
742              fstring))
743        (list (cons 'gnus-xmas-format (cons fstring (nreverse flist)))))
744       ;; A more complex spec.
745       (t
746        (list (cons 'format (cons fstring (nreverse flist)))))))
747
748     (if insert
749         (when result
750           (if dontinsert
751               result
752             (cons 'insert result)))
753       (cond ((stringp result)
754              result)
755             ((consp result)
756              (cons 'concat result))
757             (t "")))))
758
759 (defun gnus-eval-format (format &optional alist props)
760   "Eval the format variable FORMAT, using ALIST.
761 If PROPS, insert the result."
762   (let ((form (gnus-parse-format format alist props)))
763     (if props
764         (gnus-add-text-properties (point) (progn (eval form) (point)) props)
765       (eval form))))
766
767 (defun gnus-compile ()
768   "Byte-compile the user-defined format specs."
769   (interactive)
770   (require 'bytecomp)
771   (let ((entries gnus-format-specs)
772         (byte-compile-warnings '(unresolved callargs redefine))
773         entry type compiled-function)
774     (save-excursion
775       (gnus-message 7 "Compiling format specs...")
776
777       (while entries
778         (setq entry (pop entries)
779               type (car entry))
780         (if (memq type '(gnus-version version))
781             (setq gnus-format-specs (delq entry gnus-format-specs))
782           (let ((form (caddr entry)))
783             (when (and (listp form)
784                        ;; Under GNU Emacs, it's (byte-code ...)
785                        (not (eq 'byte-code (car form)))
786                        ;; Under XEmacs, it's (funcall #<compiled-function ...>)
787                        (not (and (eq 'funcall (car form))
788                                  (byte-code-function-p (cadr form)))))
789               (defalias 'gnus-tmp-func `(lambda () ,form))
790               (byte-compile 'gnus-tmp-func)
791               (setq compiled-function (gnus-byte-code 'gnus-tmp-func))
792               (set (intern (format "gnus-%s-line-format-spec" type))
793                    compiled-function)
794               (let ((elem (cdr (assq type gnus-format-specs-compiled))))
795                 (if elem
796                     (set-alist 'elem (cadr entry) compiled-function)
797                   (setq elem (list (cadr entry) compiled-function)))
798                 (set-alist 'gnus-format-specs-compiled type elem))))))
799
800       (push (cons 'version emacs-version) gnus-format-specs)
801       (gnus-message 7 "Compiling user specs...done"))))
802
803 (defun gnus-set-format (type &optional insertable)
804   (set (intern (format "gnus-%s-line-format-spec" type))
805        (gnus-parse-format
806         (symbol-value (intern (format "gnus-%s-line-format" type)))
807         (symbol-value (intern (format "gnus-%s-line-format-alist" type)))
808         insertable)))
809
810 (gnus-ems-redefine)
811
812 (provide 'gnus-spec)
813
814 ;; Local Variables:
815 ;; coding: iso-8859-1
816 ;; End:
817
818 ;;; gnus-spec.el ends here