* gnus-art.el (gnus-treat-display-grey-xface): New variable.
[gnus] / lisp / gnus-art.el
1 ;;; gnus-art.el --- article mode commands for Gnus
2 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002
3 ;;        Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; Keywords: news
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;;; Code:
28
29 (eval-when-compile (require 'cl))
30
31 (require 'gnus)
32 (require 'gnus-sum)
33 (require 'gnus-spec)
34 (require 'gnus-int)
35 (require 'gnus-win)
36 (require 'mm-bodies)
37 (require 'mail-parse)
38 (require 'mm-decode)
39 (require 'mm-view)
40 (require 'wid-edit)
41 (require 'mm-uu)
42
43 (autoload 'gnus-msg-mail "gnus-msg" nil t)
44 (autoload 'gnus-button-mailto "gnus-msg")
45 (autoload 'gnus-button-reply "gnus-msg" nil t)
46
47 (defgroup gnus-article nil
48   "Article display."
49   :link '(custom-manual "(gnus)The Article Buffer")
50   :group 'gnus)
51
52 (defgroup gnus-article-treat nil
53   "Treating article parts."
54   :link '(custom-manual "(gnus)Article Hiding")
55   :group 'gnus-article)
56
57 (defgroup gnus-article-hiding nil
58   "Hiding article parts."
59   :link '(custom-manual "(gnus)Article Hiding")
60   :group 'gnus-article)
61
62 (defgroup gnus-article-highlight nil
63   "Article highlighting."
64   :link '(custom-manual "(gnus)Article Highlighting")
65   :group 'gnus-article
66   :group 'gnus-visual)
67
68 (defgroup gnus-article-signature nil
69   "Article signatures."
70   :link '(custom-manual "(gnus)Article Signature")
71   :group 'gnus-article)
72
73 (defgroup gnus-article-headers nil
74   "Article headers."
75   :link '(custom-manual "(gnus)Hiding Headers")
76   :group 'gnus-article)
77
78 (defgroup gnus-article-washing nil
79   "Special commands on articles."
80   :link '(custom-manual "(gnus)Article Washing")
81   :group 'gnus-article)
82
83 (defgroup gnus-article-emphasis nil
84   "Fontisizing articles."
85   :link '(custom-manual "(gnus)Article Fontisizing")
86   :group 'gnus-article)
87
88 (defgroup gnus-article-saving nil
89   "Saving articles."
90   :link '(custom-manual "(gnus)Saving Articles")
91   :group 'gnus-article)
92
93 (defgroup gnus-article-mime nil
94   "Worshiping the MIME wonder."
95   :link '(custom-manual "(gnus)Using MIME")
96   :group 'gnus-article)
97
98 (defgroup gnus-article-buttons nil
99   "Pushable buttons in the article buffer."
100   :link '(custom-manual "(gnus)Article Buttons")
101   :group 'gnus-article)
102
103 (defgroup gnus-article-various nil
104   "Other article options."
105   :link '(custom-manual "(gnus)Misc Article")
106   :group 'gnus-article)
107
108 (defcustom gnus-ignored-headers
109   '("^Path:" "^Expires:" "^Date-Received:" "^References:" "^Xref:" "^Lines:"
110     "^Relay-Version:" "^Message-ID:" "^Approved:" "^Sender:" "^Received:"
111     "^X-UIDL:" "^MIME-Version:" "^Return-Path:" "^In-Reply-To:"
112     "^Content-Type:" "^Content-Transfer-Encoding:" "^X-WebTV-Signature:"
113     "^X-MimeOLE:" "^X-MSMail-Priority:" "^X-Priority:" "^X-Loop:"
114     "^X-Authentication-Warning:" "^X-MIME-Autoconverted:" "^X-Face"
115     "^X-Attribution:" "^X-Originating-IP:" "^Delivered-To:"
116     "^NNTP-[-A-Za-z]+:" "^Distribution:" "^X-no-archive:" "^X-Trace:"
117     "^X-Complaints-To:" "^X-NNTP-Posting-Host:" "^X-Orig.*:"
118     "^Abuse-Reports-To:" "^Cache-Post-Path:" "^X-Article-Creation-Date:"
119     "^X-Poster:" "^X-Mail2News-Path:" "^X-Server-Date:" "^X-Cache:"
120     "^Originator:" "^X-Problems-To:" "^X-Auth-User:" "^X-Post-Time:"
121     "^X-Admin:" "^X-UID:" "^Resent-[-A-Za-z]+:" "^X-Mailing-List:"
122     "^Precedence:" "^Original-[-A-Za-z]+:" "^X-filename:" "^X-Orcpt:"
123     "^Old-Received:" "^X-Pgp" "^X-Auth:" "^X-From-Line:"
124     "^X-Gnus-Article-Number:" "^X-Majordomo:" "^X-Url:" "^X-Sender:"
125     "^MBOX-Line" "^Priority:" "^X400-[-A-Za-z]+:"
126     "^Status:" "^X-Gnus-Mail-Source:" "^Cancel-Lock:"
127     "^X-FTN" "^X-EXP32-SerialNo:" "^Encoding:" "^Importance:"
128     "^Autoforwarded:" "^Original-Encoded-Information-Types:" "^X-Ya-Pop3:"
129     "^X-Face-Version:" "^X-Vms-To:" "^X-ML-NAME:" "^X-ML-COUNT:"
130     "^Mailing-List:" "^X-finfo:" "^X-md5sum:" "^X-md5sum-Origin:"
131     "^X-Sun-Charset:" "^X-Accept-Language:" "^X-Envelope-Sender:"
132     "^List-[A-Za-z]+:" "^X-Listprocessor-Version:"
133     "^X-Received:" "^X-Distribute:" "^X-Sequence:" "^X-Juno-Line-Breaks:"
134     "^X-Notes-Item:" "^X-MS-TNEF-Correlator:" "^x-uunet-gateway:"
135     "^X-Received:" "^Content-length:" "X-precedence:"
136     "^X-Authenticated-User:" "^X-Comment" "^X-Report:" "^X-Abuse-Info:"
137     "^X-HTTP-Proxy:" "^X-Mydeja-Info:" "^X-Copyright" "^X-No-Markup:"
138     "^X-Abuse-Info:" "^X-From_:" "^X-Accept-Language:" "^Errors-To:"
139     "^X-BeenThere:" "^X-Mailman-Version:" "^List-Help:" "^List-Post:"
140     "^List-Subscribe:" "^List-Id:" "^List-Unsubscribe:" "^List-Archive:"
141      "^X-Content-length:" "^X-Posting-Agent:" "^Original-Received:"
142      "^X-Request-PGP:" "^X-Fingerprint:" "^X-WRIEnvto:" "^X-WRIEnvfrom:"
143      "^X-Virus-Scanned:" "^X-Delivery-Agent:" "^Posted-Date:" "^X-Gateway:"
144      "^X-Local-Origin:" "^X-Local-Destination:" "^X-UserInfo1:"
145      "^X-Received-Date:")
146   "*All headers that start with this regexp will be hidden.
147 This variable can also be a list of regexps of headers to be ignored.
148 If `gnus-visible-headers' is non-nil, this variable will be ignored."
149   :type '(choice :custom-show nil
150                  regexp
151                  (repeat regexp))
152   :group 'gnus-article-hiding)
153
154 (defcustom gnus-visible-headers
155   "^From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^[BGF]?Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Mail-Followup-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-From:\\|^X-Sent:"
156   "*All headers that do not match this regexp will be hidden.
157 This variable can also be a list of regexp of headers to remain visible.
158 If this variable is non-nil, `gnus-ignored-headers' will be ignored."
159   :type '(repeat :value-to-internal (lambda (widget value)
160                                       (custom-split-regexp-maybe value))
161                  :match (lambda (widget value)
162                           (or (stringp value)
163                               (widget-editable-list-match widget value)))
164                  regexp)
165   :group 'gnus-article-hiding)
166
167 (defcustom gnus-sorted-header-list
168   '("^From:" "^Subject:" "^Summary:" "^Keywords:" "^Newsgroups:"
169     "^Followup-To:" "^To:" "^Cc:" "^Date:" "^Organization:")
170   "*This variable is a list of regular expressions.
171 If it is non-nil, headers that match the regular expressions will
172 be placed first in the article buffer in the sequence specified by
173 this list."
174   :type '(repeat regexp)
175   :group 'gnus-article-hiding)
176
177 (defcustom gnus-boring-article-headers '(empty followup-to reply-to)
178   "Headers that are only to be displayed if they have interesting data.
179 Possible values in this list are:
180
181   'empty       Headers with no content.
182   'newsgroups  Newsgroup identical to Gnus group.
183   'to-address  To identical to To-address.
184   'followup-to Followup-to identical to Newsgroups.
185   'reply-to    Reply-to identical to From.
186   'date        Date less than four days old.
187   'long-to     To and/or Cc longer than 1024 characters.
188   'many-to     Multiple To and/or Cc."
189   :type '(set (const :tag "Headers with no content." empty)
190               (const :tag "Newsgroups identical to Gnus group." newsgroups)
191               (const :tag "To identical to To-address." to-address)
192               (const :tag "Followup-to identical to Newsgroups." followup-to)
193               (const :tag "Reply-to identical to From." reply-to)
194               (const :tag "Date less than four days old." date)
195               (const :tag "To and/or Cc longer than 1024 characters." long-to)
196               (const :tag "Multiple To and/or Cc headers." many-to))
197   :group 'gnus-article-hiding)
198
199 (defcustom gnus-signature-separator '("^-- $" "^-- *$")
200   "Regexp matching signature separator.
201 This can also be a list of regexps.  In that case, it will be checked
202 from head to tail looking for a separator.  Searches will be done from
203 the end of the buffer."
204   :type '(repeat string)
205   :group 'gnus-article-signature)
206
207 (defcustom gnus-signature-limit nil
208   "Provide a limit to what is considered a signature.
209 If it is a number, no signature may not be longer (in characters) than
210 that number.  If it is a floating point number, no signature may be
211 longer (in lines) than that number.  If it is a function, the function
212 will be called without any parameters, and if it returns nil, there is
213 no signature in the buffer.  If it is a string, it will be used as a
214 regexp.  If it matches, the text in question is not a signature."
215   :type '(choice (integer :value 200)
216                  (number :value 4.0)
217                  (function :value fun)
218                  (regexp :value ".*"))
219   :group 'gnus-article-signature)
220
221 (defcustom gnus-hidden-properties '(invisible t intangible t)
222   "Property list to use for hiding text."
223   :type 'sexp
224   :group 'gnus-article-hiding)
225
226 ;; Fixme: This isn't the right thing for mixed graphical and and
227 ;; non-graphical frames in a session.
228 (defcustom gnus-article-x-face-command
229   (if (featurep 'xemacs)
230       (if (or (gnus-image-type-available-p 'xface)
231               (gnus-image-type-available-p 'pbm))
232           'gnus-display-x-face-in-from
233         "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | ee -")
234     (if (gnus-image-type-available-p 'pbm)
235         'gnus-display-x-face-in-from
236       "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | \
237 display -"))
238   "*String or function to be executed to display an X-Face header.
239 If it is a string, the command will be executed in a sub-shell
240 asynchronously.  The compressed face will be piped to this command."
241   :type `(choice string
242                  (function-item gnus-display-x-face-in-from)
243                  function)
244   :version "21.1"
245   :group 'gnus-article-washing)
246
247 (defcustom gnus-article-x-face-too-ugly nil
248   "Regexp matching posters whose face shouldn't be shown automatically."
249   :type '(choice regexp (const nil))
250   :group 'gnus-article-washing)
251
252 (defcustom gnus-article-banner-alist nil
253   "Banner alist for stripping.
254 For example,
255      ((egroups . \"^[ \\t\\n]*-------------------+\\\\( \\\\(e\\\\|Yahoo! \\\\)Groups Sponsor -+\\\\)?....\\n\\\\(.+\\n\\\\)+\"))"
256   :version "21.1"
257   :type '(repeat (cons symbol regexp))
258   :group 'gnus-article-washing)
259
260 (gnus-define-group-parameter
261  banner
262  :variable-document
263  "Alist of regexps (to match group names) and banner."
264  :variable-group gnus-article-washing
265  :parameter-type
266  '(choice :tag "Banner"
267           :value nil
268           (const :tag "Remove signature" signature)
269           (symbol :tag "Item in `gnus-article-banner-alist'" none)
270           regexp
271           (const :tag "None" nil))
272  :parameter-document
273  "If non-nil, specify how to remove `banners' from articles.
274
275 Symbol `signature' means to remove signatures delimited by
276 `gnus-signature-separator'.  Any other symbol is used to look up a
277 regular expression to match the banner in `gnus-article-banner-alist'.
278 A string is used as a regular expression to match the banner
279 directly.")
280
281 (defcustom gnus-emphasis-alist
282   (let ((format
283          "\\(\\s-\\|^\\|\\=\\|[-\"]\\|\\s(\\)\\(%s\\(\\w+\\(\\s-+\\w+\\)*[.,]?\\)%s\\)\\(\\([-,.;:!?\"]\\|\\s)\\)+\\s-\\|[?!.]\\s-\\|\\s)\\|\\s-\\)")
284         (types
285          '(("\\*" "\\*" bold)
286            ("_" "_" underline)
287            ("/" "/" italic)
288            ("_/" "/_" underline-italic)
289            ("_\\*" "\\*_" underline-bold)
290            ("\\*/" "/\\*" bold-italic)
291            ("_\\*/" "/\\*_" underline-bold-italic))))
292     `(,@(mapcar
293          (lambda (spec)
294            (list
295             (format format (car spec) (cadr spec))
296             2 3 (intern (format "gnus-emphasis-%s" (nth 2 spec)))))
297          types)
298         ("\\(\\s-\\|^\\)\\(_\\(\\(\\w\\|_[^_]\\)+\\)_\\)\\(\\s-\\|[?!.,;]\\)"
299          2 3 gnus-emphasis-underline)))
300   "*Alist that says how to fontify certain phrases.
301 Each item looks like this:
302
303   (\"_\\\\(\\\\w+\\\\)_\" 0 1 'underline)
304
305 The first element is a regular expression to be matched.  The second
306 is a number that says what regular expression grouping used to find
307 the entire emphasized word.  The third is a number that says what
308 regexp grouping should be displayed and highlighted.  The fourth
309 is the face used for highlighting."
310   :type '(repeat (list :value ("" 0 0 default)
311                        regexp
312                        (integer :tag "Match group")
313                        (integer :tag "Emphasize group")
314                        face))
315   :group 'gnus-article-emphasis)
316
317 (defcustom gnus-emphasize-whitespace-regexp "^[ \t]+\\|[ \t]*\n"
318   "A regexp to describe whitespace which should not be emphasized.
319 Typical values are \"^[ \\t]+\\\\|[ \\t]*\\n\" and \"[ \\t]+\\\\|[ \\t]*\\n\".
320 The former avoids underlining of leading and trailing whitespace,
321 and the latter avoids underlining any whitespace at all."
322   :version "21.1"
323   :group 'gnus-article-emphasis
324   :type 'regexp)
325
326 (defface gnus-emphasis-bold '((t (:bold t)))
327   "Face used for displaying strong emphasized text (*word*)."
328   :group 'gnus-article-emphasis)
329
330 (defface gnus-emphasis-italic '((t (:italic t)))
331   "Face used for displaying italic emphasized text (/word/)."
332   :group 'gnus-article-emphasis)
333
334 (defface gnus-emphasis-underline '((t (:underline t)))
335   "Face used for displaying underlined emphasized text (_word_)."
336   :group 'gnus-article-emphasis)
337
338 (defface gnus-emphasis-underline-bold '((t (:bold t :underline t)))
339   "Face used for displaying underlined bold emphasized text (_*word*_)."
340   :group 'gnus-article-emphasis)
341
342 (defface gnus-emphasis-underline-italic '((t (:italic t :underline t)))
343   "Face used for displaying underlined italic emphasized text (_/word/_)."
344   :group 'gnus-article-emphasis)
345
346 (defface gnus-emphasis-bold-italic '((t (:bold t :italic t)))
347   "Face used for displaying bold italic emphasized text (/*word*/)."
348   :group 'gnus-article-emphasis)
349
350 (defface gnus-emphasis-underline-bold-italic
351   '((t (:bold t :italic t :underline t)))
352   "Face used for displaying underlined bold italic emphasized text.
353 Esample: (_/*word*/_)."
354   :group 'gnus-article-emphasis)
355
356 (defface gnus-emphasis-highlight-words
357   '((t (:background "black" :foreground "yellow")))
358   "Face used for displaying highlighted words."
359   :group 'gnus-article-emphasis)
360
361 (defcustom gnus-article-time-format "%a, %b %d %Y %T %Z"
362   "Format for display of Date headers in article bodies.
363 See `format-time-string' for the possible values.
364
365 The variable can also be function, which should return a complete Date
366 header.  The function is called with one argument, the time, which can
367 be fed to `format-time-string'."
368   :type '(choice string symbol)
369   :link '(custom-manual "(gnus)Article Date")
370   :group 'gnus-article-washing)
371
372 (eval-and-compile
373   (autoload 'mail-extract-address-components "mail-extr"))
374
375 (defcustom gnus-save-all-headers t
376   "*If non-nil, don't remove any headers before saving."
377   :group 'gnus-article-saving
378   :type 'boolean)
379
380 (defcustom gnus-prompt-before-saving 'always
381   "*This variable says how much prompting is to be done when saving articles.
382 If it is nil, no prompting will be done, and the articles will be
383 saved to the default files.  If this variable is `always', each and
384 every article that is saved will be preceded by a prompt, even when
385 saving large batches of articles.  If this variable is neither nil not
386 `always', there the user will be prompted once for a file name for
387 each invocation of the saving commands."
388   :group 'gnus-article-saving
389   :type '(choice (item always)
390                  (item :tag "never" nil)
391                  (sexp :tag "once" :format "%t\n" :value t)))
392
393 (defcustom gnus-saved-headers gnus-visible-headers
394   "Headers to keep if `gnus-save-all-headers' is nil.
395 If `gnus-save-all-headers' is non-nil, this variable will be ignored.
396 If that variable is nil, however, all headers that match this regexp
397 will be kept while the rest will be deleted before saving."
398   :group 'gnus-article-saving
399   :type 'regexp)
400
401 (defcustom gnus-default-article-saver 'gnus-summary-save-in-rmail
402   "A function to save articles in your favourite format.
403 The function must be interactively callable (in other words, it must
404 be an Emacs command).
405
406 Gnus provides the following functions:
407
408 * gnus-summary-save-in-rmail (Rmail format)
409 * gnus-summary-save-in-mail (Unix mail format)
410 * gnus-summary-save-in-folder (MH folder)
411 * gnus-summary-save-in-file (article format)
412 * gnus-summary-save-body-in-file (article body)
413 * gnus-summary-save-in-vm (use VM's folder format)
414 * gnus-summary-write-to-file (article format -- overwrite)."
415   :group 'gnus-article-saving
416   :type '(radio (function-item gnus-summary-save-in-rmail)
417                 (function-item gnus-summary-save-in-mail)
418                 (function-item gnus-summary-save-in-folder)
419                 (function-item gnus-summary-save-in-file)
420                 (function-item gnus-summary-save-body-in-file)
421                 (function-item gnus-summary-save-in-vm)
422                 (function-item gnus-summary-write-to-file)))
423
424 (defcustom gnus-rmail-save-name 'gnus-plain-save-name
425   "A function generating a file name to save articles in Rmail format.
426 The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE."
427   :group 'gnus-article-saving
428   :type 'function)
429
430 (defcustom gnus-mail-save-name 'gnus-plain-save-name
431   "A function generating a file name to save articles in Unix mail format.
432 The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE."
433   :group 'gnus-article-saving
434   :type 'function)
435
436 (defcustom gnus-folder-save-name 'gnus-folder-save-name
437   "A function generating a file name to save articles in MH folder.
438 The function is called with NEWSGROUP, HEADERS, and optional LAST-FOLDER."
439   :group 'gnus-article-saving
440   :type 'function)
441
442 (defcustom gnus-file-save-name 'gnus-numeric-save-name
443   "A function generating a file name to save articles in article format.
444 The function is called with NEWSGROUP, HEADERS, and optional
445 LAST-FILE."
446   :group 'gnus-article-saving
447   :type 'function)
448
449 (defcustom gnus-split-methods
450   '((gnus-article-archive-name)
451     (gnus-article-nndoc-name))
452   "*Variable used to suggest where articles are to be saved.
453 For instance, if you would like to save articles related to Gnus in
454 the file \"gnus-stuff\", and articles related to VM in \"vm-stuff\",
455 you could set this variable to something like:
456
457  '((\"^Subject:.*gnus\\|^Newsgroups:.*gnus\" \"gnus-stuff\")
458    (\"^Subject:.*vm\\|^Xref:.*vm\" \"vm-stuff\"))
459
460 This variable is an alist where the where the key is the match and the
461 value is a list of possible files to save in if the match is non-nil.
462
463 If the match is a string, it is used as a regexp match on the
464 article.  If the match is a symbol, that symbol will be funcalled
465 from the buffer of the article to be saved with the newsgroup as the
466 parameter.  If it is a list, it will be evaled in the same buffer.
467
468 If this form or function returns a string, this string will be used as
469 a possible file name; and if it returns a non-nil list, that list will
470 be used as possible file names."
471   :group 'gnus-article-saving
472   :type '(repeat (choice (list :value (fun) function)
473                          (cons :value ("" "") regexp (repeat string))
474                          (sexp :value nil))))
475
476 (defcustom gnus-page-delimiter "^\^L"
477   "*Regexp describing what to use as article page delimiters.
478 The default value is \"^\^L\", which is a form linefeed at the
479 beginning of a line."
480   :type 'regexp
481   :group 'gnus-article-various)
482
483 (defcustom gnus-article-mode-line-format "Gnus: %g [%w] %S%m"
484   "*The format specification for the article mode line.
485 See `gnus-summary-mode-line-format' for a closer description.
486
487 The following additional specs are available:
488
489 %w  The article washing status.
490 %m  The number of MIME parts in the article."
491   :type 'string
492   :group 'gnus-article-various)
493
494 (defcustom gnus-article-mode-hook nil
495   "*A hook for Gnus article mode."
496   :type 'hook
497   :group 'gnus-article-various)
498
499 (when (featurep 'xemacs)
500   ;; Extracted from gnus-xmas-define in order to preserve user settings
501   (when (fboundp 'turn-off-scroll-in-place)
502     (add-hook 'gnus-article-mode-hook 'turn-off-scroll-in-place))
503   ;; Extracted from gnus-xmas-redefine in order to preserve user settings
504   (add-hook 'gnus-article-mode-hook 'gnus-xmas-article-menu-add))
505
506 (defcustom gnus-article-menu-hook nil
507   "*Hook run after the creation of the article mode menu."
508   :type 'hook
509   :group 'gnus-article-various)
510
511 (defcustom gnus-article-prepare-hook nil
512   "*A hook called after an article has been prepared in the article buffer."
513   :type 'hook
514   :group 'gnus-article-various)
515
516 (defcustom gnus-article-hide-pgp-hook nil
517   "*A hook called after successfully hiding a PGP signature."
518   :type 'hook
519   :group 'gnus-article-various)
520
521 (defcustom gnus-article-button-face 'bold
522   "Face used for highlighting buttons in the article buffer.
523
524 An article button is a piece of text that you can activate by pressing
525 `RET' or `mouse-2' above it."
526   :type 'face
527   :group 'gnus-article-buttons)
528
529 (defcustom gnus-article-mouse-face 'highlight
530   "Face used for mouse highlighting in the article buffer.
531
532 Article buttons will be displayed in this face when the cursor is
533 above them."
534   :type 'face
535   :group 'gnus-article-buttons)
536
537 (defcustom gnus-signature-face 'gnus-signature-face
538   "Face used for highlighting a signature in the article buffer.
539 Obsolete; use the face `gnus-signature-face' for customizations instead."
540   :type 'face
541   :group 'gnus-article-highlight
542   :group 'gnus-article-signature)
543
544 (defface gnus-signature-face
545   '((t
546      (:italic t)))
547   "Face used for highlighting a signature in the article buffer."
548   :group 'gnus-article-highlight
549   :group 'gnus-article-signature)
550
551 (defface gnus-header-from-face
552   '((((class color)
553       (background dark))
554      (:foreground "spring green"))
555     (((class color)
556       (background light))
557      (:foreground "red3"))
558     (t
559      (:italic t)))
560   "Face used for displaying from headers."
561   :group 'gnus-article-headers
562   :group 'gnus-article-highlight)
563
564 (defface gnus-header-subject-face
565   '((((class color)
566       (background dark))
567      (:foreground "SeaGreen3"))
568     (((class color)
569       (background light))
570      (:foreground "red4"))
571     (t
572      (:bold t :italic t)))
573   "Face used for displaying subject headers."
574   :group 'gnus-article-headers
575   :group 'gnus-article-highlight)
576
577 (defface gnus-header-newsgroups-face
578   '((((class color)
579       (background dark))
580      (:foreground "yellow" :italic t))
581     (((class color)
582       (background light))
583      (:foreground "MidnightBlue" :italic t))
584     (t
585      (:italic t)))
586   "Face used for displaying newsgroups headers."
587   :group 'gnus-article-headers
588   :group 'gnus-article-highlight)
589
590 (defface gnus-header-name-face
591   '((((class color)
592       (background dark))
593      (:foreground "SeaGreen"))
594     (((class color)
595       (background light))
596      (:foreground "maroon"))
597     (t
598      (:bold t)))
599   "Face used for displaying header names."
600   :group 'gnus-article-headers
601   :group 'gnus-article-highlight)
602
603 (defface gnus-header-content-face
604   '((((class color)
605       (background dark))
606      (:foreground "forest green" :italic t))
607     (((class color)
608       (background light))
609      (:foreground "indianred4" :italic t))
610     (t
611      (:italic t)))  "Face used for displaying header content."
612   :group 'gnus-article-headers
613   :group 'gnus-article-highlight)
614
615 (defcustom gnus-header-face-alist
616   '(("From" nil gnus-header-from-face)
617     ("Subject" nil gnus-header-subject-face)
618     ("Newsgroups:.*," nil gnus-header-newsgroups-face)
619     ("" gnus-header-name-face gnus-header-content-face))
620   "*Controls highlighting of article header.
621
622 An alist of the form (HEADER NAME CONTENT).
623
624 HEADER is a regular expression which should match the name of an
625 header header and NAME and CONTENT are either face names or nil.
626
627 The name of each header field will be displayed using the face
628 specified by the first element in the list where HEADER match the
629 header name and NAME is non-nil.  Similarly, the content will be
630 displayed by the first non-nil matching CONTENT face."
631   :group 'gnus-article-headers
632   :group 'gnus-article-highlight
633   :type '(repeat (list (regexp :tag "Header")
634                        (choice :tag "Name"
635                                (item :tag "skip" nil)
636                                (face :value default))
637                        (choice :tag "Content"
638                                (item :tag "skip" nil)
639                                (face :value default)))))
640
641 (defcustom gnus-article-decode-hook
642   '(article-decode-charset article-decode-encoded-words
643                            article-decode-group-name)
644   "*Hook run to decode charsets in articles."
645   :group 'gnus-article-headers
646   :type 'hook)
647
648 (defcustom gnus-display-mime-function 'gnus-display-mime
649   "Function to display MIME articles."
650   :group 'gnus-article-mime
651   :type 'function)
652
653 (defvar gnus-decode-header-function 'mail-decode-encoded-word-region
654   "Function used to decode headers.")
655
656 (defvar gnus-article-dumbquotes-map
657   '(("\200" "EUR")
658     ("\202" ",")
659     ("\203" "f")
660     ("\204" ",,")
661     ("\205" "...")
662     ("\213" "<")
663     ("\214" "OE")
664     ("\221" "`")
665     ("\222" "'")
666     ("\223" "``")
667     ("\224" "\"")
668     ("\225" "*")
669     ("\226" "-")
670     ("\227" "--")
671     ("\231" "(TM)")
672     ("\233" ">")
673     ("\234" "oe")
674     ("\264" "'"))
675   "Table for MS-to-Latin1 translation.")
676
677 (defcustom gnus-ignored-mime-types nil
678   "List of MIME types that should be ignored by Gnus."
679   :version "21.1"
680   :group 'gnus-article-mime
681   :type '(repeat regexp))
682
683 (defcustom gnus-unbuttonized-mime-types '(".*/.*")
684   "List of MIME types that should not be given buttons when rendered inline.
685 See also `gnus-buttonized-mime-types' which may override this variable."
686   :version "21.1"
687   :group 'gnus-article-mime
688   :type '(repeat regexp))
689
690 (defcustom gnus-buttonized-mime-types nil
691   "List of MIME types that should be given buttons when rendered inline.
692 If set, this variable overrides `gnus-unbuttonized-mime-types'.
693 To see e.g. security buttons you could set this to
694 `(\"multipart/signed\")'."
695   :version "21.1"
696   :group 'gnus-article-mime
697   :type '(repeat regexp))
698
699 (defcustom gnus-body-boundary-delimiter "_"
700   "String used to delimit header and body.
701 This variable is used by `gnus-article-treat-body-boundary' which can
702 be controlled by `gnus-treat-body-boundary'."
703   :group 'gnus-article-various
704   :type '(choice (item :tag "None" :value nil)
705                  string))
706
707 (defcustom gnus-picon-databases '("/usr/lib/picon" "/usr/local/faces")
708   "*Defines the location of the faces database.
709 For information on obtaining this database of pretty pictures, please
710 see http://www.cs.indiana.edu/picons/ftp/index.html"
711   :type 'directory
712   :group 'gnus-picon)
713
714 (defun gnus-picons-installed-p ()
715   "Say whether picons are installed on your machine."
716   (let ((installed nil))
717     (dolist (database gnus-picon-databases)
718       (when (file-exists-p database)
719         (setq installed t)))
720     installed))
721
722 (defcustom gnus-article-mime-part-function nil
723   "Function called with a MIME handle as the argument.
724 This is meant for people who want to do something automatic based
725 on parts -- for instance, adding Vcard info to a database."
726   :group 'gnus-article-mime
727   :type 'function)
728
729 (defcustom gnus-mime-multipart-functions nil
730   "An alist of MIME types to functions to display them."
731   :version "21.1"
732   :group 'gnus-article-mime
733   :type 'alist)
734
735 (defcustom gnus-article-date-lapsed-new-header nil
736   "Whether the X-Sent and Date headers can coexist.
737 When using `gnus-treat-date-lapsed', the \"X-Sent:\" header will
738 either replace the old \"Date:\" header (if this variable is nil), or
739 be added below it (otherwise)."
740   :version "21.1"
741   :group 'gnus-article-headers
742   :type 'boolean)
743
744 (defcustom gnus-article-mime-match-handle-function 'undisplayed-alternative
745   "Function called with a MIME handle as the argument.
746 This is meant for people who want to view first matched part.
747 For `undisplayed-alternative' (default), the first undisplayed
748 part or alternative part is used.  For `undisplayed', the first
749 undisplayed part is used.  For a function, the first part which
750 the function return `t' is used.  For `nil', the first part is
751 used."
752   :version "21.1"
753   :group 'gnus-article-mime
754   :type '(choice
755           (item :tag "first" :value nil)
756           (item :tag "undisplayed" :value undisplayed)
757           (item :tag "undisplayed or alternative"
758                 :value undisplayed-alternative)
759           (function)))
760
761 (defcustom gnus-mime-action-alist
762   '(("save to file" . gnus-mime-save-part)
763     ("save and strip" . gnus-mime-save-part-and-strip)
764     ("display as text" . gnus-mime-inline-part)
765     ("view the part" . gnus-mime-view-part)
766     ("pipe to command" . gnus-mime-pipe-part)
767     ("toggle display" . gnus-article-press-button)
768     ("toggle display" . gnus-article-view-part-as-charset)
769     ("view as type" . gnus-mime-view-part-as-type)
770     ("internalize type" . gnus-mime-internalize-part)
771     ("externalize type" . gnus-mime-externalize-part))
772   "An alist of actions that run on the MIME attachment."
773   :group 'gnus-article-mime
774   :type '(repeat (cons (string :tag "name")
775                        (function))))
776
777 (defcustom gnus-mime-action-alist
778   '(("save to file" . gnus-mime-save-part)
779     ("display as text" . gnus-mime-inline-part)
780     ("view the part" . gnus-mime-view-part)
781     ("pipe to command" . gnus-mime-pipe-part)
782     ("toggle display" . gnus-article-press-button)
783     ("view as type" . gnus-mime-view-part-as-type)
784     ("internalize type" . gnus-mime-internalize-part)
785     ("externalize type" . gnus-mime-externalize-part))
786   "An alist of actions that run on the MIME attachment."
787   :version "21.1"
788   :group 'gnus-article-mime
789   :type '(repeat (cons (string :tag "name")
790                        (function))))
791
792 ;;;
793 ;;; The treatment variables
794 ;;;
795
796 (defvar gnus-part-display-hook nil
797   "Hook called on parts that are to receive treatment.")
798
799 (defvar gnus-article-treat-custom
800   '(choice (const :tag "Off" nil)
801            (const :tag "On" t)
802            (const :tag "Header" head)
803            (const :tag "Last" last)
804            (integer :tag "Less")
805            (repeat :tag "Groups" regexp)
806            (sexp :tag "Predicate")))
807
808 (defvar gnus-article-treat-head-custom
809   '(choice (const :tag "Off" nil)
810            (const :tag "Header" head)))
811
812 (defvar gnus-article-treat-types '("text/plain")
813   "Parts to treat.")
814
815 (defvar gnus-inhibit-treatment nil
816   "Whether to inhibit treatment.")
817
818 (defcustom gnus-treat-highlight-signature '(or last (typep "text/x-vcard"))
819   "Highlight the signature.
820 Valid values are nil, t, `head', `last', an integer or a predicate.
821 See Info node `(gnus)Customizing Articles'."
822   :group 'gnus-article-treat
823   :type gnus-article-treat-custom)
824 (put 'gnus-treat-highlight-signature 'highlight t)
825
826 (defcustom gnus-treat-buttonize 100000
827   "Add buttons.
828 Valid values are nil, t, `head', `last', an integer or a predicate.
829 See Info node `(gnus)Customizing Articles'."
830   :group 'gnus-article-treat
831   :type gnus-article-treat-custom)
832 (put 'gnus-treat-buttonize 'highlight t)
833
834 (defcustom gnus-treat-buttonize-head 'head
835   "Add buttons to the head.
836 Valid values are nil, t, `head', `last', an integer or a predicate.
837 See Info node `(gnus)Customizing Articles' for details."
838   :group 'gnus-article-treat
839   :type gnus-article-treat-head-custom)
840 (put 'gnus-treat-buttonize-head 'highlight t)
841
842 (defcustom gnus-treat-emphasize
843   (and (or window-system
844            (featurep 'xemacs)
845            (>= (string-to-number emacs-version) 21))
846        50000)
847   "Emphasize text.
848 Valid values are nil, t, `head', `last', an integer or a predicate.
849 See Info node `(gnus)Customizing Articles' for details."
850   :group 'gnus-article-treat
851   :type gnus-article-treat-custom)
852 (put 'gnus-treat-emphasize 'highlight t)
853
854 (defcustom gnus-treat-strip-cr nil
855   "Remove carriage returns.
856 Valid values are nil, t, `head', `last', an integer or a predicate.
857 See Info node `(gnus)Customizing Articles' for details."
858   :group 'gnus-article-treat
859   :type gnus-article-treat-custom)
860
861 (defcustom gnus-treat-unsplit-urls nil
862   "Remove newlines from within URLs.
863 Valid values are nil, t, `head', `last', an integer or a predicate.
864 See Info node `(gnus)Customizing Articles' for details."
865   :group 'gnus-article-treat
866   :type gnus-article-treat-custom)
867
868 (defcustom gnus-treat-leading-whitespace nil
869   "Remove leading whitespace in headers.
870 Valid values are nil, t, `head', `last', an integer or a predicate.
871 See Info node `(gnus)Customizing Articles' for details."
872   :group 'gnus-article-treat
873   :type gnus-article-treat-custom)
874
875 (defcustom gnus-treat-hide-headers 'head
876   "Hide headers.
877 Valid values are nil, t, `head', `last', an integer or a predicate.
878 See Info node `(gnus)Customizing Articles' for details."
879   :group 'gnus-article-treat
880   :type gnus-article-treat-head-custom)
881
882 (defcustom gnus-treat-hide-boring-headers nil
883   "Hide boring headers.
884 Valid values are nil, t, `head', `last', an integer or a predicate.
885 See Info node `(gnus)Customizing Articles' for details."
886   :group 'gnus-article-treat
887   :type gnus-article-treat-head-custom)
888
889 (defcustom gnus-treat-hide-signature nil
890   "Hide the signature.
891 Valid values are nil, t, `head', `last', an integer or a predicate.
892 See Info node `(gnus)Customizing Articles' for details."
893   :group 'gnus-article-treat
894   :type gnus-article-treat-custom)
895
896 (defcustom gnus-treat-fill-article nil
897   "Fill the article.
898 Valid values are nil, t, `head', `last', an integer or a predicate.
899 See Info node `(gnus)Customizing Articles' for details."
900   :group 'gnus-article-treat
901   :type gnus-article-treat-custom)
902
903 (defcustom gnus-treat-hide-citation nil
904   "Hide cited text.
905 Valid values are nil, t, `head', `last', an integer or a predicate.
906 See Info node `(gnus)Customizing Articles' for details."
907   :group 'gnus-article-treat
908   :type gnus-article-treat-custom)
909
910 (defcustom gnus-treat-hide-citation-maybe nil
911   "Hide cited text.
912 Valid values are nil, t, `head', `last', an integer or a predicate.
913 See Info node `(gnus)Customizing Articles' for details."
914   :group 'gnus-article-treat
915   :type gnus-article-treat-custom)
916
917 (defcustom gnus-treat-strip-list-identifiers 'head
918   "Strip list identifiers from `gnus-list-identifiers`.
919 Valid values are nil, t, `head', `last', an integer or a predicate.
920 See Info node `(gnus)Customizing Articles' for details."
921   :version "21.1"
922   :group 'gnus-article-treat
923   :type gnus-article-treat-custom)
924
925 (defcustom gnus-treat-strip-pgp t
926   "Strip PGP signatures.
927 Valid values are nil, t, `head', `last', an integer or a predicate.
928 See Info node `(gnus)Customizing Articles' for details."
929   :group 'gnus-article-treat
930   :type gnus-article-treat-custom)
931
932 (defcustom gnus-treat-strip-pem nil
933   "Strip PEM signatures.
934 Valid values are nil, t, `head', `last', an integer or a predicate.
935 See Info node `(gnus)Customizing Articles' for details."
936   :group 'gnus-article-treat
937   :type gnus-article-treat-custom)
938
939 (defcustom gnus-treat-strip-banner t
940   "Strip banners from articles.
941 The banner to be stripped is specified in the `banner' group parameter.
942 Valid values are nil, t, `head', `last', an integer or a predicate.
943 See Info node `(gnus)Customizing Articles' for details."
944   :group 'gnus-article-treat
945   :type gnus-article-treat-custom)
946
947 (defcustom gnus-treat-highlight-headers 'head
948   "Highlight the headers.
949 Valid values are nil, t, `head', `last', an integer or a predicate.
950 See Info node `(gnus)Customizing Articles' for details."
951   :group 'gnus-article-treat
952   :type gnus-article-treat-head-custom)
953 (put 'gnus-treat-highlight-headers 'highlight t)
954
955 (defcustom gnus-treat-highlight-citation t
956   "Highlight cited text.
957 Valid values are nil, t, `head', `last', an integer or a predicate.
958 See Info node `(gnus)Customizing Articles' for details."
959   :group 'gnus-article-treat
960   :type gnus-article-treat-custom)
961 (put 'gnus-treat-highlight-citation 'highlight t)
962
963 (defcustom gnus-treat-date-ut nil
964   "Display the Date in UT (GMT).
965 Valid values are nil, t, `head', `last', an integer or a predicate.
966 See Info node `(gnus)Customizing Articles' for details."
967   :group 'gnus-article-treat
968   :type gnus-article-treat-head-custom)
969
970 (defcustom gnus-treat-date-local nil
971   "Display the Date in the local timezone.
972 Valid values are nil, t, `head', `last', an integer or a predicate.
973 See Info node `(gnus)Customizing Articles' for details."
974   :group 'gnus-article-treat
975   :type gnus-article-treat-head-custom)
976
977 (defcustom gnus-treat-date-english nil
978   "Display the Date in a format that can be read aloud in English.
979 Valid values are nil, t, `head', `last', an integer or a predicate.
980 See Info node `(gnus)Customizing Articles' for details."
981   :group 'gnus-article-treat
982   :type gnus-article-treat-head-custom)
983
984 (defcustom gnus-treat-date-lapsed nil
985   "Display the Date header in a way that says how much time has elapsed.
986 Valid values are nil, t, `head', `last', an integer or a predicate.
987 See Info node `(gnus)Customizing Articles' for details."
988   :group 'gnus-article-treat
989   :type gnus-article-treat-head-custom)
990
991 (defcustom gnus-treat-date-original nil
992   "Display the date in the original timezone.
993 Valid values are nil, t, `head', `last', an integer or a predicate.
994 See Info node `(gnus)Customizing Articles' for details."
995   :group 'gnus-article-treat
996   :type gnus-article-treat-head-custom)
997
998 (defcustom gnus-treat-date-iso8601 nil
999   "Display the date in the ISO8601 format.
1000 Valid values are nil, t, `head', `last', an integer or a predicate.
1001 See Info node `(gnus)Customizing Articles' for details."
1002   :version "21.1"
1003   :group 'gnus-article-treat
1004   :type gnus-article-treat-head-custom)
1005
1006 (defcustom gnus-treat-date-user-defined nil
1007   "Display the date in a user-defined format.
1008 The format is defined by the `gnus-article-time-format' variable.
1009 Valid values are nil, t, `head', `last', an integer or a predicate.
1010 See Info node `(gnus)Customizing Articles' for details."
1011   :group 'gnus-article-treat
1012   :type gnus-article-treat-head-custom)
1013
1014 (defcustom gnus-treat-strip-headers-in-body t
1015   "Strip the X-No-Archive header line from the beginning of the body.
1016 Valid values are nil, t, `head', `last', an integer or a predicate.
1017 See Info node `(gnus)Customizing Articles' for details."
1018   :version "21.1"
1019   :group 'gnus-article-treat
1020   :type gnus-article-treat-custom)
1021
1022 (defcustom gnus-treat-strip-trailing-blank-lines nil
1023   "Strip trailing blank lines.
1024 Valid values are nil, t, `head', `last', an integer or a predicate.
1025 See Info node `(gnus)Customizing Articles' for details."
1026   :group 'gnus-article-treat
1027   :type gnus-article-treat-custom)
1028
1029 (defcustom gnus-treat-strip-leading-blank-lines nil
1030   "Strip leading blank lines.
1031 Valid values are nil, t, `head', `last', an integer or a predicate.
1032 See Info node `(gnus)Customizing Articles' for details."
1033   :group 'gnus-article-treat
1034   :type gnus-article-treat-custom)
1035
1036 (defcustom gnus-treat-strip-multiple-blank-lines nil
1037   "Strip multiple blank lines.
1038 Valid values are nil, t, `head', `last', an integer or a predicate.
1039 See Info node `(gnus)Customizing Articles' for details."
1040   :group 'gnus-article-treat
1041   :type gnus-article-treat-custom)
1042
1043 (defcustom gnus-treat-unfold-headers 'head
1044   "Unfold folded header lines.
1045 Valid values are nil, t, `head', `last', an integer or a predicate.
1046 See Info node `(gnus)Customizing Articles' for details."
1047   :group 'gnus-article-treat
1048   :type gnus-article-treat-custom)
1049
1050 (defcustom gnus-treat-fold-headers nil
1051   "Fold headers.
1052 Valid values are nil, t, `head', `last', an integer or a predicate.
1053 See Info node `(gnus)Customizing Articles' for details."
1054   :group 'gnus-article-treat
1055   :type gnus-article-treat-custom)
1056
1057 (defcustom gnus-treat-fold-newsgroups 'head
1058   "Fold the Newsgroups and Followup-To headers.
1059 Valid values are nil, t, `head', `last', an integer or a predicate.
1060 See Info node `(gnus)Customizing Articles' for details."
1061   :group 'gnus-article-treat
1062   :type gnus-article-treat-custom)
1063
1064 (defcustom gnus-treat-overstrike t
1065   "Treat overstrike highlighting.
1066 Valid values are nil, t, `head', `last', an integer or a predicate.
1067 See Info node `(gnus)Customizing Articles' for details."
1068   :group 'gnus-article-treat
1069   :type gnus-article-treat-custom)
1070 (put 'gnus-treat-overstrike 'highlight t)
1071
1072 (defcustom gnus-treat-display-xface
1073   (and (or (and (fboundp 'image-type-available-p)
1074                 (image-type-available-p 'xbm)
1075                 (string-match "^0x" (shell-command-to-string "uncompface")))
1076            (and (featurep 'xemacs)
1077                 (featurep 'xface)))
1078        'head)
1079   "Display X-Face headers.
1080 Valid values are nil, t, `head', `last', an integer or a predicate.
1081 See Info node `(gnus)Customizing Articles' and Info node
1082 `(gnus)X-Face' for details."
1083   :group 'gnus-article-treat
1084   :version "21.1"
1085   :type gnus-article-treat-head-custom)
1086 (put 'gnus-treat-display-xface 'highlight t)
1087
1088 (defcustom gnus-treat-display-grey-xface
1089   (and (string-match "^0x" (shell-command-to-string "uncompface"))
1090        t)
1091   "Display grey X-Face headers.
1092 Valid values are nil, t."
1093   :group 'gnus-article-treat
1094   :version "21.3"
1095   :type 'boolean)
1096 (put 'gnus-treat-display-grey-xface 'highlight t)
1097
1098 (defcustom gnus-treat-display-smileys
1099   (if (or (and (featurep 'xemacs)
1100                (featurep 'xpm))
1101           (and (fboundp 'image-type-available-p)
1102                (image-type-available-p 'pbm)))
1103       t nil)
1104   "Display smileys.
1105 Valid values are nil, t, `head', `last', an integer or a predicate.
1106 See Info node `(gnus)Customizing Articles' and Info node
1107 `(gnus)Smileys' for details."
1108   :group 'gnus-article-treat
1109   :version "21.1"
1110   :type gnus-article-treat-custom)
1111 (put 'gnus-treat-display-smileys 'highlight t)
1112
1113 (defcustom gnus-treat-from-picon
1114   (if (and (gnus-image-type-available-p 'xpm)
1115            (gnus-picons-installed-p))
1116       'head nil)
1117   "Display picons in the From header.
1118 Valid values are nil, t, `head', `last', an integer or a predicate.
1119 See Info node `(gnus)Customizing Articles' and Info node
1120 `(gnus)Picons' for details."
1121   :group 'gnus-article-treat
1122   :type gnus-article-treat-head-custom)
1123 (put 'gnus-treat-from-picon 'highlight t)
1124
1125 (defcustom gnus-treat-mail-picon
1126   (if (and (gnus-image-type-available-p 'xpm)
1127            (gnus-picons-installed-p))
1128       'head nil)
1129   "Display picons in To and Cc headers.
1130 Valid values are nil, t, `head', `last', an integer or a predicate.
1131 See Info node `(gnus)Customizing Articles' and Info node
1132 `(gnus)Picons' for details."
1133   :group 'gnus-article-treat
1134   :type gnus-article-treat-head-custom)
1135 (put 'gnus-treat-mail-picon 'highlight t)
1136
1137 (defcustom gnus-treat-newsgroups-picon
1138   (if (and (gnus-image-type-available-p 'xpm)
1139            (gnus-picons-installed-p))
1140       'head nil)
1141   "Display picons in the Newsgroups and Followup-To headers.
1142 Valid values are nil, t, `head', `last', an integer or a predicate.
1143 See Info node `(gnus)Customizing Articles' and Info node
1144 `(gnus)Picons' for details."
1145   :group 'gnus-article-treat
1146   :type gnus-article-treat-head-custom)
1147 (put 'gnus-treat-newsgroups-picon 'highlight t)
1148
1149 (defcustom gnus-treat-body-boundary
1150   (if (or gnus-treat-newsgroups-picon
1151           gnus-treat-mail-picon
1152           gnus-treat-from-picon)
1153       'head nil)
1154   "Draw a boundary at the end of the headers.
1155 Valid values are nil, t, `head', `last', an integer or a predicate.
1156 See Info node `(gnus)Customizing Articles' for details."
1157   :version "21.1"
1158   :group 'gnus-article-treat
1159   :type gnus-article-treat-custom)
1160
1161 (defcustom gnus-treat-capitalize-sentences nil
1162   "Capitalize sentence-starting words.
1163 Valid values are nil, t, `head', `last', an integer or a predicate.
1164 See Info node `(gnus)Customizing Articles' for details."
1165   :version "21.1"
1166   :group 'gnus-article-treat
1167   :type gnus-article-treat-custom)
1168
1169 (defcustom gnus-treat-fill-long-lines nil
1170   "Fill long lines.
1171 Valid values are nil, t, `head', `last', an integer or a predicate.
1172 See Info node `(gnus)Customizing Articles' for details."
1173   :group 'gnus-article-treat
1174   :type gnus-article-treat-custom)
1175
1176 (defcustom gnus-treat-play-sounds nil
1177   "Play sounds.
1178 Valid values are nil, t, `head', `last', an integer or a predicate.
1179 See Info node `(gnus)Customizing Articles' for details."
1180   :version "21.1"
1181   :group 'gnus-article-treat
1182   :type gnus-article-treat-custom)
1183
1184 (defcustom gnus-treat-translate nil
1185   "Translate articles from one language to another.
1186 Valid values are nil, t, `head', `last', an integer or a predicate.
1187 See Info node `(gnus)Customizing Articles' for details."
1188   :version "21.1"
1189   :group 'gnus-article-treat
1190   :type gnus-article-treat-custom)
1191
1192 (defcustom gnus-treat-x-pgp-sig nil
1193   "Verify X-PGP-Sig.
1194 To automatically treat X-PGP-Sig, set it to head.
1195 Valid values are nil, t, `head', `last', an integer or a predicate.
1196 See Info node `(gnus)Customizing Articles' for details."
1197   :group 'gnus-article-treat
1198   :group 'mime-security
1199   :type gnus-article-treat-custom)
1200
1201 (defvar gnus-article-encrypt-protocol-alist
1202   '(("PGP" . mml2015-self-encrypt)))
1203
1204 ;; Set to nil if more than one protocol added to
1205 ;; gnus-article-encrypt-protocol-alist.
1206 (defcustom gnus-article-encrypt-protocol "PGP"
1207   "The protocol used for encrypt articles.
1208 It is a string, such as \"PGP\". If nil, ask user."
1209   :type 'string
1210   :group 'mime-security)
1211
1212 (defcustom gnus-article-wash-function
1213   (cond ((locate-library "w3")
1214          'gnus-article-wash-html-with-w3)
1215         ((locate-library "w3m")
1216          'gnus-article-wash-html-with-w3m))
1217   "Function used for converting HTML into text."
1218   :type '(radio (function-item gnus-article-wash-html-with-w3)
1219                 (function-item gnus-article-wash-html-with-w3m))
1220   :group 'gnus-article)
1221
1222 ;;; Internal variables
1223
1224 (defvar gnus-english-month-names
1225   '("January" "February" "March" "April" "May" "June" "July" "August"
1226     "September" "October" "November" "December"))
1227
1228 (defvar article-goto-body-goes-to-point-min-p nil)
1229 (defvar gnus-article-wash-types nil)
1230 (defvar gnus-article-emphasis-alist nil)
1231 (defvar gnus-article-image-alist nil)
1232
1233 (defvar gnus-article-mime-handle-alist-1 nil)
1234 (defvar gnus-treatment-function-alist
1235   '((gnus-treat-x-pgp-sig gnus-article-verify-x-pgp-sig)
1236     (gnus-treat-strip-banner gnus-article-strip-banner)
1237     (gnus-treat-strip-headers-in-body gnus-article-strip-headers-in-body)
1238     (gnus-treat-highlight-signature gnus-article-highlight-signature)
1239     (gnus-treat-buttonize gnus-article-add-buttons)
1240     (gnus-treat-fill-article gnus-article-fill-cited-article)
1241     (gnus-treat-fill-long-lines gnus-article-fill-long-lines)
1242     (gnus-treat-strip-cr gnus-article-remove-cr)
1243     (gnus-treat-unsplit-urls gnus-article-unsplit-urls)
1244     (gnus-treat-date-ut gnus-article-date-ut)
1245     (gnus-treat-date-local gnus-article-date-local)
1246     (gnus-treat-date-english gnus-article-date-english)
1247     (gnus-treat-date-lapsed gnus-article-date-lapsed)
1248     (gnus-treat-date-original gnus-article-date-original)
1249     (gnus-treat-date-user-defined gnus-article-date-user)
1250     (gnus-treat-date-iso8601 gnus-article-date-iso8601)
1251     (gnus-treat-display-xface gnus-article-display-x-face)
1252     (gnus-treat-hide-headers gnus-article-maybe-hide-headers)
1253     (gnus-treat-hide-boring-headers gnus-article-hide-boring-headers)
1254     (gnus-treat-hide-signature gnus-article-hide-signature)
1255     (gnus-treat-strip-list-identifiers gnus-article-hide-list-identifiers)
1256     (gnus-treat-leading-whitespace gnus-article-remove-leading-whitespace)
1257     (gnus-treat-strip-pgp gnus-article-hide-pgp)
1258     (gnus-treat-strip-pem gnus-article-hide-pem)
1259     (gnus-treat-from-picon gnus-treat-from-picon)
1260     (gnus-treat-mail-picon gnus-treat-mail-picon)
1261     (gnus-treat-newsgroups-picon gnus-treat-newsgroups-picon)
1262     (gnus-treat-highlight-headers gnus-article-highlight-headers)
1263     (gnus-treat-highlight-signature gnus-article-highlight-signature)
1264     (gnus-treat-strip-trailing-blank-lines
1265      gnus-article-remove-trailing-blank-lines)
1266     (gnus-treat-strip-leading-blank-lines
1267      gnus-article-strip-leading-blank-lines)
1268     (gnus-treat-strip-multiple-blank-lines
1269      gnus-article-strip-multiple-blank-lines)
1270     (gnus-treat-overstrike gnus-article-treat-overstrike)
1271     (gnus-treat-unfold-headers gnus-article-treat-unfold-headers)
1272     (gnus-treat-fold-headers gnus-article-treat-fold-headers)
1273     (gnus-treat-fold-newsgroups gnus-article-treat-fold-newsgroups)
1274     (gnus-treat-buttonize-head gnus-article-add-buttons-to-head)
1275     (gnus-treat-display-smileys gnus-treat-smiley)
1276     (gnus-treat-capitalize-sentences gnus-article-capitalize-sentences)
1277     (gnus-treat-emphasize gnus-article-emphasize)
1278     (gnus-treat-hide-citation gnus-article-hide-citation)
1279     (gnus-treat-hide-citation-maybe gnus-article-hide-citation-maybe)
1280     (gnus-treat-highlight-citation gnus-article-highlight-citation)
1281     (gnus-treat-body-boundary gnus-article-treat-body-boundary)
1282     (gnus-treat-play-sounds gnus-earcon-display)))
1283
1284 (defvar gnus-article-mime-handle-alist nil)
1285 (defvar article-lapsed-timer nil)
1286 (defvar gnus-article-current-summary nil)
1287
1288 (defvar gnus-article-mode-syntax-table
1289   (let ((table (copy-syntax-table text-mode-syntax-table)))
1290     ;; This causes the citation match run O(2^n).
1291     ;; (modify-syntax-entry ?- "w" table)
1292     (modify-syntax-entry ?> ")" table)
1293     (modify-syntax-entry ?< "(" table)
1294     table)
1295   "Syntax table used in article mode buffers.
1296 Initialized from `text-mode-syntax-table.")
1297
1298 (defvar gnus-save-article-buffer nil)
1299
1300 (defvar gnus-article-mode-line-format-alist
1301   (nconc '((?w (gnus-article-wash-status) ?s)
1302            (?m (gnus-article-mime-part-status) ?s))
1303          gnus-summary-mode-line-format-alist))
1304
1305 (defvar gnus-number-of-articles-to-be-saved nil)
1306
1307 (defvar gnus-inhibit-hiding nil)
1308
1309 ;;; Macros for dealing with the article buffer.
1310
1311 (defmacro gnus-with-article-headers (&rest forms)
1312   `(save-excursion
1313      (set-buffer gnus-article-buffer)
1314      (save-restriction
1315        (let ((buffer-read-only nil)
1316              (inhibit-point-motion-hooks t)
1317              (case-fold-search t))
1318          (article-narrow-to-head)
1319          ,@forms))))
1320
1321 (put 'gnus-with-article-headers 'lisp-indent-function 0)
1322 (put 'gnus-with-article-headers 'edebug-form-spec '(body))
1323
1324 (defmacro gnus-with-article-buffer (&rest forms)
1325   `(save-excursion
1326      (set-buffer gnus-article-buffer)
1327      (let ((buffer-read-only nil))
1328        ,@forms)))
1329
1330 (put 'gnus-with-article-buffer 'lisp-indent-function 0)
1331 (put 'gnus-with-article-buffer 'edebug-form-spec '(body))
1332
1333 (defun gnus-article-goto-header (header)
1334   "Go to HEADER, which is a regular expression."
1335   (re-search-forward (concat "^\\(" header "\\):") nil t))
1336
1337 (defsubst gnus-article-hide-text (b e props)
1338   "Set text PROPS on the B to E region, extending `intangible' 1 past B."
1339   (gnus-add-text-properties-when 'article-type nil b e props)
1340   (when (memq 'intangible props)
1341     (put-text-property
1342      (max (1- b) (point-min))
1343      b 'intangible (cddr (memq 'intangible props)))))
1344
1345 (defsubst gnus-article-unhide-text (b e)
1346   "Remove hidden text properties from region between B and E."
1347   (remove-text-properties b e gnus-hidden-properties)
1348   (when (memq 'intangible gnus-hidden-properties)
1349     (put-text-property (max (1- b) (point-min))
1350                        b 'intangible nil)))
1351
1352 (defun gnus-article-hide-text-type (b e type)
1353   "Hide text of TYPE between B and E."
1354   (gnus-add-wash-type type)
1355   (gnus-article-hide-text
1356    b e (cons 'article-type (cons type gnus-hidden-properties))))
1357
1358 (defun gnus-article-unhide-text-type (b e type)
1359   "Unhide text of TYPE between B and E."
1360   (gnus-delete-wash-type type)
1361   (remove-text-properties
1362    b e (cons 'article-type (cons type gnus-hidden-properties)))
1363   (when (memq 'intangible gnus-hidden-properties)
1364     (put-text-property (max (1- b) (point-min))
1365                        b 'intangible nil)))
1366
1367 (defun gnus-article-hide-text-of-type (type)
1368   "Hide text of TYPE in the current buffer."
1369   (save-excursion
1370     (let ((b (point-min))
1371           (e (point-max)))
1372       (while (setq b (text-property-any b e 'article-type type))
1373         (add-text-properties b (incf b) gnus-hidden-properties)))))
1374
1375 (defun gnus-article-delete-text-of-type (type)
1376   "Delete text of TYPE in the current buffer."
1377   (save-excursion
1378     (let ((b (point-min)))
1379       (while (setq b (text-property-any b (point-max) 'article-type type))
1380         (delete-region
1381          b (or (text-property-not-all b (point-max) 'article-type type)
1382                (point-max)))))))
1383
1384 (defun gnus-article-delete-invisible-text ()
1385   "Delete all invisible text in the current buffer."
1386   (save-excursion
1387     (let ((b (point-min)))
1388       (while (setq b (text-property-any b (point-max) 'invisible t))
1389         (delete-region
1390          b (or (text-property-not-all b (point-max) 'invisible t)
1391                (point-max)))))))
1392
1393 (defun gnus-article-text-type-exists-p (type)
1394   "Say whether any text of type TYPE exists in the buffer."
1395   (text-property-any (point-min) (point-max) 'article-type type))
1396
1397 (defsubst gnus-article-header-rank ()
1398   "Give the rank of the string HEADER as given by `gnus-sorted-header-list'."
1399   (let ((list gnus-sorted-header-list)
1400         (i 0))
1401     (while list
1402       (when (looking-at (car list))
1403         (setq list nil))
1404       (setq list (cdr list))
1405       (incf i))
1406     i))
1407
1408 (defun article-hide-headers (&optional arg delete)
1409   "Hide unwanted headers and possibly sort them as well."
1410   (interactive)
1411   ;; This function might be inhibited.
1412   (unless gnus-inhibit-hiding
1413     (save-excursion
1414       (save-restriction
1415         (let ((buffer-read-only nil)
1416               (case-fold-search t)
1417               (max (1+ (length gnus-sorted-header-list)))
1418               (ignored (when (not gnus-visible-headers)
1419                          (cond ((stringp gnus-ignored-headers)
1420                                 gnus-ignored-headers)
1421                                ((listp gnus-ignored-headers)
1422                                 (mapconcat 'identity gnus-ignored-headers
1423                                            "\\|")))))
1424               (visible
1425                (cond ((stringp gnus-visible-headers)
1426                       gnus-visible-headers)
1427                      ((and gnus-visible-headers
1428                            (listp gnus-visible-headers))
1429                       (mapconcat 'identity gnus-visible-headers "\\|"))))
1430               (inhibit-point-motion-hooks t)
1431               beg)
1432           ;; First we narrow to just the headers.
1433           (article-narrow-to-head)
1434           ;; Hide any "From " lines at the beginning of (mail) articles.
1435           (while (looking-at "From ")
1436             (forward-line 1))
1437           (unless (bobp)
1438             (delete-region (point-min) (point)))
1439           ;; Then treat the rest of the header lines.
1440           ;; Then we use the two regular expressions
1441           ;; `gnus-ignored-headers' and `gnus-visible-headers' to
1442           ;; select which header lines is to remain visible in the
1443           ;; article buffer.
1444           (while (re-search-forward "^[^ \t:]*:" nil t)
1445             (beginning-of-line)
1446             ;; Mark the rank of the header.
1447             (put-text-property
1448              (point) (1+ (point)) 'message-rank
1449              (if (or (and visible (looking-at visible))
1450                      (and ignored
1451                           (not (looking-at ignored))))
1452                  (gnus-article-header-rank)
1453                (+ 2 max)))
1454             (forward-line 1))
1455           (message-sort-headers-1)
1456           (when (setq beg (text-property-any
1457                            (point-min) (point-max) 'message-rank (+ 2 max)))
1458             ;; We delete the unwanted headers.
1459             (gnus-add-wash-type 'headers)
1460             (add-text-properties (point-min) (+ 5 (point-min))
1461                                  '(article-type headers dummy-invisible t))
1462             (delete-region beg (point-max))))))))
1463
1464 (defun article-hide-boring-headers (&optional arg)
1465   "Toggle hiding of headers that aren't very interesting.
1466 If given a negative prefix, always show; if given a positive prefix,
1467 always hide."
1468   (interactive (gnus-article-hidden-arg))
1469   (when (and (not (gnus-article-check-hidden-text 'boring-headers arg))
1470              (not gnus-show-all-headers))
1471     (save-excursion
1472       (save-restriction
1473         (let ((buffer-read-only nil)
1474               (list gnus-boring-article-headers)
1475               (inhibit-point-motion-hooks t)
1476               elem)
1477           (article-narrow-to-head)
1478           (while list
1479             (setq elem (pop list))
1480             (goto-char (point-min))
1481             (cond
1482              ;; Hide empty headers.
1483              ((eq elem 'empty)
1484               (while (re-search-forward "^[^: \t]+:[ \t]*\n[^ \t]" nil t)
1485                 (forward-line -1)
1486                 (gnus-article-hide-text-type
1487                  (progn (beginning-of-line) (point))
1488                  (progn
1489                    (end-of-line)
1490                    (if (re-search-forward "^[^ \t]" nil t)
1491                        (match-beginning 0)
1492                      (point-max)))
1493                  'boring-headers)))
1494              ;; Hide boring Newsgroups header.
1495              ((eq elem 'newsgroups)
1496               (when (gnus-string-equal
1497                      (gnus-fetch-field "newsgroups")
1498                      (gnus-group-real-name
1499                       (if (boundp 'gnus-newsgroup-name)
1500                           gnus-newsgroup-name
1501                         "")))
1502                 (gnus-article-hide-header "newsgroups")))
1503              ((eq elem 'to-address)
1504               (let ((to (message-fetch-field "to"))
1505                     (to-address
1506                      (gnus-parameter-to-address
1507                       (if (boundp 'gnus-newsgroup-name)
1508                           gnus-newsgroup-name ""))))
1509                 (when (and to to-address
1510                            (ignore-errors
1511                              (gnus-string-equal
1512                               ;; only one address in To
1513                               (nth 1 (mail-extract-address-components to))
1514                               to-address)))
1515                   (gnus-article-hide-header "to"))))
1516              ((eq elem 'followup-to)
1517               (when (gnus-string-equal
1518                      (message-fetch-field "followup-to")
1519                      (message-fetch-field "newsgroups"))
1520                 (gnus-article-hide-header "followup-to")))
1521              ((eq elem 'reply-to)
1522               (let ((from (message-fetch-field "from"))
1523                     (reply-to (message-fetch-field "reply-to")))
1524                 (when (and
1525                        from reply-to
1526                        (ignore-errors
1527                          (gnus-string-equal
1528                           (nth 1 (mail-extract-address-components from))
1529                           (nth 1 (mail-extract-address-components reply-to)))))
1530                   (gnus-article-hide-header "reply-to"))))
1531              ((eq elem 'date)
1532               (let ((date (message-fetch-field "date")))
1533                 (when (and date
1534                            (< (days-between (current-time-string) date)
1535                               4))
1536                   (gnus-article-hide-header "date"))))
1537              ((eq elem 'long-to)
1538               (let ((to (message-fetch-field "to"))
1539                     (cc (message-fetch-field "cc")))
1540                 (when (> (length to) 1024)
1541                   (gnus-article-hide-header "to"))
1542                 (when (> (length cc) 1024)
1543                   (gnus-article-hide-header "cc"))))
1544              ((eq elem 'many-to)
1545               (let ((to-count 0)
1546                     (cc-count 0))
1547                 (goto-char (point-min))
1548                 (while (re-search-forward "^to:" nil t)
1549                   (setq to-count (1+ to-count)))
1550                 (when (> to-count 1)
1551                   (while (> to-count 0)
1552                     (goto-char (point-min))
1553                     (save-restriction
1554                       (re-search-forward "^to:" nil nil to-count)
1555                       (forward-line -1)
1556                       (narrow-to-region (point) (point-max))
1557                       (gnus-article-hide-header "to"))
1558                     (setq to-count (1- to-count))))
1559                 (goto-char (point-min))
1560                 (while (re-search-forward "^cc:" nil t)
1561                   (setq cc-count (1+ cc-count)))
1562                 (when (> cc-count 1)
1563                   (while (> cc-count 0)
1564                     (goto-char (point-min))
1565                     (save-restriction
1566                       (re-search-forward "^cc:" nil nil cc-count)
1567                       (forward-line -1)
1568                       (narrow-to-region (point) (point-max))
1569                       (gnus-article-hide-header "cc"))
1570                     (setq cc-count (1- cc-count)))))))))))))
1571
1572 (defun gnus-article-hide-header (header)
1573   (save-excursion
1574     (goto-char (point-min))
1575     (when (re-search-forward (concat "^" header ":") nil t)
1576       (gnus-article-hide-text-type
1577        (progn (beginning-of-line) (point))
1578        (progn
1579          (end-of-line)
1580          (if (re-search-forward "^[^ \t]" nil t)
1581              (match-beginning 0)
1582            (point-max)))
1583        'boring-headers))))
1584
1585 (defvar gnus-article-normalized-header-length 40
1586   "Length of normalized headers.")
1587
1588 (defun article-normalize-headers ()
1589   "Make all header lines 40 characters long."
1590   (interactive)
1591   (let ((buffer-read-only nil)
1592         column)
1593     (save-excursion
1594       (save-restriction
1595         (article-narrow-to-head)
1596         (while (not (eobp))
1597           (cond
1598            ((< (setq column (- (gnus-point-at-eol) (point)))
1599                gnus-article-normalized-header-length)
1600             (end-of-line)
1601             (insert (make-string
1602                      (- gnus-article-normalized-header-length column)
1603                      ? )))
1604            ((> column gnus-article-normalized-header-length)
1605             (gnus-put-text-property
1606              (progn
1607                (forward-char gnus-article-normalized-header-length)
1608                (point))
1609              (gnus-point-at-eol)
1610              'invisible t))
1611            (t
1612             ;; Do nothing.
1613             ))
1614           (forward-line 1))))))
1615
1616 (defun article-treat-dumbquotes ()
1617   "Translate M****s*** sm*rtq**t*s into proper text.
1618 Note that this function guesses whether a character is a sm*rtq**t* or
1619 not, so it should only be used interactively.
1620
1621 Sm*rtq**t*s are M****s***'s unilateral extension to the character map
1622 in an attempt to provide more quoting characters.  If you see
1623 something like \\222 or \\264 where you're expecting some kind of
1624 apostrophe or quotation mark, then try this wash."
1625   (interactive)
1626   (article-translate-strings gnus-article-dumbquotes-map))
1627
1628 (defun article-translate-characters (from to)
1629   "Translate all characters in the body of the article according to FROM and TO.
1630 FROM is a string of characters to translate from; to is a string of
1631 characters to translate to."
1632   (save-excursion
1633     (when (article-goto-body)
1634       (let ((buffer-read-only nil)
1635             (x (make-string 225 ?x))
1636             (i -1))
1637         (while (< (incf i) (length x))
1638           (aset x i i))
1639         (setq i 0)
1640         (while (< i (length from))
1641           (aset x (aref from i) (aref to i))
1642           (incf i))
1643         (translate-region (point) (point-max) x)))))
1644
1645 (defun article-translate-strings (map)
1646   "Translate all string in the body of the article according to MAP.
1647 MAP is an alist where the elements are on the form (\"from\" \"to\")."
1648   (save-excursion
1649     (when (article-goto-body)
1650       (let ((buffer-read-only nil)
1651             elem)
1652         (while (setq elem (pop map))
1653           (save-excursion
1654             (while (search-forward (car elem) nil t)
1655               (replace-match (cadr elem)))))))))
1656
1657 (defun article-treat-overstrike ()
1658   "Translate overstrikes into bold text."
1659   (interactive)
1660   (save-excursion
1661     (when (article-goto-body)
1662       (let ((buffer-read-only nil))
1663         (while (search-forward "\b" nil t)
1664           (let ((next (char-after))
1665                 (previous (char-after (- (point) 2))))
1666             ;; We do the boldification/underlining by hiding the
1667             ;; overstrikes and putting the proper text property
1668             ;; on the letters.
1669             (cond
1670              ((eq next previous)
1671               (gnus-article-hide-text-type (- (point) 2) (point) 'overstrike)
1672               (put-text-property (point) (1+ (point)) 'face 'bold))
1673              ((eq next ?_)
1674               (gnus-article-hide-text-type
1675                (1- (point)) (1+ (point)) 'overstrike)
1676               (put-text-property
1677                (- (point) 2) (1- (point)) 'face 'underline))
1678              ((eq previous ?_)
1679               (gnus-article-hide-text-type (- (point) 2) (point) 'overstrike)
1680               (put-text-property
1681                (point) (1+ (point)) 'face 'underline)))))))))
1682
1683 (defun gnus-article-treat-unfold-headers ()
1684   "Unfold folded message headers.
1685 Only the headers that fit into the current window width will be
1686 unfolded."
1687   (interactive)
1688   (gnus-with-article-headers
1689     (let (length)
1690       (while (not (eobp))
1691         (save-restriction
1692           (mail-header-narrow-to-field)
1693           (let ((header (buffer-substring (point-min) (point-max))))
1694             (with-temp-buffer
1695               (insert header)
1696               (goto-char (point-min))
1697               (while (re-search-forward "\n[\t ]" nil t)
1698                 (replace-match " " t t)))
1699             (setq length (- (point-max) (point-min) 1)))
1700           (when (< length (window-width))
1701             (while (re-search-forward "\n[\t ]" nil t)
1702               (replace-match " " t t)))
1703           (goto-char (point-max)))))))
1704
1705 (defun gnus-article-treat-fold-headers ()
1706   "Fold message headers."
1707   (interactive)
1708   (gnus-with-article-headers
1709     (while (not (eobp))
1710       (save-restriction
1711         (mail-header-narrow-to-field)
1712         (mail-header-fold-field)
1713         (goto-char (point-max))))))
1714
1715 (defun gnus-treat-smiley ()
1716   "Toggle display of textual emoticons (\"smileys\") as small graphical icons."
1717   (interactive)
1718   (gnus-with-article-buffer
1719     (if (memq 'smiley gnus-article-wash-types)
1720         (gnus-delete-images 'smiley)
1721       (article-goto-body)
1722       (let ((images (smiley-region (point) (point-max))))
1723         (when images
1724           (gnus-add-wash-type 'smiley)
1725           (dolist (image images)
1726             (gnus-add-image 'smiley image)))))))
1727
1728 (defun gnus-article-remove-images ()
1729   "Remove all images from the article buffer."
1730   (interactive)
1731   (gnus-with-article-buffer
1732     (dolist (elem gnus-article-image-alist)
1733       (gnus-delete-images (car elem)))))
1734
1735 (defun gnus-article-treat-fold-newsgroups ()
1736   "Unfold folded message headers.
1737 Only the headers that fit into the current window width will be
1738 unfolded."
1739   (interactive)
1740   (gnus-with-article-headers
1741     (while (gnus-article-goto-header "newsgroups\\|followup-to")
1742       (save-restriction
1743         (mail-header-narrow-to-field)
1744         (while (re-search-forward ", *" nil t)
1745           (replace-match ", " t t))
1746         (mail-header-fold-field)
1747         (goto-char (point-max))))))
1748
1749 (defun gnus-article-treat-body-boundary ()
1750   "Place a boundary line at the end of the headers."
1751   (interactive)
1752   (when (and gnus-body-boundary-delimiter
1753              (> (length gnus-body-boundary-delimiter) 0))
1754     (gnus-with-article-headers
1755       (goto-char (point-max))
1756       (let ((start (point)))
1757         (insert "X-Boundary: ")
1758         (gnus-add-text-properties start (point) '(invisible t intangible t))
1759         (insert (let (str)
1760                   (while (>= (1- (window-width)) (length str))
1761                     (setq str (concat str gnus-body-boundary-delimiter)))
1762                   (substring str 0 (1- (window-width))))
1763                 "\n")
1764         (gnus-add-text-properties start (point) '(gnus-decoration 'header))))))
1765
1766 (defun article-fill-long-lines ()
1767   "Fill lines that are wider than the window width."
1768   (interactive)
1769   (save-excursion
1770     (let ((buffer-read-only nil)
1771           (width (window-width (get-buffer-window (current-buffer)))))
1772       (save-restriction
1773         (article-goto-body)
1774         (let ((adaptive-fill-mode nil)) ;Why?  -sm
1775           (while (not (eobp))
1776             (end-of-line)
1777             (when (>= (current-column) (min fill-column width))
1778               (narrow-to-region (point) (gnus-point-at-bol))
1779               (fill-paragraph nil)
1780               (goto-char (point-max))
1781               (widen))
1782             (forward-line 1)))))))
1783
1784 (defun article-capitalize-sentences ()
1785   "Capitalize the first word in each sentence."
1786   (interactive)
1787   (save-excursion
1788     (let ((buffer-read-only nil)
1789           (paragraph-start "^[\n\^L]"))
1790       (article-goto-body)
1791       (while (not (eobp))
1792         (capitalize-word 1)
1793         (forward-sentence)))))
1794
1795 (defun article-remove-cr ()
1796   "Remove trailing CRs and then translate remaining CRs into LFs."
1797   (interactive)
1798   (save-excursion
1799     (let ((buffer-read-only nil))
1800       (goto-char (point-min))
1801       (while (re-search-forward "\r+$" nil t)
1802         (replace-match "" t t))
1803       (goto-char (point-min))
1804       (while (search-forward "\r" nil t)
1805         (replace-match "\n" t t)))))
1806
1807 (defun article-remove-trailing-blank-lines ()
1808   "Remove all trailing blank lines from the article."
1809   (interactive)
1810   (save-excursion
1811     (let ((buffer-read-only nil))
1812       (goto-char (point-max))
1813       (delete-region
1814        (point)
1815        (progn
1816          (while (and (not (bobp))
1817                      (looking-at "^[ \t]*$")
1818                      (not (gnus-annotation-in-region-p
1819                            (point) (gnus-point-at-eol))))
1820            (forward-line -1))
1821          (forward-line 1)
1822          (point))))))
1823
1824 (defun article-display-x-face (&optional force)
1825   "Look for an X-Face header and display it if present."
1826   (interactive (list 'force))
1827   (let ((wash-face-p buffer-read-only)) ;; When type `W f'
1828     (gnus-with-article-headers
1829       ;; Delete the old process, if any.
1830       (when (process-status "article-x-face")
1831         (delete-process "article-x-face"))
1832       (if (memq 'xface gnus-article-wash-types)
1833           ;; We have already displayed X-Faces, so we remove them
1834           ;; instead.
1835           (gnus-delete-images 'xface)
1836         ;; Display X-Faces.
1837         (let (x-faces from face grey)
1838           (save-excursion
1839             (when (and wash-face-p
1840                        (progn
1841                          (goto-char (point-min))
1842                          (not (re-search-forward
1843                                "^X-Face\\(-[0-9]+\\)?:[\t ]*" nil t)))
1844                        (gnus-buffer-live-p gnus-original-article-buffer))
1845               ;; If type `W f', use gnus-original-article-buffer,
1846               ;; otherwise use the current buffer because displaying
1847               ;; RFC822 parts calls this function too.
1848               (set-buffer gnus-original-article-buffer))
1849             (save-restriction
1850               (mail-narrow-to-head)
1851               (let ((regexp
1852                      (if gnus-treat-display-grey-xface
1853                          "x-face\\(-[0-9]+\\)?"
1854                        "x-face")))
1855                 (while (gnus-article-goto-header regexp)
1856                   (when (match-beginning 2)
1857                     (setq grey t))
1858                   (push (mail-header-field-value) x-faces)))
1859               (setq from (message-fetch-field "from"))))
1860           (if grey
1861               (let ((xpm (gnus-convert-gray-x-face-to-xpm x-faces))
1862                     image)
1863                 (when xpm
1864                   (setq image (gnus-create-image xpm 'xpm t))
1865                   (gnus-article-goto-header "from")
1866                   (when (bobp)
1867                     (insert "From: [no `from' set]\n")
1868                     (forward-char -17))
1869                   (gnus-add-wash-type 'xface)
1870                   (gnus-add-image 'xface image)
1871                   (gnus-put-image image)))
1872             ;; Sending multiple EOFs to xv doesn't work, so we only do a
1873             ;; single external face.
1874             (when (stringp gnus-article-x-face-command)
1875               (setq x-faces (list (car x-faces))))
1876             (while (and (setq face (pop x-faces))
1877                         gnus-article-x-face-command
1878                         (or force
1879                             ;; Check whether this face is censored.
1880                             (not gnus-article-x-face-too-ugly)
1881                             (and gnus-article-x-face-too-ugly from
1882                                  (not (string-match gnus-article-x-face-too-ugly
1883                                                     from)))))
1884               ;; We display the face.
1885               (if (symbolp gnus-article-x-face-command)
1886                   ;; The command is a lisp function, so we call it.
1887                   (if (gnus-functionp gnus-article-x-face-command)
1888                       (funcall gnus-article-x-face-command face)
1889                     (error "%s is not a function" gnus-article-x-face-command))
1890                 ;; The command is a string, so we interpret the command
1891                 ;; as a, well, command, and fork it off.
1892                 (let ((process-connection-type nil))
1893                   (process-kill-without-query
1894                    (start-process
1895                     "article-x-face" nil shell-file-name shell-command-switch
1896                     gnus-article-x-face-command))
1897                   (with-temp-buffer
1898                     (insert face)
1899                     (process-send-region "article-x-face"
1900                                          (point-min) (point-max)))
1901                   (process-send-eof "article-x-face"))))))))))
1902
1903 (defun article-decode-mime-words ()
1904   "Decode all MIME-encoded words in the article."
1905   (interactive)
1906   (save-excursion
1907     (set-buffer gnus-article-buffer)
1908     (let ((inhibit-point-motion-hooks t)
1909           buffer-read-only
1910           (mail-parse-charset gnus-newsgroup-charset)
1911           (mail-parse-ignored-charsets
1912            (save-excursion (set-buffer gnus-summary-buffer)
1913                            gnus-newsgroup-ignored-charsets)))
1914       (mail-decode-encoded-word-region (point-min) (point-max)))))
1915
1916 (defun article-decode-charset (&optional prompt)
1917   "Decode charset-encoded text in the article.
1918 If PROMPT (the prefix), prompt for a coding system to use."
1919   (interactive "P")
1920   (let ((inhibit-point-motion-hooks t) (case-fold-search t)
1921         buffer-read-only
1922         (mail-parse-charset gnus-newsgroup-charset)
1923         (mail-parse-ignored-charsets
1924          (save-excursion (condition-case nil
1925                              (set-buffer gnus-summary-buffer)
1926                            (error))
1927                          gnus-newsgroup-ignored-charsets))
1928         ct cte ctl charset format)
1929   (save-excursion
1930     (save-restriction
1931       (article-narrow-to-head)
1932       (setq ct (message-fetch-field "Content-Type" t)
1933             cte (message-fetch-field "Content-Transfer-Encoding" t)
1934             ctl (and ct (ignore-errors
1935                           (mail-header-parse-content-type ct)))
1936             charset (cond
1937                      (prompt
1938                       (mm-read-coding-system "Charset to decode: "))
1939                      (ctl
1940                       (mail-content-type-get ctl 'charset)))
1941             format (and ctl (mail-content-type-get ctl 'format)))
1942       (when cte
1943         (setq cte (mail-header-strip cte)))
1944       (if (and ctl (not (string-match "/" (car ctl))))
1945           (setq ctl nil))
1946       (goto-char (point-max)))
1947     (forward-line 1)
1948     (save-restriction
1949       (narrow-to-region (point) (point-max))
1950       (when (and (eq mail-parse-charset 'gnus-decoded)
1951                  (eq (mm-body-7-or-8) '8bit))
1952         ;; The text code could have been decoded.
1953         (setq charset mail-parse-charset))
1954       (when (and (or (not ctl)
1955                      (equal (car ctl) "text/plain"))
1956                  (not format)) ;; article with format will decode later.
1957         (mm-decode-body
1958          charset (and cte (intern (downcase
1959                                    (gnus-strip-whitespace cte))))
1960          (car ctl)))))))
1961
1962 (defun article-decode-encoded-words ()
1963   "Remove encoded-word encoding from headers."
1964   (let ((inhibit-point-motion-hooks t)
1965         (mail-parse-charset gnus-newsgroup-charset)
1966         (mail-parse-ignored-charsets
1967          (save-excursion (condition-case nil
1968                              (set-buffer gnus-summary-buffer)
1969                            (error))
1970                          gnus-newsgroup-ignored-charsets))
1971         buffer-read-only)
1972     (save-restriction
1973       (article-narrow-to-head)
1974       (funcall gnus-decode-header-function (point-min) (point-max)))))
1975
1976 (defun article-decode-group-name ()
1977   "Decode group names in `Newsgroups:'."
1978   (let ((inhibit-point-motion-hooks t)
1979         buffer-read-only
1980         (method (gnus-find-method-for-group gnus-newsgroup-name)))
1981     (when (and (or gnus-group-name-charset-method-alist
1982                    gnus-group-name-charset-group-alist)
1983                (gnus-buffer-live-p gnus-original-article-buffer))
1984       (when (nnmail-fetch-field "Newsgroups")
1985         (nnheader-replace-header "Newsgroups"
1986                                  (gnus-decode-newsgroups
1987                                   (with-current-buffer
1988                                       gnus-original-article-buffer
1989                                     (nnmail-fetch-field "Newsgroups"))
1990                                   gnus-newsgroup-name method)))
1991       (when (nnmail-fetch-field "Followup-To")
1992         (nnheader-replace-header "Followup-To"
1993                                  (gnus-decode-newsgroups
1994                                   (with-current-buffer
1995                                       gnus-original-article-buffer
1996                                     (nnmail-fetch-field "Followup-To"))
1997                                   gnus-newsgroup-name method))))))
1998
1999 (defun article-de-quoted-unreadable (&optional force read-charset)
2000   "Translate a quoted-printable-encoded article.
2001 If FORCE, decode the article whether it is marked as quoted-printable
2002 or not.
2003 If READ-CHARSET, ask for a coding system."
2004   (interactive (list 'force current-prefix-arg))
2005   (save-excursion
2006     (let ((buffer-read-only nil) type charset)
2007       (if (gnus-buffer-live-p gnus-original-article-buffer)
2008           (with-current-buffer gnus-original-article-buffer
2009             (setq type
2010                   (gnus-fetch-field "content-transfer-encoding"))
2011             (let* ((ct (gnus-fetch-field "content-type"))
2012                    (ctl (and ct
2013                              (ignore-errors
2014                                (mail-header-parse-content-type ct)))))
2015               (setq charset (and ctl
2016                                  (mail-content-type-get ctl 'charset)))
2017               (if (stringp charset)
2018                   (setq charset (intern (downcase charset)))))))
2019       (if read-charset
2020           (setq charset (mm-read-coding-system "Charset: " charset)))
2021       (unless charset
2022         (setq charset gnus-newsgroup-charset))
2023       (when (or force
2024                 (and type (let ((case-fold-search t))
2025                             (string-match "quoted-printable" type))))
2026         (article-goto-body)
2027         (quoted-printable-decode-region
2028          (point) (point-max) (mm-charset-to-coding-system charset))))))
2029
2030 (defun article-de-base64-unreadable (&optional force read-charset)
2031   "Translate a base64 article.
2032 If FORCE, decode the article whether it is marked as base64 not.
2033 If READ-CHARSET, ask for a coding system."
2034   (interactive (list 'force current-prefix-arg))
2035   (save-excursion
2036     (let ((buffer-read-only nil) type charset)
2037       (if (gnus-buffer-live-p gnus-original-article-buffer)
2038           (with-current-buffer gnus-original-article-buffer
2039             (setq type
2040                   (gnus-fetch-field "content-transfer-encoding"))
2041             (let* ((ct (gnus-fetch-field "content-type"))
2042                    (ctl (and ct
2043                              (ignore-errors
2044                                (mail-header-parse-content-type ct)))))
2045               (setq charset (and ctl
2046                                  (mail-content-type-get ctl 'charset)))
2047               (if (stringp charset)
2048                   (setq charset (intern (downcase charset)))))))
2049       (if read-charset
2050           (setq charset (mm-read-coding-system "Charset: " charset)))
2051       (unless charset
2052         (setq charset gnus-newsgroup-charset))
2053       (when (or force
2054                 (and type (let ((case-fold-search t))
2055                             (string-match "base64" type))))
2056         (article-goto-body)
2057         (save-restriction
2058           (narrow-to-region (point) (point-max))
2059           (base64-decode-region (point-min) (point-max))
2060           (mm-decode-coding-region
2061            (point-min) (point-max) (mm-charset-to-coding-system charset)))))))
2062
2063 (eval-when-compile
2064   (require 'rfc1843))
2065
2066 (defun article-decode-HZ ()
2067   "Translate a HZ-encoded article."
2068   (interactive)
2069   (require 'rfc1843)
2070   (save-excursion
2071     (let ((buffer-read-only nil))
2072       (rfc1843-decode-region (point-min) (point-max)))))
2073
2074 (defun article-unsplit-urls ()
2075   "Remove the newlines that some other mailers insert into URLs."
2076   (interactive)
2077   (save-excursion
2078     (let ((buffer-read-only nil))
2079       (goto-char (point-min))
2080       (while (re-search-forward
2081               "^\\(\\(https?\\|ftp\\)://\\S-+\\)\n\\(\\S-+\\)" nil t)
2082         (replace-match "\\1\\3" t)))))
2083
2084 (defun article-wash-html (&optional read-charset)
2085   "Format an html article.
2086 If READ-CHARSET, ask for a coding system."
2087   (interactive "P")
2088   (save-excursion
2089     (let ((buffer-read-only nil)
2090           charset)
2091       (if (gnus-buffer-live-p gnus-original-article-buffer)
2092           (with-current-buffer gnus-original-article-buffer
2093             (let* ((ct (gnus-fetch-field "content-type"))
2094                    (ctl (and ct
2095                              (ignore-errors
2096                                (mail-header-parse-content-type ct)))))
2097               (setq charset (and ctl
2098                                  (mail-content-type-get ctl 'charset)))
2099               (if (stringp charset)
2100                   (setq charset (intern (downcase charset)))))))
2101       (if read-charset
2102           (setq charset (mm-read-coding-system "Charset: " charset)))
2103       (unless charset
2104         (setq charset gnus-newsgroup-charset))
2105       (article-goto-body)
2106       (save-window-excursion
2107         (save-restriction
2108           (narrow-to-region (point) (point-max))
2109           (funcall gnus-article-wash-function))))))
2110
2111 (defun gnus-article-wash-html-with-w3 ()
2112   "Wash the current buffer with w3."
2113   (mm-setup-w3)
2114   (let ((w3-strict-width (window-width))
2115         (url-standalone-mode t)
2116         (url-gateway-unplugged t)
2117         (w3-honor-stylesheets nil)
2118         (w3-delay-image-loads t))
2119     (condition-case var
2120         (w3-region (point-min) (point-max))
2121       (error))))
2122
2123 (defun gnus-article-wash-html-with-w3m ()
2124   "Wash the current buffer with emacs-w3m."
2125   (mm-setup-w3m)
2126   (save-restriction
2127     (narrow-to-region (point) (point-max))
2128     (let ((w3m-safe-url-regexp (if mm-inline-text-html-with-images
2129                                    nil
2130                                  "\\`cid:"))
2131           (w3m-display-inline-images mm-inline-text-html-with-images)
2132           w3m-force-redisplay)
2133       (w3m-region (point-min) (point-max)))
2134     (when mm-inline-text-html-with-w3m-keymap
2135       (add-text-properties
2136        (point-min) (point-max)
2137        (append '(mm-inline-text-html-with-w3m t)
2138                (gnus-local-map-property mm-w3m-mode-map))))))
2139
2140 (defun article-hide-list-identifiers ()
2141   "Remove list identifies from the Subject header.
2142 The `gnus-list-identifiers' variable specifies what to do."
2143   (interactive)
2144   (let ((inhibit-point-motion-hooks t)
2145         (regexp (if (consp gnus-list-identifiers)
2146                     (mapconcat 'identity gnus-list-identifiers " *\\|")
2147                   gnus-list-identifiers))
2148         buffer-read-only)
2149     (when regexp
2150       (save-excursion
2151         (save-restriction
2152           (article-narrow-to-head)
2153           (goto-char (point-min))
2154           (while (re-search-forward
2155                   (concat "^Subject: +\\(R[Ee]: +\\)*\\(" regexp " *\\)")
2156                   nil t)
2157             (delete-region (match-beginning 2) (match-end 0))
2158             (beginning-of-line))
2159           (when (re-search-forward
2160                  "^Subject: +\\(\\(R[Ee]: +\\)+\\)R[Ee]: +" nil t)
2161             (delete-region (match-beginning 1) (match-end 1))))))))
2162
2163 (defun article-hide-pgp ()
2164   "Remove any PGP headers and signatures in the current article."
2165   (interactive)
2166   (save-excursion
2167     (save-restriction
2168       (let ((inhibit-point-motion-hooks t)
2169             buffer-read-only beg end)
2170         (article-goto-body)
2171         ;; Hide the "header".
2172         (when (re-search-forward "^-----BEGIN PGP SIGNED MESSAGE-----\n" nil t)
2173           (gnus-add-wash-type 'pgp)
2174           (delete-region (match-beginning 0) (match-end 0))
2175           ;; Remove armor headers (rfc2440 6.2)
2176           (delete-region (point) (or (re-search-forward "^[ \t]*\n" nil t)
2177                                      (point)))
2178           (setq beg (point))
2179           ;; Hide the actual signature.
2180           (and (search-forward "\n-----BEGIN PGP SIGNATURE-----\n" nil t)
2181                (setq end (1+ (match-beginning 0)))
2182                (delete-region
2183                 end
2184                 (if (search-forward "\n-----END PGP SIGNATURE-----\n" nil t)
2185                     (match-end 0)
2186                   ;; Perhaps we shouldn't hide to the end of the buffer
2187                   ;; if there is no end to the signature?
2188                   (point-max))))
2189           ;; Hide "- " PGP quotation markers.
2190           (when (and beg end)
2191             (narrow-to-region beg end)
2192             (goto-char (point-min))
2193             (while (re-search-forward "^- " nil t)
2194               (delete-region
2195                (match-beginning 0) (match-end 0)))
2196             (widen))
2197           (gnus-run-hooks 'gnus-article-hide-pgp-hook))))))
2198
2199 (defun article-hide-pem (&optional arg)
2200   "Toggle hiding of any PEM headers and signatures in the current article.
2201 If given a negative prefix, always show; if given a positive prefix,
2202 always hide."
2203   (interactive (gnus-article-hidden-arg))
2204   (unless (gnus-article-check-hidden-text 'pem arg)
2205     (save-excursion
2206       (let (buffer-read-only end)
2207         (goto-char (point-min))
2208         ;; Hide the horrendously ugly "header".
2209         (when (and (search-forward
2210                     "\n-----BEGIN PRIVACY-ENHANCED MESSAGE-----\n"
2211                     nil t)
2212                    (setq end (1+ (match-beginning 0))))
2213           (gnus-add-wash-type 'pem)
2214           (gnus-article-hide-text-type
2215            end
2216            (if (search-forward "\n\n" nil t)
2217                (match-end 0)
2218              (point-max))
2219            'pem)
2220           ;; Hide the trailer as well
2221           (when (search-forward "\n-----END PRIVACY-ENHANCED MESSAGE-----\n"
2222                                 nil t)
2223             (gnus-article-hide-text-type
2224              (match-beginning 0) (match-end 0) 'pem)))))))
2225
2226 (defun article-strip-banner ()
2227   "Strip the banner specified by the `banner' group parameter."
2228   (interactive)
2229   (save-excursion
2230     (save-restriction
2231       (let ((inhibit-point-motion-hooks t)
2232             (banner (gnus-parameter-banner gnus-newsgroup-name))
2233             (gnus-signature-limit nil)
2234             buffer-read-only beg end)
2235         (when banner
2236           (article-goto-body)
2237           (cond
2238            ((eq banner 'signature)
2239             (when (gnus-article-narrow-to-signature)
2240               (widen)
2241               (forward-line -1)
2242               (delete-region (point) (point-max))))
2243            ((symbolp banner)
2244             (if (setq banner (cdr (assq banner gnus-article-banner-alist)))
2245                 (while (re-search-forward banner nil t)
2246                   (delete-region (match-beginning 0) (match-end 0)))))
2247            ((stringp banner)
2248             (while (re-search-forward banner nil t)
2249               (delete-region (match-beginning 0) (match-end 0))))))))))
2250
2251 (defun article-babel ()
2252   "Translate article using an online translation service."
2253   (interactive)
2254   (require 'babel)
2255   (save-excursion
2256     (set-buffer gnus-article-buffer)
2257     (when (article-goto-body)
2258       (let* ((buffer-read-only nil)
2259              (start (point))
2260              (end (point-max))
2261              (orig (buffer-substring start end))
2262              (trans (babel-as-string orig)))
2263         (save-restriction
2264           (narrow-to-region start end)
2265           (delete-region start end)
2266           (insert trans))))))
2267
2268 (defun article-hide-signature (&optional arg)
2269   "Hide the signature in the current article.
2270 If given a negative prefix, always show; if given a positive prefix,
2271 always hide."
2272   (interactive (gnus-article-hidden-arg))
2273   (unless (gnus-article-check-hidden-text 'signature arg)
2274     (save-excursion
2275       (save-restriction
2276         (let ((buffer-read-only nil))
2277           (when (gnus-article-narrow-to-signature)
2278             (gnus-article-hide-text-type
2279              (point-min) (point-max) 'signature))))))
2280   (gnus-set-mode-line 'article))
2281
2282 (defun article-strip-headers-in-body ()
2283   "Strip offensive headers from bodies."
2284   (interactive)
2285   (save-excursion
2286     (article-goto-body)
2287     (let ((case-fold-search t))
2288       (when (looking-at "x-no-archive:")
2289         (gnus-delete-line)))))
2290
2291 (defun article-strip-leading-blank-lines ()
2292   "Remove all blank lines from the beginning of the article."
2293   (interactive)
2294   (save-excursion
2295     (let ((inhibit-point-motion-hooks t)
2296           buffer-read-only)
2297       (when (article-goto-body)
2298         (while (and (not (eobp))
2299                     (looking-at "[ \t]*$"))
2300           (gnus-delete-line))))))
2301
2302 (defun article-narrow-to-head ()
2303   "Narrow the buffer to the head of the message.
2304 Point is left at the beginning of the narrowed-to region."
2305   (narrow-to-region
2306    (goto-char (point-min))
2307    (if (search-forward "\n\n" nil 1)
2308        (1- (point))
2309      (point-max)))
2310   (goto-char (point-min)))
2311
2312 (defun article-goto-body ()
2313   "Place point at the start of the body."
2314   (goto-char (point-min))
2315   (cond
2316    ;; This variable is only bound when dealing with separate
2317    ;; MIME body parts.
2318    (article-goto-body-goes-to-point-min-p
2319     t)
2320    ((search-forward "\n\n" nil t)
2321     t)
2322    (t
2323     (goto-char (point-max))
2324     nil)))
2325
2326 (defun article-strip-multiple-blank-lines ()
2327   "Replace consecutive blank lines with one empty line."
2328   (interactive)
2329   (save-excursion
2330     (let ((inhibit-point-motion-hooks t)
2331           buffer-read-only)
2332       ;; First make all blank lines empty.
2333       (article-goto-body)
2334       (while (re-search-forward "^[ \t]+$" nil t)
2335         (unless (gnus-annotation-in-region-p
2336                  (match-beginning 0) (match-end 0))
2337           (replace-match "" nil t)))
2338       ;; Then replace multiple empty lines with a single empty line.
2339       (article-goto-body)
2340       (while (re-search-forward "\n\n\\(\n+\\)" nil t)
2341         (unless (gnus-annotation-in-region-p
2342                  (match-beginning 0) (match-end 0))
2343           (delete-region (match-beginning 1) (match-end 1)))))))
2344
2345 (defun article-strip-leading-space ()
2346   "Remove all white space from the beginning of the lines in the article."
2347   (interactive)
2348   (save-excursion
2349     (let ((inhibit-point-motion-hooks t)
2350           buffer-read-only)
2351       (article-goto-body)
2352       (while (re-search-forward "^[ \t]+" nil t)
2353         (replace-match "" t t)))))
2354
2355 (defun article-strip-trailing-space ()
2356   "Remove all white space from the end of the lines in the article."
2357   (interactive)
2358   (save-excursion
2359     (let ((inhibit-point-motion-hooks t)
2360           buffer-read-only)
2361       (article-goto-body)
2362       (while (re-search-forward "[ \t]+$" nil t)
2363         (replace-match "" t t)))))
2364
2365 (defun article-strip-blank-lines ()
2366   "Strip leading, trailing and multiple blank lines."
2367   (interactive)
2368   (article-strip-leading-blank-lines)
2369   (article-remove-trailing-blank-lines)
2370   (article-strip-multiple-blank-lines))
2371
2372 (defun article-strip-all-blank-lines ()
2373   "Strip all blank lines."
2374   (interactive)
2375   (save-excursion
2376     (let ((inhibit-point-motion-hooks t)
2377           buffer-read-only)
2378       (article-goto-body)
2379       (while (re-search-forward "^[ \t]*\n" nil t)
2380         (replace-match "" t t)))))
2381
2382 (defun gnus-article-narrow-to-signature ()
2383   "Narrow to the signature; return t if a signature is found, else nil."
2384   (let ((inhibit-point-motion-hooks t))
2385     (when (gnus-article-search-signature)
2386       (forward-line 1)
2387       ;; Check whether we have some limits to what we consider
2388       ;; to be a signature.
2389       (let ((limits (if (listp gnus-signature-limit) gnus-signature-limit
2390                       (list gnus-signature-limit)))
2391             limit limited)
2392         (while (setq limit (pop limits))
2393           (if (or (and (integerp limit)
2394                        (< (- (point-max) (point)) limit))
2395                   (and (floatp limit)
2396                        (< (count-lines (point) (point-max)) limit))
2397                   (and (gnus-functionp limit)
2398                        (funcall limit))
2399                   (and (stringp limit)
2400                        (not (re-search-forward limit nil t))))
2401               ()                        ; This limit did not succeed.
2402             (setq limited t
2403                   limits nil)))
2404         (unless limited
2405           (narrow-to-region (point) (point-max))
2406           t)))))
2407
2408 (defun gnus-article-search-signature ()
2409   "Search the current buffer for the signature separator.
2410 Put point at the beginning of the signature separator."
2411   (let ((cur (point)))
2412     (goto-char (point-max))
2413     (if (if (stringp gnus-signature-separator)
2414             (re-search-backward gnus-signature-separator nil t)
2415           (let ((seps gnus-signature-separator))
2416             (while (and seps
2417                         (not (re-search-backward (car seps) nil t)))
2418               (pop seps))
2419             seps))
2420         t
2421       (goto-char cur)
2422       nil)))
2423
2424 (defun gnus-article-hidden-arg ()
2425   "Return the current prefix arg as a number, or 0 if no prefix."
2426   (list (if current-prefix-arg
2427             (prefix-numeric-value current-prefix-arg)
2428           0)))
2429
2430 (defun gnus-article-check-hidden-text (type arg)
2431   "Return nil if hiding is necessary.
2432 Arg can be nil or a number.  nil and positive means hide, negative
2433 means show, 0 means toggle."
2434   (save-excursion
2435     (save-restriction
2436       (let ((hide (gnus-article-hidden-text-p type)))
2437         (cond
2438          ((or (null arg)
2439               (> arg 0))
2440           nil)
2441          ((< arg 0)
2442           (gnus-article-show-hidden-text type)
2443           t)
2444          (t
2445           (if (eq hide 'hidden)
2446               (progn
2447                 (gnus-article-show-hidden-text type)
2448                 t)
2449             nil)))))))
2450
2451 (defun gnus-article-hidden-text-p (type)
2452   "Say whether the current buffer contains hidden text of type TYPE."
2453   (let ((pos (text-property-any (point-min) (point-max) 'article-type type)))
2454     (while (and pos
2455                 (not (get-text-property pos 'invisible))
2456                 (not (get-text-property pos 'dummy-invisible)))
2457       (setq pos
2458             (text-property-any (1+ pos) (point-max) 'article-type type)))
2459     (if pos
2460         'hidden
2461       nil)))
2462
2463 (defun gnus-article-show-hidden-text (type &optional dummy)
2464   "Show all hidden text of type TYPE.
2465 Originally it is hide instead of DUMMY."
2466   (let ((buffer-read-only nil)
2467         (inhibit-point-motion-hooks t))
2468     (gnus-remove-text-properties-when
2469      'article-type type
2470      (point-min) (point-max)
2471      (cons 'article-type (cons type
2472                                gnus-hidden-properties)))
2473     (gnus-delete-wash-type type)))
2474
2475 (defconst article-time-units
2476   `((year . ,(* 365.25 24 60 60))
2477     (week . ,(* 7 24 60 60))
2478     (day . ,(* 24 60 60))
2479     (hour . ,(* 60 60))
2480     (minute . 60)
2481     (second . 1))
2482   "Mapping from time units to seconds.")
2483
2484 (defun article-date-ut (&optional type highlight header)
2485   "Convert DATE date to universal time in the current article.
2486 If TYPE is `local', convert to local time; if it is `lapsed', output
2487 how much time has lapsed since DATE.  For `lapsed', the value of
2488 `gnus-article-date-lapsed-new-header' says whether the \"X-Sent:\" header
2489 should replace the \"Date:\" one, or should be added below it."
2490   (interactive (list 'ut t))
2491   (let* ((header (or header
2492                      (message-fetch-field "date")
2493                      ""))
2494          (tdate-regexp "^Date:[ \t]\\|^X-Sent:[ \t]")
2495          (date-regexp
2496           (cond
2497            ((not gnus-article-date-lapsed-new-header)
2498             tdate-regexp)
2499            ((eq type 'lapsed)
2500             "^X-Sent:[ \t]")
2501            (t
2502             "^Date:[ \t]")))
2503          (date (if (vectorp header) (mail-header-date header)
2504                  header))
2505          (inhibit-point-motion-hooks t)
2506          pos
2507          bface eface)
2508     (save-excursion
2509       (save-restriction
2510         (article-narrow-to-head)
2511         (when (re-search-forward tdate-regexp nil t)
2512           (setq bface (get-text-property (gnus-point-at-bol) 'face)
2513                 date (or (get-text-property (gnus-point-at-bol)
2514                                             'original-date)
2515                          date)
2516                 eface (get-text-property (1- (gnus-point-at-eol)) 'face))
2517           (forward-line 1))
2518         (when (and date (not (string= date "")))
2519           (goto-char (point-min))
2520           (let ((buffer-read-only nil))
2521             ;; Delete any old Date headers.
2522             (while (re-search-forward date-regexp nil t)
2523               (if pos
2524                   (delete-region (progn (beginning-of-line) (point))
2525                                  (progn (forward-line 1) (point)))
2526                 (delete-region (progn (beginning-of-line) (point))
2527                                (progn (end-of-line) (point)))
2528                 (setq pos (point))))
2529             (when (and (not pos) (re-search-forward tdate-regexp nil t))
2530               (forward-line 1))
2531             (if pos (goto-char pos))
2532             (insert (article-make-date-line date (or type 'ut)))
2533             (when (not pos)
2534               (insert "\n")
2535               (forward-line -1))
2536             ;; Do highlighting.
2537             (beginning-of-line)
2538             (when (looking-at "\\([^:]+\\): *\\(.*\\)$")
2539               (put-text-property (match-beginning 1) (1+ (match-end 1))
2540                                  'original-date date)
2541               (put-text-property (match-beginning 1) (1+ (match-end 1))
2542                                  'face bface)
2543               (put-text-property (match-beginning 2) (match-end 2)
2544                                  'face eface))))))))
2545
2546 (defun article-make-date-line (date type)
2547   "Return a DATE line of TYPE."
2548   (unless (memq type '(local ut original user iso8601 lapsed english))
2549     (error "Unknown conversion type: %s" type))
2550   (condition-case ()
2551       (let ((time (date-to-time date)))
2552         (cond
2553          ;; Convert to the local timezone.
2554          ((eq type 'local)
2555           (let ((tz (car (current-time-zone time))))
2556             (format "Date: %s %s%02d%02d" (current-time-string time)
2557                     (if (> tz 0) "+" "-") (/ (abs tz) 3600)
2558                     (/ (% (abs tz) 3600) 60))))
2559          ;; Convert to Universal Time.
2560          ((eq type 'ut)
2561           (concat "Date: "
2562                   (current-time-string
2563                    (let* ((e (parse-time-string date))
2564                           (tm (apply 'encode-time e))
2565                           (ms (car tm))
2566                           (ls (- (cadr tm) (car (current-time-zone time)))))
2567                      (cond ((< ls 0) (list (1- ms) (+ ls 65536)))
2568                            ((> ls 65535) (list (1+ ms) (- ls 65536)))
2569                            (t (list ms ls)))))
2570                   " UT"))
2571          ;; Get the original date from the article.
2572          ((eq type 'original)
2573           (concat "Date: " (if (string-match "\n+$" date)
2574                                (substring date 0 (match-beginning 0))
2575                              date)))
2576          ;; Let the user define the format.
2577          ((eq type 'user)
2578           (if (gnus-functionp gnus-article-time-format)
2579               (funcall gnus-article-time-format time)
2580             (concat
2581              "Date: "
2582              (format-time-string gnus-article-time-format time))))
2583          ;; ISO 8601.
2584          ((eq type 'iso8601)
2585           (let ((tz (car (current-time-zone time))))
2586             (concat
2587              "Date: "
2588              (format-time-string "%Y%m%dT%H%M%S" time)
2589              (format "%s%02d%02d"
2590                      (if (> tz 0) "+" "-") (/ (abs tz) 3600)
2591                      (/ (% (abs tz) 3600) 60)))))
2592          ;; Do an X-Sent lapsed format.
2593          ((eq type 'lapsed)
2594           ;; If the date is seriously mangled, the timezone functions are
2595           ;; liable to bug out, so we ignore all errors.
2596           (let* ((now (current-time))
2597                  (real-time (subtract-time now time))
2598                  (real-sec (and real-time
2599                                 (+ (* (float (car real-time)) 65536)
2600                                    (cadr real-time))))
2601                  (sec (and real-time (abs real-sec)))
2602                  num prev)
2603             (cond
2604              ((null real-time)
2605               "X-Sent: Unknown")
2606              ((zerop sec)
2607               "X-Sent: Now")
2608              (t
2609               (concat
2610                "X-Sent: "
2611                ;; This is a bit convoluted, but basically we go
2612                ;; through the time units for years, weeks, etc,
2613                ;; and divide things to see whether that results
2614                ;; in positive answers.
2615                (mapconcat
2616                 (lambda (unit)
2617                   (if (zerop (setq num (ffloor (/ sec (cdr unit)))))
2618                       ;; The (remaining) seconds are too few to
2619                       ;; be divided into this time unit.
2620                       ""
2621                     ;; It's big enough, so we output it.
2622                     (setq sec (- sec (* num (cdr unit))))
2623                     (prog1
2624                         (concat (if prev ", " "") (int-to-string
2625                                                    (floor num))
2626                                 " " (symbol-name (car unit))
2627                                 (if (> num 1) "s" ""))
2628                       (setq prev t))))
2629                 article-time-units "")
2630                ;; If dates are odd, then it might appear like the
2631                ;; article was sent in the future.
2632                (if (> real-sec 0)
2633                    " ago"
2634                  " in the future"))))))
2635          ;; Display the date in proper English
2636          ((eq type 'english)
2637           (let ((dtime (decode-time time)))
2638             (concat
2639              "Date: the "
2640              (number-to-string (nth 3 dtime))
2641              (let ((digit (% (nth 3 dtime) 10)))
2642                (cond
2643                 ((memq (nth 3 dtime) '(11 12 13)) "th")
2644                 ((= digit 1) "st")
2645                 ((= digit 2) "nd")
2646                 ((= digit 3) "rd")
2647                 (t "th")))
2648              " of "
2649              (nth (1- (nth 4 dtime)) gnus-english-month-names)
2650              " "
2651              (number-to-string (nth 5 dtime))
2652              " at "
2653              (format "%02d" (nth 2 dtime))
2654              ":"
2655              (format "%02d" (nth 1 dtime)))))))
2656         (error
2657          (format "Date: %s (from Oort)" date))))
2658
2659 (defun article-date-local (&optional highlight)
2660   "Convert the current article date to the local timezone."
2661   (interactive (list t))
2662   (article-date-ut 'local highlight))
2663
2664 (defun article-date-english (&optional highlight)
2665   "Convert the current article date to something that is proper English."
2666   (interactive (list t))
2667   (article-date-ut 'english highlight))
2668
2669 (defun article-date-original (&optional highlight)
2670   "Convert the current article date to what it was originally.
2671 This is only useful if you have used some other date conversion
2672 function and want to see what the date was before converting."
2673   (interactive (list t))
2674   (article-date-ut 'original highlight))
2675
2676 (defun article-date-lapsed (&optional highlight)
2677   "Convert the current article date to time lapsed since it was sent."
2678   (interactive (list t))
2679   (article-date-ut 'lapsed highlight))
2680
2681 (defun article-update-date-lapsed ()
2682   "Function to be run from a timer to update the lapsed time line."
2683   (let (deactivate-mark)
2684     (save-excursion
2685       (ignore-errors
2686         (walk-windows
2687          (lambda (w)
2688            (set-buffer (window-buffer w))
2689            (when (eq major-mode 'gnus-article-mode)
2690              (goto-char (point-min))
2691              (when (re-search-forward "^X-Sent:" nil t)
2692                (article-date-lapsed t))))
2693          nil 'visible)))))
2694
2695 (defun gnus-start-date-timer (&optional n)
2696   "Start a timer to update the X-Sent header in the article buffers.
2697 The numerical prefix says how frequently (in seconds) the function
2698 is to run."
2699   (interactive "p")
2700   (unless n
2701     (setq n 1))
2702   (gnus-stop-date-timer)
2703   (setq article-lapsed-timer
2704         (nnheader-run-at-time 1 n 'article-update-date-lapsed)))
2705
2706 (defun gnus-stop-date-timer ()
2707   "Stop the X-Sent timer."
2708   (interactive)
2709   (when article-lapsed-timer
2710     (nnheader-cancel-timer article-lapsed-timer)
2711     (setq article-lapsed-timer nil)))
2712
2713 (defun article-date-user (&optional highlight)
2714   "Convert the current article date to the user-defined format.
2715 This format is defined by the `gnus-article-time-format' variable."
2716   (interactive (list t))
2717   (article-date-ut 'user highlight))
2718
2719 (defun article-date-iso8601 (&optional highlight)
2720   "Convert the current article date to ISO8601."
2721   (interactive (list t))
2722   (article-date-ut 'iso8601 highlight))
2723
2724 ;; (defun article-show-all ()
2725 ;;   "Show all hidden text in the article buffer."
2726 ;;   (interactive)
2727 ;;   (save-excursion
2728 ;;     (let ((buffer-read-only nil))
2729 ;;       (gnus-article-unhide-text (point-min) (point-max)))))
2730
2731 (defun article-remove-leading-whitespace ()
2732   "Remove excessive whitespace from all headers."
2733   (interactive)
2734   (save-excursion
2735     (save-restriction
2736       (let ((buffer-read-only nil))
2737         (article-narrow-to-head)
2738         (goto-char (point-min))
2739         (while (re-search-forward "^[^ :]+: \\([ \t]+\\)" nil t)
2740           (delete-region (match-beginning 1) (match-end 1)))))))
2741
2742 (defun article-emphasize (&optional arg)
2743   "Emphasize text according to `gnus-emphasis-alist'."
2744   (interactive (gnus-article-hidden-arg))
2745   (unless (gnus-article-check-hidden-text 'emphasis arg)
2746     (save-excursion
2747       (let ((alist (or
2748                     (condition-case nil
2749                         (with-current-buffer gnus-summary-buffer
2750                           gnus-article-emphasis-alist)
2751                       (error))
2752                     gnus-emphasis-alist))
2753             (buffer-read-only nil)
2754             (props (append '(article-type emphasis)
2755                            gnus-hidden-properties))
2756             regexp elem beg invisible visible face)
2757         (article-goto-body)
2758         (setq beg (point))
2759         (while (setq elem (pop alist))
2760           (goto-char beg)
2761           (setq regexp (car elem)
2762                 invisible (nth 1 elem)
2763                 visible (nth 2 elem)
2764                 face (nth 3 elem))
2765           (while (re-search-forward regexp nil t)
2766             (when (and (match-beginning visible) (match-beginning invisible))
2767               (gnus-article-hide-text
2768                (match-beginning invisible) (match-end invisible) props)
2769               (gnus-article-unhide-text-type
2770                (match-beginning visible) (match-end visible) 'emphasis)
2771               (gnus-put-overlay-excluding-newlines
2772                (match-beginning visible) (match-end visible) 'face face)
2773               (gnus-add-wash-type 'emphasis)
2774               (goto-char (match-end invisible)))))))))
2775
2776 (defun gnus-article-setup-highlight-words (&optional highlight-words)
2777   "Setup newsgroup emphasis alist."
2778   (unless gnus-article-emphasis-alist
2779     (let ((name (and gnus-newsgroup-name
2780                      (gnus-group-real-name gnus-newsgroup-name))))
2781       (make-local-variable 'gnus-article-emphasis-alist)
2782       (setq gnus-article-emphasis-alist
2783             (nconc
2784              (let ((alist gnus-group-highlight-words-alist) elem highlight)
2785                (while (setq elem (pop alist))
2786                  (when (and name (string-match (car elem) name))
2787                    (setq alist nil
2788                          highlight (copy-sequence (cdr elem)))))
2789                highlight)
2790              (copy-sequence highlight-words)
2791              (if gnus-newsgroup-name
2792                  (copy-sequence (gnus-group-find-parameter
2793                                  gnus-newsgroup-name 'highlight-words t)))
2794              gnus-emphasis-alist)))))
2795
2796 (eval-when-compile
2797   (defvar gnus-summary-article-menu)
2798   (defvar gnus-summary-post-menu))
2799
2800 ;;; Saving functions.
2801
2802 (defun gnus-article-save (save-buffer file &optional num)
2803   "Save the currently selected article."
2804   (unless gnus-save-all-headers
2805     ;; Remove headers according to `gnus-saved-headers'.
2806     (let ((gnus-visible-headers
2807            (or gnus-saved-headers gnus-visible-headers))
2808           (gnus-article-buffer save-buffer))
2809       (save-excursion
2810         (set-buffer save-buffer)
2811         (article-hide-headers 1 t))))
2812   (save-window-excursion
2813     (if (not gnus-default-article-saver)
2814         (error "No default saver is defined")
2815       ;; !!! Magic!  The saving functions all save
2816       ;; `gnus-save-article-buffer' (or so they think), but we
2817       ;; bind that variable to our save-buffer.
2818       (set-buffer gnus-article-buffer)
2819       (let* ((gnus-save-article-buffer save-buffer)
2820              (filename
2821               (cond
2822                ((not gnus-prompt-before-saving) 'default)
2823                ((eq gnus-prompt-before-saving 'always) nil)
2824                (t file)))
2825              (gnus-number-of-articles-to-be-saved
2826               (when (eq gnus-prompt-before-saving t)
2827                 num)))                  ; Magic
2828         (set-buffer gnus-article-current-summary)
2829         (funcall gnus-default-article-saver filename)))))
2830
2831 (defun gnus-read-save-file-name (prompt &optional filename
2832                                         function group headers variable)
2833   (let ((default-name
2834           (funcall function group headers (symbol-value variable)))
2835         result)
2836     (setq result
2837           (expand-file-name
2838            (cond
2839             ((eq filename 'default)
2840              default-name)
2841             ((eq filename t)
2842              default-name)
2843             (filename filename)
2844             (t
2845              (let* ((split-name (gnus-get-split-value gnus-split-methods))
2846                     (prompt
2847                      (format prompt
2848                              (if (and gnus-number-of-articles-to-be-saved
2849                                       (> gnus-number-of-articles-to-be-saved 1))
2850                                  (format "these %d articles"
2851                                          gnus-number-of-articles-to-be-saved)
2852                                "this article")))
2853                     (file
2854                      ;; Let the split methods have their say.
2855                      (cond
2856                       ;; No split name was found.
2857                       ((null split-name)
2858                        (read-file-name
2859                         (concat prompt " (default "
2860                                 (file-name-nondirectory default-name) ") ")
2861                         (file-name-directory default-name)
2862                         default-name))
2863                       ;; A single group name is returned.
2864                       ((stringp split-name)
2865                        (setq default-name
2866                              (funcall function split-name headers
2867                                       (symbol-value variable)))
2868                        (read-file-name
2869                         (concat prompt " (default "
2870                                 (file-name-nondirectory default-name) ") ")
2871                         (file-name-directory default-name)
2872                         default-name))
2873                       ;; A single split name was found
2874                       ((= 1 (length split-name))
2875                        (let* ((name (expand-file-name
2876                                      (car split-name)
2877                                      gnus-article-save-directory))
2878                               (dir (cond ((file-directory-p name)
2879                                           (file-name-as-directory name))
2880                                          ((file-exists-p name) name)
2881                                          (t gnus-article-save-directory))))
2882                          (read-file-name
2883                           (concat prompt " (default " name ") ")
2884                           dir name)))
2885                       ;; A list of splits was found.
2886                       (t
2887                        (setq split-name (nreverse split-name))
2888                        (let (result)
2889                          (let ((file-name-history
2890                                 (nconc split-name file-name-history)))
2891                            (setq result
2892                                  (expand-file-name
2893                                   (read-file-name
2894                                    (concat prompt " (`M-p' for defaults) ")
2895                                    gnus-article-save-directory
2896                                    (car split-name))
2897                                   gnus-article-save-directory)))
2898                          (car (push result file-name-history)))))))
2899                ;; Create the directory.
2900                (gnus-make-directory (file-name-directory file))
2901       ;; If we have read a directory, we append the default file name.
2902                (when (file-directory-p file)
2903                  (setq file (expand-file-name (file-name-nondirectory
2904                                                default-name)
2905                                               (file-name-as-directory file))))
2906                ;; Possibly translate some characters.
2907                (nnheader-translate-file-chars file))))))
2908     (gnus-make-directory (file-name-directory result))
2909     (set variable result)))
2910
2911 (defun gnus-article-archive-name (group)
2912   "Return the first instance of an \"Archive-name\" in the current buffer."
2913   (let ((case-fold-search t))
2914     (when (re-search-forward "archive-name: *\\([^ \n\t]+\\)[ \t]*$" nil t)
2915       (nnheader-concat gnus-article-save-directory
2916                        (match-string 1)))))
2917
2918 (defun gnus-article-nndoc-name (group)
2919   "If GROUP is an nndoc group, return the name of the parent group."
2920   (when (eq (car (gnus-find-method-for-group group)) 'nndoc)
2921     (gnus-group-get-parameter group 'save-article-group)))
2922
2923 (defun gnus-summary-save-in-rmail (&optional filename)
2924   "Append this article to Rmail file.
2925 Optional argument FILENAME specifies file name.
2926 Directory to save to is default to `gnus-article-save-directory'."
2927   (setq filename (gnus-read-save-file-name
2928                   "Save %s in rmail file:" filename
2929                   gnus-rmail-save-name gnus-newsgroup-name
2930                   gnus-current-headers 'gnus-newsgroup-last-rmail))
2931   (gnus-eval-in-buffer-window gnus-save-article-buffer
2932     (save-excursion
2933       (save-restriction
2934         (widen)
2935         (gnus-output-to-rmail filename))))
2936   filename)
2937
2938 (defun gnus-summary-save-in-mail (&optional filename)
2939   "Append this article to Unix mail file.
2940 Optional argument FILENAME specifies file name.
2941 Directory to save to is default to `gnus-article-save-directory'."
2942   (setq filename (gnus-read-save-file-name
2943                   "Save %s in Unix mail file:" filename
2944                   gnus-mail-save-name gnus-newsgroup-name
2945                   gnus-current-headers 'gnus-newsgroup-last-mail))
2946   (gnus-eval-in-buffer-window gnus-save-article-buffer
2947     (save-excursion
2948       (save-restriction
2949         (widen)
2950         (if (and (file-readable-p filename)
2951                  (mail-file-babyl-p filename))
2952             (rmail-output-to-rmail-file filename t)
2953           (gnus-output-to-mail filename)))))
2954   filename)
2955
2956 (defun gnus-summary-save-in-file (&optional filename overwrite)
2957   "Append this article to file.
2958 Optional argument FILENAME specifies file name.
2959 Directory to save to is default to `gnus-article-save-directory'."
2960   (setq filename (gnus-read-save-file-name
2961                   "Save %s in file:" filename
2962                   gnus-file-save-name gnus-newsgroup-name
2963                   gnus-current-headers 'gnus-newsgroup-last-file))
2964   (gnus-eval-in-buffer-window gnus-save-article-buffer
2965     (save-excursion
2966       (save-restriction
2967         (widen)
2968         (when (and overwrite
2969                    (file-exists-p filename))
2970           (delete-file filename))
2971         (gnus-output-to-file filename))))
2972   filename)
2973
2974 (defun gnus-summary-write-to-file (&optional filename)
2975   "Write this article to a file, overwriting it if the file exists.
2976 Optional argument FILENAME specifies file name.
2977 The directory to save in defaults to `gnus-article-save-directory'."
2978   (gnus-summary-save-in-file nil t))
2979
2980 (defun gnus-summary-save-body-in-file (&optional filename)
2981   "Append this article body to a file.
2982 Optional argument FILENAME specifies file name.
2983 The directory to save in defaults to `gnus-article-save-directory'."
2984   (setq filename (gnus-read-save-file-name
2985                   "Save %s body in file:" filename
2986                   gnus-file-save-name gnus-newsgroup-name
2987                   gnus-current-headers 'gnus-newsgroup-last-file))
2988   (gnus-eval-in-buffer-window gnus-save-article-buffer
2989     (save-excursion
2990       (save-restriction
2991         (widen)
2992         (when (article-goto-body)
2993           (narrow-to-region (point) (point-max)))
2994         (gnus-output-to-file filename))))
2995   filename)
2996
2997 (defun gnus-summary-save-in-pipe (&optional command)
2998   "Pipe this article to subprocess."
2999   (setq command
3000         (cond ((and (eq command 'default)
3001                     gnus-last-shell-command)
3002                gnus-last-shell-command)
3003               ((stringp command)
3004                command)
3005               (t (read-string
3006                   (format
3007                    "Shell command on %s: "
3008                    (if (and gnus-number-of-articles-to-be-saved
3009                             (> gnus-number-of-articles-to-be-saved 1))
3010                        (format "these %d articles"
3011                                gnus-number-of-articles-to-be-saved)
3012                      "this article"))
3013                   gnus-last-shell-command))))
3014   (when (string-equal command "")
3015     (if gnus-last-shell-command
3016         (setq command gnus-last-shell-command)
3017       (error "A command is required")))
3018   (gnus-eval-in-buffer-window gnus-article-buffer
3019     (save-restriction
3020       (widen)
3021       (shell-command-on-region (point-min) (point-max) command nil)))
3022   (setq gnus-last-shell-command command))
3023
3024 (defun gnus-summary-pipe-to-muttprint (&optional command)
3025   "Pipe this article to muttprint."
3026   (setq command (read-string
3027                  "Print using command: " gnus-summary-muttprint-program
3028                  nil gnus-summary-muttprint-program))
3029   (gnus-summary-save-in-pipe command))
3030
3031 ;;; Article file names when saving.
3032
3033 (defun gnus-capitalize-newsgroup (newsgroup)
3034   "Capitalize NEWSGROUP name."
3035   (when (not (zerop (length newsgroup)))
3036     (concat (char-to-string (upcase (aref newsgroup 0)))
3037             (substring newsgroup 1))))
3038
3039 (defun gnus-Numeric-save-name (newsgroup headers &optional last-file)
3040   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
3041 If variable `gnus-use-long-file-name' is non-nil, it is ~/News/News.group/num.
3042 Otherwise, it is like ~/News/news/group/num."
3043   (let ((default
3044           (expand-file-name
3045            (concat (if (gnus-use-long-file-name 'not-save)
3046                        (gnus-capitalize-newsgroup newsgroup)
3047                      (gnus-newsgroup-directory-form newsgroup))
3048                    "/" (int-to-string (mail-header-number headers)))
3049            gnus-article-save-directory)))
3050     (if (and last-file
3051              (string-equal (file-name-directory default)
3052                            (file-name-directory last-file))
3053              (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
3054         default
3055       (or last-file default))))
3056
3057 (defun gnus-numeric-save-name (newsgroup headers &optional last-file)
3058   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
3059 If variable `gnus-use-long-file-name' is non-nil, it is
3060 ~/News/news.group/num.  Otherwise, it is like ~/News/news/group/num."
3061   (let ((default
3062           (expand-file-name
3063            (concat (if (gnus-use-long-file-name 'not-save)
3064                        newsgroup
3065                      (gnus-newsgroup-directory-form newsgroup))
3066                    "/" (int-to-string (mail-header-number headers)))
3067            gnus-article-save-directory)))
3068     (if (and last-file
3069              (string-equal (file-name-directory default)
3070                            (file-name-directory last-file))
3071              (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
3072         default
3073       (or last-file default))))
3074
3075 (defun gnus-plain-save-name (newsgroup headers &optional last-file)
3076   "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE.
3077 If variable `gnus-use-long-file-name' is non-nil, it is
3078 ~/News/news.group.  Otherwise, it is like ~/News/news/group/news."
3079   (or last-file
3080       (expand-file-name
3081        (if (gnus-use-long-file-name 'not-save)
3082            newsgroup
3083          (file-relative-name
3084           (expand-file-name "news" (gnus-newsgroup-directory-form newsgroup))
3085           default-directory))
3086        gnus-article-save-directory)))
3087
3088 (defun gnus-sender-save-name (newsgroup headers &optional last-file)
3089   "Generate file name from sender."
3090   (let ((from (mail-header-from headers)))
3091     (expand-file-name
3092      (if (and from (string-match "\\([^ <]+\\)@" from))
3093          (match-string 1 from)
3094        "nobody")
3095      gnus-article-save-directory)))
3096
3097 (defun article-verify-x-pgp-sig ()
3098   "Verify X-PGP-Sig."
3099   (interactive)
3100   (if (gnus-buffer-live-p gnus-original-article-buffer)
3101       (let ((sig (with-current-buffer gnus-original-article-buffer
3102                    (gnus-fetch-field "X-PGP-Sig")))
3103             items info headers)
3104         (when (and sig
3105                    mml2015-use
3106                    (mml2015-clear-verify-function))
3107           (with-temp-buffer
3108             (insert-buffer gnus-original-article-buffer)
3109             (setq items (split-string sig))
3110             (message-narrow-to-head)
3111             (let ((inhibit-point-motion-hooks t)
3112                   (case-fold-search t))
3113               ;; Don't verify multiple headers.
3114               (setq headers (mapconcat (lambda (header)
3115                                          (concat header ": "
3116                                                  (mail-fetch-field header) "\n"))
3117                                        (split-string (nth 1 items) ",") "")))
3118             (delete-region (point-min) (point-max))
3119             (insert "-----BEGIN PGP SIGNED MESSAGE-----\n\n")
3120             (insert "X-Signed-Headers: " (nth 1 items) "\n")
3121             (insert headers)
3122             (widen)
3123             (forward-line)
3124             (while (not (eobp))
3125               (if (looking-at "^-")
3126                   (insert "- "))
3127               (forward-line))
3128             (insert "\n-----BEGIN PGP SIGNATURE-----\n")
3129             (insert "Version: " (car items) "\n\n")
3130             (insert (mapconcat 'identity (cddr items) "\n"))
3131             (insert "\n-----END PGP SIGNATURE-----\n")
3132             (let ((mm-security-handle (list (format "multipart/signed"))))
3133               (mml2015-clean-buffer)
3134               (let ((coding-system-for-write (or gnus-newsgroup-charset
3135                                                  'iso-8859-1)))
3136                 (funcall (mml2015-clear-verify-function)))
3137               (setq info
3138                     (or (mm-handle-multipart-ctl-parameter
3139                          mm-security-handle 'gnus-details)
3140                         (mm-handle-multipart-ctl-parameter
3141                          mm-security-handle 'gnus-info)))))
3142           (when info
3143             (let (buffer-read-only bface eface)
3144               (save-restriction
3145                 (message-narrow-to-head)
3146                 (goto-char (point-max))
3147                 (forward-line -1)
3148                 (setq bface (get-text-property (gnus-point-at-bol) 'face)
3149                       eface (get-text-property (1- (gnus-point-at-eol)) 'face))
3150                 (message-remove-header "X-Gnus-PGP-Verify")
3151                 (if (re-search-forward "^X-PGP-Sig:" nil t)
3152                     (forward-line)
3153                   (goto-char (point-max)))
3154                 (narrow-to-region (point) (point))
3155                 (insert "X-Gnus-PGP-Verify: " info "\n")
3156                 (goto-char (point-min))
3157                 (forward-line)
3158                 (while (not (eobp))
3159                   (if (not (looking-at "^[ \t]"))
3160                       (insert " "))
3161                   (forward-line))
3162                 ;; Do highlighting.
3163                 (goto-char (point-min))
3164                 (when (looking-at "\\([^:]+\\): *")
3165                   (put-text-property (match-beginning 1) (1+ (match-end 1))
3166                                      'face bface)
3167                   (put-text-property (match-end 0) (point-max)
3168                                      'face eface)))))))))
3169
3170 (defun article-verify-cancel-lock ()
3171   "Verify Cancel-Lock header."
3172   (interactive)
3173   (if (gnus-buffer-live-p gnus-original-article-buffer)
3174       (canlock-verify gnus-original-article-buffer)))
3175
3176 (eval-and-compile
3177   (mapcar
3178    (lambda (func)
3179      (let (afunc gfunc)
3180        (if (consp func)
3181            (setq afunc (car func)
3182                  gfunc (cdr func))
3183          (setq afunc func
3184                gfunc (intern (format "gnus-%s" func))))
3185        (defalias gfunc
3186          (when (fboundp afunc)
3187            `(lambda (&optional interactive &rest args)
3188               ,(documentation afunc t)
3189               (interactive (list t))
3190               (save-excursion
3191                 (set-buffer gnus-article-buffer)
3192                 (if interactive
3193                     (call-interactively ',afunc)
3194                   (apply ',afunc args))))))))
3195    '(article-hide-headers
3196      article-verify-x-pgp-sig
3197      article-verify-cancel-lock
3198      article-hide-boring-headers
3199      article-treat-overstrike
3200      article-fill-long-lines
3201      article-capitalize-sentences
3202      article-remove-cr
3203      article-remove-leading-whitespace
3204      article-display-x-face
3205      article-de-quoted-unreadable
3206      article-de-base64-unreadable
3207      article-decode-HZ
3208      article-wash-html
3209      article-unsplit-urls
3210      article-hide-list-identifiers
3211      article-hide-pgp
3212      article-strip-banner
3213      article-babel
3214      article-hide-pem
3215      article-hide-signature
3216      article-strip-headers-in-body
3217      article-remove-trailing-blank-lines
3218      article-strip-leading-blank-lines
3219      article-strip-multiple-blank-lines
3220      article-strip-leading-space
3221      article-strip-trailing-space
3222      article-strip-blank-lines
3223      article-strip-all-blank-lines
3224      article-replace-with-quoted-text
3225      article-date-local
3226      article-date-english
3227      article-date-iso8601
3228      article-date-original
3229      article-date-ut
3230      article-decode-mime-words
3231      article-decode-charset
3232      article-decode-encoded-words
3233      article-date-user
3234      article-date-lapsed
3235      article-emphasize
3236      article-treat-dumbquotes
3237      article-normalize-headers
3238 ;;     (article-show-all . gnus-article-show-all-headers)
3239      )))
3240 \f
3241 ;;;
3242 ;;; Gnus article mode
3243 ;;;
3244
3245 (put 'gnus-article-mode 'mode-class 'special)
3246
3247 (set-keymap-parent gnus-article-mode-map widget-keymap)
3248
3249 (gnus-define-keys gnus-article-mode-map
3250   " " gnus-article-goto-next-page
3251   "\177" gnus-article-goto-prev-page
3252   [delete] gnus-article-goto-prev-page
3253   [backspace] gnus-article-goto-prev-page
3254   "\C-c^" gnus-article-refer-article
3255   "h" gnus-article-show-summary
3256   "s" gnus-article-show-summary
3257   "\C-c\C-m" gnus-article-mail
3258   "?" gnus-article-describe-briefly
3259   "e" gnus-summary-edit-article
3260   "<" beginning-of-buffer
3261   ">" end-of-buffer
3262   "\C-c\C-i" gnus-info-find-node
3263   "\C-c\C-b" gnus-bug
3264   "R" gnus-article-reply-with-original
3265   "F" gnus-article-followup-with-original
3266   "\C-hk" gnus-article-describe-key
3267   "\C-hc" gnus-article-describe-key-briefly
3268
3269   "\C-d" gnus-article-read-summary-keys
3270   "\M-*" gnus-article-read-summary-keys
3271   "\M-#" gnus-article-read-summary-keys
3272   "\M-^" gnus-article-read-summary-keys
3273   "\M-g" gnus-article-read-summary-keys)
3274
3275 (substitute-key-definition
3276  'undefined 'gnus-article-read-summary-keys gnus-article-mode-map)
3277
3278 (defun gnus-article-make-menu-bar ()
3279   (unless (boundp 'gnus-article-commands-menu)
3280     (gnus-summary-make-menu-bar))
3281   (gnus-turn-off-edit-menu 'article)
3282   (unless (boundp 'gnus-article-article-menu)
3283     (easy-menu-define
3284      gnus-article-article-menu gnus-article-mode-map ""
3285      '("Article"
3286        ["Scroll forwards" gnus-article-goto-next-page t]
3287        ["Scroll backwards" gnus-article-goto-prev-page t]
3288        ["Show summary" gnus-article-show-summary t]
3289        ["Fetch Message-ID at point" gnus-article-refer-article t]
3290        ["Mail to address at point" gnus-article-mail t]
3291        ["Send a bug report" gnus-bug t]))
3292
3293     (easy-menu-define
3294      gnus-article-treatment-menu gnus-article-mode-map ""
3295      ;; Fixme: this should use :active (and maybe :visible).
3296      '("Treatment"
3297        ["Hide headers" gnus-article-hide-headers t]
3298        ["Hide signature" gnus-article-hide-signature t]
3299        ["Hide citation" gnus-article-hide-citation t]
3300        ["Treat overstrike" gnus-article-treat-overstrike t]
3301        ["Remove carriage return" gnus-article-remove-cr t]
3302        ["Remove leading whitespace" gnus-article-remove-leading-whitespace t]
3303        ["Remove quoted-unreadable" gnus-article-de-quoted-unreadable t]
3304        ["Remove base64" gnus-article-de-base64-unreadable t]
3305        ["Treat html" gnus-article-wash-html t]
3306        ["Remove newlines from within URLs" gnus-article-unsplit-urls t]
3307        ["Decode HZ" gnus-article-decode-HZ t]))
3308
3309     ;; Note "Commands" menu is defined in gnus-sum.el for consistency
3310
3311     ;; Note "Post" menu is defined in gnus-sum.el for consistency
3312
3313     (gnus-run-hooks 'gnus-article-menu-hook)))
3314
3315 ;; Fixme: do something for the Emacs tool bar in Article mode a la
3316 ;; Summary.
3317
3318 (defun gnus-article-mode ()
3319   "Major mode for displaying an article.
3320
3321 All normal editing commands are switched off.
3322
3323 The following commands are available in addition to all summary mode
3324 commands:
3325 \\<gnus-article-mode-map>
3326 \\[gnus-article-next-page]\t Scroll the article one page forwards
3327 \\[gnus-article-prev-page]\t Scroll the article one page backwards
3328 \\[gnus-article-refer-article]\t Go to the article referred to by an article id near point
3329 \\[gnus-article-show-summary]\t Display the summary buffer
3330 \\[gnus-article-mail]\t Send a reply to the address near point
3331 \\[gnus-article-describe-briefly]\t Describe the current mode briefly
3332 \\[gnus-info-find-node]\t Go to the Gnus info node"
3333   (interactive)
3334   (gnus-simplify-mode-line)
3335   (setq mode-name "Article")
3336   (setq major-mode 'gnus-article-mode)
3337   (make-local-variable 'minor-mode-alist)
3338   (use-local-map gnus-article-mode-map)
3339   (when (gnus-visual-p 'article-menu 'menu)
3340     (gnus-article-make-menu-bar))
3341   (gnus-update-format-specifications nil 'article-mode)
3342   (set (make-local-variable 'page-delimiter) gnus-page-delimiter)
3343   (make-local-variable 'gnus-page-broken)
3344   (make-local-variable 'gnus-button-marker-list)
3345   (make-local-variable 'gnus-article-current-summary)
3346   (make-local-variable 'gnus-article-mime-handles)
3347   (make-local-variable 'gnus-article-decoded-p)
3348   (make-local-variable 'gnus-article-mime-handle-alist)
3349   (make-local-variable 'gnus-article-wash-types)
3350   (make-local-variable 'gnus-article-image-alist)
3351   (make-local-variable 'gnus-article-charset)
3352   (make-local-variable 'gnus-article-ignored-charsets)
3353   (gnus-set-default-directory)
3354   (buffer-disable-undo)
3355   (setq buffer-read-only t)
3356   (set-syntax-table gnus-article-mode-syntax-table)
3357   (mm-enable-multibyte)
3358   (gnus-run-hooks 'gnus-article-mode-hook))
3359
3360 (defun gnus-article-setup-buffer ()
3361   "Initialize the article buffer."
3362   (let* ((name (if gnus-single-article-buffer "*Article*"
3363                  (concat "*Article " gnus-newsgroup-name "*")))
3364          (original
3365           (progn (string-match "\\*Article" name)
3366                  (concat " *Original Article"
3367                          (substring name (match-end 0))))))
3368     (setq gnus-article-buffer name)
3369     (setq gnus-original-article-buffer original)
3370     (setq gnus-article-mime-handle-alist nil)
3371     ;; This might be a variable local to the summary buffer.
3372     (unless gnus-single-article-buffer
3373       (save-excursion
3374         (set-buffer gnus-summary-buffer)
3375         (setq gnus-article-buffer name)
3376         (setq gnus-original-article-buffer original)
3377         (gnus-set-global-variables)))
3378     (gnus-article-setup-highlight-words)
3379     ;; Init original article buffer.
3380     (save-excursion
3381       (set-buffer (gnus-get-buffer-create gnus-original-article-buffer))
3382       (mm-enable-multibyte)
3383       (setq major-mode 'gnus-original-article-mode)
3384       (make-local-variable 'gnus-original-article))
3385     (if (get-buffer name)
3386         (save-excursion
3387           (set-buffer name)
3388           (when gnus-article-mime-handles
3389             (mm-destroy-parts gnus-article-mime-handles)
3390             (setq gnus-article-mime-handles nil))
3391           ;; Set it to nil in article-buffer!
3392           (setq gnus-article-mime-handle-alist nil)
3393           (buffer-disable-undo)
3394           (setq buffer-read-only t)
3395           (unless (eq major-mode 'gnus-article-mode)
3396             (gnus-article-mode))
3397           (current-buffer))
3398       (save-excursion
3399         (set-buffer (gnus-get-buffer-create name))
3400         (gnus-article-mode)
3401         (make-local-variable 'gnus-summary-buffer)
3402         (gnus-summary-set-local-parameters gnus-newsgroup-name)
3403         (current-buffer)))))
3404
3405 ;; Set article window start at LINE, where LINE is the number of lines
3406 ;; from the head of the article.
3407 (defun gnus-article-set-window-start (&optional line)
3408   (set-window-start
3409    (gnus-get-buffer-window gnus-article-buffer t)
3410    (save-excursion
3411      (set-buffer gnus-article-buffer)
3412      (goto-char (point-min))
3413      (if (not line)
3414          (point-min)
3415        (gnus-message 6 "Moved to bookmark")
3416        (search-forward "\n\n" nil t)
3417        (forward-line line)
3418        (point)))))
3419
3420 (defun gnus-article-prepare (article &optional all-headers header)
3421   "Prepare ARTICLE in article mode buffer.
3422 ARTICLE should either be an article number or a Message-ID.
3423 If ARTICLE is an id, HEADER should be the article headers.
3424 If ALL-HEADERS is non-nil, no headers are hidden."
3425   (save-excursion
3426     ;; Make sure we start in a summary buffer.
3427     (unless (eq major-mode 'gnus-summary-mode)
3428       (set-buffer gnus-summary-buffer))
3429     (setq gnus-summary-buffer (current-buffer))
3430     (let* ((gnus-article (if header (mail-header-number header) article))
3431            (summary-buffer (current-buffer))
3432            (gnus-tmp-internal-hook gnus-article-internal-prepare-hook)
3433            (group gnus-newsgroup-name)
3434            result)
3435       (save-excursion
3436         (gnus-article-setup-buffer)
3437         (set-buffer gnus-article-buffer)
3438         ;; Deactivate active regions.
3439         (when (and (boundp 'transient-mark-mode)
3440                    transient-mark-mode)
3441           (setq mark-active nil))
3442         (if (not (setq result (let ((buffer-read-only nil))
3443                                 (gnus-request-article-this-buffer
3444                                  article group))))
3445             ;; There is no such article.
3446             (save-excursion
3447               (when (and (numberp article)
3448                          (not (memq article gnus-newsgroup-sparse)))
3449                 (setq gnus-article-current
3450                       (cons gnus-newsgroup-name article))
3451                 (set-buffer gnus-summary-buffer)
3452                 (setq gnus-current-article article)
3453                 (if (eq (gnus-article-mark article) gnus-undownloaded-mark)
3454                     (progn
3455                       (gnus-summary-set-agent-mark article)
3456                       (message "Message marked for downloading"))
3457                   (gnus-summary-mark-article article gnus-canceled-mark)
3458                   (unless (memq article gnus-newsgroup-sparse)
3459                     (gnus-error 1 "No such article (may have expired or been canceled)")))))
3460           (if (or (eq result 'pseudo)
3461                   (eq result 'nneething))
3462               (progn
3463                 (save-excursion
3464                   (set-buffer summary-buffer)
3465                   (push article gnus-newsgroup-history)
3466                   (setq gnus-last-article gnus-current-article
3467                         gnus-current-article 0
3468                         gnus-current-headers nil
3469                         gnus-article-current nil)
3470                   (if (eq result 'nneething)
3471                       (gnus-configure-windows 'summary)
3472                     (gnus-configure-windows 'article))
3473                   (gnus-set-global-variables))
3474                 (let ((gnus-article-mime-handle-alist-1
3475                        gnus-article-mime-handle-alist))
3476                   (gnus-set-mode-line 'article)))
3477             ;; The result from the `request' was an actual article -
3478             ;; or at least some text that is now displayed in the
3479             ;; article buffer.
3480             (when (and (numberp article)
3481                        (not (eq article gnus-current-article)))
3482               ;; Seems like a new article has been selected.
3483               ;; `gnus-current-article' must be an article number.
3484               (save-excursion
3485                 (set-buffer summary-buffer)
3486                 (push article gnus-newsgroup-history)
3487                 (setq gnus-last-article gnus-current-article
3488                       gnus-current-article article
3489                       gnus-current-headers
3490                       (gnus-summary-article-header gnus-current-article)
3491                       gnus-article-current
3492                       (cons gnus-newsgroup-name gnus-current-article))
3493                 (unless (vectorp gnus-current-headers)
3494                   (setq gnus-current-headers nil))
3495                 (gnus-summary-goto-subject gnus-current-article)
3496                 (when (gnus-summary-show-thread)
3497                   ;; If the summary buffer really was folded, the
3498                   ;; previous goto may not actually have gone to
3499                   ;; the right article, but the thread root instead.
3500                   ;; So we go again.
3501                   (gnus-summary-goto-subject gnus-current-article))
3502                 (gnus-run-hooks 'gnus-mark-article-hook)
3503                 (gnus-set-mode-line 'summary)
3504                 (when (gnus-visual-p 'article-highlight 'highlight)
3505                   (gnus-run-hooks 'gnus-visual-mark-article-hook))
3506                 ;; Set the global newsgroup variables here.
3507                 (gnus-set-global-variables)
3508                 (setq gnus-have-all-headers
3509                       (or all-headers gnus-show-all-headers))))
3510             (save-excursion
3511               (gnus-configure-windows 'article))
3512             (when (or (numberp article)
3513                       (stringp article))
3514               (gnus-article-prepare-display)
3515               ;; Do page break.
3516               (goto-char (point-min))
3517               (setq gnus-page-broken
3518                     (when gnus-break-pages
3519                       (gnus-narrow-to-page)
3520                       t)))
3521             (let ((gnus-article-mime-handle-alist-1
3522                    gnus-article-mime-handle-alist))
3523               (gnus-set-mode-line 'article))
3524             (article-goto-body)
3525             (set-window-point (get-buffer-window (current-buffer)) (point))
3526             (gnus-configure-windows 'article)
3527             t))))))
3528
3529 ;;;###autoload
3530 (defun gnus-article-prepare-display ()
3531   "Make the current buffer look like a nice article."
3532   ;; Hooks for getting information from the article.
3533   ;; This hook must be called before being narrowed.
3534   (let ((gnus-article-buffer (current-buffer))
3535         buffer-read-only)
3536     (unless (eq major-mode 'gnus-article-mode)
3537       (gnus-article-mode))
3538     (setq buffer-read-only nil
3539           gnus-article-wash-types nil
3540           gnus-article-image-alist nil)
3541     (gnus-run-hooks 'gnus-tmp-internal-hook)
3542     (when gnus-display-mime-function
3543       (funcall gnus-display-mime-function))
3544     (gnus-run-hooks 'gnus-article-prepare-hook)))
3545
3546 ;;;
3547 ;;; Gnus MIME viewing functions
3548 ;;;
3549
3550 (defvar gnus-mime-button-line-format "%{%([%p. %d%T]%)%}%e\n"
3551   "Format of the MIME buttons.
3552
3553 Valid specifiers include:
3554 %t  The MIME type
3555 %T  MIME type, along with additional info
3556 %n  The `name' parameter
3557 %d  The description, if any
3558 %l  The length of the encoded part
3559 %p  The part identifier number
3560 %e  Dots if the part isn't displayed
3561
3562 General format specifiers can also be used.  See
3563 (gnus)Formatting Variables.")
3564
3565 (defvar gnus-mime-button-line-format-alist
3566   '((?t gnus-tmp-type ?s)
3567     (?T gnus-tmp-type-long ?s)
3568     (?n gnus-tmp-name ?s)
3569     (?d gnus-tmp-description ?s)
3570     (?p gnus-tmp-id ?s)
3571     (?l gnus-tmp-length ?d)
3572     (?e gnus-tmp-dots ?s)))
3573
3574 (defvar gnus-mime-button-commands
3575   '((gnus-article-press-button "\r" "Toggle Display")
3576     (gnus-mime-view-part "v" "View Interactively...")
3577     (gnus-mime-view-part-as-type "t" "View As Type...")
3578     (gnus-mime-view-part-as-charset "C" "View As charset...")
3579     (gnus-mime-save-part "o" "Save...")
3580     (gnus-mime-save-part-and-strip "\C-o" "Save and Strip")
3581     (gnus-mime-copy-part "c" "View As Text, In Other Buffer")
3582     (gnus-mime-inline-part "i" "View As Text, In This Buffer")
3583     (gnus-mime-internalize-part "E" "View Internally")
3584     (gnus-mime-externalize-part "e" "View Externally")
3585     (gnus-mime-print-part "p" "Print")
3586     (gnus-mime-pipe-part "|" "Pipe To Command...")
3587     (gnus-mime-action-on-part "." "Take action on the part")))
3588
3589 (defun gnus-article-mime-part-status ()
3590   (if gnus-article-mime-handle-alist-1
3591       (if (eq 1 (length gnus-article-mime-handle-alist-1))
3592           " (1 part)"
3593         (format " (%d parts)" (length gnus-article-mime-handle-alist-1)))
3594     ""))
3595
3596 (defvar gnus-mime-button-map
3597   (let ((map (make-sparse-keymap)))
3598     (unless (>= (string-to-number emacs-version) 21)
3599       ;; XEmacs doesn't care.
3600       (set-keymap-parent map gnus-article-mode-map))
3601     (define-key map gnus-mouse-2 'gnus-article-push-button)
3602     (define-key map gnus-down-mouse-3 'gnus-mime-button-menu)
3603     (dolist (c gnus-mime-button-commands)
3604       (define-key map (cadr c) (car c)))
3605     map))
3606
3607 (defun gnus-mime-button-menu (event)
3608   "Construct a context-sensitive menu of MIME commands."
3609   (interactive "e")
3610   (save-window-excursion
3611     (let ((pos (event-start event)))
3612       (select-window (posn-window pos))
3613       (goto-char (posn-point pos))
3614       (gnus-article-check-buffer)
3615       (let ((response (x-popup-menu
3616                        t `("MIME Part"
3617                            ("" ,@(mapcar (lambda (c)
3618                                            (cons (caddr c) (car c)))
3619                                          gnus-mime-button-commands))))))
3620         (if response
3621             (call-interactively response))))))
3622
3623 (defun gnus-mime-view-all-parts (&optional handles)
3624   "View all the MIME parts."
3625   (interactive)
3626   (save-current-buffer
3627     (set-buffer gnus-article-buffer)
3628     (let ((handles (or handles gnus-article-mime-handles))
3629           (mail-parse-charset gnus-newsgroup-charset)
3630           (mail-parse-ignored-charsets
3631            (with-current-buffer gnus-summary-buffer
3632              gnus-newsgroup-ignored-charsets)))
3633       (when handles
3634         (mm-remove-parts handles)
3635         (goto-char (point-min))
3636         (or (search-forward "\n\n") (goto-char (point-max)))
3637         (let (buffer-read-only)
3638           (delete-region (point) (point-max))
3639           (mm-display-parts handles))))))
3640
3641 (defun gnus-mime-save-part-and-strip ()
3642   "Save the MIME part under point then replace it with an external body."
3643   (interactive)
3644   (gnus-article-check-buffer)
3645   (let* ((data (get-text-property (point) 'gnus-data))
3646          file param
3647          (handles gnus-article-mime-handles))
3648     (if (mm-multiple-handles gnus-article-mime-handles)
3649         (error "This function is not implemented"))
3650     (setq file (and data (mm-save-part data)))
3651     (when file
3652       (with-current-buffer (mm-handle-buffer data)
3653         (erase-buffer)
3654         (insert "Content-Type: " (mm-handle-media-type data))
3655         (mml-insert-parameter-string (cdr (mm-handle-type data))
3656                                      '(charset))
3657         (insert "\n")
3658         (insert "Content-ID: " (message-make-message-id) "\n")
3659         (insert "Content-Transfer-Encoding: binary\n")
3660         (insert "\n"))
3661       (setcdr data
3662               (cdr (mm-make-handle nil
3663                                    `("message/external-body"
3664                                      (access-type . "LOCAL-FILE")
3665                                      (name . ,file)))))
3666       (set-buffer gnus-summary-buffer)
3667       (gnus-article-edit-article
3668        `(lambda ()
3669            (erase-buffer)
3670            (let ((mail-parse-charset (or gnus-article-charset
3671                                          ',gnus-newsgroup-charset))
3672                  (mail-parse-ignored-charsets
3673                   (or gnus-article-ignored-charsets
3674                       ',gnus-newsgroup-ignored-charsets))
3675                  (mbl mml-buffer-list))
3676              (setq mml-buffer-list nil)
3677              (insert-buffer gnus-original-article-buffer)
3678              (mime-to-mml ',handles)
3679              (setq gnus-article-mime-handles nil)
3680              (let ((mbl1 mml-buffer-list))
3681                (setq mml-buffer-list mbl)
3682                (set (make-local-variable 'mml-buffer-list) mbl1))
3683              ;; LOCAL argument of add-hook differs between GNU Emacs
3684              ;; and XEmacs. make-local-hook makes sure they are local.
3685              (make-local-hook 'kill-buffer-hook)
3686              (add-hook 'kill-buffer-hook 'mml-destroy-buffers t t)))
3687        `(lambda (no-highlight)
3688           (let ((mail-parse-charset (or gnus-article-charset
3689                                         ',gnus-newsgroup-charset))
3690                 (message-options message-options)
3691                 (message-options-set-recipient)
3692                 (mail-parse-ignored-charsets
3693                  (or gnus-article-ignored-charsets
3694                      ',gnus-newsgroup-ignored-charsets)))
3695            (mml-to-mime)
3696            (mml-destroy-buffers)
3697            (remove-hook 'kill-buffer-hook
3698                         'mml-destroy-buffers t)
3699            (kill-local-variable 'mml-buffer-list))
3700           (gnus-summary-edit-article-done
3701            ,(or (mail-header-references gnus-current-headers) "")
3702            ,(gnus-group-read-only-p)
3703            ,gnus-summary-buffer no-highlight))))))
3704
3705 (defun gnus-mime-save-part ()
3706   "Save the MIME part under point."
3707   (interactive)
3708   (gnus-article-check-buffer)
3709   (let ((data (get-text-property (point) 'gnus-data)))
3710     (when data
3711       (mm-save-part data))))
3712
3713 (defun gnus-mime-pipe-part ()
3714   "Pipe the MIME part under point to a process."
3715   (interactive)
3716   (gnus-article-check-buffer)
3717   (let ((data (get-text-property (point) 'gnus-data)))
3718     (when data
3719       (mm-pipe-part data))))
3720
3721 (defun gnus-mime-view-part ()
3722   "Interactively choose a viewing method for the MIME part under point."
3723   (interactive)
3724   (gnus-article-check-buffer)
3725   (let ((data (get-text-property (point) 'gnus-data)))
3726     (when data
3727       (setq gnus-article-mime-handles
3728             (mm-merge-handles
3729              gnus-article-mime-handles (setq data (copy-sequence data))))
3730       (mm-interactively-view-part data))))
3731
3732 (defun gnus-mime-view-part-as-type-internal ()
3733   (gnus-article-check-buffer)
3734   (let* ((name (mail-content-type-get
3735                 (mm-handle-type (get-text-property (point) 'gnus-data))
3736                 'name))
3737          (def-type (and name (mm-default-file-encoding name))))
3738     (and def-type (cons def-type 0))))
3739
3740 (defun gnus-mime-view-part-as-type (&optional mime-type)
3741   "Choose a MIME media type, and view the part as such."
3742   (interactive)
3743   (unless mime-type
3744     (setq mime-type (completing-read
3745                      "View as MIME type: "
3746                      (mapcar #'list (mailcap-mime-types))
3747                      nil nil
3748                      (gnus-mime-view-part-as-type-internal))))
3749   (gnus-article-check-buffer)
3750   (let ((handle (get-text-property (point) 'gnus-data)))
3751     (when handle
3752       (setq handle
3753             (mm-make-handle (mm-handle-buffer handle)
3754                             (cons mime-type (cdr (mm-handle-type handle)))
3755                             (mm-handle-encoding handle)
3756                             (mm-handle-undisplayer handle)
3757                             (mm-handle-disposition handle)
3758                             (mm-handle-description handle)
3759                             nil
3760                             (mm-handle-id handle)))
3761       (setq gnus-article-mime-handles
3762             (mm-merge-handles gnus-article-mime-handles handle))
3763       (gnus-mm-display-part handle))))
3764
3765 (defun gnus-mime-copy-part (&optional handle)
3766   "Put the MIME part under point into a new buffer."
3767   (interactive)
3768   (gnus-article-check-buffer)
3769   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
3770          (contents (and handle (mm-get-part handle)))
3771          (base (and handle
3772                     (file-name-nondirectory
3773                      (or
3774                       (mail-content-type-get (mm-handle-type handle) 'name)
3775                       (mail-content-type-get (mm-handle-type handle)
3776                                              'filename)
3777                       "*decoded*"))))
3778          (buffer (and base (generate-new-buffer base))))
3779     (when contents
3780       (switch-to-buffer buffer)
3781       (insert contents)
3782       ;; We do it this way to make `normal-mode' set the appropriate mode.
3783       (unwind-protect
3784           (progn
3785             (setq buffer-file-name (expand-file-name base))
3786             (normal-mode))
3787         (setq buffer-file-name nil))
3788       (goto-char (point-min)))))
3789
3790 (defun gnus-mime-print-part (&optional handle filename)
3791   "Print the MIME part under point."
3792   (interactive (list nil (ps-print-preprint current-prefix-arg)))
3793   (gnus-article-check-buffer)
3794   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
3795          (contents (and handle (mm-get-part handle)))
3796          (file (make-temp-name (expand-file-name "mm." mm-tmp-directory)))
3797          (printer (mailcap-mime-info (mm-handle-type handle) "print")))
3798     (when contents
3799         (if printer
3800             (unwind-protect
3801                 (progn
3802                   (with-temp-file file
3803                     (insert contents))
3804                   (call-process shell-file-name nil
3805                                 (generate-new-buffer " *mm*")
3806                                 nil
3807                                 shell-command-switch
3808                                 (mm-mailcap-command
3809                                  printer file (mm-handle-type handle))))
3810               (delete-file file))
3811           (with-temp-buffer
3812             (insert contents)
3813             (gnus-print-buffer))
3814           (ps-despool filename)))))
3815
3816 (defun gnus-mime-inline-part (&optional handle arg)
3817   "Insert the MIME part under point into the current buffer."
3818   (interactive (list nil current-prefix-arg))
3819   (gnus-article-check-buffer)
3820   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
3821          contents charset
3822          (b (point))
3823          buffer-read-only)
3824     (when handle
3825       (if (and (not arg) (mm-handle-undisplayer handle))
3826           (mm-remove-part handle)
3827         (setq contents (mm-get-part handle))
3828         (cond
3829          ((not arg)
3830           (setq charset (or (mail-content-type-get
3831                              (mm-handle-type handle) 'charset)
3832                             gnus-newsgroup-charset)))
3833          ((numberp arg)
3834           (if (mm-handle-undisplayer handle)
3835               (mm-remove-part handle))
3836           (setq charset
3837                 (or (cdr (assq arg
3838                                gnus-summary-show-article-charset-alist))
3839                     (mm-read-coding-system "Charset: ")))))
3840         (forward-line 2)
3841         (mm-insert-inline handle
3842                           (if (and charset
3843                                    (setq charset (mm-charset-to-coding-system
3844                                                   charset))
3845                                    (not (eq charset 'ascii)))
3846                               (mm-decode-coding-string contents charset)
3847                             contents))
3848         (goto-char b)))))
3849
3850 (defun gnus-mime-view-part-as-charset (&optional handle arg)
3851   "Insert the MIME part under point into the current buffer using the
3852 specified charset."
3853   (interactive (list nil current-prefix-arg))
3854   (gnus-article-check-buffer)
3855   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
3856          contents charset
3857          (b (point))
3858          buffer-read-only)
3859     (when handle
3860       (if (mm-handle-undisplayer handle)
3861           (mm-remove-part handle))
3862       (let ((gnus-newsgroup-charset
3863              (or (cdr (assq arg
3864                             gnus-summary-show-article-charset-alist))
3865                  (mm-read-coding-system "Charset: ")))
3866           (gnus-newsgroup-ignored-charsets 'gnus-all))
3867         (gnus-article-press-button)))))
3868
3869 (defun gnus-mime-externalize-part (&optional handle)
3870   "View the MIME part under point with an external viewer."
3871   (interactive)
3872   (gnus-article-check-buffer)
3873   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
3874          (mm-user-display-methods nil)
3875          (mm-inlined-types nil)
3876          (mail-parse-charset gnus-newsgroup-charset)
3877          (mail-parse-ignored-charsets
3878           (save-excursion (set-buffer gnus-summary-buffer)
3879                           gnus-newsgroup-ignored-charsets)))
3880     (when handle
3881       (if (mm-handle-undisplayer handle)
3882           (mm-remove-part handle)
3883         (mm-display-part handle)))))
3884
3885 (defun gnus-mime-internalize-part (&optional handle)
3886   "View the MIME part under point with an internal viewer.
3887 If no internal viewer is available, use an external viewer."
3888   (interactive)
3889   (gnus-article-check-buffer)
3890   (let* ((handle (or handle (get-text-property (point) 'gnus-data)))
3891          (mm-inlined-types '(".*"))
3892          (mm-inline-large-images t)
3893          (mail-parse-charset gnus-newsgroup-charset)
3894          (mail-parse-ignored-charsets
3895           (save-excursion (set-buffer gnus-summary-buffer)
3896                           gnus-newsgroup-ignored-charsets)))
3897     (when handle
3898       (if (mm-handle-undisplayer handle)
3899           (mm-remove-part handle)
3900         (mm-display-part handle)))))
3901
3902 (defun gnus-mime-action-on-part (&optional action)
3903   "Do something with the MIME attachment at \(point\)."
3904   (interactive
3905    (list (completing-read "Action: " gnus-mime-action-alist)))
3906   (gnus-article-check-buffer)
3907   (let ((action-pair (assoc action gnus-mime-action-alist)))
3908     (if action-pair
3909         (funcall (cdr action-pair)))))
3910
3911 (defun gnus-article-part-wrapper (n function)
3912   (save-current-buffer
3913     (set-buffer gnus-article-buffer)
3914     (when (> n (length gnus-article-mime-handle-alist))
3915       (error "No such part"))
3916     (gnus-article-goto-part n)
3917     (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
3918       (funcall function handle))))
3919
3920 (defun gnus-article-pipe-part (n)
3921   "Pipe MIME part N, which is the numerical prefix."
3922   (interactive "p")
3923   (gnus-article-part-wrapper n 'mm-pipe-part))
3924
3925 (defun gnus-article-save-part (n)
3926   "Save MIME part N, which is the numerical prefix."
3927   (interactive "p")
3928   (gnus-article-part-wrapper n 'mm-save-part))
3929
3930 (defun gnus-article-interactively-view-part (n)
3931   "View MIME part N interactively, which is the numerical prefix."
3932   (interactive "p")
3933   (gnus-article-part-wrapper n 'mm-interactively-view-part))
3934
3935 (defun gnus-article-copy-part (n)
3936   "Copy MIME part N, which is the numerical prefix."
3937   (interactive "p")
3938   (gnus-article-part-wrapper n 'gnus-mime-copy-part))
3939
3940 (defun gnus-article-view-part-as-charset (n)
3941   "Copy MIME part N, which is the numerical prefix."
3942   (interactive "p")
3943   (gnus-article-part-wrapper n 'gnus-mime-view-part-as-charset))
3944
3945 (defun gnus-article-externalize-part (n)
3946   "View MIME part N externally, which is the numerical prefix."
3947   (interactive "p")
3948   (gnus-article-part-wrapper n 'gnus-mime-externalize-part))
3949
3950 (defun gnus-article-inline-part (n)
3951   "Inline MIME part N, which is the numerical prefix."
3952   (interactive "p")
3953   (gnus-article-part-wrapper n 'gnus-mime-inline-part))
3954
3955 (defun gnus-article-mime-match-handle-first (condition)
3956   (if condition
3957       (let ((alist gnus-article-mime-handle-alist) ihandle n)
3958         (while (setq ihandle (pop alist))
3959           (if (and (cond
3960                     ((functionp condition)
3961                      (funcall condition (cdr ihandle)))
3962                     ((eq condition 'undisplayed)
3963                      (not (or (mm-handle-undisplayer (cdr ihandle))
3964                               (equal (mm-handle-media-type (cdr ihandle))
3965                                      "multipart/alternative"))))
3966                     ((eq condition 'undisplayed-alternative)
3967                      (not (mm-handle-undisplayer (cdr ihandle))))
3968                     (t t))
3969                    (gnus-article-goto-part (car ihandle))
3970                    (or (not n) (< (car ihandle) n)))
3971               (setq n (car ihandle))))
3972         (or n 1))
3973     1))
3974
3975 (defun gnus-article-view-part (&optional n)
3976   "View MIME part N, which is the numerical prefix."
3977   (interactive "P")
3978   (save-current-buffer
3979     (set-buffer gnus-article-buffer)
3980     (or (numberp n) (setq n (gnus-article-mime-match-handle-first
3981                              gnus-article-mime-match-handle-function)))
3982     (when (> n (length gnus-article-mime-handle-alist))
3983       (error "No such part"))
3984     (let ((handle (cdr (assq n gnus-article-mime-handle-alist))))
3985       (when (gnus-article-goto-part n)
3986         (if (equal (car handle) "multipart/alternative")
3987             (gnus-article-press-button)
3988           (when (eq (gnus-mm-display-part handle) 'internal)
3989             (gnus-set-window-start)))))))
3990
3991 (defsubst gnus-article-mime-total-parts ()
3992   (if (bufferp (car gnus-article-mime-handles))
3993       1 ;; single part
3994     (1- (length gnus-article-mime-handles))))
3995
3996 (defun gnus-mm-display-part (handle)
3997   "Display HANDLE and fix MIME button."
3998   (let ((id (get-text-property (point) 'gnus-part))
3999         (point (point))
4000         buffer-read-only)
4001     (forward-line 1)
4002     (prog1
4003         (let ((window (selected-window))
4004               (mail-parse-charset gnus-newsgroup-charset)
4005               (mail-parse-ignored-charsets
4006                (if (gnus-buffer-live-p gnus-summary-buffer)
4007                    (save-excursion
4008                      (set-buffer gnus-summary-buffer)
4009                      gnus-newsgroup-ignored-charsets)
4010                  nil)))
4011           (save-excursion
4012             (unwind-protect
4013                 (let ((win (gnus-get-buffer-window (current-buffer) t))
4014                       (beg (point)))
4015                   (when win
4016                     (select-window win))
4017                   (goto-char point)
4018                   (forward-line)
4019                   (if (mm-handle-displayed-p handle)
4020                       ;; This will remove the part.
4021                       (mm-display-part handle)
4022                     (save-restriction
4023                       (narrow-to-region (point)
4024                                         (if (eobp) (point) (1+ (point))))
4025                       (mm-display-part handle)
4026                       ;; We narrow to the part itself and
4027                       ;; then call the treatment functions.
4028                       (goto-char (point-min))
4029                       (forward-line 1)
4030                       (narrow-to-region (point) (point-max))
4031                       (gnus-treat-article
4032                        nil id
4033                        (gnus-article-mime-total-parts)
4034                        (mm-handle-media-type handle)))))
4035               (if (window-live-p window)
4036                   (select-window window)))))
4037       (goto-char point)
4038       (delete-region (gnus-point-at-bol) (progn (forward-line 1) (point)))
4039       (gnus-insert-mime-button
4040        handle id (list (mm-handle-displayed-p handle)))
4041       (goto-char point))))
4042
4043 (defun gnus-article-goto-part (n)
4044   "Go to MIME part N."
4045   (let ((point (text-property-any (point-min) (point-max) 'gnus-part n)))
4046     (when point
4047       (goto-char point))))
4048
4049 (defun gnus-insert-mime-button (handle gnus-tmp-id &optional displayed)
4050   (let ((gnus-tmp-name
4051          (or (mail-content-type-get (mm-handle-type handle) 'name)
4052              (mail-content-type-get (mm-handle-disposition handle) 'filename)
4053              (mail-content-type-get (mm-handle-type handle) 'url)
4054              ""))
4055         (gnus-tmp-type (mm-handle-media-type handle))
4056         (gnus-tmp-description
4057          (mail-decode-encoded-word-string (or (mm-handle-description handle)
4058                                               "")))
4059         (gnus-tmp-dots
4060          (if (if displayed (car displayed)
4061                (mm-handle-displayed-p handle))
4062              "" "..."))
4063         (gnus-tmp-length (with-current-buffer (mm-handle-buffer handle)
4064                            (buffer-size)))
4065         gnus-tmp-type-long b e)
4066     (when (string-match ".*/" gnus-tmp-name)
4067       (setq gnus-tmp-name (replace-match "" t t gnus-tmp-name)))
4068     (setq gnus-tmp-type-long (concat gnus-tmp-type
4069                                      (and (not (equal gnus-tmp-name ""))
4070                                           (concat "; " gnus-tmp-name))))
4071     (unless (equal gnus-tmp-description "")
4072       (setq gnus-tmp-type-long (concat " --- " gnus-tmp-type-long)))
4073     (unless (bolp)
4074       (insert "\n"))
4075     (setq b (point))
4076     (gnus-eval-format
4077      gnus-mime-button-line-format gnus-mime-button-line-format-alist
4078      `(,@(gnus-local-map-property gnus-mime-button-map)
4079          gnus-callback gnus-mm-display-part
4080          gnus-part ,gnus-tmp-id
4081          article-type annotation
4082          gnus-data ,handle))
4083     (setq e (point))
4084     (widget-convert-button
4085      'link b e
4086      :mime-handle handle
4087      :action 'gnus-widget-press-button
4088      :button-keymap gnus-mime-button-map
4089      :help-echo
4090      (lambda (widget/window &optional overlay pos)
4091        ;; Needed to properly clear the message due to a bug in
4092        ;; wid-edit (XEmacs only).
4093        (if (boundp 'help-echo-owns-message)
4094            (setq help-echo-owns-message t))
4095        (format
4096         "%S: %s the MIME part; %S: more options"
4097         (aref gnus-mouse-2 0)
4098         ;; XEmacs will get a single widget arg; Emacs 21 will get
4099         ;; window, overlay, position.
4100         (if (mm-handle-displayed-p
4101              (if overlay
4102                  (with-current-buffer (gnus-overlay-buffer overlay)
4103                    (widget-get (widget-at (gnus-overlay-start overlay))
4104                                :mime-handle))
4105                (widget-get widget/window :mime-handle)))
4106             "hide" "show")
4107         (aref gnus-down-mouse-3 0))))))
4108
4109 (defun gnus-widget-press-button (elems el)
4110   (goto-char (widget-get elems :from))
4111   (gnus-article-press-button))
4112
4113 (defvar gnus-displaying-mime nil)
4114
4115 (defun gnus-display-mime (&optional ihandles)
4116   "Display the MIME parts."
4117   (save-excursion
4118     (save-selected-window
4119       (let ((window (get-buffer-window gnus-article-buffer))
4120             (point (point)))
4121         (when window
4122           (select-window window)
4123           ;; We have to do this since selecting the window
4124           ;; may change the point.  So we set the window point.
4125           (set-window-point window point)))
4126       (let* ((handles (or ihandles (mm-dissect-buffer 
4127                                     gnus-article-no-strict-mime)
4128                           (mm-uu-dissect)))
4129              buffer-read-only handle name type b e display)
4130         (when (and (not ihandles)
4131                    (not gnus-displaying-mime))
4132           ;; Top-level call; we clean up.
4133           (when gnus-article-mime-handles
4134             (mm-destroy-parts gnus-article-mime-handles)
4135             (setq gnus-article-mime-handle-alist nil));; A trick.
4136           (setq gnus-article-mime-handles handles)
4137           ;; We allow users to glean info from the handles.
4138           (when gnus-article-mime-part-function
4139             (gnus-mime-part-function handles)))
4140         (if (and handles
4141                  (or (not (stringp (car handles)))
4142                      (cdr handles)))
4143             (progn
4144               (when (and (not ihandles)
4145                          (not gnus-displaying-mime))
4146                 ;; Clean up for mime parts.
4147                 (article-goto-body)
4148                 (delete-region (point) (point-max)))
4149               (let ((gnus-displaying-mime t))
4150                 (gnus-mime-display-part handles)))
4151           (save-restriction
4152             (article-goto-body)
4153             (narrow-to-region (point) (point-max))
4154             (gnus-treat-article nil 1 1)
4155             (widen)))
4156         (unless ihandles
4157           ;; Highlight the headers.
4158           (save-excursion
4159             (save-restriction
4160               (article-goto-body)
4161               (narrow-to-region (point-min) (point))
4162               (gnus-treat-article 'head))))))))
4163
4164 (defvar gnus-mime-display-multipart-as-mixed nil)
4165 (defvar gnus-mime-display-multipart-alternative-as-mixed nil)
4166 (defvar gnus-mime-display-multipart-related-as-mixed nil)
4167
4168 (defun gnus-mime-display-part (handle)
4169   (cond
4170    ;; Single part.
4171    ((not (stringp (car handle)))
4172     (gnus-mime-display-single handle))
4173    ;; User-defined multipart
4174    ((cdr (assoc (car handle) gnus-mime-multipart-functions))
4175     (funcall (cdr (assoc (car handle) gnus-mime-multipart-functions))
4176              handle))
4177    ;; multipart/alternative
4178    ((and (equal (car handle) "multipart/alternative")
4179          (not (or gnus-mime-display-multipart-as-mixed
4180                   gnus-mime-display-multipart-alternative-as-mixed)))
4181     (let ((id (1+ (length gnus-article-mime-handle-alist))))
4182       (push (cons id handle) gnus-article-mime-handle-alist)
4183       (gnus-mime-display-alternative (cdr handle) nil nil id)))
4184    ;; multipart/related
4185    ((and (equal (car handle) "multipart/related")
4186          (not (or gnus-mime-display-multipart-as-mixed
4187                   gnus-mime-display-multipart-related-as-mixed)))
4188     ;;;!!!We should find the start part, but we just default
4189     ;;;!!!to the first part.
4190     ;;(gnus-mime-display-part (cadr handle))
4191     ;;;!!! Most multipart/related is an HTML message plus images.
4192     ;;;!!! Unfortunately we are unable to let W3 display those
4193     ;;;!!! included images, so we just display it as a mixed multipart.
4194     ;;(gnus-mime-display-mixed (cdr handle))
4195     ;;;!!! No, w3 can display everything just fine.
4196     (gnus-mime-display-part (cadr handle)))
4197    ((equal (car handle) "multipart/signed")
4198     (gnus-add-wash-type 'signed)
4199     (gnus-mime-display-security handle))
4200    ((equal (car handle) "multipart/encrypted")
4201     (gnus-add-wash-type 'encrypted)
4202     (gnus-mime-display-security handle))
4203    ;; Other multiparts are handled like multipart/mixed.
4204    (t
4205     (gnus-mime-display-mixed (cdr handle)))))
4206
4207 (defun gnus-mime-part-function (handles)
4208   (if (stringp (car handles))
4209       (mapcar 'gnus-mime-part-function (cdr handles))
4210     (funcall gnus-article-mime-part-function handles)))
4211
4212 (defun gnus-mime-display-mixed (handles)
4213   (mapcar 'gnus-mime-display-part handles))
4214
4215 (defun gnus-mime-display-single (handle)
4216   (let ((type (mm-handle-media-type handle))
4217         (ignored gnus-ignored-mime-types)
4218         (not-attachment t)
4219         (move nil)
4220         display text)
4221     (catch 'ignored
4222       (progn
4223         (while ignored
4224           (when (string-match (pop ignored) type)
4225             (throw 'ignored nil)))
4226         (if (and (setq not-attachment
4227                        (and (not (mm-inline-override-p handle))
4228                             (or (not (mm-handle-disposition handle))
4229                                 (equal (car (mm-handle-disposition handle))
4230                                        "inline")
4231                                 (mm-attachment-override-p handle))))
4232                  (mm-automatic-display-p handle)
4233                  (or (and
4234                       (mm-inlinable-p handle)
4235                       (mm-inlined-p handle))
4236                      (mm-automatic-external-display-p type)))
4237             (setq display t)
4238           (when (equal (mm-handle-media-supertype handle) "text")
4239             (setq text t)))
4240         (let ((id (1+ (length gnus-article-mime-handle-alist)))
4241               beg)
4242           (push (cons id handle) gnus-article-mime-handle-alist)
4243           (when (or (not display)
4244                     (not (gnus-unbuttonized-mime-type-p type)))
4245             ;(gnus-article-insert-newline)
4246             (gnus-insert-mime-button
4247              handle id (list (or display (and not-attachment text))))
4248             (gnus-article-insert-newline)
4249             ;(gnus-article-insert-newline)
4250             ;; Remember modify the number of forward lines.
4251             (setq move t))
4252           (setq beg (point))
4253           (cond
4254            (display
4255             (when move
4256               (forward-line -1)
4257               (setq beg (point)))
4258             (let ((mail-parse-charset gnus-newsgroup-charset)
4259                   (mail-parse-ignored-charsets
4260                    (save-excursion (condition-case ()
4261                                        (set-buffer gnus-summary-buffer)
4262                                      (error))
4263                                    gnus-newsgroup-ignored-charsets)))
4264               (mm-display-part handle t))
4265             (goto-char (point-max)))
4266            ((and text not-attachment)
4267             (when move
4268               (forward-line -1)
4269               (setq beg (point)))
4270             (gnus-article-insert-newline)
4271             (mm-insert-inline handle (mm-get-part handle))
4272             (goto-char (point-max))))
4273           ;; Do highlighting.
4274           (save-excursion
4275             (save-restriction
4276               (narrow-to-region beg (point))
4277               (gnus-treat-article
4278                nil id
4279                (gnus-article-mime-total-parts)
4280                (mm-handle-media-type handle)))))))))
4281
4282 (defun gnus-unbuttonized-mime-type-p (type)
4283   "Say whether TYPE is to be unbuttonized."
4284   (unless gnus-inhibit-mime-unbuttonizing
4285     (when (catch 'found
4286             (let ((types gnus-unbuttonized-mime-types))
4287               (while types
4288                 (when (string-match (pop types) type)
4289                   (throw 'found t)))))
4290       (not (catch 'found
4291              (let ((types gnus-buttonized-mime-types))
4292                (while types
4293                  (when (string-match (pop types) type)
4294                    (throw 'found t)))))))))
4295
4296 (defun gnus-article-insert-newline ()
4297   "Insert a newline, but mark it as undeletable."
4298   (gnus-put-text-property
4299    (point) (progn (insert "\n") (point)) 'gnus-undeletable t))
4300
4301 (defun gnus-mime-display-alternative (handles &optional preferred ibegend id)
4302   (let* ((preferred (or preferred (mm-preferred-alternative handles)))
4303          (ihandles handles)
4304          (point (point))
4305          handle buffer-read-only from props begend not-pref)
4306     (save-window-excursion
4307       (save-restriction
4308         (when ibegend
4309           (narrow-to-region (car ibegend)
4310                             (or (cdr ibegend)
4311                                 (progn
4312                                   (goto-char (car ibegend))
4313                                   (forward-line 2)
4314                                   (point))))
4315           (delete-region (point-min) (point-max))
4316           (mm-remove-parts handles))
4317         (setq begend (list (point-marker)))
4318         ;; Do the toggle.
4319         (unless (setq not-pref (cadr (member preferred ihandles)))
4320           (setq not-pref (car ihandles)))
4321         (when (or ibegend
4322                   (not preferred)
4323                   (not (gnus-unbuttonized-mime-type-p
4324                         "multipart/alternative")))
4325           (gnus-add-text-properties
4326            (setq from (point))
4327            (progn
4328              (insert (format "%d.  " id))
4329              (point))
4330            `(gnus-callback
4331              (lambda (handles)
4332                (unless ,(not ibegend)
4333                  (setq gnus-article-mime-handle-alist
4334                        ',gnus-article-mime-handle-alist))
4335                (gnus-mime-display-alternative
4336                 ',ihandles ',not-pref ',begend ,id))
4337              ,@(gnus-local-map-property gnus-mime-button-map)
4338              ,gnus-mouse-face-prop ,gnus-article-mouse-face
4339              face ,gnus-article-button-face
4340              gnus-part ,id
4341              gnus-data ,handle))
4342           (widget-convert-button 'link from (point)
4343                                  :action 'gnus-widget-press-button
4344                                  :button-keymap gnus-widget-button-keymap)
4345           ;; Do the handles
4346           (while (setq handle (pop handles))
4347             (gnus-add-text-properties
4348              (setq from (point))
4349              (progn
4350                (insert (format "(%c) %-18s"
4351                                (if (equal handle preferred) ?* ? )
4352                                (mm-handle-media-type handle)))
4353                (point))
4354              `(gnus-callback
4355                (lambda (handles)
4356                  (unless ,(not ibegend)
4357                    (setq gnus-article-mime-handle-alist
4358                          ',gnus-article-mime-handle-alist))
4359                  (gnus-mime-display-alternative
4360                   ',ihandles ',handle ',begend ,id))
4361                ,@(gnus-local-map-property gnus-mime-button-map)
4362                ,gnus-mouse-face-prop ,gnus-article-mouse-face
4363                face ,gnus-article-button-face
4364                gnus-part ,id
4365                gnus-data ,handle))
4366             (widget-convert-button 'link from (point)
4367                                    :action 'gnus-widget-press-button
4368                                    :button-keymap gnus-widget-button-keymap)
4369             (insert "  "))
4370           (insert "\n\n"))
4371         (when preferred
4372           (if (stringp (car preferred))
4373               (gnus-display-mime preferred)
4374             (let ((mail-parse-charset gnus-newsgroup-charset)
4375                   (mail-parse-ignored-charsets
4376                    (save-excursion (set-buffer gnus-summary-buffer)
4377                                    gnus-newsgroup-ignored-charsets)))
4378               (mm-display-part preferred)
4379               ;; Do highlighting.
4380               (save-excursion
4381                 (save-restriction
4382                   (narrow-to-region (car begend) (point-max))
4383                   (gnus-treat-article
4384                    nil (length gnus-article-mime-handle-alist)
4385                    (gnus-article-mime-total-parts)
4386                    (mm-handle-media-type handle))))))
4387           (goto-char (point-max))
4388           (setcdr begend (point-marker)))))
4389     (when ibegend
4390       (goto-char point))))
4391
4392 (defconst gnus-article-wash-status-strings
4393   (let ((alist '((cite "c" "Possible hidden citation text"
4394                        " " "All citation text visible")
4395                  (headers "h" "Hidden headers"
4396                           " " "All headers visible.")
4397                  (pgp "p" "Encrypted or signed message status hidden"
4398                       " " "No hidden encryption nor digital signature status")
4399                  (signature "s" "Signature has been hidden"
4400                             " " "Signature is visible")
4401                  (overstrike "o" "Overstrike (^H) characters applied"
4402                              " " "No overstrike characters applied")
4403                  (emphasis "e" "/*_Emphasis_*/ characters applied"
4404                            " " "No /*_emphasis_*/ characters applied")))
4405         result)
4406     (dolist (entry alist result)
4407       (let ((key (nth 0 entry))
4408             (on (copy-sequence (nth 1 entry)))
4409             (on-help (nth 2 entry))
4410             (off (copy-sequence (nth 3 entry)))
4411             (off-help (nth 4 entry)))
4412         (put-text-property 0 1 'help-echo on-help on)
4413         (put-text-property 0 1 'help-echo off-help off)
4414         (push (list key on off) result))))
4415   "Alist of strings describing wash status in the mode line.
4416 Each entry has the form (KEY ON OF), where the KEY is a symbol
4417 representing the particular washing function, ON is the string to use
4418 in the article mode line when the washing function is active, and OFF
4419 is the string to use when it is inactive.")
4420
4421 (defun gnus-article-wash-status-entry (key value)
4422   (let ((entry (assoc key gnus-article-wash-status-strings)))
4423     (if value (nth 1 entry) (nth 2 entry))))
4424
4425 (defun gnus-article-wash-status ()
4426   "Return a string which display status of article washing."
4427   (save-excursion
4428     (set-buffer gnus-article-buffer)
4429     (let ((cite (memq 'cite gnus-article-wash-types))
4430           (headers (memq 'headers gnus-article-wash-types))
4431           (boring (memq 'boring-headers gnus-article-wash-types))
4432           (pgp (memq 'pgp gnus-article-wash-types))
4433           (pem (memq 'pem gnus-article-wash-types))
4434           (signed (memq 'signed gnus-article-wash-types))
4435           (encrypted (memq 'encrypted gnus-article-wash-types))
4436           (signature (memq 'signature gnus-article-wash-types))
4437           (overstrike (memq 'overstrike gnus-article-wash-types))
4438           (emphasis (memq 'emphasis gnus-article-wash-types)))
4439       (concat
4440        (gnus-article-wash-status-entry 'cite cite)
4441        (gnus-article-wash-status-entry 'headers (or headers boring))
4442        (gnus-article-wash-status-entry 'pgp (or pgp pem signed encrypted))
4443        (gnus-article-wash-status-entry 'signature signature)
4444        (gnus-article-wash-status-entry 'overstrike overstrike)
4445        (gnus-article-wash-status-entry 'emphasis emphasis)))))
4446
4447 (defun gnus-add-wash-type (type)
4448   "Add a washing of TYPE to the current status."
4449   (add-to-list 'gnus-article-wash-types type))
4450
4451 (defun gnus-delete-wash-type (type)
4452   "Add a washing of TYPE to the current status."
4453   (setq gnus-article-wash-types (delq type gnus-article-wash-types)))
4454
4455 (defun gnus-add-image (category image)
4456   "Add IMAGE of CATEGORY to the list of displayed images."
4457   (let ((entry (assq category gnus-article-image-alist)))
4458     (unless entry
4459       (setq entry (list category))
4460       (push entry gnus-article-image-alist))
4461     (nconc entry (list image))))
4462
4463 (defun gnus-delete-images (category)
4464   "Delete all images in CATEGORY."
4465   (let ((entry (assq category gnus-article-image-alist)))
4466     (dolist (image (cdr entry))
4467       (gnus-remove-image image))
4468     (setq gnus-article-image-alist (delq entry gnus-article-image-alist))
4469     (gnus-delete-wash-type category)))
4470
4471 (defalias 'gnus-article-hide-headers-if-wanted 'gnus-article-maybe-hide-headers)
4472
4473 (defun gnus-article-maybe-hide-headers ()
4474   "Hide unwanted headers if `gnus-have-all-headers' is nil.
4475 Provided for backwards compatibility."
4476   (when (and (or (not (gnus-buffer-live-p gnus-summary-buffer))
4477                  (not (save-excursion (set-buffer gnus-summary-buffer)
4478                                       gnus-have-all-headers)))
4479              (not gnus-inhibit-hiding))
4480     (gnus-article-hide-headers)))
4481
4482 ;;; Article savers.
4483
4484 (defun gnus-output-to-file (file-name)
4485   "Append the current article to a file named FILE-NAME."
4486   (let ((artbuf (current-buffer)))
4487     (with-temp-buffer
4488       (insert-buffer-substring artbuf)
4489       ;; Append newline at end of the buffer as separator, and then
4490       ;; save it to file.
4491       (goto-char (point-max))
4492       (insert "\n")
4493       (let ((file-name-coding-system nnmail-pathname-coding-system))
4494         (mm-append-to-file (point-min) (point-max) file-name))
4495       t)))
4496
4497 (defun gnus-narrow-to-page (&optional arg)
4498   "Narrow the article buffer to a page.
4499 If given a numerical ARG, move forward ARG pages."
4500   (interactive "P")
4501   (setq arg (if arg (prefix-numeric-value arg) 0))
4502   (save-excursion
4503     (set-buffer gnus-article-buffer)
4504     (goto-char (point-min))
4505     (widen)
4506     ;; Remove any old next/prev buttons.
4507     (when (gnus-visual-p 'page-marker)
4508       (let ((buffer-read-only nil))
4509         (gnus-remove-text-with-property 'gnus-prev)
4510         (gnus-remove-text-with-property 'gnus-next)))
4511     (when
4512         (cond ((< arg 0)
4513                (re-search-backward page-delimiter nil 'move (1+ (abs arg))))
4514               ((> arg 0)
4515                (re-search-forward page-delimiter nil 'move arg)))
4516       (goto-char (match-end 0)))
4517     (narrow-to-region
4518      (point)
4519      (if (re-search-forward page-delimiter nil 'move)
4520          (match-beginning 0)
4521        (point)))
4522     (when (and (gnus-visual-p 'page-marker)
4523                (not (= (point-min) 1)))
4524       (save-excursion
4525         (goto-char (point-min))
4526         (gnus-insert-prev-page-button)))
4527     (when (and (gnus-visual-p 'page-marker)
4528                (< (+ (point-max) 2) (buffer-size)))
4529       (save-excursion
4530         (goto-char (point-max))
4531         (gnus-insert-next-page-button)))))
4532
4533 ;; Article mode commands
4534
4535 (defun gnus-article-goto-next-page ()
4536   "Show the next page of the article."
4537   (interactive)
4538   (when (gnus-article-next-page)
4539     (goto-char (point-min))
4540     (gnus-article-read-summary-keys nil (gnus-character-to-event ?n))))
4541
4542 (defun gnus-article-goto-prev-page ()
4543   "Show the next page of the article."
4544   (interactive)
4545   (if (bobp) (gnus-article-read-summary-keys nil (gnus-character-to-event ?p))
4546     (gnus-article-prev-page nil)))
4547
4548 (defun gnus-article-next-page (&optional lines)
4549   "Show the next page of the current article.
4550 If end of article, return non-nil.  Otherwise return nil.
4551 Argument LINES specifies lines to be scrolled up."
4552   (interactive "p")
4553   (move-to-window-line -1)
4554   (if (save-excursion
4555         (end-of-line)
4556         (and (pos-visible-in-window-p)  ;Not continuation line.
4557              (eobp)))
4558       ;; Nothing in this page.
4559       (if (or (not gnus-page-broken)
4560               (save-excursion
4561                 (save-restriction
4562                   (widen) (forward-line 1) (eobp)))) ;Real end-of-buffer?
4563           t                             ;Nothing more.
4564         (gnus-narrow-to-page 1)         ;Go to next page.
4565         nil)
4566     ;; More in this page.
4567     (let ((scroll-in-place nil))
4568       (condition-case ()
4569           (scroll-up lines)
4570         (end-of-buffer
4571          ;; Long lines may cause an end-of-buffer error.
4572          (goto-char (point-max)))))
4573     (move-to-window-line 0)
4574     nil))
4575
4576 (defun gnus-article-prev-page (&optional lines)
4577   "Show previous page of current article.
4578 Argument LINES specifies lines to be scrolled down."
4579   (interactive "p")
4580   (move-to-window-line 0)
4581   (if (and gnus-page-broken
4582            (bobp)
4583            (not (save-restriction (widen) (bobp)))) ;Real beginning-of-buffer?
4584       (progn
4585         (gnus-narrow-to-page -1)        ;Go to previous page.
4586         (goto-char (point-max))
4587         (recenter -1))
4588     (let ((scroll-in-place nil))
4589       (prog1
4590           (condition-case ()
4591               (scroll-down lines)
4592             (beginning-of-buffer
4593              (goto-char (point-min))))
4594         (move-to-window-line 0)))))
4595
4596 (defun gnus-article-refer-article ()
4597   "Read article specified by message-id around point."
4598   (interactive)
4599   (let ((point (point)))
4600     (search-forward ">" nil t)          ;Move point to end of "<....>".
4601     (if (re-search-backward "\\(<[^<> \t\n]+>\\)" nil t)
4602         (let ((message-id (match-string 1)))
4603           (goto-char point)
4604           (set-buffer gnus-summary-buffer)
4605           (gnus-summary-refer-article message-id))
4606       (goto-char (point))
4607       (error "No references around point"))))
4608
4609 (defun gnus-article-show-summary ()
4610   "Reconfigure windows to show summary buffer."
4611   (interactive)
4612   (if (not (gnus-buffer-live-p gnus-summary-buffer))
4613       (error "There is no summary buffer for this article buffer")
4614     (gnus-article-set-globals)
4615     (gnus-configure-windows 'article)
4616     (gnus-summary-goto-subject gnus-current-article)
4617     (gnus-summary-position-point)))
4618
4619 (defun gnus-article-describe-briefly ()
4620   "Describe article mode commands briefly."
4621   (interactive)
4622   (gnus-message 6 (substitute-command-keys "\\<gnus-article-mode-map>\\[gnus-article-goto-next-page]:Next page   \\[gnus-article-goto-prev-page]:Prev page  \\[gnus-article-show-summary]:Show summary  \\[gnus-info-find-node]:Run Info  \\[gnus-article-describe-briefly]:This help")))
4623
4624 (defun gnus-article-summary-command ()
4625   "Execute the last keystroke in the summary buffer."
4626   (interactive)
4627   (let ((obuf (current-buffer))
4628         (owin (current-window-configuration))
4629         func)
4630     (switch-to-buffer gnus-article-current-summary 'norecord)
4631     (setq func (lookup-key (current-local-map) (this-command-keys)))
4632     (call-interactively func)
4633     (set-buffer obuf)
4634     (set-window-configuration owin)
4635     (set-window-point (get-buffer-window (current-buffer)) (point))))
4636
4637 (defun gnus-article-summary-command-nosave ()
4638   "Execute the last keystroke in the summary buffer."
4639   (interactive)
4640   (let (func)
4641     (pop-to-buffer gnus-article-current-summary 'norecord)
4642     (setq func (lookup-key (current-local-map) (this-command-keys)))
4643     (call-interactively func)))
4644
4645 (defun gnus-article-check-buffer ()
4646   "Beep if not in an article buffer."
4647   (unless (equal major-mode 'gnus-article-mode)
4648     (error "Command invoked outside of a Gnus article buffer")))
4649
4650 (defun gnus-article-read-summary-keys (&optional arg key not-restore-window)
4651   "Read a summary buffer key sequence and execute it from the article buffer."
4652   (interactive "P")
4653   (gnus-article-check-buffer)
4654   (let ((nosaves
4655          '("q" "Q"  "c" "r" "\C-c\C-f" "m"  "a" "f"
4656            "Zc" "ZC" "ZE" "ZQ" "ZZ" "Zn" "ZR" "ZG" "ZN" "ZP"
4657            "=" "^" "\M-^" "|"))
4658         (nosave-but-article
4659          '("A\r"))
4660         (nosave-in-article
4661          '("\C-d"))
4662         (up-to-top
4663          '("n" "Gn" "p" "Gp"))
4664         keys new-sum-point)
4665     (save-excursion
4666       (set-buffer gnus-article-current-summary)
4667       (let (gnus-pick-mode)
4668         (push (or key last-command-event) unread-command-events)
4669         (setq keys (if (featurep 'xemacs)
4670                        (events-to-keys (read-key-sequence nil))
4671                      (read-key-sequence nil)))))
4672
4673     (message "")
4674
4675     (if (or (member keys nosaves)
4676             (member keys nosave-but-article)
4677             (member keys nosave-in-article))
4678         (let (func)
4679           (save-window-excursion
4680             (pop-to-buffer gnus-article-current-summary 'norecord)
4681             ;; We disable the pick minor mode commands.
4682             (let (gnus-pick-mode)
4683               (setq func (lookup-key (current-local-map) keys))))
4684           (if (or (not func)
4685                   (numberp func))
4686               (ding)
4687             (unless (member keys nosave-in-article)
4688               (set-buffer gnus-article-current-summary))
4689             (call-interactively func)
4690             (setq new-sum-point (point)))
4691           (when (member keys nosave-but-article)
4692             (pop-to-buffer gnus-article-buffer 'norecord)))
4693       ;; These commands should restore window configuration.
4694       (let ((obuf (current-buffer))
4695             (owin (current-window-configuration))
4696             (opoint (point))
4697             (summary gnus-article-current-summary)
4698             func in-buffer selected)
4699         (if not-restore-window
4700             (pop-to-buffer summary 'norecord)
4701           (switch-to-buffer summary 'norecord))
4702         (setq in-buffer (current-buffer))
4703         ;; We disable the pick minor mode commands.
4704         (if (and (setq func (let (gnus-pick-mode)
4705                               (lookup-key (current-local-map) keys)))
4706                  (functionp func))
4707             (progn
4708               (call-interactively func)
4709               (setq new-sum-point (point))
4710               (when (eq in-buffer (current-buffer))
4711                 (setq selected (gnus-summary-select-article))
4712                 (set-buffer obuf)
4713                 (unless not-restore-window
4714                   (set-window-configuration owin))
4715                 (when (eq selected 'old)
4716                   (article-goto-body)
4717                   (set-window-start (get-buffer-window (current-buffer))
4718                                     1)
4719                   (set-window-point (get-buffer-window (current-buffer))
4720                                     (point)))
4721                 (let ((win (get-buffer-window gnus-article-current-summary)))
4722                   (when win
4723                     (set-window-point win new-sum-point))))    )
4724           (switch-to-buffer gnus-article-buffer)
4725           (ding))))))
4726
4727 (defun gnus-article-describe-key (key)
4728   "Display documentation of the function invoked by KEY.  KEY is a string."
4729   (interactive "kDescribe key: ")
4730   (gnus-article-check-buffer)
4731   (if (eq (key-binding key) 'gnus-article-read-summary-keys)
4732       (save-excursion
4733         (set-buffer gnus-article-current-summary)
4734         (let (gnus-pick-mode)
4735           (if (featurep 'xemacs)
4736               (progn
4737                 (push (elt key 0) unread-command-events)
4738                 (setq key (events-to-keys
4739                            (read-key-sequence "Describe key: "))))
4740             (setq unread-command-events
4741                   (mapcar
4742                    (lambda (x) (if (>= x 128) (list 'meta (- x 128)) x))
4743                    (string-to-list key)))
4744             (setq key (read-key-sequence "Describe key: "))))
4745         (describe-key key))
4746     (describe-key key)))
4747
4748 (defun gnus-article-describe-key-briefly (key &optional insert)
4749   "Display documentation of the function invoked by KEY.  KEY is a string."
4750   (interactive "kDescribe key: \nP")
4751   (gnus-article-check-buffer)
4752   (if (eq (key-binding key) 'gnus-article-read-summary-keys)
4753       (save-excursion
4754         (set-buffer gnus-article-current-summary)
4755         (let (gnus-pick-mode)
4756           (if (featurep 'xemacs)
4757               (progn
4758                 (push (elt key 0) unread-command-events)
4759                 (setq key (events-to-keys
4760                            (read-key-sequence "Describe key: "))))
4761             (setq unread-command-events
4762                   (mapcar
4763                    (lambda (x) (if (>= x 128) (list 'meta (- x 128)) x))
4764                    (string-to-list key)))
4765             (setq key (read-key-sequence "Describe key: "))))
4766         (describe-key-briefly key insert))
4767     (describe-key-briefly key insert)))
4768
4769 (defun gnus-article-reply-with-original (&optional wide)
4770   "Start composing a reply mail to the current message.
4771 The text in the region will be yanked.  If the region isn't active,
4772 the entire article will be yanked."
4773   (interactive "P")
4774   (let ((article (cdr gnus-article-current)) cont)
4775     (if (not (mark t))
4776         (gnus-summary-reply (list (list article)) wide)
4777       (setq cont (buffer-substring (point) (mark t)))
4778       ;; Deactivate active regions.
4779       (when (and (boundp 'transient-mark-mode)
4780                  transient-mark-mode)
4781         (setq mark-active nil))
4782       (gnus-summary-reply
4783        (list (list article cont)) wide))))
4784
4785 (defun gnus-article-followup-with-original ()
4786   "Compose a followup to the current article.
4787 The text in the region will be yanked.  If the region isn't active,
4788 the entire article will be yanked."
4789   (interactive)
4790   (let ((article (cdr gnus-article-current))
4791         cont)
4792     (if (not (mark t))
4793         (gnus-summary-followup (list (list article)))
4794       (setq cont (buffer-substring (point) (mark t)))
4795       ;; Deactivate active regions.
4796       (when (and (boundp 'transient-mark-mode)
4797                  transient-mark-mode)
4798         (setq mark-active nil))
4799       (gnus-summary-followup
4800        (list (list article cont))))))
4801
4802 (defun gnus-article-hide (&optional arg force)
4803   "Hide all the gruft in the current article.
4804 This means that PGP stuff, signatures, cited text and (some)
4805 headers will be hidden.
4806 If given a prefix, show the hidden text instead."
4807   (interactive (append (gnus-article-hidden-arg) (list 'force)))
4808   (gnus-article-hide-headers arg)
4809   (gnus-article-hide-list-identifiers arg)
4810   (gnus-article-hide-pgp arg)
4811   (gnus-article-hide-citation-maybe arg force)
4812   (gnus-article-hide-signature arg))
4813
4814 (defun gnus-article-maybe-highlight ()
4815   "Do some article highlighting if article highlighting is requested."
4816   (when (gnus-visual-p 'article-highlight 'highlight)
4817     (gnus-article-highlight-some)))
4818
4819 (defun gnus-check-group-server ()
4820   ;; Make sure the connection to the server is alive.
4821   (unless (gnus-server-opened
4822            (gnus-find-method-for-group gnus-newsgroup-name))
4823     (gnus-check-server (gnus-find-method-for-group gnus-newsgroup-name))
4824     (gnus-request-group gnus-newsgroup-name t)))
4825
4826 (eval-when-compile
4827   (autoload 'nneething-get-file-name "nneething"))
4828
4829 (defun gnus-request-article-this-buffer (article group)
4830   "Get an article and insert it into this buffer."
4831   (let (do-update-line sparse-header)
4832     (prog1
4833         (save-excursion
4834           (erase-buffer)
4835           (gnus-kill-all-overlays)
4836           (setq group (or group gnus-newsgroup-name))
4837
4838           ;; Using `gnus-request-article' directly will insert the article into
4839           ;; `nntp-server-buffer' - so we'll save some time by not having to
4840           ;; copy it from the server buffer into the article buffer.
4841
4842           ;; We only request an article by message-id when we do not have the
4843           ;; headers for it, so we'll have to get those.
4844           (when (stringp article)
4845             (gnus-read-header article))
4846
4847           ;; If the article number is negative, that means that this article
4848           ;; doesn't belong in this newsgroup (possibly), so we find its
4849           ;; message-id and request it by id instead of number.
4850           (when (and (numberp article)
4851                      gnus-summary-buffer
4852                      (get-buffer gnus-summary-buffer)
4853                      (gnus-buffer-exists-p gnus-summary-buffer))
4854             (save-excursion
4855               (set-buffer gnus-summary-buffer)
4856               (let ((header (gnus-summary-article-header article)))
4857                 (when (< article 0)
4858                   (cond
4859                    ((memq article gnus-newsgroup-sparse)
4860                     ;; This is a sparse gap article.
4861                     (setq do-update-line article)
4862                     (setq article (mail-header-id header))
4863                     (setq sparse-header (gnus-read-header article))
4864                     (setq gnus-newsgroup-sparse
4865                           (delq article gnus-newsgroup-sparse)))
4866                    ((vectorp header)
4867                     ;; It's a real article.
4868                     (setq article (mail-header-id header)))
4869                    (t
4870                     ;; It is an extracted pseudo-article.
4871                     (setq article 'pseudo)
4872                     (gnus-request-pseudo-article header))))
4873
4874                 (let ((method (gnus-find-method-for-group
4875                                gnus-newsgroup-name)))
4876                   (when (and (eq (car method) 'nneething)
4877                              (vectorp header))
4878                     (let ((dir (nneething-get-file-name
4879                                 (mail-header-id header))))
4880                       (when (and (stringp dir)
4881                                  (file-directory-p dir))
4882                         (setq article 'nneething)
4883                         (gnus-group-enter-directory dir))))))))
4884
4885           (cond
4886            ;; Refuse to select canceled articles.
4887            ((and (numberp article)
4888                  gnus-summary-buffer
4889                  (get-buffer gnus-summary-buffer)
4890                  (gnus-buffer-exists-p gnus-summary-buffer)
4891                  (eq (cdr (save-excursion
4892                             (set-buffer gnus-summary-buffer)
4893                             (assq article gnus-newsgroup-reads)))
4894                      gnus-canceled-mark))
4895             nil)
4896            ;; We first check `gnus-original-article-buffer'.
4897            ((and (get-buffer gnus-original-article-buffer)
4898                  (numberp article)
4899                  (save-excursion
4900                    (set-buffer gnus-original-article-buffer)
4901                    (and (equal (car gnus-original-article) group)
4902                         (eq (cdr gnus-original-article) article))))
4903             (insert-buffer-substring gnus-original-article-buffer)
4904             'article)
4905            ;; Check the backlog.
4906            ((and gnus-keep-backlog
4907                  (gnus-backlog-request-article group article (current-buffer)))
4908             'article)
4909            ;; Check asynchronous pre-fetch.
4910            ((gnus-async-request-fetched-article group article (current-buffer))
4911             (gnus-async-prefetch-next group article gnus-summary-buffer)
4912             (when (and (numberp article) gnus-keep-backlog)
4913               (gnus-backlog-enter-article group article (current-buffer)))
4914             'article)
4915            ;; Check the cache.
4916            ((and gnus-use-cache
4917                  (numberp article)
4918                  (gnus-cache-request-article article group))
4919             'article)
4920            ;; Check the agent cache.
4921            ((and gnus-agent gnus-agent-cache gnus-plugged
4922                  (numberp article)
4923                  (gnus-agent-request-article article group))
4924             'article)
4925            ;; Get the article and put into the article buffer.
4926            ((or (stringp article)
4927                 (numberp article))
4928             (let ((gnus-override-method gnus-override-method)
4929                   (methods (and (stringp article)
4930                                 gnus-refer-article-method))
4931                   result
4932                   (buffer-read-only nil))
4933               (if (or (not (listp methods))
4934                       (and (symbolp (car methods))
4935                            (assq (car methods) nnoo-definition-alist)))
4936                   (setq methods (list methods)))
4937               (when (and (null gnus-override-method)
4938                          methods)
4939                 (setq gnus-override-method (pop methods)))
4940               (while (not result)
4941                 (when (eq gnus-override-method 'current)
4942                   (setq gnus-override-method
4943                         (with-current-buffer gnus-summary-buffer
4944                           gnus-current-select-method)))
4945                 (erase-buffer)
4946                 (gnus-kill-all-overlays)
4947                 (let ((gnus-newsgroup-name group))
4948                   (gnus-check-group-server))
4949                 (when (gnus-request-article article group (current-buffer))
4950                   (when (numberp article)
4951                     (gnus-async-prefetch-next group article
4952                                               gnus-summary-buffer)
4953                     (when gnus-keep-backlog
4954                       (gnus-backlog-enter-article
4955                        group article (current-buffer))))
4956                   (setq result 'article))
4957                 (if (not result)
4958                     (if methods
4959                         (setq gnus-override-method (pop methods))
4960                       (setq result 'done))))
4961               (and (eq result 'article) 'article)))
4962            ;; It was a pseudo.
4963            (t article)))
4964
4965       ;; Associate this article with the current summary buffer.
4966       (setq gnus-article-current-summary gnus-summary-buffer)
4967
4968       ;; Take the article from the original article buffer
4969       ;; and place it in the buffer it's supposed to be in.
4970       (when (and (get-buffer gnus-article-buffer)
4971                  (equal (buffer-name (current-buffer))
4972                         (buffer-name (get-buffer gnus-article-buffer))))
4973         (save-excursion
4974           (if (get-buffer gnus-original-article-buffer)
4975               (set-buffer gnus-original-article-buffer)
4976             (set-buffer (gnus-get-buffer-create gnus-original-article-buffer))
4977             (buffer-disable-undo)
4978             (setq major-mode 'gnus-original-article-mode)
4979             (setq buffer-read-only t))
4980           (let (buffer-read-only)
4981             (erase-buffer)
4982             (insert-buffer-substring gnus-article-buffer))
4983           (setq gnus-original-article (cons group article)))
4984
4985         ;; Decode charsets.
4986         (run-hooks 'gnus-article-decode-hook)
4987         ;; Mark article as decoded or not.
4988         (setq gnus-article-decoded-p gnus-article-decode-hook))
4989
4990       ;; Update sparse articles.
4991       (when (and do-update-line
4992                  (or (numberp article)
4993                      (stringp article)))
4994         (let ((buf (current-buffer)))
4995           (set-buffer gnus-summary-buffer)
4996           (gnus-summary-update-article do-update-line sparse-header)
4997           (gnus-summary-goto-subject do-update-line nil t)
4998           (set-window-point (gnus-get-buffer-window (current-buffer) t)
4999                             (point))
5000           (set-buffer buf))))))
5001
5002 ;;;
5003 ;;; Article editing
5004 ;;;
5005
5006 (defcustom gnus-article-edit-mode-hook nil
5007   "Hook run in article edit mode buffers."
5008   :group 'gnus-article-various
5009   :type 'hook)
5010
5011 (defvar gnus-article-edit-done-function nil)
5012
5013 (defvar gnus-article-edit-mode-map nil)
5014
5015 ;; Should we be using derived.el for this?
5016 (unless gnus-article-edit-mode-map
5017   (setq gnus-article-edit-mode-map (make-sparse-keymap))
5018   (set-keymap-parent gnus-article-edit-mode-map text-mode-map)
5019
5020   (gnus-define-keys gnus-article-edit-mode-map
5021     "\C-c\C-c" gnus-article-edit-done
5022     "\C-c\C-k" gnus-article-edit-exit)
5023
5024   (gnus-define-keys (gnus-article-edit-wash-map
5025                      "\C-c\C-w" gnus-article-edit-mode-map)
5026     "f" gnus-article-edit-full-stops))
5027
5028 (define-derived-mode gnus-article-edit-mode text-mode "Article Edit"
5029   "Major mode for editing articles.
5030 This is an extended text-mode.
5031
5032 \\{gnus-article-edit-mode-map}"
5033   (make-local-variable 'gnus-article-edit-done-function)
5034   (make-local-variable 'gnus-prev-winconf)
5035   (set (make-local-variable 'font-lock-defaults)
5036        '(message-font-lock-keywords t))
5037   (setq buffer-read-only nil)
5038   (buffer-enable-undo)
5039   (widen))
5040
5041 (defun gnus-article-edit (&optional force)
5042   "Edit the current article.
5043 This will have permanent effect only in mail groups.
5044 If FORCE is non-nil, allow editing of articles even in read-only
5045 groups."
5046   (interactive "P")
5047   (when (and (not force)
5048              (gnus-group-read-only-p))
5049     (error "The current newsgroup does not support article editing"))
5050   (gnus-article-date-original)
5051   (gnus-article-edit-article
5052    'ignore
5053    `(lambda (no-highlight)
5054       'ignore
5055       (gnus-summary-edit-article-done
5056        ,(or (mail-header-references gnus-current-headers) "")
5057        ,(gnus-group-read-only-p) ,gnus-summary-buffer no-highlight))))
5058
5059 (defun gnus-article-edit-article (start-func exit-func)
5060   "Start editing the contents of the current article buffer."
5061   (let ((winconf (current-window-configuration)))
5062     (set-buffer gnus-article-buffer)
5063     (gnus-article-edit-mode)
5064     (funcall start-func)
5065     (set-buffer-modified-p nil)
5066     (gnus-configure-windows 'edit-article)
5067     (setq gnus-article-edit-done-function exit-func)
5068     (setq gnus-prev-winconf winconf)
5069     (gnus-message 6 "C-c C-c to end edits")))
5070
5071 (defun gnus-article-edit-done (&optional arg)
5072   "Update the article edits and exit."
5073   (interactive "P")
5074   (let ((func gnus-article-edit-done-function)
5075         (buf (current-buffer))
5076         (start (window-start)))
5077     ;; We remove all text props from the article buffer.
5078     (let ((content
5079            (buffer-substring-no-properties (point-min) (point-max)))
5080           (p (point)))
5081       (erase-buffer)
5082       (insert content)
5083       (let ((winconf gnus-prev-winconf))
5084         (gnus-article-mode)
5085         (set-window-configuration winconf)
5086         ;; Tippy-toe some to make sure that point remains where it was.
5087         (save-current-buffer
5088           (set-buffer buf)
5089           (set-window-start (get-buffer-window (current-buffer)) start)
5090           (goto-char p))))
5091     (save-excursion
5092       (set-buffer buf)
5093       (let ((buffer-read-only nil))
5094         (funcall func arg))
5095       ;; The cache and backlog have to be flushed somewhat.
5096       (when gnus-keep-backlog
5097         (gnus-backlog-remove-article
5098          (car gnus-article-current) (cdr gnus-article-current)))
5099       ;; Flush original article as well.
5100       (save-excursion
5101         (when (get-buffer gnus-original-article-buffer)
5102           (set-buffer gnus-original-article-buffer)
5103           (setq gnus-original-article nil)))
5104       (when gnus-use-cache
5105         (gnus-cache-update-article
5106          (car gnus-article-current) (cdr gnus-article-current))))
5107     (set-buffer buf)
5108     (set-window-start (get-buffer-window buf) start)
5109     (set-window-point (get-buffer-window buf) (point))))
5110
5111 (defun gnus-article-edit-exit ()
5112   "Exit the article editing without updating."
5113   (interactive)
5114   (when (or (not (buffer-modified-p))
5115             (yes-or-no-p "Article modified; kill anyway? "))
5116     (let ((curbuf (current-buffer))
5117           (p (point))
5118           (window-start (window-start)))
5119       (erase-buffer)
5120       (if (gnus-buffer-live-p gnus-original-article-buffer)
5121           (insert-buffer gnus-original-article-buffer))
5122       (let ((winconf gnus-prev-winconf))
5123         (gnus-article-mode)
5124         (set-window-configuration winconf)
5125         ;; Tippy-toe some to make sure that point remains where it was.
5126         (save-current-buffer
5127           (set-buffer curbuf)
5128           (set-window-start (get-buffer-window (current-buffer)) window-start)
5129           (goto-char p))))))
5130
5131 (defun gnus-article-edit-full-stops ()
5132   "Interactively repair spacing at end of sentences."
5133   (interactive)
5134   (save-excursion
5135     (goto-char (point-min))
5136     (search-forward-regexp "^$" nil t)
5137     (let ((case-fold-search nil))
5138       (query-replace-regexp "\\([.!?][])}]* \\)\\([[({A-Z]\\)" "\\1 \\2"))))
5139
5140 ;;;
5141 ;;; Article highlights
5142 ;;;
5143
5144 ;; Written by Per Abrahamsen <abraham@iesd.auc.dk>.
5145
5146 ;;; Internal Variables:
5147
5148 (defcustom gnus-button-url-regexp "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\|info\\):\\)\\(//[-a-zA-Z0-9_.]+:[0-9]*\\)?\\([-a-zA-Z0-9_=!?#$@~`%&*+|\\/:;.,]\\|\\w\\)+\\([-a-zA-Z0-9_=#$@~`%&*+|\\/]\\|\\w\\)\\)"
5149   "Regular expression that matches URLs."
5150   :group 'gnus-article-buttons
5151   :type 'regexp)
5152
5153 (defcustom gnus-button-alist
5154   `(("<\\(url:[>\n\t ]*?\\)?\\(nntp\\|news\\):[>\n\t ]*\\([^>\n\t ]*@[^>\n\t ]*\\)>"
5155      0 t gnus-button-handle-news 3)
5156     ("\\b\\(nntp\\|news\\):\\([^>\n\t ]*@[^>)!;:,\n\t ]*\\)" 0 t
5157      gnus-button-handle-news 2)
5158     ("\\(\\b<\\(url:[>\n\t ]*\\)?news:[>\n\t ]*\\(//\\)?\\([^>\n\t ]*\\)>\\)"
5159      1 t
5160      gnus-button-fetch-group 4)
5161     ("\\bnews:\\(//\\)?\\([^'\">\n\t ]+\\)" 0 t gnus-button-fetch-group 2)
5162     ("\\bin\\( +article\\| +message\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)" 2
5163      t gnus-button-message-id 3)
5164     ("\\(<URL: *\\)mailto: *\\([^> \n\t]+\\)>" 0 t gnus-url-mailto 2)
5165     ("mailto:\\([-a-zA-Z.@_+0-9%=?]+\\)" 0 t gnus-url-mailto 1)
5166     ("\\bmailto:\\([^ \n\t]+\\)" 0 t gnus-url-mailto 1)
5167     ;; This is info
5168     ("\\binfo:\\(//\\)?\\([^'\">\n\t ]+\\)" 0 t
5169      gnus-button-handle-info 2)
5170     ;; This is how URLs _should_ be embedded in text...
5171     ("<URL: *\\([^<>]*\\)>" 1 t gnus-button-embedded-url 1)
5172     ;; Raw URLs.
5173     (,gnus-button-url-regexp 0 t browse-url 0))
5174   "*Alist of regexps matching buttons in article bodies.
5175
5176 Each entry has the form (REGEXP BUTTON FORM CALLBACK PAR...), where
5177 REGEXP: is the string matching text around the button,
5178 BUTTON: is the number of the regexp grouping actually matching the button,
5179 FORM: is a lisp expression which must eval to true for the button to
5180 be added,
5181 CALLBACK: is the function to call when the user push this button, and each
5182 PAR: is a number of a regexp grouping whose text will be passed to CALLBACK.
5183
5184 CALLBACK can also be a variable, in that case the value of that
5185 variable it the real callback function."
5186   :group 'gnus-article-buttons
5187   :type '(repeat (list regexp
5188                        (integer :tag "Button")
5189                        (sexp :tag "Form")
5190                        (function :tag "Callback")
5191                        (repeat :tag "Par"
5192                                :inline t
5193                                (integer :tag "Regexp group")))))
5194
5195 (defcustom gnus-header-button-alist
5196   `(("^\\(References\\|Message-I[Dd]\\):" "<[^<>]+>"
5197      0 t gnus-button-message-id 0)
5198     ("^\\(From\\|Reply-To\\):" ": *\\(.+\\)$" 1 t gnus-button-reply 1)
5199     ("^\\(Cc\\|To\\):" "[^ \t\n<>,()\"]+@[^ \t\n<>,()\"]+"
5200      0 t gnus-button-mailto 0)
5201     ("^X-[Uu][Rr][Ll]:" ,gnus-button-url-regexp 0 t browse-url 0)
5202     ("^Subject:" ,gnus-button-url-regexp 0 t browse-url 0)
5203     ("^[^:]+:" ,gnus-button-url-regexp 0 t browse-url 0)
5204     ("^[^:]+:" "\\bmailto:\\([-a-zA-Z.@_+0-9%=?]+\\)" 0 t gnus-url-mailto 1)
5205     ("^[^:]+:" "\\(<\\(url: \\)?news:\\([^>\n ]*\\)>\\)" 1 t
5206      gnus-button-message-id 3))
5207   "*Alist of headers and regexps to match buttons in article heads.
5208
5209 This alist is very similar to `gnus-button-alist', except that each
5210 alist has an additional HEADER element first in each entry:
5211
5212 \(HEADER REGEXP BUTTON FORM CALLBACK PAR)
5213
5214 HEADER is a regexp to match a header.  For a fuller explanation, see
5215 `gnus-button-alist'."
5216   :group 'gnus-article-buttons
5217   :group 'gnus-article-headers
5218   :type '(repeat (list (regexp :tag "Header")
5219                        regexp
5220                        (integer :tag "Button")
5221                        (sexp :tag "Form")
5222                        (function :tag "Callback")
5223                        (repeat :tag "Par"
5224                                :inline t
5225                                (integer :tag "Regexp group")))))
5226
5227 (defvar gnus-button-regexp nil)
5228 (defvar gnus-button-marker-list nil)
5229 ;; Regexp matching any of the regexps from `gnus-button-alist'.
5230
5231 (defvar gnus-button-last nil)
5232 ;; The value of `gnus-button-alist' when `gnus-button-regexp' was build.
5233
5234 ;;; Commands:
5235
5236 (defun gnus-article-push-button (event)
5237   "Check text under the mouse pointer for a callback function.
5238 If the text under the mouse pointer has a `gnus-callback' property,
5239 call it with the value of the `gnus-data' text property."
5240   (interactive "e")
5241   (set-buffer (window-buffer (posn-window (event-start event))))
5242   (let* ((pos (posn-point (event-start event)))
5243          (data (get-text-property pos 'gnus-data))
5244          (fun (get-text-property pos 'gnus-callback)))
5245     (goto-char pos)
5246     (when fun
5247       (funcall fun data))))
5248
5249 (defun gnus-article-press-button ()
5250   "Check text at point for a callback function.
5251 If the text at point has a `gnus-callback' property,
5252 call it with the value of the `gnus-data' text property."
5253   (interactive)
5254   (let* ((data (get-text-property (point) 'gnus-data))
5255          (fun (get-text-property (point) 'gnus-callback)))
5256     (when fun
5257       (funcall fun data))))
5258
5259 (defun gnus-article-highlight (&optional force)
5260   "Highlight current article.
5261 This function calls `gnus-article-highlight-headers',
5262 `gnus-article-highlight-citation',
5263 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
5264 do the highlighting.  See the documentation for those functions."
5265   (interactive (list 'force))
5266   (gnus-article-highlight-headers)
5267   (gnus-article-highlight-citation force)
5268   (gnus-article-highlight-signature)
5269   (gnus-article-add-buttons force)
5270   (gnus-article-add-buttons-to-head))
5271
5272 (defun gnus-article-highlight-some (&optional force)
5273   "Highlight current article.
5274 This function calls `gnus-article-highlight-headers',
5275 `gnus-article-highlight-signature', and `gnus-article-add-buttons' to
5276 do the highlighting.  See the documentation for those functions."
5277   (interactive (list 'force))
5278   (gnus-article-highlight-headers)
5279   (gnus-article-highlight-signature)
5280   (gnus-article-add-buttons))
5281
5282 (defun gnus-article-highlight-headers ()
5283   "Highlight article headers as specified by `gnus-header-face-alist'."
5284   (interactive)
5285   (save-excursion
5286     (set-buffer gnus-article-buffer)
5287     (save-restriction
5288       (let ((alist gnus-header-face-alist)
5289             (buffer-read-only nil)
5290             (case-fold-search t)
5291             (inhibit-point-motion-hooks t)
5292             entry regexp header-face field-face from hpoints fpoints)
5293         (article-narrow-to-head)
5294         (while (setq entry (pop alist))
5295           (goto-char (point-min))
5296           (setq regexp (concat "^\\("
5297                                (if (string-equal "" (nth 0 entry))
5298                                    "[^\t ]"
5299                                  (nth 0 entry))
5300                                "\\)")
5301                 header-face (nth 1 entry)
5302                 field-face (nth 2 entry))
5303           (while (and (re-search-forward regexp nil t)
5304                       (not (eobp)))
5305             (beginning-of-line)
5306             (setq from (point))
5307             (unless (search-forward ":" nil t)
5308               (forward-char 1))
5309             (when (and header-face
5310                        (not (memq (point) hpoints)))
5311               (push (point) hpoints)
5312               (gnus-put-text-property from (point) 'face header-face))
5313             (when (and field-face
5314                        (not (memq (setq from (point)) fpoints)))
5315               (push from fpoints)
5316               (if (re-search-forward "^[^ \t]" nil t)
5317                   (forward-char -2)
5318                 (goto-char (point-max)))
5319               (gnus-put-text-property from (point) 'face field-face))))))))
5320
5321 (defun gnus-article-highlight-signature ()
5322   "Highlight the signature in an article.
5323 It does this by highlighting everything after
5324 `gnus-signature-separator' using `gnus-signature-face'."
5325   (interactive)
5326   (save-excursion
5327     (set-buffer gnus-article-buffer)
5328     (let ((buffer-read-only nil)
5329           (inhibit-point-motion-hooks t))
5330       (save-restriction
5331         (when (and gnus-signature-face
5332                    (gnus-article-narrow-to-signature))
5333           (gnus-overlay-put (gnus-make-overlay (point-min) (point-max))
5334                             'face gnus-signature-face)
5335           (widen)
5336           (gnus-article-search-signature)
5337           (let ((start (match-beginning 0))
5338                 (end (set-marker (make-marker) (1+ (match-end 0)))))
5339             (gnus-article-add-button start (1- end) 'gnus-signature-toggle
5340                                      end)))))))
5341
5342 (defun gnus-button-in-region-p (b e prop)
5343   "Say whether PROP exists in the region."
5344   (text-property-not-all b e prop nil))
5345
5346 (defun gnus-article-add-buttons (&optional force)
5347   "Find external references in the article and make buttons of them.
5348 \"External references\" are things like Message-IDs and URLs, as
5349 specified by `gnus-button-alist'."
5350   (interactive (list 'force))
5351   (save-excursion
5352     (set-buffer gnus-article-buffer)
5353     (let ((buffer-read-only nil)
5354           (inhibit-point-motion-hooks t)
5355           (case-fold-search t)
5356           (alist gnus-button-alist)
5357           beg entry regexp)
5358       ;; Remove all old markers.
5359       (let (marker entry new-list)
5360         (while (setq marker (pop gnus-button-marker-list))
5361           (if (or (< marker (point-min)) (>= marker (point-max)))
5362               (push marker new-list)
5363             (goto-char marker)
5364             (when (setq entry (gnus-button-entry))
5365               (put-text-property (match-beginning (nth 1 entry))
5366                                  (match-end (nth 1 entry))
5367                                  'gnus-callback nil))
5368             (set-marker marker nil)))
5369         (setq gnus-button-marker-list new-list))
5370       ;; We skip the headers.
5371       (article-goto-body)
5372       (setq beg (point))
5373       (while (setq entry (pop alist))
5374         (setq regexp (car entry))
5375         (goto-char beg)
5376         (while (re-search-forward regexp nil t)
5377           (let* ((start (and entry (match-beginning (nth 1 entry))))
5378                  (end (and entry (match-end (nth 1 entry))))
5379                  (from (match-beginning 0)))
5380             (when (and (or (eq t (nth 2 entry))
5381                            (eval (nth 2 entry)))
5382                        (not (gnus-button-in-region-p
5383                              start end 'gnus-callback)))
5384               ;; That optional form returned non-nil, so we add the
5385               ;; button.
5386               (gnus-article-add-button
5387                start end 'gnus-button-push
5388                (car (push (set-marker (make-marker) from)
5389                           gnus-button-marker-list))))))))))
5390
5391 ;; Add buttons to the head of an article.
5392 (defun gnus-article-add-buttons-to-head ()
5393   "Add buttons to the head of the article."
5394   (interactive)
5395   (save-excursion
5396     (set-buffer gnus-article-buffer)
5397     (save-restriction
5398       (let ((buffer-read-only nil)
5399             (inhibit-point-motion-hooks t)
5400             (case-fold-search t)
5401             (alist gnus-header-button-alist)
5402             entry beg end)
5403         (article-narrow-to-head)
5404         (while alist
5405           ;; Each alist entry.
5406           (setq entry (car alist)
5407                 alist (cdr alist))
5408           (goto-char (point-min))
5409           (while (re-search-forward (car entry) nil t)
5410             ;; Each header matching the entry.
5411             (setq beg (match-beginning 0))
5412             (setq end (or (and (re-search-forward "^[^ \t]" nil t)
5413                                (match-beginning 0))
5414                           (point-max)))
5415             (goto-char beg)
5416             (while (re-search-forward (nth 1 entry) end t)
5417               ;; Each match within a header.
5418               (let* ((entry (cdr entry))
5419                      (start (match-beginning (nth 1 entry)))
5420                      (end (match-end (nth 1 entry)))
5421                      (form (nth 2 entry)))
5422                 (goto-char (match-end 0))
5423                 (when (eval form)
5424                   (gnus-article-add-button
5425                    start end (nth 3 entry)
5426                    (buffer-substring (match-beginning (nth 4 entry))
5427                                      (match-end (nth 4 entry)))))))
5428             (goto-char end)))))))
5429
5430 ;;; External functions:
5431
5432 (defun gnus-article-add-button (from to fun &optional data)
5433   "Create a button between FROM and TO with callback FUN and data DATA."
5434   (when gnus-article-button-face
5435     (gnus-overlay-put (gnus-make-overlay from to)
5436                       'face gnus-article-button-face))
5437   (gnus-add-text-properties
5438    from to
5439    (nconc (and gnus-article-mouse-face
5440                (list gnus-mouse-face-prop gnus-article-mouse-face))
5441           (list 'gnus-callback fun)
5442           (and data (list 'gnus-data data))))
5443   (widget-convert-button 'link from to :action 'gnus-widget-press-button
5444                          :button-keymap gnus-widget-button-keymap))
5445
5446 ;;; Internal functions:
5447
5448 (defun gnus-article-set-globals ()
5449   (save-excursion
5450     (set-buffer gnus-summary-buffer)
5451     (gnus-set-global-variables)))
5452
5453 (defun gnus-signature-toggle (end)
5454   (save-excursion
5455     (set-buffer gnus-article-buffer)
5456     (let ((buffer-read-only nil)
5457           (inhibit-point-motion-hooks t))
5458       (if (text-property-any end (point-max) 'article-type 'signature)
5459           (progn
5460             (gnus-delete-wash-type 'signature)
5461             (gnus-remove-text-properties-when
5462              'article-type 'signature end (point-max)
5463              (cons 'article-type (cons 'signature
5464                                        gnus-hidden-properties))))
5465         (gnus-add-wash-type 'signature)
5466         (gnus-add-text-properties-when
5467          'article-type nil end (point-max)
5468          (cons 'article-type (cons 'signature
5469                                    gnus-hidden-properties)))))
5470     (let ((gnus-article-mime-handle-alist-1 gnus-article-mime-handle-alist))
5471       (gnus-set-mode-line 'article))))
5472
5473 (defun gnus-button-entry ()
5474   ;; Return the first entry in `gnus-button-alist' matching this place.
5475   (let ((alist gnus-button-alist)
5476         (entry nil))
5477     (while alist
5478       (setq entry (pop alist))
5479       (if (looking-at (car entry))
5480           (setq alist nil)
5481         (setq entry nil)))
5482     entry))
5483
5484 (defun gnus-button-push (marker)
5485   ;; Push button starting at MARKER.
5486   (save-excursion
5487     (goto-char marker)
5488     (let* ((entry (gnus-button-entry))
5489            (inhibit-point-motion-hooks t)
5490            (fun (nth 3 entry))
5491            (args (mapcar (lambda (group)
5492                            (let ((string (match-string group)))
5493                              (gnus-set-text-properties
5494                               0 (length string) nil string)
5495                              string))
5496                          (nthcdr 4 entry))))
5497       (cond
5498        ((fboundp fun)
5499         (apply fun args))
5500        ((and (boundp fun)
5501              (fboundp (symbol-value fun)))
5502         (apply (symbol-value fun) args))
5503        (t
5504         (gnus-message 1 "You must define `%S' to use this button"
5505                       (cons fun args)))))))
5506
5507 (defun gnus-parse-news-url (url)
5508   (let (scheme server group message-id articles)
5509     (with-temp-buffer
5510       (insert url)
5511       (goto-char (point-min))
5512       (when (looking-at "\\([A-Za-z]+\\):")
5513         (setq scheme (match-string 1))
5514         (goto-char (match-end 0)))
5515       (when (looking-at "//\\([^/]+\\)/")
5516         (setq server (match-string 1))
5517         (goto-char (match-end 0)))
5518
5519       (cond
5520        ((looking-at "\\(.*@.*\\)")
5521         (setq message-id (match-string 1)))
5522        ((looking-at "\\([^/]+\\)/\\([-0-9]+\\)")
5523         (setq group (match-string 1)
5524               articles (split-string (match-string 2) "-")))
5525        ((looking-at "\\([^/]+\\)/?")
5526         (setq group (match-string 1)))
5527        (t
5528         (error "Unknown news URL syntax"))))
5529     (list scheme server group message-id articles)))
5530
5531 (defun gnus-button-handle-news (url)
5532   "Fetch a news URL."
5533   (destructuring-bind (scheme server group message-id articles)
5534       (gnus-parse-news-url url)
5535     (cond
5536      (message-id
5537       (save-excursion
5538         (set-buffer gnus-summary-buffer)
5539         (if server
5540             (let ((gnus-refer-article-method (list (list 'nntp server))))
5541               (gnus-summary-refer-article message-id))
5542           (gnus-summary-refer-article message-id))))
5543      (group
5544       (gnus-button-fetch-group url)))))
5545
5546 (defun gnus-button-handle-info (url)
5547   "Fetch an info URL."
5548   (if (string-match
5549        "^\\([^:/]+\\)?/\\(.*\\)"
5550        url)
5551       (gnus-info-find-node
5552        (concat "(" (or (gnus-url-unhex-string (match-string 1 url))
5553                        "Gnus")
5554                ")"
5555                (gnus-url-unhex-string (match-string 2 url))))
5556     (error "Can't parse %s" url)))
5557
5558 (defun gnus-button-message-id (message-id)
5559   "Fetch MESSAGE-ID."
5560   (save-excursion
5561     (set-buffer gnus-summary-buffer)
5562     (gnus-summary-refer-article message-id)))
5563
5564 (defun gnus-button-fetch-group (address)
5565   "Fetch GROUP specified by ADDRESS."
5566   (if (not (string-match "[:/]" address))
5567       ;; This is just a simple group url.
5568       (gnus-group-read-ephemeral-group address gnus-select-method)
5569     (if (not
5570          (string-match
5571           "^\\([^:/]+\\)\\(:\\([^/]+\\)\\)?/\\([^/]+\\)\\(/\\([0-9]+\\)\\)?"
5572           address))
5573         (error "Can't parse %s" address)
5574       (gnus-group-read-ephemeral-group
5575        (match-string 4 address)
5576        `(nntp ,(match-string 1 address)
5577               (nntp-address ,(match-string 1 address))
5578               (nntp-port-number ,(if (match-end 3)
5579                                      (match-string 3 address)
5580                                    "nntp")))
5581        nil nil nil
5582        (and (match-end 6) (list (string-to-int (match-string 6 address))))))))
5583
5584 (defun gnus-url-parse-query-string (query &optional downcase)
5585   (let (retval pairs cur key val)
5586     (setq pairs (split-string query "&"))
5587     (while pairs
5588       (setq cur (car pairs)
5589             pairs (cdr pairs))
5590       (if (not (string-match "=" cur))
5591           nil                           ; Grace
5592         (setq key (gnus-url-unhex-string (substring cur 0 (match-beginning 0)))
5593               val (gnus-url-unhex-string (substring cur (match-end 0) nil) t))
5594         (if downcase
5595             (setq key (downcase key)))
5596         (setq cur (assoc key retval))
5597         (if cur
5598             (setcdr cur (cons val (cdr cur)))
5599           (setq retval (cons (list key val) retval)))))
5600     retval))
5601
5602 (defun gnus-url-mailto (url)
5603   ;; Send mail to someone
5604   (when (string-match "mailto:/*\\(.*\\)" url)
5605     (setq url (substring url (match-beginning 1) nil)))
5606   (let (to args subject func)
5607     (if (string-match (regexp-quote "?") url)
5608         (setq to (gnus-url-unhex-string (substring url 0 (match-beginning 0)))
5609               args (gnus-url-parse-query-string
5610                     (substring url (match-end 0) nil) t))
5611       (setq to (gnus-url-unhex-string url)))
5612     (setq args (cons (list "to" to) args)
5613           subject (cdr-safe (assoc "subject" args)))
5614     (gnus-msg-mail)
5615     (while args
5616       (setq func (intern-soft (concat "message-goto-" (downcase (caar args)))))
5617       (if (fboundp func)
5618           (funcall func)
5619         (message-position-on-field (caar args)))
5620       (insert (mapconcat 'identity (cdar args) ", "))
5621       (setq args (cdr args)))
5622     (if subject
5623         (message-goto-body)
5624       (message-goto-subject))))
5625
5626 (defun gnus-button-embedded-url (address)
5627   "Activate ADDRESS with `browse-url'."
5628   (browse-url (gnus-strip-whitespace address)))
5629
5630 ;;; Next/prev buttons in the article buffer.
5631
5632 (defvar gnus-next-page-line-format "%{%(Next page...%)%}\n")
5633 (defvar gnus-prev-page-line-format "%{%(Previous page...%)%}\n")
5634
5635 (defvar gnus-prev-page-map
5636   (let ((map (make-sparse-keymap)))
5637     (unless (>= emacs-major-version 21)
5638       ;; XEmacs doesn't care.
5639       (set-keymap-parent map gnus-article-mode-map))
5640     (define-key map gnus-mouse-2 'gnus-button-prev-page)
5641     (define-key map "\r" 'gnus-button-prev-page)
5642     map))
5643
5644 (defun gnus-insert-prev-page-button ()
5645   (let ((b (point))
5646         (buffer-read-only nil))
5647     (gnus-eval-format
5648      gnus-prev-page-line-format nil
5649      `(,@(gnus-local-map-property gnus-prev-page-map)
5650          gnus-prev t
5651          gnus-callback gnus-article-button-prev-page
5652          article-type annotation))
5653     (widget-convert-button
5654      'link b (point)
5655      :action 'gnus-button-prev-page
5656      :button-keymap gnus-prev-page-map)))
5657
5658 (defvar gnus-prev-page-map
5659   (let ((map (make-sparse-keymap)))
5660     (unless (>= emacs-major-version 21)
5661       ;; XEmacs doesn't care.
5662       (set-keymap-parent map gnus-article-mode-map))
5663     (define-key map gnus-mouse-2 'gnus-button-prev-page)
5664     (define-key map "\r" 'gnus-button-prev-page)
5665     map))
5666
5667 (defvar gnus-next-page-map
5668   (let ((map (make-sparse-keymap)))
5669     (unless (>= emacs-major-version 21)
5670       ;; XEmacs doesn't care.
5671       (set-keymap-parent map gnus-article-mode-map))
5672     (define-key map gnus-mouse-2 'gnus-button-next-page)
5673     (define-key map "\r" 'gnus-button-next-page)
5674     map))
5675
5676 (defun gnus-button-next-page (&optional args more-args)
5677   "Go to the next page."
5678   (interactive)
5679   (let ((win (selected-window)))
5680     (select-window (gnus-get-buffer-window gnus-article-buffer t))
5681     (gnus-article-next-page)
5682     (select-window win)))
5683
5684 (defun gnus-button-prev-page (&optional args more-args)
5685   "Go to the prev page."
5686   (interactive)
5687   (let ((win (selected-window)))
5688     (select-window (gnus-get-buffer-window gnus-article-buffer t))
5689     (gnus-article-prev-page)
5690     (select-window win)))
5691
5692 (defun gnus-insert-next-page-button ()
5693   (let ((b (point))
5694         (buffer-read-only nil))
5695     (gnus-eval-format gnus-next-page-line-format nil
5696                       `(,@(gnus-local-map-property gnus-next-page-map)
5697                           gnus-next t
5698                           gnus-callback gnus-article-button-next-page
5699                           article-type annotation))
5700     (widget-convert-button
5701      'link b (point)
5702      :action 'gnus-button-next-page
5703      :button-keymap gnus-next-page-map)))
5704
5705 (defun gnus-article-button-next-page (arg)
5706   "Go to the next page."
5707   (interactive "P")
5708   (let ((win (selected-window)))
5709     (select-window (gnus-get-buffer-window gnus-article-buffer t))
5710     (gnus-article-next-page)
5711     (select-window win)))
5712
5713 (defun gnus-article-button-prev-page (arg)
5714   "Go to the prev page."
5715   (interactive "P")
5716   (let ((win (selected-window)))
5717     (select-window (gnus-get-buffer-window gnus-article-buffer t))
5718     (gnus-article-prev-page)
5719     (select-window win)))
5720
5721 (defvar gnus-decode-header-methods
5722   '(mail-decode-encoded-word-region)
5723   "List of methods used to decode headers.
5724
5725 This variable is a list of FUNCTION or (REGEXP . FUNCTION).  If item
5726 is FUNCTION, FUNCTION will be apply to all newsgroups.  If item is a
5727 (REGEXP . FUNCTION), FUNCTION will be only apply to thes newsgroups
5728 whose names match REGEXP.
5729
5730 For example:
5731 ((\"chinese\" . gnus-decode-encoded-word-region-by-guess)
5732  mail-decode-encoded-word-region
5733  (\"chinese\" . rfc1843-decode-region))
5734 ")
5735
5736 (defvar gnus-decode-header-methods-cache nil)
5737
5738 (defun gnus-multi-decode-header (start end)
5739   "Apply the functions from `gnus-encoded-word-methods' that match."
5740   (unless (and gnus-decode-header-methods-cache
5741                (eq gnus-newsgroup-name
5742                    (car gnus-decode-header-methods-cache)))
5743     (setq gnus-decode-header-methods-cache (list gnus-newsgroup-name))
5744     (mapcar (lambda (x)
5745               (if (symbolp x)
5746                   (nconc gnus-decode-header-methods-cache (list x))
5747                 (if (and gnus-newsgroup-name
5748                          (string-match (car x) gnus-newsgroup-name))
5749                     (nconc gnus-decode-header-methods-cache
5750                            (list (cdr x))))))
5751           gnus-decode-header-methods))
5752   (let ((xlist gnus-decode-header-methods-cache))
5753     (pop xlist)
5754     (save-restriction
5755       (narrow-to-region start end)
5756       (while xlist
5757         (funcall (pop xlist) (point-min) (point-max))))))
5758
5759 ;;;
5760 ;;; Treatment top-level handling.
5761 ;;;
5762
5763 (defun gnus-treat-article (condition &optional part-number total-parts type)
5764   (let ((length (- (point-max) (point-min)))
5765         (alist gnus-treatment-function-alist)
5766         (article-goto-body-goes-to-point-min-p t)
5767         (treated-type
5768          (or (not type)
5769              (catch 'found
5770                (let ((list gnus-article-treat-types))
5771                  (while list
5772                    (when (string-match (pop list) type)
5773                      (throw 'found t)))))))
5774         (highlightp (gnus-visual-p 'article-highlight 'highlight))
5775         val elem)
5776     (gnus-run-hooks 'gnus-part-display-hook)
5777     (while (setq elem (pop alist))
5778       (setq val
5779             (save-excursion
5780               (if (gnus-buffer-live-p gnus-summary-buffer)
5781                   (set-buffer gnus-summary-buffer))
5782               (symbol-value (car elem))))
5783       (when (and (or (consp val)
5784                      treated-type)
5785                  (gnus-treat-predicate val)
5786                  (or (not (get (car elem) 'highlight))
5787                      highlightp))
5788         (save-restriction
5789           (funcall (cadr elem)))))))
5790
5791 ;; Dynamic variables.
5792 (eval-when-compile
5793   (defvar part-number)
5794   (defvar total-parts)
5795   (defvar type)
5796   (defvar condition)
5797   (defvar length))
5798
5799 (defun gnus-treat-predicate (val)
5800   (cond
5801    ((null val)
5802     nil)
5803    ((and (listp val)
5804          (stringp (car val)))
5805     (apply 'gnus-or (mapcar `(lambda (s)
5806                                (string-match s ,(or gnus-newsgroup-name "")))
5807                             val)))
5808    ((listp val)
5809     (let ((pred (pop val)))
5810       (cond
5811        ((eq pred 'or)
5812         (apply 'gnus-or (mapcar 'gnus-treat-predicate val)))
5813        ((eq pred 'and)
5814         (apply 'gnus-and (mapcar 'gnus-treat-predicate val)))
5815        ((eq pred 'not)
5816         (not (gnus-treat-predicate (car val))))
5817        ((eq pred 'typep)
5818         (equal (car val) type))
5819        (t
5820         (error "%S is not a valid predicate" pred)))))
5821    (condition
5822     (eq condition val))
5823    ((eq val t)
5824     t)
5825    ((eq val 'head)
5826     nil)
5827    ((eq val 'last)
5828     (eq part-number total-parts))
5829    ((numberp val)
5830     (< length val))
5831    (t
5832     (error "%S is not a valid value" val))))
5833
5834 (defun gnus-article-encrypt-body (protocol &optional n)
5835   "Encrypt the article body."
5836   (interactive
5837    (list
5838     (or gnus-article-encrypt-protocol
5839         (completing-read "Encrypt protocol: "
5840                          gnus-article-encrypt-protocol-alist
5841                          nil t))
5842     current-prefix-arg))
5843   (let ((func (cdr (assoc protocol gnus-article-encrypt-protocol-alist))))
5844     (unless func
5845       (error (format "Can't find the encrypt protocol %s" protocol)))
5846     (if (equal gnus-newsgroup-name "nndraft:drafts")
5847         (error "Can't encrypt the article in group nndraft:drafts"))
5848     (if (equal gnus-newsgroup-name "nndraft:queue")
5849         (error "Don't encrypt the article in group nndraft:queue"))
5850     (gnus-summary-iterate n
5851       (save-excursion
5852         (set-buffer gnus-summary-buffer)
5853         (let ((mail-parse-charset gnus-newsgroup-charset)
5854               (mail-parse-ignored-charsets gnus-newsgroup-ignored-charsets)
5855               (summary-buffer gnus-summary-buffer)
5856               references point)
5857           (gnus-set-global-variables)
5858           (when (gnus-group-read-only-p)
5859             (error "The current newsgroup does not support article encrypt"))
5860           (gnus-summary-show-article t)
5861           (setq references
5862               (or (mail-header-references gnus-current-headers) ""))
5863           (set-buffer gnus-article-buffer)
5864           (let* ((buffer-read-only nil)
5865                  (headers
5866                   (mapcar (lambda (field)
5867                             (and (save-restriction
5868                                    (message-narrow-to-head)
5869                                    (goto-char (point-min))
5870                                    (search-forward field nil t))
5871                                  (prog2
5872                                      (message-narrow-to-field)
5873                                      (buffer-substring (point-min) (point-max))
5874                                    (delete-region (point-min) (point-max))
5875                                    (widen))))
5876                           '("Content-Type:" "Content-Transfer-Encoding:"
5877                             "Content-Disposition:"))))
5878             (message-narrow-to-head)
5879             (message-remove-header "MIME-Version")
5880             (goto-char (point-max))
5881             (setq point (point))
5882             (insert (apply 'concat headers))
5883             (widen)
5884             (narrow-to-region point (point-max))
5885             (let ((message-options message-options))
5886               (message-options-set 'message-sender user-mail-address)
5887               (message-options-set 'message-recipients user-mail-address)
5888               (message-options-set 'message-sign-encrypt 'not)
5889               (funcall func))
5890             (goto-char (point-min))
5891             (insert "MIME-Version: 1.0\n")
5892             (widen)
5893             (gnus-summary-edit-article-done
5894              references nil summary-buffer t))
5895           (when gnus-keep-backlog
5896             (gnus-backlog-remove-article
5897              (car gnus-article-current) (cdr gnus-article-current)))
5898           (save-excursion
5899             (when (get-buffer gnus-original-article-buffer)
5900               (set-buffer gnus-original-article-buffer)
5901               (setq gnus-original-article nil)))
5902           (when gnus-use-cache
5903             (gnus-cache-update-article
5904              (car gnus-article-current) (cdr gnus-article-current))))))))
5905
5906 (defvar gnus-mime-security-button-line-format "%{%([[%t:%i]%D]%)%}\n"
5907   "The following specs can be used:
5908 %t  The security MIME type
5909 %i  Additional info
5910 %d  Details
5911 %D  Details if button is pressed")
5912
5913 (defvar gnus-mime-security-button-end-line-format "%{%([[End of %t]%D]%)%}\n"
5914   "The following specs can be used:
5915 %t  The security MIME type
5916 %i  Additional info
5917 %d  Details
5918 %D  Details if button is pressed")
5919
5920 (defvar gnus-mime-security-button-line-format-alist
5921   '((?t gnus-tmp-type ?s)
5922     (?i gnus-tmp-info ?s)
5923     (?d gnus-tmp-details ?s)
5924     (?D gnus-tmp-pressed-details ?s)))
5925
5926 (defvar gnus-mime-security-button-map
5927   (let ((map (make-sparse-keymap)))
5928     (unless (>= (string-to-number emacs-version) 21)
5929       (set-keymap-parent map gnus-article-mode-map))
5930     (define-key map gnus-mouse-2 'gnus-article-push-button)
5931     (define-key map "\r" 'gnus-article-press-button)
5932     map))
5933
5934 (defvar gnus-mime-security-details-buffer nil)
5935
5936 (defvar gnus-mime-security-button-pressed nil)
5937
5938 (defvar gnus-mime-security-show-details-inline t
5939   "If non-nil, show details in the article buffer.")
5940
5941 (defun gnus-mime-security-verify-or-decrypt (handle)
5942   (mm-remove-parts (cdr handle))
5943   (let ((region (mm-handle-multipart-ctl-parameter handle 'gnus-region))
5944         point buffer-read-only)
5945     (if region
5946         (goto-char (car region)))
5947     (save-restriction
5948       (narrow-to-region (point) (point))
5949       (with-current-buffer (mm-handle-multipart-original-buffer handle)
5950         (let* ((mm-verify-option 'known)
5951                (mm-decrypt-option 'known)
5952                (nparts (mm-possibly-verify-or-decrypt (cdr handle) handle)))
5953           (unless (eq nparts (cdr handle))
5954             (mm-destroy-parts (cdr handle))
5955             (setcdr handle nparts))))
5956       (setq point (point))
5957       (gnus-mime-display-security handle)
5958       (goto-char (point-max)))
5959     (when region
5960       (delete-region (point) (cdr region))
5961       (set-marker (car region) nil)
5962       (set-marker (cdr region) nil))
5963     (goto-char point)))
5964
5965 (defun gnus-mime-security-show-details (handle)
5966   (let ((details (mm-handle-multipart-ctl-parameter handle 'gnus-details)))
5967     (if details
5968         (if gnus-mime-security-show-details-inline
5969             (let ((gnus-mime-security-button-pressed t)
5970                   (gnus-mime-security-button-line-format
5971                    (get-text-property (point) 'gnus-line-format))
5972                 buffer-read-only)
5973               (forward-char -1)
5974               (while (eq (get-text-property (point) 'gnus-line-format)
5975                          gnus-mime-security-button-line-format)
5976                 (forward-char -1))
5977               (forward-char)
5978               (save-restriction
5979                 (narrow-to-region (point) (point))
5980                 (gnus-insert-mime-security-button handle))
5981               (delete-region (point)
5982                              (or (text-property-not-all
5983                                   (point) (point-max)
5984                                   'gnus-line-format
5985                                   gnus-mime-security-button-line-format)
5986                                  (point-max))))
5987           (if (gnus-buffer-live-p gnus-mime-security-details-buffer)
5988               (with-current-buffer gnus-mime-security-details-buffer
5989                 (erase-buffer)
5990                 t)
5991             (setq gnus-mime-security-details-buffer
5992                   (gnus-get-buffer-create "*MIME Security Details*")))
5993           (with-current-buffer gnus-mime-security-details-buffer
5994             (insert details)
5995             (goto-char (point-min)))
5996           (pop-to-buffer gnus-mime-security-details-buffer))
5997       (gnus-message 5 "No details."))))
5998
5999 (defun gnus-mime-security-press-button (handle)
6000   (if (mm-handle-multipart-ctl-parameter handle 'gnus-info)
6001       (gnus-mime-security-show-details handle)
6002     (gnus-mime-security-verify-or-decrypt handle)))
6003
6004 (defun gnus-insert-mime-security-button (handle &optional displayed)
6005   (let* ((protocol (mm-handle-multipart-ctl-parameter handle 'protocol))
6006          (gnus-tmp-type
6007           (concat
6008            (or (nth 2 (assoc protocol mm-verify-function-alist))
6009                (nth 2 (assoc protocol mm-decrypt-function-alist))
6010                "Unknown")
6011            (if (equal (car handle) "multipart/signed")
6012                " Signed" " Encrypted")
6013            " Part"))
6014          (gnus-tmp-info
6015           (or (mm-handle-multipart-ctl-parameter handle 'gnus-info)
6016               "Undecided"))
6017          (gnus-tmp-details
6018           (mm-handle-multipart-ctl-parameter handle 'gnus-details))
6019          gnus-tmp-pressed-details
6020          b e)
6021     (setq gnus-tmp-details
6022           (if gnus-tmp-details
6023               (concat "\n" gnus-tmp-details) ""))
6024     (setq gnus-tmp-pressed-details
6025           (if gnus-mime-security-button-pressed gnus-tmp-details ""))
6026     (unless (bolp)
6027       (insert "\n"))
6028     (setq b (point))
6029     (gnus-eval-format
6030      gnus-mime-security-button-line-format
6031      gnus-mime-security-button-line-format-alist
6032      `(,@(gnus-local-map-property gnus-mime-security-button-map)
6033          gnus-callback gnus-mime-security-press-button
6034          gnus-line-format ,gnus-mime-security-button-line-format
6035          article-type annotation
6036          gnus-data ,handle))
6037     (setq e (point))
6038     (widget-convert-button
6039      'link b e
6040      :mime-handle handle
6041      :action 'gnus-widget-press-button
6042      :button-keymap gnus-mime-security-button-map
6043      :help-echo
6044      (lambda (widget/window &optional overlay pos)
6045        ;; Needed to properly clear the message due to a bug in
6046        ;; wid-edit (XEmacs only).
6047        (if (boundp 'help-echo-owns-message)
6048            (setq help-echo-owns-message t))
6049        (format
6050         "%S: show detail"
6051         (aref gnus-mouse-2 0))))))
6052
6053 (defun gnus-mime-display-security (handle)
6054   (save-restriction
6055     (narrow-to-region (point) (point))
6056     (unless (gnus-unbuttonized-mime-type-p (car handle))
6057       (gnus-insert-mime-security-button handle))
6058     (gnus-mime-display-mixed (cdr handle))
6059     (unless (bolp)
6060       (insert "\n"))
6061     (unless (gnus-unbuttonized-mime-type-p (car handle))
6062       (let ((gnus-mime-security-button-line-format
6063              gnus-mime-security-button-end-line-format))
6064         (gnus-insert-mime-security-button handle)))
6065     (mm-set-handle-multipart-parameter
6066      handle 'gnus-region
6067      (cons (set-marker (make-marker) (point-min))
6068            (set-marker (make-marker) (point-max))))))
6069
6070 (gnus-ems-redefine)
6071
6072 (provide 'gnus-art)
6073
6074 (run-hooks 'gnus-art-load-hook)
6075
6076 ;;; gnus-art.el ends here