1 ;;; vm-save.el --- Saving and piping messages under VM
3 ;; Copyright (C) 1989, 1990, 1993, 1994 Kyle E. Jones
4 ;; Copyright (C) 2003-2006 Robert Widhopf-Fenk
6 ;; This program is free software; you can redistribute it and/or modify
7 ;; it under the terms of the GNU General Public License as published by
8 ;; the Free Software Foundation; either version 2 of the License, or
9 ;; (at your option) any later version.
11 ;; This program is distributed in the hope that it will be useful,
12 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;; GNU General Public License for more details.
16 ;; You should have received a copy of the GNU General Public License along
17 ;; with this program; if not, write to the Free Software Foundation, Inc.,
18 ;; 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 ;; (match-data) returns the match data as MARKERS, often corrupting it in the
21 ;; process due to buffer narrowing, and the fact that buffers are indexed from
22 ;; 1 while strings are indexed from 0. :-(
27 (defun vm-match-data ()
28 (let ((n (1- (/ (length (match-data)) 2)))
31 (setq list (cons (match-beginning n)
32 (cons (match-end n) list))
37 (defun vm-auto-select-folder (mp auto-folder-alist)
38 (condition-case error-data
40 (let (header alist tuple-list)
41 (setq alist auto-folder-alist)
43 (setq header (vm-get-header-contents (car mp) (car (car alist))
47 (setq tuple-list (cdr (car alist)))
49 (if (let ((case-fold-search vm-auto-folder-case-fold-search))
50 (string-match (car (car tuple-list)) header))
51 ;; Don't waste time eval'ing an atom.
52 (if (stringp (cdr (car tuple-list)))
53 (throw 'match (cdr (car tuple-list)))
54 (let* ((match-data (vm-match-data))
55 ;; allow this buffer to live forever
56 (buf (get-buffer-create " *vm-auto-folder*"))
58 ;; Set up a buffer that matches our cached
62 (if vm-fsfemacs-mule-p
63 (set-buffer-multibyte nil))
67 ;; It appears that get-buffer-create clobbers the
70 ;; The match data is off by one because we matched
71 ;; a string and Emacs indexes strings from 0 and
74 ;; Also store-match-data only accepts MARKERS!!
78 (function (lambda (n) (and n (vm-marker n))))
80 (function (lambda (n) (and n (1+ n))))
82 (setq result (eval (cdr (car tuple-list))))
84 (setq result (vm-auto-select-folder mp result)))
86 (throw 'match result))))))
87 (setq tuple-list (cdr tuple-list))))
88 (setq alist (cdr alist)))
90 (error (error "error processing vm-auto-folder-alist: %s"
91 (prin1-to-string error-data)))))
94 (defun vm-auto-archive-messages (&optional arg)
95 "Save all unfiled messages that auto-match a folder via
96 vm-auto-folder-alist to their appropriate folders. Messages that
97 are flagged for deletion are not saved.
99 Prefix arg means to ask user for confirmation before saving each message.
101 When invoked on marked messages (via vm-next-command-uses-marks),
102 only marked messages are checked against vm-auto-folder-alist.
104 The saved messages are flagged as `filed'."
106 (vm-select-folder-buffer)
107 (vm-check-for-killed-summary)
108 (vm-error-if-folder-empty)
109 (message "Archiving...")
113 ;; Need separate (let ...) so vm-message-pointer can
114 ;; revert back in time for
115 ;; (vm-update-summary-and-mode-line).
116 ;; vm-last-save-folder is tucked away here since archives
117 ;; shouldn't affect its value.
118 (let ((vm-message-pointer
119 (if (eq last-command 'vm-next-command-uses-marks)
120 (vm-select-marked-or-prefixed-messages 0)
124 (vm-last-save-folder vm-last-save-folder)
125 (vm-move-after-deleting nil))
126 ;; mark the place where we should stop. otherwise if any
127 ;; messages in this folder are archived to this folder
128 ;; we would file messages into this folder forever.
129 (setq stop-point (vm-last vm-message-pointer))
131 (and (not (vm-filed-flag (car vm-message-pointer)))
132 ;; don't archive deleted messages
133 (not (vm-deleted-flag (car vm-message-pointer)))
134 (setq auto-folder (vm-auto-select-folder
136 vm-auto-folder-alist))
137 ;; Don't let user archive into the same folder
138 ;; that they are visiting.
139 (not (eq (vm-get-file-buffer auto-folder)
143 (format "Save message %s in folder %s? "
144 (vm-number-of (car vm-message-pointer))
146 (let ((vm-delete-after-saving vm-delete-after-archiving)
147 (last-command 'vm-auto-archive-messages))
148 (vm-save-message auto-folder)
149 (vm-increment archived)
150 (message "%d archived, still working..." archived)))
151 (setq done (eq vm-message-pointer stop-point)
152 vm-message-pointer (cdr vm-message-pointer))))
154 (intern (buffer-name) vm-buffers-needing-display-update)
155 (vm-update-summary-and-mode-line))
157 (message "No messages were archived")
158 (message "%d message%s archived"
159 archived (if (= 1 archived) "" "s")))))
161 ;;;---------------------------------------------------------------------------
162 ;; The following defun seems a lot less efficient than it might be,
163 ;; but I don't have a better sense of how to access the folder buffer
164 ;; and read its local variables. [2006/10/31:rpg]
165 ;;---------------------------------------------------------------------------
167 (defun vm-imap-folder-p ()
168 "Is the current folder an IMAP folder?"
170 (vm-select-folder-buffer)
171 (eq vm-folder-access-method 'imap)))
173 ;;;---------------------------------------------------------------------------
174 ;; New shell defun to handle both IMAP and local saving.
175 ;;---------------------------------------------------------------------------
177 (defun vm-save-message (folder &optional count)
178 "Save the current message. This may be done either by saving it
179 to an IMAP folder or by saving it to a local filesystem folder.
180 Which is done is controlled by the type of the current vm-folder
181 buffer and the variable `vm-imap-save-to-server'."
183 (if (and vm-imap-save-to-server
185 ;; IMAP saving --- argument parsing taken from
186 ;; vm-save-message-to-imap-folder
188 (vm-session-initialization)
189 (vm-check-for-killed-folder)
190 (vm-select-folder-buffer-if-possible)
191 (let ((this-command this-command)
192 (last-command last-command))
193 (list (vm-read-imap-folder-name "Save to IMAP folder: "
194 vm-imap-server-list t)
195 (prefix-numeric-value current-prefix-arg))))
196 ;; saving to local filesystem. argument parsing taken from old
197 ;; vm-save-message now vm-save-message-to-local-folder
199 ;; protect value of last-command
200 (let ((last-command last-command)
201 (this-command this-command))
202 (vm-follow-summary-cursor)
203 (let ((default (save-excursion
204 (vm-select-folder-buffer)
205 (vm-check-for-killed-summary)
206 (vm-error-if-folder-empty)
207 (or (vm-auto-select-folder vm-message-pointer
208 vm-auto-folder-alist)
209 vm-last-save-folder)))
210 (dir (or vm-folder-directory default-directory)))
212 (let ((default-directory dir))
213 (file-directory-p default)))
214 (vm-read-file-name "Save in folder: " dir nil nil default))
217 (format "Save in folder: (default %s) " default)
220 (vm-read-file-name "Save in folder: " dir nil)))))
221 (prefix-numeric-value current-prefix-arg))))
222 (if (and vm-imap-save-to-server
224 (vm-save-message-to-imap-folder folder count)
225 (vm-save-message-to-local-folder folder count)))
228 (defun vm-save-message-to-local-folder (folder &optional count)
229 "Save the current message to a mail folder.
230 If the folder already exists, the message will be appended to it.
232 Prefix arg COUNT means save this message and the next COUNT-1
233 messages. A negative COUNT means save this message and the
234 previous COUNT-1 messages.
236 When invoked on marked messages (via vm-next-command-uses-marks),
237 all marked messages in the current folder are saved; other messages are
240 The saved messages are flagged as `filed'."
243 ;; protect value of last-command
244 (let ((last-command last-command)
245 (this-command this-command))
246 (vm-follow-summary-cursor)
247 (let ((default (save-excursion
248 (vm-select-folder-buffer)
249 (vm-check-for-killed-summary)
250 (vm-error-if-folder-empty)
251 (or (vm-auto-select-folder vm-message-pointer
252 vm-auto-folder-alist)
253 vm-last-save-folder)))
254 (dir (or vm-folder-directory default-directory)))
256 (let ((default-directory dir))
257 (file-directory-p default)))
258 (vm-read-file-name "Save in folder: " dir nil nil default))
261 (format "Save in folder: (default %s) " default)
264 (vm-read-file-name "Save in folder: " dir nil)))))
265 (prefix-numeric-value current-prefix-arg)))
266 (let (auto-folder unexpanded-folder)
267 (vm-select-folder-buffer)
268 (vm-check-for-killed-summary)
269 (vm-error-if-folder-empty)
270 (setq unexpanded-folder folder
271 auto-folder (vm-auto-select-folder vm-message-pointer
272 vm-auto-folder-alist))
273 (vm-display nil nil '(vm-save-message) '(vm-save-message))
274 (or count (setq count 1))
275 ;; Expand the filename, forcing relative paths to resolve
276 ;; into the folder directory.
277 (let ((default-directory
278 (expand-file-name (or vm-folder-directory default-directory))))
279 (setq folder (expand-file-name folder)))
280 ;; Confirm new folders, if the user requested this.
281 (if (and vm-confirm-new-folders
282 (not (file-exists-p folder))
283 (or (not vm-visit-when-saving) (not (vm-get-file-buffer folder)))
284 (not (y-or-n-p (format "%s does not exist, save there anyway? "
286 (error "Save aborted"))
287 ;; Check and see if we are currently visiting the folder
288 ;; that the user wants to save to.
289 (if (and (not vm-visit-when-saving) (vm-get-file-buffer folder))
290 (error "Folder %s is being visited, cannot save." folder))
291 (let ((mlist (vm-select-marked-or-prefixed-messages count))
292 (coding-system-for-write
293 (if (file-exists-p folder)
294 (vm-get-file-line-ending-coding-system folder)
295 (vm-new-folder-line-ending-coding-system)))
296 (oldmodebits (and (fboundp 'default-file-modes)
297 (default-file-modes)))
298 (m nil) (count 0) folder-buffer target-type)
299 (cond ((and mlist (eq vm-visit-when-saving t))
300 (setq folder-buffer (or (vm-get-file-buffer folder)
301 ;; avoid letter bombs
302 (let ((inhibit-local-variables t)
303 (enable-local-eval nil)
304 (enable-local-variables nil))
305 (find-file-noselect folder)))))
306 ((and mlist vm-visit-when-saving)
307 (setq folder-buffer (vm-get-file-buffer folder))))
308 (if (and mlist vm-check-folder-types)
310 (setq target-type (or (vm-get-folder-type folder)
311 vm-default-folder-type
313 (vm-message-type-of (car mlist)))))
314 (if (eq target-type 'unknown)
315 (error "Folder %s's type is unrecognized" folder))))
318 (and oldmodebits (set-default-file-modes
319 vm-default-folder-permission-bits))
320 ;; if target folder is empty or nonexistent we need to
321 ;; write out the folder header first.
323 (let ((attrs (file-attributes folder)))
324 (if (or (null attrs) (= 0 (nth 7 attrs)))
325 (if (null folder-buffer)
326 (vm-write-string folder
327 (vm-folder-header target-type))
328 (vm-write-string folder-buffer
329 (vm-folder-header target-type))))))
331 (setq m (vm-real-message-of (car mlist)))
332 (set-buffer (vm-buffer-of m))
335 ;; have to stuff the attributes in all cases because
336 ;; the deleted attribute may have been stuffed
337 ;; previously and we don't want to save that attribute.
338 ;; also we don't want to save out the cached summary entry.
339 (vm-stuff-attributes m t)
340 (if (null folder-buffer)
341 (if (or (null vm-check-folder-types)
342 (eq target-type (vm-message-type-of m)))
343 (write-region (vm-start-of m)
346 (if (null vm-convert-folder-types)
347 (if (not (vm-virtual-message-p (car mlist)))
348 (error "Folder type mismatch: %s, %s"
349 (vm-message-type-of m) target-type)
350 (error "Message %s type mismatches folder %s"
351 (vm-number-of (car mlist))
353 (vm-message-type-of m)
357 (vm-leading-message-separator target-type m t))
358 (if (eq target-type 'From_-with-Content-Length)
361 (concat vm-content-length-header " "
362 (vm-su-byte-count m) "\n")))
363 (write-region (vm-headers-of m)
368 (vm-trailing-message-separator target-type))))
370 (set-buffer folder-buffer)
371 ;; if the buffer is a live VM folder
372 ;; honor vm-folder-read-only.
373 (if vm-folder-read-only
374 (signal 'folder-read-only (list (current-buffer))))
375 (let ((buffer-read-only nil))
379 (goto-char (point-max))
380 (if (or (null vm-check-folder-types)
381 (eq target-type (vm-message-type-of m)))
382 (insert-buffer-substring
384 (vm-start-of m) (vm-end-of m))
385 (if (null vm-convert-folder-types)
386 (if (not (vm-virtual-message-p (car mlist)))
387 (error "Folder type mismatch: %s, %s"
388 (vm-message-type-of m) target-type)
389 (error "Message %s type mismatches folder %s"
390 (vm-number-of (car mlist))
392 (vm-message-type-of m)
396 (vm-leading-message-separator target-type m t))
397 (if (eq target-type 'From_-with-Content-Length)
400 (concat vm-content-length-header " "
401 (vm-su-byte-count m) "\n")))
402 (insert-buffer-substring (vm-buffer-of m)
407 (vm-trailing-message-separator target-type)))))
408 ;; vars should exist and be local
409 ;; but they may have strange values,
410 ;; so check the major-mode.
411 (cond ((eq major-mode 'vm-mode)
412 (vm-increment vm-messages-not-on-disk)
413 (vm-clear-modification-flag-undos)))))))
414 (if (null (vm-filed-flag m))
415 (vm-set-filed-flag m t))
417 (vm-modify-folder-totals folder 'saved 1 m)
418 (vm-update-summary-and-mode-line)
419 (setq mlist (cdr mlist)))))
420 (and oldmodebits (set-default-file-modes oldmodebits)))
425 (set-buffer folder-buffer)
426 (if (eq major-mode 'vm-mode)
428 (vm-check-for-killed-summary)
429 (vm-assimilate-new-messages)
430 (if (null vm-message-pointer)
431 (progn (setq vm-message-pointer vm-message-list
432 vm-need-summary-pointer-update t)
433 (intern (buffer-name)
434 vm-buffers-needing-display-update)
435 (vm-preview-current-message))
436 (vm-update-summary-and-mode-line)))))
437 (message "%d message%s saved to buffer %s"
439 (if (/= 1 count) "s" "")
440 (buffer-name folder-buffer)))
441 (message "%d message%s saved to %s"
442 count (if (/= 1 count) "s" "") folder))))
443 (if (or (null vm-last-save-folder)
444 (not (equal unexpanded-folder auto-folder)))
445 (setq vm-last-save-folder unexpanded-folder))
446 (if (and vm-delete-after-saving (not vm-folder-read-only))
447 (vm-delete-message count))
451 (defun vm-save-message-sans-headers (file &optional count)
452 "Save the current message to a file, without its header section.
453 If the file already exists, the message body will be appended to it.
454 Prefix arg COUNT means save the next COUNT message bodiess. A
455 negative COUNT means save the previous COUNT bodies.
457 When invoked on marked messages (via vm-next-command-uses-marks),
458 only the next COUNT marked messages are saved; other intervening
459 messages are ignored.
461 The saved messages are flagged as `written'.
463 This command should NOT be used to save message to mail folders; use
464 vm-save-message instead (normally bound to `s')."
466 ;; protect value of last-command
467 (let ((last-command last-command)
468 (this-command this-command))
469 (vm-follow-summary-cursor)
470 (vm-select-folder-buffer)
473 (if vm-last-written-file
474 (format "Write text to file: (default %s) "
475 vm-last-written-file)
476 "Write text to file: ")
477 nil vm-last-written-file nil)
478 (prefix-numeric-value current-prefix-arg))))
479 (vm-select-folder-buffer)
480 (vm-check-for-killed-summary)
481 (vm-error-if-folder-empty)
482 (vm-display nil nil '(vm-save-message-sans-headers)
483 '(vm-save-message-sans-headers))
484 (or count (setq count 1))
485 (setq file (expand-file-name file))
486 ;; Check and see if we are currently visiting the file
487 ;; that the user wants to save to.
488 (if (and (not vm-visit-when-saving) (vm-get-file-buffer file))
489 (error "File %s is being visited, cannot save." file))
490 (let ((mlist (vm-select-marked-or-prefixed-messages count))
491 (oldmodebits (and (fboundp 'default-file-modes)
492 (default-file-modes)))
493 (coding-system-for-write
494 (vm-get-file-line-ending-coding-system file))
496 (cond ((and mlist (eq vm-visit-when-saving t))
497 (setq file-buffer (or (vm-get-file-buffer file)
498 (find-file-noselect file))))
499 ((and mlist vm-visit-when-saving)
500 (setq file-buffer (vm-get-file-buffer file))))
501 (if (and (not (memq (vm-get-folder-type file) '(nil unknown)))
502 (not (y-or-n-p "This file looks like a mail folder, append to it anyway? ")))
506 (and oldmodebits (set-default-file-modes
507 vm-default-folder-permission-bits))
509 (setq m (vm-real-message-of (car mlist)))
510 (set-buffer (vm-buffer-of m))
513 (if (null file-buffer)
514 (write-region (vm-text-of m)
517 (let ((start (vm-text-of m))
518 (end (vm-text-end-of m)))
520 (set-buffer file-buffer)
522 (let (buffer-read-only)
526 (goto-char (point-max))
527 (insert-buffer-substring
530 (if (null (vm-written-flag m))
531 (vm-set-written-flag m t))
532 (vm-update-summary-and-mode-line)
533 (setq mlist (cdr mlist)))))
534 (and oldmodebits (set-default-file-modes oldmodebits)))
537 (message "Message%s written to buffer %s" (if (/= 1 count) "s" "")
538 (buffer-name file-buffer))
539 (message "Message%s written to %s" (if (/= 1 count) "s" "") file)))
540 (setq vm-last-written-file file)))
542 (defun vm-switch-to-command-output-buffer (command buffer discard-output)
543 "Eventually switch to the output buffer of the command."
544 (let ((output-bytes (save-excursion (set-buffer buffer) (buffer-size))))
545 (if (zerop output-bytes)
546 (message "Command '%s' produced no output." command)
548 (message "Command '%s' produced %d bytes of output."
549 command output-bytes)
550 (display-buffer buffer)))))
552 (defun vm-pipe-message-part (m arg)
553 "Return (START END) bounds for piping to external command, based on ARG."
554 (cond ((equal prefix-arg '(4))
555 (list (vm-text-of m) (vm-text-end-of m)))
556 ((equal prefix-arg '(16))
557 (list (vm-headers-of m) (vm-text-of m)))
558 ((equal prefix-arg '(64))
559 (list (vm-vheaders-of m) (vm-text-end-of m)))
561 (list (vm-headers-of m) (vm-text-end-of m)))))
564 (defun vm-pipe-message-to-command (command &optional prefix-arg discard-output)
565 "Runs a shell command with contents from the current message as input.
566 By default, the entire message is used.
567 With one \\[universal-argument] the text portion of the message is used.
568 With two \\[universal-argument]'s the header portion of the message is used.
569 With three \\[universal-argument]'s the visible header portion of the message
570 plus the text portion is used.
572 When invoked on marked messages (via vm-next-command-uses-marks),
573 each marked message is successively piped to the shell command,
574 one message per command invocation.
576 Output, if any, is displayed. The message is not altered."
578 ;; protect value of last-command
579 (let ((last-command last-command)
580 (this-command this-command))
581 (vm-follow-summary-cursor)
582 (vm-select-folder-buffer)
583 (list (read-string "Pipe to command: " vm-last-pipe-command)
584 current-prefix-arg)))
585 (vm-select-folder-buffer)
586 (vm-check-for-killed-summary)
587 (vm-error-if-folder-empty)
588 (setq vm-last-pipe-command command)
589 (let ((buffer (get-buffer-create "*Shell Command Output*"))
591 (pop-up-windows (and pop-up-windows (eq vm-mutable-windows t)))
592 ;; prefix arg doesn't have "normal" meaning here, so only call
593 ;; vm-select-marked-or-prefixed-messages if we're using marks.
594 (mlist (if (eq last-command 'vm-next-command-uses-marks)
595 (vm-select-marked-or-prefixed-messages 0)
596 (list (car vm-message-pointer)))))
601 (setq m (vm-real-message-of (car mlist)))
602 (set-buffer (vm-buffer-of m))
605 (let ((pop-up-windows (and pop-up-windows (eq vm-mutable-windows t)))
606 ;; call-process-region calls write-region.
607 ;; don't let it do CR -> LF translation.
608 (selective-display nil)
609 (region (vm-pipe-message-part m prefix-arg)))
610 (call-process-region (nth 0 region) (nth 1 region)
611 (or shell-file-name "sh")
612 nil buffer nil shell-command-switch command)))
613 (setq mlist (cdr mlist)))
614 (vm-display nil nil '(vm-pipe-message-to-command)
615 '(vm-pipe-message-to-command))
616 (vm-switch-to-command-output-buffer command buffer discard-output)
619 (defun vm-pipe-message-to-command-to-string (command &optional prefix-arg)
620 "Run a shell command with contents from the current message as input.
621 This function is like `vm-pipe-message-to-command', but will not display the
622 output of the command, but return it as a string."
624 (set-buffer (vm-pipe-message-to-command command prefix-arg t))
625 (buffer-substring-no-properties (point-min) (point-max))))
628 (defun vm-pipe-message-to-command-discard-output (command &optional prefix-arg)
629 "Run a shell command with contents from the current message as input.
630 This function is like `vm-pipe-message-to-command', but will not display the
631 output of the command."
633 ;; protect value of last-command
634 (let ((last-command last-command)
635 (this-command this-command))
636 (vm-follow-summary-cursor)
637 (vm-select-folder-buffer)
638 (list (read-string "Pipe to command: " vm-last-pipe-command)
639 current-prefix-arg)))
640 (vm-pipe-message-to-command command prefix-arg t))
642 (defun vm-pipe-command-exit-handler (process discard-output
643 &optional exit-handler)
644 "Switch to output buffer of PROCESS if DISCARD-OUTPUT non-nil.
645 If non-nil call EXIT-HANDLER with the two arguments COMMAND and OUTPUT-BUFFER."
646 (let ((exit-code (process-exit-status process))
647 (buffer (process-buffer process))
648 (command (process-command process)))
649 (if (not (zerop exit-code))
650 (message "Command '%s' exit code is %d." command exit-code))
651 (vm-display nil nil '(vm-pipe-message-to-command)
652 '(vm-pipe-message-to-command))
653 (vm-switch-to-command-output-buffer command buffer discard-output)
655 (funcall exit-handler command buffer))))
657 (defvar vm-pipe-messages-to-command-start ""
658 "*Inserted by `vm-pipe-messages-to-command' before a message.")
660 (defvar vm-pipe-messages-to-command-end "\n"
661 "*Inserted by `vm-pipe-messages-to-command' after a message.")
664 (defun vm-pipe-messages-to-command (command &optional prefix-arg
665 discard-output no-wait)
666 "Run a shell command with contents from messages as input.
668 Similar to `vm-pipe-message-to-command', but it will call process
669 just once and pipe all messages to it. For bulk operations this
670 is much faster than calling the command on each message. This is
671 more like saving to a pipe.
673 Before a message it will insert `vm-pipe-messages-to-command-start'
674 and after a message `vm-pipe-messages-to-command-end'.
676 Output, if any, is displayed unless DISCARD-OUTPUT is t.
678 If NO-WAIT is t, then do not wait for process to finish, if it is
679 a function then call it with the COMMAND and OUTPUT-BUFFER as
680 arguments after the command finished."
682 ;; protect value of last-command
683 (let ((last-command last-command)
684 (this-command this-command))
685 (vm-follow-summary-cursor)
686 (vm-select-folder-buffer)
687 (list (read-string "Pipe to command: " vm-last-pipe-command)
688 current-prefix-arg)))
689 (vm-select-folder-buffer)
690 (vm-check-for-killed-summary)
691 (vm-error-if-folder-empty)
692 (setq vm-last-pipe-command command)
693 (let ((buffer (get-buffer-create "*Shell Command Output*"))
694 (pop-up-windows (and pop-up-windows (eq vm-mutable-windows t)))
695 ;; prefix arg doesn't have "normal" meaning here, so only call
696 ;; vm-select-marked-or-prefixed-messages if we're using marks.
697 (mlist (if (eq last-command 'vm-next-command-uses-marks)
698 (vm-select-marked-or-prefixed-messages 0)
699 (list (car vm-message-pointer))))
704 (setq process (start-process command buffer
705 (or shell-file-name "sh")
706 shell-command-switch command))
707 (set-process-sentinel
709 `(lambda (process status)
710 (setq status (process-status process))
711 (if (eq 'exit status)
713 (vm-pipe-command-exit-handler
714 process ,command ,discard-output
715 (if (and ,no-wait (functionp ,no-wait))
717 (message "Command '%s' changed state to %s."
720 (setq m (vm-real-message-of (car mlist)))
721 (set-buffer (vm-buffer-of m))
722 (process-send-string process vm-pipe-messages-to-command-start)
725 (let ((region (vm-pipe-message-part m prefix-arg)))
726 (process-send-region process (nth 0 region) (nth 1 region))))
727 (process-send-string process vm-pipe-messages-to-command-end)
728 (setq mlist (cdr mlist)))
730 (process-send-eof process)
733 (while (and (eq 'run (process-status process)))
734 (accept-process-output process)
736 (vm-pipe-command-exit-handler process command discard-output))
739 (defun vm-pipe-messages-to-command-to-string (command &optional prefix-arg)
740 "Runs a shell command with contents from the current message as input.
741 This function is like `vm-pipe-messages-to-command', but will not display the
742 output of the command, but return it as a string."
744 ;; protect value of last-command
745 (let ((last-command last-command)
746 (this-command this-command))
747 (vm-follow-summary-cursor)
748 (vm-select-folder-buffer)
749 (list (read-string "Pipe to command: " vm-last-pipe-command)
750 current-prefix-arg)))
752 (set-buffer (vm-pipe-messages-to-command command prefix-arg t))
753 (buffer-substring-no-properties (point-min) (point-max))))
756 (defun vm-pipe-messages-to-command-discard-output (command &optional prefix-arg)
757 "Runs a shell command with contents from the current message as input.
758 This function is like `vm-pipe-messages-to-command', but will not display the
759 output of the command."
761 ;; protect value of last-command
762 (let ((last-command last-command)
763 (this-command this-command))
764 (vm-follow-summary-cursor)
765 (vm-select-folder-buffer)
766 (list (read-string "Pipe to command: " vm-last-pipe-command)
767 current-prefix-arg)))
768 (vm-pipe-messages-to-command command prefix-arg t))
771 (defun vm-print-message (&optional count)
772 "Print the current message
773 Prefix arg N means print the current message and the next N - 1 messages.
774 Prefix arg -N means print the current message and the previous N - 1 messages.
776 The variable `vm-print-command' controls what command is run to
777 print the message, and `vm-print-command-switches' is a list of switches
778 to pass to the command.
780 When invoked on marked messages (via vm-next-command-uses-marks),
781 each marked message is printed, one message per vm-print-command invocation.
783 Output, if any, is displayed. The message is not altered."
785 (vm-follow-summary-cursor)
786 (vm-select-folder-buffer)
787 (vm-check-for-killed-summary)
788 (vm-error-if-folder-empty)
789 (or count (setq count 1))
790 (let* ((buffer (get-buffer-create "*Shell Command Output*"))
791 (need-tempfile (string-match ".*-.*-\\(win95\\|nt\\)"
792 system-configuration))
793 (tempfile (if need-tempfile (vm-make-tempfile-name)))
794 (command (mapconcat (function identity)
795 (nconc (list vm-print-command)
796 (copy-sequence vm-print-command-switches)
801 (pop-up-windows (and pop-up-windows (eq vm-mutable-windows t)))
802 (mlist (vm-select-marked-or-prefixed-messages count)))
807 (setq m (vm-real-message-of (car mlist)))
808 (set-buffer (vm-buffer-of m))
809 (if (and vm-display-using-mime (vectorp (vm-mm-layout m)))
810 (let ((work-buffer nil))
813 (setq work-buffer (vm-make-work-buffer))
814 (set-buffer work-buffer)
815 (vm-insert-region-from-buffer
816 (vm-buffer-of m) (vm-vheaders-of m) (vm-text-of m))
817 (vm-decode-mime-encoded-words)
818 (goto-char (point-max))
819 (let ((vm-auto-displayed-mime-content-types
820 '("text" "multipart"))
821 (vm-mime-internal-content-types
822 '("text" "multipart"))
823 (vm-mime-external-content-types-alist nil))
824 (vm-decode-mime-layout (vm-mm-layout m)))
825 (let ((pop-up-windows (and pop-up-windows
826 (eq vm-mutable-windows t)))
827 ;; call-process-region calls write-region.
828 ;; don't let it do CR -> LF translation.
829 (selective-display nil))
831 (write-region (point-min) (point-max)
833 (call-process-region (point-min) (point-max)
834 (or shell-file-name "sh")
836 shell-command-switch command)
838 (vm-error-free-call 'delete-file tempfile))))
839 (and work-buffer (kill-buffer work-buffer))))
842 (narrow-to-region (vm-vheaders-of m) (vm-text-end-of m))
843 (let ((pop-up-windows (and pop-up-windows
844 (eq vm-mutable-windows t)))
845 ;; call-process-region calls write-region.
846 ;; don't let it do CR -> LF translation.
847 (selective-display nil))
849 (write-region (point-min) (point-max)
851 (call-process-region (point-min) (point-max)
852 (or shell-file-name "sh")
854 shell-command-switch command)
856 (vm-error-free-call 'delete-file tempfile)))))
857 (setq mlist (cdr mlist)))
858 (vm-display nil nil '(vm-print-message) '(vm-print-message))
859 (vm-switch-to-command-output-buffer command buffer nil)))
862 (defun vm-save-message-to-imap-folder (folder &optional count)
863 "Save the current message to an IMAP folder.
864 Prefix arg COUNT means save this message and the next COUNT-1
865 messages. A negative COUNT means save this message and the
866 previous COUNT-1 messages.
868 When invoked on marked messages (via vm-next-command-uses-marks),
869 all marked messages in the current folder are saved; other messages are
872 The saved messages are flagged as `filed'."
875 (vm-session-initialization)
876 (vm-check-for-killed-folder)
877 (vm-select-folder-buffer-if-possible)
878 (let ((this-command this-command)
879 (last-command last-command))
880 (list (vm-read-imap-folder-name "Save to IMAP folder: "
881 vm-imap-server-list t)
882 (prefix-numeric-value current-prefix-arg)))))
883 (vm-select-folder-buffer)
884 (vm-check-for-killed-summary)
885 (vm-error-if-folder-empty)
886 (vm-display nil nil '(vm-save-message-to-imap-folder)
887 '(vm-save-message-to-imap-folder))
888 (or count (setq count 1))
889 (let ((mlist (vm-select-marked-or-prefixed-messages count))
891 (mailbox (nth 3 (vm-imap-parse-spec-to-list folder)))
895 (setq process (vm-imap-make-session folder))
896 (set-buffer (process-buffer process))
899 (vm-imap-save-message process m mailbox)
900 (when (null (vm-filed-flag m))
901 (vm-set-filed-flag m t))
903 (vm-modify-folder-totals folder 'saved 1 m)
904 (setq mlist (cdr mlist))))
905 (and process (vm-imap-end-session process)))
906 (vm-update-summary-and-mode-line)
907 (message "%d message%s saved to %s"
908 count (if (/= 1 count) "s" "")
909 (vm-safe-imapdrop-string folder))
910 (when (and vm-delete-after-saving (not vm-folder-read-only))
911 (vm-delete-message count))
916 ;;; vm-save.el ends here