]> code.delx.au - gnu-emacs-elpa/commitdiff
Merge commit '0b9eb2b647a49ffa3dc4e3e61cb8bd94c7fe3634' as 'packages/gnorb'
authorEric Abrahamsen <eric@ericabrahamsen.net>
Thu, 9 Oct 2014 01:47:46 +0000 (09:47 +0800)
committerEric Abrahamsen <eric@ericabrahamsen.net>
Thu, 9 Oct 2014 01:47:46 +0000 (09:47 +0800)
15 files changed:
1  2 
packages/gnorb/.elpaignore
packages/gnorb/.gitignore
packages/gnorb/NEWS
packages/gnorb/README.org
packages/gnorb/dir
packages/gnorb/gnorb-bbdb.el
packages/gnorb/gnorb-gnus.el
packages/gnorb/gnorb-org.el
packages/gnorb/gnorb-registry.el
packages/gnorb/gnorb-utils.el
packages/gnorb/gnorb.el
packages/gnorb/gnorb.info
packages/gnorb/gnorb.org
packages/gnorb/gnorb.texi
packages/gnorb/nngnorb.el

index 0000000000000000000000000000000000000000,f614ef7669d224f02ae5bf1f415843c061ffeef2..f614ef7669d224f02ae5bf1f415843c061ffeef2
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1 +1,1 @@@
+ gnorb.org
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..60fa2cac292cac06e71964103849d73512d9679b
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,4 @@@
++*.elc
++notes.org
++gnorb-pkg.el
++gnorb-autoloads.el
diff --combined packages/gnorb/NEWS
index 0000000000000000000000000000000000000000,728f21950cb1ee394238aece9aadf231260281ba..728f21950cb1ee394238aece9aadf231260281ba
mode 000000,100644..100644
--- /dev/null
--- 2/NEWS
@@@ -1,0 -1,11 +1,11 @@@
+ GNU Emacs Gnorb NEWS -- history of user-visible changes.  -*- org -*-
+ * Version 1 [2014-10-07 Tue]
+ ** First Elpa Version
+ ** Email Tracking
+ The mechanism for email tracking has changed since Gnorb was made
+ available on Elpa. See the manual for set-up instructions.
+ ** Directory Structure
+ The directory structure has changed since Gnorb was made available on
+ Elpa. There is no longer a lisp/ directory -- all *.el files are now
+ at the top level.
index 0000000000000000000000000000000000000000,cbb13afe9a60cf896c9083f8bdc1e4ba0e2da12f..cbb13afe9a60cf896c9083f8bdc1e4ba0e2da12f
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,31 +1,31 @@@
+ * Gnorb
+ Glue code between the Gnus, Org, and BBDB packages for Emacs.
+ This package connects Emacs-based email, project management, and
+ contact management a little more closely together. The goal is to
+ reduce friction when manipulating TODOs, contacts, messages, and
+ files.
+ Probably the most interesting thing Gnorb does is tracking
+ correspondences between Gnus email messages and Org headings. Rather
+ than "turning your inbox into a TODO list", as some software puts it,
+ Gnorb (kind of) does the opposite: turning your TODO headings into
+ mini mailboxes.
+ *Note for previous users*: If you were using Gnorb from Github before
+ it shifted to the Elpa repository, the email tracking mechanism has
+ changed, please see the manual for details.
+ ** Installation
+ It's easiest to install Gnorb from Elpa: run `list-packages' and look
+ for it there.
+ Or clone the Git repo at https://github.com/girzel/gnorb, and add the
+ top-level directory to your load path.
+ 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.
diff --combined packages/gnorb/dir
index 0000000000000000000000000000000000000000,b0d0ad6e6e9bceff914576752cba20e1bf6b200b..b0d0ad6e6e9bceff914576752cba20e1bf6b200b
mode 000000,100644..100644
--- /dev/null
--- 2/dir
@@@ -1,0 -1,18 +1,18 @@@
+ This is the file .../info/dir, which contains the
+ topmost node of the Info hierarchy, called (dir)Top.
+ The first time you invoke Info you start off looking at this node.
\1f
+ File: dir,    Node: Top       This is the top of the INFO tree
+   This (the Directory node) gives a menu of major topics.
+   Typing "q" exits, "?" lists all Info commands, "d" returns here,
+   "h" gives a primer for first-timers,
+   "mEmacs<Return>" visits the Emacs manual, etc.
+   In Emacs, you can click mouse button 2 on a menu item or cross reference
+   to select it.
+ * Menu:
+ Emacs
+ * Gnorb: (gnorb).               Glue code for Gnus, Org, and BBDB.
index 0000000000000000000000000000000000000000,b30298f9277d94f34c2819ec68527d481e2e060d..b30298f9277d94f34c2819ec68527d481e2e060d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,565 +1,565 @@@
+ ;;; gnorb-bbdb.el --- The BBDB-centric functions of gnorb
+ ;; Copyright (C) 2014  Eric Abrahamsen
+ ;; Author: Eric Abrahamsen <eric@ericabrahamsen.net>
+ ;; Keywords: 
+ ;; This program is free software; you can redistribute it and/or modify
+ ;; it under the terms of the GNU General Public License as published by
+ ;; the Free Software Foundation, either version 3 of the License, or
+ ;; (at your option) any later version.
+ ;; This program is distributed in the hope that it will be useful,
+ ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ ;; GNU General Public License for more details.
+ ;; You should have received a copy of the GNU General Public License
+ ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ ;;; Commentary:
+ ;; 
+ ;;; Code:
+ (require 'gnorb-utils)
+ (defgroup gnorb-bbdb nil
+   "The BBDB bits of gnorb."
+   :tag "Gnorb BBDB"
+   :group 'gnorb)
+ (defcustom gnorb-bbdb-org-tag-field 'org-tags
+   "The name (as a symbol) of the field to use for org tags."
+   :group 'gnorb-bbdb
+   :type 'symbol)
+ (unless (assoc gnorb-bbdb-org-tag-field bbdb-separator-alist)
+   (push `(,gnorb-bbdb-org-tag-field ":" ":") bbdb-separator-alist))
+ (defcustom gnorb-bbdb-messages-field 'messages
+   "The name (as a symbol) of the field where links to recent gnus
+ messages from this record are stored.
+ \\<bbdb-mode-map>Records that do not have this field defined
+ will not collect links to messages: you have to call
+ \"\\[gnorb-bbdb-open-link]\" on the record once -- after that,
+ message links will be collected and updated automatically."
+   :group 'gnorb-bbdb
+   :type 'symbol)
+ (defcustom gnorb-bbdb-collect-N-messages 5
+   "For records with a `gnorb-bbdb-messages-field' defined,
+ collect links to a maximum of this many messages."
+   :group 'gnorb-bbdb
+   :type 'integer)
+ (defcustom gnorb-bbdb-define-recent 'seen
+   "For records with a `gnorb-bbdb-message-tag-field' defined,
+ this variable controls how gnorb defines a \"recent\" message.
+ Setting it to the symbol seen will collect the messages most
+ recently opened and viewed. The symbol received means gnorb will
+ collect the most recent messages by Date header.
+ In other words, if this variable is set to 'received, and a
+ record's messages field is already full of recently-received
+ messages, opening a five-year-old message (for instance) from
+ this record will not push a link to the message into the field."
+   :group 'gnorb-bbdb
+   :type '(choice (const :tag "Most recently seen" 'seen)
+                  (const :tag "Most recently received" 'received)))
+ (defcustom gnorb-bbdb-message-link-format-multi "%:count. %D: %:subject"
+   "How a single message is formatted in the list of recent messages.
+ This format string is used in multi-line record display.
+ Available information for each message includes the subject, the
+ date, and the message's count in the list, as an integer. You can
+ access subject and count using the %:subject and %:count escapes.
+ The message date can be formatted using any of the escapes
+ mentioned in the docstring of `format-time-string', which see."
+   :group 'gnorb-bbdb
+   :type 'string)
+ (defcustom gnorb-bbdb-message-link-format-one "%:count"
+   "How a single message is formatted in the list of recent messages.
+ This format string is used in single-line display -- note that by
+ default, no user-created xfields are displayed in the 'one-line
+ layout found in `bbdb-layout-alist'. If you want this field to
+ appear there, put its name in the \"order\" list of the 'one-line
+ layout.
+ Available information for each message includes the subject, the
+ date, and the message's count in the list, as an integer. You can
+ access subject and count using the %:subject and %:count escapes.
+ The message date can be formatted using any of the escapes
+ mentioned in the docstring of `format-time-string', which see."
+   :group 'gnorb-bbdb
+   :type 'string)
+ (defface gnorb-bbdb-link (org-compatible-face 'org-link nil)
+   "Custom face for displaying message links in the *BBDB* buffer.
+   Defaults to org-link."
+   :group 'gnorb-bbdb)
+ (defstruct gnorb-bbdb-link
+   subject date group id)
+ (defcustom gnorb-bbdb-posting-styles nil
+   "An alist of styles to use when composing messages to the BBDB
+ record(s) under point. This is entirely analogous to
+ `gnus-posting-styles', it simply works by examining record fields
+ rather than group names.
+ When composing a message to multiple contacts (using the \"*\"
+ prefix), the records will be scanned in order, with the record
+ initially under point (if any) set aside for last. That means
+ that, in the case of conflicting styles, the record under point
+ will override the others.
+ In order not to be too intrusive, this option has no effect on
+ the usual `bbdb-mail' command. Instead, the wrapper command
+ `gnorb-bbdb-mail' is provided, which consults this option and
+ then hands off to `bbdb-compose-mail'. If you'd always like to
+ use `gnorb-bbdb-mail', you can simply bind it to \"m\" in the
+ `bbdb-mode-map'.
+ The value of the option should be a list of sexps, each one
+ matching a single field. The first element should match a field
+ name: one of the built-in fields like lastname, or an xfield.
+ Field names should be given as symbols.
+ The second element is a regexp used to match against the value of
+ the field (non-string field values will be cast to strings, if
+ possible). It can also be a cons of two strings, the first of
+ which matches the field label, the second the field value.
+ Alternately, the first element can be the name of a custom
+ function that is called with the record as its only argument, and
+ returns either t or nil. In this case, the second element of the
+ list is disregarded.
+ All following elements should be field setters for the message to
+ be composed, just as in `gnus-posting-styles'.
+ An example value might look like:"
+   :group 'gnorb-bbdb)
+ ;;;###autoload
+ (defun gnorb-bbdb-mail (records &optional subject n verbose)
+   "\\<bbdb-mode-map>Acts just like `bbdb-mail', except runs
+ RECORDS through `gnorb-bbdb-posting-styles', allowing
+ customization of message styles for certain records. From the
+ `bbdb-mail' docstring:
+ Compose a mail message to RECORDS (optional: using SUBJECT).
+ Interactively, use BBDB prefix \\[bbdb-do-all-records], see
+ `bbdb-do-all-records'. By default, the first mail addresses of
+ RECORDS are used. If prefix N is a number, use Nth mail address
+ of RECORDS (starting from 1). If prefix N is C-u (t
+ noninteractively) use all mail addresses of RECORDS. If VERBOSE
+ is non-nil (as in interactive calls) be verbose."
+   ;; see the function `gnus-configure-posting-styles' for tips on how
+   ;; to actually do this.
+   (interactive (list (bbdb-do-records) nil
+                    (or (consp current-prefix-arg)
+                          current-prefix-arg)
+                    t))
+   (setq records (bbdb-record-list records))
+   (if (not records)
+       (user-error "No records displayed")
+     (let ((head (bbdb-current-record))
+         (to (bbdb-mail-address records n nil verbose))
+         (message-mode-hook (copy-sequence message-mode-hook)))
+       (setq records (remove head records))
+       (when gnorb-bbdb-posting-styles
+       (add-hook 'message-mode-hook
+                 `(lambda ()
+                    (gnorb-bbdb-configure-posting-styles (quote ,records))
+                    (gnorb-bbdb-configure-posting-styles (list ,head)))))
+       (bbdb-compose-mail to subject))))
+ (defun gnorb-bbdb-configure-posting-styles (recs)
+   ;; My most magnificent work of copy pasta!
+   (dolist (r recs)
+     (let (field val label rec-val element filep
+               element v value results name address)
+       (dolist (style gnorb-bbdb-posting-styles)
+       (setq field (pop style)
+             val (pop style))
+       (when (consp val) ;; (label value)
+         (setq label (pop val)
+               val (pop val)))
+       (unless (fboundp field)
+         ;; what's the record's existing value for this field?
+         (setq rec-val (bbdb-record-field r field)))
+       (when (cond
+              ((eq field 'address)
+               (dolist (a rec-val)
+                 (unless (and label
+                              (not (string-match label (car a))))
+                   (string-match val (bbdb-format-address-default a)))))
+              ((eq field 'phone)
+               (dolist (p rec-val)
+                 (unless (and label
+                              (not (string-match label (car p))))
+                   (string-match val (bbdb-phone-string p)))))
+              ((consp rec-val)
+               (dolist (f rec-val)
+                 (string-match val f)))
+              ((fboundp field)
+               (funcall field r))
+              ((stringp rec-val)
+               (string-match val rec-val)))
+         ;; there are matches, run through the field setters in last
+         ;; element of the sexp
+         (dolist (attribute style)
+           (setq element (pop attribute)
+                 filep nil)
+           (setq value
+                 (cond
+                  ((eq (car attribute) :file)
+                   (setq filep t)
+                   (cadr attribute))
+                  ((eq (car attribute) :value)
+                   (cadr attribute))
+                  (t
+                   (car attribute))))
+           ;; We get the value.
+           (setq v
+                 (cond
+                  ((stringp value)
+                   value)
+                  ((or (symbolp value)
+                       (functionp value))
+                   (cond ((functionp value)
+                          (funcall value))
+                         ((boundp value)
+                          (symbol-value value))))
+                  ((listp value)
+                   (eval value))))
+           ;; Post-processing for the signature posting-style:
+           (and (eq element 'signature) filep
+                message-signature-directory
+                ;; don't actually use the signature directory
+                ;; if message-signature-file contains a path.
+                (not (file-name-directory v))
+                (setq v (nnheader-concat message-signature-directory v)))
+           ;; Get the contents of file elems.
+           (when (and filep v)
+             (setq v (with-temp-buffer
+                       (insert-file-contents v)
+                       (buffer-substring
+                        (point-min)
+                        (progn
+                          (goto-char (point-max))
+                          (if (zerop (skip-chars-backward "\n"))
+                              (point)
+                            (1+ (point))))))))
+           (setq results (delq (assoc element results) results))
+           (push (cons element v) results))))
+       (setq name (assq 'name results)
+           address (assq 'address results))
+       (setq results (delq name (delq address results)))
+       (gnus-make-local-hook 'message-setup-hook)
+       (setq results (sort results (lambda (x y)
+                                   (string-lessp (car x) (car y)))))
+       (dolist (result results)
+       (add-hook 'message-setup-hook
+                 (cond
+                  ((eq 'eval (car result))
+                   'ignore)
+                  ((eq 'body (car result))
+                   `(lambda ()
+                      (save-excursion
+                        (message-goto-body)
+                        (insert ,(cdr result)))))
+                  ((eq 'signature (car result))
+                   (set (make-local-variable 'message-signature) nil)
+                   (set (make-local-variable 'message-signature-file) nil)
+                   (if (not (cdr result))
+                       'ignore
+                     `(lambda ()
+                        (save-excursion
+                          (let ((message-signature ,(cdr result)))
+                            (when message-signature
+                              (message-insert-signature)))))))
+                  (t
+                   (let ((header
+                          (if (symbolp (car result))
+                              (capitalize (symbol-name (car result)))
+                            (car result))))
+                     `(lambda ()
+                        (save-excursion
+                          (message-remove-header ,header)
+                          (let ((value ,(cdr result)))
+                            (when value
+                              (message-goto-eoh)
+                              (insert ,header ": " value)
+                              (unless (bolp)
+                                (insert "\n")))))))))
+                 t 'local))
+       (when (or name address)
+       (add-hook 'message-setup-hook
+                 `(lambda ()
+                    (set (make-local-variable 'user-mail-address)
+                         ,(or (cdr address) user-mail-address))
+                    (let ((user-full-name ,(or (cdr name) (user-full-name)))
+                          (user-mail-address
+                           ,(or (cdr address) user-mail-address)))
+                      (save-excursion
+                        (message-remove-header "From")
+                        (message-goto-eoh)
+                        (insert "From: " (message-make-from) "\n"))))
+                 t 'local)))))
+ ;;;###autoload
+ (defun gnorb-bbdb-tag-agenda (records)
+   "Open an Org agenda tags view from the BBDB buffer, using the
+ value of the record's org-tags field. This shows only TODOs by
+ default; a prefix argument shows all tagged headings; a \"*\"
+ prefix operates on all currently visible records. If you want
+ both, use \"C-u\" before the \"*\"."
+   (interactive (list (bbdb-do-records)))
+   (require 'org-agenda)
+   (unless (and (eq major-mode 'bbdb-mode)
+              (equal (buffer-name) bbdb-buffer-name))
+     (error "Only works in the BBDB buffer"))
+   (setq records (bbdb-record-list records))
+   (let ((tag-string
+        (mapconcat
+         'identity
+         (delete-dups
+          (mapcan (lambda (r)
+                    (bbdb-record-xfield-split r gnorb-bbdb-org-tag-field))
+                  records))
+         "|")))
+     (if tag-string
+       ;; C-u = all headings, not just todos
+       (if (equal current-prefix-arg '(4))
+           (org-tags-view nil tag-string)
+         (org-tags-view t tag-string))
+       (error "No org-tags field present"))))
+ ;;;###autoload
+ (defun gnorb-bbdb-mail-search (records)
+   "Initiate a mail search from the BBDB buffer.
+ Use the prefix arg to edit the search string first, and the \"*\"
+ prefix to search mails from all visible contacts. When using both
+ a prefix arg and \"*\", the prefix arg must come first."
+   (interactive (list (bbdb-do-records)))
+   (unless (and (eq major-mode 'bbdb-mode)
+              (equal (buffer-name) bbdb-buffer-name))
+     (error "Only works in the BBDB buffer"))
+   (setq records (bbdb-record-list records))
+   (require 'gnorb-gnus)
+   (let* ((backend (or (assoc gnorb-gnus-mail-search-backend
+                            gnorb-gnus-mail-search-backends)
+                     (error "No search backend specified")))
+        (search-string
+         (funcall (second backend)
+                  (cl-mapcan 'bbdb-record-mail records))))
+     (when (equal current-prefix-arg '(4))
+       (setq search-string
+           (read-from-minibuffer
+            (format "%s search string: " (first backend)) search-string)))
+     (funcall (third backend) search-string)
+     (delete-other-windows)))  
+ ;;;###autoload
+ (defun gnorb-bbdb-cite-contact (rec)
+   (interactive (list (gnorb-prompt-for-bbdb-record)))
+   (let ((mail-string (bbdb-dwim-mail rec)))
+    (if (called-interactively-p 'any)
+        (insert mail-string)
+      mail-string)))
+ ;;; Field containing links to recent messages
+ (add-to-list 'bbdb-xfield-label-list gnorb-bbdb-messages-field nil 'eq)
+ (defun gnorb-bbdb-display-messages (record format)
+   "Show links to the messages collected in the
+ `gnorb-bbdb-messages-field' field of a BBDB record. Each link
+ will be formatted using the format string in
+ `gnorb-bbdb-message-link-format-multi' or
+ `gnorb-bbdb-message-link-format-one', depending on the current
+ layout type."
+   (let ((full-field (assq gnorb-bbdb-messages-field
+                         (bbdb-record-xfields record)))
+       (val (bbdb-record-xfield record gnorb-bbdb-messages-field))
+       (map (make-sparse-keymap))
+       (count 1)) ; one-indexed to fit with prefix arg to `gnorb-bbdb-open-link'
+     (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))
+       (insert (cond ((and (stringp val)
+                         (eq format 'multi))
+                    (bbdb-indent-string (concat val "\n") indent))
+                   ((listp val)
+                    (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)
+                           (incf count)))
+                       val (if (eq format 'multi)
+                               "\n" ", "))
+                      indent)
+                     (if (eq format 'multi) "\n" "")))
+                   (t
+                    ""))))))
+ (fset (intern (format "bbdb-display-%s-multi-line"
+                     gnorb-bbdb-messages-field))
+       (lambda (record)
+       (gnorb-bbdb-display-messages record 'multi)))
+ (fset (intern (format "bbdb-display-%s-one-line"
+                     gnorb-bbdb-messages-field))
+       (lambda (record)
+       (gnorb-bbdb-display-messages record 'one)))
+ ;; Don't allow direct editing of this field
+ (fset (intern (format "bbdb-read-xfield-%s"
+                     gnorb-bbdb-messages-field))
+       (lambda (&optional init)
+       (user-error "This field shouldn't be edited manually")))
+ ;; Open links from the *BBDB* buffer.
+ ;;;###autoload
+ (defun gnorb-bbdb-open-link (record arg)
+   "\\<bbdb-mode-map>Call this on a BBDB record to open one of the
+ links in the message field. By default, the first link will be
+ opened. Use a prefix arg to open different links. For instance,
+ M-3 \\[gnorb-bbdb-open-link] will open the third link in the
+ list. If the %:count escape is present in the message formatting
+ string (see `gnorb-bbdb-message-link-format-multi' and
+ `gnorb-bbdb-message-link-format-one'), that's the number to use.
+ This function also needs to be called on a contact once before
+ that contact will start collecting links to messages."
+   (interactive (list
+               (or (bbdb-current-record)
+                   (user-error "No record under point"))
+               current-prefix-arg))
+   (unless (fboundp 'bbdb-record-xfield-string)
+     (user-error "This function only works with the git version of BBDB"))
+   (let* ((record (bbdb-current-record))
+        msg-list target-msg)
+     (if (not (memq gnorb-bbdb-messages-field
+                  (mapcar 'car (bbdb-record-xfields record))))
+       (when (y-or-n-p
+              (format "Start collecting message links for %s?"
+                      (bbdb-record-name record)))
+         (bbdb-record-set-xfield record gnorb-bbdb-messages-field "no links yet")
+         (message "Opening messages from %s will add links to the %s field"
+                  (bbdb-record-name record)
+                  gnorb-bbdb-messages-field)
+         (bbdb-change-record record))
+       (setq msg-list
+             (bbdb-record-xfield record gnorb-bbdb-messages-field))
+       (setq target-msg
+             (or (and arg
+                      (nth (1- arg) msg-list))
+                 (car msg-list)))
+       (when target-msg
+         (org-gnus-follow-link (gnorb-bbdb-link-group target-msg)
+                               (gnorb-bbdb-link-id target-msg))))))
+ (defun gnorb-bbdb-mouse-open-link (event)
+   (interactive "e")
+   (mouse-set-point event)
+   (let ((rec (bbdb-current-record))
+       (num (get-text-property (point) 'gnorb-bbdb-link-count)))
+     (if (not num)
+       (user-error "No link under point")
+       (gnorb-bbdb-open-link rec num))))
+ (defun gnorb-bbdb-RET-open-link ()
+   (interactive)
+   (let ((rec (bbdb-current-record))
+       (num (get-text-property (point) 'gnorb-bbdb-link-count)))
+     (if (not num)
+       (user-error "No link under point")
+       (gnorb-bbdb-open-link rec num))))
+ (defun gnorb-bbdb-store-message-link (record)
+   "Used in the `bbdb-notice-record-hook' to possibly save a link
+ to a message into the record's `gnorb-bbdb-messages-field'."
+   (when (not (fboundp 'bbdb-record-xfield-string))
+     (user-error "This function only works with the git version of BBDB"))
+   (unless (or (not (and (memq gnorb-bbdb-messages-field
+                             (mapcar 'car (bbdb-record-xfields record)))
+                       (memq major-mode '(gnus-summary-mode gnus-article-mode))))
+             (with-current-buffer gnus-article-buffer
+               (not ; only store messages if the record is the sender
+                (member (nth 1 (car (bbdb-get-address-components 'sender)))
+                        (bbdb-record-mail record)))))
+     (with-current-buffer gnus-summary-buffer
+       (let* ((val (bbdb-record-xfield record gnorb-bbdb-messages-field))
+            (art-no (gnus-summary-article-number))
+            (heads (gnus-summary-article-header art-no))
+            (date (apply 'encode-time
+                         (parse-time-string (mail-header-date heads))))
+            (subject (mail-header-subject heads))
+            (id (mail-header-id heads))
+            (group gnus-newsgroup-name)
+            link)
+       ;; check for both nnvirtual and nnir, and link to the real
+       ;; group in those cases
+       (when (eq (car (gnus-find-method-for-group group))
+                 'nnvirtual)
+         (setq group (car (nnvirtual-map-article art-no))))
+       (when (eq (car (gnus-find-method-for-group group))
+                 'nnir)
+         (setq group (nnir-article-group art-no)))
+       (if (not (and date subject id group))
+           (message "Could not save a link to this message")
+         (setq link (make-gnorb-bbdb-link :subject subject :date date
+                                          :group group :id id))
+         (when (stringp val)
+           (setq val nil))
+         (setq val (cons link (delete link val)))
+         (when (eq gnorb-bbdb-define-recent 'received)
+           (setq val (sort val
+                           (lambda (a b)
+                             (time-less-p
+                              (gnorb-bbdb-link-date b)
+                              (gnorb-bbdb-link-date a))))))
+         (setq val (subseq val 0 gnorb-bbdb-collect-N-messages))
+         (bbdb-record-set-xfield record
+                                 gnorb-bbdb-messages-field
+                                 (delq nil val))
+         (bbdb-change-record record))))))
+ (add-hook 'bbdb-notice-record-hook 'gnorb-bbdb-store-message-link)
+ (provide 'gnorb-bbdb)
+ ;;; gnorb-bbdb.el ends here
index 0000000000000000000000000000000000000000,ba72107f93261269609941bbc7c882a12c08753e..ba72107f93261269609941bbc7c882a12c08753e
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,671 +1,671 @@@
+ ;;; gnorb-gnus.el --- The gnus-centric fuctions of gnorb
+ ;; Copyright (C) 2014  Eric Abrahamsen
+ ;; Author: Eric Abrahamsen <eric@ericabrahamsen.net>
+ ;; Keywords: 
+ ;; This program is free software; you can redistribute it and/or modify
+ ;; it under the terms of the GNU General Public License as published by
+ ;; the Free Software Foundation, either version 3 of the License, or
+ ;; (at your option) any later version.
+ ;; This program is distributed in the hope that it will be useful,
+ ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ ;; GNU General Public License for more details.
+ ;; You should have received a copy of the GNU General Public License
+ ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ ;;; Commentary:
+ ;; 
+ ;;; Code:
+ (require 'gnorb-utils)
+ (declare-function org-gnus-article-link "org-gnus"
+                 (group newsgroups message-id x-no-archive))
+ (declare-function org-gnus-follow-link "org-gnus"
+                 (group article))
+ (defgroup gnorb-gnus nil
+   "The Gnus bits of Gnorb."
+   :tag "Gnorb Gnus"
+   :group 'gnorb)
+ (defcustom gnorb-gnus-mail-search-backends
+   '((notmuch (lambda (terms)
+              (mapconcat
+               (lambda (m)
+                 (replace-regexp-in-string "\\." "\\\\." m))
+               terms " OR "))
+            notmuch-search)
+     (mairix (lambda (terms)
+             (mapconcat 'identity
+                        terms ","))
+           mairix-search)
+     (namazu (lambda (terms)
+             (mapconcat 'identity
+                        terms " or "))
+           namazu-search))
+   "Various backends for mail search.
+ An alist of backends, where each element consists of three parts:
+ the symbol name of the backend, a lambda form which receives a
+ list of email addresses and returns a properly-formatted search
+ string, and the symbol name of the function used to initiate the
+ search."
+   :group 'gnorb-gnus
+   :type 'list)
+ (defcustom gnorb-gnus-mail-search-backend nil
+   "Mail search backend currently in use. One of the three symbols
+ notmuch, namazu, or mairix."
+   :group 'gnorb-gnus
+   :type 'symbol)
+ (defcustom gnorb-gnus-capture-always-attach nil
+   "Always prompt about attaching attachments when capturing from
+   a Gnus message, even if the template being used hasn't
+   specified the :gnus-attachments key.
+ Basically behave as if all attachments have \":gnus-attachments t\"."
+   :group 'gnorb-gnus
+   :type 'boolean)
+ (defcustom gnorb-gnus-new-todo-capture-key nil
+   "Key for the capture template to use when creating a new TODO
+   from an outgoing message."
+   :group 'gnorb-gnus
+   :type 'string)
+ (defcustom gnorb-gnus-hint-relevant-article t
+   "When opening a gnus message, should gnorb let you know if the
+   message is relevant to an existing TODO?"
+   :group 'gnorb-gnus
+   :type 'boolean)
+ (defcustom gnorb-gnus-summary-mark-format-letter "g"
+   "Format letter to be used as part of your
+   `gnus-summary-line-format', to indicate in the *Summary* buffer
+   which articles might be relevant to TODOs. Since this is a user
+   format code, it should be prefixed with %u, eg %ug. It will
+   result in the insertion of the value of
+   `gnorb-gnus-summary-mark', for relevant messages, or
+   else a space."
+   :group 'gnorb-gnus
+   :type 'string)
+ (defcustom gnorb-gnus-summary-mark "ยก"
+   "Default mark to insert in the summary format line of articles
+   that are likely relevant to existing TODO headings."
+   :group 'gnorb-gnus
+   :type 'string)
+ (defcustom gnorb-gnus-trigger-refile-targets
+   '((org-agenda-files :maxlevel . 4))
+   "A value to use as an equivalent of `org-refile-targets' (which
+   see) when offering trigger targets for
+   `gnorb-gnus-incoming-do-todo'."
+   :group 'gnorb-gnus
+   :type 'list)
+ (defcustom gnorb-gnus-sent-groups nil
+   "A list of strings indicating sent mail groups.
+ In some cases, Gnorb can't detect where your sent messages are
+ stored (ie if you're using IMAP sent mail folders instead of
+ local archiving. If you want Gnorb to be able to find sent
+ messages, this option can help it do that. It should be set to a
+ list of strings, which are assumed to be fully qualified
+ server+group combinations, ie \"nnimap+Server:[Gmail]/Sent
+ Mail\", or something similar. This only has to be done once for
+ each message."
+   :group 'gnorb-gnus
+   :type 'list)
+ (defvar gnorb-gnus-capture-attachments nil
+   "Holding place for attachment names during the capture
+   process.")
+ ;;; What follows is a very careful copy-pasta of bits and pieces from
+ ;;; mm-decode.el and gnus-art.el. Voodoo was involved.
+ ;;;###autoload
+ (defun gnorb-gnus-article-org-attach (n)
+   "Save MIME part N, which is the numerical prefix, of the
+   article under point as an attachment to the specified org
+   heading."
+   (interactive "P")
+   (gnus-article-part-wrapper n 'gnorb-gnus-attach-part))
+ ;;;###autoload
+ (defun gnorb-gnus-mime-org-attach ()
+   "Save the MIME part under point as an attachment to the
+   specified org heading."
+   (interactive)
+   (gnus-article-check-buffer)
+   (let ((data (get-text-property (point) 'gnus-data)))
+     (when data
+       (gnorb-gnus-attach-part data))))
+ (defun gnorb-gnus-attach-part (handle &optional org-heading)
+   "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))))
+     (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-attach-attach filename nil 'mv))))
+ (defun gnorb-gnus-save-part (handle)
+   (let ((filename (or (mail-content-type-get
+                      (mm-handle-disposition handle) 'filename)
+                     (mail-content-type-get
+                      (mm-handle-type handle) 'name))))
+     (setq filename
+         (gnus-map-function mm-file-name-rewrite-functions
+                            (file-name-nondirectory filename)))
+     (setq filename (expand-file-name filename gnorb-tmp-dir))
+     (mm-save-part-to-file handle filename)
+     filename))
+ (defun gnorb-gnus-collect-all-attachments (&optional capture-p store)
+   "Collect all the attachments from the message under point, and
+ save them into `gnorb-tmp-dir'."
+   (save-window-excursion
+     (when capture-p
+       (set-buffer (org-capture-get :original-buffer)))
+     (unless (memq major-mode '(gnus-summary-mode gnus-article-mode))
+       (error "Only works in Gnus summary or article buffers"))
+     (let ((article (gnus-summary-article-number)) 
+         mime-handles)
+       (when (or (null gnus-current-article)
+               (null gnus-article-current)
+               (/= article (cdr gnus-article-current))
+               (not (equal (car gnus-article-current) gnus-newsgroup-name)))
+       (gnus-summary-display-article article))
+       (gnus-eval-in-buffer-window gnus-article-buffer
+       (setq mime-handles (cl-remove-if-not
+                           (lambda (h)
+                             (let ((disp (mm-handle-disposition (cdr h))))
+                               (and (member (car disp)
+                                            '("inline" "attachment"))
+                                    (mail-content-type-get disp 'filename))))
+                           gnus-article-mime-handle-alist)))
+       (when mime-handles
+       (dolist (h mime-handles)
+         (let ((filename
+                (gnorb-gnus-save-part (cdr h))))
+           (when (or capture-p store)
+             (push filename gnorb-gnus-capture-attachments))))))))
+ ;;; Make the above work in the capture process
+ (defun gnorb-gnus-capture-attach ()
+   (when (and (or gnorb-gnus-capture-always-attach
+                (org-capture-get :gnus-attachments))
+            (with-current-buffer
+                (org-capture-get :original-buffer)
+              (memq major-mode '(gnus-summary-mode gnus-article-mode))))
+     (require 'org-attach)
+     (setq gnorb-gnus-capture-attachments nil)
+     (gnorb-gnus-collect-all-attachments t)
+     (map-y-or-n-p
+      (lambda (a)
+        (format "Attach %s to capture 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)))
+ (add-hook 'org-capture-mode-hook 'gnorb-gnus-capture-attach)
+ (defun gnorb-gnus-capture-abort-cleanup ()
+   (when (and org-note-abort
+            (org-capture-get :gnus-attachments))
+     (condition-case error
+       (progn (org-attach-delete-all)
+              (setq abort-note 'clean)
+              ;; remove any gnorb-mail-header values here
+              )
+       (error
+        (setq abort-note 'dirty)))))
+ (add-hook 'org-capture-prepare-finalize-hook
+         'gnorb-gnus-capture-abort-cleanup)
+ ;;; Storing, removing, and acting on Org headers in messages.
+ (defvar gnorb-gnus-message-info nil
+   "Place to store the To, Subject, Date, and Message-ID headers
+   of the currently-sending or last-sent message.")
+ (defun gnorb-gnus-check-outgoing-headers ()
+   "Save the value of the `gnorb-mail-header' for the current
+ message; multiple header values returned as a string. Also save
+ information about the outgoing message into
+ `gnorb-gnus-message-info'."
+   (save-restriction
+     (message-narrow-to-headers)
+     (setq gnorb-gnus-message-info nil)
+     (let* ((org-ids (mail-fetch-field gnorb-mail-header nil nil t))
+          (msg-id (mail-fetch-field "Message-ID"))
+          (refs (mail-fetch-field "References"))
+          (in-reply-to (mail-fetch-field "In-Reply-To"))
+          (to (if (message-news-p)
+                  (mail-fetch-field "Newsgroups")
+                (mail-fetch-field "To")))
+          (from (mail-fetch-field "From"))
+          (subject (mail-fetch-field "Subject"))
+          (date (mail-fetch-field "Date"))
+          ;; If we can get a link, that's awesome.
+          (gcc (mail-fetch-field "Gcc"))
+          (link (or (and gcc
+                         (org-store-link nil))
+                    nil))
+          (group (ignore-errors (car (split-string link "#")))))
+       ;; If we can't make a real link, then save some information so
+       ;; we can fake it.
+       (when in-reply-to
+       (setq refs (concat refs " " in-reply-to)))
+       (when refs
+       (setq refs (gnus-extract-references refs)))
+       (setq gnorb-gnus-message-info
+           `(:subject ,subject :msg-id ,msg-id
+                      :to ,to :from ,from
+                      :link ,link :date ,date :refs ,refs
+                      :group ,group))
+       (if org-ids
+         (progn
+           (require 'gnorb-org)
+           (setq gnorb-message-org-ids org-ids)
+           ;; `gnorb-org-setup-message' may have put this here, but
+           ;; if we're working from a draft, or triggering this from
+           ;; a reply, it might not be there yet.
+           (add-to-list 'message-exit-actions
+                        'gnorb-org-restore-after-send t))
+       (setq gnorb-message-org-ids nil)))))
+ (add-hook 'message-header-hook 'gnorb-gnus-check-outgoing-headers)
+ ;;;###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.
+ 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.
+ If a new todo is made, it needs a capture template: set
+ `gnorb-gnus-new-todo-capture-key' to the string key for the
+ appropriate capture template. If you're using a gnus-based
+ archive method (ie you have `gnus-message-archive-group' set to
+ something, and your outgoing messages have a \"Fcc\" header),
+ then a real link will be made to the outgoing message, and all
+ the gnus-type escapes will be available (see the Info
+ manual (org) Template expansion section). If you don't, then the
+ %:subject, %:to, %:toname, %:toaddress, and %:date escapes for
+ the outgoing message will still be available -- nothing else will
+ 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
+       (setq rel-headings
+           (org-refile-get-location "Trigger action on" nil t))
+       (setq rel-headings
+           (list (list (save-window-excursion
+                         (find-file (nth 1 rel-headings))
+                         (goto-char (nth 3 rel-headings))
+                         (org-id-get-create))))))
+     (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.
+             (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")))))
+ (defun gnorb-gnus-outgoing-make-todo-1 ()
+   (unless gnorb-gnus-new-todo-capture-key
+     (error "No capture template key set, customize gnorb-gnus-new-todo-capture-key"))
+   (let* ((link (plist-get gnorb-gnus-message-info :link))
+        (group (plist-get gnorb-gnus-message-info :group))
+        (date (plist-get gnorb-gnus-message-info :date))
+        (date-ts (and date
+                      (ignore-errors
+                        (format-time-string
+                         (org-time-stamp-format t)
+                         (date-to-time date)))))
+        (date-ts-ia (and date
+                         (ignore-errors
+                           (format-time-string
+                            (org-time-stamp-format t t)
+                            (date-to-time date)))))
+        (msg-id (plist-get gnorb-gnus-message-info :msg-id))
+        (sender (plist-get gnorb-gnus-message-info :from))
+        (subject (plist-get gnorb-gnus-message-info :subject))
+        ;; Convince Org we already have a link stored, even if we
+        ;; don't.
+        (org-capture-link-is-already-stored t))
+     (if link
+       ;; Even if you make a link to not-yet-sent messages, even if
+       ;; you've saved the draft and it has a Date header, that
+       ;; header isn't saved into the link plist. So fake that, too.
+       (org-add-link-props
+        :date date
+        :date-timestamp date-ts
+        :date-timestamp-inactive date-ts-ia
+        :annotation link)
+       (org-store-link-props
+        :subject (plist-get gnorb-gnus-message-info :subject)
+        :to (plist-get gnorb-gnus-message-info :to)
+        :date date
+        :date-timestamp date-ts
+        :date-timestamp-inactive date-ts-ia
+        :message-id msg-id
+        :annotation link))
+     (org-capture nil gnorb-gnus-new-todo-capture-key)
+     (when msg-id
+       (org-entry-put (point) gnorb-org-msg-id-key msg-id)
+       (gnorb-registry-make-entry msg-id sender subject (org-id-get-create) group))))
+ ;;; If an incoming message should trigger state-change for a Org todo,
+ ;;; call this function on it.
+ ;;;###autoload
+ (defun gnorb-gnus-incoming-do-todo (arg headers &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.
+ Set `gnorb-trigger-todo-default' to 'note or 'todo (you can
+ get the non-default behavior by calling this function with a
+ prefix argument), or to 'prompt to always be prompted.
+ In some cases, Gnorb can guess for you which Org heading you
+ probably want to trigger, which can save some time. It does this
+ by looking in the References header, and seeing if any of the IDs
+ 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"))
+   (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
+   ;; `org-stored-links'. There's some duplicate storage, at
+   ;; present. Take a look at calling it non-interactively.
+   (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))
+        (from (mail-header-from headers))
+        (subject (mail-header-subject headers))
+        (date (mail-header-date headers))
+        (to (cdr (assoc 'To (mail-header-extra headers))))
+        (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))))
+        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))
+     (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)
+       (setq targ (org-refile-get-location
+                   "Trigger heading" nil t))
+       (find-file (nth 1 targ))
+       (goto-char (nth 3 targ))
+       (gnorb-trigger-todo-action arg)))))
+ ;;;###autoload
+ (defun gnorb-gnus-search-messages (str &optional ret)
+   "Initiate a search for gnus message links in an org subtree.
+ The arg STR can be one of two things: an Org heading id value
+ \(IDs should be prefixed with \"id+\"\), in which case links will
+ be collected from that heading, or a string corresponding to an
+ Org tags search, in which case links will be collected from all
+ matching headings.
+ In either case, once a collection of links have been made, they
+ will all be displayed in an ephemeral group on the \"nngnorb\"
+ server. There must be an active \"nngnorb\" server for this to
+ work."
+   (interactive)
+   (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))
+     (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
+      ;; "nnir:" (prin1-to-string '((query str))))
+      (if (version= "5.13" gnus-version-number)
+        (concat "nnir:" (prin1-to-string `((query ,str))))
+        (concat "gnorb-" str))
+      (if (version= "5.13" gnus-version-number)
+        (list 'nnir nnir-address)
+        (list 'nnir "nnir"))
+      nil
+      ret ;; it's possible you can't just put an arbitrary form in
+        ;; here, which sucks.
+      nil nil
+      ;; the following seems to simply be ignored under gnus 5.13
+      (list (cons 'nnir-specs (list (cons 'nnir-query-spec `((query . ,str)))
+                                  (cons 'nnir-group-spec `((,nnir-address nil)))))
+          (cons 'nnir-artlist nil)))
+     (gnorb-summary-minor-mode)))
+ ;;; Automatic noticing of relevant messages
+ ;; likely hooks for the summary buffer include:
+ ;; `gnus-parse-headers-hook'
+ ;; BBDB puts its notice stuff in the `gnus-article-prepare-hook',
+ ;; which seems as good a spot as any.
+ (defun gnorb-gnus-hint-relevant-message ()
+   "When opening an article buffer, check the message to see if it
+ 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
+            (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"))
+          (assoc-heading
+           (gnus-registry-get-id-key msg-id 'gnorb-ids))
+          (key
+           (where-is-internal 'gnorb-gnus-incoming-do-todo
+                              nil t))
+          rel-headings)
+       (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"))))))))
+ (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
+               " "))
+         " "))
+ (fset (intern (concat "gnus-user-format-function-"
+                     gnorb-gnus-summary-mark-format-letter))
+       (lambda (header)
+       (gnorb-gnus-insert-format-letter-maybe header)))
+ ;;;###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))
+       (delete-other-windows)
+       (org-id-goto (car rel-headings)))))
+ (provide 'gnorb-gnus)
+ ;;; gnorb-gnus.el ends here
index 0000000000000000000000000000000000000000,bc46eda7acd9d9a8eb41f3929e7fae3124c8533b..bc46eda7acd9d9a8eb41f3929e7fae3124c8533b
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,678 +1,678 @@@
+ ;;; gnorb-org.el --- The Org-centric functions of gnorb
+ ;; Copyright (C) 2014  Eric Abrahamsen
+ ;; Author: Eric Abrahamsen  <eric@ericabrahamsen.net>
+ ;; Keywords: 
+ ;; This program is free software; you can redistribute it and/or modify
+ ;; it under the terms of the GNU General Public License as published by
+ ;; the Free Software Foundation, either version 3 of the License, or
+ ;; (at your option) any later version.
+ ;; This program is distributed in the hope that it will be useful,
+ ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ ;; GNU General Public License for more details.
+ ;; You should have received a copy of the GNU General Public License
+ ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ ;;; Commentary:
+ ;; 
+ ;;; Code:
+ (require 'gnorb-utils)
+ (defgroup gnorb-org nil
+   "The Org bits of Gnorb."
+   :tag "Gnorb Org"
+   :group 'gnorb)
+ (defcustom gnorb-org-after-message-setup-hook nil
+   "Hook run in a message buffer after setting up the message from
+   `gnorb-org-handle-mail' or `gnorb-org-email-subtree'."
+   :group 'gnorb-org
+   :type 'hook)
+ (defcustom gnorb-org-trigger-actions
+   '(("todo state" . todo)
+     ("take note" . note)
+     ("don't associate" . no-associate)
+     ("only associate" . associate)
+ ;    ("capture to child" . cap-child)
+ ;    ("capture to sibling" . cap-sib)
+ )
+   "List of potential actions that can be taken on headings.
+ When triggering an Org heading after receiving or sending a
+ message, this option lists the possible actions to take. Built-in
+ actions include:
+ todo state: Associate the message, and change TODO state.
+ take note: Associate the message, and take a note.
+ don't associate: Do nothing at all, don't connect the message and TODO.
+ only associate: Associate the message with this heading, do nothing else.
+ capture to child: [not yet implemented] Associate this message with a new child heading.
+ capture to sibling: [not yet implemented] Associate this message with a new sibling heading.
+ You can reorder this list or remove items as suits your workflow.
+ The two \"capture\" options will use the value of
+ `gnorb-gnus-new-todo-capture-key' to find the appropriate
+ template.
+ You can also add custom actions to the list. Actions should be a
+ cons of a string tag and a symbol indicating a custom function.
+ This function will be called on the heading in question, and
+ passed a plist containing information about the message from
+ which we're triggering."
+   :group 'gnorb-org
+   :type 'list)
+ (defcustom gnorb-org-msg-id-key "GNORB_MSG_ID"
+   "The name of the org property used to store the Message-IDs
+   from relevant messages. This is no longer used, and will be
+   removed soon."
+   :group 'gnorb-org
+   :type 'string)
+ (defcustom gnorb-org-mail-scan-scope 2
+   "Number of paragraphs to scan for mail-related links.
+ When handling a TODO heading with `gnorb-org-handle-mail', Gnorb
+ will typically reply to the most recent message associated with
+ this heading. If there are no such messages, or message tracking
+ is disabled entirely, or `gnorb-org-handle-mail' has been called
+ with a prefix arg, the heading and body text of the subtree under
+ point will instead be scanned for gnus:, mailto:, and bbdb:
+ links. This option controls how many paragraphs of body text to
+ scan. Set to 0 to only look in the heading.")
+ (make-obsolete-variable
+  'gnorb-org-mail-scan-strategies
+  "This variable has been superseded by `gnorb-org-trigger-actions'"
+  "September 12, 2014" 'set)
+ (make-obsolete-variable
+  'gnorb-org-mail-scan-state-changes
+  "This variable has been superseded by `gnorb-org-trigger-actions'"
+  "September 12, 2014" 'set)
+ (make-obsolete-variable
+  'gnorb-org-mail-scan-function
+  "This variable has been superseded by `gnorb-org-trigger-actions'"
+  "September 12, 2014" 'set)
+ (defcustom gnorb-org-find-candidates-match nil
+   "When scanning all org files for heading related to an incoming
+ message, this option will limit which headings will be offered as
+ target candidates. Specifically it will be used as the second
+ argument to `org-map-entries', and syntax is the same as that
+ used in an agenda tags view."
+   :group 'gnorb-org
+   :type 'symbol)
+ ;;;###autoload
+ (defun gnorb-org-contact-link (rec)
+   "Prompt for a BBDB record and insert a link to that record at
+ point.
+ There's really no reason to use this instead of regular old
+ `org-insert-link' with BBDB completion. But there might be in the
+ future!"
+   ;; this needs to handle an active region.
+   (interactive (list (gnorb-prompt-for-bbdb-record)))
+   (let* ((name (bbdb-record-name rec))
+        (link (concat "bbdb:" (org-link-escape name))))
+     (org-store-link-props :type "bbdb" :name name
+                         :link link :description name)
+     (if (called-interactively-p 'any)
+       (insert (format "[[%s][%s]]" link name))
+       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."
+   (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))
+ (defun gnorb-org-extract-links (&optional arg region)
+   "See if there are viable links in the subtree under point."
+   ;; We're not currently using the arg. What could we do with it?
+   (let (strings)
+     ;; If the region was active, only use the region
+     (if region
+       (push (buffer-substring (car region) (cdr region))
+             strings)
+       ;; Otherwise collect the heading text, and all the paragraph
+       ;; text.
+       (save-restriction
+       (org-narrow-to-subtree)
+       (let ((head (org-element-at-point))
+             (tree (org-element-parse-buffer)))
+         (push (org-element-property
+                :raw-value
+                head)
+               strings)
+         (org-element-map tree 'paragraph
+           (lambda (p)
+             (push (org-element-interpret-data p)
+                   strings))
+           nil nil 'drawer))))
+     (when strings
+       ;; Limit number of paragraphs based on
+       ;; `gnorb-org-mail-scan-scope'
+       (setq strings
+           (cond ((eq gnorb-org-mail-scan-scope 'all)
+                  strings)
+                 ((numberp gnorb-org-mail-scan-scope)
+                  (delq nil
+                        (subseq
+                         strings 0 (1+ gnorb-org-mail-scan-scope))))
+                 ;; We could provide more options here. 'tree vs
+                 ;; 'subtree, for instance.
+                 (t
+                  strings)))
+       (with-temp-buffer
+       (dolist (s strings)
+         (insert s)
+         (insert "\n"))
+       (goto-char (point-min))
+       (gnorb-scan-links (point-max) 'gnus 'mail 'bbdb)))))
+ (defun gnorb-org-extract-mail-stuff (&optional arg region)
+   "Decide how to hande the Org heading under point as an email task.
+ See the docstring of `gnorb-org-handle-mail' for details."
+   (if (or (not gnorb-tracking-enabled)
+         region)
+       (gnorb-org-extract-links arg region)
+     ;; Get all the messages associated with the IDS in this subtree.
+     (let ((assoc-msg-ids
+          (delete-dups
+           (cl-mapcan
+            (lambda (id)
+              (gnorb-registry-org-id-search id))
+            (gnorb-collect-ids)))))
+       (gnorb-org-extract-mail-tracking assoc-msg-ids arg region))))
+ (defun gnorb-org-extract-mail-tracking (assoc-msg-ids &optional arg region)
+   (let* ((all-links (gnorb-org-extract-links nil region))
+        ;; The latest (by the creation-time registry key) of all the
+        ;; tracked messages that were not sent by our user.
+        (latest-msg-id
+         (when assoc-msg-ids
+           (car
+            (sort
+             (remove-if
+              (lambda (m)
+                (let ((from (car (gnus-registry-get-id-key m 'sender))))
+                  (or (null from)
+                      (string-match-p
+                       user-mail-address from)
+                      (string-match-p
+                       message-alternative-emails from))))
+              assoc-msg-ids)
+             (lambda (r l)
+               (time-less-p
+                (car (gnus-registry-get-id-key l 'creation-time))
+                (car (gnus-registry-get-id-key r 'creation-time)))))))))
+     (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))
+       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)))))))
+ (defun gnorb-org-setup-message
+     (&optional messages mails from cc bcc attachments text ids)
+   "Common message setup routine for other gnorb-org commands.
+ MESSAGES is a list of gnus links pointing to messages -- we
+ currently only use the first of the list. MAILS is a list of
+ email address strings suitable for inserting in the To header.
+ ATTACHMENTS is a list of filenames to attach. TEXT is a string or
+ buffer, which is inserted in the message body. IDS is one or more
+ Org heading ids, associating the outgoing message with those
+ headings."
+   (require 'gnorb-gnus)
+   (if (not messages)
+       ;; Either compose new message...
+       (compose-mail (mapconcat 'identity mails ", "))
+     ;; ...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))
+            (signal (car err) (cdr err)))))
+   ;; Return us after message is sent.
+   (add-to-list 'message-exit-actions
+              'gnorb-org-restore-after-send t)
+   ;; Set headers from MAIL_* properties (from, cc, and bcc).
+   (cl-flet ((sh (h)
+               (when (cdr h)
+                 (funcall (intern (format "message-goto-%s" (car h))))
+                 (let ((message-beginning-of-line t)
+                       (show-trailing-whitespace t))
+                   (message-beginning-of-line)
+                   (unless (bolp)
+                     (kill-line))
+                   (insert (cdr h))))))
+     (dolist (h `((from . ,from) (cc . ,cc) (bcc . ,bcc)))
+       (sh h)))
+   ;; attach ATTACHMENTS
+   (map-y-or-n-p
+    (lambda (a) (format "Attach %s to outgoing message? "
+                      (file-name-nondirectory a)))
+    (lambda (a)
+      (mml-attach-file a (mm-default-file-encoding a)
+                     nil "attachment"))
+    attachments
+    '("file" "files" "attach"))
+   ;; insert text, if any
+   (when text
+     (message-goto-body)
+     (insert"\n")
+     (if (bufferp text)
+       (insert-buffer-substring text)
+       (insert text)))
+   ;; insert org ids, if any
+   (when ids
+     (unless (listp ids)
+       (setq ids (list ids)))
+     (save-excursion
+       (save-restriction
+       (message-narrow-to-headers)
+       (dolist (i ids)
+         (goto-char (point-at-bol))
+         (open-line 1)
+         ;; this function hardly does anything
+         (message-insert-header
+          (intern gnorb-mail-header) i)))))
+   ;; put point somewhere reasonable
+   (if (or mails messages)
+       (if (not messages)
+         (message-goto-subject)
+        (message-goto-body))
+     (message-goto-to))
+   (run-hooks 'gnorb-org-after-message-setup-hook))
+ (defun gnorb-org-attachment-list (&optional id)
+   "Get a list of files (absolute filenames) attached to the
+ current heading, or the heading indicated by optional argument ID."
+   (when (featurep 'org-attach)
+     (let* ((attach-dir (save-excursion
+                        (when id
+                          (org-id-goto id))
+                        (org-attach-dir t)))
+          (files
+           (mapcar
+            (lambda (f)
+              (expand-file-name f attach-dir))
+            (org-attach-file-list attach-dir))))
+       files)))
+ ;;;###autoload
+ (defun gnorb-org-handle-mail (&optional arg text file)
+   "Handle current headline as a mail TODO.
+ How this function behaves depends on whether you're using Gnorb
+ for email tracking, also on the prefix arg, and on the active
+ region.
+ If tracking is enabled and there is no prefix arg, Gnorb will
+ begin a reply to the newest associated message that wasn't sent
+ by the user -- ie, the Sender header doesn't match
+ `user-mail-address' or `message-alternative-emails'.
+ If tracking is enabled and there is a prefix arg, ignore the
+ tracked messages and instead scan the subtree for mail-related
+ links. This means links prefixed with gnus:, mailto:, or bbdb:.
+ See `gnorb-org-mail-scan-scope' to limit the scope of this scan.
+ Do something appropriate with the resulting links.
+ With a double prefix arg, ignore all tracked messages and all
+ links, and compose a blank new message.
+ If tracking is enabled and you want to reply to a
+ specific (earlier) message in the tracking history, use
+ `gnorb-org-view' to open an nnir *Summary* buffer containing all
+ the messages, and reply to the one you want. Your reply will be
+ automatically tracked, as well.
+ If tracking is not enabled and you want to use a specific link in
+ the subtree as a basis for the email action, then put the region
+ around that link before you call this message."
+   (interactive "P")
+   (setq gnorb-window-conf (current-window-configuration))
+   (move-marker gnorb-return-marker (point))
+   (when (eq major-mode 'org-agenda-mode)
+     ;; If this is all the different types, we could skip the check.
+     (org-agenda-check-type t 'agenda 'timeline 'todo 'tags 'search)
+     (org-agenda-check-no-diary)
+     (let* ((marker (or (org-get-at-bol 'org-hd-marker)
+                      (org-agenda-error)))
+          (buffer (marker-buffer marker))
+          (pos (marker-position marker)))
+       (switch-to-buffer buffer)
+       (widen)
+       (goto-char pos)))
+   (let ((region
+        (when (use-region-p)
+          (cons (region-beginning) (region-end)))))
+     (deactivate-mark)
+     (save-excursion
+       (unless (org-back-to-heading t)
+       (error "Not in an org item"))
+       (cl-flet ((mp (p) (org-entry-get (point) p t)))
+       ;; Double prefix means ignore everything and compose a blank
+       ;; mail.
+       (let* ((links (unless (equal arg '(16))
+                       (gnorb-org-extract-mail-stuff arg region)))
+              (attachments (gnorb-org-attachment-list))
+              (from (mp "MAIL_FROM"))
+              (cc (mp "MAIL_CC"))
+              (bcc (mp "MAIL_BCC"))
+              (org-id (org-id-get-create))
+              (recs (plist-get links :bbdb))
+              (message-mode-hook (copy-sequence message-mode-hook))
+              mails)
+         (when file
+           (cons file attachments))
+         (when recs
+           (setq recs
+                 (delq nil
+                       (mapcar
+                        (lambda (r)
+                          (car (bbdb-message-search
+                                (org-link-unescape r)
+                                nil)))
+                        recs))))
+         (when recs
+           (dolist (r recs)
+             (push (bbdb-mail-address r) mails)))
+         (when (and recs
+                    gnorb-bbdb-posting-styles)
+           (add-hook 'message-mode-hook
+                     (lambda ()
+                       (gnorb-bbdb-configure-posting-styles (cdr recs))
+                       (gnorb-bbdb-configure-posting-styles (list (car recs))))))
+         (gnorb-org-setup-message
+          (plist-get links :gnus)
+          (append mails (plist-get links :mail))
+          from cc bcc
+          attachments text org-id))))))
+ ;;; Email subtree
+ (defcustom gnorb-org-email-subtree-text-parameters nil
+   "A plist of export parameters corresponding to the EXT-PLIST
+   argument to the export functions, for use when exporting to
+   text."
+   :group 'gnorb-org
+   :type 'boolean)
+ (defcustom gnorb-org-email-subtree-file-parameters nil
+   "A plist of export parameters corresponding to the EXT-PLIST
+   argument to the export functions, for use when exporting to a
+   file."
+   :group 'gnorb-org
+   :type 'boolean)
+ (defcustom gnorb-org-email-subtree-text-options '(nil t nil t)
+   "A list of ts and nils corresponding to Org's export options,
+ to be used when exporting to text. The options, in order, are
+ async, subtreep, visible-only, and body-only."
+   :group 'gnorb-org
+   :type 'list)
+ (defcustom gnorb-org-email-subtree-file-options '(nil t nil nil)
+   "A list of ts and nils corresponding to Org's export options,
+ to be used when exporting to a file. The options, in order, are
+ async, subtreep, visible-only, and body-only."
+   :group 'gnorb-org
+   :type 'list)
+ (defcustom gnorb-org-export-extensions
+   '((latex ".tex")
+     (ascii ".txt")
+     (html ".html")
+     (org ".org")
+     (icalendar ".ics")
+     (man ".man")
+     (md ".md")
+     (odt ".odt") ; not really, though
+     (texinfo ".texi")
+     (beamer ".tex"))
+   "Correspondence between export backends and their
+ respective (usual) file extensions. Ugly way to do it, but what
+ the hey..."
+   :group 'gnorb-org)
+ ;;;###autoload
+ (defun gnorb-org-email-subtree (&optional arg)
+   "Call on a subtree to export it either to a text string or a file,
+ then compose a mail message either with the exported text
+ inserted into the message body, or the exported file attached to
+ the message.
+ Export options default to the following: When exporting to a
+ buffer: async = nil, subtreep = t, visible-only = nil, body-only
+ = t. Options are the same for files, except body-only is set to
+ nil. Customize `gnorb-org-email-subtree-text-options' and
+ `gnorb-org-email-subtree-file-options', respectively.
+ Customize `gnorb-org-email-subtree-parameters' to your preferred
+ default set of parameters."
+   ;; I sure would have liked to use the built-in dispatch ui, but it's
+   ;; got too much hard-coded stuff.
+   (interactive "P")
+   (org-back-to-heading t)
+   (let* ((backend-string
+         (org-completing-read
+          "Export backend: "
+          (mapcar (lambda (b)
+                    (symbol-name (org-export-backend-name b)))
+                  org-export--registered-backends) nil t))
+        (backend-symbol (intern backend-string))
+        (f-or-t (org-completing-read "Export as file or text? "
+                                     '("file" "text") nil t))
+        (org-export-show-temporary-export-buffer nil)
+        (opts (if (equal f-or-t "text")
+                  gnorb-org-email-subtree-text-options
+                gnorb-org-email-subtree-file-options))
+        (result
+         (if (equal f-or-t "text")
+             (apply 'org-export-to-buffer
+                    `(,backend-symbol
+                      "*Gnorb Export*"
+                      ,@opts
+                      ,gnorb-org-email-subtree-text-parameters))
+           (apply 'org-export-to-file
+                  `(,backend-symbol
+                    ,(org-export-output-file-name
+                      (second (assoc backend-symbol gnorb-org-export-extensions))
+                      t gnorb-tmp-dir)
+                    ,@opts
+                    ,gnorb-org-email-subtree-file-parameters))))
+        text file)
+     (setq gnorb-window-conf (current-window-configuration))
+     (move-marker gnorb-return-marker (point))
+     (if (bufferp result)
+       (setq text result)
+       (setq file result))
+     (gnorb-org-handle-mail arg text file)))
+ (defcustom gnorb-org-capture-collect-link-p t
+   "Should the capture process store a link to the gnus message or
+   BBDB record under point, even if it's not part of the template?
+   You'll probably end up needing it, anyway."
+   :group 'gnorb-org)
+ (defun gnorb-org-capture-collect-link ()
+   (when gnorb-org-capture-collect-link-p
+     (let ((buf (org-capture-get :original-buffer)))
+       (when buf
+       (with-current-buffer buf
+         (when (memq major-mode '(gnus-summary-mode
+                                  gnus-article-mode
+                                  bbdb-mode))
+           (call-interactively 'org-store-link)))))))
+ (add-hook 'org-capture-mode-hook 'gnorb-org-capture-collect-link)
+ ;;; Agenda/BBDB popup stuff
+ (defcustom gnorb-org-agenda-popup-bbdb nil
+   "Should Agenda tags search pop up a BBDB buffer with matching
+   records?
+ Records are considered matching if they have an `org-tags' field
+ matching the current Agenda search. The name of that field can be
+ customized with `gnorb-bbdb-org-tag-field'."
+   :group 'gnorb-org)
+ (defcustom gnorb-org-bbdb-popup-layout 'pop-up-multi-line
+   "Default BBDB buffer layout for automatic Org Agenda display."
+   :group 'gnorb-org
+   :type '(choice (const one-line)
+                (const multi-line)
+                (const full-multi-line)
+                (symbol)))
+ ;;;###autoload
+ (defun gnorb-org-popup-bbdb (&optional str)
+   "In an `org-tags-view' Agenda buffer, pop up a BBDB buffer
+ showing records whose `org-tags' field matches the current tags
+ search."
+   ;; I was hoping to use `org-make-tags-matcher' directly, then snag
+   ;; the tagmatcher from the resulting value, but there doesn't seem
+   ;; to be a reliable way of only getting the tag-related returns. But
+   ;; I'd still like to use that function. So an ugly hack to first
+   ;; remove non-tag contents from the query string, and then make a
+   ;; new call to `org-make-tags-matcher'.
+   (interactive)
+   (require 'gnorb-bbdb)
+   (let (recs)
+     (cond ((and
+           (and (eq major-mode 'org-agenda-mode)
+                (eq org-agenda-type 'tags))
+           (or (called-interactively-p 'any)
+               gnorb-org-agenda-popup-bbdb))
+          (let ((todo-only nil)
+                (str (or str org-agenda-query-string))
+                (re "^&?\\([-+:]\\)?\\({[^}]+}\\|LEVEL\\([<=>]\\{1,2\\}\\)\\([0-9]+\\)\\|\\(\\(?:[[:alnum:]_]+\\(?:\\\\-\\)*\\)+\\)\\([<>=]\\{1,2\\}\\)\\({[^}]+}\\|\"[^\"]*\"\\|-?[.0-9]+\\(?:[eE][-+]?[0-9]+\\)?\\)\\|[[:alnum:]_@#%]+\\)")
+                or-terms term rest out-or acc tag-clause)
+            (setq or-terms (org-split-string str "|"))
+            (while (setq term (pop or-terms))
+              (setq acc nil)
+              (while (string-match re term)
+                (setq rest (substring term (match-end 0)))
+                (let ((sub-term (match-string 0 term)))
+                  (unless (save-match-data ; this isn't a tag, don't want it
+                            (string-match "\\([<>=]\\)" sub-term))
+                    (push sub-term acc))
+                  (setq term rest)))
+              (push (mapconcat 'identity (nreverse acc) "") out-or))
+            (setq str (mapconcat 'identity (nreverse out-or) "|"))
+            (setq tag-clause (cdr (org-make-tags-matcher str)))
+            (unless (equal str "")
+              (setq recs
+                    (remove-if-not
+                     (lambda (r)
+                       (let ((rec-tags (bbdb-record-xfield
+                                        r gnorb-bbdb-org-tag-field)))
+                         (and rec-tags
+                              (let ((tags-list (org-split-string rec-tags ":"))
+                                    (case-fold-search t)
+                                    (org-trust-scanner-tags t))
+                                (eval tag-clause)))))
+                     (bbdb-records))))))
+         ((eq major-mode 'org-mode)
+          (save-excursion
+            (org-back-to-heading)
+            (let ((bound (org-element-property
+                          :end (org-element-at-point)))
+                  desc rec)
+              (while (re-search-forward
+                      org-bracket-link-analytic-regexp bound t)
+                (when (string-match-p "bbdb" (match-string 2))
+                  (setq desc (match-string 5)
+                        rec (bbdb-search (bbdb-records) desc desc desc)
+                        recs (append recs rec))))))))
+     (if recs
+       (bbdb-display-records
+        recs gnorb-org-bbdb-popup-layout)
+       (when (get-buffer-window bbdb-buffer-name)
+       (quit-window nil
+                    (get-buffer-window bbdb-buffer-name)))
+       (when (called-interactively-p 'any)
+       (message "No relevant BBDB records")))))
+ (if (featurep 'gnorb-bbdb)
+     (add-hook 'org-agenda-finalize-hook 'gnorb-org-popup-bbdb))
+ ;;; Groups from the gnorb gnus server backend
+ ;;;###autoload
+ (defun gnorb-org-view ()
+   "Search the subtree at point for links to gnus messages, and
+ then show them in an ephemeral group, in gnus.
+ 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)
+   (setq gnorb-window-conf (current-window-configuration))
+   (move-marker gnorb-return-marker (point))
+   (when (eq major-mode 'org-agenda-mode)
+     (org-agenda-check-type t 'agenda 'timeline 'todo 'tags)
+     (org-agenda-check-no-diary)
+     (let* ((marker (or (org-get-at-bol 'org-hd-marker)
+                      (org-agenda-error)))
+          (buffer (marker-buffer marker))
+          (pos (marker-position marker)))
+       (switch-to-buffer buffer)
+       (goto-char pos)
+       (org-reveal)))
+   (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)))))
+ (provide 'gnorb-org)
+ ;;; gnorb-org.el ends here
index 0000000000000000000000000000000000000000,0eee32ccd221835d3fee05396b227916e0b42698..0eee32ccd221835d3fee05396b227916e0b42698
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,194 +1,194 @@@
+ ;;; gnorb-registry.el --- Registry implementation for Gnorb
+ ;; This file is in the public domain.
+ ;; Author: Eric Abrahamsen <eric@ericabrahamsen.net.>
+ ;; This file is part of GNU Emacs.
+ ;; GNU Emacs is free software: you can redistribute it and/or modify
+ ;; it under the terms of the GNU General Public License as published by
+ ;; the Free Software Foundation, either version 3 of the License, or
+ ;; (at your option) any later version.
+ ;; GNU Emacs is distributed in the hope that it will be useful,
+ ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ ;; GNU General Public License for more details.
+ ;; You should have received a copy of the GNU General Public License
+ ;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+ ;;; Commentary:
+ ;; Early on, Gnorb's message/todo tracking was done by relying on the
+ ;; user to insert links to received messages into an Org heading, and
+ ;; by automatically storing the Message-Ids of sent messages in a
+ ;; property (`gnorb-org-msg-id-key', defaulting to GNORB_MSG_ID) on
+ ;; the same heading. The heading could find all relevant messages by
+ ;; combining the links (incoming) and the IDs of the Gnorb-specific
+ ;; property (outgoing).
+ ;;
+ ;; In the end, this proved to be fragile and messy. Enter the
+ ;; registry. The Gnus registry is a specialization of a general
+ ;; "registry" library -- it's possible to roll your own. If you want
+ ;; to track connections between messages and Org headings, it's an
+ ;; obvious choice: Each relevant message is stored in the registry,
+ ;; keyed on its Message-ID, and the org-ids of all relevant headings
+ ;; are stored in a custom property, in our case gnorb-ids. This allows
+ ;; us to keep all Gnorb-specific data in one place, without polluting
+ ;; Org files or Gnus messages, persistent on disk, and with the added
+ ;; bonus of providing a place to keep arbitrary additional metadata.
+ ;;
+ ;; The drawback is that the connections are no longer readily visible
+ ;; to the user (they need to query the registry to see them), and it
+ ;; becomes perhaps a bit more difficult (but only a bit) to keep
+ ;; registry data in sync with the current state of the user's Gnus and
+ ;; Org files. But a clear win, in the end.
+ ;;; Code:
+ (require 'gnus-registry)
+ (defgroup gnorb-registry nil
+   "Gnorb's use of the Gnus registry."
+   :tag "Gnorb Registry"
+   :group 'gnorb)
+ (defun gnorb-registry-make-entry (msg-id sender subject org-id group)
+   "Create a Gnus registry entry for a message, either received or
+ sent. Save the relevant Org ids in the 'gnorb-ids key."
+   ;; This set-id-key stuff is actually horribly
+   ;; inefficient.
+   (when gnorb-tracking-enabled
+     (gnus-registry-get-or-make-entry msg-id)
+     (when sender
+       (gnus-registry-set-id-key msg-id 'sender (list sender)))
+     (when subject
+       (gnus-registry-set-id-key msg-id 'subject (list subject)))
+     (when org-id
+       (let ((ids (gnus-registry-get-id-key msg-id 'gnorb-ids)))
+       (unless (member org-id ids)
+        (gnus-registry-set-id-key msg-id 'gnorb-ids (if (stringp org-id)
+                                                        (cons org-id ids)
+                                                      (append org-id ids))))))
+     (when group
+       (gnus-registry-set-id-key msg-id 'group (list group)))
+     (gnus-registry-get-or-make-entry msg-id)))
+ (defun gnorb-registry-capture ()
+   "When capturing from a Gnus message, add our new Org heading id
+ to the message's registry entry, under the 'gnorb-ids key."
+   (when (and (with-current-buffer
+                (org-capture-get :original-buffer)
+              (memq major-mode '(gnus-summary-mode gnus-article-mode)))
+            (not org-note-abort))
+     (let* ((msg-id
+           (format "<%s>" (plist-get org-store-link-plist :message-id)))
+          (entry (gnus-registry-get-or-make-entry msg-id))
+          (org-ids
+           (gnus-registry-get-id-key msg-id 'gnorb-ids))
+          (new-org-id (org-id-get-create)))
+       (plist-put org-capture-plist :gnorb-id new-org-id)
+       (setq org-ids (cons new-org-id org-ids))
+       (setq org-ids (delete-dups org-ids))
+       (gnus-registry-set-id-key msg-id 'gnorb-ids org-ids))))
+ (defun gnorb-registry-capture-abort-cleanup ()
+   (when (and (org-capture-get :gnorb-id)
+            org-note-abort)
+     (condition-case error
+       (let* ((msg-id (format "<%s>" (plist-get org-store-link-plist :message-id)))
+              (existing-org-ids (gnus-registry-get-id-key msg-id 'gnorb-ids))
+              (org-id (org-capture-get :gnorb-id)))
+         (when (member org-id existing-org-ids)
+           (gnus-registry-set-id-key msg-id 'gnorb-ids
+                                     (remove org-id existing-org-ids)))
+         (setq abort-note 'clean))
+       (error
+        (setq abort-note 'dirty)))))
+ (defun gnorb-find-visit-candidates (ids)
+   "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."
+   (let (ret-val sub-val)
+     (when (stringp ids)
+       (setq ids (gnus-extract-references ids)))
+     (when gnorb-tracking-enabled
+       (setq ids (delete-dups ids))
+       (progn
+       (dolist (id ids)
+         (when
+             (setq sub-val
+                   (gnus-registry-get-id-key id 'gnorb-ids))
+           (setq ret-val (append sub-val ret-val))))))
+     (delete-dups ret-val)))
+ (defun gnorb-registry-org-id-search (id)
+   "Find all messages that have the org ID in their 'gnorb-ids
+ key."
+   (registry-search gnus-registry-db :member `((gnorb-ids ,id))))
+ (defun gnorb-registry-transition-from-props (arg)
+   "Helper function for transitioning the old tracking system to the new.
+ The old system relied on storing sent message ids on relevant Org
+ headings, in the `gnorb-org-msg-id-key' property. The new system
+ uses the gnus registry to track relations between messages and
+ Org headings. This function will go through your agenda files,
+ find headings that have the `gnorb-org-msg-id-key' property set,
+ and create new registry entries that reflect that connection.
+ Call with a prefix arg to additionally delete the
+ `gnorb-org-msg-id-key' altogether from your Org headings. As this
+ function will not create duplicate registry entries, it's safe to
+ run it once with no prefix arg, to keep the properties in place,
+ and then once you're sure everything's working okay, run it again
+ with a prefix arg, to clean the Gnorb-specific properties from
+ your Org files."
+   (interactive "P")
+   (let ((count 0))
+     (message "Collecting all relevant Org headings, this could take a while...")
+     (org-map-entries
+      (lambda ()
+        (let ((id (org-id-get))
+            (props (org-entry-get-multivalued-property
+              (point) gnorb-org-msg-id-key))
+            links group id)
+       (when props
+         ;; If the property is set, we should probably assume that any
+         ;; Gnus links in the subtree are relevant, and should also be
+         ;; collected and associated.
+         (setq links (gnorb-scan-links
+                      (org-element-property :end (org-element-at-point))
+                      'gnus))
+         (dolist (l (plist-get links :gnus))
+           (gnorb-registry-make-entry
+            (second (split-string l "#")) nil nil
+            id (first (split-string l "#"))))
+         (dolist (p props)
+           (setq id )
+           (gnorb-registry-make-entry p nil nil id nil)
+           ;; This function will try to find the group for the message
+           ;; and set that value on the registry entry if it can find
+           ;; it.
+           (unless (gnus-registry-get-id-key p 'group)
+             (gnorb-msg-id-to-group p))
+           (incf count)))))
+      gnorb-org-find-candidates-match
+      'agenda 'archive 'comment)
+     (message "Collecting all relevant Org headings, this could take a while... done")
+     ;; Delete the properties if the user has asked us to do so.
+     (if (equal arg '(4))
+       (progn
+         (dolist (f (org-agenda-files))
+           (with-current-buffer (get-file-buffer f)
+             (org-delete-property-globally gnorb-org-msg-id-key)))
+         (message "%d entries created; all Gnorb-specific properties deleted."
+                  count))
+       (message "%d entries created." count))))
+ (provide 'gnorb-registry)
index 0000000000000000000000000000000000000000,68fe6b670b0204ae05916de41d16ed5da9159433..68fe6b670b0204ae05916de41d16ed5da9159433
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,311 +1,311 @@@
+ ;;; gnorb-utils.el --- Common utilities for all gnorb stuff.
+ ;; Copyright (C) 2014  Eric Abrahamsen
+ ;; Author: Eric Abrahamsen <eric@ericabrahamsen.net>
+ ;; Keywords:
+ ;; This program is free software; you can redistribute it and/or modify
+ ;; it under the terms of the GNU General Public License as published by
+ ;; the Free Software Foundation, either version 3 of the License, or
+ ;; (at your option) any later version.
+ ;; This program is distributed in the hope that it will be useful,
+ ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ ;; GNU General Public License for more details.
+ ;; You should have received a copy of the GNU General Public License
+ ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ ;;; Commentary:
+ ;;
+ ;;; Code:
+ (require 'cl)
+ (require 'mailcap)
+ (require 'gnus)
+ ;(require 'message)
+ (require 'bbdb)
+ (require 'org)
+ (require 'org-bbdb)
+ (require 'org-gnus)
+ (mailcap-parse-mimetypes)
+ (defgroup gnorb nil
+   "Glue code between Gnus, Org, and BBDB."
+   :tag "Gnorb")
+ (make-obsolete-variable
+  'gnorb-trigger-todo-default
+  "This variable has been superseded by
+ `gnorb-org-trigger-actions'"
+  "September 8, 2014" 'set)
+ (defun gnorb-prompt-for-bbdb-record ()
+   "Prompt the user for a BBDB record."
+   (let ((recs (bbdb-records))
+       name)
+     (while (> (length recs) 1)
+       (setq name
+           (completing-read
+            (format "Filter records by regexp (%d remaining): "
+                    (length recs))
+            (mapcar 'bbdb-record-name recs)))
+       (setq recs (bbdb-search recs name name name nil nil)))
+     (if recs
+       (car recs)
+       (error "No matching records"))))
+ (defvar gnorb-tmp-dir (make-temp-file "emacs-gnorb" t)
+   "Temporary directory where attachments etc are saved.")
+ (defvar gnorb-message-org-ids nil
+   "List of Org heading IDs from the outgoing Gnus message, used
+   to mark mail TODOs as done once the message is sent."
+   ;; The send hook either populates this, or sets it to nil, depending
+   ;; on whether the message in question has an Org id header. Then
+   ;; `gnorb-org-restore-after-send' checks for it and acts
+   ;; appropriately, then sets it to nil.
+   )
+ (defvar gnorb-window-conf nil
+   "Save window configurations here, for restoration after mails
+ are sent, or Org headings triggered.")
+ (defvar gnorb-return-marker (make-marker)
+   "Return point here after various actions, to be used together
+ with `gnorb-window-conf'.")
+ (defcustom gnorb-mail-header "X-Org-ID"
+   "Name of the mail header used to store the ID of a related Org
+   heading. Only used locally: always stripped when the mail is
+   sent."
+   :group 'gnorb
+   :type 'string)
+ ;;; this is just ghastly, but the value of this var is single regexp
+ ;;; group containing various header names, and we want our value
+ ;;; inside that group.
+ (eval-after-load 'message
+   `(let ((ign-headers-list
+         (split-string message-ignored-mail-headers
+                       "|"))
+        (our-val (concat gnorb-mail-header "\\")))
+      (unless (member our-val ign-headers-list)
+        (setq ign-headers-list
+            `(,@(butlast ign-headers-list 1) ,our-val
+              ,@(last ign-headers-list 1)))
+        (setq message-ignored-mail-headers
+            (mapconcat
+             'identity ign-headers-list "|")))))
+ (defun gnorb-restore-layout ()
+   "Restore window layout and value of point after a Gnorb command.
+ Some Gnorb commands change the window layout (ie `gnorb-org-view'
+ or incoming email triggering). This command restores the layout
+ 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)
+     (set-window-configuration gnorb-window-conf)
+     (when (buffer-live-p (marker-buffer gnorb-return-marker))
+       (goto-char gnorb-return-marker))))
+ (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
+ agenda. Then let the user choose an action from the value of
+ `gnorb-org-trigger-actions'."
+   (let ((agenda-p (eq major-mode 'org-agenda-mode))
+       (action (cdr (assoc
+                     (org-completing-read
+                      "Action to take: "
+                      gnorb-org-trigger-actions nil t)
+                     gnorb-org-trigger-actions)))
+       (root-marker (make-marker)))
+     ;; Place the marker for the relevant TODO heading.
+     (cond (agenda-p
+          (setq root-marker
+                (copy-marker
+                 (org-get-at-bol 'org-hd-marker))))
+         ((derived-mode-p 'org-mode)
+          (move-marker root-marker (point-at-bol)))
+         (id
+          (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)
+     (cl-labels
+       ((make-entry
+         (id)
+         (gnorb-registry-make-entry
+          (plist-get gnorb-gnus-message-info :msg-id)
+          (plist-get gnorb-gnus-message-info :from)
+          (plist-get gnorb-gnus-message-info :subject)
+          id
+          (plist-get gnorb-gnus-message-info :group))))
+       ;; Handle our action.
+       (cond ((eq action 'note)
+            (org-with-point-at root-marker
+              (make-entry (org-id-get-create))
+              (call-interactively 'org-add-note)))
+           ((eq action 'todo)
+            (if agenda-p
+                (progn
+                  (org-with-point-at root-marker
+                   (make-entry (org-id-get-create)))
+                  (call-interactively 'org-agenda-todo))
+              (org-with-point-at root-marker
+                (make-entry (org-id-get-create))
+                (call-interactively 'org-todo))))
+           ((eq action 'no-associate)
+            nil)
+           ((eq action 'associate)
+            (org-with-point-at root-marker
+              (make-entry (org-id-get-create))))
+           ((fboundp action)
+            (org-with-point-at root-marker
+              (make-entry (org-id-get-create))
+              (funcall action gnorb-gnus-message-info)))))))
+ (defun gnorb-pretty-outline (id &optional kw)
+   "Return pretty outline path of the Org heading indicated by ID.
+ If the KW argument is true, add the TODO keyword into the path."
+   (org-with-point-at (org-id-find id t)
+     (let ((el (org-element-at-point)))
+       (concat
+        (if kw
+          (format "(%s): "
+                  (org-element-property :todo-keyword el))
+        "")
+        (org-format-outline-path
+       (append
+        (list
+         (file-name-nondirectory
+          (buffer-file-name
+           (org-base-buffer (current-buffer)))))
+        (org-get-outline-path)
+        (list
+         (replace-regexp-in-string
+          org-bracket-link-regexp
+          "\\3" (org-element-property :raw-value el)))))))))
+ (defun gnorb-scan-links (bound &rest types)
+   "Scan from point to BOUND looking for links of type in TYPES.
+ TYPES is a list of symbols, possible values include 'bbdb, 'mail,
+ and 'gnus."
+   ;; this function could be refactored somewhat -- lots of code
+   ;; repetition. It also should be a little faster for when we're
+   ;; scanning for gnus links only, that's a little slow. We should
+   ;; probably use a different regexp based on the value of TYPES.
+   ;;
+   ;; This function should also *not* be responsible for unescaping
+   ;; links -- we don't know what they're going to be used for, and
+   ;; unescaped is safer.
+   (unless (= (point) bound)
+     (let (addr gnus mail bbdb)
+       (while (re-search-forward org-any-link-re bound t)
+       (setq addr (or (match-string-no-properties 2)
+                      (match-string-no-properties 0)))
+       (cond
+        ((and (memq 'gnus types)
+              (string-match "^<?gnus:" addr))
+         (push (substring addr (match-end 0)) gnus))
+        ((and (memq 'mail types)
+              (string-match "^<?mailto:" addr))
+         (push (substring addr (match-end 0)) mail))
+        ((and (memq 'bbdb types)
+              (string-match "^<?bbdb:" addr))
+         (push (substring addr (match-end 0)) bbdb))))
+       `(:gnus ,(reverse gnus) :mail ,(reverse mail) :bbdb ,(reverse bbdb)))))
+ (defun gnorb-msg-id-to-link (msg-id)
+   "Given a message id, try to create a full org link to the
+ message."
+   (let ((server-group (gnorb-msg-id-to-group msg-id)))
+     (when server-group
+       (org-link-escape (concat server-group "#" msg-id)))))
+ (defun gnorb-msg-id-to-group (msg-id)
+   "Given a message id, try to find the group it's in.
+ 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)
+     (catch 'found
+       (when gnorb-tracking-enabled
+       ;; Make a big list of all the groups where this message might
+       ;; conceivably be.
+       (setq candidates
+             (append (gnus-registry-get-id-key msg-id 'group)
+                     gnorb-gnus-sent-groups))
+       (while (setq server-group (pop candidates))
+         (when (and (stringp server-group)
+                    (not
+                     (string-match-p
+                      "\\(nnir\\|nnvirtual\\|UNKNOWN\\)"
+                      server-group))
+                    (ignore-errors
+                      (gnus-request-head msg-id server-group)))
+               (throw 'found server-group))))
+       (when (featurep 'notmuch)
+       nil))))
+ (defun gnorb-collect-ids (&optional id)
+   "Collect all Org IDs for a subtree.
+ Starting with the heading under point (or the heading indicated
+ by the ID argument), collect its ID property, and the IDs of all
+ child headings."
+   (save-excursion
+     (save-restriction
+       (when id
+       (org-id-goto id))
+       (org-narrow-to-subtree)
+       (org-element-map (org-element-parse-buffer)
+         'headline
+       (lambda (hl)
+         (org-element-property :ID hl))))))
+ ;; Loading the registry
+ (defvar gnorb-tracking-enabled nil
+   "Internal flag indicating whether Gnorb is successfully plugged
+   into the registry or not.")
+ (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)
+   (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))))
+ (provide 'gnorb-utils)
+ ;;; gnorb-utils.el ends here
diff --combined packages/gnorb/gnorb.el
index 0000000000000000000000000000000000000000,b702b71698871b72b02855d34aa2da2107e7f1f5..b702b71698871b72b02855d34aa2da2107e7f1f5
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,41 +1,41 @@@
+ ;;; gnorb.el --- Glue code between Gnus, Org, and BBDB
+ ;; Copyright (C) 2014  Eric Abrahamsen
+ ;; Version: 1
+ ;; Maintainer: Eric Abrahamsen <eric@ericabrahamsen.net>
+ ;; Author: Eric Abrahamsen <eric@ericabrahamsen.net>
+ ;; Keywords: mail org gnus bbdb todo task
+ ;; URL: https://github.com/girzel/gnorb
+ ;; This program is free software; you can redistribute it and/or modify
+ ;; it under the terms of the GNU General Public License as published by
+ ;; the Free Software Foundation, either version 3 of the License, or
+ ;; (at your option) any later version.
+ ;; This program is distributed in the hope that it will be useful,
+ ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ ;; GNU General Public License for more details.
+ ;; You should have received a copy of the GNU General Public License
+ ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ ;;; Commentary:
+ ;; Load this file to load everything.
+ ;;; Code:
+ (require 'gnorb-utils)
+ (require 'nngnorb)
+ (require 'gnorb-gnus)
+ (require 'gnorb-bbdb)
+ (require 'gnorb-org)
+ (require 'gnorb-registry)
+ (provide 'gnorb)
+ ;;; gnorb.el ends here
index 0000000000000000000000000000000000000000,64304bdcefdb40f892f695f39dbedbf181ac0c00..64304bdcefdb40f892f695f39dbedbf181ac0c00
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,701 +1,701 @@@
+ This is gnorb.info, produced by makeinfo version 5.2 from gnorb.texi.
+ INFO-DIR-SECTION Emacs
+ START-INFO-DIR-ENTRY
+ * Gnorb: (gnorb).       Glue code for Gnus, Org, and BBDB.
+ END-INFO-DIR-ENTRY
\1f
+ File: gnorb.info,  Node: Top,  Next: Introduction,  Up: (dir)
+ Gnorb Manual
+ ************
+ * Menu:
+ * Introduction::
+ * Installation::
+ * Setup::
+ * Email Tracking::
+ * Restoring Window Layout::
+ * Recent Mails From BBDB Contacts::
+ * BBDB posting styles::
+ * BBDB Org tagging::
+ * Misc BBDB::
+ * Misc Org::
+ * Misc Gnus::
+ * Suggested Keybindings::
+ * Wishlist/TODO::
+ * Index::
+ โ€” The Detailed Node Listing โ€”
+ Email Tracking
+ * Email-Related Commands::
+ * Trigger Actions::
+ * Viewing Tracked Messages in *Summary* Buffers::
+ * Hinting in Gnus::
+ * Message Attachments::
+ Misc BBDB
+ * Searching for messages from BBDB contacts::
+ * Citing BBDB contacts::
+ * User Options::
+ Misc Org
+ * Inserting BBDB links::
+ * User Options: User Optionsx. 
+ Misc Gnus
+ * Viewing Org headlines relevant to a message::
+ * User Options: User Optionsxx. 
\1f
+ File: gnorb.info,  Node: Introduction,  Next: Installation,  Prev: Top,  Up: Top
+ 1 Introduction
+ **************
+ Gnorb provides glue code between the Gnus, Org, and BBDB packages.  Itโ€™s
+ aimed at supporting email-based project management, and generally making
+ it easier to keep track of email communication.
+    Much of the code consists of single-use convenience functions, but
+ tracking email conversations with Org requires is more complicated, and
+ requires a bit of setup.
+    Gnorb can be used in a modular fashion, by selectively loading the
+ files โ€œgnorb-orgโ€, โ€œgnorb-gnusโ€ or โ€œgnorb-bbdbโ€ instead of plain old
+ โ€œgnorbโ€.  The package as a whole is rather Org-centric, though, and it
+ wonโ€™t do much of interest without โ€œgnorb-orgโ€.
+    This means that Gnorb doesnโ€™t have hard requirements to any of the
+ three base libraries.  For the libraries you are using, however, youโ€™ll
+ get best results from using the most recent stable version (yes, that
+ means BBDB 3).  Some of the features in Gnorb only work with development
+ versions of these libraries (those cases are noted below).
\1f
+ File: gnorb.info,  Node: Installation,  Next: Setup,  Prev: Introduction,  Up: Top
+ 2 Installation
+ **************
+ Gnorb is best installed via the Elpa package manager โ€“ look for it in
+ โ€˜list-packagesโ€™.
+    You can also clone the source code from
+ <https://github.com/girzel/gnorb>, and put the โ€œgnorbโ€ directory on your
+ load-path.  The Github site is also a good place to report bugs and
+ other issues.
\1f
+ File: gnorb.info,  Node: Setup,  Next: Email Tracking,  Prev: Installation,  Up: Top
+ 3 Setup
+ *******
+ Loading โ€œgnorbโ€ will make the basic functions available.  Using Gnorb
+ for email tracking takes a bit more setup, however:
+   1. Email tracking is done via the Gnus registry, so that must be
+      activated with โ€˜gnus-registry-initializeโ€™.
+   2. It also requires the org-id package to be loaded, and
+      โ€˜org-id-track-globallyโ€™ set to t (thatโ€™s the default value, so
+      simply loading the package should be enough).
+   3. Add a nngnorb entry to your โ€˜gnus-secondary-select-methodsโ€™
+      variable.  It will look like (nngnorb โ€œServer nameโ€).  This does
+      nothing but provide a place to hang nnir searches.
+   4. Then put a call to โ€˜gnorb-tracking-initializeโ€™ in your init files,
+      at some point after the Gnus registry is initialized.
+   5. If youโ€™re not using a local archive method for saving your sent
+      messages (ie youโ€™re using IMAP), youโ€™ll also need to tell Gnorb
+      where to find your sent messages.  Set the variable
+      โ€˜gnorb-gnus-sent-groupsโ€™ to a list of strings; each string should
+      indicate a fully-qualified group name, eg โ€œnnimap+SERVER:GROUPโ€.
+    Lastly, Gnorb doesnโ€™t bind any keys by default; see the *note
+ Suggested Keybindings: Suggested Keybindings. section below for
+ possibilities.
\1f
+ File: gnorb.info,  Node: Email Tracking,  Next: Restoring Window Layout,  Prev: Setup,  Up: Top
+ 4 Email Tracking
+ ****************
+ The most interesting thing Gnorb does is using Org headings to track
+ email conversations.  This can mean anything from reminding yourself to
+ write to your mother, to conducting delicate business negotiations over
+ email, to running an email-based bug tracker.
+    Gnorb assists in this process by using the Gnus registry to track
+ correspondences between emails and Org headings โ€“ specifically, message
+ IDs are associated with Org heading ids.  As a conversation develops,
+ messages are collected on a heading (and/or its children).  You can
+ compose new messages directly from the Org heading, and Gnorb will
+ automatically associate your sent message with the conversation.  You
+ can open temporary Gnus *Summary* buffers holding all the messages
+ associated with an Org subtree, and reply from there.  When you receive
+ new messages relevant to a conversation, Gnorb will notice them and
+ prompt you to associate them with the appropriate Org heading.
+ Attachments on incoming messages can be automatically saved as
+ attachments on Org headings, using org-attach.
+    In general, the goal is to keep track of whole conversations, reduce
+ friction when moving between Gnus and Org, and keep you in the Org
+ agenda rather than in Gnus.
+ * Menu:
+ * Email-Related Commands::
+ * Trigger Actions::
+ * Viewing Tracked Messages in *Summary* Buffers::
+ * Hinting in Gnus::
+ * Message Attachments::
\1f
+ File: gnorb.info,  Node: Email-Related Commands,  Next: Trigger Actions,  Up: Email Tracking
+ 4.1 Email-Related Commands
+ ==========================
+ Email tracking starts in one of three ways:
+   1. With an Org heading that represents an email TODO. Call
+      โ€˜gnorb-org-handle-mailโ€™ (see below) on the heading to compose a new
+      message, and start the tracking process.
+   2. By calling org-capture on a received message.  Any heading captured
+      from a message will automatically be associated with that message.
+   3. By calling โ€˜gnorb-gnus-outgoing-do-todoโ€™ in a message composition
+      buffer โ€“ see below.
+    There are three main email-related commands:
+   1. โ€˜gnorb-org-handle-mailโ€™ is called on an Org heading to compose a
+      new message.  By default, this will begin a reply to the most
+      recent message in the conversation.  If there are no associated
+      messages to reply to (or you call the function with a double prefix
+      arg), Gnorb will look for mailto: or bbdb: links in the heading,
+      and compose a new message to them.
+      The sent message will be associated with the Org heading, and
+      youโ€™ll be brought back to the heading and asked to trigger an
+      action on it.
+      โ€˜gnorb-email-subtreeโ€™ is an alternative entry-point to
+      โ€˜gnorb-org-handle-mailโ€™.  It does the same thing as the latter, but
+      first exports the body of the subtree as either text or a file,
+      then inserts the text into the message body, or attaches the file
+      to the message, depending on what youโ€™ve chosen.
+   2. โ€˜gnorb-gnus-incoming-do-todoโ€™ is called on a message in a Gnus
+      *Summary* buffer.  Youโ€™ll be prompted for an Org heading, taken to
+      that heading, and asked to trigger an action on it.
+   3. โ€˜gnorb-gnus-outgoing-do-todoโ€™ is called in message mode, while
+      composing a new message.
+      If called without a prefix arg, a new Org heading will be created
+      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.
+      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.
+    Because these three commands all express a similar intent, but are
+ called in different modes, it can make sense to give each of them the
+ same keybinding in the keymaps for Org mode, Gnus summary mode, and
+ Message mode, respectively.
\1f
+ File: gnorb.info,  Node: Trigger Actions,  Next: Viewing Tracked Messages in *Summary* Buffers,  Prev: Email-Related Commands,  Up: Email Tracking
+ 4.2 Trigger Actions
+ ===================
+ After calling โ€˜gnorb-gnus-incoming-do-todoโ€™ on a message, or after
+ sending a message associated with an Org heading, youโ€™ll be taken to the
+ heading and asked to โ€œtrigger an actionโ€ on it.  At the moment there are
+ four different possibilities: triggering a TODO state-change 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โ€™.
\1f
+ File: gnorb.info,  Node: Viewing Tracked Messages in *Summary* Buffers,  Next: Hinting in Gnus,  Prev: Trigger Actions,  Up: Email Tracking
+ 4.3 Viewing Tracked Messages in *Summary* Buffers
+ =================================================
+ Call โ€˜gnorb-org-viewโ€™ on an Org heading to open an nnir *Summary* buffer
+ showing all the messages associated with that heading (this requires
+ that youโ€™ve added an nngnorb server to your Gnus backends).  A minor
+ mode will be in effect, ensuring that any replies you send to messages
+ in this buffer will automatically be associated with the original Org
+ heading.  You can also invoke โ€˜gnorb-summary-disassociate-messageโ€™ (โ€œC-c
+ dโ€) to disassociate the message with the Org heading.
+    As a bonus, itโ€™s possible to go into Gnusโ€™ *Server* buffer, find the
+ line specifying your nngnorb server, and hit โ€œGโ€ (aka
+ โ€˜gnus-group-make-nnir-groupโ€™).  At the query prompt, enter an Org-style
+ tags-todo Agenda query string (eg โ€œ+work-computerโ€, or what have you).
+ Gnorb will find all headings matching this query, scan their subtrees
+ for gnus links, and then give you a Summary buffer containing all the
+ linked messages.  This is dog-slow at the moment; it will get faster.
\1f
+ File: gnorb.info,  Node: Hinting in Gnus,  Next: Message Attachments,  Prev: Viewing Tracked Messages in *Summary* Buffers,  Up: Email Tracking
+ 4.4 Hinting in Gnus
+ ===================
+ When you receive new mails that might be relevant to existing Org TODOs,
+ Gnorb can alert you to that fact.  When
+ โ€˜gnorb-gnus-hint-relevant-articleโ€™ is t (the default), Gnorb will
+ display a message in the minibuffer when opening potentially relevant
+ messages.  You can then use โ€˜gnorb-gnus-incoming-to-todoโ€™ to trigger an
+ action on the relevant TODO.
+    This hinting can happen in the Gnus summary buffer as well.  If you
+ use the escape indicated by โ€˜gnorb-gnus-summary-mark-format-letterโ€ as
+ part of your โ€˜gnus-summary-line-formatโ€™, articles that are relevant to
+ TODOs will be marked with a special character in the Summary buffer, as
+ determined by โ€˜gnorb-gnus-summary-markโ€™.  By default, the format letter
+ is โ€œgโ€ (meaning it is used as โ€œ%ugโ€ in the format line), and the mark is
+ โ€œยกโ€.
\1f
+ File: gnorb.info,  Node: Message Attachments,  Prev: Hinting in Gnus,  Up: Email Tracking
+ 4.5 Message Attachments
+ =======================
+ Gnorb simplifies the handling of attachments that you receive in emails.
+ When you call โ€˜gnorb-gnus-incoming-do-todoโ€™ on a message, youโ€™ll be
+ prompted to re-attach the emailโ€™s attachments onto the Org heading,
+ using the org-attach library.
+    You can also do this as part of the capture process.  Set the new
+ :gnus-attachments key to โ€œtโ€ in a capture template that you use on mail
+ messages, and youโ€™ll be queried to re-attach the messageโ€™s attachments
+ onto the newly-captured heading.  Or set
+ โ€˜gnorb-gnus-capture-always-attachโ€™ to โ€œtโ€ to have Gnorb do this for all
+ capture templates.
+    You can also do this using the regular system of MIME commands,
+ without invoking the email tracking process.  See *note Suggested
+ Keybindings: Suggested Keybindings, below.
+    The same process works in reverse: when you send a message from an
+ Org 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.
\1f
+ File: gnorb.info,  Node: Restoring Window Layout,  Next: Recent Mails From BBDB Contacts,  Prev: Email Tracking,  Up: Top
+ 5 Restoring Window Layout
+ *************************
+ Many Gnorb functions alter the window layout and value of point.  In
+ most of these cases, you can restore the previous layout using the
+ interactive function โ€˜gnorb-restore-layoutโ€™.
\1f
+ File: gnorb.info,  Node: Recent Mails From BBDB Contacts,  Next: BBDB posting styles,  Prev: Restoring Window Layout,  Up: Top
+ 6 Recent Mails From BBDB Contacts
+ *********************************
+ If youโ€™re using a recent git version of BBDB (circa mid-May 2014 or
+ later), you can give your BBDB contacts a special field which will
+ collect links to recent emails from that contact.  The default name of
+ the field is โ€œmessagesโ€, but you can customize that name using the
+ โ€˜gnorb-bbdb-messages-fieldโ€™ option.
+    Gnorb will not collect links by default: you need to call
+ โ€˜gnorb-bbdb-open-linkโ€™ on a contact once to start the process.
+ Thereafter, opening mails from that contact will store a link to the
+ message.
+    Once some links are stored, โ€˜gnorb-bbdb-open-linkโ€™ will open them:
+ Use a prefix arg to the function call to select particular messages to
+ open.  There are several options controlling how all this works; see the
+ gnorb-bbdb user options section below for details.
\1f
+ File: gnorb.info,  Node: BBDB posting styles,  Next: BBDB Org tagging,  Prev: Recent Mails From BBDB Contacts,  Up: Top
+ 7 BBDB posting styles
+ *********************
+ Gnorb comes with a BBDB posting-style system, inspired by (copied from)
+ gnus-posting-styles.  You can specify how messages are composed to
+ specific contacts, by matching on contact field values (the same way
+ gnus-posting-styles matches on group names).  See the docstring of
+ โ€˜gnorb-bbdb-posting-stylesโ€™ for details.
+    In order not to be too intrusive, Gnorb doesnโ€™t alter the behavior of
+ โ€˜bbdb-mailโ€™, the usual mail-composition function.  Instead it provides
+ an alternate โ€˜gnorb-bbdb-mailโ€™, which does exactly the same thing, but
+ first processes the new mail according to โ€˜gnorb-bbdb-posting-stylesโ€™.
+ If you want to use this feature regularly, you can remap โ€˜bbdb-mailโ€™ to
+ โ€˜gnorb-bbdb-mailโ€™ in the โ€˜bbdb-mode-mapโ€™.
\1f
+ File: gnorb.info,  Node: BBDB Org tagging,  Next: Misc BBDB,  Prev: BBDB posting styles,  Up: Top
+ 8 BBDB Org tagging
+ ******************
+ BBDB contacts can be tagged with the same tags you use in your Org
+ files.  This allows you to pop up a *BBDB* buffer alongside your Org
+ Agenda when searching for certain tags.  This can happen automatically
+ for all Org tags-todo searches, if you set the option
+ โ€˜gnorb-org-agenda-popup-bbdbโ€™ to t.  Or you can do it manually, by
+ calling the command of the same name.  This command only shows TODOs by
+ default: use a prefix argument to show all tagged headings.
+    Tags are stored in an xfield named org-tags, by default.  You can
+ customize the name of this field using โ€˜gnorb-bbdb-org-tag-fieldโ€™.
\1f
+ File: gnorb.info,  Node: Misc BBDB,  Next: Misc Org,  Prev: BBDB Org tagging,  Up: Top
+ 9 Misc BBDB
+ ***********
+ * Menu:
+ * Searching for messages from BBDB contacts::
+ * Citing BBDB contacts::
+ * User Options::
\1f
+ File: gnorb.info,  Node: Searching for messages from BBDB contacts,  Next: Citing BBDB contacts,  Up: Misc BBDB
+ 9.1 Searching for messages from BBDB contacts
+ =============================================
+ Call โ€˜gnorb-bbdb-mail-searchโ€™ to search for all mail messages from the
+ record(s) displayed.  Currently supports the notmuch, mairix, and namazu
+ search backends; set โ€˜gnorb-gnus-mail-search-backendโ€™ to one of those
+ symbol values.
\1f
+ File: gnorb.info,  Node: Citing BBDB contacts,  Next: User Options,  Prev: Searching for messages from BBDB contacts,  Up: Misc BBDB
+ 9.2 Citing BBDB contacts
+ ========================
+ Calling โ€˜gnorb-bbdb-cite-contactโ€™ will prompt for a BBDB record and
+ insert a string of the type โ€œBob Smith <bob@smith.com>โ€.
\1f
+ File: gnorb.info,  Node: User Options,  Prev: Citing BBDB contacts,  Up: Misc BBDB
+ 9.3 User Options
+ ================
+ โ€˜`gnorb-bbdb-org-tag-fieldโ€™
+      The name of the BBDB xfield, as a symbol, that holds Org-related
+      tags.  Specified as a string with the โ€œ:โ€ separator between tags,
+      same as for Org headings.  Defaults to org-tag.
+ โ€˜`gnorb-bbdb-messages-field'โ€™
+      The name of the BBDB xfield that holds links to recently-received
+      messages from this contact.  Defaults to โ€˜messages.
+ โ€˜`gnorb-bbdb-collect-N-messages'โ€™
+      Collect at most this many links to messages from this contact.
+      Defaults to 5.
+ โ€˜`gnorb-bbdb-define-recent'โ€™
+      What does โ€œrecently-receivedโ€ mean?  Possible values are the
+      symbols seen and received.  When set to seen, the most
+      recently-opened messages are collected.  When set to received, the
+      most recently-received (by Date header) messages are collected.
+      Defaults to seen.
+ โ€˜`gnorb-bbdb-message-link-format-multi'โ€™
+      How is a single messageโ€™s link formatted in the multi-line BBDB
+      layout format?  Defaults to โ€œ%:count.  %D: %:subjectโ€ (see the
+      docstring for details).
+ โ€˜` gnorb-bbdb-message-link-format-one'โ€™
+      How is a single messageโ€™s link formatted in the one-line BBDB
+      layout format?  Defaults to nil (see the docstring for details).
+ โ€˜`gnorb-bbdb-posting-styles'โ€™
+      Styles to use for influencing the format of mails composed to the
+      BBDB record(s) under point (see the docstring for details).
\1f
+ File: gnorb.info,  Node: Misc Org,  Next: Misc Gnus,  Prev: Misc BBDB,  Up: Top
+ 10 Misc Org
+ ***********
+ * Menu:
+ * Inserting BBDB links::
+ * User Options: User Optionsx. 
\1f
+ File: gnorb.info,  Node: Inserting BBDB links,  Next: User Optionsx,  Up: Misc Org
+ 10.1 Inserting BBDB links
+ =========================
+ Calling โ€˜gnorb-org-contact-linkโ€™ will prompt for a BBDB record and
+ insert an Org link to that record at point.
\1f
+ File: gnorb.info,  Node: User Optionsx,  Prev: Inserting BBDB links,  Up: Misc Org
+ 10.2 User Options
+ =================
+ โ€˜`gnorb-org-after-message-setup-hook'โ€™
+      Hook run in a message buffer after setting up the message, from
+      โ€˜gnorb-org-handle-mailโ€™ or โ€˜gnorb-org-email-subtreeโ€™.
+ โ€˜`gnorb-org-trigger-actions'โ€™
+      List of potential actions that can be taken on headings after a
+      message is sent.  See docstring for details.
+ โ€˜`gnorb-org-mail-scan-scope'โ€™
+      The number of paragraphs to scan for mail-related links.  This
+      comes into play when calling โ€˜gnorb-org-handle-mailโ€™ on a heading
+      with no associated messages, or when โ€˜gnorb-org-handle-mailโ€™ is
+      called with a prefix arg.
+ โ€˜`gnorb-org-find-candidates-match'โ€™
+      When searching all Org files for headings to collect messages from,
+      this option can limit which headings are searched.  It is used as
+      the second argument to a call to โ€˜org-map-entriesโ€™, and has the
+      same syntax as that used in an agenda tags view.
+ โ€˜`gnorb-org-email-subtree-text-parameters'โ€™
+      A plist of export parameters corresponding to the EXT-PLIST
+      argument to the export functions, for use when exporting to text.
+ โ€˜`gnorb-org-email-subtree-file-parameters'โ€™
+      A plist of export parameters corresponding to the EXT-PLIST
+      argument to the export functions, for use when exporting to a file.
+ โ€˜`gnorb-org-email-subtree-text-options'โ€™
+      A list of ts and nils corresponding to Orgโ€™s export options, to be
+      used when exporting to text.  The options, in order, are async,
+      subtreep, visible-only, and body-only.
+ โ€˜`gnorb-org-email-subtree-file-options'โ€™
+      A list of ts and nils corresponding to Orgโ€™s export options, to be
+      used when exporting to a file.  The options, in order, are async,
+      subtreep, visible-only, and body-only.
+ โ€˜`gnorb-org-export-extensions'โ€™
+      Correspondence between export backends and their respective (usual)
+      file extensions.
+ โ€˜`gnorb-org-capture-collect-link-p'โ€™
+      When this is set to t, the capture process will always store a link
+      to the Gnus message or BBDB record under point, even when the link
+      isnโ€™t part of the capture template.  It can then be added to the
+      captured heading with org-insert-link, as usual.
+ โ€˜`gnorb-org-agenda-popup-bbdb'โ€™
+      Set to โ€œtโ€ to automatically pop up the BBDB buffer displaying
+      records corresponding to the Org Agenda tags search underway.  If
+      this is nil you can always do it manually with the command of the
+      same name.
+ โ€˜`gnorb-org-bbdb-popup-layout'โ€™
+      Controls the layout of the Agenda-related BBDB popup, takes the
+      same values as bbdb-pop-up-layout.
\1f
+ File: gnorb.info,  Node: Misc Gnus,  Next: Suggested Keybindings,  Prev: Misc Org,  Up: Top
+ 11 Misc Gnus
+ ************
+ * Menu:
+ * Viewing Org headlines relevant to a message::
+ * User Options: User Optionsxx. 
\1f
+ File: gnorb.info,  Node: Viewing Org headlines relevant to a message,  Next: User Optionsxx,  Up: Misc Gnus
+ 11.1 Viewing Org headlines relevant to a message
+ ================================================
+ Call โ€˜gnorb-gnus-viewโ€™ on a message that is associated with an Org
+ heading to jump to that heading.
\1f
+ File: gnorb.info,  Node: User Optionsxx,  Prev: Viewing Org headlines relevant to a message,  Up: Misc Gnus
+ 11.2 User Options
+ =================
+ โ€˜`gnorb-gnus-mail-search-backend'โ€™
+      Specifies the search backend that you use for searching mails.
+      Currently supports notmuch, mairix, and namazu: set this option to
+      one of those symbols.
+ โ€˜`gnorb-gnus-capture-always-attach'โ€™
+      Treat all capture templates as if they had the :gnus-attachments
+      key set to โ€œtโ€.  This only has any effect if youโ€™re capturing from
+      a Gnus summary or article buffer.
+ โ€˜`gnorb-trigger-todo-default'โ€™
+      Set to either โ€˜note or โ€˜todo to tell โ€˜gnorb-gnus-incoming-do-todoโ€™
+      what to do by default.  You can reach the non-default behavior by
+      calling that function with a prefix argument.  Alternately, set to
+      โ€˜prompt to always prompt for the appropriate action.
+ โ€˜`gnorb-gnus-trigger-refile-targets'โ€™
+      If you use โ€˜gnorb-gnus-incoming-do-todoโ€™ on an incoming message,
+      Gnorb will try to locate a TODO heading thatโ€™s relevant to that
+      message.  If it canโ€™t, it will prompt you for one, using the refile
+      interface.  This option will be used as the value of
+      โ€˜org-refile-targetsโ€™ during that process: see the docstring of
+      โ€˜org-refile-targetsโ€™ for the appropriate syntax.
+ โ€˜`gnorb-gnus-new-todo-capture-key'โ€™
+      Set this to a single-character string pointing at an Org capture
+      template to use when creating TODOs from outgoing messages.  The
+      template is a regular capture template, with a few exceptions.  If
+      Gnus helps you archive outgoing messages (ie you have
+      โ€˜gnus-message-archive-groupโ€™ set to something, and your outgoing
+      messages have a โ€œFccโ€ header), a link to that message will be made,
+      and youโ€™ll be able to use all the escapes related to gnus messages.
+      If you donโ€™t archive outgoing messages, youโ€™ll still be able to use
+      the %:subject, %:to, %:toname, %:toaddress, and %:date escapes in
+      the capture template.
+ โ€˜`gnorb-gnus-hint-relevant-article'โ€™
+      Set to โ€œtโ€ (the default) to have Gnorb give you a hint in the
+      minibuffer when opening messages that might be relevant to existing
+      Org TODOs.
+ โ€˜`gnorb-gnus-summary-mark-format-letter'โ€™
+      The formatting letter to use as part of your
+      โ€˜gnus-summary-line-formatโ€™, to indicate messages which might be
+      relevant to Org TODOs.  Defaults to โ€œgโ€, meaning it should be used
+      as โ€œ%ugโ€ in the format line.
+ โ€˜`gnorb-gnus-summary-mark'โ€™
+      The mark used to indicate relevant messages in the Summary buffer,
+      when โ€˜gnorb-gnus-summary-mark-format-letterโ€™ is present in the
+      format line.  Defaults to โ€œยกโ€.
\1f
+ File: gnorb.info,  Node: Suggested Keybindings,  Next: Wishlist/TODO,  Prev: Misc Gnus,  Up: Top
+ 12 Suggested Keybindings
+ ************************
+      (eval-after-load "gnorb-bbdb"
+        '(progn
+           (define-key bbdb-mode-map (kbd "O") 'gnorb-bbdb-tag-agenda)
+           (define-key bbdb-mode-map (kbd "S") 'gnorb-bbdb-mail-search)
+           (define-key bbdb-mode-map [remap bbdb-mail] 'gnorb-bbdb-mail)
+           (define-key bbdb-mode-map (kbd "l") 'gnorb-bbdb-open-link)
+           (global-set-key (kbd "C-c C") 'gnorb-bbdb-cite-contact)))
+      (eval-after-load "gnorb-org"
+        '(progn
+           (org-defkey org-mode-map (kbd "C-c C") 'gnorb-org-contact-link)
+           (org-defkey org-mode-map (kbd "C-c t") 'gnorb-org-handle-mail)
+           (org-defkey org-mode-map (kbd "C-c e") 'gnorb-org-view)
+           (org-defkey org-mode-map (kbd "C-c E") 'gnorb-org-email-subtree)
+           (org-defkey org-mode-map (kbd "C-c V") 'gnorb-org-popup-bbdb)
+           (setq gnorb-org-agenda-popup-bbdb t)
+           (eval-after-load "org-agenda"
+             '(progn (org-defkey org-agenda-mode-map (kbd "H") 'gnorb-org-handle-mail)
+                     (org-defkey org-agenda-mode-map (kbd "V") 'gnorb-org-popup-bbdb)))))
+      (eval-after-load "gnorb-gnus"
+        '(progn
+           (define-key gnus-summary-mime-map "a" 'gnorb-gnus-article-org-attach)
+           (define-key gnus-summary-mode-map (kbd "C-c t") 'gnorb-gnus-incoming-do-todo)
+           (push '("attach to org heading" . gnorb-gnus-mime-org-attach)
+                 gnus-mime-action-alist)
+           ;; The only way to add mime button command keys is by redefining
+           ;; gnus-mime-button-map, possibly not ideal. Ideal would be a
+           ;; setter function in gnus itself.
+           (push '(gnorb-gnus-mime-org-attach "a" "Attach to Org heading")
+                 gnus-mime-button-commands)
+           (setq gnus-mime-button-map
+                 (let ((map (make-sparse-keymap)))
+                   (define-key map gnus-mouse-2 'gnus-article-push-button)
+                   (define-key map gnus-down-mouse-3 'gnus-mime-button-menu)
+                   (dolist (c gnus-mime-button-commands)
+                     (define-key map (cadr c) (car c)))
+                   map))))
+      (eval-after-load "message"
+        '(progn
+           (define-key message-mode-map (kbd "C-c t") 'gnorb-gnus-outgoing-do-todo)))
\1f
+ 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.
\1f
+ File: gnorb.info,  Node: Index,  Prev: Wishlist/TODO,  Up: Top
+ 14 Index
+ ********
\1f
+ Tag Table:
+ Node: Top\7f194
+ Node: Introduction\7f1017
+ Node: Installation\7f2126
+ Node: Setup\7f2540
+ Node: Email Tracking\7f3907
+ Node: Email-Related Commands\7f5418
+ Node: Trigger Actions\7f8239
+ Node: Viewing Tracked Messages in *Summary* Buffers\7f9053
+ Node: Hinting in Gnus\7f10287
+ Node: Message Attachments\7f11295
+ Node: Restoring Window Layout\7f12453
+ Node: Recent Mails From BBDB Contacts\7f12817
+ Node: BBDB posting styles\7f13813
+ Node: BBDB Org tagging\7f14729
+ Node: Misc BBDB\7f15475
+ Node: Searching for messages from BBDB contacts\7f15688
+ Node: Citing BBDB contacts\7f16134
+ Node: User Options\7f16455
+ Node: Misc Org\7f17994
+ Node: Inserting BBDB links\7f18169
+ Node: User Optionsx\7f18424
+ Node: Misc Gnus\7f21161
+ Node: Viewing Org headlines relevant to a message\7f21374
+ Node: User Optionsxx\7f21689
+ Node: Suggested Keybindings\7f24453
+ Node: Wishlist/TODO\7f26824
+ Node: Index\7f28139
\1f
+ End Tag Table
\1f
+ Local Variables:
+ coding: utf-8
+ End:
diff --combined packages/gnorb/gnorb.org
index 0000000000000000000000000000000000000000,b03098b20f09bedb828f054c7eebb4d26a7512f4..b03098b20f09bedb828f054c7eebb4d26a7512f4
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,507 +1,507 @@@
+ #+TEXINFO_CLASS: info
+ #+TEXINFO_HEADER: @syncodeindex pg cp
+ #+TITLE: Gnorb Manual
+ #+SUBTITLE: for version 1, updated 3 October, 2014
+ #+TEXINFO_DIR_CATEGORY: Emacs
+ #+TEXINFO_DIR_TITLE: Gnorb: (gnorb)
+ #+TEXINFO_DIR_DESC: Glue code for Gnus, Org, and BBDB
+ #+OPTIONS: *:nil num:t toc:nil
+ * Introduction
+ Gnorb provides glue code between the Gnus, Org, and BBDB packages.
+ It's aimed at supporting email-based project management, and generally
+ making it easier to keep track of email communication.
+ Much of the code consists of single-use convenience functions, but
+ tracking email conversations with Org requires is more complicated,
+ and requires a bit of setup.
+ Gnorb can be used in a modular fashion, by selectively loading the
+ files "gnorb-org", "gnorb-gnus" or "gnorb-bbdb" instead of plain old
+ "gnorb". The package as a whole is rather Org-centric, though, and it
+ won't do much of interest without "gnorb-org".
+ This means that Gnorb doesn't have hard requirements to any of the
+ three base libraries. For the libraries you are using, however, you'll
+ get best results from using the most recent stable version (yes, that
+ means BBDB 3). Some of the features in Gnorb only work with
+ development versions of these libraries (those cases are noted below).
+ * Installation
+ Gnorb is best installed via the Elpa package manager -- look for it in
+ `list-packages'.
+ You can also clone the source code from
+ https://github.com/girzel/gnorb, and put the "gnorb" directory on your
+ load-path. The Github site is also a good place to report bugs and
+ other issues.
+ * Setup
+ Loading "gnorb" will make the basic functions available. Using Gnorb
+ for email tracking takes a bit more setup, however:
+ 1. Email tracking is done via the Gnus registry, so that must be
+    activated with 'gnus-registry-initialize'.
+ 2. It also requires the org-id package to be loaded, and
+    `org-id-track-globally' set to t (that's the default value, so
+    simply loading the package should be enough).
+ 3. Add a nngnorb entry to your `gnus-secondary-select-methods'
+    variable. It will look like (nngnorb "Server name"). This does
+    nothing but provide a place to hang nnir searches.
+ 4. Then put a call to `gnorb-tracking-initialize' in your init files,
+    at some point after the Gnus registry is initialized.
+ 5. If you're not using a local archive method for saving your sent
+    messages (ie you're using IMAP), you'll also need to tell Gnorb
+    where to find your sent messages. Set the variable
+    `gnorb-gnus-sent-groups' to a list of strings; each string should
+    indicate a fully-qualified group name, eg "nnimap+SERVER:GROUP".
+ Lastly, Gnorb doesn't bind any keys by default; see the [[id:de1b2579-86c2-4bb1-b77e-3467a3d2b3c7][Suggested
+ Keybindings]] section below for possibilities.
+ * Email Tracking
+ The most interesting thing Gnorb does is using Org headings to track
+ email conversations. This can mean anything from reminding yourself to
+ write to your mother, to conducting delicate business negotiations
+ over email, to running an email-based bug tracker.
+ Gnorb assists in this process by using the Gnus registry to track
+ correspondences between emails and Org headings -- specifically,
+ message IDs are associated with Org heading ids. As a conversation
+ develops, messages are collected on a heading (and/or its children).
+ You can compose new messages directly from the Org heading, and Gnorb
+ will automatically associate your sent message with the conversation.
+ You can open temporary Gnus *Summary* buffers holding all the messages
+ associated with an Org subtree, and reply from there. When you receive
+ new messages relevant to a conversation, Gnorb will notice them and
+ prompt you to associate them with the appropriate Org heading.
+ Attachments on incoming messages can be automatically saved as
+ attachments on Org headings, using org-attach.
+ In general, the goal is to keep track of whole conversations, reduce
+ friction when moving between Gnus and Org, and keep you in the Org
+ agenda rather than in Gnus.
+ ** Email-Related Commands
+ Email tracking starts in one of three ways:
+ 1. With an Org heading that represents an email TODO. Call
+    `gnorb-org-handle-mail' (see below) on the heading to compose a new
+    message, and start the tracking process.
+ 2. By calling org-capture on a received message. Any heading captured
+    from a message will automatically be associated with that message.
+ 3. By calling `gnorb-gnus-outgoing-do-todo' in a message composition
+    buffer -- see below.
+ There are three main email-related commands:
+ 1. `gnorb-org-handle-mail' is called on an Org heading to compose a
+    new message. By default, this will begin a reply to the most recent
+    message in the conversation. If there are no associated messages to
+    reply to (or you call the function with a double prefix arg), Gnorb
+    will look for mailto: or bbdb: links in the heading, and compose a
+    new message to them.
+    
+    The sent message will be associated with the Org heading, and
+    you'll be brought back to the heading and asked to trigger an
+    action on it.
+    
+    `gnorb-email-subtree' is an alternative entry-point to
+    `gnorb-org-handle-mail'. It does the same thing as the latter, but
+    first exports the body of the subtree as either text or a file,
+    then inserts the text into the message body, or attaches the file
+    to the message, depending on what you've chosen.
+ 2. `gnorb-gnus-incoming-do-todo' is called on a message in a Gnus
+    *Summary* buffer. You'll be prompted for an Org heading, taken to
+    that heading, and asked to trigger an action on it.
+ 3. `gnorb-gnus-outgoing-do-todo' is called in message mode, while
+    composing a new message. 
+     
+    If called without a prefix arg, a new Org heading will be created
+    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.
+    
+    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.
+ Because these three commands all express a similar intent, but are
+ called in different modes, it can make sense to give each of them the
+ same keybinding in the keymaps for Org mode, Gnus summary mode, and
+ Message mode, respectively.
+ ** Trigger Actions
+ After calling `gnorb-gnus-incoming-do-todo' on a message, or after
+ sending a message associated with an Org heading, you'll be taken to
+ the heading and asked to "trigger an action" on it. At the moment
+ there are four different possibilities: triggering a TODO state-change
+ 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
+ rearrange or delete existing actions, and add your own: see the
+ docstring of `gnorb-org-trigger-actions'.
+ ** Viewing Tracked Messages in *Summary* Buffers
+ :PROPERTIES:
+ :END:
+ Call `gnorb-org-view' on an Org heading to open an nnir *Summary*
+ buffer showing all the messages associated with that heading (this
+ requires that you've added an nngnorb server to your Gnus backends). A
+ minor mode will be in effect, ensuring that any replies you send to
+ messages in this buffer will automatically be associated with the
+ original Org heading. You can also invoke
+ `gnorb-summary-disassociate-message' ("C-c d") to disassociate the
+ message with the Org heading.
+ As a bonus, it's possible to go into Gnus' *Server* buffer, find the
+ line specifying your nngnorb server, and hit "G" (aka
+ `gnus-group-make-nnir-group'). At the query prompt, enter an Org-style
+ tags-todo Agenda query string (eg "+work-computer", or what have you).
+ Gnorb will find all headings matching this query, scan their subtrees
+ for gnus links, and then give you a Summary buffer containing all the
+ linked messages. This is dog-slow at the moment; it will get faster.
+ ** Hinting in Gnus
+ :PROPERTIES:
+ :END:
+ When you receive new mails that might be relevant to existing Org
+ TODOs, Gnorb can alert you to that fact. When
+ `gnorb-gnus-hint-relevant-article' is t (the default), Gnorb will
+ display a message in the minibuffer when opening potentially relevant
+ messages. You can then use `gnorb-gnus-incoming-to-todo' to trigger an
+ action on the relevant TODO.
+ This hinting can happen in the Gnus summary buffer as well. If you use
+ the escape indicated by `gnorb-gnus-summary-mark-format-letter" as
+ part of your `gnus-summary-line-format', articles that are relevant to
+ TODOs will be marked with a special character in the Summary buffer,
+ as determined by `gnorb-gnus-summary-mark'. By default, the format
+ letter is "g" (meaning it is used as "%ug" in the format line), and
+ the mark is "ยก".
+ ** Message Attachments
+ :PROPERTIES:
+ :END:
+ Gnorb simplifies the handling of attachments that you receive in
+ emails. When you call `gnorb-gnus-incoming-do-todo' on a message,
+ you'll be prompted to re-attach the email's attachments onto the Org
+ heading, using the org-attach library.
+ You can also do this as part of the capture process. Set the
+ new :gnus-attachments key to "t" in a capture template that you use on
+ mail messages, and you'll be queried to re-attach the message's
+ attachments onto the newly-captured heading. Or set
+ `gnorb-gnus-capture-always-attach' to "t" to have Gnorb do this for
+ all capture templates.
+ You can also do this using the regular system of MIME commands,
+ without invoking the email tracking process. See [[id:de1b2579-86c2-4bb1-b77e-3467a3d2b3c7][Suggested
+ Keybindings]], below.
+ The same process works in reverse: when you send a message from an Org
+ 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.
+ ** 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.
+ * Restoring Window Layout
+ Many Gnorb functions alter the window layout and value of point. In
+ most of these cases, you can restore the previous layout using the
+ interactive function `gnorb-restore-layout'.
+ * Recent Mails From BBDB Contacts
+ :PROPERTIES:
+ :END:
+ If you're using a recent git version of BBDB (circa mid-May 2014 or
+ later), you can give your BBDB contacts a special field which will
+ collect links to recent emails from that contact. The default name of
+ the field is "messages", but you can customize that name using the
+ `gnorb-bbdb-messages-field' option.
+ Gnorb will not collect links by default: you need to call
+ `gnorb-bbdb-open-link' on a contact once to start the process.
+ Thereafter, opening mails from that contact will store a link to the
+ message.
+ Once some links are stored, `gnorb-bbdb-open-link' will open them: Use
+ a prefix arg to the function call to select particular messages to
+ open. There are several options controlling how all this works; see
+ the gnorb-bbdb user options section below for details.
+ * BBDB posting styles
+ :PROPERTIES:
+ :END:
+ Gnorb comes with a BBDB posting-style system, inspired by (copied
+ from) gnus-posting-styles. You can specify how messages are composed
+ to specific contacts, by matching on contact field values (the same
+ way gnus-posting-styles matches on group names). See the docstring of
+ `gnorb-bbdb-posting-styles' for details.
+ In order not to be too intrusive, Gnorb doesn't alter the behavior of
+ `bbdb-mail', the usual mail-composition function. Instead it provides
+ an alternate `gnorb-bbdb-mail', which does exactly the same thing, but
+ first processes the new mail according to `gnorb-bbdb-posting-styles'.
+ If you want to use this feature regularly, you can remap `bbdb-mail'
+ to `gnorb-bbdb-mail' in the `bbdb-mode-map'.
+ * BBDB Org tagging
+ BBDB contacts can be tagged with the same tags you use in your Org
+ files. This allows you to pop up a *BBDB* buffer alongside your Org
+ Agenda when searching for certain tags. This can happen automatically
+ for all Org tags-todo searches, if you set the option
+ `gnorb-org-agenda-popup-bbdb' to t. Or you can do it manually, by
+ calling the command of the same name. This command only shows TODOs by
+ default: use a prefix argument to show all tagged headings.
+ Tags are stored in an xfield named org-tags, by default. You can
+ customize the name of this field using `gnorb-bbdb-org-tag-field'.
+ * Misc BBDB
+ ** Searching for messages from BBDB contacts
+ :PROPERTIES:
+ :END:
+ Call `gnorb-bbdb-mail-search' to search for all mail messages from the
+ record(s) displayed. Currently supports the notmuch, mairix, and
+ namazu search backends; set `gnorb-gnus-mail-search-backend' to one of
+ those symbol values.
+ ** Citing BBDB contacts
+ :PROPERTIES:
+ :END:
+ Calling `gnorb-bbdb-cite-contact' will prompt for a BBDB record and
+ insert a string of the type "Bob Smith <bob@smith.com>".
+ ** User Options
+ - `gnorb-bbdb-org-tag-field :: The name of the BBDB xfield, as a
+      symbol, that holds Org-related tags. Specified as a string with
+      the ":" separator between tags, same as for Org headings.
+      Defaults to org-tag.
+ - `gnorb-bbdb-messages-field' :: The name of the BBDB xfield that
+      holds links to recently-received messages from this contact.
+      Defaults to 'messages.
+ - `gnorb-bbdb-collect-N-messages' :: Collect at most this many links
+      to messages from this contact. Defaults to 5.
+ - `gnorb-bbdb-define-recent' :: What does "recently-received" mean?
+      Possible values are the symbols seen and received. When set to
+      seen, the most recently-opened messages are collected. When set
+      to received, the most recently-received (by Date header) messages
+      are collected. Defaults to seen.
+ - `gnorb-bbdb-message-link-format-multi' :: How is a single message's
+      link formatted in the multi-line BBDB layout format? Defaults to
+      "%:count. %D: %:subject" (see the docstring for details).
+ - ` gnorb-bbdb-message-link-format-one' :: How is a single message's
+      link formatted in the one-line BBDB layout format? Defaults to
+      nil (see the docstring for details).
+ - `gnorb-bbdb-posting-styles' :: Styles to use for influencing the
+      format of mails composed to the BBDB record(s) under point (see
+      the docstring for details).
+ * Misc Org
+ ** Inserting BBDB links
+ :PROPERTIES:
+ :END:
+ Calling `gnorb-org-contact-link' will prompt for a BBDB record and
+ insert an Org link to that record at point.
+ ** User Options
+ - `gnorb-org-after-message-setup-hook' :: Hook run in a message buffer
+      after setting up the message, from `gnorb-org-handle-mail' or
+      `gnorb-org-email-subtree'.
+ - `gnorb-org-trigger-actions' :: List of potential actions that can be
+      taken on headings after a message is sent. See docstring for
+      details.
+ - `gnorb-org-mail-scan-scope' :: The number of paragraphs to scan for
+      mail-related links. This comes into play when calling
+      `gnorb-org-handle-mail' on a heading with no associated messages,
+      or when `gnorb-org-handle-mail' is called with a prefix arg.
+ - `gnorb-org-find-candidates-match' :: When searching all Org files
+      for headings to collect messages from, this option can limit
+      which headings are searched. It is used as the second argument to
+      a call to `org-map-entries', and has the same syntax as that used
+      in an agenda tags view.
+ - `gnorb-org-email-subtree-text-parameters' :: A plist of export
+      parameters corresponding to the EXT-PLIST argument to the export
+      functions, for use when exporting to text.
+ - `gnorb-org-email-subtree-file-parameters' :: A plist of export
+      parameters corresponding to the EXT-PLIST argument to the export
+      functions, for use when exporting to a file.
+ - `gnorb-org-email-subtree-text-options' :: A list of ts and nils
+      corresponding to Org's export options, to be used when exporting
+      to text. The options, in order, are async, subtreep,
+      visible-only, and body-only.
+ - `gnorb-org-email-subtree-file-options' :: A list of ts and nils
+      corresponding to Org's export options, to be used when exporting
+      to a file. The options, in order, are async, subtreep,
+      visible-only, and body-only.
+ - `gnorb-org-export-extensions' :: Correspondence between export
+      backends and their respective (usual) file extensions.
+ - `gnorb-org-capture-collect-link-p' :: When this is set to t, the
+      capture process will always store a link to the Gnus message or
+      BBDB record under point, even when the link isn't part of the
+      capture template. It can then be added to the captured heading
+      with org-insert-link, as usual.
+ - `gnorb-org-agenda-popup-bbdb' :: Set to "t" to automatically pop up
+      the BBDB buffer displaying records corresponding to the Org
+      Agenda tags search underway. If this is nil you can always do it
+      manually with the command of the same name.
+ - `gnorb-org-bbdb-popup-layout' :: Controls the layout of the
+      Agenda-related BBDB popup, takes the same values as
+      bbdb-pop-up-layout.
+ * Misc Gnus
+ ** Viewing Org headlines relevant to a message
+ :PROPERTIES:
+ :END:
+ Call `gnorb-gnus-view' on a message that is associated with an Org
+ heading to jump to that heading.
+ ** User Options
+ - `gnorb-gnus-mail-search-backend' :: Specifies the search backend
+      that you use for searching mails. Currently supports notmuch,
+      mairix, and namazu: set this option to one of those symbols.
+ - `gnorb-gnus-capture-always-attach' :: Treat all capture templates as
+      if they had the :gnus-attachments key set to "t". This only has
+      any effect if you're capturing from a Gnus summary or article
+      buffer.
+ - `gnorb-trigger-todo-default' :: Set to either 'note or 'todo to tell
+      `gnorb-gnus-incoming-do-todo' what to do by default. You can
+      reach the non-default behavior by calling that function with a
+      prefix argument. Alternately, set to 'prompt to always prompt for
+      the appropriate action.
+ - `gnorb-gnus-trigger-refile-targets' :: If you use
+      `gnorb-gnus-incoming-do-todo' on an incoming message, Gnorb will
+      try to locate a TODO heading that's relevant to that message. If
+      it can't, it will prompt you for one, using the refile interface.
+      This option will be used as the value of `org-refile-targets'
+      during that process: see the docstring of `org-refile-targets'
+      for the appropriate syntax.
+ - `gnorb-gnus-new-todo-capture-key' :: Set this to a single-character
+      string pointing at an Org capture template to use when creating
+      TODOs from outgoing messages. The template is a regular capture
+      template, with a few exceptions. If Gnus helps you archive
+      outgoing messages (ie you have `gnus-message-archive-group' set
+      to something, and your outgoing messages have a "Fcc" header), a
+      link to that message will be made, and you'll be able to use all
+      the escapes related to gnus messages. If you don't archive
+      outgoing messages, you'll still be able to use the %:subject,
+      %:to, %:toname, %:toaddress, and %:date escapes in the capture
+      template.
+ - `gnorb-gnus-hint-relevant-article' :: Set to "t" (the default) to
+      have Gnorb give you a hint in the minibuffer when opening
+      messages that might be relevant to existing Org TODOs.
+ - `gnorb-gnus-summary-mark-format-letter' :: The formatting letter to
+      use as part of your `gnus-summary-line-format', to indicate
+      messages which might be relevant to Org TODOs. Defaults to "g",
+      meaning it should be used as "%ug" in the format line.
+ - `gnorb-gnus-summary-mark' :: The mark used to indicate relevant
+      messages in the Summary buffer, when
+      `gnorb-gnus-summary-mark-format-letter' is present in the format
+      line. Defaults to "ยก".
+ * Suggested Keybindings
+ :PROPERTIES:
+ :ID:       de1b2579-86c2-4bb1-b77e-3467a3d2b3c7
+ :END:
+ #+BEGIN_SRC emacs-lisp
+   (eval-after-load "gnorb-bbdb"
+     '(progn
+        (define-key bbdb-mode-map (kbd "O") 'gnorb-bbdb-tag-agenda)
+        (define-key bbdb-mode-map (kbd "S") 'gnorb-bbdb-mail-search)
+        (define-key bbdb-mode-map [remap bbdb-mail] 'gnorb-bbdb-mail)
+        (define-key bbdb-mode-map (kbd "l") 'gnorb-bbdb-open-link)
+        (global-set-key (kbd "C-c C") 'gnorb-bbdb-cite-contact)))
+   (eval-after-load "gnorb-org"
+     '(progn
+        (org-defkey org-mode-map (kbd "C-c C") 'gnorb-org-contact-link)
+        (org-defkey org-mode-map (kbd "C-c t") 'gnorb-org-handle-mail)
+        (org-defkey org-mode-map (kbd "C-c e") 'gnorb-org-view)
+        (org-defkey org-mode-map (kbd "C-c E") 'gnorb-org-email-subtree)
+        (org-defkey org-mode-map (kbd "C-c V") 'gnorb-org-popup-bbdb)
+        (setq gnorb-org-agenda-popup-bbdb t)
+        (eval-after-load "org-agenda"
+          '(progn (org-defkey org-agenda-mode-map (kbd "H") 'gnorb-org-handle-mail)
+                  (org-defkey org-agenda-mode-map (kbd "V") 'gnorb-org-popup-bbdb)))))
+   (eval-after-load "gnorb-gnus"
+     '(progn
+        (define-key gnus-summary-mime-map "a" 'gnorb-gnus-article-org-attach)
+        (define-key gnus-summary-mode-map (kbd "C-c t") 'gnorb-gnus-incoming-do-todo)
+        (push '("attach to org heading" . gnorb-gnus-mime-org-attach)
+              gnus-mime-action-alist)
+        ;; The only way to add mime button command keys is by redefining
+        ;; gnus-mime-button-map, possibly not ideal. Ideal would be a
+        ;; setter function in gnus itself.
+        (push '(gnorb-gnus-mime-org-attach "a" "Attach to Org heading")
+              gnus-mime-button-commands)
+        (setq gnus-mime-button-map
+              (let ((map (make-sparse-keymap)))
+                (define-key map gnus-mouse-2 'gnus-article-push-button)
+                (define-key map gnus-down-mouse-3 'gnus-mime-button-menu)
+                (dolist (c gnus-mime-button-commands)
+                  (define-key map (cadr c) (car c)))
+                map))))
+   (eval-after-load "message"
+     '(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.
index 0000000000000000000000000000000000000000,643552fbb62ba81eb85370893266f923d4e56652..643552fbb62ba81eb85370893266f923d4e56652
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,654 +1,654 @@@
+ \input texinfo    @c -*- texinfo -*-
+ @c %**start of header
+ @setfilename ./gnorb.info
+ @settitle Gnorb Manual
+ @documentencoding UTF-8
+ @documentlanguage en
+ @syncodeindex pg cp
+ @c %**end of header
+ @dircategory Emacs
+ @direntry
+ * Gnorb: (gnorb).       Glue code for Gnus, Org, and BBDB.
+ @end direntry
+ @finalout
+ @titlepage
+ @title Gnorb Manual
+ @subtitle for version 1, updated 3 October, 2014
+ @end titlepage
+ @ifnottex
+ @node Top
+ @top Gnorb Manual
+ @end ifnottex
+ @menu
+ * Introduction::
+ * Installation::
+ * Setup::
+ * Email Tracking::
+ * Restoring Window Layout::
+ * Recent Mails From BBDB Contacts::
+ * BBDB posting styles::
+ * BBDB Org tagging::
+ * Misc BBDB::
+ * Misc Org::
+ * Misc Gnus::
+ * Suggested Keybindings::
+ * Wishlist/TODO::
+ * Index::
+ @detailmenu
+ --- The Detailed Node Listing ---
+ Email Tracking
+ * Email-Related Commands::
+ * Trigger Actions::
+ * Viewing Tracked Messages in *Summary* Buffers::
+ * Hinting in Gnus::
+ * Message Attachments::
+ Misc BBDB
+ * Searching for messages from BBDB contacts::
+ * Citing BBDB contacts::
+ * User Options::
+ Misc Org
+ * Inserting BBDB links::
+ * User Options: User Optionsx. 
+ Misc Gnus
+ * Viewing Org headlines relevant to a message::
+ * User Options: User Optionsxx. 
+ @end detailmenu
+ @end menu
+ @node Introduction
+ @chapter Introduction
+ Gnorb provides glue code between the Gnus, Org, and BBDB packages.
+ It's aimed at supporting email-based project management, and generally
+ making it easier to keep track of email communication.
+ Much of the code consists of single-use convenience functions, but
+ tracking email conversations with Org requires is more complicated,
+ and requires a bit of setup.
+ Gnorb can be used in a modular fashion, by selectively loading the
+ files ``gnorb-org'', ``gnorb-gnus'' or ``gnorb-bbdb'' instead of plain old
+ ``gnorb''. The package as a whole is rather Org-centric, though, and it
+ won't do much of interest without ``gnorb-org''.
+ This means that Gnorb doesn't have hard requirements to any of the
+ three base libraries. For the libraries you are using, however, you'll
+ get best results from using the most recent stable version (yes, that
+ means BBDB 3). Some of the features in Gnorb only work with
+ development versions of these libraries (those cases are noted below).
+ @node Installation
+ @chapter Installation
+ Gnorb is best installed via the Elpa package manager -- look for it in
+ `list-packages'.
+ You can also clone the source code from
+ @uref{https://github.com/girzel/gnorb}, and put the ``gnorb'' directory on your
+ load-path. The Github site is also a good place to report bugs and
+ other issues.
+ @node Setup
+ @chapter Setup
+ Loading ``gnorb'' will make the basic functions available. Using Gnorb
+ for email tracking takes a bit more setup, however:
+ @enumerate
+ @item
+ Email tracking is done via the Gnus registry, so that must be
+ activated with `gnus-registry-initialize'.
+ @item
+ It also requires the org-id package to be loaded, and
+ `org-id-track-globally' set to t (that's the default value, so
+ simply loading the package should be enough).
+ @item
+ Add a nngnorb entry to your `gnus-secondary-select-methods'
+ variable. It will look like (nngnorb ``Server name''). This does
+ nothing but provide a place to hang nnir searches.
+ @item
+ Then put a call to `gnorb-tracking-initialize' in your init files,
+ at some point after the Gnus registry is initialized.
+ @item
+ If you're not using a local archive method for saving your sent
+ messages (ie you're using IMAP), you'll also need to tell Gnorb
+ where to find your sent messages. Set the variable
+ `gnorb-gnus-sent-groups' to a list of strings; each string should
+ indicate a fully-qualified group name, eg ``nnimap+SERVER:GROUP''.
+ @end enumerate
+ Lastly, Gnorb doesn't bind any keys by default; see the @ref{Suggested Keybindings,Suggested
+ Keybindings} section below for possibilities.
+ @node Email Tracking
+ @chapter Email Tracking
+ The most interesting thing Gnorb does is using Org headings to track
+ email conversations. This can mean anything from reminding yourself to
+ write to your mother, to conducting delicate business negotiations
+ over email, to running an email-based bug tracker.
+ Gnorb assists in this process by using the Gnus registry to track
+ correspondences between emails and Org headings -- specifically,
+ message IDs are associated with Org heading ids. As a conversation
+ develops, messages are collected on a heading (and/or its children).
+ You can compose new messages directly from the Org heading, and Gnorb
+ will automatically associate your sent message with the conversation.
+ You can open temporary Gnus *Summary* buffers holding all the messages
+ associated with an Org subtree, and reply from there. When you receive
+ new messages relevant to a conversation, Gnorb will notice them and
+ prompt you to associate them with the appropriate Org heading.
+ Attachments on incoming messages can be automatically saved as
+ attachments on Org headings, using org-attach.
+ In general, the goal is to keep track of whole conversations, reduce
+ friction when moving between Gnus and Org, and keep you in the Org
+ agenda rather than in Gnus.
+ @menu
+ * Email-Related Commands::
+ * Trigger Actions::
+ * Viewing Tracked Messages in *Summary* Buffers::
+ * Hinting in Gnus::
+ * Message Attachments::
+ @end menu
+ @node Email-Related Commands
+ @section Email-Related Commands
+ Email tracking starts in one of three ways:
+ @enumerate
+ @item
+ With an Org heading that represents an email TODO. Call
+ `gnorb-org-handle-mail' (see below) on the heading to compose a new
+ message, and start the tracking process.
+ @item
+ By calling org-capture on a received message. Any heading captured
+ from a message will automatically be associated with that message.
+ @item
+ By calling `gnorb-gnus-outgoing-do-todo' in a message composition
+ buffer -- see below.
+ @end enumerate
+ There are three main email-related commands:
+ @enumerate
+ @item
+ `gnorb-org-handle-mail' is called on an Org heading to compose a
+ new message. By default, this will begin a reply to the most recent
+ message in the conversation. If there are no associated messages to
+ reply to (or you call the function with a double prefix arg), Gnorb
+ will look for mailto: or bbdb: links in the heading, and compose a
+ new message to them.
+ The sent message will be associated with the Org heading, and
+ you'll be brought back to the heading and asked to trigger an
+ action on it.
+ `gnorb-email-subtree' is an alternative entry-point to
+ `gnorb-org-handle-mail'. It does the same thing as the latter, but
+ first exports the body of the subtree as either text or a file,
+ then inserts the text into the message body, or attaches the file
+ to the message, depending on what you've chosen.
+ @item
+ `gnorb-gnus-incoming-do-todo' is called on a message in a Gnus
+ *Summary* buffer. You'll be prompted for an Org heading, taken to
+ that heading, and asked to trigger an action on it.
+ @item
+ `gnorb-gnus-outgoing-do-todo' is called in message mode, while
+ composing a new message. 
+ If called without a prefix arg, a new Org heading will be created
+ 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.
+ 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.
+ @end enumerate
+ Because these three commands all express a similar intent, but are
+ called in different modes, it can make sense to give each of them the
+ same keybinding in the keymaps for Org mode, Gnus summary mode, and
+ Message mode, respectively.
+ @node Trigger Actions
+ @section Trigger Actions
+ After calling `gnorb-gnus-incoming-do-todo' on a message, or after
+ sending a message associated with an Org heading, you'll be taken to
+ the heading and asked to ``trigger an action'' on it. At the moment
+ there are four different possibilities: triggering a TODO state-change
+ 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'.
+ @node Viewing Tracked Messages in *Summary* Buffers
+ @section Viewing Tracked Messages in *Summary* Buffers
+ Call `gnorb-org-view' on an Org heading to open an nnir *Summary*
+ buffer showing all the messages associated with that heading (this
+ requires that you've added an nngnorb server to your Gnus backends). A
+ minor mode will be in effect, ensuring that any replies you send to
+ messages in this buffer will automatically be associated with the
+ original Org heading. You can also invoke
+ `gnorb-summary-disassociate-message' (``C-c d'') to disassociate the
+ message with the Org heading.
+ As a bonus, it's possible to go into Gnus' *Server* buffer, find the
+ line specifying your nngnorb server, and hit ``G'' (aka
+ `gnus-group-make-nnir-group'). At the query prompt, enter an Org-style
+ tags-todo Agenda query string (eg ``+work-computer'', or what have you).
+ Gnorb will find all headings matching this query, scan their subtrees
+ for gnus links, and then give you a Summary buffer containing all the
+ linked messages. This is dog-slow at the moment; it will get faster.
+ @node Hinting in Gnus
+ @section Hinting in Gnus
+ When you receive new mails that might be relevant to existing Org
+ TODOs, Gnorb can alert you to that fact. When
+ `gnorb-gnus-hint-relevant-article' is t (the default), Gnorb will
+ display a message in the minibuffer when opening potentially relevant
+ messages. You can then use `gnorb-gnus-incoming-to-todo' to trigger an
+ action on the relevant TODO.
+ This hinting can happen in the Gnus summary buffer as well. If you use
+ the escape indicated by `gnorb-gnus-summary-mark-format-letter'' as
+ part of your `gnus-summary-line-format', articles that are relevant to
+ TODOs will be marked with a special character in the Summary buffer,
+ as determined by `gnorb-gnus-summary-mark'. By default, the format
+ letter is ``g'' (meaning it is used as ``%ug'' in the format line), and
+ the mark is ``ยก''.
+ @node Message Attachments
+ @section Message Attachments
+ Gnorb simplifies the handling of attachments that you receive in
+ emails. When you call `gnorb-gnus-incoming-do-todo' on a message,
+ you'll be prompted to re-attach the email's attachments onto the Org
+ heading, using the org-attach library.
+ You can also do this as part of the capture process. Set the
+ new :gnus-attachments key to ``t'' in a capture template that you use on
+ mail messages, and you'll be queried to re-attach the message's
+ attachments onto the newly-captured heading. Or set
+ `gnorb-gnus-capture-always-attach' to ``t'' to have Gnorb do this for
+ all capture templates.
+ You can also do this using the regular system of MIME commands,
+ without invoking the email tracking process. See @ref{Suggested Keybindings,Suggested
+ Keybindings}, below.
+ The same process works in reverse: when you send a message from an Org
+ 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.
+ @node Restoring Window Layout
+ @chapter Restoring Window Layout
+ Many Gnorb functions alter the window layout and value of point. In
+ most of these cases, you can restore the previous layout using the
+ interactive function `gnorb-restore-layout'.
+ @node Recent Mails From BBDB Contacts
+ @chapter Recent Mails From BBDB Contacts
+ If you're using a recent git version of BBDB (circa mid-May 2014 or
+ later), you can give your BBDB contacts a special field which will
+ collect links to recent emails from that contact. The default name of
+ the field is ``messages'', but you can customize that name using the
+ `gnorb-bbdb-messages-field' option.
+ Gnorb will not collect links by default: you need to call
+ `gnorb-bbdb-open-link' on a contact once to start the process.
+ Thereafter, opening mails from that contact will store a link to the
+ message.
+ Once some links are stored, `gnorb-bbdb-open-link' will open them: Use
+ a prefix arg to the function call to select particular messages to
+ open. There are several options controlling how all this works; see
+ the gnorb-bbdb user options section below for details.
+ @node BBDB posting styles
+ @chapter BBDB posting styles
+ Gnorb comes with a BBDB posting-style system, inspired by (copied
+ from) gnus-posting-styles. You can specify how messages are composed
+ to specific contacts, by matching on contact field values (the same
+ way gnus-posting-styles matches on group names). See the docstring of
+ `gnorb-bbdb-posting-styles' for details.
+ In order not to be too intrusive, Gnorb doesn't alter the behavior of
+ `bbdb-mail', the usual mail-composition function. Instead it provides
+ an alternate `gnorb-bbdb-mail', which does exactly the same thing, but
+ first processes the new mail according to `gnorb-bbdb-posting-styles'.
+ If you want to use this feature regularly, you can remap `bbdb-mail'
+ to `gnorb-bbdb-mail' in the `bbdb-mode-map'.
+ @node BBDB Org tagging
+ @chapter BBDB Org tagging
+ BBDB contacts can be tagged with the same tags you use in your Org
+ files. This allows you to pop up a *BBDB* buffer alongside your Org
+ Agenda when searching for certain tags. This can happen automatically
+ for all Org tags-todo searches, if you set the option
+ `gnorb-org-agenda-popup-bbdb' to t. Or you can do it manually, by
+ calling the command of the same name. This command only shows TODOs by
+ default: use a prefix argument to show all tagged headings.
+ Tags are stored in an xfield named org-tags, by default. You can
+ customize the name of this field using `gnorb-bbdb-org-tag-field'.
+ @node Misc BBDB
+ @chapter Misc BBDB
+ @menu
+ * Searching for messages from BBDB contacts::
+ * Citing BBDB contacts::
+ * User Options::
+ @end menu
+ @node Searching for messages from BBDB contacts
+ @section Searching for messages from BBDB contacts
+ Call `gnorb-bbdb-mail-search' to search for all mail messages from the
+ record(s) displayed. Currently supports the notmuch, mairix, and
+ namazu search backends; set `gnorb-gnus-mail-search-backend' to one of
+ those symbol values.
+ @node Citing BBDB contacts
+ @section Citing BBDB contacts
+ Calling `gnorb-bbdb-cite-contact' will prompt for a BBDB record and
+ insert a string of the type ``Bob Smith <bob@@smith.com>''.
+ @node User Options
+ @section User Options
+ @table @samp
+ @item `gnorb-bbdb-org-tag-field
+ The name of the BBDB xfield, as a
+ symbol, that holds Org-related tags. Specified as a string with
+ the ``:'' separator between tags, same as for Org headings.
+ Defaults to org-tag.
+ @item `gnorb-bbdb-messages-field'
+ The name of the BBDB xfield that
+ holds links to recently-received messages from this contact.
+ Defaults to `messages.
+ @item `gnorb-bbdb-collect-N-messages'
+ Collect at most this many links
+ to messages from this contact. Defaults to 5.
+ @item `gnorb-bbdb-define-recent'
+ What does ``recently-received'' mean?
+ Possible values are the symbols seen and received. When set to
+ seen, the most recently-opened messages are collected. When set
+ to received, the most recently-received (by Date header) messages
+ are collected. Defaults to seen.
+ @item `gnorb-bbdb-message-link-format-multi'
+ How is a single message's
+ link formatted in the multi-line BBDB layout format? Defaults to
+ ``%:count. %D: %:subject'' (see the docstring for details).
+ @item ` gnorb-bbdb-message-link-format-one'
+ How is a single message's
+ link formatted in the one-line BBDB layout format? Defaults to
+ nil (see the docstring for details).
+ @item `gnorb-bbdb-posting-styles'
+ Styles to use for influencing the
+ format of mails composed to the BBDB record(s) under point (see
+ the docstring for details).
+ @end table
+ @node Misc Org
+ @chapter Misc Org
+ @menu
+ * Inserting BBDB links::
+ * User Options: User Optionsx. 
+ @end menu
+ @node Inserting BBDB links
+ @section Inserting BBDB links
+ Calling `gnorb-org-contact-link' will prompt for a BBDB record and
+ insert an Org link to that record at point.
+ @node User Optionsx
+ @section User Options
+ @table @samp
+ @item `gnorb-org-after-message-setup-hook'
+ Hook run in a message buffer
+ after setting up the message, from `gnorb-org-handle-mail' or
+ `gnorb-org-email-subtree'.
+ @item `gnorb-org-trigger-actions'
+ List of potential actions that can be
+ taken on headings after a message is sent. See docstring for
+ details.
+ @item `gnorb-org-mail-scan-scope'
+ The number of paragraphs to scan for
+ mail-related links. This comes into play when calling
+ `gnorb-org-handle-mail' on a heading with no associated messages,
+ or when `gnorb-org-handle-mail' is called with a prefix arg.
+ @item `gnorb-org-find-candidates-match'
+ When searching all Org files
+ for headings to collect messages from, this option can limit
+ which headings are searched. It is used as the second argument to
+ a call to `org-map-entries', and has the same syntax as that used
+ in an agenda tags view.
+ @item `gnorb-org-email-subtree-text-parameters'
+ A plist of export
+ parameters corresponding to the EXT-PLIST argument to the export
+ functions, for use when exporting to text.
+ @item `gnorb-org-email-subtree-file-parameters'
+ A plist of export
+ parameters corresponding to the EXT-PLIST argument to the export
+ functions, for use when exporting to a file.
+ @item `gnorb-org-email-subtree-text-options'
+ A list of ts and nils
+ corresponding to Org's export options, to be used when exporting
+ to text. The options, in order, are async, subtreep,
+ visible-only, and body-only.
+ @item `gnorb-org-email-subtree-file-options'
+ A list of ts and nils
+ corresponding to Org's export options, to be used when exporting
+ to a file. The options, in order, are async, subtreep,
+ visible-only, and body-only.
+ @item `gnorb-org-export-extensions'
+ Correspondence between export
+ backends and their respective (usual) file extensions.
+ @item `gnorb-org-capture-collect-link-p'
+ When this is set to t, the
+ capture process will always store a link to the Gnus message or
+ BBDB record under point, even when the link isn't part of the
+ capture template. It can then be added to the captured heading
+ with org-insert-link, as usual.
+ @item `gnorb-org-agenda-popup-bbdb'
+ Set to ``t'' to automatically pop up
+ the BBDB buffer displaying records corresponding to the Org
+ Agenda tags search underway. If this is nil you can always do it
+ manually with the command of the same name.
+ @item `gnorb-org-bbdb-popup-layout'
+ Controls the layout of the
+ Agenda-related BBDB popup, takes the same values as
+ bbdb-pop-up-layout.
+ @end table
+ @node Misc Gnus
+ @chapter Misc Gnus
+ @menu
+ * Viewing Org headlines relevant to a message::
+ * User Options: User Optionsxx. 
+ @end menu
+ @node Viewing Org headlines relevant to a message
+ @section Viewing Org headlines relevant to a message
+ Call `gnorb-gnus-view' on a message that is associated with an Org
+ heading to jump to that heading.
+ @node User Optionsxx
+ @section User Options
+ @table @samp
+ @item `gnorb-gnus-mail-search-backend'
+ Specifies the search backend
+ that you use for searching mails. Currently supports notmuch,
+ mairix, and namazu: set this option to one of those symbols.
+ @item `gnorb-gnus-capture-always-attach'
+ Treat all capture templates as
+ if they had the :gnus-attachments key set to ``t''. This only has
+ any effect if you're capturing from a Gnus summary or article
+ buffer.
+ @item `gnorb-trigger-todo-default'
+ Set to either `note or `todo to tell
+ `gnorb-gnus-incoming-do-todo' what to do by default. You can
+ reach the non-default behavior by calling that function with a
+ prefix argument. Alternately, set to `prompt to always prompt for
+ the appropriate action.
+ @item `gnorb-gnus-trigger-refile-targets'
+ If you use
+ `gnorb-gnus-incoming-do-todo' on an incoming message, Gnorb will
+ try to locate a TODO heading that's relevant to that message. If
+ it can't, it will prompt you for one, using the refile interface.
+ This option will be used as the value of `org-refile-targets'
+ during that process: see the docstring of `org-refile-targets'
+ for the appropriate syntax.
+ @item `gnorb-gnus-new-todo-capture-key'
+ Set this to a single-character
+ string pointing at an Org capture template to use when creating
+ TODOs from outgoing messages. The template is a regular capture
+ template, with a few exceptions. If Gnus helps you archive
+ outgoing messages (ie you have `gnus-message-archive-group' set
+ to something, and your outgoing messages have a ``Fcc'' header), a
+ link to that message will be made, and you'll be able to use all
+ the escapes related to gnus messages. If you don't archive
+ outgoing messages, you'll still be able to use the %:subject,
+ %:to, %:toname, %:toaddress, and %:date escapes in the capture
+ template.
+ @item `gnorb-gnus-hint-relevant-article'
+ Set to ``t'' (the default) to
+ have Gnorb give you a hint in the minibuffer when opening
+ messages that might be relevant to existing Org TODOs.
+ @item `gnorb-gnus-summary-mark-format-letter'
+ The formatting letter to
+ use as part of your `gnus-summary-line-format', to indicate
+ messages which might be relevant to Org TODOs. Defaults to ``g'',
+ meaning it should be used as ``%ug'' in the format line.
+ @item `gnorb-gnus-summary-mark'
+ The mark used to indicate relevant
+ messages in the Summary buffer, when
+ `gnorb-gnus-summary-mark-format-letter' is present in the format
+ line. Defaults to ``ยก''.
+ @end table
+ @node Suggested Keybindings
+ @chapter Suggested Keybindings
+ @lisp
+ (eval-after-load "gnorb-bbdb"
+   '(progn
+      (define-key bbdb-mode-map (kbd "O") 'gnorb-bbdb-tag-agenda)
+      (define-key bbdb-mode-map (kbd "S") 'gnorb-bbdb-mail-search)
+      (define-key bbdb-mode-map [remap bbdb-mail] 'gnorb-bbdb-mail)
+      (define-key bbdb-mode-map (kbd "l") 'gnorb-bbdb-open-link)
+      (global-set-key (kbd "C-c C") 'gnorb-bbdb-cite-contact)))
+ (eval-after-load "gnorb-org"
+   '(progn
+      (org-defkey org-mode-map (kbd "C-c C") 'gnorb-org-contact-link)
+      (org-defkey org-mode-map (kbd "C-c t") 'gnorb-org-handle-mail)
+      (org-defkey org-mode-map (kbd "C-c e") 'gnorb-org-view)
+      (org-defkey org-mode-map (kbd "C-c E") 'gnorb-org-email-subtree)
+      (org-defkey org-mode-map (kbd "C-c V") 'gnorb-org-popup-bbdb)
+      (setq gnorb-org-agenda-popup-bbdb t)
+      (eval-after-load "org-agenda"
+        '(progn (org-defkey org-agenda-mode-map (kbd "H") 'gnorb-org-handle-mail)
+                (org-defkey org-agenda-mode-map (kbd "V") 'gnorb-org-popup-bbdb)))))
+ (eval-after-load "gnorb-gnus"
+   '(progn
+      (define-key gnus-summary-mime-map "a" 'gnorb-gnus-article-org-attach)
+      (define-key gnus-summary-mode-map (kbd "C-c t") 'gnorb-gnus-incoming-do-todo)
+      (push '("attach to org heading" . gnorb-gnus-mime-org-attach)
+            gnus-mime-action-alist)
+      ;; The only way to add mime button command keys is by redefining
+      ;; gnus-mime-button-map, possibly not ideal. Ideal would be a
+      ;; setter function in gnus itself.
+      (push '(gnorb-gnus-mime-org-attach "a" "Attach to Org heading")
+            gnus-mime-button-commands)
+      (setq gnus-mime-button-map
+            (let ((map (make-sparse-keymap)))
+              (define-key map gnus-mouse-2 'gnus-article-push-button)
+              (define-key map gnus-down-mouse-3 'gnus-mime-button-menu)
+              (dolist (c gnus-mime-button-commands)
+                (define-key map (cadr c) (car c)))
+              map))))
+ (eval-after-load "message"
+   '(progn
+      (define-key message-mode-map (kbd "C-c t") 'gnorb-gnus-outgoing-do-todo)))
+ @end lisp
+ @node Wishlist/TODO
+ @chapter Wishlist/TODO
+ @itemize
+ @item
+ 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?
+ @item
+ Add trigger actions that create new sibling or child headings on the
+ original Org heading.
+ @item
+ Allow tagging of Gnus messages, by giving the message's registry
+ entry an `org-tags key.
+ @item
+ Provide persistent nngnorb search groups.
+ @item
+ 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.
+ @item
+ Provide completion when setting Org tags on a BBDB contact.
+ @item
+ Provide a `gnorb-bbdb-view' command that opens a *Summary* buffer
+ containing all the tracked messages from the contact(s) under point.
+ @item
+ 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.
+ @end itemize
+ @node Index
+ @chapter Index
+ @c Emacs 25.0.50.8 (Org mode 8.3beta)
+ @bye
index 0000000000000000000000000000000000000000,bdaf569bc8fa66be8ee4d39fc04ae1f6885dbfcd..bdaf569bc8fa66be8ee4d39fc04ae1f6885dbfcd
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,375 +1,375 @@@
+ ;;; nngnorb.el --- Gnorb backend for Gnus
+ ;; This file is in the public domain.
+ ;; Author: Eric Abrahamsen <eric@ericabrahamsen.net.>
+ ;; This file is part of GNU Emacs.
+ ;; GNU Emacs is free software: you can redistribute it and/or modify
+ ;; it under the terms of the GNU General Public License as published by
+ ;; the Free Software Foundation, either version 3 of the License, or
+ ;; (at your option) any later version.
+ ;; GNU Emacs is distributed in the hope that it will be useful,
+ ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ ;; GNU General Public License for more details.
+ ;; You should have received a copy of the GNU General Public License
+ ;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+ ;;; Commentary:
+ ;; This is a backend for supporting Gnorb-related stuff. I'm going to
+ ;; regret this, I know.
+ ;; It started off just with wanting to collect all the gnus links in a
+ ;; subtree, and display all the messages in an ephemeral group. But it
+ ;; doesn't seem possible to create ephemeral groups without
+ ;; associating them with a server, and which server would that be?
+ ;; Nnir also provides a nice interface to creating ephemeral groups,
+ ;; but again, it relies on a server parameter to know which nnir
+ ;; engine to use, and if you try to fake it it still craps out.
+ ;; So this file is a copy-pasta from nnnil.el -- I'm trying to keep
+ ;; this as simple as possible. Right now it does nothing but serving
+ ;; as a place to hang ephemeral groups made with nnir searches of
+ ;; message from the rest of your gnus installation. Enjoy.
+ ;;; Code:
+ (eval-and-compile
+   (require 'nnheader)
+   (require 'nnir))
+ (defvar nngnorb-status-string "")
+ (defvar nngnorb-attachment-file-list nil
+   "A place to store Org attachments relevant to the subtree being
+   viewed.")
+ (make-variable-buffer-local 'nngnorb-attachment-file-list)
+ (gnus-declare-backend "nngnorb" 'none)
+ (add-to-list 'nnir-method-default-engines '(nngnorb . gnorb))
+ (add-to-list 'nnir-engines
+            '(gnorb nnir-run-gnorb))
+ (defun nnir-run-gnorb (query server &optional group)
+   "Run the actual search for messages to display. See nnir.el for
+ some details of how this gets called.
+ As things stand, the query string can be given as one of two
+ different things. First is the ID string of an Org heading,
+ prefixed with \"id+\". This was probably a bad choice as it could
+ conceivably look like an org tags search string. Fix that later.
+ If it's an ID, then the entire subtree text of that heading is
+ scanned for gnus links, and the messages relevant to the subtree
+ are collected from the registry, and all the resulting messages
+ are displayed in an ephemeral group.
+ Otherwise, the query string can be a tags match string, a la the
+ Org agenda tags search. All headings matched by this string will
+ be scanned for gnus messages, and those messages displayed."
+   ;; During the transition period between using message-ids stored in
+   ;; a property, and the new registry-based system, we're going to use
+   ;; both methods to collect relevant messages. This could be a little
+   ;; slower, but for the time being it will be safer.
+   (save-excursion
+     (let ((q (cdr (assq 'query query)))
+         (buf (get-buffer-create nnir-tmp-buffer))
+         msg-ids org-ids links vectors)
+       (with-current-buffer buf
+       (erase-buffer)
+       (setq nngnorb-attachment-file-list nil))
+       (when (equal "5.13" gnus-version-number)
+       (setq q (car q)))
+       (cond ((string-match "id\\+\\([[:alnum:]-]+\\)$" q)
+            (with-demoted-errors "Error: %S"
+              (org-id-goto (match-string 1 q))
+              (append-to-buffer
+               buf
+               (point)
+               (org-element-property
+                :end (org-element-at-point)))
+              (save-restriction
+                (org-narrow-to-subtree)
+                (setq org-ids
+                      (append
+                       (gnorb-collect-ids)
+                       org-ids))
+                (when org-ids
+                  (with-current-buffer buf
+                    ;; The file list var is buffer local, so set it
+                    ;; (local to the nnir-tmp-buffer) to a full list
+                    ;; of all files in the subtree.
+                    (dolist (id org-ids)
+                      (setq nngnorb-attachment-file-list
+                            (append (gnorb-org-attachment-list id)
+                                    nngnorb-attachment-file-list))))))))
+           ((listp q)
+            ;; be a little careful: this could be a list of links, or
+            ;; it could be the full plist
+            (setq links (if (plist-member q :gnus)
+                            (plist-get q :gnus)
+                          q)))
+           (t (org-map-entries
+               (lambda ()
+                 (push (org-id-get) org-ids)
+                 (append-to-buffer
+                  buf
+                  (point)
+                  (save-excursion
+                    (outline-next-heading)
+                    (point))))
+               q
+               'agenda)))
+       (with-current-buffer buf
+       (goto-char (point-min))
+       (setq links (plist-get (gnorb-scan-links (point-max) 'gnus)
+                              :gnus))
+       (goto-char (point-min))
+       (while (re-search-forward
+               (concat ":" gnorb-org-msg-id-key ": \\([^\n]+\\)")
+               (point-max) t)
+         (setq msg-ids (append (split-string (match-string 1)) msg-ids))))
+       ;; Here's where we maybe do some duplicate work using the
+       ;; registry. Take our org ids and find all relevant message ids.
+       (dolist (i (delq nil org-ids))
+       (let ((rel-msg-id (gnorb-registry-org-id-search i)))
+         (when rel-msg-id
+           (setq msg-ids (append rel-msg-id msg-ids)))))
+       (when msg-ids
+         (dolist (id msg-ids)
+           (let ((link (gnorb-msg-id-to-link id)))
+             (when link
+               (push link links)))))
+       (setq links (delete-dups links))
+       (unless (gnus-alive-p)
+       (gnus))
+       (dolist (m links (when vectors
+                        (nreverse vectors)))
+       (let (server-group msg-id result artno)
+         (setq m (org-link-unescape m))
+         (when (string-match "\\`\\([^#]+\\)\\(#\\(.*\\)\\)?" m)
+           (setq server-group (match-string 1 m)
+                 msg-id (match-string 3 m)
+                 result (ignore-errors (gnus-request-head msg-id server-group)))
+           (when result
+            (setq artno (cdr result))
+            (when (and (integerp artno) (> artno 0))
+              (push (vector server-group artno 100) vectors)))))))))
+ (defvar gnorb-summary-minor-mode-map (make-sparse-keymap)
+   "Keymap for use in Gnorb's *Summary* minor mode.")
+ (define-minor-mode gnorb-summary-minor-mode
+   "A minor mode for use in nnir *Summary* buffers created by Gnorb.
+ These *Summary* buffers are usually created by calling
+ `gnorb-org-view', or by initiating an nnir search on a nngnorb server.
+ While active, this mode provides some Gnorb-specific commands,
+ and also advises Gnus' reply-related commands in order to
+ continue to provide tracking of sent messages."
+   nil " Gnorb" gnorb-summary-minor-mode-map
+   (setq nngnorb-attachment-file-list
+       ;; Copy the list of attached files from the nnir-tmp-buffer to
+       ;; this summary buffer.
+       (buffer-local-value
+        'nngnorb-attachment-file-list
+         (get-buffer nnir-tmp-buffer))))
+ (define-key gnorb-summary-minor-mode-map
+   [remap gnus-summary-exit]
+   'gnorb-summary-exit)
+ (define-key gnorb-summary-minor-mode-map (kbd "C-c d")
+   'gnorb-summary-disassociate-message)
+ ;; All this is pretty horrible, but it's the only way to get sane
+ ;; behavior, there are no appropriate hooks, and I want to avoid
+ ;; advising functions.
+ (define-key gnorb-summary-minor-mode-map
+   [remap gnus-summary-very-wide-reply-with-original]
+   'gnorb-summary-very-wide-reply-with-original)
+ (define-key gnorb-summary-minor-mode-map
+   [remap gnus-summary-wide-reply-with-original]
+   'gnorb-summary-wide-reply-with-original)
+ (define-key gnorb-summary-minor-mode-map
+   [remap gnus-summary-reply]
+   'gnorb-summary-reply)
+ (define-key gnorb-summary-minor-mode-map
+   [remap gnus-summary-very-wide-reply]
+   'gnorb-summary-very-wide-reply)
+ (define-key gnorb-summary-minor-mode-map
+   [remap gnus-summary-reply-with-original]
+   'gnorb-summary-reply-with-original)
+ (define-key gnorb-summary-minor-mode-map
+   [remap gnus-summary-wide-reply]
+   'gnorb-summary-wide-reply)
+ (define-key gnorb-summary-minor-mode-map
+   [remap gnus-summary-mail-forward]
+   'gnorb-summary-mail-forward)
+ (defun gnorb-summary-wide-reply (&optional yank)
+   (interactive
+    (list (and current-prefix-arg
+             (gnus-summary-work-articles 1))))
+   (gnorb-summary-reply yank t))
+ (defun gnorb-summary-reply-with-original (n &optional wide)
+   (interactive "P")
+   (gnorb-summary-reply (gnus-summary-work-articles n) wide))
+ (defun gnorb-summary-very-wide-reply (&optional yank)
+   (interactive
+    (list (and current-prefix-arg
+             (gnus-summary-work-articles 1))))
+   (gnorb-summary-reply yank t (gnus-summary-work-articles yank)))
+ (defun gnorb-summary-reply (&optional yank wide very-wide)
+   (interactive)
+   (gnus-summary-reply yank wide very-wide)
+   (gnorb-summary-reply-hook))
+ (defun gnorb-summary-wide-reply-with-original (n)
+   (interactive "P")
+   (gnorb-summary-reply-with-original n t))
+ (defun gnorb-summary-very-wide-reply-with-original (n)
+   (interactive "P")
+   (gnorb-summary-reply
+    (gnus-summary-work-articles n) t (gnus-summary-work-articles n)))
+ (defun gnorb-summary-mail-forward (n)
+   (interactive "P")
+   (gnus-summary-mail-forward n t)
+   (gnorb-summary-reply-hook))
+ (defun gnorb-summary-reply-hook (&rest args)
+   "Function that runs after any command that creates a reply."
+   ;; Not actually a "hook"
+   (let* ((msg-id (aref message-reply-headers 4))
+        (org-id (car-safe (gnus-registry-get-id-key msg-id 'gnorb-ids)))
+        (compose-marker (make-marker))
+        (attachments (buffer-local-value
+                      'nngnorb-attachment-file-list
+                      (get-buffer nnir-tmp-buffer))))
+     (when org-id
+       (move-marker compose-marker (point))
+       (save-restriction
+       (widen)
+       (message-narrow-to-headers-or-head)
+       (goto-char (point-at-bol))
+       (open-line 1)
+       (message-insert-header
+        (intern gnorb-mail-header)
+        org-id)
+       (add-to-list 'message-exit-actions
+                    'gnorb-org-restore-after-send t))
+       (goto-char compose-marker))
+     (when attachments
+       (map-y-or-n-p
+        (lambda (a) (format "Attach %s to outgoing message? "
+                          (file-name-nondirectory a)))
+        (lambda (a)
+        (mml-attach-file a (mm-default-file-encoding a)
+                         nil "attachment"))
+        attachments
+        '("file" "files" "attach")))))
+ (defun gnorb-summary-exit ()
+   "Like `gnus-summary-exit', but restores the gnorb window conf."
+   (interactive)
+   (call-interactively 'gnus-summary-exit)
+   (gnorb-restore-layout))
+ (defun gnorb-summary-disassociate-message ()
+   "Disassociate a message from its Org TODO.
+ 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)
+   (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)))))
+ (defvar nngnorb-status-string "")
+ (defun nngnorb-retrieve-headers (articles &optional group server fetch-old)
+   (with-current-buffer nntp-server-buffer
+     (erase-buffer))
+   'nov)
+ (defun nngnorb-open-server (server &optional definitions)
+   t)
+ (defun nngnorb-close-server (&optional server)
+   t)
+ (defun nngnorb-request-close ()
+   t)
+ (defun nngnorb-server-opened (&optional server)
+   t)
+ (defun nngnorb-status-message (&optional server)
+   nngnorb-status-string)
+ (defun nngnorb-request-article (article &optional group server to-buffer)
+   (setq nngnorb-status-string "No such group")
+   nil)
+ (defun nngnorb-request-group (group &optional server fast info)
+   (let (deactivate-mark)
+     (with-current-buffer nntp-server-buffer
+       (erase-buffer)
+       (insert "411 no such news group\n")))
+   (setq nngnorb-status-string "No such group")
+   nil)
+ (defun nngnorb-close-group (group &optional server)
+   t)
+ (defun nngnorb-request-list (&optional server)
+   (with-current-buffer nntp-server-buffer
+     (erase-buffer))
+   t)
+ (defun nngnorb-request-post (&optional server)
+   (setq nngnorb-status-string "Read-only server")
+   nil)
+ (provide 'nngnorb)
+ ;;; nnnil.el ends here