--- /dev/null
+ gnorb.org
--- /dev/null
--- /dev/null
++*.elc
++notes.org
++gnorb-pkg.el
++gnorb-autoloads.el
--- /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
--- /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