emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] 01/01: Merging Gnorb commits up to 1.0.1


From: Eric Abrahamsen
Subject: [elpa] 01/01: Merging Gnorb commits up to 1.0.1
Date: Sat, 25 Oct 2014 15:20:12 +0000

girzel pushed a commit to branch master
in repository elpa.

commit ce7004456df8d17d1b1bb9b1feab3ddafb1e078a
Author: Eric Abrahamsen <address@hidden>
Date:   Sat Oct 25 08:19:41 2014 -0700

    Merging Gnorb commits up to 1.0.1
---
 packages/gnorb/.gitignore        |    3 +-
 packages/gnorb/NEWS              |   10 +
 packages/gnorb/README.org        |   56 ++++++
 packages/gnorb/gnorb-bbdb.el     |  123 ++++++++++----
 packages/gnorb/gnorb-gnus.el     |  353 +++++++++++++++++++------------------
 packages/gnorb/gnorb-org.el      |   69 ++++----
 packages/gnorb/gnorb-registry.el |   38 ++++-
 packages/gnorb/gnorb-utils.el    |  179 +++++++++++++++++--
 packages/gnorb/gnorb.el          |   17 +-
 packages/gnorb/gnorb.info        |  163 ++++++++++--------
 packages/gnorb/gnorb.org         |   32 +---
 packages/gnorb/gnorb.texi        |  110 +++++++-----
 packages/gnorb/nngnorb.el        |   45 +++--
 13 files changed, 774 insertions(+), 424 deletions(-)

diff --git a/packages/gnorb/.gitignore b/packages/gnorb/.gitignore
index 60fa2ca..c8ea459 100644
--- a/packages/gnorb/.gitignore
+++ b/packages/gnorb/.gitignore
@@ -1,4 +1,5 @@
 *.elc
 notes.org
 gnorb-pkg.el
-gnorb-autoloads.el
\ No newline at end of file
+gnorb-autoloads.el
+TAGS
diff --git a/packages/gnorb/NEWS b/packages/gnorb/NEWS
index 728f219..59bc343 100644
--- a/packages/gnorb/NEWS
+++ b/packages/gnorb/NEWS
@@ -1,5 +1,15 @@
 GNU Emacs Gnorb NEWS -- history of user-visible changes.  -*- org -*-
 
+* Version 1.0.1 [2014-10-22 Wed]
+** Deleting associations
+It's now possible to delete associations between messages and
+headings; the user is also prompted to do this at a few points.
+** Link following
+Following links to messages is a little more clever, and will re-use
+existing windows/frames when possible.
+** Cleanups/Bugfixes
+Proper autoloads, addressing compiler warnings, better chain of
+requires, bugfixes.
 * Version 1 [2014-10-07 Tue]
 ** First Elpa Version
 ** Email Tracking
diff --git a/packages/gnorb/README.org b/packages/gnorb/README.org
index cbb13af..1f8f82f 100644
--- a/packages/gnorb/README.org
+++ b/packages/gnorb/README.org
@@ -29,3 +29,59 @@ If you want to use Gnorb for tracking emails with TODOs, 
you'll need
 to add a nngnorb server to your `gnus-secondary-select-methods'
 variable, then call `gnorb-tracking-initialize' in your init files.
 Again, see the manual for details.
+** Roadmap
+*** More fully utilize nngnorb
+Instead of just being place to put nnir searches, the nngnorb server
+should have groups in its own right. Users will still be able to make
+ephemeral search groups if they like, but they'll also have the option
+of creating regular Gnus groups that track certain Org subtrees. These
+groups will be persistent, and they will auto-update, so it's possible
+to handle more TODO stuff from the Gnus group buffer. Copying a
+message into the group will set up tracking for the message; deleting
+it from the group will remove tracking.
+*** Comprehensive views of related information
+For instance, A `gnorb-view' command that takes a tags-todo search
+phrase (or a single Org heading ID), finds all relevant messages, Org
+headings, and BBDB records, and sets up a four-pane view: Org Agenda,
+BBDB buffer, Gnus Summary buffer, and Gnus Article buffer.
+
+Or something like that -- more pondering is necessary.
+*** Consider consolidating posting styles, personas, etc
+There are many solutions out there for setting up "personas" in Gnus
+or elsewhere: wrappers for mail composition and reply that create
+certain identities by setting variables and mail headers.
+
+Do we need another one? Gnorb seems so well placed for this sort of
+thing: we've already got bbdb-posting-styles, and allowing further
+configuration based on, say, the Org tags of the subtree we're
+composing messages from... Or maybe it's just a case of NIH.
+** To do list
+*** TODO Agenda date-span message search
+Provide an Org Agenda command that does an email search for messages
+received in the visible date span, or day under point, etc. Make it
+work in the calendar, as well?
+*** TODO Capture to child/subtree trigger actions
+Add trigger actions that create new sibling or child headings on the
+original Org heading.
+*** TODO Gnus message tagging
+Allow tagging of Gnus messages, by giving the message's registry entry
+an 'org-tags key.
+*** TODO Email subtree export to doc and rtf
+When using `gnorb-email-subtree', provide built-in options for
+exporting to doc and rtf attachments; these are such commonly-needed
+formats. Do the odt conversion automatically.
+*** TODO Collect BBDB messages by thread
+At present, when you collect message links on a BBDB contact, each
+message is a separate link. If you have lengthy conversations with
+this contact, you'll get a whole bunch of links with the same summary:
+not very useful. Provide an option to collect one /thread/ per link:
+each link represents the top-level message in the thread, and
+following the link opens a Summary buffer where the whole thread is
+visible.
+*** TODO Automatic org-tagging for BBDB contacts
+When messages from a contact are associated with an Org heading, make
+it possible for the contact to inherit that heading's tags
+automatically.
+*** TODO gnorb-bbdb-view
+Provide a `gnorb-bbdb-view' command that opens a Summary buffer
+containing all the tracked messages from the contact(s) under point.
diff --git a/packages/gnorb/gnorb-bbdb.el b/packages/gnorb/gnorb-bbdb.el
index 2404985..66be793 100644
--- a/packages/gnorb/gnorb-bbdb.el
+++ b/packages/gnorb/gnorb-bbdb.el
@@ -24,6 +24,10 @@
 
 ;;; Code:
 
+(eval-when-compile
+  (require 'cl))
+
+(require 'bbdb)
 (require 'gnorb-utils)
 (require 'cl-lib)
 
@@ -151,6 +155,61 @@ An example value might look like:"
 
 (defvar message-mode-hook)
 
+(when (fboundp 'bbdb-record-xfield-string)
+  (fset (intern (format "bbdb-read-xfield-%s"
+                       gnorb-bbdb-org-tag-field))
+       (lambda (&optional init)
+         (gnorb-bbdb-read-org-tags init)))
+
+  (fset (intern (format "bbdb-display-%s-multi-line"
+                       gnorb-bbdb-org-tag-field))
+       (lambda (record)
+         (gnorb-bbdb-display-org-tags record))))
+
+(defun gnorb-bbdb-read-org-tags (&optional init)
+  "Read Org mode tags, with `completing-read-multiple'."
+  (if (string< "24.3" (substring emacs-version 0 4))
+      (let ((crm-separator
+            (concat "[ \t\n]*"
+                    (cadr (assq gnorb-bbdb-org-tag-field
+                                bbdb-separator-alist))
+                    "[ \t\n]*"))
+           (crm-local-completion-map bbdb-crm-local-completion-map)
+           (table (cl-mapcar #'car
+                          (org-global-tags-completion-table
+                           (org-agenda-files))))
+           (init (if (consp init)
+                     (bbdb-join init
+                                (nth 2 (assq gnorb-bbdb-org-tag-field
+                                             bbdb-separator-alist)))
+                   init)))
+       (completing-read-multiple
+        "Tags: " table
+        nil nil init))
+    (bbdb-split gnorb-bbdb-org-tag-field
+               (bbdb-read-string "Tags: " init))))
+
+(defun gnorb-bbdb-display-org-tags (record)
+  "Display the Org tags associated with the record.
+
+Org tags are stored in the `gnorb-bbdb-org-tags-field'."
+  (let ((full-field (assq gnorb-bbdb-org-tag-field
+                         (bbdb-record-xfields record)))
+       (val (bbdb-record-xfield
+             record
+             gnorb-bbdb-org-tag-field)))
+    (when val
+      ;; We already know that `fmt' and `indent' are dynamically
+      ;; bound, shut up about it.
+      (with-no-warnings
+       (bbdb-display-text (format fmt gnorb-bbdb-org-tag-field)
+                         `(xfields ,full-field field-label)
+                         'bbdb-field-name)
+       (if (consp val)
+          (bbdb-display-list val gnorb-bbdb-org-tag-field "\n")
+        (insert
+         (bbdb-indent-string (concat val "\n") indent)))))))
+
 ;;;###autoload
 (defun gnorb-bbdb-mail (records &optional subject n verbose)
   "\\<bbdb-mode-map>Acts just like `bbdb-mail', except runs
@@ -338,13 +397,13 @@ both, use \"C-u\" before the \"*\"."
          (delete-dups
           (cl-mapcan (lambda (r)
                     (bbdb-record-xfield-split r gnorb-bbdb-org-tag-field))
-                  records))
+                  records)))
          "|")))
     (if tag-string
        ;; C-u = all headings, not just todos
        (org-tags-view (not (equal current-prefix-arg '(4)))
                        tag-string)
-      (error "No org-tags field present"))))
+      (error "No org-tags field present")))
 
 ;;;###autoload
 (defun gnorb-bbdb-mail-search (records)
@@ -400,39 +459,43 @@ layout type."
     (define-key map [mouse-1] 'gnorb-bbdb-mouse-open-link)
     (define-key map (kbd "<RET>") 'gnorb-bbdb-RET-open-link)
     (when val
-      ;; indent and fmt are dynamically bound
       (when (eq format 'multi)
-       (bbdb-display-text (format fmt gnorb-bbdb-messages-field)
-                          `(xfields ,full-field field-label)
-                          'bbdb-field-name))
+       (with-no-warnings ; For `fmt'
+         (bbdb-display-text (format fmt gnorb-bbdb-messages-field)
+                            `(xfields ,full-field field-label)
+                            'bbdb-field-name)))
       (insert (cond ((and (stringp val)
                          (eq format 'multi))
-                    (bbdb-indent-string (concat val "\n") indent))
+                    (with-no-warnings ; For `indent'
+                      (bbdb-indent-string (concat val "\n") indent)))
                    ((listp val)
+                    ;; Why aren't I using `bbdb-display-list' with a
+                    ;; preformatted list of messages?
                     (concat
-                     (bbdb-indent-string
-                      (mapconcat
-                       (lambda (m)
-                         (prog1
-                             (org-propertize
-                              (concat
-                               (format-time-string
-                                (replace-regexp-in-string
-                                 "%:subject" (gnorb-bbdb-link-subject m)
-                                 (replace-regexp-in-string
-                                  "%:count" (number-to-string count)
-                                  (if (eq format 'multi)
-                                      gnorb-bbdb-message-link-format-multi
-                                    gnorb-bbdb-message-link-format-one)))
-                                (gnorb-bbdb-link-date m)))
-                              'face 'gnorb-bbdb-link
-                              'mouse-face 'highlight
-                              'gnorb-bbdb-link-count count
-                              'keymap map)
-                           (cl-incf count)))
-                       val (if (eq format 'multi)
-                               "\n" ", "))
-                      indent)
+                     (with-no-warnings ; For `indent' again
+                       (bbdb-indent-string
+                        (mapconcat
+                         (lambda (m)
+                           (prog1
+                               (org-propertize
+                                (concat
+                                 (format-time-string
+                                  (replace-regexp-in-string
+                                   "%:subject" (gnorb-bbdb-link-subject m)
+                                   (replace-regexp-in-string
+                                    "%:count" (number-to-string count)
+                                    (if (eq format 'multi)
+                                        gnorb-bbdb-message-link-format-multi
+                                      gnorb-bbdb-message-link-format-one)))
+                                  (gnorb-bbdb-link-date m)))
+                                'face 'gnorb-bbdb-link
+                                'mouse-face 'highlight
+                                'gnorb-bbdb-link-count count
+                                'keymap map)
+                             (incf count)))
+                         val (if (eq format 'multi)
+                                 "\n" ", "))
+                        indent))
                      (if (eq format 'multi) "\n" "")))
                    (t
                     ""))))))
diff --git a/packages/gnorb/gnorb-gnus.el b/packages/gnorb/gnorb-gnus.el
index 75da114..e650677 100644
--- a/packages/gnorb/gnorb-gnus.el
+++ b/packages/gnorb/gnorb-gnus.el
@@ -24,6 +24,10 @@
 
 ;;; Code:
 
+(eval-when-compile
+  (require 'cl))
+
+(require 'gnus)
 (require 'gnorb-utils)
 
 (declare-function org-gnus-article-link "org-gnus"
@@ -157,27 +161,15 @@ each message."
   "Attach HANDLE to an existing org heading."
   (let* ((filename (gnorb-gnus-save-part handle))
         (org-refile-targets gnorb-gnus-trigger-refile-targets)
-        (ref-msg-ids
-         (concat (gnus-fetch-original-field "references") " "
-                 (gnus-fetch-original-field "in-reply-to")))
-        (rel-heading
-         (when gnorb-tracking-enabled
-           (car (gnorb-find-visit-candidates
-                 ref-msg-ids))))
-        (org-heading
-         (if (and rel-heading
-                  (y-or-n-p (message
-                             "Attach part to %s"
-                             (gnorb-pretty-outline rel-heading))))
-             rel-heading
-           (org-refile-get-location "Attach part to" nil t))))
+        (headers (gnus-data-header
+                  (gnus-data-find
+                   (gnus-summary-article-number))))
+        (tracked-headings (gnorb-find-tracked-headings headers))
+        (target-heading
+         (gnorb-choose-trigger-heading tracked-headings)))
     (require 'org-attach)
     (save-window-excursion
-      (if (stringp org-heading)
-         (org-id-goto org-heading)
-       (progn
-         (find-file (nth 1 org-heading))
-         (goto-char (nth 3 org-heading))))
+      (org-id-goto target-heading)
       (org-attach-attach filename nil 'mv))))
 
 (defun gnorb-gnus-save-part (handle)
@@ -316,15 +308,21 @@ information about the outgoing message into
 
 ;;;###autoload
 (defun gnorb-gnus-outgoing-do-todo (&optional arg)
-  "Call this function to use the message currently being composed
-as an email todo action. If it's a new message, or a reply to a
-message that isn't referenced by any TODOs, a new TODO will be
-created. If it references an existing TODO, you'll be prompted to
-trigger a state-change or a note on that TODO.
+  "Use this command to use the message currently being composed
+as an email todo action.
+
+If it's a new message, or a reply to a message that isn't
+referenced by any TODOs, a new TODO will be created.
+
+If it references an existing TODO, you'll be prompted to trigger
+a state-change or a note on that TODO after the message is sent.
 
-Otherwise, you can call it with a prefix arg to associate the
-sending/sent message with an existing Org subtree, and trigger an
-action on that subtree.
+You can call it with a prefix arg to force choosing an Org
+subtree to associate with.
+
+If you've already called this command, but realize you made a
+mistake, you can call this command with a double prefix to reset
+the association.
 
 If a new todo is made, it needs a capture template: set
 `gnorb-gnus-new-todo-capture-key' to the string key for the
@@ -340,9 +338,9 @@ work."
   (interactive "P")
   (let ((org-refile-targets gnorb-gnus-trigger-refile-targets)
        (compose-marker (make-marker))
-       header-ids ref-ids rel-headings gnorb-window-conf
-       reply-id reply-group in-reply-to)
-    (when arg
+       header-ids ref-ids rel-headings
+       gnorb-window-conf in-reply-to)
+    (when (equal arg '(4))
       (setq rel-headings
            (org-refile-get-location "Trigger action on" nil t))
       (setq rel-headings
@@ -353,87 +351,90 @@ work."
     (if (not (eq major-mode 'message-mode))
        ;; The message is already sent, so we're relying on whatever was
        ;; stored into `gnorb-gnus-message-info'.
-       (if arg
-           (progn
-             (push (car rel-headings) gnorb-message-org-ids)
-             (gnorb-org-restore-after-send))
-         (setq ref-ids (plist-get gnorb-gnus-message-info :refs))
-         (if ref-ids
-             ;; the message might be relevant to some TODO
-             ;; heading(s). But if there had been org-id
-             ;; headers, they would already have been
-             ;; handled when the message was sent.
+       (if (equal arg '(16))
+           (user-error "A double prefix is only useful with an
+           unsent message.")
+         (if arg
              (progn
-               (setq rel-headings (gnorb-find-visit-candidates ref-ids))
-               (if (not rel-headings)
-                   (gnorb-gnus-outgoing-make-todo-1)
-                 (dolist (h rel-headings)
-                   (push h gnorb-message-org-ids))
-                 (gnorb-org-restore-after-send)))
-           ;; not relevant, just make a new TODO
-           (gnorb-gnus-outgoing-make-todo-1)))
+               (push (car rel-headings) gnorb-message-org-ids)
+               (gnorb-org-restore-after-send))
+           (setq ref-ids (plist-get gnorb-gnus-message-info :refs))
+           (if ref-ids
+               ;; the message might be relevant to some TODO
+               ;; heading(s). But if there had been org-id
+               ;; headers, they would already have been
+               ;; handled when the message was sent.
+               (progn
+                 (setq rel-headings (gnorb-find-visit-candidates ref-ids))
+                 (if (not rel-headings)
+                     (gnorb-gnus-outgoing-make-todo-1)
+                   (dolist (h rel-headings)
+                     (push h gnorb-message-org-ids))
+                   (gnorb-org-restore-after-send)))
+             ;; not relevant, just make a new TODO
+             (gnorb-gnus-outgoing-make-todo-1))))
       ;; We are still in the message composition buffer, so let's see
       ;; what we've got.
 
-      ;; What we want is a link to the original message we're replying
-      ;; to, if this is actually a reply.
-      (when message-reply-headers
-       (setq reply-id (aref message-reply-headers 4)))
-      ;; Save-excursion won't work, because point will move if we
-      ;; insert headings.
-      (move-marker compose-marker (point))
-      (save-restriction
-       (widen)
-       (message-narrow-to-headers-or-head)
-       (setq header-ids (mail-fetch-field gnorb-mail-header nil nil t))
-       ;; With a prefix arg we do not check references, because the
-       ;; whole point is to add new references. We still want to know
-       ;; what org id headers are present, though, so we don't add
-       ;; duplicates.
-       (setq ref-ids (unless arg (mail-fetch-field "References" t)))
-       (setq in-reply-to (unless arg (mail-fetch-field "In-Reply-to" t)))
-       (when in-reply-to
-         (setq ref-ids (concat ref-ids " " in-reply-to)))
-       (setq reply-group (when (mail-fetch-field "X-Draft-From" t)
-                           (car-safe (read (mail-fetch-field "X-Draft-From" 
t)))))
-       ;; when it's a reply, store a link to the reply just in case.
-       ;; This is pretty embarrassing -- we follow a link just to
-       ;; create a link. But I'm not going to recreate all of
-       ;; `org-store-link' by hand.
-       (when (and reply-group reply-id)
-         (save-window-excursion
-           (org-gnus-follow-link reply-group reply-id)
-           (call-interactively 'org-store-link)))
-       (when ref-ids
-         ;; if the References header points to any message ids that are
-         ;; tracked by TODO headings...
-         (setq rel-headings (gnorb-find-visit-candidates ref-ids)))
-       (when rel-headings
-         (goto-char (point-min))
-         (dolist (h (delete-dups rel-headings))
-           ;; then get the org-ids of those headings, and insert
-           ;; them into this message as headers. If the id was
-           ;; already present in a header, don't add it again.
-           (unless (member h header-ids)
-             (goto-char (point-at-bol))
-             (open-line 1)
-             (message-insert-header
-              (intern gnorb-mail-header)
-              h)
-             ;; tell the rest of the function that this is a relevant
-             ;; message
-             (push h header-ids)))))
-      (goto-char compose-marker)
-      (add-to-list
-       'message-exit-actions
-       (if header-ids
-          'gnorb-org-restore-after-send
-        'gnorb-gnus-outgoing-make-todo-1)
-       t)
-      (message
-       (if header-ids
-          "Message will trigger TODO state-changes after sending"
-        "A TODO will be made from this message after it's sent")))))
+      (if (equal arg '(16))
+         ;; Double prefix arg means delete the association we already
+         ;; made.
+         (save-excursion
+           (save-restriction
+             (widen)
+             (setq message-exit-actions
+                   (remove 'gnorb-org-restore-after-send
+                           (remove 'gnorb-gnus-outgoing-make-todo-1
+                                   message-exit-actions)))
+             (message-narrow-to-headers-or-head)
+             (message-remove-header
+              gnorb-mail-header)
+             (message "Message associations have been reset")))
+       ;; Save-excursion won't work, because point will move if we
+       ;; insert headings.
+       (move-marker compose-marker (point))
+       (save-restriction
+         (widen)
+         (message-narrow-to-headers-or-head)
+         (setq header-ids (mail-fetch-field gnorb-mail-header nil nil t))
+         ;; With a prefix arg we do not check references, because the
+         ;; whole point is to add new references. We still want to know
+         ;; what org id headers are present, though, so we don't add
+         ;; duplicates.
+         (setq ref-ids (unless arg (mail-fetch-field "References" t)))
+         (setq in-reply-to (unless arg (mail-fetch-field "In-Reply-to" t)))
+         (when in-reply-to
+           (setq ref-ids (concat ref-ids " " in-reply-to)))
+         (when ref-ids
+           ;; if the References header points to any message ids that are
+           ;; tracked by TODO headings...
+           (setq rel-headings (gnorb-find-visit-candidates ref-ids)))
+         (when rel-headings
+           (goto-char (point-min))
+           (dolist (h (delete-dups rel-headings))
+             ;; then get the org-ids of those headings, and insert
+             ;; them into this message as headers. If the id was
+             ;; already present in a header, don't add it again.
+             (unless (member h header-ids)
+               (goto-char (point-at-bol))
+               (open-line 1)
+               (message-insert-header
+                (intern gnorb-mail-header)
+                h)
+               ;; tell the rest of the function that this is a relevant
+               ;; message
+               (push h header-ids)))))
+       (goto-char compose-marker)
+       (add-to-list
+        'message-exit-actions
+        (if header-ids
+            'gnorb-org-restore-after-send
+          'gnorb-gnus-outgoing-make-todo-1)
+        t)
+       (message
+        (if header-ids
+            "Message will trigger TODO state-changes after sending"
+          "A TODO will be made from this message after it's sent"))))))
 
 (defvar org-capture-link-is-already-stored)
 
@@ -485,7 +486,7 @@ work."
 ;;; call this function on it.
 
 ;;;###autoload
-(defun gnorb-gnus-incoming-do-todo (arg headers &optional id)
+(defun gnorb-gnus-incoming-do-todo (arg &optional id)
   "Call this function from a received gnus message to store a
 link to the message, prompt for a related Org heading, visit the
 heading, and either add a note or trigger a TODO state change.
@@ -500,7 +501,7 @@ there match the value of the `gnorb-org-msg-id-key' 
property for
 any headings. In order for this to work, you will have to have
 loaded org-id, and have the variable `org-id-track-globally' set
 to t (it is, by default)."
-  (interactive (gnus-interactive "P\nH"))
+  (interactive "P")
   (when (not (memq major-mode '(gnus-summary-mode gnus-article-mode)))
     (user-error "Only works in gnus summary or article mode"))
   ;; We should only store a link if it's not already at the head of
@@ -509,7 +510,10 @@ to t (it is, by default)."
   (setq gnorb-window-conf (current-window-configuration))
   (move-marker gnorb-return-marker (point))
   (setq gnorb-gnus-message-info nil)
-  (let* ((msg-id (mail-header-id headers))
+  (let* ((headers (gnus-data-header
+                  (gnus-data-find
+                   (gnus-summary-article-number))))
+        (msg-id (mail-header-id headers))
         (from (mail-header-from headers))
         (subject (mail-header-subject headers))
         (date (mail-header-date headers))
@@ -517,33 +521,43 @@ to t (it is, by default)."
         (group gnus-newsgroup-name)
         (link (call-interactively 'org-store-link))
         (org-refile-targets gnorb-gnus-trigger-refile-targets)
-        (ref-msg-ids (mail-header-references headers))
-        (offer-heading
-         (when (and (not id) ref-msg-ids gnorb-tracking-enabled)
-           (if org-id-track-globally
-               ;; for now we're basically ignoring the fact that
-               ;; multiple candidates could exist; just do the first
-               ;; one.
-               (car (gnorb-find-visit-candidates
-                     ref-msg-ids))
-             (message "Gnorb can't check for relevant headings unless 
`org-id-track-globally' is t")
-             (sit-for 1))))
+        (ref-msg-ids (concat (mail-header-references headers) " "
+                             msg-id))
+        (related-headings
+         (when (and (null id) ref-msg-ids)
+           ;; Specifically ask for zombies, so the user has chance to
+           ;; flush them out.
+           (gnorb-find-tracked-headings headers t)))
         targ)
     (setq gnorb-gnus-message-info
-           `(:subject ,subject :msg-id ,msg-id
-                      :to ,to :from ,from
-                      :link ,link :date ,date :refs ,ref-msg-ids
-                      :group ,group))
+         `(:subject ,subject :msg-id ,msg-id
+                    :to ,to :from ,from
+                    :link ,link :date ,date :refs ,ref-msg-ids
+                    :group ,group))
     (gnorb-gnus-collect-all-attachments nil t)
     ;; Delete other windows, users can restore with
     ;; `gnorb-restore-layout'.
     (delete-other-windows)
     (if id
        (gnorb-trigger-todo-action arg id)
-      (if (and offer-heading
-              (y-or-n-p (format "Trigger action on %s"
-                                (gnorb-pretty-outline offer-heading))))
-         (gnorb-trigger-todo-action arg offer-heading)
+      ;; Flush out zombies (dead associations).
+      (setq related-headings
+           (cl-remove-if
+            (lambda (h)
+              (when (null (org-id-find-id-file h))
+                (when (y-or-n-p
+                       (format
+                        "ID %s no longer exists, disassociate message?"
+                        h))
+                  (gnorb-delete-association msg-id h))))
+            related-headings))
+      (if (catch 'target
+           (dolist (h related-headings nil)
+             (when (yes-or-no-p
+                    (format "Trigger action on %s"
+                            (gnorb-pretty-outline h)))
+               (throw 'target (setq targ h)))))
+         (gnorb-trigger-todo-action arg targ)
        (setq targ (org-refile-get-location
                    "Trigger heading" nil t))
        (find-file (nth 1 targ))
@@ -564,15 +578,17 @@ will all be displayed in an ephemeral group on the 
\"nngnorb\"
 server. There must be an active \"nngnorb\" server for this to
 work."
   (interactive)
+  (require 'nnir)
   (let ((nnir-address
         (or (gnus-method-to-server '(nngnorb))
             (user-error
              "Please add a \"nngnorb\" backend to your gnus installation."))))
     (when (version= "5.13" gnus-version-number)
-      (setq nnir-current-query nil
-           nnir-current-server nil
-           nnir-current-group-marked nil
-           nnir-artlist nil))
+      (with-no-warnings                  ; All these variables are available.
+       (setq nnir-current-query nil
+             nnir-current-server nil
+             nnir-current-group-marked nil
+             nnir-artlist nil)))
     (gnus-group-read-ephemeral-group
      ;; in 24.4, the group name is mostly decorative. in 24.3, the
      ;; query itself is read from there. It should look like (concat
@@ -584,8 +600,7 @@ work."
         (list 'nnir nnir-address)
        (list 'nnir "nnir"))
      nil
-     ret ;; it's possible you can't just put an arbitrary form in
-        ;; here, which sucks.
+     ret
      nil nil
      ;; the following seems to simply be ignored under gnus 5.13
      (list (cons 'nnir-specs (list (cons 'nnir-query-spec `((query . ,str)))
@@ -607,17 +622,18 @@ is relevant to any existing TODO headings. If so, flash a 
message
 to that effect. This function is added to the
 `gnus-article-prepare-hook'. It will only do anything if the
 option `gnorb-gnus-hint-relevant-article' is non-nil."
-  (when (and gnorb-tracking-enabled
-            gnorb-gnus-hint-relevant-article
+  (when (and gnorb-gnus-hint-relevant-article
             (not (memq (car (gnus-find-method-for-group
                              gnus-newsgroup-name))
                        '(nnvirtual nnir))))
-    (let* ((ref-ids (concat
-                    (gnus-fetch-original-field "references") " "
-                    (gnus-fetch-original-field "in-reply-to")))
-          (msg-id (gnus-fetch-original-field "message-id"))
+    (let* ((headers
+           (gnus-data-header
+            (gnus-data-find
+             (gnus-summary-article-number))))
           (assoc-heading
-           (gnus-registry-get-id-key msg-id 'gnorb-ids))
+           (gnus-registry-get-id-key
+            (gnus-fetch-original-field "message-id") 'gnorb-ids))
+          (tracked-headings (gnorb-find-tracked-headings headers))
           (key
            (where-is-internal 'gnorb-gnus-incoming-do-todo
                               nil t))
@@ -625,30 +641,24 @@ option `gnorb-gnus-hint-relevant-article' is non-nil."
       (cond (assoc-heading
             (message "Message is associated with %s"
                      (gnorb-pretty-outline (car assoc-heading) t)))
-           (ref-ids
-            (when (setq rel-headings
-                        (gnorb-find-visit-candidates ref-ids))
-              (message "Possible relevant todo %s, trigger with %s"
-                       (gnorb-pretty-outline (car rel-headings) t)
-                       (if key
-                           (key-description key)
-                         "M-x gnorb-gnus-incoming-do-todo"))))))))
+           (tracked-headings
+            (message "Possible relevant todo %s, trigger with %s"
+                     (gnorb-pretty-outline (car tracked-headings) t)
+                     (if key
+                         (key-description key)
+                       "M-x gnorb-gnus-incoming-do-todo")))
+           (t nil)))))
 
 (add-hook 'gnus-article-prepare-hook 'gnorb-gnus-hint-relevant-message)
 
 (defun gnorb-gnus-insert-format-letter-maybe (header)
-  (if (and gnorb-tracking-enabled
-                (not (memq (car (gnus-find-method-for-group
-                                 gnus-newsgroup-name))
-                           '(nnvirtual nnir))))
-           (let ((ref-ids (mail-header-references header))
-                 (msg-id (mail-header-message-id header)))
-             (if (or (gnus-registry-get-id-key msg-id 'gnorb-ids)
-                     (and ref-ids
-                          (gnorb-find-visit-candidates ref-ids)))
-                 gnorb-gnus-summary-mark
-               " "))
-         " "))
+  (if (not (memq (car (gnus-find-method-for-group
+                      gnus-newsgroup-name))
+                '(nnvirtual nnir)))
+      (if (gnorb-find-tracked-headings header)
+         gnorb-gnus-summary-mark
+       " ")
+    " "))
 
 (fset (intern (concat "gnus-user-format-function-"
                      gnorb-gnus-summary-mark-format-letter))
@@ -658,18 +668,17 @@ option `gnorb-gnus-hint-relevant-article' is non-nil."
 ;;;###autoload
 (defun gnorb-gnus-view ()
   "Display the first relevant TODO heading for the message under point"
-  ;; this is pretty barebones, need to make sure we have a valid
-  ;; article buffer to access, and think about what to do for
-  ;; window-configuration!
-
-  ;; boy is this broken now.
   (interactive)
-  (let ((refs (gnus-fetch-original-field "references"))
-       rel-headings)
-    (when refs
-      (setq rel-headings (gnorb-find-visit-candidates refs))
+  (let ((headers (gnus-data-header
+                  (gnus-data-find
+                   (gnus-summary-article-number))))
+       (tracked-headings
+        (gnorb-find-tracked-headings headers)))
+    (when tracked-headings
+      (setq gnorb-window-conf (current-window-configuration))
+      (move-marker gnorb-return-marker (point))
       (delete-other-windows)
-      (org-id-goto (car rel-headings)))))
+      (org-id-goto (car tracked-headings)))))
 
 (provide 'gnorb-gnus)
 ;;; gnorb-gnus.el ends here
diff --git a/packages/gnorb/gnorb-org.el b/packages/gnorb/gnorb-org.el
index 72d3df8..8ac2503 100644
--- a/packages/gnorb/gnorb-org.el
+++ b/packages/gnorb/gnorb-org.el
@@ -24,6 +24,9 @@
 
 ;;; Code:
 
+(eval-when-compile
+  (require 'cl))
+
 (require 'gnorb-utils)
 (require 'cl-lib)
 
@@ -134,18 +137,18 @@ future!"
       link)))
 
 (defun gnorb-org-restore-after-send ()
-  "After an email is sent, clean up the gnus summary buffer, put
-us back where we came from, and go through all the org ids that
-might have been in the outgoing message's headers and call
-`gnorb-trigger-todo-action' on each one."
+  "After an email is sent, go through all the org ids that might
+have been in the outgoing message's headers and call
+`gnorb-trigger-todo-action' on each one, then put us back where
+we came from."
   (delete-other-windows)
   (dolist (id gnorb-message-org-ids)
     (org-id-goto id)
-    (org-reveal)
     (gnorb-trigger-todo-action nil id))
   ;; this is a little unnecessary, but it may save grief
   (setq gnorb-gnus-message-info nil)
-  (setq gnorb-message-org-ids nil))
+  (setq gnorb-message-org-ids nil)
+  (gnorb-restore-layout))
 
 (defun gnorb-org-extract-links (&optional arg region)
   "See if there are viable links in the subtree under point."
@@ -228,18 +231,21 @@ See the docstring of `gnorb-org-handle-mail' for details."
              (lambda (r l)
                (time-less-p
                 (car (gnus-registry-get-id-key l 'creation-time))
-                (car (gnus-registry-get-id-key r 'creation-time)))))))))
+                (car (gnus-registry-get-id-key r 'creation-time))))))))
+        (msg-id-link
+         (when latest-msg-id
+           (gnorb-msg-id-to-link latest-msg-id))))
     (cond
      ;; If there are no tracked messages, or the user has specifically
      ;; requested we ignore them with the prefix arg, just return the
      ;; found links in the subtree.
      ((or arg
-         (null latest-msg-id))
+         (null msg-id-link))
       all-links)
      ;; Otherwise ignore the other links in the subtree, and return
      ;; the latest message.
-     (latest-msg-id
-      `(:gnus ,(list (gnorb-msg-id-to-link latest-msg-id)))))))
+     (msg-id-link
+      `(:gnus ,(list msg-id-link))))))
 
 (defvar message-beginning-of-line)
 
@@ -256,26 +262,18 @@ headings."
   (require 'gnorb-gnus)
   (if (not messages)
       ;; Either compose new message...
-      (compose-mail (mapconcat 'identity mails ", "))
+      (compose-mail)
     ;; ...or follow link and start reply.
     (condition-case err
-       (let ((ret-val (org-gnus-open (org-link-unescape (car messages)))))
-         ;; We failed to open the link (probably), ret-val would be
-         ;; t otherwise
-         (when (stringp ret-val)
-           (error ret-val))
-         (call-interactively
-          'gnus-summary-wide-reply-with-original)
-         ;; Add MAILS to message To header.
-         (when mails
-           (message-goto-to)
-           (insert ", ")
-           (insert (mapconcat 'identity mails ", "))))
-      (error (when (and (window-configuration-p gnorb-window-conf)
-                       gnorb-return-marker)
-              (set-window-configuration gnorb-window-conf)
-              (goto-char gnorb-return-marker))
+       (gnorb-reply-to-gnus-link (car messages))
+      (error (gnorb-restore-layout)
             (signal (car err) (cdr err)))))
+  ;; Add MAILS to message To header.
+  (when mails
+    (message-goto-to)
+    (when messages
+      (insert ", "))
+    (insert (mapconcat 'identity mails ", ")))
   ;; Return us after message is sent.
   (add-to-list 'message-exit-actions
               'gnorb-org-restore-after-send t)
@@ -324,7 +322,7 @@ headings."
   (if (or mails messages)
       (if (not messages)
          (message-goto-subject)
-       (message-goto-body))
+       (message-goto-body))
     (message-goto-to))
   (run-hooks 'gnorb-org-after-message-setup-hook))
 
@@ -659,6 +657,7 @@ This won't work unless you've added a \"nngnorb\" server to
 your gnus select methods."
   ;; this should also work on the active region, if there is one.
   (interactive)
+  (require 'gnorb-gnus)
   (setq gnorb-window-conf (current-window-configuration))
   (move-marker gnorb-return-marker (point))
   (when (eq major-mode 'org-agenda-mode)
@@ -674,13 +673,13 @@ your gnus select methods."
   (let (id)
     (save-excursion
       (org-back-to-heading)
-      (setq id (concat "id+" (org-id-get-create))))
-    (gnorb-gnus-search-messages
-     id
-     `(when (and (window-configuration-p gnorb-window-conf)
-                gnorb-return-marker)
-       (set-window-configuration gnorb-window-conf)
-       (goto-char gnorb-return-marker)))))
+      (setq id (concat "id+" (org-id-get-create)))
+      (gnorb-gnus-search-messages
+       id
+       `(when (and (window-configuration-p gnorb-window-conf)
+                  gnorb-return-marker)
+         (set-window-configuration gnorb-window-conf)
+         (goto-char gnorb-return-marker))))))
 
 (provide 'gnorb-org)
 ;;; gnorb-org.el ends here
diff --git a/packages/gnorb/gnorb-registry.el b/packages/gnorb/gnorb-registry.el
index ecd723e..f7b402c 100644
--- a/packages/gnorb/gnorb-registry.el
+++ b/packages/gnorb/gnorb-registry.el
@@ -112,11 +112,14 @@ to the message's registry entry, under the 'gnorb-ids 
key."
       (error
        (setq abort-note 'dirty)))))
 
-(defun gnorb-find-visit-candidates (ids)
+(defun gnorb-find-visit-candidates (ids &optional include-zombies)
   "For all message-ids in IDS (which should be a list of
 Message-ID strings, with angle brackets, or a single string of
 Message-IDs), produce a list of Org ids for headings that are
-relevant to that message."
+relevant to that message.
+
+If optional argument INCLUDE_ZOMBIES is non-nil, return ID values
+even for headings that appear to no longer exist."
   (let (ret-val sub-val)
     (when (stringp ids)
       (setq ids (gnus-extract-references ids)))
@@ -128,8 +131,39 @@ relevant to that message."
              (setq sub-val
                    (gnus-registry-get-id-key id 'gnorb-ids))
            (setq ret-val (append sub-val ret-val))))))
+    ;; This lets us be reasonably confident that the
+    ;; headings still exist.
+    (unless include-zombies
+      (cl-remove-if-not
+       (lambda (org-id)
+        (org-id-find-id-file org-id))
+       ret-val))
     (delete-dups ret-val)))
 
+(defun gnorb-delete-association (msg-id org-id)
+  "Disassociate a message and a headline.
+
+This removes an Org heading's ORG-ID from the 'gnorb-ids key of
+the MSG-ID."
+  (let ((org-ids (gnus-registry-get-id-key msg-id 'gnorb-ids)))
+    (when (member org-id org-ids)
+      (gnus-registry-set-id-key msg-id 'gnorb-ids
+                               (remove org-id org-ids)))))
+
+(defun gnorb-delete-all-assocations (org-id)
+  "Delete all message associations for an Org heading.
+
+The heading is identified by ORG-ID. This is suitable for use
+after an Org heading is deleted, for instance."
+  (let ((assoc-msgs (gnorb-registry-org-id-search org-id)))
+    (mapcar
+     (lambda (msg-id)
+       (let ((org-ids
+             (gnus-registry-get-id-key msg-id 'gnorb-ids)))
+        (gnus-registry-set-id-key
+         msg-id 'gnorb-ids (remove org-id org-ids))))
+     assoc-msgs)))
+
 (defun gnorb-registry-org-id-search (id)
   "Find all messages that have the org ID in their 'gnorb-ids
 key."
diff --git a/packages/gnorb/gnorb-utils.el b/packages/gnorb/gnorb-utils.el
index b4fec36..c132a68 100644
--- a/packages/gnorb/gnorb-utils.el
+++ b/packages/gnorb/gnorb-utils.el
@@ -24,13 +24,8 @@
 
 ;;; Code:
 
-(require 'mailcap)
-(require 'gnus)
-;(require 'message)
-;; (require 'bbdb) ;Avoid compilation failure if BBDB is not available.
-(require 'org)
-(require 'org-bbdb)
-(require 'org-gnus)
+(eval-when-compile
+  (require 'cl))
 
 (mailcap-parse-mimetypes)
 
@@ -102,6 +97,7 @@ with `gnorb-window-conf'.")
             (mapconcat
              'identity ign-headers-list "|")))))
 
+;;;###autoload
 (defun gnorb-restore-layout ()
   "Restore window layout and value of point after a Gnorb command.
 
@@ -111,10 +107,107 @@ to what it was. Bind it to a global key, or to local 
keys in Org
 and Gnus and BBDB maps."
   (interactive)
   (when (window-configuration-p gnorb-window-conf)
+    (select-frame-set-input-focus
+     (window-configuration-frame gnorb-window-conf))
     (set-window-configuration gnorb-window-conf)
     (when (buffer-live-p (marker-buffer gnorb-return-marker))
       (goto-char gnorb-return-marker))))
 
+(defun gnorb-bracket-message-id (id)
+  "Ensure message-id ID is bound by angle brackets."
+  ;; Always use a message-id with angle brackets around it.
+  ;; `gnus-summary-goto-article' can handle either, but
+  ;; `gnus-request-head' will fail without brackets IF you're
+  ;; requesting from an nntp group. Mysterious.
+  (unless (string-match "\\`<" id)
+    (setq id (concat "<" id)))
+  (unless (string-match ">\\'" id)
+    (setq id (concat id ">")))
+  id)
+
+(defun gnorb-unbracket-message-id (id)
+  "Ensure message-id ID is NOT bound by angle brackets."
+  ;; This shit is annoying, but Org wants an id with no brackets, and
+  ;; Gnus is safest with an id that has brackets. So here we are.
+  (replace-regexp-in-string "\\(\\`<\\|>\\'\\)" "" id))
+
+(defun gnorb-reply-to-gnus-link (link)
+  "Start a reply to the linked message."
+  (let* ((link (org-link-unescape link))
+        (group (car (org-split-string link "#")))
+        (id (gnorb-bracket-message-id
+             (second (org-split-string link "#"))))
+        (backend
+         (car (gnus-find-method-for-group group))))
+    (gnorb-follow-gnus-link group id)
+    (call-interactively
+     (if (eq backend 'nntp)
+        'gnus-summary-followup-with-original
+       'gnus-summary-wide-reply-with-original))))
+
+(defun gnorb-follow-gnus-link (group id)
+  "Be a little clever about following gnus links.
+
+The goal here is reuse frames and windows as much as possible, so
+we're not opening multiple windows on the *Group* buffer, for
+instance, and messing up people's layouts. There also seems to be
+an issue when opening a link to a message whose *Summary* buffer
+is already visible: somehow, after following the link, point ends
+up on the message _after_ the one we want, and things go haywire.
+
+So we try to be a little clever. The logical progression here is
+this:
+
+1. If the link's target group is already open in a *Summary*
+buffer, just switch to that buffer (if it's visible in any frame
+then raise it and switch focus, otherwise pull it into the
+current window) and go to the message with
+`gnus-summary-goto-article'.
+
+2. If the Gnus *Group* buffer is visible in any window or frame,
+raise that frame/window and give it focus before following the
+link.
+
+3. Otherwise just follow the link as usual, in the current
+window."
+  (let* ((sum-buffer (gnus-summary-buffer-name group))
+        (target-buffer
+         (cond
+          ((gnus-buffer-exists-p sum-buffer)
+           sum-buffer)
+          ((gnus-buffer-exists-p gnus-group-buffer)
+           gnus-group-buffer)
+          (t nil)))
+        (target-window (when target-buffer
+                         (get-buffer-window target-buffer t))))
+    (if target-window
+       ;; Our target buffer is displayed somewhere: just go there.
+       (progn
+         (select-frame-set-input-focus
+          (window-frame target-window))
+         (switch-to-buffer target-buffer))
+      ;; Our target buffer exists, but isn't displayed: pull it up.
+      (if target-buffer
+         (switch-to-buffer target-buffer)))
+    (message "Following link...")
+    (if (gnus-buffer-exists-p sum-buffer)
+       (gnus-summary-goto-article id nil t)
+      (gnorb-open-gnus-link group id))))
+
+(defun gnorb-open-gnus-link (group id)
+  "Gnorb version of `org-gnus-follow-link'."
+  ;; We've probably already bracketed the id, but just in case this is
+  ;; called from elsewhere...
+  (let* ((id (gnorb-bracket-message-id id))
+        (art-no (cdr (gnus-request-head id group)))
+        (arts (gnus-group-unread group))
+        success)
+    (gnus-activate-group group)
+    (setq success (gnus-group-read-group arts t group))
+    (if success
+       (gnus-summary-goto-article (or art-no id) nil t)
+      (signal 'error "Group could not be opened."))))
+
 (defun gnorb-trigger-todo-action (arg &optional id)
   "Do the actual restore action. Two main things here. First: if
 we were in the agenda when this was called, then keep us in the
@@ -138,16 +231,20 @@ agenda. Then let the user choose an action from the value 
of
           (save-excursion
             (org-id-goto id)
             (move-marker root-marker (point-at-bol)))))
-    ;; Query about attaching email attachments.
-    (org-with-point-at root-marker
-      (map-y-or-n-p
-       (lambda (a)
-        (format "Attach %s to heading? "
-                (file-name-nondirectory a)))
-       (lambda (a) (org-attach-attach a nil 'mv))
-       gnorb-gnus-capture-attachments
-       '("file" "files" "attach")))
-    (setq gnorb-gnus-capture-attachments nil)
+    (unless agenda-p
+      (org-reveal))
+    ;; Query about attaching email attachments. No matter what
+    ;; happens, clear `gnorb-gnus-capture-attachments'.
+    (unwind-protect
+       (org-with-point-at root-marker
+         (map-y-or-n-p
+          (lambda (a)
+            (format "Attach %s to heading? "
+                    (file-name-nondirectory a)))
+          (lambda (a) (org-attach-attach a nil 'mv))
+          gnorb-gnus-capture-attachments
+          '("file" "files" "attach")))
+      (setq gnorb-gnus-capture-attachments nil))
     (cl-labels
        ((make-entry
          (id)
@@ -239,7 +336,9 @@ and 'gnus."
 message."
   (let ((server-group (gnorb-msg-id-to-group msg-id)))
     (when server-group
-      (org-link-escape (concat server-group "#" msg-id)))))
+      (org-link-escape
+       (concat server-group "#"
+              (gnorb-unbracket-message-id msg-id))))))
 
 (defun gnorb-msg-id-to-group (msg-id)
   "Given a message id, try to find the group it's in.
@@ -248,6 +347,7 @@ So far we're checking the registry, then the groups in
 `gnorb-gnus-sent-groups'. Use search engines? Other clever
 methods?"
   (let (candidates server-group)
+    (setq msg-id (gnorb-bracket-message-id msg-id))
     (catch 'found
       (when gnorb-tracking-enabled
        ;; Make a big list of all the groups where this message might
@@ -283,25 +383,64 @@ child headings."
        (lambda (hl)
          (org-element-property :ID hl))))))
 
+;; Common functions for extracting references and relevant headings
+;; from the message under point. For use in gnorb-gnus.el functions.
+
+(defun gnorb-find-tracked-headings (headers &optional include-zombies)
+  "Check HEADERS for message references and return relevant heading IDs.
+
+HEADERs is a message's data header, as produced by
+\(gnus-interactive \"H\"\), or, equivalently:
+
+\(gnus-data-header \(gnus-data-find \(gnus-summary-article-number\)\)\)"
+  (let ((references (mail-header-references headers))
+       (msg-id (mail-header-message-id headers)))
+    (when gnorb-tracking-enabled
+      (gnorb-find-visit-candidates
+       (concat msg-id " " references) include-zombies))))
+
+(defun gnorb-choose-trigger-heading (&optional id)
+  "Given an Org heading ID, ask the user if they want to trigger it.
+
+If not, prompt for another target heading. Either way, return the
+target heading id."
+  (let ((id (if (stringp id)
+               id
+             (car-safe id)))
+       refile-result)
+    (if (and id
+            (y-or-n-p (message
+                       "Attach part to %s"
+                       (gnorb-pretty-outline id))))
+       id
+      (setq refile-result
+           (org-refile-get-location "Attach part to" nil t))
+      (save-window-excursion
+       (find-file (nth 1 refile-result))
+       (goto-char (nth 3 refile-result))
+       (org-id-get-create)))))
+
 ;; Loading the registry
 
 (defvar gnorb-tracking-enabled nil
   "Internal flag indicating whether Gnorb is successfully plugged
   into the registry or not.")
 
+;;;###autoload
 (defun gnorb-tracking-initialize ()
   "Start using the Gnus registry to track correspondences between
 Gnus messages and Org headings. This requires that the Gnus
 registry be in use, and should be called after the call to
 `gnus-registry-initialize'."
   (require 'gnorb-registry)
+  (with-eval-after-load 'gnus-registry
+    (add-to-list 'gnus-registry-extra-entries-precious 'gnorb-ids)
+    (add-to-list 'gnus-registry-track-extra 'gnorb-ids))
   (add-hook
    'gnus-started-hook
    (lambda ()
      (unless (gnus-registry-install-p)
        (user-error "Gnorb tracking requires that the Gnus registry be 
installed."))
-     (add-to-list 'gnus-registry-extra-entries-precious 'gnorb-ids)
-     (add-to-list 'gnus-registry-track-extra 'gnorb-ids)
      (add-hook 'org-capture-mode-hook 'gnorb-registry-capture)
      (add-hook 'org-capture-prepare-finalize-hook 
'gnorb-registry-capture-abort-cleanup)
      (setq gnorb-tracking-enabled t))))
diff --git a/packages/gnorb/gnorb.el b/packages/gnorb/gnorb.el
index c0db6af..22531d2 100644
--- a/packages/gnorb/gnorb.el
+++ b/packages/gnorb/gnorb.el
@@ -2,7 +2,7 @@
 
 ;; Copyright (C) 2014  Free Software Foundation, Inc.
 
-;; Version: 1
+;; Version: 1.0.1
 ;; Package-Requires: ((cl-lib "0.5"))
 
 ;; Maintainer: Eric Abrahamsen <address@hidden>
@@ -31,13 +31,14 @@
 
 ;;; Code:
 
-(if t (require 'bbdb))      ;`if'-trick avoids loading bbdb during compilation!
-(require 'gnorb-utils)
-(require 'nngnorb)
-(require 'gnorb-gnus)
-(require 'gnorb-bbdb)
-(require 'gnorb-org)
-(require 'gnorb-registry)
+(with-eval-after-load 'gnus
+ (require 'nngnorb)
+ (require 'gnorb-gnus)
+ (require 'gnorb-registry))
+(with-eval-after-load 'bbdb
+  (require 'gnorb-bbdb))
+(with-eval-after-load 'org
+ (require 'gnorb-org))
 
 (provide 'gnorb)
 ;;; gnorb.el ends here
diff --git a/packages/gnorb/gnorb.info b/packages/gnorb/gnorb.info
index 64304bd..08085e1 100644
--- a/packages/gnorb/gnorb.info
+++ b/packages/gnorb/gnorb.info
@@ -25,8 +25,6 @@ Gnorb Manual
 * Misc Org::
 * Misc Gnus::
 * Suggested Keybindings::
-* Wishlist/TODO::
-* Index::
 
 — The Detailed Node Listing —
 
@@ -37,6 +35,7 @@ Email Tracking
 * Viewing Tracked Messages in *Summary* Buffers::
 * Hinting in Gnus::
 * Message Attachments::
+* Likely Workflow::
 
 Misc BBDB
 
@@ -156,6 +155,7 @@ agenda rather than in Gnus.
 * Viewing Tracked Messages in *Summary* Buffers::
 * Hinting in Gnus::
 * Message Attachments::
+* Likely Workflow::
 
 
 File: gnorb.info,  Node: Email-Related Commands,  Next: Trigger Actions,  Up: 
Email Tracking
@@ -202,10 +202,14 @@ Email tracking starts in one of three ways:
      The new heading will be created as a capture heading, using the
      template specified by the ‘gnorb-gnus-new-todo-capture-key’ option.
 
-     If you call this function with a prefix arg, you’ll be prompted to
-     choose an existing Org heading instead.  After the the message is
-     sent, you’ll be taken to that heading and prompted to trigger an
-     action on it.
+     If you call this function with a single prefix arg, you’ll be
+     prompted to choose an existing Org heading instead.  After the the
+     message is sent, you’ll be taken to that heading and prompted to
+     trigger an action on it.
+
+     If you’ve called this function, and then realize you’ve associated
+     the message with the wrong TODO, call it again with a double prefix
+     to clear all associations.
 
      It’s also possible to call this function *after* a message is sent,
      in case you forgot.  Gnorb saves information about the most
@@ -230,8 +234,9 @@ heading, taking a note on the heading (both these options 
will associate
 the message with the heading), associating the message but doing nothing
 else, and lastly, doing nothing at all.
 
-   More actions will be added in the future; it’s also possible to add
-your own action: see the docstring of ‘gnorb-org-trigger-actions’.
+   More actions will be added in the future; it’s also possible to
+rearrange or delete existing actions, and add your own: see the
+docstring of ‘gnorb-org-trigger-actions’.
 
 
 File: gnorb.info,  Node: Viewing Tracked Messages in *Summary* Buffers,  Next: 
Hinting in Gnus,  Prev: Trigger Actions,  Up: Email Tracking
@@ -277,7 +282,7 @@ is “g” (meaning it is used as “%ug” in the format line), 
and the mark is
 “¡”.
 
 
-File: gnorb.info,  Node: Message Attachments,  Prev: Hinting in Gnus,  Up: 
Email Tracking
+File: gnorb.info,  Node: Message Attachments,  Next: Likely Workflow,  Prev: 
Hinting in Gnus,  Up: Email Tracking
 
 4.5 Message Attachments
 =======================
@@ -304,6 +309,60 @@ attach the files in the heading’s org-attach directory to 
the outgoing
 message.
 
 
+File: gnorb.info,  Node: Likely Workflow,  Prev: Message Attachments,  Up: 
Email Tracking
+
+4.6 Likely Workflow
+===================
+
+You receive an email from Jimmy, who wants to rent a room in your house.
+“I’ll respond to this later,” you think.
+
+   You capture an Org TODO from the email, call it “Jimmy renting a
+room”, and give it a REPLY keyword.  Gnorb quietly records the
+correspondence between the email and the TODO, using the Gnus registry.
+
+   The next day, looking at your Agenda, you see the TODO and decide to
+respond to the email.  You call ‘gnorb-org-handle-mail’ on the heading,
+and Gnorb opens Jimmy’s email and starts a reply to it.
+
+   You tell Jimmy the room’s available in March, and send the message.
+Gnorb takes you back to the heading, and asks you to trigger an action
+on it.  You choose “todo state”, and change the heading keyword to WAIT.
+
+   Two days later, Jimmy replies to your message, saying that March is
+perfect.  When you open his response, Gnorb politely reminds you that
+the message is relevant to an existing TODO. You call
+‘gnorb-gnus-incoming-do-todo’ on the message, and are again taken to the
+TODO and asked to trigger an action.  Again you choose “todo state”, and
+change the heading keyword back to REPLY.
+
+   You get another email, from Samantha, warning you not to rent the
+room to Jimmy.  She even attaches a picture of a room in her house, as
+it looked after Jimmy had stayed there for six months.  It’s bad.  You
+call ‘gnorb-gnus-incoming-do-todo’ on her message, and pick the “Jimmy
+renting a room” heading.  This time, you choose “take note” as the
+trigger action, and make a brief note about how bad that room looked.
+Gnorb asks if you’d like to attach the picture to the Org heading.  You
+decide you will.
+
+   Now it’s time to write to Jimmy and say something noncommittal.
+Calling ‘gnorb-org-handle-mail’ on the heading would respond to
+Samantha’s email, the most recent of the associated messages, which
+isn’t what you want.  Instead you call ‘gnorb-org-view’ on the heading,
+which opens up a Gnus *Summary* buffer containing all four messages:
+Jimmy’s first, your response, his response to that, and Samantha’s
+message.  You pick Jimmy’s second email, and reply to it normally.
+Gnorb asks if you’d like to send the picture of the room as an
+attachment.  You would not.  When you send the reply Gnorb tracks that
+as well, and does the “trigger an action” trick again.
+
+   In this way Gnorb helps you manage an entire conversation, possibly
+with multiple threads and multiple participants.  Mostly all you need to
+do is call ‘gnorb-gnus-incoming-do-todo’ on newly-received messages, and
+‘gnorb-org-handle-mail’ on the heading when it’s time to compose a new
+reply.
+
+
 File: gnorb.info,  Node: Restoring Window Layout,  Next: Recent Mails From 
BBDB Contacts,  Prev: Email Tracking,  Up: Top
 
 5 Restoring Window Layout
@@ -581,7 +640,7 @@ File: gnorb.info,  Node: User Optionsxx,  Prev: Viewing Org 
headlines relevant t
      format line.  Defaults to “¡”.
 
 
-File: gnorb.info,  Node: Suggested Keybindings,  Next: Wishlist/TODO,  Prev: 
Misc Gnus,  Up: Top
+File: gnorb.info,  Node: Suggested Keybindings,  Prev: Misc Gnus,  Up: Top
 
 12 Suggested Keybindings
 ************************
@@ -629,69 +688,35 @@ File: gnorb.info,  Node: Suggested Keybindings,  Next: 
Wishlist/TODO,  Prev: Mis
        '(progn
           (define-key message-mode-map (kbd "C-c t") 
'gnorb-gnus-outgoing-do-todo)))
 
-
-File: gnorb.info,  Node: Wishlist/TODO,  Next: Index,  Prev: Suggested 
Keybindings,  Up: Top
-
-13 Wishlist/TODO
-****************
-
-   • Provide a command that, when in the Org Agenda, does an email
-     search for messages received in the visible date span, or day under
-     point, etc.  Make it work in the calendar, as well?
-   • Add trigger actions that create new sibling or child headings on
-     the original Org heading.
-   • Allow tagging of Gnus messages, by giving the message’s registry
-     entry an ‘org-tags key.
-   • Provide persistent nngnorb search groups.
-   • Allow automatic org-tagging of BBDB contacts: when messages from a
-     contact are associated with an Org heading, make it possible for
-     the contact to inherit that heading’s tags automatically.
-   • Provide completion when setting Org tags on a BBDB contact.
-   • Provide a ‘gnorb-bbdb-view’ command that opens a *Summary* buffer
-     containing all the tracked messages from the contact(s) under
-     point.
-   • Provide a ‘gnorb-view’ command that takes a tags-todo search phrase
-     (or a single Org heading ID), finds all relevant messages, Org
-     headings, and BBDB records, and sets up a four-pane view: Org
-     Agenda, **Article* SummaryBBDB* buffer, Gnus *buffer, and an *
-     buffer.
-
-
-File: gnorb.info,  Node: Index,  Prev: Wishlist/TODO,  Up: Top
-
-14 Index
-********
-
 
 
 Tag Table:
 Node: Top194
-Node: Introduction1017
-Node: Installation2126
-Node: Setup2540
-Node: Email Tracking3907
-Node: Email-Related Commands5418
-Node: Trigger Actions8239
-Node: Viewing Tracked Messages in *Summary* Buffers9053
-Node: Hinting in Gnus10287
-Node: Message Attachments11295
-Node: Restoring Window Layout12453
-Node: Recent Mails From BBDB Contacts12817
-Node: BBDB posting styles13813
-Node: BBDB Org tagging14729
-Node: Misc BBDB15475
-Node: Searching for messages from BBDB contacts15688
-Node: Citing BBDB contacts16134
-Node: User Options16455
-Node: Misc Org17994
-Node: Inserting BBDB links18169
-Node: User Optionsx18424
-Node: Misc Gnus21161
-Node: Viewing Org headlines relevant to a message21374
-Node: User Optionsxx21689
-Node: Suggested Keybindings24453
-Node: Wishlist/TODO26824
-Node: Index28139
+Node: Introduction1009
+Node: Installation2118
+Node: Setup2532
+Node: Email Tracking3899
+Node: Email-Related Commands5430
+Node: Trigger Actions8440
+Node: Viewing Tracked Messages in *Summary* Buffers9289
+Node: Hinting in Gnus10523
+Node: Message Attachments11531
+Node: Likely Workflow12713
+Node: Restoring Window Layout15518
+Node: Recent Mails From BBDB Contacts15882
+Node: BBDB posting styles16878
+Node: BBDB Org tagging17794
+Node: Misc BBDB18540
+Node: Searching for messages from BBDB contacts18753
+Node: Citing BBDB contacts19199
+Node: User Options19520
+Node: Misc Org21059
+Node: Inserting BBDB links21234
+Node: User Optionsx21489
+Node: Misc Gnus24226
+Node: Viewing Org headlines relevant to a message24439
+Node: User Optionsxx24754
+Node: Suggested Keybindings27518
 
 End Tag Table
 
diff --git a/packages/gnorb/gnorb.org b/packages/gnorb/gnorb.org
index b03098b..e19422d 100644
--- a/packages/gnorb/gnorb.org
+++ b/packages/gnorb/gnorb.org
@@ -118,11 +118,15 @@ There are three main email-related commands:
    The new heading will be created as a capture heading, using the
    template specified by the `gnorb-gnus-new-todo-capture-key' option.
    
-   If you call this function with a prefix arg, you'll be prompted to
-   choose an existing Org heading instead. After the the message is
-   sent, you'll be taken to that heading and prompted to trigger an
-   action on it.
+   If you call this function with a single prefix arg, you'll be
+   prompted to choose an existing Org heading instead. After the the
+   message is sent, you'll be taken to that heading and prompted to
+   trigger an action on it.
    
+   If you've called this function, and then realize you've associated
+   the message with the wrong TODO, call it again with a double prefix
+   to clear all associations.
+
    It's also possible to call this function *after* a message is sent,
    in case you forgot. Gnorb saves information about the most recently
    sent message for this purpose.
@@ -485,23 +489,3 @@ heading to jump to that heading.
     '(progn
        (define-key message-mode-map (kbd "C-c t") 
'gnorb-gnus-outgoing-do-todo)))
 #+END_SRC
-* Wishlist/TODO
-- Provide a command that, when in the Org Agenda, does an email search
-  for messages received in the visible date span, or day under point,
-  etc. Make it work in the calendar, as well?
-- Add trigger actions that create new sibling or child headings on the
-  original Org heading.
-- Allow tagging of Gnus messages, by giving the message's registry
-  entry an 'org-tags key.
-- Provide persistent nngnorb search groups.
-- Allow automatic org-tagging of BBDB contacts: when messages from a
-  contact are associated with an Org heading, make it possible for the
-  contact to inherit that heading's tags automatically.
-- Provide completion when setting Org tags on a BBDB contact.
-- Provide a `gnorb-bbdb-view' command that opens a *Summary* buffer
-  containing all the tracked messages from the contact(s) under point.
-- Provide a `gnorb-view' command that takes a tags-todo search phrase
-  (or a single Org heading ID), finds all relevant messages, Org
-  headings, and BBDB records, and sets up a four-pane view: Org
-  Agenda, *BBDB* buffer, Gnus *Summary* buffer, and an *Article*
-  buffer.
diff --git a/packages/gnorb/gnorb.texi b/packages/gnorb/gnorb.texi
index 643552f..f1fccc5 100644
--- a/packages/gnorb/gnorb.texi
+++ b/packages/gnorb/gnorb.texi
@@ -36,8 +36,6 @@
 * Misc Org::
 * Misc Gnus::
 * Suggested Keybindings::
-* Wishlist/TODO::
-* Index::
 
 @detailmenu
 --- The Detailed Node Listing ---
@@ -49,6 +47,7 @@ Email Tracking
 * Viewing Tracked Messages in *Summary* Buffers::
 * Hinting in Gnus::
 * Message Attachments::
+* Likely Workflow::
 
 Misc BBDB
 
@@ -163,6 +162,7 @@ agenda rather than in Gnus.
 * Viewing Tracked Messages in *Summary* Buffers::
 * Hinting in Gnus::
 * Message Attachments::
+* Likely Workflow::
 @end menu
 
 @node Email-Related Commands
@@ -216,10 +216,14 @@ after the message is sent, and the sent message 
associated with it.
 The new heading will be created as a capture heading, using the
 template specified by the `gnorb-gnus-new-todo-capture-key' option.
 
-If you call this function with a prefix arg, you'll be prompted to
-choose an existing Org heading instead. After the the message is
-sent, you'll be taken to that heading and prompted to trigger an
-action on it.
+If you call this function with a single prefix arg, you'll be
+prompted to choose an existing Org heading instead. After the the
+message is sent, you'll be taken to that heading and prompted to
+trigger an action on it.
+
+If you've called this function, and then realize you've associated
+the message with the wrong TODO, call it again with a double prefix
+to clear all associations.
 
 It's also possible to call this function *after* a message is sent,
 in case you forgot. Gnorb saves information about the most recently
@@ -242,8 +246,9 @@ on the heading, taking a note on the heading (both these 
options will
 associate the message with the heading), associating the message but
 doing nothing else, and lastly, doing nothing at all.
 
-More actions will be added in the future; it's also possible to add
-your own action: see the docstring of `gnorb-org-trigger-actions'.
+More actions will be added in the future; it's also possible to
+rearrange or delete existing actions, and add your own: see the
+docstring of `gnorb-org-trigger-actions'.
 
 @node Viewing Tracked Messages in *Summary* Buffers
 @section Viewing Tracked Messages in *Summary* Buffers
@@ -307,6 +312,59 @@ heading using `gnorb-org-handle-mail', Gnorb will ask if 
you want to
 attach the files in the heading's org-attach directory to the outgoing
 message.
 
address@hidden Likely Workflow
address@hidden Likely Workflow
+
+You receive an email from Jimmy, who wants to rent a room in your
+house. ``I'll respond to this later,'' you think.
+
+You capture an Org TODO from the email, call it ``Jimmy renting a
+room'', and give it a REPLY keyword. Gnorb quietly records the
+correspondence between the email and the TODO, using the Gnus
+registry.
+
+The next day, looking at your Agenda, you see the TODO and decide to
+respond to the email. You call `gnorb-org-handle-mail' on the heading,
+and Gnorb opens Jimmy's email and starts a reply to it.
+
+You tell Jimmy the room's available in March, and send the message.
+Gnorb takes you back to the heading, and asks you to trigger an action
+on it. You choose ``todo state'', and change the heading keyword to
+WAIT.
+
+Two days later, Jimmy replies to your message, saying that March is
+perfect. When you open his response, Gnorb politely reminds you that
+the message is relevant to an existing TODO. You call
+`gnorb-gnus-incoming-do-todo' on the message, and are again taken to
+the TODO and asked to trigger an action. Again you choose ``todo
+state'', and change the heading keyword back to REPLY.
+
+You get another email, from Samantha, warning you not to rent the room
+to Jimmy. She even attaches a picture of a room in her house, as it
+looked after Jimmy had stayed there for six months. It's bad. You call
+`gnorb-gnus-incoming-do-todo' on her message, and pick the ``Jimmy
+renting a room'' heading. This time, you choose ``take note'' as the
+trigger action, and make a brief note about how bad that room looked.
+Gnorb asks if you'd like to attach the picture to the Org heading. You
+decide you will.
+
+Now it's time to write to Jimmy and say something noncommittal.
+Calling `gnorb-org-handle-mail' on the heading would respond to
+Samantha's email, the most recent of the associated messages, which
+isn't what you want. Instead you call `gnorb-org-view' on the heading,
+which opens up a Gnus *Summary* buffer containing all four messages:
+Jimmy's first, your response, his response to that, and Samantha's
+message. You pick Jimmy's second email, and reply to it normally.
+Gnorb asks if you'd like to send the picture of the room as an
+attachment. You would not. When you send the reply Gnorb tracks that
+as well, and does the ``trigger an action'' trick again.
+
+In this way Gnorb helps you manage an entire conversation, possibly
+with multiple threads and multiple participants. Mostly all you need
+to do is call `gnorb-gnus-incoming-do-todo' on newly-received
+messages, and `gnorb-org-handle-mail' on the heading when it's time to
+compose a new reply.
+
 @node Restoring Window Layout
 @chapter Restoring Window Layout
 
@@ -614,41 +672,5 @@ line. Defaults to ``¡''.
      (define-key message-mode-map (kbd "C-c t") 'gnorb-gnus-outgoing-do-todo)))
 @end lisp
 
address@hidden Wishlist/TODO
address@hidden Wishlist/TODO
-
address@hidden
address@hidden
-Provide a command that, when in the Org Agenda, does an email search
-for messages received in the visible date span, or day under point,
-etc. Make it work in the calendar, as well?
address@hidden
-Add trigger actions that create new sibling or child headings on the
-original Org heading.
address@hidden
-Allow tagging of Gnus messages, by giving the message's registry
-entry an `org-tags key.
address@hidden
-Provide persistent nngnorb search groups.
address@hidden
-Allow automatic org-tagging of BBDB contacts: when messages from a
-contact are associated with an Org heading, make it possible for the
-contact to inherit that heading's tags automatically.
address@hidden
-Provide completion when setting Org tags on a BBDB contact.
address@hidden
-Provide a `gnorb-bbdb-view' command that opens a *Summary* buffer
-containing all the tracked messages from the contact(s) under point.
address@hidden
-Provide a `gnorb-view' command that takes a tags-todo search phrase
-(or a single Org heading ID), finds all relevant messages, Org
-headings, and BBDB records, and sets up a four-pane view: Org
-Agenda, **Article* SummaryBBDB* buffer, Gnus *buffer, and an *
-buffer.
address@hidden itemize
-
address@hidden Index
address@hidden Index
-
 @c Emacs 25.0.50.8 (Org mode 8.3beta)
 @bye
\ No newline at end of file
diff --git a/packages/gnorb/nngnorb.el b/packages/gnorb/nngnorb.el
index 0f0999a..d1ed896 100644
--- a/packages/gnorb/nngnorb.el
+++ b/packages/gnorb/nngnorb.el
@@ -39,6 +39,7 @@
 
 ;;; Code:
 
+(require 'gnus)
 (eval-and-compile
   (require 'nnheader)
   (require 'nnir))
@@ -156,7 +157,8 @@ be scanned for gnus messages, and those messages displayed."
          (setq m (org-link-unescape m))
          (when (string-match "\\`\\([^#]+\\)\\(#\\(.*\\)\\)?" m)
            (setq server-group (match-string 1 m)
-                 msg-id (match-string 3 m)
+                 msg-id (gnorb-bracket-message-id
+                         (match-string 3 m))
                  result (ignore-errors (gnus-request-head msg-id 
server-group)))
            (when result
             (setq artno (cdr result))
@@ -302,27 +304,32 @@ This is used in a Gnorb-created *Summary* buffer to 
remove the
 connection between the message and whichever Org TODO resulted in
 the message being included in this search."
   (interactive)
+  (unless (get-buffer-window gnus-article-buffer t)
+    (gnus-summary-display-article
+     (gnus-summary-article-number)))
   (let* ((msg-id (gnus-fetch-original-field "message-id"))
         (org-ids (gnus-registry-get-id-key msg-id 'gnorb-ids))
         chosen)
-    (when org-ids
-      (if (= (length org-ids) 1)
-         ;; Only one associated Org TODO.
-         (progn (gnus-registry-set-id-key msg-id 'gnorb-ids)
-                (setq chosen (car org-ids)))
-       ;; Multiple associated TODOs, prompt to choose one.
-       (setq chosen
-             (cdr
-              (org-completing-read
-               "Choose a TODO to disassociate from: "
-               (mapcar
-                (lambda (h)
-                  (cons (gnorb-pretty-outline h) h))
-                org-ids))))
-       (gnus-registry-set-id-key msg-id 'gnorb-ids
-                                 (remove chosen org-ids)))
-      (message "Message disassociated from %s"
-              (gnorb-pretty-outline chosen)))))
+    (if org-ids
+       (progn
+         (if (= (length org-ids) 1)
+             ;; Only one associated Org TODO.
+             (progn (gnus-registry-set-id-key msg-id 'gnorb-ids nil)
+                    (setq chosen (car org-ids)))
+           ;; Multiple associated TODOs, prompt to choose one.
+           (setq chosen
+                 (cdr
+                  (org-completing-read
+                   "Choose a TODO to disassociate from: "
+                   (mapcar
+                    (lambda (h)
+                      (cons (gnorb-pretty-outline h) h))
+                    org-ids))))
+           (gnus-registry-set-id-key msg-id 'gnorb-ids
+                                     (remove chosen org-ids)))
+         (message "Message disassociated from %s"
+                  (gnorb-pretty-outline chosen)))
+      (message "Message has no associations"))))
 
 (defvar nngnorb-status-string "")
 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]