;;; hmail.el --- GNU Hyperbole buttons embedded in e-mail messages -*- lexical-binding: t; -*-
;;
;; Author: Bob Weiner
;;
;; Orig-Date: 9-Oct-91 at 18:38:05
;; Last-Mod: 10-Mar-24 at 11:52:34 by Bob Weiner
;;
;; SPDX-License-Identifier: GPL-3.0-or-later
;;
;; Copyright (C) 1991-2024 Free Software Foundation, Inc.
;; See the HY-COPY (Hyperbole) or BR-COPY (OO-Browser) file for license
;; information.
;;
;; This file is part of GNU Hyperbole.
;;
;;; Commentary:
;;
;; The `hmail' class provides an abstract interface for connecting
;; GNU Emacs-based mail readers and composers to Hyperbole. Its
;; public variables together with supporting classes determine the
;; mail tools that Hyperbole will support.
;;
;; The `rmail' and `lmail' classes provide a set of feature names
;; that Hyperbole packages can call to interface to a user's selected
;; mail reader. Eventually, a full abstract calling interface may be
;; developed. The public features (the ones above the line of dashes)
;; must be redefined for any mail reader. The private features are
;; used only by a particular mail reader.
;;
;; The `smail' class is similar; it connects a mail composer for use
;; with Hyperbole.
;;; Code:
;;; ************************************************************************
;;; Public declarations
;;; ************************************************************************
(declare-function rmail:msg-widen "hmail")
(declare-function hypb:insert-region "hypb")
;;; ************************************************************************
;;; Public variables
;;; ************************************************************************
(defvar hnews:composer 'news-reply-mode
"Major mode for composing USENET news to be sent with Hyperbole buttons.")
(defvar hnews:lister 'gnus-summary-mode
"Major mode for listing USENET news header summaries with Hyperbole buttons.")
(defvar hnews:reader 'gnus-article-mode
"Major mode for reading USENET news with Hyperbole buttons.")
(defcustom hmail:init-function nil
"*Function (a symbol) to initialize Hyperbole support for a mail reader/composer.
Valid values are: nil, Mh-init or Rmail-init."
:type '(choice (const nil)
(const Mh-init)
(const Rmail-init))
:group 'hyperbole-commands)
(defvar hmail:composer 'message-mode
"Major mode for composing mail to be sent with Hyperbole buttons.")
(defvar hmail:lister nil
"Major mode for listing mail header summaries with Hyperbole buttons.")
(defvar hmail:modifier nil
"Major mode for editing received mail with Hyperbole buttons.")
(defvar hmail:reader nil
"Major mode for reading mail with Hyperbole buttons.")
;;; ************************************************************************
;;; Private variables
;;; ************************************************************************
(defvar hmail:hbdata-sep "\^Lbd"
"Text separating e-mail msg from any trailing Hyperbole button data.")
;;; ************************************************************************
;;; Public functions
;;; ************************************************************************
;;; ========================================================================
;;; hmail class - abstract
;;; ========================================================================
(defun hmail:hbdata-start (&optional msg-start msg-end)
"Return point immediately before any Hyperbole button data in current msg.
Return message end point when no button data is found.
Has side-effect of widening buffer.
Message's displayable part begins at optional MSG-START and ends at or before
MSG-END."
(widen)
(unless msg-end
(setq msg-end (point-max)))
(save-excursion
(goto-char msg-end)
(if (search-backward hmail:hbdata-sep msg-start t) (1- (point)) msg-end)))
(defun hmail:hbdata-to-p ()
"Move point to the start of embedded Hyperbole button data.
Return t if button data is found, else nil."
(when (cond ((memq major-mode (list hmail:reader hmail:modifier))
(hmail:msg-narrow)
t)
((or (hmail:lister-p) (hnews:lister-p)) t)
((or (not buffer-file-name)
(memq major-mode (list hmail:composer hnews:reader
hnews:composer)))
(widen)
t))
(goto-char (point-max))
(when (search-backward hmail:hbdata-sep nil t)
(forward-line 1)
t)))
(defun hmail:browser-p ()
"Return t iff current major mode helps browse received e-mail messages."
(memq major-mode (list hmail:reader hmail:lister)))
(defun hmail:buffer (&optional buf invisible-flag)
"Start composing mail with the contents of optional BUF as the message body.
Invisible text is expanded and included in the mail only if INVISIBLE-FLAG is
non-nil. BUF defaults to the current buffer and may be a buffer or buffer
name."
(interactive (list (current-buffer) (y-or-n-p "Include invisible text? ")))
(unless buf
(setq buf (current-buffer)))
(when (stringp buf)
(setq buf (get-buffer buf)))
(set-buffer buf)
(hmail:region (point-min) (point-max) buf invisible-flag))
;;;###autoload
(defun hmail:compose (address expr &optional subject help)
"Compose mail with ADDRESS and evaluation of EXPR.
Optional SUBJECT and HELP message may also be given."
(interactive "sDeliver e-mail to: \nSubject: ")
(require 'hactypes) ;; Needed in case EXPR calls hact.
(unless (or (stringp help) (stringp subject))
(setq subject "Delete this text but write a detailed subject. Make a statement or ask a question."))
(hmail:invoke address nil subject)
(eval expr)
(when (re-search-backward "^Subject: " nil t)
(goto-char (match-end 0)))
(message (if (stringp help)
help
"Replace subject, compose message, and then mail.")))
(defun hmail:composing-dir (key-src)
"If button KEY-SRC is a mail/news composure buffer, return composure directory.
Otherwise, return nil."
(save-excursion
(and (bufferp key-src)
(progn (set-buffer key-src)
(or (eq major-mode hmail:composer)
(eq major-mode hnews:composer)))
default-directory)))
(defun hmail:editor-p ()
"Return t iff current major mode edits Hyperbole e-mail/news messages."
(memq major-mode (list hmail:composer hnews:composer hmail:modifier)))
(defun hmail:init (class-prefix func-suffix-list)
"Set up CLASS-PREFIX functions with aliases for FUNC-SUFFIX-LIST.
`hmail:reader' should be set appropriately before this is called."
(when hmail:reader
(let* ((reader-name (symbol-name hmail:reader))
(reader-prefix (capitalize
(substring reader-name
0 (string-match "-" reader-name))))
hmail-func)
(mapcar (lambda (func-suffix)
(setq hmail-func (replace-regexp-in-string
"Summ-" "" func-suffix nil t))
(defalias (intern (concat class-prefix hmail-func))
(intern (concat reader-prefix "-" func-suffix))))
func-suffix-list))))
(defun hmail:invoke (&optional address cc subject)
"Invoke user preferred mail composer: mh-send or mail.
Optional arguments are ADDRESS, CC list and SUBJECT of mail."
;; Next 3 lines prevent blank lines between fields due to
;; fill-region-as-paragraph within mail-setup.
(when (equal address "")
(setq address nil))
(when (equal cc "")
(setq cc nil))
(when (equal subject "")
(setq subject nil))
(compose-mail address subject (if cc (list (cons "CC" cc)))))
(defun hmail:lister-p ()
"Return t iff current major mode is a Hyperbole e-mail lister mode."
(eq major-mode hmail:lister))
(defun hnews:lister-p ()
"Return t iff current major mode is a Hyperbole news summary lister mode."
(eq major-mode hnews:lister))
(defun hmail:mode-is-p ()
"Return current major mode if a Hyperbole e-mail or news mode, else nil."
(car (memq major-mode
(list hmail:reader hmail:composer hmail:lister hmail:modifier
hnews:reader hnews:composer hnews:lister))))
;;;###autoload
(defun hmail:msg-narrow (&optional msg-start msg-end)
"Narrows buffer to displayable part of current message.
Its displayable part begins at optional MSG-START and ends at or before
MSG-END."
(when (hmail:reader-p)
(rmail:msg-widen))
(setq msg-start (or msg-start (point-min))
msg-end (or msg-end (point-max)))
(narrow-to-region msg-start (hmail:hbdata-start msg-start msg-end)))
(defun hmail:reader-p ()
"Return t iff current major mode show received Hyperbole e-mail messages."
(memq major-mode (list hmail:reader hmail:modifier)))
(defun hmail:region (start end &optional buf invisible-flag)
"Start composing mail with region between START and END included in message.
Invisible text is expanded and included in the mail only if INVISIBLE-FLAG is
non-nil. Optional BUF contains the region and defaults to the current
buffer. It may be a buffer or buffer name."
(interactive (list (region-beginning) (region-end) (current-buffer)
(y-or-n-p "Include invisible text? ")))
(unless buf
(setq buf (current-buffer)))
(when (stringp buf)
(setq buf (get-buffer buf)))
(let (mail-buf)
(hmail:invoke)
(setq mail-buf (current-buffer))
(save-excursion
(rfc822-goto-eoh)
(forward-line)
(set-buffer buf)
(hypb:insert-region mail-buf start end invisible-flag))))
;;; ========================================================================
;;; rmail class - mail reader interface - abstract
;;; ========================================================================
(defun rmail:init ()
"Initialize Hyperbole abstract mail interface for a particular mail reader.
`hmail:reader' should be set appropriately before this is called."
(hmail:init "rmail:" '("msg-hdrs-full" "msg-narrow" "msg-num"
"msg-prev" "msg-next"
"msg-to-p" ;; 2 args: (mail-msg-id mail-file)
"msg-widen" "to"))
(hmail:init "lmail:" '("Summ-delete" "Summ-expunge" "Summ-goto" "Summ-to"
"Summ-undelete-all")))
(defvar rmail:msg-hdr-prefix "\\(^Date: \\|\n\nFrom [^ \n]+ \\)"
"String header preceding an e-mail received message-id.")
(defun rmail:msg-id-get ()
"Return current msg id for an `hmail:reader' buffer as a string, else nil.
Signals error when current mail reader is not supported."
(let* ((reader (symbol-name hmail:reader))
;; (toggled)
)
(unless (fboundp 'rmail:msg-hdrs-full)
(error "(rmail:msg-id-get): Invalid mail reader: %s" reader))
(save-excursion
(unwind-protect
(progn
;; (setq toggled (rmail:msg-hdrs-full nil))
(goto-char (point-min))
(when (re-search-forward (concat rmail:msg-hdr-prefix "\\(.+\\)"))
;; Found matching msg
(buffer-substring (match-beginning 2) (match-end 2))))
;; (rmail:msg-hdrs-full toggled)
()))))
;;; ------------------------------------------------------------------------
;;; Each mail reader-specific Hyperbole support module must also define
;;; the following functions, commonly aliased to existing mail reader
;;; functions within the "-init" function of the Hyperbole module.
;;; See "hrmail.el" for examples.
;;;
;;; rmail:get-new, rmail:msg-forward, rmail:summ-msg-to, rmail:summ-new
(provide 'hmail)
;;; hmail.el ends here