comparison lisp/mh-e/mh-utils.el @ 50702:7dd3d5eae9c7

Upgraded to MH-E version 7.3. See etc/MH-E-NEWS and lisp/mh-e/ChangeLog for details.
author Bill Wohler <wohler@newt.com>
date Fri, 25 Apr 2003 05:52:00 +0000
parents 0d8b17d428b5
children 695cf19ef79e
comparison
equal deleted inserted replaced
50701:cb5f0a5d5b36 50702:7dd3d5eae9c7
1 ;;; mh-utils.el --- MH-E code needed for both sending and reading 1 ;;; mh-utils.el --- MH-E code needed for both sending and reading
2 2
3 ;; Copyright (C) 1993, 1995, 1997, 2000, 2001, 2002 Free Software Foundation, Inc. 3 ;; Copyright (C) 1993, 95, 1997,
4 ;; 2000, 01, 02, 2003 Free Software Foundation, Inc.
4 5
5 ;; Author: Bill Wohler <wohler@newt.com> 6 ;; Author: Bill Wohler <wohler@newt.com>
6 ;; Maintainer: Bill Wohler <wohler@newt.com> 7 ;; Maintainer: Bill Wohler <wohler@newt.com>
7 ;; Keywords: mail 8 ;; Keywords: mail
8 ;; See: mh-e.el 9 ;; See: mh-e.el
27 ;;; Commentary: 28 ;;; Commentary:
28 29
29 ;; Internal support for MH-E package. 30 ;; Internal support for MH-E package.
30 31
31 ;;; Change Log: 32 ;;; Change Log:
32
33 ;; $Id: mh-utils.el,v 1.2 2003/02/03 20:55:30 wohler Exp $
34 33
35 ;;; Code: 34 ;;; Code:
36 35
37 ;; Is this XEmacs-land? Located here since needed by mh-customize.el. 36 ;; Is this XEmacs-land? Located here since needed by mh-customize.el.
38 (defvar mh-xemacs-flag (featurep 'xemacs) 37 (defvar mh-xemacs-flag (featurep 'xemacs)
55 (defvar mark-active) 54 (defvar mark-active)
56 (defvar tool-bar-mode) 55 (defvar tool-bar-mode)
57 56
58 ;;; Autoloads 57 ;;; Autoloads
59 (autoload 'gnus-article-highlight-citation "gnus-cite") 58 (autoload 'gnus-article-highlight-citation "gnus-cite")
60 (autoload 'mail-header-end "sendmail") 59 (require 'sendmail)
61 (autoload 'Info-goto-node "info") 60 (autoload 'Info-goto-node "info")
62 (unless (fboundp 'make-hash-table) 61 (unless (fboundp 'make-hash-table)
63 (autoload 'make-hash-table "cl")) 62 (autoload 'make-hash-table "cl"))
64 63
65 ;;; Set for local environment: 64 ;;; Set for local environment:
98 of `search' in the CL package." 97 of `search' in the CL package."
99 (loop for index from (1- (length string)) downto 0 98 (loop for index from (1- (length string)) downto 0
100 when (equal (aref string index) char) return index 99 when (equal (aref string index) char) return index
101 finally return nil)) 100 finally return nil))
102 101
103 ;;; Macro to generate correct code for different emacs variants 102 ;;; Macros to generate correct code for different emacs variants
103
104 (defmacro mh-do-in-gnu-emacs (&rest body)
105 "Execute BODY if in GNU Emacs."
106 (unless mh-xemacs-flag `(progn ,@body)))
107 (put 'mh-do-in-gnu-emacs 'lisp-indent-hook 'defun)
108
109 (defmacro mh-do-in-xemacs (&rest body)
110 "Execute BODY if in GNU Emacs."
111 (when mh-xemacs-flag `(progn ,@body)))
112 (put 'mh-do-in-xemacs 'lisp-indent-hook 'defun)
113
114 (defmacro mh-funcall-if-exists (function &rest args)
115 "Call FUNCTION with ARGS as parameters if it exists."
116 (if (fboundp function)
117 `(funcall ',function ,@args)))
118
119 (defmacro mh-make-local-hook (hook)
120 "Make HOOK local if needed.
121 XEmacs and versions of GNU Emacs before 21.1 require `make-local-hook' to be
122 called."
123 (when (and (fboundp 'make-local-hook)
124 (not (get 'make-local-hook 'byte-obsolete-info)))
125 `(make-local-hook ,hook)))
104 126
105 (defmacro mh-mark-active-p (check-transient-mark-mode-flag) 127 (defmacro mh-mark-active-p (check-transient-mark-mode-flag)
106 "A macro that expands into appropriate code in XEmacs and nil in GNU Emacs. 128 "A macro that expands into appropriate code in XEmacs and nil in GNU Emacs.
107 In GNU Emacs if CHECK-TRANSIENT-MARK-MODE-FLAG is non-nil then check if 129 In GNU Emacs if CHECK-TRANSIENT-MARK-MODE-FLAG is non-nil then check if
108 variable `transient-mark-mode' is active." 130 variable `transient-mark-mode' is active."
285 ("image/.*" ignore ignore) 307 ("image/.*" ignore ignore)
286 ;; Default to displaying as text 308 ;; Default to displaying as text
287 (".*" mm-inline-text mm-readable-p)) 309 (".*" mm-inline-text mm-readable-p))
288 "Alist of media types/tests saying whether types can be displayed inline.") 310 "Alist of media types/tests saying whether types can be displayed inline.")
289 311
290 ;; Needed by mh-comp.el and mh-mime.el
291 (defvar mh-mhn-compose-insert-flag nil
292 "Non-nil means MIME insertion was done.
293 Triggers an automatic call to `mh-edit-mhn' in `mh-send-letter'.
294 This variable is buffer-local.")
295 (make-variable-buffer-local 'mh-mhn-compose-insert-flag)
296
297 (defvar mh-mml-compose-insert-flag nil
298 "Non-nil means that a MIME insertion was done.
299 This buffer-local variable is used to remember if a MIME insertion was done.
300 Triggers an automatic call to `mh-mml-to-mime' in `mh-send-letter'.")
301 (make-variable-buffer-local 'mh-mml-compose-insert-flag)
302
303 ;; Copy of `goto-address-mail-regexp' 312 ;; Copy of `goto-address-mail-regexp'
304 (defvar mh-address-mail-regexp 313 (defvar mh-address-mail-regexp
305 "[-a-zA-Z0-9._]+@[-a-zA-z0-9_]+\\.+[a-zA-Z0-9]+" 314 "[-a-zA-Z0-9._]+@[-a-zA-z0-9_]+\\.+[a-zA-Z0-9]+"
306 "A regular expression probably matching an e-mail address.") 315 "A regular expression probably matching an e-mail address.")
307 316
316 (and (re-search-forward mh-address-mail-regexp 325 (and (re-search-forward mh-address-mail-regexp
317 (line-end-position) 'lim) 326 (line-end-position) 'lim)
318 (goto-char (match-beginning 0)))) 327 (goto-char (match-beginning 0))))
319 (match-string-no-properties 0))) 328 (match-string-no-properties 0)))
320 329
330 (defun mh-mail-header-end ()
331 "Substitute for `mail-header-end' that doesn't widen the buffer.
332 In MH-E we frequently need to find the end of headers in nested messages, where
333 the buffer has been narrowed. This function works in this situation."
334 (save-excursion
335 (rfc822-goto-eoh)
336 (point)))
337
321 (defun mh-in-header-p () 338 (defun mh-in-header-p ()
322 "Return non-nil if the point is in the header of a draft message." 339 "Return non-nil if the point is in the header of a draft message."
323 (< (point) (mail-header-end))) 340 (< (point) (mh-mail-header-end)))
324 341
325 (defun mh-header-field-beginning () 342 (defun mh-header-field-beginning ()
326 "Move to the beginning of the current header field. 343 "Move to the beginning of the current header field.
327 Handles RFC 822 continuation lines." 344 Handles RFC 822 continuation lines."
328 (beginning-of-line) 345 (beginning-of-line)
340 (defun mh-letter-header-font-lock (limit) 357 (defun mh-letter-header-font-lock (limit)
341 "Return the entire mail header to font-lock. 358 "Return the entire mail header to font-lock.
342 Argument LIMIT limits search." 359 Argument LIMIT limits search."
343 (if (= (point) limit) 360 (if (= (point) limit)
344 nil 361 nil
345 (let* ((mail-header-end (save-match-data (mail-header-end))) 362 (let* ((mail-header-end (save-match-data (mh-mail-header-end)))
346 (lesser-limit (if (< mail-header-end limit) mail-header-end limit))) 363 (lesser-limit (if (< mail-header-end limit) mail-header-end limit)))
347 (when (mh-in-header-p) 364 (when (mh-in-header-p)
348 (set-match-data (list 1 lesser-limit)) 365 (set-match-data (list 1 lesser-limit))
349 (goto-char lesser-limit) 366 (goto-char lesser-limit)
350 t)))) 367 t))))
352 (defun mh-header-field-font-lock (field limit) 369 (defun mh-header-field-font-lock (field limit)
353 "Return the value of a header field FIELD to font-lock. 370 "Return the value of a header field FIELD to font-lock.
354 Argument LIMIT limits search." 371 Argument LIMIT limits search."
355 (if (= (point) limit) 372 (if (= (point) limit)
356 nil 373 nil
357 (let* ((mail-header-end (mail-header-end)) 374 (let* ((mail-header-end (mh-mail-header-end))
358 (lesser-limit (if (< mail-header-end limit) mail-header-end limit)) 375 (lesser-limit (if (< mail-header-end limit) mail-header-end limit))
359 (case-fold-search t)) 376 (case-fold-search t))
360 (when (and (< (point) mail-header-end) ;Only within header 377 (when (and (< (point) mail-header-end) ;Only within header
361 (re-search-forward (format "^%s" field) lesser-limit t)) 378 (re-search-forward (format "^%s" field) lesser-limit t))
362 (let ((match-one-b (match-beginning 0)) 379 (let ((match-one-b (match-beginning 0))
422 "Limit font-lock in `mh-show-mode' to the header. 439 "Limit font-lock in `mh-show-mode' to the header.
423 Used when `mh-highlight-citation-p' is set to gnus, leaving the body to be 440 Used when `mh-highlight-citation-p' is set to gnus, leaving the body to be
424 dealt with by gnus highlighting. The region between BEG and END is 441 dealt with by gnus highlighting. The region between BEG and END is
425 given over to be fontified and LOUDLY controls if a user sees a 442 given over to be fontified and LOUDLY controls if a user sees a
426 message about the fontification operation." 443 message about the fontification operation."
427 (let ((header-end (mail-header-end))) 444 (let ((header-end (mh-mail-header-end)))
428 (cond 445 (cond
429 ((and (< beg header-end)(< end header-end)) 446 ((and (< beg header-end)(< end header-end))
430 (font-lock-default-fontify-region beg end loudly)) 447 (font-lock-default-fontify-region beg end loudly))
431 ((and (< beg header-end)(>= end header-end)) 448 ((and (< beg header-end)(>= end header-end))
432 (font-lock-default-fontify-region beg header-end loudly)) 449 (font-lock-default-fontify-region beg header-end loudly))
499 (defconst mh-folders-buffer "*MH-E Folders*") ;folder list 516 (defconst mh-folders-buffer "*MH-E Folders*") ;folder list
500 (defconst mh-info-buffer "*MH-E Info*") ;version information buffer 517 (defconst mh-info-buffer "*MH-E Info*") ;version information buffer
501 (defconst mh-log-buffer "*MH-E Log*") ;output of MH commands and so on 518 (defconst mh-log-buffer "*MH-E Log*") ;output of MH commands and so on
502 (defconst mh-recipients-buffer "*MH-E Recipients*") ;killed when draft sent 519 (defconst mh-recipients-buffer "*MH-E Recipients*") ;killed when draft sent
503 (defconst mh-sequences-buffer "*MH-E Sequences*") ;sequences list 520 (defconst mh-sequences-buffer "*MH-E Sequences*") ;sequences list
521 (defconst mh-mail-delivery-buffer "*MH-E Mail Delivery*") ;mail delivery log
522
523 ;; Number of lines to keep in mh-log-buffer.
524 (defvar mh-log-buffer-lines 100)
504 525
505 ;; Window configuration before MH-E command. 526 ;; Window configuration before MH-E command.
506 (defvar mh-previous-window-config nil) 527 (defvar mh-previous-window-config nil)
507 528
508 ;;Non-nil means next SPC or whatever goes to next undeleted message. 529 ;;Non-nil means next SPC or whatever goes to next undeleted message.
533 554
534 (defvar mh-logo-cache nil) 555 (defvar mh-logo-cache nil)
535 556
536 (defun mh-logo-display () 557 (defun mh-logo-display ()
537 "Modify mode line to display MH-E logo." 558 "Modify mode line to display MH-E logo."
538 (when (fboundp 'find-image) 559 (mh-do-in-gnu-emacs
539 (add-text-properties 560 (add-text-properties
540 0 2 561 0 2
541 `(display ,(or mh-logo-cache 562 `(display ,(or mh-logo-cache
542 (setq mh-logo-cache 563 (setq mh-logo-cache
543 (find-image '((:type xpm :ascent center 564 (mh-funcall-if-exists
544 :file "mh-logo.xpm")))))) 565 find-image '((:type xpm :ascent center
545 (car mode-line-buffer-identification)))) 566 :file "mh-logo.xpm"))))))
567 (car mode-line-buffer-identification)))
568 (mh-do-in-xemacs
569 (setq modeline-buffer-identification
570 (list
571 (if mh-modeline-glyph
572 (cons modeline-buffer-id-left-extent mh-modeline-glyph)
573 (cons modeline-buffer-id-left-extent "XEmacs%N:"))
574 (cons modeline-buffer-id-right-extent " %17b")))))
575
546 576
547 ;;; This holds a documentation string used by describe-mode. 577 ;;; This holds a documentation string used by describe-mode.
548 (defun mh-showing-mode (&optional arg) 578 (defun mh-showing-mode (&optional arg)
549 "Change whether messages should be displayed. 579 "Change whether messages should be displayed.
550 With arg, display messages iff ARG is positive." 580 With arg, display messages iff ARG is positive."
583 ,@body) 613 ,@body)
584 (mh-set-folder-modified-p mh-folder-updating-mod-flag))) 614 (mh-set-folder-modified-p mh-folder-updating-mod-flag)))
585 ,@(if (not save-modification-flag) 615 ,@(if (not save-modification-flag)
586 '((mh-set-folder-modified-p nil))))) 616 '((mh-set-folder-modified-p nil)))))
587 617
588 (put 'with-mh-folder-updating 'lisp-indent-hook 1) 618 (put 'with-mh-folder-updating 'lisp-indent-hook 'defun)
589 619
590 (defmacro mh-in-show-buffer (show-buffer &rest body) 620 (defmacro mh-in-show-buffer (show-buffer &rest body)
591 "Format is (mh-in-show-buffer (SHOW-BUFFER) &body BODY). 621 "Format is (mh-in-show-buffer (SHOW-BUFFER) &body BODY).
592 Display buffer SHOW-BUFFER in other window and execute BODY in it. 622 Display buffer SHOW-BUFFER in other window and execute BODY in it.
593 Stronger than `save-excursion', weaker than `save-window-excursion'." 623 Stronger than `save-excursion', weaker than `save-window-excursion'."
598 (unwind-protect 628 (unwind-protect
599 (progn 629 (progn
600 ,@body) 630 ,@body)
601 (select-window mh-in-show-buffer-saved-window)))) 631 (select-window mh-in-show-buffer-saved-window))))
602 632
603 (put 'mh-in-show-buffer 'lisp-indent-hook 1) 633 (put 'mh-in-show-buffer 'lisp-indent-hook 'defun)
604 634
605 (defmacro mh-make-seq (name msgs) 635 (defmacro mh-make-seq (name msgs)
606 "Create sequence NAME with the given MSGS." 636 "Create sequence NAME with the given MSGS."
607 (list 'cons name msgs)) 637 (list 'cons name msgs))
608 638
724 (unless (equal (buffer-name 754 (unless (equal (buffer-name
725 (window-buffer (frame-first-window (selected-frame)))) 755 (window-buffer (frame-first-window (selected-frame))))
726 folder-buffer) 756 folder-buffer)
727 (delete-other-windows)) 757 (delete-other-windows))
728 (mh-goto-cur-msg t) 758 (mh-goto-cur-msg t)
729 (and (fboundp 'deactivate-mark) (deactivate-mark)) 759 (mh-funcall-if-exists deactivate-mark)
730 (unwind-protect 760 (unwind-protect
731 (prog1 (call-interactively (function ,original-function)) 761 (prog1 (call-interactively (function ,original-function))
732 (setq normal-exit t)) 762 (setq normal-exit t))
733 (and (fboundp 'deactivate-mark) (deactivate-mark)) 763 (mh-funcall-if-exists deactivate-mark)
734 (cond ((not normal-exit) 764 (cond ((not normal-exit)
735 (set-window-configuration config)) 765 (set-window-configuration config))
736 ,(if dont-return 766 ,(if dont-return
737 `(t (setq mh-previous-window-config config)) 767 `(t (setq mh-previous-window-config config))
738 `((and (get-buffer cur-buffer-name) 768 `((and (get-buffer cur-buffer-name)
817 (mh-defun-show-buffer mh-show-thread-ancestor mh-thread-ancestor) 847 (mh-defun-show-buffer mh-show-thread-ancestor mh-thread-ancestor)
818 (mh-defun-show-buffer mh-show-thread-next-sibling mh-thread-next-sibling) 848 (mh-defun-show-buffer mh-show-thread-next-sibling mh-thread-next-sibling)
819 (mh-defun-show-buffer mh-show-thread-previous-sibling 849 (mh-defun-show-buffer mh-show-thread-previous-sibling
820 mh-thread-previous-sibling) 850 mh-thread-previous-sibling)
821 (mh-defun-show-buffer mh-show-index-visit-folder mh-index-visit-folder t) 851 (mh-defun-show-buffer mh-show-index-visit-folder mh-index-visit-folder t)
852 (mh-defun-show-buffer mh-show-toggle-tick mh-toggle-tick)
853 (mh-defun-show-buffer mh-show-narrow-to-tick mh-narrow-to-tick)
854 (mh-defun-show-buffer mh-show-junk-blacklist mh-junk-blacklist)
855 (mh-defun-show-buffer mh-show-junk-whitelist mh-junk-whitelist)
856 (mh-defun-show-buffer mh-show-index-new-messages mh-index-new-messages)
822 857
823 ;;; Populate mh-show-mode-map 858 ;;; Populate mh-show-mode-map
824 (gnus-define-keys mh-show-mode-map 859 (gnus-define-keys mh-show-mode-map
825 " " mh-show-page-msg 860 " " mh-show-page-msg
826 "!" mh-show-refile-or-write-again 861 "!" mh-show-refile-or-write-again
862 "'" mh-show-toggle-tick
827 "," mh-show-header-display 863 "," mh-show-header-display
828 "." mh-show-show 864 "." mh-show-show
829 ">" mh-show-write-message-to-file 865 ">" mh-show-write-message-to-file
830 "?" mh-help 866 "?" mh-help
831 "E" mh-show-extract-rejected-mail 867 "E" mh-show-extract-rejected-mail
865 "S" mh-show-sort-folder 901 "S" mh-show-sort-folder
866 "f" mh-show-visit-folder 902 "f" mh-show-visit-folder
867 "i" mh-index-search 903 "i" mh-index-search
868 "k" mh-show-kill-folder 904 "k" mh-show-kill-folder
869 "l" mh-show-list-folders 905 "l" mh-show-list-folders
906 "n" mh-index-new-messages
870 "o" mh-show-visit-folder 907 "o" mh-show-visit-folder
871 "r" mh-show-rescan-folder 908 "r" mh-show-rescan-folder
872 "s" mh-show-search-folder 909 "s" mh-show-search-folder
873 "t" mh-show-toggle-threads 910 "t" mh-show-toggle-threads
874 "u" mh-show-undo-folder 911 "u" mh-show-undo-folder
882 "n" mh-show-narrow-to-seq 919 "n" mh-show-narrow-to-seq
883 "p" mh-show-put-msg-in-seq 920 "p" mh-show-put-msg-in-seq
884 "s" mh-show-msg-is-in-seq 921 "s" mh-show-msg-is-in-seq
885 "w" mh-show-widen) 922 "w" mh-show-widen)
886 923
924 (define-key mh-show-mode-map "I" mh-inc-spool-map)
925
926 (gnus-define-keys (mh-show-junk-map "J" mh-show-mode-map)
927 "?" mh-prefix-help
928 "b" mh-show-junk-blacklist
929 "w" mh-show-junk-whitelist)
930
887 (gnus-define-keys (mh-show-thread-map "T" mh-show-mode-map) 931 (gnus-define-keys (mh-show-thread-map "T" mh-show-mode-map)
888 "?" mh-prefix-help 932 "?" mh-prefix-help
889 "u" mh-show-thread-ancestor 933 "u" mh-show-thread-ancestor
890 "p" mh-show-thread-previous-sibling 934 "p" mh-show-thread-previous-sibling
891 "n" mh-show-thread-next-sibling 935 "n" mh-show-thread-next-sibling
892 "t" mh-show-toggle-threads 936 "t" mh-show-toggle-threads
893 "d" mh-show-thread-delete 937 "d" mh-show-thread-delete
894 "o" mh-show-thread-refile) 938 "o" mh-show-thread-refile)
895 939
896 (gnus-define-keys (mh-show-limit-map "/" mh-show-mode-map) 940 (gnus-define-keys (mh-show-limit-map "/" mh-show-mode-map)
941 "'" mh-show-narrow-to-tick
897 "?" mh-prefix-help 942 "?" mh-prefix-help
898 "s" mh-show-narrow-to-subject 943 "s" mh-show-narrow-to-subject
899 "w" mh-show-widen) 944 "w" mh-show-widen)
900 945
901 (gnus-define-keys (mh-show-extract-map "X" mh-show-mode-map) 946 (gnus-define-keys (mh-show-extract-map "X" mh-show-mode-map)
930 ["Delete Sequence..." mh-show-delete-seq t] 975 ["Delete Sequence..." mh-show-delete-seq t]
931 ["Narrow to Sequence..." mh-show-narrow-to-seq t] 976 ["Narrow to Sequence..." mh-show-narrow-to-seq t]
932 ["Widen from Sequence" mh-show-widen t] 977 ["Widen from Sequence" mh-show-widen t]
933 "--" 978 "--"
934 ["Narrow to Subject Sequence" mh-show-narrow-to-subject t] 979 ["Narrow to Subject Sequence" mh-show-narrow-to-subject t]
980 ["Narrow to Tick Sequence" mh-show-narrow-to-tick
981 (save-excursion
982 (set-buffer mh-show-folder-buffer)
983 (and mh-tick-seq (mh-seq-msgs (mh-find-seq mh-tick-seq))))]
935 ["Delete Rest of Same Subject" mh-show-delete-subject t] 984 ["Delete Rest of Same Subject" mh-show-delete-subject t]
985 ["Toggle Tick Mark" mh-show-toggle-tick t]
936 "--" 986 "--"
937 ["Push State Out to MH" mh-show-update-sequences t])) 987 ["Push State Out to MH" mh-show-update-sequences t]))
938 988
939 (easy-menu-define 989 (easy-menu-define
940 mh-show-message-menu mh-show-mode-map "Menu for MH-E folder-message." 990 mh-show-message-menu mh-show-mode-map "Menu for MH-E folder-message."
977 ["Pack Folder" mh-show-pack-folder t] 1027 ["Pack Folder" mh-show-pack-folder t]
978 ["Sort Folder" mh-show-sort-folder t] 1028 ["Sort Folder" mh-show-sort-folder t]
979 "--" 1029 "--"
980 ["List Folders" mh-show-list-folders t] 1030 ["List Folders" mh-show-list-folders t]
981 ["Visit a Folder..." mh-show-visit-folder t] 1031 ["Visit a Folder..." mh-show-visit-folder t]
1032 ["View New Messages" mh-show-index-new-messages t]
982 ["Search a Folder..." mh-show-search-folder t] 1033 ["Search a Folder..." mh-show-search-folder t]
983 ["Indexed Search..." mh-index-search t] 1034 ["Indexed Search..." mh-index-search t]
984 "--" 1035 "--"
985 ["Quit MH-E" mh-quit t])) 1036 ["Quit MH-E" mh-quit t]))
986 1037
987 1038
988 ;;; Ensure new buffers won't get this mode if default-major-mode is nil. 1039 ;;; Ensure new buffers won't get this mode if default-major-mode is nil.
989 (put 'mh-show-mode 'mode-class 'special) 1040 (put 'mh-show-mode 'mode-class 'special)
1041
1042 ;; Avoid compiler warning
1043 (defvar tool-bar-map)
990 1044
991 (define-derived-mode mh-show-mode text-mode "MH-Show" 1045 (define-derived-mode mh-show-mode text-mode "MH-Show"
992 "Major mode for showing messages in MH-E.\\<mh-show-mode-map> 1046 "Major mode for showing messages in MH-E.\\<mh-show-mode-map>
993 The value of `mh-show-mode-hook' is a list of functions to 1047 The value of `mh-show-mode-hook' is a list of functions to
994 be called, with no arguments, upon entry to this mode." 1048 be called, with no arguments, upon entry to this mode."
1013 (if (and mh-xemacs-flag 1067 (if (and mh-xemacs-flag
1014 font-lock-auto-fontify) 1068 font-lock-auto-fontify)
1015 (turn-on-font-lock)) 1069 (turn-on-font-lock))
1016 (if (and (boundp 'tool-bar-mode) tool-bar-mode) 1070 (if (and (boundp 'tool-bar-mode) tool-bar-mode)
1017 (set (make-local-variable 'tool-bar-map) mh-show-tool-bar-map)) 1071 (set (make-local-variable 'tool-bar-map) mh-show-tool-bar-map))
1072 (mh-funcall-if-exists mh-toolbar-init :show)
1018 (when mh-decode-mime-flag 1073 (when mh-decode-mime-flag
1074 (mh-make-local-hook 'kill-buffer-hook)
1019 (add-hook 'kill-buffer-hook 'mh-mime-cleanup nil t)) 1075 (add-hook 'kill-buffer-hook 'mh-mime-cleanup nil t))
1020 (easy-menu-add mh-show-sequence-menu) 1076 (easy-menu-add mh-show-sequence-menu)
1021 (easy-menu-add mh-show-message-menu) 1077 (easy-menu-add mh-show-message-menu)
1022 (easy-menu-add mh-show-folder-menu) 1078 (easy-menu-add mh-show-folder-menu)
1023 (make-local-variable 'mh-show-folder-buffer) 1079 (make-local-variable 'mh-show-folder-buffer)
1032 (if (not (featurep 'goto-addr)) 1088 (if (not (featurep 'goto-addr))
1033 (load "goto-addr" t t)) 1089 (load "goto-addr" t t))
1034 (if (fboundp 'goto-address) 1090 (if (fboundp 'goto-address)
1035 (goto-address)))) 1091 (goto-address))))
1036 1092
1093
1094
1095 ;; X-Face and Face display
1037 (defvar mh-show-xface-function 1096 (defvar mh-show-xface-function
1038 (cond ((and mh-xemacs-flag (locate-library "x-face")) 1097 (cond ((and mh-xemacs-flag (locate-library "x-face") (not (featurep 'xface)))
1039 (load "x-face" t t) 1098 (load "x-face" t t)
1040 (if (fboundp 'x-face-xmas-wl-display-x-face) 1099 #'mh-face-display-function)
1041 #'x-face-xmas-wl-display-x-face 1100 ((>= emacs-major-version 21)
1042 #'ignore)) 1101 #'mh-face-display-function)
1043 ((and (not mh-xemacs-flag) (>= emacs-major-version 21))
1044 (load "x-face-e21" t t)
1045 (if (fboundp 'x-face-decode-message-header)
1046 #'x-face-decode-message-header
1047 #'ignore))
1048 (t #'ignore)) 1102 (t #'ignore))
1049 "Determine at run time what function should be called to display X-Face.") 1103 "Determine at run time what function should be called to display X-Face.")
1050 1104
1105 (defvar mh-uncompface-executable
1106 (and (fboundp 'executable-find) (executable-find "uncompface")))
1107
1108 (defun mh-face-to-png (data)
1109 "Convert base64 encoded DATA to png image."
1110 (with-temp-buffer
1111 (insert data)
1112 (ignore-errors (base64-decode-region (point-min) (point-max)))
1113 (buffer-string)))
1114
1115 (defun mh-uncompface (data)
1116 "Run DATA through `uncompface' to generate bitmap."
1117 (with-temp-buffer
1118 (insert data)
1119 (when (and mh-uncompface-executable
1120 (equal (call-process-region (point-min) (point-max)
1121 mh-uncompface-executable t '(t nil))
1122 0))
1123 (mh-icontopbm)
1124 (buffer-string))))
1125
1126 (defun mh-icontopbm ()
1127 "Elisp substitute for `icontopbm'."
1128 (goto-char (point-min))
1129 (let ((end (point-max)))
1130 (while (re-search-forward "0x\\(..\\)\\(..\\)," nil t)
1131 (save-excursion
1132 (goto-char (point-max))
1133 (insert (string-to-number (match-string 1) 16))
1134 (insert (string-to-number (match-string 2) 16))))
1135 (delete-region (point-min) end)
1136 (goto-char (point-min))
1137 (insert "P4\n48 48\n")))
1138
1139 (mh-do-in-xemacs (defvar default-enable-multibyte-characters))
1140
1141 (defun mh-face-display-function ()
1142 "Display a Face or X-Face header field.
1143 Display Face if both are present."
1144 (save-restriction
1145 (goto-char (point-min))
1146 (re-search-forward "\n\n" (point-max) t)
1147 (narrow-to-region (point-min) (point))
1148 (let* ((case-fold-search t)
1149 (default-enable-multibyte-characters nil)
1150 (face (message-fetch-field "face" t))
1151 (x-face (message-fetch-field "x-face" t))
1152 (url (message-fetch-field "x-image-url" t))
1153 raw type)
1154 (cond (face (setq raw (mh-face-to-png face)
1155 type 'png))
1156 (x-face (setq raw (mh-uncompface x-face)
1157 type 'pbm))
1158 (url (setq type 'url)))
1159 (when type
1160 (goto-char (point-min))
1161 (when (re-search-forward "^from:" (point-max) t)
1162 ;; GNU Emacs
1163 (mh-do-in-gnu-emacs
1164 (if (eq type 'url)
1165 (mh-x-image-url-display url)
1166 (mh-funcall-if-exists
1167 insert-image (create-image
1168 raw type t
1169 :foreground (face-foreground 'mh-show-xface-face)
1170 :background (face-background 'mh-show-xface-face))
1171 " ")))
1172 ;; XEmacs
1173 (mh-do-in-xemacs
1174 (cond
1175 ((eq type 'url)
1176 (mh-x-image-url-display url))
1177 ((eq type 'png)
1178 (when (featurep 'png)
1179 (set-extent-begin-glyph
1180 (make-extent (point) (point))
1181 (make-glyph (vector 'png ':data (mh-face-to-png face))))))
1182 ;; Try internal xface support if available...
1183 ((and (eq type 'pbm) (featurep 'xface))
1184 (set-glyph-face
1185 (set-extent-begin-glyph
1186 (make-extent (point) (point))
1187 (make-glyph (vector 'xface ':data (concat "X-Face: " x-face))))
1188 'mh-show-xface-face))
1189 ;; Otherwise try external support with x-face...
1190 ((and (eq type 'pbm)
1191 (fboundp 'x-face-xmas-wl-display-x-face)
1192 (fboundp 'executable-find) (executable-find "uncompface"))
1193 (mh-funcall-if-exists x-face-xmas-wl-display-x-face)))
1194 (when raw (insert " "))))))))
1195
1196
1051 (defun mh-show-xface () 1197 (defun mh-show-xface ()
1052 "Display X-Face." 1198 "Display X-Face."
1053 (when (and mh-show-use-xface-flag 1199 (when (and window-system mh-show-use-xface-flag
1054 (or mh-decode-mime-flag mhl-formfile 1200 (or mh-decode-mime-flag mhl-formfile
1055 mh-clean-message-header-flag)) 1201 mh-clean-message-header-flag))
1056 (funcall mh-show-xface-function))) 1202 (funcall mh-show-xface-function)))
1203
1204
1205
1206 ;; X-Image-URL display
1207
1208 (defvar mh-x-image-cache-directory nil
1209 "Directory where X-Image-URL images are cached.")
1210
1211 (defvar mh-convert-executable (executable-find "convert"))
1212 (defvar mh-wget-executable (executable-find "wget"))
1213 (defvar mh-x-image-temp-file nil)
1214 (defvar mh-x-image-url nil)
1215 (defvar mh-x-image-marker nil)
1216 (defvar mh-x-image-url-cache-file nil)
1217
1218 (defun mh-x-image-url-cache-canonicalize (url)
1219 "Canonicalize URL.
1220 Replace the ?/ character with a ?! character."
1221 (with-temp-buffer
1222 (insert url)
1223 (goto-char (point-min))
1224 (while (search-forward "/" nil t) (replace-match "!"))
1225 (format "%s/%s.png" mh-x-image-cache-directory (buffer-string))))
1226
1227 (defun mh-x-image-url-fetch-image (url cache-file marker sentinel)
1228 "Fetch and display the image specified by URL.
1229 After the image is fetched, it is stored in CACHE-FILE. It will be displayed
1230 in a buffer and position specified by MARKER. The actual display is carried
1231 out by the SENTINEL function."
1232 (if (and mh-wget-executable
1233 mh-fetch-x-image-url
1234 (or (eq mh-fetch-x-image-url t)
1235 (y-or-n-p (format "Fetch %s? " url))))
1236 (let ((buffer (get-buffer-create (generate-new-buffer-name " *mh-url*")))
1237 (filename (make-temp-name "/tmp/mhe-wget")))
1238 (save-excursion
1239 (set-buffer buffer)
1240 (set (make-local-variable 'mh-x-image-url-cache-file) cache-file)
1241 (set (make-local-variable 'mh-x-image-marker) marker)
1242 (set (make-local-variable 'mh-x-image-temp-file) filename))
1243 (set-process-sentinel
1244 (start-process "*wget*" buffer mh-wget-executable "-O" filename url)
1245 sentinel))
1246 ;; Make sure we don't ask about this image again
1247 (when (and mh-wget-executable (eq mh-fetch-x-image-url 'ask))
1248 (make-symbolic-link mh-x-image-cache-directory cache-file t))))
1249
1250 (defun mh-x-image-display (image marker)
1251 "Display IMAGE at MARKER."
1252 (save-excursion
1253 (set-buffer (marker-buffer marker))
1254 (let ((buffer-read-only nil)
1255 (default-enable-multibyte-characters nil)
1256 (buffer-modified-flag (buffer-modified-p)))
1257 (unwind-protect
1258 (when (and (file-readable-p image) (not (file-symlink-p image)))
1259 (goto-char marker)
1260 (mh-do-in-gnu-emacs
1261 (mh-funcall-if-exists insert-image (create-image image 'png)))
1262 (mh-do-in-xemacs
1263 (when (featurep 'png)
1264 (set-extent-begin-glyph
1265 (make-extent (point) (point))
1266 (make-glyph
1267 (vector 'png ':data (with-temp-buffer
1268 (insert-file-contents-literally image)
1269 (buffer-string))))))))
1270 (set-buffer-modified-p buffer-modified-flag)))))
1271
1272 (defun mh-x-image-scale-and-display (process change)
1273 "When the wget PROCESS terminates scale and display image.
1274 The argument CHANGE is ignored."
1275 (when (eq (process-status process) 'exit)
1276 (let (marker temp-file cache-filename wget-buffer)
1277 (save-excursion
1278 (set-buffer (setq wget-buffer (process-buffer process)))
1279 (setq marker mh-x-image-marker
1280 cache-filename mh-x-image-url-cache-file
1281 temp-file mh-x-image-temp-file))
1282 (when mh-convert-executable
1283 (call-process mh-convert-executable nil nil nil "-resize" "96x48"
1284 temp-file cache-filename))
1285 (if (file-exists-p cache-filename)
1286 (mh-x-image-display cache-filename marker)
1287 (make-symbolic-link mh-x-image-cache-directory cache-filename t))
1288 (ignore-errors
1289 (set-marker marker nil)
1290 (delete-process process)
1291 (kill-buffer wget-buffer)
1292 (delete-file temp-file)))))
1293
1294 (defun mh-x-image-url-display (url)
1295 "Display image from location URL.
1296 If the URL isn't present in the cache then it is fetched with wget."
1297 (let ((cache-filename (mh-x-image-url-cache-canonicalize url))
1298 (marker (set-marker (make-marker) (point))))
1299 (cond ((file-exists-p cache-filename)
1300 (mh-x-image-display cache-filename marker))
1301 ((not mh-fetch-x-image-url)
1302 (set-marker marker nil))
1303 ((and (not (file-exists-p mh-x-image-cache-directory))
1304 (call-process "mkdir" nil nil nil mh-x-image-cache-directory)
1305 nil))
1306 ((and (file-exists-p mh-x-image-cache-directory)
1307 (file-directory-p mh-x-image-cache-directory))
1308 (mh-x-image-url-fetch-image url cache-filename marker
1309 'mh-x-image-scale-and-display)))))
1310
1311
1057 1312
1058 (defun mh-maybe-show (&optional msg) 1313 (defun mh-maybe-show (&optional msg)
1059 "Display message at cursor, but only if in show mode. 1314 "Display message at cursor, but only if in show mode.
1060 If optional arg MSG is non-nil, display that message instead." 1315 If optional arg MSG is non-nil, display that message instead."
1061 (if mh-showing-mode (mh-show msg))) 1316 (if mh-showing-mode (mh-show msg)))
1108 (shrink-window (- (window-height) mh-summary-height))) 1363 (shrink-window (- (window-height) mh-summary-height)))
1109 (mh-recenter nil) 1364 (mh-recenter nil)
1110 (if (not (memq msg mh-seen-list)) 1365 (if (not (memq msg mh-seen-list))
1111 (setq mh-seen-list (cons msg mh-seen-list))) 1366 (setq mh-seen-list (cons msg mh-seen-list)))
1112 (when mh-update-sequences-after-mh-show-flag 1367 (when mh-update-sequences-after-mh-show-flag
1368 (if mh-index-data (mh-index-update-unseen msg))
1113 (mh-update-sequences)) 1369 (mh-update-sequences))
1114 (run-hooks 'mh-show-hook)) 1370 (run-hooks 'mh-show-hook))
1115 1371
1116 (defun mh-modify (&optional message) 1372 (defun mh-modify (&optional message)
1117 "Edit message at cursor. 1373 "Edit message at cursor.
1145 1401
1146 ;; Just show the edit buffer... 1402 ;; Just show the edit buffer...
1147 (delete-other-windows) 1403 (delete-other-windows)
1148 (switch-to-buffer edit-buffer))) 1404 (switch-to-buffer edit-buffer)))
1149 1405
1150 (defun mh-decode-content-transfer-encoded-message ()
1151 "Run mimencode on message body, if needed."
1152 (let ((case-fold-search t)
1153 (header-end (mail-header-end)))
1154 (goto-char (point-min))
1155 (when (re-search-forward "^content-transfer-encoding: " header-end t)
1156 (let ((enc (buffer-substring-no-properties (point) (line-end-position)))
1157 cmdline)
1158 (setq cmdline
1159 (cond ((string-match "base64" enc) (list "-u" "-b" "-p"))
1160 ((string-match "quoted-printable" enc) (list "-u" "-q"))
1161 (t nil)))
1162 (when cmdline
1163 (beginning-of-line)
1164 (insert "Removed-")
1165 (setq header-end (mail-header-end))
1166 (goto-char (1+ header-end))
1167 (apply #'call-process-region (1+ header-end) (point-max) "mimencode"
1168 t t nil cmdline))))))
1169
1170 (defun mh-show-unquote-From () 1406 (defun mh-show-unquote-From ()
1171 "Decode >From at beginning of lines for `mh-show-mode'." 1407 "Decode >From at beginning of lines for `mh-show-mode'."
1172 (save-excursion 1408 (save-excursion
1173 (let ((modified (buffer-modified-p)) 1409 (let ((modified (buffer-modified-p))
1174 (case-fold-search nil)) 1410 (case-fold-search nil))
1175 (goto-char (mail-header-end)) 1411 (goto-char (mh-mail-header-end))
1176 (while (re-search-forward "^>From" nil t) 1412 (while (re-search-forward "^>From" nil t)
1177 (replace-match "From")) 1413 (replace-match "From"))
1178 (set-buffer-modified-p modified)))) 1414 (set-buffer-modified-p modified))))
1179 1415
1180 (defun mh-msg-folder (folder-name) 1416 (defun mh-msg-folder (folder-name)
1224 (mh-exec-lib-cmd-output "mhl" "-nobell" "-noclear" 1460 (mh-exec-lib-cmd-output "mhl" "-nobell" "-noclear"
1225 (if (stringp formfile) 1461 (if (stringp formfile)
1226 (list "-form" formfile)) 1462 (list "-form" formfile))
1227 msg-filename) 1463 msg-filename)
1228 (insert-file-contents-literally msg-filename)) 1464 (insert-file-contents-literally msg-filename))
1229 (if mh-decode-content-transfer-encoded-message-flag
1230 (mh-decode-content-transfer-encoded-message))
1231 ;; Cleanup old mime handles 1465 ;; Cleanup old mime handles
1232 (mh-mime-cleanup) 1466 (mh-mime-cleanup)
1233 ;; Use mm to display buffer 1467 ;; Use mm to display buffer
1234 (when (and mh-decode-mime-flag (not formfile)) 1468 (when (and mh-decode-mime-flag (not formfile))
1235 (mh-add-missing-mime-version-header) 1469 (mh-add-missing-mime-version-header)
1236 (setf (mh-buffer-data) (mh-make-buffer-data)) 1470 (setf (mh-buffer-data) (mh-make-buffer-data))
1237 (mh-mime-display)) 1471 (mh-mime-display))
1472 (mh-show-mode)
1238 ;; Header cleanup 1473 ;; Header cleanup
1239 (goto-char (point-min)) 1474 (goto-char (point-min))
1240 (cond (clean-message-header 1475 (cond (clean-message-header
1241 (mh-clean-msg-header (point-min) 1476 (mh-clean-msg-header (point-min)
1242 invisible-headers 1477 invisible-headers
1243 visible-headers) 1478 visible-headers)
1244 (goto-char (point-min))) 1479 (goto-char (point-min)))
1245 (t 1480 (t
1246 (mh-start-of-uncleaned-message))) 1481 (mh-start-of-uncleaned-message)))
1482 (mh-decode-message-header)
1247 ;; the parts of visiting we want to do (no locking) 1483 ;; the parts of visiting we want to do (no locking)
1248 (or (eq buffer-undo-list t) ;don't save undo info for prev msgs 1484 (or (eq buffer-undo-list t) ;don't save undo info for prev msgs
1249 (setq buffer-undo-list nil)) 1485 (setq buffer-undo-list nil))
1250 (set-buffer-auto-saved) 1486 (set-buffer-auto-saved)
1251 ;; the parts of set-visited-file-name we want to do (no locking) 1487 ;; the parts of set-visited-file-name we want to do (no locking)
1252 (setq buffer-file-name msg-filename) 1488 (setq buffer-file-name msg-filename)
1253 (setq buffer-backed-up nil) 1489 (setq buffer-backed-up nil)
1254 (auto-save-mode 1) 1490 (auto-save-mode 1)
1255 (set-mark nil) 1491 (set-mark nil)
1256 (mh-show-mode)
1257 (unwind-protect 1492 (unwind-protect
1258 (when (and mh-decode-mime-flag (not formfile)) 1493 (when (and mh-decode-mime-flag (not formfile))
1259 (setq buffer-read-only nil) 1494 (setq buffer-read-only nil)
1260 (mh-display-smileys) 1495 (mh-display-smileys)
1261 (mh-display-emphasis)) 1496 (mh-display-emphasis))
1274 Header is cleaned from START to the end of the message header. 1509 Header is cleaned from START to the end of the message header.
1275 INVISIBLE-HEADERS contains a regular expression specifying lines to delete 1510 INVISIBLE-HEADERS contains a regular expression specifying lines to delete
1276 from the header. VISIBLE-HEADERS contains a regular expression specifying the 1511 from the header. VISIBLE-HEADERS contains a regular expression specifying the
1277 lines to display. INVISIBLE-HEADERS is ignored if VISIBLE-HEADERS is non-nil." 1512 lines to display. INVISIBLE-HEADERS is ignored if VISIBLE-HEADERS is non-nil."
1278 (let ((case-fold-search t) 1513 (let ((case-fold-search t)
1514 (buffer-read-only nil)
1279 (after-change-functions nil)) ;Work around emacs-20 font-lock bug 1515 (after-change-functions nil)) ;Work around emacs-20 font-lock bug
1280 ;causing an endless loop. 1516 ;causing an endless loop.
1281 (save-restriction 1517 (save-restriction
1282 (goto-char start) 1518 (goto-char start)
1283 (if (search-forward "\n\n" nil 'move) 1519 (if (search-forward "\n\n" nil 'move)
1304 "Delete the next LINES lines." 1540 "Delete the next LINES lines."
1305 (delete-region (point) (progn (forward-line lines) (point)))) 1541 (delete-region (point) (progn (forward-line lines) (point))))
1306 1542
1307 (defun mh-notate (msg notation offset) 1543 (defun mh-notate (msg notation offset)
1308 "Mark MSG with the character NOTATION at position OFFSET. 1544 "Mark MSG with the character NOTATION at position OFFSET.
1309 Null MSG means the message at cursor." 1545 Null MSG means the message at cursor.
1546 If NOTATION is nil then no change in the buffer occurs."
1310 (save-excursion 1547 (save-excursion
1311 (if (or (null msg) 1548 (if (or (null msg)
1312 (mh-goto-msg msg t t)) 1549 (mh-goto-msg msg t t))
1313 (with-mh-folder-updating (t) 1550 (with-mh-folder-updating (t)
1314 (beginning-of-line) 1551 (beginning-of-line)
1315 (forward-char offset) 1552 (forward-char offset)
1316 (delete-char 1) 1553 (let ((notation (or notation (char-after))))
1317 (insert notation))))) 1554 (delete-char 1)
1555 (insert notation))))))
1318 1556
1319 (defun mh-find-msg-get-num (step) 1557 (defun mh-find-msg-get-num (step)
1320 "Return the message number of the message nearest the cursor. 1558 "Return the message number of the message nearest the cursor.
1321 Jumps over non-message lines, such as inc errors. 1559 Jumps over non-message lines, such as inc errors.
1322 If we have to search, STEP tells whether to search forward or backward." 1560 If we have to search, STEP tells whether to search forward or backward."
1403 (if (not mh-user-path) 1641 (if (not mh-user-path)
1404 (setq mh-user-path "Mail")) 1642 (setq mh-user-path "Mail"))
1405 (setq mh-user-path 1643 (setq mh-user-path
1406 (file-name-as-directory 1644 (file-name-as-directory
1407 (expand-file-name mh-user-path (expand-file-name "~")))) 1645 (expand-file-name mh-user-path (expand-file-name "~"))))
1646 (unless mh-x-image-cache-directory
1647 (setq mh-x-image-cache-directory
1648 (expand-file-name ".mhe-x-image-cache" mh-user-path)))
1408 (setq mh-draft-folder (mh-get-profile-field "Draft-Folder:")) 1649 (setq mh-draft-folder (mh-get-profile-field "Draft-Folder:"))
1409 (if mh-draft-folder 1650 (if mh-draft-folder
1410 (progn 1651 (progn
1411 (if (not (mh-folder-name-p mh-draft-folder)) 1652 (if (not (mh-folder-name-p mh-draft-folder))
1412 (setq mh-draft-folder (format "+%s" mh-draft-folder))) 1653 (setq mh-draft-folder (format "+%s" mh-draft-folder)))
1540 (width 0)) 1781 (width 0))
1541 (save-excursion 1782 (save-excursion
1542 (set-buffer tmp-buffer) 1783 (set-buffer tmp-buffer)
1543 (erase-buffer) 1784 (erase-buffer)
1544 (apply 'call-process 1785 (apply 'call-process
1545 (expand-file-name "scan" mh-progs) nil '(t nil) nil 1786 (expand-file-name mh-scan-prog mh-progs) nil '(t nil) nil
1546 (list folder "last" "-format" "%(msg)")) 1787 (list folder "last" "-format" "%(msg)"))
1547 (goto-char (point-min)) 1788 (goto-char (point-min))
1548 (if (re-search-forward mh-scan-msg-number-regexp nil 0 1) 1789 (if (re-search-forward mh-scan-msg-number-regexp nil 0 1)
1549 (setq width (length (buffer-substring 1790 (setq width (length (buffer-substring
1550 (match-beginning 1) (match-end 1)))))) 1791 (match-beginning 1) (match-end 1))))))
1580 (setcdr head (cddr head)) 1821 (setcdr head (cddr head))
1581 (setq head (cdr head)))) 1822 (setq head (cdr head))))
1582 sorted-msgs)) 1823 sorted-msgs))
1583 1824
1584 (defvar mh-sub-folders-cache (make-hash-table :test #'equal)) 1825 (defvar mh-sub-folders-cache (make-hash-table :test #'equal))
1826 (defvar mh-current-folder-name nil)
1585 1827
1586 (defun mh-normalize-folder-name (folder &optional empty-string-okay 1828 (defun mh-normalize-folder-name (folder &optional empty-string-okay
1587 dont-remove-trailing-slash) 1829 dont-remove-trailing-slash)
1588 "Normalizes FOLDER name. 1830 "Normalizes FOLDER name.
1589 Makes sure that two '/' characters never occur next to each other. Also all 1831 Makes sure that two '/' characters never occur next to each other. Also all
1600 ;; Replace two or more consecutive '/' characters with a single '/' 1842 ;; Replace two or more consecutive '/' characters with a single '/'
1601 (while (string-match "//" folder) 1843 (while (string-match "//" folder)
1602 (setq folder (replace-match "/" nil t folder))) 1844 (setq folder (replace-match "/" nil t folder)))
1603 (let* ((length (length folder)) 1845 (let* ((length (length folder))
1604 (trailing-slash-present (and (> length 0) 1846 (trailing-slash-present (and (> length 0)
1605 (equal (aref folder (1- length)) ?/)))) 1847 (equal (aref folder (1- length)) ?/)))
1606 (let ((components (split-string folder "/")) 1848 (leading-slash-present (and (> length 0)
1849 (equal (aref folder 0) ?/))))
1850 (when (and (> length 0) (equal (aref folder 0) ?@)
1851 (stringp mh-current-folder-name))
1852 (setq folder (format "%s/%s/" mh-current-folder-name
1853 (substring folder 1))))
1854 ;; XXX: Purge empty strings from the list that split-string returns. In
1855 ;; XEmacs, (split-string "+foo/" "/") returns ("+foo" "") while in GNU
1856 ;; Emacs it returns ("+foo"). In the code it is assumed that the
1857 ;; components list has no empty strings.
1858 (let ((components (delete "" (split-string folder "/")))
1607 (result ())) 1859 (result ()))
1608 ;; Remove .. and . from the pathname. 1860 ;; Remove .. and . from the pathname.
1609 (dolist (component components) 1861 (dolist (component components)
1610 (cond ((and (equal component "..") result) 1862 (cond ((and (equal component "..") result)
1611 (pop result)) 1863 (pop result))
1616 (dolist (component result) 1868 (dolist (component result)
1617 (setq folder (concat component "/" folder))) 1869 (setq folder (concat component "/" folder)))
1618 ;; Remove trailing '/' if needed. 1870 ;; Remove trailing '/' if needed.
1619 (unless (and trailing-slash-present dont-remove-trailing-slash) 1871 (unless (and trailing-slash-present dont-remove-trailing-slash)
1620 (when (not (equal folder "")) 1872 (when (not (equal folder ""))
1621 (setq folder (substring folder 0 (1- (length folder)))))))) 1873 (setq folder (substring folder 0 (1- (length folder))))))
1874 (when leading-slash-present
1875 (setq folder (concat "/" folder)))))
1622 (cond ((and empty-string-okay (equal folder ""))) 1876 (cond ((and empty-string-okay (equal folder "")))
1623 ((equal folder "") (setq folder "+")) 1877 ((equal folder "") (setq folder "+"))
1624 ((not (equal (aref folder 0) ?+)) (setq folder (concat "+" folder))))) 1878 ((not (equal (aref folder 0) ?+)) (setq folder (concat "+" folder)))))
1625 folder) 1879 folder)
1626 1880
1711 (setq one-ancestor-found t)))) 1965 (setq one-ancestor-found t))))
1712 (remhash nil mh-sub-folders-cache)))) 1966 (remhash nil mh-sub-folders-cache))))
1713 1967
1714 (defvar mh-folder-hist nil) 1968 (defvar mh-folder-hist nil)
1715 (defvar mh-speed-folder-map) 1969 (defvar mh-speed-folder-map)
1970 (defvar mh-speed-flists-cache)
1971
1972 (defvar mh-allow-root-folder-flag nil
1973 "Non-nil means \"+\" is an acceptable folder name.
1974 This variable is used to communicate with `mh-folder-completion-function'. That
1975 function can have exactly three arguments so we bind this variable to t or nil.
1976
1977 This variable should never be set.")
1978
1716 (defvar mh-folder-completion-map (copy-keymap minibuffer-local-completion-map)) 1979 (defvar mh-folder-completion-map (copy-keymap minibuffer-local-completion-map))
1717 (define-key mh-folder-completion-map " " 'minibuffer-complete) 1980 (define-key mh-folder-completion-map " " 'minibuffer-complete)
1981
1982 (defun mh-speed-flists-active-p ()
1983 "Check if speedbar is running with message counts enabled."
1984 (and (featurep 'mh-speed)
1985 (> (hash-table-count mh-speed-flists-cache) 0)))
1718 1986
1719 (defun mh-folder-completion-function (name predicate flag) 1987 (defun mh-folder-completion-function (name predicate flag)
1720 "Programmable completion for folder names. 1988 "Programmable completion for folder names.
1721 NAME is the partial folder name that has been input. PREDICATE if non-nil is a 1989 NAME is the partial folder name that has been input. PREDICATE if non-nil is a
1722 function that is used to filter the possible choices and FLAG determines 1990 function that is used to filter the possible choices and FLAG determines
1745 (t try-res)))) 2013 (t try-res))))
1746 ((eq flag t) 2014 ((eq flag t)
1747 (all-completions 2015 (all-completions
1748 remainder (mh-sub-folders last-complete t) predicate)) 2016 remainder (mh-sub-folders last-complete t) predicate))
1749 ((eq flag 'lambda) 2017 ((eq flag 'lambda)
1750 (file-exists-p 2018 (let ((path (concat mh-user-path
1751 (concat mh-user-path 2019 (substring (mh-normalize-folder-name name) 1))))
1752 (substring (mh-normalize-folder-name name) 1))))))) 2020 (cond (mh-allow-root-folder-flag (file-exists-p path))
1753 2021 ((equal path mh-user-path) nil)
1754 (defun mh-folder-completing-read (prompt default) 2022 (t (file-exists-p path))))))))
1755 "Read folder name with PROMPT and default result DEFAULT." 2023
2024 (defun mh-folder-completing-read (prompt default allow-root-folder-flag)
2025 "Read folder name with PROMPT and default result DEFAULT.
2026 If ALLOW-ROOT-FOLDER-FLAG is non-nil then \"+\" is allowed to be a folder name
2027 corresponding to `mh-user-path'."
1756 (mh-normalize-folder-name 2028 (mh-normalize-folder-name
1757 (let ((minibuffer-local-completion-map mh-folder-completion-map)) 2029 (let ((minibuffer-local-completion-map mh-folder-completion-map)
2030 (mh-allow-root-folder-flag allow-root-folder-flag))
1758 (completing-read prompt 'mh-folder-completion-function nil nil nil 2031 (completing-read prompt 'mh-folder-completion-function nil nil nil
1759 'mh-folder-hist default)) 2032 'mh-folder-hist default))
1760 t)) 2033 t))
1761 2034
1762 (defun mh-prompt-for-folder (prompt default can-create 2035 (defun mh-prompt-for-folder (prompt default can-create
1773 (let* ((default-string (cond (default-string (format " [%s]? " 2046 (let* ((default-string (cond (default-string (format " [%s]? "
1774 default-string)) 2047 default-string))
1775 ((equal "" default) "? ") 2048 ((equal "" default) "? ")
1776 (t (format " [%s]? " default)))) 2049 (t (format " [%s]? " default))))
1777 (prompt (format "%s folder%s" prompt default-string)) 2050 (prompt (format "%s folder%s" prompt default-string))
2051 (mh-current-folder-name mh-current-folder)
1778 read-name folder-name) 2052 read-name folder-name)
1779 (while (and (setq read-name (mh-folder-completing-read prompt default)) 2053 (while (and (setq read-name (mh-folder-completing-read
2054 prompt default allow-root-folder-flag))
1780 (equal read-name "") 2055 (equal read-name "")
1781 (equal default ""))) 2056 (equal default "")))
1782 (cond ((or (equal read-name "") 2057 (cond ((or (equal read-name "")
1783 (and (equal read-name "+") (not allow-root-folder-flag))) 2058 (and (equal read-name "+") (not allow-root-folder-flag)))
1784 (setq read-name default)) 2059 (setq read-name default))
1788 (error "No folder specified")) 2063 (error "No folder specified"))
1789 (setq folder-name read-name) 2064 (setq folder-name read-name)
1790 (cond ((and (> (length folder-name) 0) 2065 (cond ((and (> (length folder-name) 0)
1791 (eq (aref folder-name (1- (length folder-name))) ?/)) 2066 (eq (aref folder-name (1- (length folder-name))) ?/))
1792 (setq folder-name (substring folder-name 0 -1)))) 2067 (setq folder-name (substring folder-name 0 -1))))
2068 (let* ((last-slash (mh-search-from-end ?/ folder-name))
2069 (parent (and last-slash (substring folder-name 0 last-slash)))
2070 (child (if last-slash
2071 (substring folder-name (1+ last-slash))
2072 (substring folder-name 1))))
2073 (unless (member child
2074 (mapcar #'car (gethash parent mh-sub-folders-cache)))
2075 (mh-remove-from-sub-folders-cache folder-name)))
1793 (let ((new-file-flag 2076 (let ((new-file-flag
1794 (not (file-exists-p (mh-expand-file-name folder-name))))) 2077 (not (file-exists-p (mh-expand-file-name folder-name)))))
1795 (cond ((and new-file-flag 2078 (cond ((and new-file-flag
1796 (y-or-n-p 2079 (y-or-n-p
1797 (format "Folder %s does not exist. Create it? " 2080 (format "Folder %s does not exist. Create it? "
1807 ((not (file-directory-p (mh-expand-file-name folder-name))) 2090 ((not (file-directory-p (mh-expand-file-name folder-name)))
1808 (error "\"%s\" is not a directory" 2091 (error "\"%s\" is not a directory"
1809 (mh-expand-file-name folder-name))))) 2092 (mh-expand-file-name folder-name)))))
1810 folder-name)) 2093 folder-name))
1811 2094
2095 (defun mh-truncate-log-buffer ()
2096 "If `mh-log-buffer' is too big then truncate it.
2097 If the number of lines in `mh-log-buffer' exceeds `mh-log-buffer-lines' then
2098 keep only the last `mh-log-buffer-lines'. As a side effect the point is set to
2099 the end of the log buffer.
2100
2101 The function returns the size of the final size of the log buffer."
2102 (with-current-buffer (get-buffer-create mh-log-buffer)
2103 (goto-char (point-max))
2104 (save-excursion
2105 (when (equal (forward-line (- mh-log-buffer-lines)) 0)
2106 (delete-region (point-min) (point))))
2107 (unless (or (bobp)
2108 (save-excursion
2109 (and (equal (forward-line -1) 0) (equal (char-after) ? ))))
2110 (insert "\n \n"))
2111 (buffer-size)))
2112
1812 ;;; Issue commands to MH. 2113 ;;; Issue commands to MH.
1813 2114
1814 (defun mh-exec-cmd (command &rest args) 2115 (defun mh-exec-cmd (command &rest args)
1815 "Execute mh-command COMMAND with ARGS. 2116 "Execute mh-command COMMAND with ARGS.
1816 The side effects are what is desired. 2117 The side effects are what is desired.
1817 Any output is assumed to be an error and is shown to the user. 2118 Any output is assumed to be an error and is shown to the user.
1818 The output is not read or parsed by MH-E." 2119 The output is not read or parsed by MH-E."
1819 (save-excursion 2120 (save-excursion
1820 (set-buffer (get-buffer-create mh-log-buffer)) 2121 (set-buffer (get-buffer-create mh-log-buffer))
1821 (erase-buffer) 2122 (let ((initial-size (mh-truncate-log-buffer)))
1822 (apply 'call-process 2123 (apply 'call-process
1823 (expand-file-name command mh-progs) nil t nil 2124 (expand-file-name command mh-progs) nil t nil
1824 (mh-list-to-string args)) 2125 (mh-list-to-string args))
1825 (if (> (buffer-size) 0) 2126 (if (> (buffer-size) initial-size)
1826 (save-window-excursion 2127 (save-window-excursion
1827 (switch-to-buffer-other-window mh-log-buffer) 2128 (switch-to-buffer-other-window mh-log-buffer)
1828 (sit-for 5))))) 2129 (sit-for 5))))))
1829 2130
1830 (defun mh-exec-cmd-error (env command &rest args) 2131 (defun mh-exec-cmd-error (env command &rest args)
1831 "In environment ENV, execute mh-command COMMAND with ARGS. 2132 "In environment ENV, execute mh-command COMMAND with ARGS.
1832 ENV is nil or a string of space-separated \"var=value\" elements. 2133 ENV is nil or a string of space-separated \"var=value\" elements.
1833 Signals an error if process does not complete successfully." 2134 Signals an error if process does not complete successfully."
1834 (save-excursion 2135 (save-excursion
1835 (set-buffer (get-buffer-create mh-temp-buffer)) 2136 (set-buffer (get-buffer-create mh-temp-buffer))
1836 (erase-buffer) 2137 (erase-buffer)
1837 (let ((status 2138 (let ((process-environment process-environment))
1838 (if env 2139 ;; XXX: We should purge the list that split-string returns of empty
1839 ;; the shell hacks necessary here shows just how broken Unix is 2140 ;; strings. This can happen in XEmacs if leading or trailing spaces
1840 (apply 'call-process "/bin/sh" nil t nil "-c" 2141 ;; are present.
1841 (format "%s %s ${1+\"$@\"}" 2142 (dolist (elem (if (stringp env) (split-string env " ") ()))
1842 env 2143 (push elem process-environment))
1843 (expand-file-name command mh-progs)) 2144 (mh-handle-process-error
1844 command 2145 command (apply #'call-process (expand-file-name command mh-progs)
1845 (mh-list-to-string args)) 2146 nil t nil (mh-list-to-string args))))))
1846 (apply 'call-process
1847 (expand-file-name command mh-progs) nil t nil
1848 (mh-list-to-string args)))))
1849 (mh-handle-process-error command status))))
1850 2147
1851 (defun mh-exec-cmd-daemon (command filter &rest args) 2148 (defun mh-exec-cmd-daemon (command filter &rest args)
1852 "Execute MH command COMMAND in the background. 2149 "Execute MH command COMMAND in the background.
1853 2150
1854 If FILTER is non-nil then it is used to process the output otherwise the 2151 If FILTER is non-nil then it is used to process the output otherwise the
1856 details of FILTER. 2153 details of FILTER.
1857 2154
1858 ARGS are passed to COMMAND as command line arguments." 2155 ARGS are passed to COMMAND as command line arguments."
1859 (save-excursion 2156 (save-excursion
1860 (set-buffer (get-buffer-create mh-log-buffer)) 2157 (set-buffer (get-buffer-create mh-log-buffer))
1861 (erase-buffer)) 2158 (mh-truncate-log-buffer))
1862 (let* ((process-connection-type nil) 2159 (let* ((process-connection-type nil)
1863 (process (apply 'start-process 2160 (process (apply 'start-process
1864 command nil 2161 command nil
1865 (expand-file-name command mh-progs) 2162 (expand-file-name command mh-progs)
1866 (mh-list-to-string args)))) 2163 (mh-list-to-string args))))
1867 (set-process-filter process (or filter 'mh-process-daemon)))) 2164 (set-process-filter process (or filter 'mh-process-daemon))))
2165
2166 (defun mh-exec-cmd-env-daemon (env command filter &rest args)
2167 "In ennvironment ENV, execute mh-command COMMAND in the background.
2168
2169 ENV is nil or a string of space-separated \"var=value\" elements.
2170 Signals an error if process does not complete successfully.
2171
2172 If FILTER is non-nil then it is used to process the output otherwise the
2173 default filter `mh-process-daemon' is used. See `set-process-filter' for more
2174 details of FILTER.
2175
2176 ARGS are passed to COMMAND as command line arguments."
2177 (let ((process-environment process-environment))
2178 (dolist (elem (if (stringp env) (split-string env " ") ()))
2179 (push elem process-environment))
2180 (apply #'mh-exec-cmd-daemon command filter args)))
1868 2181
1869 (defun mh-process-daemon (process output) 2182 (defun mh-process-daemon (process output)
1870 "PROCESS daemon that puts OUTPUT into a temporary buffer. 2183 "PROCESS daemon that puts OUTPUT into a temporary buffer.
1871 Any output from the process is displayed in an asynchronous pop-up window." 2184 Any output from the process is displayed in an asynchronous pop-up window."
1872 (set-buffer (get-buffer-create mh-log-buffer)) 2185 (set-buffer (get-buffer-create mh-log-buffer))
1931 "Execute MH library command COMMAND with ARGS. 2244 "Execute MH library command COMMAND with ARGS.
1932 Put the output into buffer after point. Set mark after inserted text." 2245 Put the output into buffer after point. Set mark after inserted text."
1933 (apply 'mh-exec-cmd-output (expand-file-name command mh-lib-progs) nil args)) 2246 (apply 'mh-exec-cmd-output (expand-file-name command mh-lib-progs) nil args))
1934 2247
1935 (defun mh-handle-process-error (command status) 2248 (defun mh-handle-process-error (command status)
1936 "Raise error if COMMAND returned non-zero STATUS, otherwise return STATUS. 2249 "Raise error if COMMAND returned non-zero STATUS, otherwise return STATUS."
1937 STATUS is return value from `call-process'. 2250 (if (equal status 0)
1938 Program output is in current buffer. 2251 status
1939 If output is too long to include in error message, display the buffer." 2252 (goto-char (point-min))
1940 (cond ((eq status 0) ;success 2253 (insert (if (integerp status)
1941 status) 2254 (format "%s: exit code %d\n" command status)
1942 ((stringp status) ;kill string 2255 (format "%s: %s\n" command status)))
1943 (error "%s: %s" command status)) 2256 (save-excursion
1944 (t ;exit code 2257 (let ((error-message (buffer-substring (point-min) (point-max))))
1945 (cond 2258 (set-buffer (get-buffer-create mh-log-buffer))
1946 ((= (buffer-size) 0) ;program produced no error message 2259 (mh-truncate-log-buffer)
1947 (error "%s: exit code %d" command status)) 2260 (insert error-message)))
1948 (t 2261 (error "%s failed, check %s buffer for error message"
1949 ;; will error message fit on one line? 2262 command mh-log-buffer)))
1950 (goto-line 2)
1951 (if (and (< (buffer-size) (frame-width))
1952 (eobp))
1953 (error "%s"
1954 (buffer-substring 1 (progn (goto-char 1)
1955 (end-of-line)
1956 (point))))
1957 (display-buffer (current-buffer))
1958 (error "%s failed with status %d. See error message in other window"
1959 command status)))))))
1960 2263
1961 (defun mh-list-to-string (l) 2264 (defun mh-list-to-string (l)
1962 "Flatten the list L and make every element of the new list into a string." 2265 "Flatten the list L and make every element of the new list into a string."
1963 (nreverse (mh-list-to-string-1 l))) 2266 (nreverse (mh-list-to-string-1 l)))
1964 2267