tach.el: an attachment interface for message mode.
authorJesse Rosenthal <jrosenthal@jhu.edu>
Fri, 20 Jan 2012 17:04:49 +0000 (12:04 +1900)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:42:49 +0000 (09:42 -0800)
6f/7e031d8e9452333d490a9d4643d9bffd39be86 [new file with mode: 0644]

diff --git a/6f/7e031d8e9452333d490a9d4643d9bffd39be86 b/6f/7e031d8e9452333d490a9d4643d9bffd39be86
new file mode 100644 (file)
index 0000000..7d0c395
--- /dev/null
@@ -0,0 +1,452 @@
+Return-Path: <prvs=359e35a8b=jrosenthal@jhu.edu>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+       by olra.theworths.org (Postfix) with ESMTP id 9CC57431FAF\r
+       for <notmuch@notmuchmail.org>; Fri, 20 Jan 2012 09:03:51 -0800 (PST)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -2.29\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-2.29 tagged_above=-999 required=5\r
+       tests=[RCVD_IN_DNSWL_MED=-2.3, T_MIME_NO_TEXT=0.01] autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+       with ESMTP id sYfZCLIXd0wu for <notmuch@notmuchmail.org>;\r
+       Fri, 20 Jan 2012 09:03:51 -0800 (PST)\r
+Received: from ipex3.johnshopkins.edu (ipex3.johnshopkins.edu\r
+       [128.220.161.140]) (using TLSv1 with cipher RC4-SHA (128/128 bits))\r
+       (No client certificate requested)\r
+       by olra.theworths.org (Postfix) with ESMTPS id ED671431FAE\r
+       for <notmuch@notmuchmail.org>; Fri, 20 Jan 2012 09:03:50 -0800 (PST)\r
+X-IronPort-AV: E=Sophos;i="4.71,543,1320642000"; \r
+       d="el'?scan'208";a="183159808"\r
+Received: from unknown (HELO watt) ([10.161.33.18])\r
+       by ipex3.johnshopkins.edu with ESMTP/TLS/AES256-SHA;\r
+       20 Jan 2012 12:03:50 -0500\r
+Received: from jkr by watt with local (Exim 4.76)\r
+       (envelope-from <jrosenthal@jhu.edu>)\r
+       id 1RoHtR-0007y8-Jr; Fri, 20 Jan 2012 12:04:49 -0500\r
+From: Jesse Rosenthal <jrosenthal@jhu.edu>\r
+To: notmuch@notmuchmail.org\r
+Subject: tach.el: an attachment interface for message mode.\r
+Date: Fri, 20 Jan 2012 12:04:49 -0500\r
+Message-ID: <877h0mwbim.fsf@jhu.edu>\r
+MIME-Version: 1.0\r
+Content-Type: multipart/mixed; boundary="=-=-="\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+       <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Fri, 20 Jan 2012 17:03:51 -0000\r
+\r
+--=-=-=\r
+\r
+Dear All,\r
+\r
+I sent this to the list a couple of years back, but now that things are\r
+moving again, and there are new eyes on the list, I thought I'd send it\r
+again. I believe I'm the only person to use this (and might well\r
+continue to be so) but I've been using it for a couple of years without\r
+any problems, and it has made using message mode a lot more\r
+convenient. As far as I know, this doesn't intersect with the recent\r
+security problems pointed out in mml-mode, but I could be wrong.\r
+\r
+My issue was this: the handling of (outgoing) attachments in\r
+message-mode left a lot to be desired. MML's markup can be confusing,\r
+and can easily be edited by mistake.\r
+\r
+Thus: tach.el. Tach is a minor mode that adds mutt-like attachment\r
+handling to message mode. It's not notmuch specific, but I wrote it to\r
+use with notmuch, and I thought it might be of use to some on the\r
+list.\r
+\r
+You can find tach.el attached to this email.\r
+\r
+To use tach, put tach.el in your load-path, and add the following to\r
+your .emacs:\r
+\r
+(require 'tach)\r
+(add-hook 'message-mode-hook 'tach-minor-mode)\r
+\r
+Now when you type "C-c C-a" in message-mode, you should get a new window\r
+with an attachment list. In that window, you can add and delete\r
+attachments using `+' and `-', and scroll through them using the arrow\r
+keys or the emacs direction commands.\r
+\r
+tach.el will convert the attachments into MML markup as a last\r
+step before sending. Hopefully you should never have to deal with it by\r
+hand.\r
+\r
+Some details: tach actually makes a numerical list at the bottom of the\r
+message itself, separated by a custom separator. The message is narrowed\r
+to above this separator, and the attachment window is an indirect buffer\r
+narrowed to the region below the separator. The separator is erased when\r
+the messages are translated to mml markup at the end.\r
+\r
+This has remained at its earliest stages, and the usual disclaimers\r
+apply. It certainly needs more a lot more commenting and\r
+documentation. But I thought it might be useful, or at least fun to play\r
+around with. And it might fill a niche for some users, as notmuch's\r
+popularity continues to grow.\r
+\r
+Best,\r
+Jesse\r
+\r
+\r
+\r
+--=-=-=\r
+Content-Type: application/emacs-lisp\r
+Content-Disposition: attachment; filename=tach.el\r
+Content-Transfer-Encoding: quoted-printable\r
+\r
+;; tach.el -- Interface for handling attachments in message-mode\r
+\r
+;; Filename: tach.el\r
+;; Copyright (C) 2010 Jesse Rosenthal\r
+;; Author: Jesse Rosenthal <jrosenthal@jhu.edu>\r
+;; Maintainer: Jesse Rosenthal <jrosenthal@jhu.edu>\r
+;; Created: 18 Feb 2010\r
+;; Description: Handles attachments for message mode\r
+;; Version 0.01alpha\r
+\r
+;; This file is not part of GNU Emacs.\r
+\r
+;; This file is free software; you can redistribute it and/or modify\r
+;; it under the terms of the GNU General Public License as published\r
+;; by the Free Software Foundation; either version 2, or (at your\r
+;; option) any later version.\r
+\r
+;; This program is distributed in the hope that it will be useful,\r
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+;; GNU General Public License for more details.\r
+\r
+;; You should have received a copy of the GNU General Public License\r
+;; along with GNU Emacs; see the file COPYING.  If not, write to the\r
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\r
+;; Boston, MA 02110-1301, USA.\r
+\r
+;;; Commentary:\r
+\r
+;; To use: add the following to your .emacs:=20\r
+;;   (require 'tach)\r
+;;   (add-hook 'message-mode-hook 'tach-minor-mode)\r
+;;\r
+;; Pressing `C-cC-a' in message mode will open up an attachment\r
+;; window. The first time you open it, it will prompt for a file name.\r
+;;\r
+;; In the attachment window, you can press `+' to add a file, or `-'\r
+;; to remove one.\r
+;;\r
+;; Note that the attachment window is actually a different view of the\r
+;; message buffer, so that if there is some failure, the attachment\r
+;; list will be saved at the bottom of the message, as a numerical\r
+;; list under a customizable separator.\r
+;;\r
+;; The files will be added to the outgoing message by mml before it is\r
+;; sent.\r
+\r
+\r
+(require 'message)\r
+(require 'mml)\r
+\r
+(defconst tach-sep  "--attachments follow this line--")\r
+\r
+(defconst tach-line-regexp "^\\([0-9]+.\\) +\\(.+?\\) +\\(\\[.+, [0-9\.]+[K=\r
+M]\\]\\)$")\r
+\r
+(defvar tach-send-confirmation nil)\r
+\r
+(defvar tach-buffer-name)\r
+(make-variable-buffer-local 'tach-buffer-name)\r
+\r
+(defvar tach-mode-hooks 'nil)\r
+(make-variable-buffer-local 'tach-mode-hooks)\r
+\r
+(defvar tach-mode-map\r
+  (let ((map (make-sparse-keymap)))\r
+    (define-key map "+" 'tach-add-file)\r
+    (define-key map "-" 'tach-delete-file)\r
+    (define-key map "\C-c\C-c" 'tach-send-from-attach-buffer)\r
+    (define-key map [up] 'tach-prev-entry)\r
+    (define-key map [down] 'tach-next-entry)\r
+    (define-key map "n" 'tach-next-entry)\r
+    (define-key map "p" 'tach-prev-entry)\r
+    (define-key map "\C-n" 'tach-next-entry)\r
+    (define-key map "\C-p" 'tach-prev-entry)\r
+    map)\r
+  "Keymap for attachment mode")\r
+(fset 'tach-mode-map tach-mode-map)\r
+\r
+(defvar tach-minor-mode-map\r
+  (let ((map (make-sparse-keymap)))\r
+    (define-key map "\C-c\C-a"  'tach-goto)\r
+    map)\r
+  "Keymap for attachment minor mode")\r
+(fset 'tach-minor-mode-map tach-minor-mode-map)\r
+\r
+(defun tach-mode ()\r
+  (interactive)\r
+  (kill-all-local-variables)\r
+  (use-local-map 'tach-mode-map)\r
+  (hl-line-mode 1)\r
+  (setq major-mode 'tach-mode\r
+       mode-name "attachment")\r
+  (run-hooks 'tach-mode-hooks)\r
+  (widen)\r
+  (narrow-to-region (tach-buffer-point-min) (point-max))\r
+  (setq buffer-read-only t))\r
+\r
+(defun tach-buffer-point-min ()\r
+  (save-excursion\r
+    (goto-char (point-max))\r
+    (search-backward-regexp tach-sep)\r
+    (search-forward-regexp (concat tach-sep "\n"))\r
+    (point)))\r
+\r
+\r
+(defun tach-message-point-max ()\r
+  (save-excursion\r
+    (goto-char (point-max))\r
+    (search-backward-regexp tach-sep)\r
+    (point)))\r
+=20=20\r
+\r
+(defun tach-first-entry-p ()\r
+  (save-restriction\r
+    (widen)\r
+    (save-excursion\r
+      (forward-line -1)\r
+      (looking-at (concat "^" tach-sep "%")))))\r
+\r
+(defun tach-last-entry-p ()\r
+  (save-excursion\r
+    (forward-line)\r
+    (looking-at "^\s*$")))\r
+\r
+(defun tach-next-entry ()\r
+  (interactive)\r
+  (unless (tach-last-entry-p)\r
+    (forward-line 1)))\r
+\r
+(defun tach-prev-entry ()\r
+  (interactive)\r
+  (unless (tach-first-entry-p)\r
+    (forward-line -1)))\r
+=20=20=20=20\r
+\r
+(defun tach-has-attachments-p ()\r
+  (interactive)\r
+  (save-excursion\r
+  (goto-char (point-max))\r
+  (cond ((re-search-backward (concat "^" tach-sep "$")  nil t)\r
+        (forward-line)\r
+        (while (looking-at tach-line-regexp)\r
+          (forward-line))\r
+        (let ((remaining=20\r
+               (buffer-substring-no-properties (point) (point-max))))\r
+          (if (string-match "[^\s\n]" remaining)\r
+              nil\r
+            t)))\r
+       (t\r
+        nil))))\r
+\r
+(defun tach-message-initialize ()\r
+  (save-excursion\r
+   (unless (tach-has-attachments-p)\r
+     (goto-char (point-max))\r
+     (insert (concat "\n" tach-sep "\n")))\r
+    (narrow-to-region (point-min) (tach-message-point-max))))\r
+\r
+(defun tach-goto ()\r
+  (interactive)\r
+  (if (get-buffer tach-buffer-name)\r
+      (pop-to-buffer tach-buffer-name)\r
+    ;else\r
+    (tach-message-initialize)\r
+    (pop-to-buffer (make-indirect-buffer=20\r
+                   (current-buffer)\r
+                   tach-buffer-name)))\r
+  (tach-mode))\r
+\r
+(defun tach-read-list ()\r
+  (save-excursion\r
+    (let ((output nil))\r
+      (goto-char (point-max))\r
+      (re-search-backward (concat "^" tach-sep "$"))\r
+      (forward-line)\r
+      (while (and (looking-at tach-line-regexp)\r
+                 (not (=3D (line-end-position) (point-max))))\r
+       (setq output (cons (replace-regexp-in-string\r
+                           tach-line-regexp "\\2"\r
+                           (buffer-substring-no-properties (line-beginning-position) (line-end-=\r
+position)))\r
+                          output))\r
+       (forward-line))\r
+      (reverse output))))\r
+\r
+(defun tach-delete-list ()\r
+  (save-excursion\r
+    (goto-char (point-max))\r
+    (re-search-backward (concat "^" tach-sep "$"))\r
+    (end-of-line)\r
+    (delete-region (point) (point-max))))\r
+\r
+(defun tach-write-list (lst)\r
+  (save-excursion\r
+    (goto-char (point-max))\r
+    (re-search-backward (concat "^" tach-sep "$"))\r
+    (end-of-line)\r
+    (newline)\r
+    (let ((counter 1))\r
+      (dolist (elt lst)\r
+       (insert (concat (int-to-string counter) ". " elt\r
+                       "  ["\r
+                       (if (mm-default-file-encoding elt)\r
+                           (mm-default-file-encoding elt)\r
+                         "(type unknown)")\r
+                       ", "\r
+                       (tach-format-file-size (nth 7 (file-attributes elt)))\r
+                       "]"))\r
+       (newline)\r
+       (setq counter (+ counter 1))))))\r
+\r
+(defun tach-format-file-size (bytes)\r
+  (let ((kbytes (fceiling (/ bytes 1024.0))))\r
+    (cond ((< kbytes 1)p\r
+          (format "%.1fK" kbytes))\r
+         ((< kbytes 1000)=20\r
+          (format "%.0fK" kbytes))\r
+         (t\r
+          (format "%.1fM" (/ kbytes 1000.0))))))\r
+\r
+(defun tach-first-n-items (lst n)\r
+  (let ((x 0)\r
+       y)\r
+    (if (> n (length lst))\r
+       (setq y lst)\r
+      (while (< x n)\r
+       (setq y (nconc y (list (nth x lst)))\r
+             x (1+ x))))\r
+    y))\r
+\r
+(defun tach-insert-item-at-idx (item idx lst)\r
+  (append (tach-first-n-items lst idx) (cons item (nthcdr idx lst))))\r
+\r
+(defun tach-remove-item-at-idx (idx lst)\r
+  (append (tach-first-n-items lst idx) (nthcdr (+ 1 idx) lst)))\r
+\r
+(defun tach-add-file (f &optional idx)\r
+  (interactive "fFile to attach: ")\r
+  (if (file-directory-p f)\r
+      (error "Cannot attach a directory")\r
+    ;;else\r
+    (when buffer-read-only\r
+      (setq buffer-read-only nil))\r
+    (widen)\r
+    (let ((file-lst (tach-read-list))\r
+         (orig-line (line-number-at-pos))\r
+         (orig-point (point)))\r
+      (tach-delete-list)\r
+      (when (null idx)\r
+       (cond ((=3D (length file-lst) 0)\r
+              (setq idx 0))\r
+             (t\r
+              (setq idx (- orig-line (line-number-at-pos))))))\r
+      (tach-write-list=20\r
+       (tach-insert-item-at-idx f idx file-lst)))\r
+      (narrow-to-region (tach-buffer-point-min) (point-max))\r
+      (forward-line idx)\r
+      (when (null buffer-read-only)\r
+       (setq buffer-read-only t))))\r
+\r
+(defun tach-delete-file (&optional idx)\r
+  (interactive)\r
+    (when buffer-read-only\r
+      (setq buffer-read-only nil))\r
+    (widen)\r
+    (let ((file-lst (tach-read-list))\r
+         (orig-line (line-number-at-pos))\r
+         (orig-point (point)))\r
+      (tach-delete-list)\r
+      (when (null idx)\r
+       (setq idx (- (- orig-line (line-number-at-pos)) 1)))\r
+      (tach-write-list (tach-remove-item-at-idx idx file-lst)))\r
+    (narrow-to-region (tach-buffer-point-min) (point-max))\r
+    (unless (=3D idx 0)\r
+      (forward-line (- idx 1)))\r
+    (when (null buffer-read-only)\r
+      (setq buffer-read-only t)))\r
+\r
+(defun tach-mml-files ()\r
+  (interactive)\r
+  (when (tach-has-attachments-p)\r
+  (widen)\r
+  (let ((file-lst (tach-read-list)))\r
+    (tach-delete-list)\r
+    (goto-char (point-max))\r
+    (re-search-backward (concat "^" tach-sep "$"))\r
+    (delete-region (point) (point-max))\r
+    (newline)\r
+    (dolist (f file-lst)\r
+      (mml-attach-file f)\r
+      (goto-char (point-max))))))\r
+\r
+\r
+(defun tach-kill-buffer ()\r
+  (when (get-buffer tach-buffer-name)\r
+    (delete-windows-on tach-buffer-name)\r
+    (kill-buffer tach-buffer-name)))\r
+\r
+(defun tach-send-from-attach-buffer ()\r
+  (interactive)\r
+  (when (buffer-base-buffer tach-buffer-name)\r
+    (with-current-buffer (buffer-base-buffer tach-buffer-name)\r
+      (message-send-and-exit))))\r
+\r
+\r
+\r
+(define-minor-mode tach-minor-mode ()\r
+  nil\r
+  " Tach"\r
+  'tach-minor-mode-map\r
+  (if tach-minor-mode\r
+      (progn\r
+       ;; set the attachment buffer local variable\r
+       (setq tach-buffer-name\r
+             (generate-new-buffer-name\r
+              (concat=20\r
+               "*"\r
+               (replace-regexp-in-string=20\r
+                "^\\(\**\\)\\(.*?\\)\\(\**\\)$" "\\2" (buffer-name))\r
+               "-attachments*")))\r
+       ;; add the send hook\r
+       (add-hook 'message-send-hook '(lambda ()\r
+                                            (tach-mml-files)\r
+                                            (tach-kill-buffer))))\r
+    ;; remove the send hook\r
+    (remove-hook 'message-send-hook '(lambda ()\r
+                                           (tach-mml-files)\r
+                                           (tach-kill-buffer)))))\r
+\r
+\r
+\r
+=20=20=20=20\r
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\r
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\r
+\r
+(provide 'tach)\r
+\r
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\r
+\r
+\r
+--=-=-=--\r