--- /dev/null
+gnorb.org
\ No newline at end of file
--- /dev/null
+*.elc
+notes.org
+gnorb-pkg.el
+gnorb-autoloads.el
\ No newline at end of file
--- /dev/null
+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.
--- /dev/null
+* 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.
--- /dev/null
+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.
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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)
--- /dev/null
+;;; 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
--- /dev/null
+;;; 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
--- /dev/null
+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:
--- /dev/null
+#+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.
--- /dev/null
+\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
\ No newline at end of file
--- /dev/null
+;;; 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