1 ;;; gnus-spec.el --- format spec functions for Gnus
2 ;; Copyright (C) 1996,97,98 Free Software Foundation, Inc.
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;; This file is part of GNU Emacs.
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING. If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
28 (eval-when-compile (require 'cl
))
30 (eval-when-compile (require 'cl
))
34 ;;; Internal variables.
36 (defvar gnus-summary-mark-positions nil
)
37 (defvar gnus-group-mark-positions nil
)
38 (defvar gnus-group-indentation
"")
40 ;; Format specs. The chunks below are the machine-generated forms
41 ;; that are to be evaled as the result of the default format strings.
42 ;; We write them in here to get them byte-compiled. That way the
43 ;; default actions will be quite fast, while still retaining the full
44 ;; flexibility of the user-defined format specs.
46 ;; First we have lots of dummy defvars to let the compiler know these
47 ;; are really dynamic variables.
49 (defvar gnus-tmp-unread
)
50 (defvar gnus-tmp-replied
)
51 (defvar gnus-tmp-score-char
)
52 (defvar gnus-tmp-indentation
)
53 (defvar gnus-tmp-opening-bracket
)
54 (defvar gnus-tmp-lines
)
55 (defvar gnus-tmp-name
)
56 (defvar gnus-tmp-closing-bracket
)
57 (defvar gnus-tmp-subject-or-nil
)
58 (defvar gnus-tmp-subject
)
59 (defvar gnus-tmp-marked
)
60 (defvar gnus-tmp-marked-mark
)
61 (defvar gnus-tmp-subscribed
)
62 (defvar gnus-tmp-process-marked
)
63 (defvar gnus-tmp-number-of-unread
)
64 (defvar gnus-tmp-group-name
)
65 (defvar gnus-tmp-group
)
66 (defvar gnus-tmp-article-number
)
67 (defvar gnus-tmp-unread-and-unselected
)
68 (defvar gnus-tmp-news-method
)
69 (defvar gnus-tmp-news-server
)
70 (defvar gnus-tmp-article-number
)
71 (defvar gnus-mouse-face
)
72 (defvar gnus-mouse-face-prop
)
74 (defun gnus-summary-line-format-spec ()
75 (insert gnus-tmp-unread gnus-tmp-replied
76 gnus-tmp-score-char gnus-tmp-indentation
)
77 (gnus-put-text-property
81 gnus-tmp-opening-bracket
84 (if (> (length gnus-tmp-name
) 20)
85 (substring gnus-tmp-name
0 20)
87 gnus-tmp-closing-bracket
)
89 gnus-mouse-face-prop gnus-mouse-face
)
90 (insert " " gnus-tmp-subject-or-nil
"\n"))
92 (defvar gnus-summary-line-format-spec
93 (gnus-byte-code 'gnus-summary-line-format-spec
))
95 (defun gnus-summary-dummy-line-format-spec ()
97 (gnus-put-text-property
102 gnus-mouse-face-prop gnus-mouse-face
)
103 (insert " " gnus-tmp-subject
"\n"))
105 (defvar gnus-summary-dummy-line-format-spec
106 (gnus-byte-code 'gnus-summary-dummy-line-format-spec
))
108 (defun gnus-group-line-format-spec ()
109 (insert gnus-tmp-marked-mark gnus-tmp-subscribed
110 gnus-tmp-process-marked
111 gnus-group-indentation
112 (format "%5s: " gnus-tmp-number-of-unread
))
113 (gnus-put-text-property
116 (insert gnus-tmp-group
"\n")
118 gnus-mouse-face-prop gnus-mouse-face
))
119 (defvar gnus-group-line-format-spec
120 (gnus-byte-code 'gnus-group-line-format-spec
))
122 (defvar gnus-format-specs
123 `((version .
,emacs-version
)
124 (group "%M\%S\%p\%P\%5y: %(%g%)%l\n" ,gnus-group-line-format-spec
)
125 (summary-dummy "* %(: :%) %S\n"
126 ,gnus-summary-dummy-line-format-spec
)
127 (summary "%U\%R\%z\%I\%(%[%4L: %-20,20n%]%) %s\n"
128 ,gnus-summary-line-format-spec
))
129 "Alist of format specs.")
131 (defvar gnus-article-mode-line-format-spec nil
)
132 (defvar gnus-summary-mode-line-format-spec nil
)
133 (defvar gnus-group-mode-line-format-spec nil
)
135 ;;; Phew. All that gruft is over, fortunately.
138 (defun gnus-update-format (var)
139 "Update the format specification near point."
144 ;; Find the end of the current word.
145 (re-search-forward "[ \t\n]" nil t
)
147 (when (re-search-backward "\\(gnus-[-a-z]+-line-format\\)" nil t
)
149 (let* ((type (intern (progn (string-match "gnus-\\([-a-z]+\\)-line" var
)
150 (match-string 1 var
))))
151 (entry (assq type gnus-format-specs
))
154 (setq gnus-format-specs
(delq entry gnus-format-specs
)))
156 (intern (format "%s-spec" var
))
157 (gnus-parse-format (setq value
(symbol-value (intern var
)))
158 (symbol-value (intern (format "%s-alist" var
)))
159 (not (string-match "mode" var
))))
160 (setq spec
(symbol-value (intern (format "%s-spec" var
))))
161 (push (list type value spec
) gnus-format-specs
)
163 (pop-to-buffer "*Gnus Format*")
165 (lisp-interaction-mode)
166 (insert (pp-to-string spec
))))
168 (defun gnus-update-format-specifications (&optional force
&rest types
)
169 "Update all (necessary) format specifications."
170 ;; Make the indentation array.
171 ;; See whether all the stored info needs to be flushed.
173 (not (equal emacs-version
174 (cdr (assq 'version gnus-format-specs
)))))
175 (setq gnus-format-specs nil
))
177 ;; Go through all the formats and see whether they need updating.
178 (let (new-format entry type val
)
179 (while (setq type
(pop types
))
180 ;; Jump to the proper buffer to find out the value of
181 ;; the variable, if possible. (It may be buffer-local.)
183 (let ((buffer (intern (format "gnus-%s-buffer" type
)))
185 (when (and (boundp buffer
)
186 (setq val
(symbol-value buffer
))
187 (gnus-buffer-exists-p val
))
189 (setq new-format
(symbol-value
190 (intern (format "gnus-%s-line-format" type
)))))
191 (setq entry
(cdr (assq type gnus-format-specs
)))
193 (equal (car entry
) new-format
))
194 ;; Use the old format.
195 (set (intern (format "gnus-%s-line-format-spec" type
))
197 ;; This is a new format.
199 (if (not (stringp new-format
))
200 ;; This is a function call or something.
202 ;; This is a "real" format.
206 (intern (format "gnus-%s-line-format-alist"
207 (if (eq type
'article-mode
)
208 'summary-mode type
))))
209 (not (string-match "mode$" (symbol-name type
))))))
210 ;; Enter the new format spec into the list.
213 (setcar (cdr entry
) val
)
214 (setcar entry new-format
))
215 (push (list type new-format val
) gnus-format-specs
))
216 (set (intern (format "gnus-%s-line-format-spec" type
)) val
)))))
218 (unless (assq 'version gnus-format-specs
)
219 (push (cons 'version emacs-version
) gnus-format-specs
)))
221 (defvar gnus-mouse-face-0
'highlight
)
222 (defvar gnus-mouse-face-1
'highlight
)
223 (defvar gnus-mouse-face-2
'highlight
)
224 (defvar gnus-mouse-face-3
'highlight
)
225 (defvar gnus-mouse-face-4
'highlight
)
227 (defun gnus-mouse-face-function (form type
)
228 `(gnus-put-text-property
229 (point) (progn ,@form
(point))
233 `(quote ,(symbol-value (intern (format "gnus-mouse-face-%d" type
)))))))
235 (defvar gnus-face-0
'bold
)
236 (defvar gnus-face-1
'italic
)
237 (defvar gnus-face-2
'bold-italic
)
238 (defvar gnus-face-3
'bold
)
239 (defvar gnus-face-4
'bold
)
241 (defun gnus-face-face-function (form type
)
242 `(gnus-add-text-properties
243 (point) (progn ,@form
(point))
244 '(gnus-face t face
,(symbol-value (intern (format "gnus-face-%d" type
))))))
246 (defun gnus-tilde-max-form (el max-width
)
247 "Return a form that limits EL to MAX-WIDTH."
248 (let ((max (abs max-width
)))
250 `(if (> (length ,el
) ,max
)
252 `(substring ,el
(- (length el
) ,max
))
253 `(substring ,el
0 ,max
))
255 `(let ((val (eval ,el
)))
256 (if (> (length val
) ,max
)
258 `(substring val
(- (length val
) ,max
))
259 `(substring val
0 ,max
))
262 (defun gnus-tilde-cut-form (el cut-width
)
263 "Return a form that cuts CUT-WIDTH off of EL."
264 (let ((cut (abs cut-width
)))
266 `(if (> (length ,el
) ,cut
)
268 `(substring ,el
0 (- (length el
) ,cut
))
269 `(substring ,el
,cut
))
271 `(let ((val (eval ,el
)))
272 (if (> (length val
) ,cut
)
274 `(substring val
0 (- (length val
) ,cut
))
275 `(substring val
,cut
))
278 (defun gnus-tilde-ignore-form (el ignore-value
)
279 "Return a form that is blank when EL is IGNORE-VALUE."
281 `(if (equal ,el
,ignore-value
)
283 `(let ((val (eval ,el
)))
284 (if (equal val
,ignore-value
)
287 (defun gnus-parse-format (format spec-alist
&optional insert
)
288 ;; This function parses the FORMAT string with the help of the
289 ;; SPEC-ALIST and returns a list that can be eval'ed to return the
290 ;; string. If the FORMAT string contains the specifiers %( and %)
291 ;; the text between them will have the mouse-face text property.
293 "\\`\\(.*\\)%[0-9]?[{(]\\(.*\\)%[0-9]?[})]\\(.*\n?\\)\\'"
295 (gnus-parse-complex-format format spec-alist
)
296 ;; This is a simple format.
297 (gnus-parse-simple-format format spec-alist insert
)))
299 (defun gnus-parse-complex-format (format spec-alist
)
301 (gnus-set-work-buffer)
303 (goto-char (point-min))
304 (while (re-search-forward "\"" nil t
)
305 (replace-match "\\\"" nil t
))
306 (goto-char (point-min))
308 (while (re-search-forward "%\\([0-9]+\\)?\\([{}()]\\)" nil t
)
309 (let ((number (if (match-beginning 1)
310 (match-string 1) "0"))
311 (delim (aref (match-string 2) 0)))
312 (if (or (= delim ?\
()
314 (replace-match (concat "\"(" (if (= delim ?\
() "mouse" "face")
316 (replace-match "\")\""))))
317 (goto-char (point-max))
319 (goto-char (point-min))
320 (let ((form (read (current-buffer))))
321 (cons 'progn
(gnus-complex-form-to-spec form spec-alist
)))))
323 (defun gnus-complex-form-to-spec (form spec-alist
)
328 (gnus-parse-simple-format sform spec-alist t
)
329 (funcall (intern (format "gnus-%s-face-function" (car sform
)))
330 (gnus-complex-form-to-spec (cddr sform
) spec-alist
)
334 (defun gnus-parse-simple-format (format spec-alist
&optional insert
)
335 ;; This function parses the FORMAT string with the help of the
336 ;; SPEC-ALIST and returns a list that can be eval'ed to return a
339 spec flist fstring elem result dontinsert user-defined
340 type value pad-width spec-beg cut-width ignore-value
341 tilde-form tilde elem-type
)
343 (gnus-set-work-buffer)
345 (goto-char (point-min))
346 (while (re-search-forward "%" nil t
)
347 (setq user-defined nil
354 (setq spec-beg
(1- (point)))
356 ;; Parse this spec fully.
359 ((looking-at "\\([-.0-9]+\\)\\(,[-0-9]+\\)?")
360 (setq pad-width
(string-to-number (match-string 1)))
361 (when (match-beginning 2)
362 (setq max-width
(string-to-number (buffer-substring
363 (1+ (match-beginning 2))
365 (goto-char (match-end 0)))
368 (setq tilde
(read (current-buffer))
372 ((memq type
'(pad pad-left
))
373 (setq pad-width value
))
374 ((eq type
'pad-right
)
375 (setq pad-width
(- value
)))
376 ((memq type
'(max-right max
))
377 (setq max-width value
))
379 (setq max-width
(- value
)))
380 ((memq type
'(cut cut-left
))
381 (setq cut-width value
))
382 ((eq type
'cut-right
)
383 (setq cut-width
(- value
)))
386 (if (stringp value
) value
(format "%s" value
))))
388 (setq tilde-form value
))
390 (error "Unknown tilde type: %s" tilde
)))
394 ;; User-defined spec -- find the spec name.
395 (when (= (setq spec
(following-char)) ?u
)
397 (setq user-defined
(following-char)))
399 (delete-region spec-beg
(point))
401 ;; Now we have all the relevant data on this spec, so
402 ;; we start doing stuff.
405 ;; "%%" just results in a "%".
410 (setq elem
(list tilde-form ?s
)))
411 ;; Treat user defined format specifiers specially.
415 (list (intern (format "gnus-user-format-function-%c"
419 ;; Find the specification from `spec-alist'.
420 ((setq elem
(cdr (assq spec spec-alist
))))
422 (setq elem
'("*" ?s
))))
423 (setq elem-type
(cadr elem
))
424 ;; Insert the new format elements.
426 (insert (number-to-string pad-width
)))
427 ;; Create the form to be evaled.
428 (if (or max-width cut-width ignore-value
)
431 (let ((el (car elem
)))
432 (cond ((= (cadr elem
) ?c
)
433 (setq el
(list 'char-to-string el
)))
435 (setq el
(list 'int-to-string el
))))
437 (setq el
(gnus-tilde-ignore-form el ignore-value
)))
439 (setq el
(gnus-tilde-cut-form el cut-width
)))
441 (setq el
(gnus-tilde-max-form el max-width
)))
444 (push (car elem
) flist
))))
445 (setq fstring
(buffer-string)))
447 ;; Do some postprocessing to increase efficiency.
452 ((string= fstring
"")
454 ;; Not a format string.
455 ((not (string-match "%" fstring
))
457 ;; A format string with just a single string spec.
458 ((string= fstring
"%s")
460 ;; A single character.
461 ((string= fstring
"%c")
464 ((string= fstring
"%d")
467 (list `(princ ,(car flist
)))
468 (list `(int-to-string ,(car flist
)))))
469 ;; Just lots of chars and strings.
470 ((string-match "\\`\\(%[cs]\\)+\\'" fstring
)
472 ;; A single string spec at the beginning of the spec.
473 ((string-match "\\`%[sc][^%]+\\'" fstring
)
474 (list (car flist
) (substring fstring
2)))
475 ;; A single string spec in the middle of the spec.
476 ((string-match "\\`\\([^%]+\\)%[sc]\\([^%]+\\)\\'" fstring
)
477 (list (match-string 1 fstring
) (car flist
) (match-string 2 fstring
)))
478 ;; A single string spec in the end of the spec.
479 ((string-match "\\`\\([^%]+\\)%[sc]\\'" fstring
)
480 (list (match-string 1 fstring
) (car flist
)))
481 ;; A more complex spec.
483 (list (cons 'format
(cons fstring
(nreverse flist
)))))))
489 (cons 'insert result
)))
490 (cond ((stringp result
)
493 (cons 'concat result
))
496 (defun gnus-eval-format (format &optional alist props
)
497 "Eval the format variable FORMAT, using ALIST.
498 If PROPS, insert the result."
499 (let ((form (gnus-parse-format format alist props
)))
501 (gnus-add-text-properties (point) (progn (eval form
) (point)) props
)
504 (defun gnus-compile ()
505 "Byte-compile the user-defined format specs."
508 (let ((entries gnus-format-specs
)
509 (byte-compile-warnings '(unresolved callargs redefine
))
512 (gnus-message 7 "Compiling format specs...")
515 (setq entry
(pop entries
))
516 (if (eq (car entry
) 'version
)
517 (setq gnus-format-specs
(delq entry gnus-format-specs
))
518 (let ((form (caddr entry
)))
519 (when (and (listp form
)
520 ;; Under GNU Emacs, it's (byte-code ...)
521 (not (eq 'byte-code
(car form
)))
522 ;; Under XEmacs, it's (funcall #<compiled-function ...>)
523 (not (and (eq 'funcall
(car form
))
524 (compiled-function-p (cadr form
)))))
525 (fset 'gnus-tmp-func
`(lambda () ,form
))
526 (byte-compile 'gnus-tmp-func
)
527 (setcar (cddr entry
) (gnus-byte-code 'gnus-tmp-func
))))))
529 (push (cons 'version emacs-version
) gnus-format-specs
)
530 ;; Mark the .newsrc.eld file as "dirty".
532 (gnus-message 7 "Compiling user specs...done"))))
534 (defun gnus-set-format (type &optional insertable
)
535 (set (intern (format "gnus-%s-line-format-spec" type
))
537 (symbol-value (intern (format "gnus-%s-line-format" type
)))
538 (symbol-value (intern (format "gnus-%s-line-format-alist" type
)))
544 ;;; gnus-spec.el ends here