gnus-spec.el 26 KB

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