;;; Commentary:
+;; Jaap-Henk Hoepman (jhh@xs4all.nl):
+;;
+;; Added support for delayed destroy of external MIME viewers. All external
+;; viewers for mime types in mm-keep-viewer-alive-types will remain active
+;; after switching articles or groups, and will only be removed when exiting
+;; gnus.
+;;
+
;;; Code:
(require 'mail-parse)
(autoload 'mm-inline-external-body "mm-extern")
(autoload 'mm-insert-inline "mm-view"))
+(add-hook 'gnus-exit-gnus-hook 'mm-destroy-postponed-undisplay-list)
+
(defgroup mime-display ()
"Display of MIME in mail and news articles."
:link '(custom-manual "(emacs-mime)Customization")
("application/pkcs7-signature" ignore identity)
("multipart/alternative" ignore identity)
("multipart/mixed" ignore identity)
- ("multipart/related" ignore identity))
+ ("multipart/related" ignore identity)
+ ;; Default to displaying as text
+ (".*" mm-inline-text identity))
"Alist of media types/tests saying whether types can be displayed inline."
:type '(repeat (list (string :tag "MIME type")
(function :tag "Display function")
"message/partial" "message/external-body" "application/emacs-lisp"
"application/pgp-signature" "application/x-pkcs7-signature"
"application/pkcs7-signature")
- "List of media types that are to be displayed inline."
+ "List of media types that are to be displayed inline.
+See also `mm-inline-media-tests', which says how to display a media
+type inline."
+ :type '(repeat string)
+ :group 'mime-display)
+
+(defcustom mm-keep-viewer-alive-types
+ '("application/postscript" "application/msword" "application/vnd.ms-excel"
+ "application/pdf" "application/x-dvi")
+ "List of media types for which the external viewer will not be killed
+when selecting a different article."
:type '(repeat string)
:group 'mime-display)
:type '(repeat string)
:group 'mime-display)
-(defvar mm-tmp-directory
+(defcustom mm-tmp-directory
(cond ((fboundp 'temp-directory) (temp-directory))
((boundp 'temporary-file-directory) temporary-file-directory)
("/tmp/"))
- "Where mm will store its temporary files.")
+ "Where mm will store its temporary files."
+ :type 'directory
+ :group 'mime-display)
(defcustom mm-inline-large-images nil
"If non-nil, then all images fit in the buffer."
:type 'boolean
:group 'mime-display)
+(defvar mm-file-name-rewrite-functions nil
+ "*List of functions used for rewriting file names of MIME parts.
+Each function takes a file name as input and returns a file name.
+
+Ready-made functions include
+`mm-file-name-delete-whitespace',
+`mm-file-name-trim-whitespace',
+`mm-file-name-collapse-whitespace',
+`mm-file-name-replace-whitespace',
+`capitalize', `downcase', `upcase', and
+`upcase-initials'.")
+
+(defvar mm-file-name-replace-whitespace nil
+ "String used for replacing whitespace characters; default is `\"_\"'.")
+
+(defcustom mm-default-directory nil
+ "The default directory where mm will save files.
+If not set, `default-directory' will be used."
+ :type 'directory
+ :group 'mime-display)
+
;;; Internal variables.
(defvar mm-dissection-list nil)
(defvar mm-last-shell-command "")
(defvar mm-content-id-alist nil)
+(defvar mm-postponed-undisplay-list nil)
;; According to RFC2046, in particular, in a digest, the default
;; Content-Type value for a body part is changed from "text/plain" to
(setq alist (cdr alist)))
(nreverse plist)))
+(defun mm-keep-viewer-alive-p (handle)
+ "Say whether external viewer for HANDLE should stay alive."
+ (let ((types mm-keep-viewer-alive-types)
+ (type (mm-handle-media-type handle))
+ ty)
+ (catch 'found
+ (while (setq ty (pop types))
+ (when (string-match ty type)
+ (throw 'found t))))))
+
+(defun mm-handle-set-external-undisplayer (handle function)
+ "Set the undisplayer for this handle; postpone undisplaying of viewers
+for types in mm-keep-viewer-alive-types."
+ (if (mm-keep-viewer-alive-p handle)
+ (let ((new-handle (copy-sequence handle)))
+ (mm-handle-set-undisplayer new-handle function)
+ (mm-handle-set-undisplayer handle nil)
+ (push new-handle mm-postponed-undisplay-list))
+ (mm-handle-set-undisplayer handle function)))
+
+(defun mm-destroy-postponed-undisplay-list ()
+ (message "Destroying external MIME viewers")
+ (mm-destroy-parts mm-postponed-undisplay-list))
+
(defun mm-dissect-buffer (&optional no-strict-mime)
"Dissect the current buffer and return a list of MIME handles."
(save-excursion
cte (mail-fetch-field "content-transfer-encoding")
cd (mail-fetch-field "content-disposition")
description (mail-fetch-field "content-description")
- from (cadr (mail-extract-address-components
- (or (mail-fetch-field "from") "")))
- id (mail-fetch-field "content-id"))))
+ from (mail-fetch-field "from")
+ id (mail-fetch-field "content-id"))
+ ;; FIXME: In some circumstances, this code is running within
+ ;; an unibyte macro. mail-extract-address-components
+ ;; creates unibyte buffers. This `if', though not a perfect
+ ;; solution, avoids most of them.
+ (if from
+ (setq from (cadr (mail-extract-address-components from))))))
(when cte
(setq cte (mail-header-strip cte)))
(if (or (not ctl)
(defun mm-copy-to-buffer ()
"Copy the contents of the current buffer to a fresh buffer."
(save-excursion
- (let ((obuf (current-buffer))
- beg)
+ (let ((flag enable-multibyte-characters)
+ (new-buffer (generate-new-buffer " *mm*")))
(goto-char (point-min))
(search-forward-regexp "^\n" nil t)
- (setq beg (point))
- (set-buffer (generate-new-buffer " *mm*"))
- (insert-buffer-substring obuf beg)
- (current-buffer))))
+ (save-restriction
+ (narrow-to-region (point) (point-max))
+ (when flag
+ (set-buffer-multibyte nil))
+ (copy-to-buffer new-buffer (point-min) (point-max))
+ (when flag
+ (set-buffer-multibyte t)))
+ new-buffer)))
(defun mm-display-parts (handle &optional no-default)
(if (stringp (car handle))
(mm-remove-part handle)
(let* ((type (mm-handle-media-type handle))
(method (mailcap-mime-info type)))
- (if (mm-inlined-p handle)
+ (if (and (mm-inlinable-p handle)
+ (mm-inlined-p handle))
(progn
(forward-line 1)
(mm-display-inline handle)
shell-command-switch
(mm-mailcap-command
method file (mm-handle-type handle)))
- (mm-handle-set-undisplayer handle (cons file buffer)))
+ (mm-handle-set-external-undisplayer handle (cons file buffer)))
(message "Displaying %s..." (format method file))
'external)
(copiousoutput
shell-command-switch
(mm-mailcap-command
method file (mm-handle-type handle)))
- (mm-handle-set-undisplayer handle (cons file buffer)))
+ (mm-handle-set-external-undisplayer handle (cons file buffer)))
(message "Displaying %s..." (format method file))
'external)))))))
(when (string-match (car elem) type)
(return elem))))
+(defun mm-automatic-display-p (handle)
+ "Say whether the user wants HANDLE to be displayed automatically."
+ (let ((methods mm-automatic-display)
+ (type (mm-handle-media-type handle))
+ method result)
+ (while (setq method (pop methods))
+ (when (and (not (mm-inline-override-p handle))
+ (string-match method type))
+ (setq result t
+ methods nil)))
+ result))
+
(defun mm-inlinable-p (handle)
"Say whether HANDLE can be displayed inline."
(let ((alist mm-inline-media-tests)
(pop alist))
test))
-(defun mm-automatic-display-p (handle)
- "Say whether the user wants HANDLE to be displayed automatically."
- (let ((methods mm-automatic-display)
- (type (mm-handle-media-type handle))
- method result)
- (while (setq method (pop methods))
- (when (and (not (mm-inline-override-p handle))
- (string-match method type)
- (mm-inlinable-p handle))
- (setq result t
- methods nil)))
- result))
-
(defun mm-inlined-p (handle)
- "Say whether the user wants HANDLE to be displayed automatically."
+ "Say whether the user wants HANDLE to be displayed inline."
(let ((methods mm-inlined-types)
(type (mm-handle-media-type handle))
method result)
(while (setq method (pop methods))
(when (and (not (mm-inline-override-p handle))
- (string-match method type)
- (mm-inlinable-p handle))
+ (string-match method type))
(setq result t
methods nil)))
result))
(catch 'found
(while (setq ty (pop types))
(when (and (string-match ty type)
- (mm-inlinable-p handle))
+ (mm-inlinable-p ty))
(throw 'found t))))))
(defun mm-inline-override-p (handle)
(set-buffer cur)
(insert-buffer-substring temp)))))))
-(defvar mm-default-directory nil)
+(defun mm-file-name-delete-whitespace (file-name)
+ "Remove all whitespace characters from FILE-NAME."
+ (while (string-match "\\s-+" file-name)
+ (setq file-name (replace-match "" t t file-name)))
+ file-name)
+
+(defun mm-file-name-trim-whitespace (file-name)
+ "Remove leading and trailing whitespace characters from FILE-NAME."
+ (when (string-match "\\`\\s-+" file-name)
+ (setq file-name (substring file-name (match-end 0))))
+ (when (string-match "\\s-+\\'" file-name)
+ (setq file-name (substring file-name 0 (match-beginning 0))))
+ file-name)
+
+(defun mm-file-name-collapse-whitespace (file-name)
+ "Collapse multiple whitespace characters in FILE-NAME."
+ (while (string-match "\\s-\\s-+" file-name)
+ (setq file-name (replace-match " " t t file-name)))
+ file-name)
+
+(defun mm-file-name-replace-whitespace (file-name)
+ "Replace whitespace characters in FILE-NAME with underscores.
+Set `mm-file-name-replace-whitespace' to any other string if you do not
+like underscores."
+ (let ((s (or mm-file-name-replace-whitespace "_")))
+ (while (string-match "\\s-" file-name)
+ (setq file-name (replace-match s t t file-name))))
+ file-name)
(defun mm-save-part (handle)
"Write HANDLE to a file."
(mm-handle-disposition handle) 'filename))
file)
(when filename
- (setq filename (file-name-nondirectory filename)))
+ (setq filename (gnus-map-function mm-file-name-rewrite-functions
+ (file-name-nondirectory filename))))
(setq file
(read-file-name "Save MIME part to: "
(expand-file-name
(read-string "Shell command on MIME part: " mm-last-shell-command)))
(mm-with-unibyte-buffer
(mm-insert-part handle)
- (shell-command-on-region (point-min) (point-max) command nil))))
+ (let ((coding-system-for-write 'binary))
+ (shell-command-on-region (point-min) (point-max) command nil)))))
(defun mm-interactively-view-part (handle)
"Display HANDLE using METHOD."