branch: master commit 3e5c11a13981a1ff613cb4442ad644285c44e481 Author: Eric Abrahamsen <e...@ericabrahamsen.net> Commit: Eric Abrahamsen <e...@ericabrahamsen.net>
Squashed 'packages/gnorb/' changes from 321b23b..4e7039a 4e7039a Various compiler-inspired improvements 9b2b269 Use with-eval-after-load not eval-after-load 86fa893 Fix up all cl-lib calls a59dac2 Use hook for determining Gnorb summary minor mode 4d3de61 Various documentation improvements acb91c5 Fix doc error 6fd368d Provide more format marks in summary buffers d9a1d89 Remove unused let variable 3f9c534 Report Gnorb email tracking usage 0f18c45 Allow persistent nnir search groups 2d30b0c Reset window conf after nnir-run-gnorb 160f43a New function for returning all tracked messages 9efae5a Fix call to cl-subseq ce764a5 fixup with new quick reply command b0fe9ae New command `gnorb-gnus-quick-reply' 5897188 Capture to child/sibling is done 4f99dd7 Handle conditions where `registry-search' returns nil b951675 Merge capture-to-child branch 13bb840 Hint which heading will be triggered c13f4df Better check for capture cleanup dfa0043 Safer usage of cl-subseq 94fe1b8 Incorporate changes from Stefan M d2e1e11 Mention registry bugs in README a4089f8 Fix completing-read in message disassociation 9c910c9 Re-raise errors in the triggering process 648f5a7 Remove process mark after bulk association 84ff7a7 Don't let attach errors derail the trigger process 819b1e5 Suggest binding gnorb-org-view in Org Agenda 9d64acb Update gnorb-registry-capture to use convenience funcs cc7d45b Be more careful handling org tags on BBDB records f585c03 condition-case the incoming trigger process 821a6b2 Allow bulk association of messages 4b19c83 New function for pruning dead associations 09679fa Misspelled function name 41c6778 nngnorb should be a virtual server 6e6ee46 Zap another with-eval-after-load c3279d2 Fix tracking messages from virtual groups 9220a10 Docstring fix c8b80c5 Bugfix for gnorb-gnus-view 8c333ee Merge pull request #20 from totherme/master 3801ad7 Check both gnus version and emacs version. 94f6897 Don't use with-eval-after-load fd91084 Remove incorrect "fix" for Gnus 5.13 8a9c167 Fix the cl-lib loading stuff git-subtree-dir: packages/gnorb git-subtree-split: 4e7039a15b47244e7bd2c580d8bce976a6116b5a --- README.org | 29 ++++++- gnorb-bbdb.el | 41 ++++----- gnorb-gnus.el | 280 ++++++++++++++++++++++++++++++++++++----------------- gnorb-org.el | 60 +++++++---- gnorb-registry.el | 118 +++++++++++++++++++--- gnorb-utils.el | 169 ++++++++++++++++++++++---------- gnorb.el | 11 +- gnorb.info | 199 +++++++++++++++++++++++++------------- gnorb.org | 108 +++++++++++++++------ gnorb.texi | 132 +++++++++++++++++++------ nngnorb.el | 36 ++++--- 11 files changed, 834 insertions(+), 349 deletions(-) diff --git a/README.org b/README.org index 1f8f82f..9e2f9bd 100644 --- a/README.org +++ b/README.org @@ -16,7 +16,31 @@ 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. +** Known bugs/issues +*** Gnus Registry +Prior to late December, 2014, the Gnus registry had some issues with +preserving "precious" entries while pruning. +When the registry approaches its maximum size it will delete excess +entries, a process referred to as "pruning". "Precious" entries are +those that contain important information: they should not be pruned. + +Gnorb uses the registry to track associations between messages and Org +headings, and marks those entries as precious. The entire process of +tracking, in fact, relies on these entries being preserved, and Gnorb +goes to some lengths to protect this information. Older versions of +the registry could nevertheless delete those entries. + +These issues are fixed circa the end of December, 2014, around "Ma +Gnus v0.12", whatever that means. If you think there's a possibility +your registry is full, and associations are being deleted, you might +consider upgrading to a recent Gnus. +*** Multiple Associations +Gnorb theoretically supports email messages being associated with +multiple Org headings. In practice, however, this situation hasn't +been thought through completely, and you may experience weirdness. If +you do, and you have some ideas about how it should be handled, please +contact the author and suggest them. ** Installation It's easiest to install Gnorb from Elpa: run `list-packages' and look @@ -60,7 +84,10 @@ composing messages from... Or maybe it's just a case of NIH. Provide an Org Agenda command that does an email search for messages received in the visible date span, or day under point, etc. Make it work in the calendar, as well? -*** TODO Capture to child/subtree trigger actions +*** DONE Capture to child/subtree trigger actions +:LOGBOOK: +- State "DONE" from "TODO" [2015-03-17 Tue 17:42] +:END: Add trigger actions that create new sibling or child headings on the original Org heading. *** TODO Gnus message tagging diff --git a/gnorb-bbdb.el b/gnorb-bbdb.el index 058011c..eb2f6eb 100644 --- a/gnorb-bbdb.el +++ b/gnorb-bbdb.el @@ -1,6 +1,6 @@ ;;; gnorb-bbdb.el --- The BBDB-centric functions of gnorb -;; Copyright (C) 2014 Eric Abrahamsen +;; Copyright (C) 2014 Free Software Foundation, Inc. ;; Author: Eric Abrahamsen <e...@ericabrahamsen.net> ;; Keywords: @@ -24,11 +24,9 @@ ;;; Code: -(eval-when-compile - (require 'cl)) - -(require 'bbdb) +(require 'bbdb nil t) (require 'gnorb-utils) +(require 'cl-lib) (defgroup gnorb-bbdb nil "The BBDB bits of gnorb." @@ -40,8 +38,9 @@ :group 'gnorb-bbdb :type 'symbol) -(unless (assoc gnorb-bbdb-org-tag-field bbdb-separator-alist) - (push `(,gnorb-bbdb-org-tag-field ":" ":") bbdb-separator-alist)) +(when (boundp 'bbdb-separator-alist) ;Allow compilation if BBDB is absent! + (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 @@ -108,7 +107,7 @@ mentioned in the docstring of `format-time-string', which see." Defaults to org-link." :group 'gnorb-bbdb) -(defstruct gnorb-bbdb-link +(cl-defstruct gnorb-bbdb-link subject date group id) (defcustom gnorb-bbdb-posting-styles nil @@ -206,6 +205,8 @@ Org tags are stored in the `gnorb-bbdb-org-tags-field'." (insert (bbdb-indent-string (concat val "\n") indent))))))) +(defvar message-mode-hook) + ;;;###autoload (defun gnorb-bbdb-mail (records &optional subject n verbose) "\\<bbdb-mode-map>Acts just like `bbdb-mail', except runs @@ -420,14 +421,14 @@ a prefix arg and \"*\", the prefix arg must come first." gnorb-gnus-mail-search-backends) (error "No search backend specified"))) (search-string - (funcall (second backend) + (funcall (cl-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))) + (funcall (cl-third backend) search-string) + (delete-other-windows))) ;;;###autoload (defun gnorb-bbdb-cite-contact (rec) @@ -438,8 +439,8 @@ a prefix arg and \"*\", the prefix arg must come first." mail-string))) ;;; Field containing links to recent messages - -(add-to-list 'bbdb-xfield-label-list gnorb-bbdb-messages-field nil 'eq) +(when (boundp 'bbdb-xfield-label-list) + (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 @@ -594,16 +595,10 @@ to a message into the record's `gnorb-bbdb-messages-field'." (parse-time-string (mail-header-date heads)))) (subject (mail-header-subject heads)) (id (mail-header-id heads)) - (group gnus-newsgroup-name) + (group (gnorb-get-real-group-name + gnus-newsgroup-name + art-no)) 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 @@ -617,7 +612,7 @@ to a message into the record's `gnorb-bbdb-messages-field'." (time-less-p (gnorb-bbdb-link-date b) (gnorb-bbdb-link-date a)))))) - (setq val (cl-subseq val 0 gnorb-bbdb-collect-N-messages)) + (setq val (cl-subseq val 0 (min (length val) gnorb-bbdb-collect-N-messages))) (bbdb-record-set-xfield record gnorb-bbdb-messages-field (delq nil val)) diff --git a/gnorb-gnus.el b/gnorb-gnus.el index 4b7a16f..a77a7ed 100644 --- a/gnorb-gnus.el +++ b/gnorb-gnus.el @@ -1,6 +1,6 @@ ;;; gnorb-gnus.el --- The gnus-centric fuctions of gnorb -;; Copyright (C) 2014 Eric Abrahamsen +;; Copyright (C) 2014 Free Software Foundation, Inc. ;; Author: Eric Abrahamsen <e...@ericabrahamsen.net> ;; Keywords: @@ -24,9 +24,6 @@ ;;; Code: -(eval-when-compile - (require 'cl)) - (require 'gnus) (require 'gnorb-utils) @@ -110,6 +107,12 @@ Basically behave as if all attachments have \":gnus-attachments t\"." :group 'gnorb-gnus :type 'string) +(defcustom gnorb-gnus-summary-tracked-mark "&" + "Default mark to insert in the summary format line of articles + that are already tracked by 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 @@ -192,7 +195,7 @@ save them into `gnorb-tmp-dir'." (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)) + (let ((article (gnus-summary-article-number)) mime-handles) (when (or (null gnus-current-article) (null gnus-article-current) @@ -236,10 +239,13 @@ save them into `gnorb-tmp-dir'." (add-hook 'org-capture-mode-hook 'gnorb-gnus-capture-attach) +(defvar org-note-abort) + (defun gnorb-gnus-capture-abort-cleanup () (with-no-warnings ; For `org-note-abort' - (when (and org-note-abort - (org-capture-get :gnus-attachments)) + (when (and org-note-abort + (or gnorb-gnus-capture-always-attach + (org-capture-get :gnus-attachments))) (condition-case error (progn (org-attach-delete-all) (setq abort-note 'clean) @@ -299,11 +305,14 @@ information about the outgoing message into ;; `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 + (add-to-list 'message-send-actions 'gnorb-org-restore-after-send t)) (setq gnorb-message-org-ids nil))))) -(add-hook 'message-header-hook 'gnorb-gnus-check-outgoing-headers) +;; This sets the global value, but the hook is made buffer-local in +;; `gnus-inews-add-send-actions', so this is ignored +;(add-hook 'message-header-hook 'gnorb-gnus-check-outgoing-headers) +(add-hook 'message-send-hook 'gnorb-gnus-check-outgoing-headers t) ;;;###autoload (defun gnorb-gnus-outgoing-do-todo (&optional arg) @@ -381,10 +390,9 @@ work." (save-excursion (save-restriction (widen) - (setq message-exit-actions - (remove 'gnorb-org-restore-after-send - (remove 'gnorb-gnus-outgoing-make-todo-1 - message-exit-actions))) + (setq message-send-actions + (remove 'gnorb-gnus-outgoing-make-todo-1 + message-send-actions)) (message-narrow-to-headers-or-head) (message-remove-header gnorb-mail-header) @@ -424,17 +432,16 @@ work." ;; 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) + (unless header-ids + (add-to-list 'message-send-actions + 'gnorb-gnus-outgoing-make-todo-1 t)) (message (if header-ids "Message will trigger TODO state-changes after sending" "A TODO will be made from this message after it's sent")))))) +(defvar org-capture-link-is-already-stored) + (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")) @@ -486,18 +493,21 @@ work." (defun gnorb-gnus-incoming-do-todo (arg &optional id) "Call this function from a received gnus message to store a link to the message, prompt for a related Org heading, visit the -heading, and either add a note or trigger a TODO state change. -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)." +heading, and trigger an action on it \(see +`gnorb-org-trigger-actions'\). + +If you've set up message tracking \(with +`gnorb-tracking-initialize'\), Gnorb can guess 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 messages referenced there are already being tracked by any +headings. + +If you mark several messages before calling this function, or +call it with a numerical prefix arg, those messages will be +\"bulk associated\" with the chosen Org heading: associations +will be made, but you won't be prompted to trigger an action, and +you'll stay in the Gnus summary buffer." (interactive "P") (when (not (memq major-mode '(gnus-summary-mode gnus-article-mode))) (user-error "Only works in gnus summary or article mode")) @@ -507,15 +517,18 @@ to t (it is, by default)." (setq gnorb-window-conf (current-window-configuration)) (move-marker gnorb-return-marker (point)) (setq gnorb-gnus-message-info nil) - (let* ((headers (gnus-data-header - (gnus-data-find - (gnus-summary-article-number)))) + (let* ((articles (gnus-summary-work-articles arg)) + (art-no (gnus-summary-article-number)) + (headers (gnus-data-header + (gnus-data-find art-no))) (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) + (group (gnorb-get-real-group-name + gnus-newsgroup-name + art-no)) (link (call-interactively 'org-store-link)) (org-refile-targets gnorb-gnus-trigger-refile-targets) (ref-msg-ids (concat (mail-header-references headers) " " @@ -532,37 +545,109 @@ to t (it is, by default)." :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) - ;; Flush out zombies (dead associations). - (setq related-headings - (cl-remove-if - (lambda (h) - (when (null (org-id-find-id-file h)) - (when (y-or-n-p - (format - "ID %s no longer exists, disassociate message?" - h)) - (gnorb-delete-association msg-id h)))) - related-headings)) - (if (catch 'target - (dolist (h related-headings nil) - (when (yes-or-no-p - (format "Trigger action on %s" - (gnorb-pretty-outline h))) - (throw 'target (setq targ h))))) - (gnorb-trigger-todo-action arg targ) - (setq targ (org-refile-get-location - "Trigger heading" nil t)) - (find-file (nth 1 targ)) - (goto-char (nth 3 targ)) - (gnorb-trigger-todo-action arg))))) + (condition-case err + (if id + (progn + (delete-other-windows) + (gnorb-trigger-todo-action nil id)) + ;; Flush out zombies (dead associations). + (setq related-headings + (cl-remove-if + (lambda (h) + (when (null (org-id-find-id-file h)) + (when (y-or-n-p + (format + "ID %s no longer exists, disassociate message?" + h)) + (gnorb-delete-association msg-id h)))) + related-headings)) + ;; See if one of the related headings is chosen. + (unless (catch 'target + (dolist (h related-headings nil) + (when (yes-or-no-p + (format "Trigger action on %s" + (gnorb-pretty-outline h))) + (throw 'target (setq targ h))))) + ;; If not, use the refile interface to choose one. + (setq targ (org-refile-get-location + "Trigger heading" nil t)) + (setq targ + (save-window-excursion + (find-file (nth 1 targ)) + (goto-char (nth 3 targ)) + (org-id-get-create)))) + ;; Either bulk associate multiple messages... + (if (> (length articles) 1) + (progn + (dolist (a articles) + (gnorb-registry-make-entry + (mail-header-id + (gnus-data-header + (gnus-data-find a))) + from subject targ group) + (gnus-summary-remove-process-mark a)) + (message "Associated %d messages with %s" + (length articles) (gnorb-pretty-outline targ))) + ;; ...or just trigger the one. + (delete-other-windows) + (gnorb-trigger-todo-action nil targ))) + (error + ;; If these are left populated after an error, it plays hell + ;; with future trigger processes. + (setq gnorb-gnus-message-info nil) + (setq gnorb-gnus-capture-attachments nil) + (signal (car err) (cdr err)))))) ;;;###autoload -(defun gnorb-gnus-search-messages (str &optional ret) +(defun gnorb-gnus-quick-reply () + "Compose a reply to the message under point, and associate both +the original message and the reply with the selected heading. +Take no other action. + +Use this when you want to compose a reply to a message on the +spot, and track both messages, without having to go through the +hassle of triggering an action on a heading, and then starting a +reply." + (interactive) + (when (not (memq major-mode '(gnus-summary-mode gnus-article-mode))) + (user-error "Only works in gnus summary or article mode")) + (let* ((art-no (gnus-summary-article-number)) + (headers (gnus-data-header + (gnus-data-find art-no))) + (msg-id (mail-header-id headers)) + (from (mail-header-from headers)) + (subject (mail-header-subject headers)) + (group (gnorb-get-real-group-name + gnus-newsgroup-name + art-no)) + (ref-msg-ids (concat (mail-header-references headers) " " + msg-id)) + (related-headings + (when ref-msg-ids + (gnorb-find-tracked-headings headers t))) + (targ (car-safe related-headings))) + (if targ + (let ((ret (make-marker))) + ;; Assume the first heading is the one we want. + (gnorb-registry-make-entry + msg-id from subject targ group) + (gnus-summary-wide-reply-with-original 1) + (move-marker ret (point)) + (save-restriction + (widen) + (message-narrow-to-headers-or-head) + (goto-char (point-min)) + (open-line 1) + (message-insert-header + (intern gnorb-mail-header) targ)) + (goto-char ret) + (message + (format "Original message and reply will be associated with %s" + (gnorb-pretty-outline targ)))) + (message "No associated headings found")))) + +;;;###autoload +(defun gnorb-gnus-search-messages (str persist &optional head-text 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 @@ -579,31 +664,46 @@ work." (let ((nnir-address (or (gnus-method-to-server '(nngnorb)) (user-error - "Please add a \"nngnorb\" backend to your gnus installation.")))) + "Please add a \"nngnorb\" backend to your gnus installation."))) + name method spec) (when (version= "5.13" gnus-version-number) (with-no-warnings ; All these variables are available. (setq nnir-current-query nil nnir-current-server nil nnir-current-group-marked nil nnir-artlist nil))) - (gnus-group-read-ephemeral-group - ;; in 24.4, the group name is mostly decorative. in 24.3, the - ;; query itself is read from there. It should look like (concat - ;; "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 - nil nil - ;; the following seems to simply be ignored under gnus 5.13 - (list (cons 'nnir-specs (list (cons 'nnir-query-spec `((query . ,str))) + ;; In 24.4, the group name is mostly decorative, but in 24.3, the + ;; actual query is held there. + (setq name (if (version= "5.13" gnus-version-number) + (concat "nnir:" (prin1-to-string `((query ,str)))) + (if persist + (read-string + (format "Name for group (default %s): " head-text) + nil head-text t) + (concat "gnorb-" str)))) + (setq method (if (version= "5.13" gnus-version-number) + (list 'nnir nnir-address) + (list 'nnir "Gnorb"))) + (setq spec + (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))) + (if persist + (progn + (switch-to-buffer gnus-group-buffer) + (gnus-group-make-group name method nil spec) + (gnus-group-select-group)) + (gnus-group-read-ephemeral-group name method nil ret nil nil spec)))) + +(defun gnorb-gnus-summary-mode-hook () + "Check if we've entered a Gnorb-generated group, and activate + `gnorb-summary-minor-mode', if so." + (let ((method (gnus-find-method-for-group gnus-newsgroup-name))) + (when (string-match-p "Gnorb" (cadr method)) + (gnorb-summary-minor-mode)))) + +(add-hook 'gnus-summary-mode-hook #'gnorb-gnus-summary-mode-hook) ;;; Automatic noticing of relevant messages @@ -633,8 +733,7 @@ option `gnorb-gnus-hint-relevant-article' is non-nil." (tracked-headings (gnorb-find-tracked-headings headers)) (key (where-is-internal 'gnorb-gnus-incoming-do-todo - nil t)) - rel-headings) + nil t))) (cond (assoc-heading (message "Message is associated with %s" (gnorb-pretty-outline (car assoc-heading) t))) @@ -652,9 +751,12 @@ option `gnorb-gnus-hint-relevant-article' is non-nil." (if (not (memq (car (gnus-find-method-for-group gnus-newsgroup-name)) '(nnvirtual nnir))) - (if (gnorb-find-tracked-headings header) - gnorb-gnus-summary-mark - " ") + (cond ((gnus-registry-get-id-key + (mail-header-message-id header) 'gnorb-ids) + gnorb-gnus-summary-tracked-mark) + ((gnorb-find-tracked-headings header) + gnorb-gnus-summary-mark) + (t " ")) " ")) (fset (intern (concat "gnus-user-format-function-" @@ -666,11 +768,11 @@ option `gnorb-gnus-hint-relevant-article' is non-nil." (defun gnorb-gnus-view () "Display the first relevant TODO heading for the message under point" (interactive) - (let ((headers (gnus-data-header + (let* ((headers (gnus-data-header (gnus-data-find (gnus-summary-article-number)))) - (tracked-headings - (gnorb-find-tracked-headings headers))) + (tracked-headings + (gnorb-find-tracked-headings headers))) (when tracked-headings (setq gnorb-window-conf (current-window-configuration)) (move-marker gnorb-return-marker (point)) diff --git a/gnorb-org.el b/gnorb-org.el index 846ded9..d54e9ba 100644 --- a/gnorb-org.el +++ b/gnorb-org.el @@ -1,6 +1,6 @@ ;;; gnorb-org.el --- The Org-centric functions of gnorb -;; Copyright (C) 2014 Eric Abrahamsen +;; Copyright (C) 2014 Free Software Foundation, Inc. ;; Author: Eric Abrahamsen <e...@ericabrahamsen.net> ;; Keywords: @@ -24,10 +24,8 @@ ;;; Code: -(eval-when-compile - (require 'cl)) - (require 'gnorb-utils) +(require 'cl-lib) (defgroup gnorb-org nil "The Org bits of Gnorb." @@ -45,9 +43,8 @@ ("take note" . note) ("don't associate" . no-associate) ("only associate" . associate) -; ("capture to child" . cap-child) -; ("capture to sibling" . cap-sib) -) + ("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 @@ -58,8 +55,8 @@ 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. +capture to child: Associate this message with a new child heading. +capture to sibling: 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 @@ -179,9 +176,10 @@ we came from." (cond ((eq gnorb-org-mail-scan-scope 'all) strings) ((numberp gnorb-org-mail-scan-scope) - (delq nil - (cl-subseq - strings 0 (1+ gnorb-org-mail-scan-scope)))) + (cl-subseq + strings 0 (min + (length strings) + (1+ gnorb-org-mail-scan-scope)))) ;; We could provide more options here. 'tree vs ;; 'subtree, for instance. (t @@ -246,6 +244,8 @@ See the docstring of `gnorb-org-handle-mail' for details." (msg-id-link `(:gnus ,(list msg-id-link)))))) +(defvar message-beginning-of-line) + (defun gnorb-org-setup-message (&optional messages mails from cc bcc attachments text ids) "Common message setup routine for other gnorb-org commands. @@ -271,10 +271,14 @@ headings." (when messages (insert ", ")) (insert (mapconcat 'identity mails ", "))) - ;; Return us after message is sent. - (add-to-list 'message-exit-actions - 'gnorb-org-restore-after-send t) - ;; Set headers from MAIL_* properties (from, cc, and bcc). + ;; Commenting this out because + ;; `gnorb-gnus-check-outgoing-headers' is set unconditionally in the + ;; `message-send-hook, so this should be redundant. Also, we've + ;; switched to using message-send-actions. + + ;; (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)))) @@ -338,6 +342,8 @@ current heading, or the heading indicated by optional argument ID." (org-attach-file-list attach-dir)))) files))) +(defvar message-mode-hook) + ;;;###autoload (defun gnorb-org-handle-mail (&optional arg text file) "Handle current headline as a mail TODO. @@ -475,6 +481,8 @@ respective (usual) file extensions. Ugly way to do it, but what the hey..." :group 'gnorb-org) +(defvar org-export-show-temporary-export-buffer) + ;;;###autoload (defun gnorb-org-email-subtree (&optional arg) "Call on a subtree to export it either to a text string or a file, @@ -517,7 +525,7 @@ default set of parameters." (apply 'org-export-to-file `(,backend-symbol ,(org-export-output-file-name - (second (assoc backend-symbol gnorb-org-export-extensions)) + (cl-second (assoc backend-symbol gnorb-org-export-extensions)) t gnorb-tmp-dir) ,@opts ,gnorb-org-email-subtree-file-parameters)))) @@ -609,7 +617,9 @@ search." (let ((rec-tags (bbdb-record-xfield r gnorb-bbdb-org-tag-field))) (and rec-tags - (let ((tags-list (org-split-string rec-tags ":")) + (let ((tags-list (if (stringp rec-tags) + (org-split-string rec-tags ":") + rec-tags)) (case-fold-search t) (org-trust-scanner-tags t)) (eval tag-clause))))) @@ -641,14 +651,17 @@ search." ;;; Groups from the gnorb gnus server backend ;;;###autoload -(defun gnorb-org-view () +(defun gnorb-org-view (arg) "Search the subtree at point for links to gnus messages, and -then show them in an ephemeral group, in gnus. +then show them in an ephemeral group, in Gnus. + +With a prefix arg, create a search group that will persist across +Gnus sessions, and can be refreshed. 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) + (interactive "P") (require 'gnorb-gnus) (setq gnorb-window-conf (current-window-configuration)) (move-marker gnorb-return-marker (point)) @@ -667,7 +680,10 @@ your gnus select methods." (org-back-to-heading) (setq id (concat "id+" (org-id-get-create))) (gnorb-gnus-search-messages - id + id arg + (replace-regexp-in-string + org-bracket-link-regexp "\\3" + (nth 4 (org-heading-components))) `(when (and (window-configuration-p gnorb-window-conf) gnorb-return-marker) (set-window-configuration gnorb-window-conf) diff --git a/gnorb-registry.el b/gnorb-registry.el index f70531c..bcd5adc 100644 --- a/gnorb-registry.el +++ b/gnorb-registry.el @@ -1,6 +1,6 @@ ;;; gnorb-registry.el --- Registry implementation for Gnorb -;; This file is in the public domain. +;; Copyright (C) 2014 Free Software Foundation, Inc. ;; Author: Eric Abrahamsen <e...@ericabrahamsen.net.> @@ -49,6 +49,8 @@ ;;; Code: (require 'gnus-registry) +(require 'gnorb-utils) +(require 'cl-lib) (defgroup gnorb-registry nil "Gnorb's use of the Gnus registry." @@ -84,15 +86,11 @@ to the message's registry entry, under the 'gnorb-ids key." (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)))) + (gnorb-bracket-message-id + (plist-get org-store-link-plist :message-id))) + (org-id (org-id-get-create))) + (plist-put org-capture-plist :gnorb-id org-id) + (gnorb-registry-make-entry msg-id nil nil org-id nil)))) (defun gnorb-registry-capture-abort-cleanup () @@ -148,25 +146,113 @@ the MSG-ID." (gnus-registry-set-id-key msg-id 'gnorb-ids (remove org-id org-ids))))) -(defun gnorb-delete-all-assocations (org-id) +(defun gnorb-delete-all-associations (org-id) "Delete all message associations for an Org heading. The heading is identified by ORG-ID. This is suitable for use after an Org heading is deleted, for instance." - (let ((assoc-msgs (gnorb-registry-org-id-search org-id))) + (let ((assoc-msgs (gnorb-registry-org-id-search org-id)) + (gnorb-id-tracker + (registry-lookup-secondary gnus-registry-db 'gnorb-ids))) (mapcar (lambda (msg-id) (let ((org-ids (gnus-registry-get-id-key msg-id 'gnorb-ids))) (gnus-registry-set-id-key msg-id 'gnorb-ids (remove org-id org-ids)))) - assoc-msgs))) + assoc-msgs) + (remhash org-id gnorb-id-tracker))) + +(defun gnorb-flush-dead-associations (&optional clean-archived) + "Clean the registry of associations with nonexistent headings. + +Gnus will not prune registry entries that appear to be associated +with an Org heading. If your registry is limited to a very small +size, you may end up with a full registry. Use this function to +remove dead associations, and free up more entries for possible +pruning. + +By default, associations are considered \"live\" if the Org +heading exists in an Org file or in an Org archive file. When +optional CLEAN_ARCHIVED is non-nil, delete associations from +archived headings as well." + (interactive "P") + (let ((gnorb-id-tracker + (registry-lookup-secondary gnus-registry-db 'gnorb-ids)) + (deleted-count 0)) + (require 'org-id) + (maphash + (lambda (k _) + (let ((file (org-id-find-id-file k))) + (when (or (not file) + (and clean-archived + (string-match-p "org_archive$" file))) + (gnorb-delete-all-associations k) + (incf deleted-count)))) + gnorb-id-tracker) + (message "Deleted %d invalid associations" + deleted-count))) (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-tracked-messages () + "Return all message-ids that have non-empty 'gnorb-ids keys." + (registry-search gnus-registry-db :regex `((gnorb-ids ".+")))) + +(defun gnorb-registry-tracked-headings () + "Return all Org heading ids that are associated with messages." + (hash-table-keys + (registry-lookup-secondary gnus-registry-db 'gnorb-ids))) + +(defun gnorb-report-tracking-usage () + "Pop up a temporary window reporting on Gnorb usage of the Gnus +registry to track message/heading associations. Reports the +number of tracked messages, the number of tracked headings, and how much of the registry is occupied." + (interactive) + (progn + (pop-to-buffer + (get-buffer-create "*Gnorb Usage*") + '(nil . ((window-height . 10)))) + (gnorb-refresh-usage-status) + (special-mode) + (setq revert-buffer-function #'gnorb-refresh-usage-status) + (local-set-key (kbd "d") (lambda () + (interactive) + (progn + (gnorb-flush-dead-associations) + (gnorb-refresh-usage-status)))) + (local-set-key (kbd "D") (lambda () + (interactive) + (progn + (gnorb-flush-dead-associations t) + (gnorb-refresh-usage-status)))))) + +(defun gnorb-refresh-usage-status (&optional ignore-auto noconfirm) + "Clear and re-format the *Gnorb Usage* buffer." + (let ((messages (length (gnorb-registry-tracked-messages))) + (headings (length (gnorb-registry-tracked-headings))) + (reg-size (registry-size gnus-registry-db)) + (reg-max-size (oref gnus-registry-db max-size))) + (with-current-buffer "*Gnorb Usage*" + (let ((inhibit-read-only t)) + (erase-buffer) + (insert + (format + "Tracking %d Gnus messages associated with %d Org headings." + messages headings)) + (insert "\n\n") + (insert + (format + "Occupying %.2f%% (%d/%d) of the registry (max %d)." + (* 100 (/ (float messages) reg-size)) + messages reg-size reg-max-size)) + (insert "\n\n") + (insert "Press 'd' to delete associations for non-existent Org headings.\n") + (insert "Press 'D' to delete associations for both non-existent and archived Org headings."))))) + (defun gnorb-registry-transition-from-props (arg) "Helper function for transitioning the old tracking system to the new. @@ -202,8 +288,8 @@ your Org files." 'gnus)) (dolist (l (plist-get links :gnus)) (gnorb-registry-make-entry - (second (split-string l "#")) nil nil - id (first (split-string l "#")))) + (cl-second (split-string l "#")) nil nil + id (cl-first (split-string l "#")))) (dolist (p props) (setq id ) (gnorb-registry-make-entry p nil nil id nil) @@ -212,7 +298,7 @@ your Org files." ;; it. (unless (gnus-registry-get-id-key p 'group) (gnorb-msg-id-to-group p)) - (incf count))))) + (cl-incf count))))) gnorb-org-find-candidates-match 'agenda 'archive 'comment) (message "Collecting all relevant Org headings, this could take a while... done") diff --git a/gnorb-utils.el b/gnorb-utils.el index 81efa32..d7f5e86 100644 --- a/gnorb-utils.el +++ b/gnorb-utils.el @@ -1,6 +1,6 @@ ;;; gnorb-utils.el --- Common utilities for all gnorb stuff. -;; Copyright (C) 2014 Eric Abrahamsen +;; Copyright (C) 2014 Free Software Foundation, Inc. ;; Author: Eric Abrahamsen <e...@ericabrahamsen.net> ;; Keywords: @@ -24,9 +24,9 @@ ;;; Code: -(eval-when-compile - (require 'cl)) +(require 'cl-lib) +(require 'mailcap) (mailcap-parse-mimetypes) (defgroup gnorb nil @@ -74,6 +74,11 @@ are sent, or Org headings triggered.") "Return point here after various actions, to be used together with `gnorb-window-conf'.") +(defvar gnorb-trigger-capture-location nil + "Marker pointing at the location where we want to place capture + templates, for the capture-to-child and capture-to-sibling + trigger actions.") + (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 @@ -213,38 +218,32 @@ window." 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 + (let* ((agenda-p (eq major-mode 'org-agenda-mode)) + (root-marker + (cond (agenda-p (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))))) + (org-get-at-bol 'org-hd-marker))) + ((derived-mode-p 'org-mode) + (save-excursion + (org-back-to-heading) + (point-marker))) + (id + (save-excursion + (org-id-goto id) + (org-back-to-heading) + (point-marker))))) + (id (or id + (org-with-point-at root-marker + (org-id-get-create)))) + (action (cdr (assoc + (org-completing-read + (format + "Trigger action on %s: " + (gnorb-pretty-outline id)) + gnorb-org-trigger-actions nil t) + gnorb-org-trigger-actions)))) (unless agenda-p (org-reveal)) - ;; Query about attaching email attachments. No matter what - ;; happens, clear `gnorb-gnus-capture-attachments'. - (unwind-protect - (org-with-point-at root-marker - (map-y-or-n-p - (lambda (a) - (format "Attach %s to heading? " - (file-name-nondirectory a))) - (lambda (a) (org-attach-attach a nil 'mv)) - gnorb-gnus-capture-attachments - '("file" "files" "attach"))) - (setq gnorb-gnus-capture-attachments nil)) (cl-labels ((make-entry (id) @@ -255,28 +254,81 @@ agenda. Then let the user choose an action from the value of 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) + (if (fboundp action) + (org-with-point-at root-marker + (make-entry (org-id-get-create)) + (funcall action gnorb-gnus-message-info)) + (cl-case action + (note + (org-with-point-at root-marker + (make-entry (org-id-get-create)) + (call-interactively 'org-add-note))) + (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)) - (funcall action gnorb-gnus-message-info))))))) + (call-interactively 'org-todo)))) + (no-associate + nil) + (associate + (org-with-point-at root-marker + (make-entry (org-id-get-create)))) + ;; We're going to capture a new heading + ((cap-child cap-sib) + (org-with-point-at root-marker + (setq gnorb-trigger-capture-location (point-marker))) + (let ((entry + ;; Pick a template. + (copy-sequence (org-capture-select-template)))) + ;; Do surgery on that template so that it finds its + ;; location using our function. + (setf (nth 3 entry) + `(function + ,(if (eq action 'cap-child) + #'gnorb-trigger-capture-child + #'gnorb-trigger-capture-sibling))) + ;; This will likely fail horribly for capture templates + ;; that aren't entries or list items. + (let ((org-capture-entry entry)) + ;; When org-capture-entry is let-bound, the capture + ;; process will use that template instead of + ;; prompting the user. Also, `gnorb-registry-capture' + ;; will take care of making the registry entry for us. + (call-interactively 'org-capture))))))) + ;; Lastly, query about attaching email attachments. No matter what + ;; happens, clear `gnorb-gnus-capture-attachments'. + (unwind-protect + (org-with-point-at + (if (memq action '(cap-child cap-sib)) + (point) + root-marker) + (map-y-or-n-p + (lambda (a) + (format "Attach %s to heading? " + (file-name-nondirectory a))) + (lambda (a) + (with-demoted-errors + (org-attach-attach a nil 'mv))) + gnorb-gnus-capture-attachments + '("file" "files" "attach"))) + (setq gnorb-gnus-capture-attachments nil)))) + +(defun gnorb-trigger-capture-child () + ;; The capture process creates a child by default + (org-goto-marker-or-bmk gnorb-trigger-capture-location) + (org-back-to-heading)) + +(defun gnorb-trigger-capture-sibling () + ;; This only works if we're not trying to create a sibling for a + ;; top-level heading, there appears to be no way to do that. But in + ;; that case this trigger action isn't really necessary, just + ;; handle it with a regular capture. + (org-goto-marker-or-bmk gnorb-trigger-capture-location) + (org-up-heading-safe)) (defun gnorb-pretty-outline (id &optional kw) "Return pretty outline path of the Org heading indicated by ID. @@ -364,8 +416,7 @@ methods?" (ignore-errors (gnus-request-head msg-id server-group))) (throw 'found server-group)))) - (when (featurep 'notmuch) - nil)))) + nil))) (defun gnorb-collect-ids (&optional id) "Collect all Org IDs for a subtree. @@ -386,6 +437,16 @@ child headings." ;; Common functions for extracting references and relevant headings ;; from the message under point. For use in gnorb-gnus.el functions. +(defun gnorb-get-real-group-name (group art-no) + "Find the original group name of a message in a virtual or nnir +group." + (cl-case (car (gnus-find-method-for-group group)) + (nnvirtual + (setq group (car (nnvirtual-map-article art-no)))) + (nnir + (setq group (nnir-article-group art-no)))) + group) + (defun gnorb-find-tracked-headings (headers &optional include-zombies) "Check HEADERS for message references and return relevant heading IDs. diff --git a/gnorb.el b/gnorb.el index 6238623..cb7d908 100644 --- a/gnorb.el +++ b/gnorb.el @@ -1,8 +1,9 @@ ;;; gnorb.el --- Glue code between Gnus, Org, and BBDB -;; Copyright (C) 2014 Eric Abrahamsen +;; Copyright (C) 2014 Free Software Foundation, Inc. ;; Version: 1.0.1 +;; Package-Requires: ((cl-lib "0.5")) ;; Maintainer: Eric Abrahamsen <e...@ericabrahamsen.net> @@ -31,13 +32,13 @@ ;;; Code: (with-eval-after-load 'gnus - (require 'nngnorb) - (require 'gnorb-gnus) - (require 'gnorb-registry)) + (require 'nngnorb) + (require 'gnorb-gnus) + (require 'gnorb-registry)) (with-eval-after-load 'bbdb (require 'gnorb-bbdb)) (with-eval-after-load 'org - (require 'gnorb-org)) + (require 'gnorb-org)) (provide 'gnorb) diff --git a/gnorb.info b/gnorb.info index 08085e1..52ca072 100644 --- a/gnorb.info +++ b/gnorb.info @@ -30,11 +30,13 @@ Gnorb Manual Email Tracking +* Basic Usage:: * Email-Related Commands:: * Trigger Actions:: * Viewing Tracked Messages in *Summary* Buffers:: * Hinting in Gnus:: * Message Attachments:: +* Registry Usage:: * Likely Workflow:: Misc BBDB @@ -138,29 +140,56 @@ 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. +can open Gnus *Summary* buffers holding all the messages associated with +an Org subtree, and reply from there – these groups can be made +persistent, if you like. 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: +* Basic Usage:: * Email-Related Commands:: * Trigger Actions:: * Viewing Tracked Messages in *Summary* Buffers:: * Hinting in Gnus:: * Message Attachments:: +* Registry Usage:: * Likely Workflow:: -File: gnorb.info, Node: Email-Related Commands, Next: Trigger Actions, Up: Email Tracking +File: gnorb.info, Node: Basic Usage, Next: Email-Related Commands, Up: Email Tracking + +4.1 Basic Usage +=============== + +The following sections might be a bit confusing to read if you haven’t +actually tried using Gnorb. If you don’t want to dive in all the way +just yet, you can just dabble your toes. First set up email tracking as +specified in *note Setup: Setup, then do the following: + + 1. Add “%ug” somewhere appropriate in your ‘gnus-summary-line-format’ + variable. + 2. If you don’t use a local archive method, add your sent message + groups to ‘gnorb-gnus-sent-groups’ (see the docstring). + 3. Use Org capture from Gnus summary buffers to create reminders for + emails you need to reply to. + 4. Reply to those emails by pressing “C-c t” on the TODO heading in + either the Agenda, or in regular Org files. + 5. If you ever get confused about what’s associated with an Org + heading, press “C-c v” on the heading (works in either the Agenda, + or regular Org files). + + That should be enough to get started. -4.1 Email-Related Commands + +File: gnorb.info, Node: Email-Related Commands, Next: Trigger Actions, Prev: Basic Usage, Up: Email Tracking + +4.2 Email-Related Commands ========================== Email tracking starts in one of three ways: @@ -178,11 +207,14 @@ Email tracking starts in one of three ways: 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 + messages to reply to (or you call the function with a single 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 + Calling the function with a double prefix arg will ignore all + associated messages and links, and compose a blank message. + + Once sent, the 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. @@ -190,7 +222,7 @@ Email tracking starts in one of three ways: ‘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. + to the message, respectively. 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. @@ -218,39 +250,54 @@ Email tracking starts in one of three ways: 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. +Message mode. + + An additional convenience command is available for use in Gnus +summary buffers: ‘gnorb-gnus-quick-reply’. If you don’t want to go +through the whole round trip of triggering an action and then starting a +new reply, call this command on an incoming message to associate it with +a heading, start a reply, and associate your reply with the same +heading. File: gnorb.info, Node: Trigger Actions, Next: Viewing Tracked Messages in *Summary* Buffers, Prev: Email-Related Commands, Up: Email Tracking -4.2 Trigger Actions +4.3 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 +six 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. +else, capturing a new Org heading as a sibling to the tracked heading, +capturing a new Org heading as a child, and lastly, doing nothing at +all. - More actions will be added in the future; it’s also possible to + More actions may be added in the future; it’s also possible to rearrange or delete existing actions, and add your own: see the docstring of ‘gnorb-org-trigger-actions’. File: gnorb.info, Node: Viewing Tracked Messages in *Summary* Buffers, Next: Hinting in Gnus, Prev: Trigger Actions, Up: Email Tracking -4.3 Viewing Tracked Messages in *Summary* Buffers +4.4 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. +Call ‘gnorb-org-view’ on an Org heading to open an nnir summary buffer +showing all the messages associated with that heading and child headings +(this requires you to have added an nngnorb server to your Gnus +backends). A minor mode is 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. + + If you call ‘gnorb-org-view’ with a prefix argument, the search group +will be made persistent across Gnus sessions. You can re-run the search +and update the group contents by hitting “M-g” on the group in the Gnus +*Group* buffer. As a bonus, it’s possible to go into Gnus’ *Server* buffer, find the line specifying your nngnorb server, and hit “G” (aka @@ -263,7 +310,7 @@ linked messages. This is dog-slow at the moment; it will get faster. 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 +4.5 Hinting in Gnus =================== When you receive new mails that might be relevant to existing Org TODOs, @@ -273,18 +320,19 @@ 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 -“¡”. + 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 may be 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 “&” for messages that are already tracked, and “¡” for +messages that may be relevant. -File: gnorb.info, Node: Message Attachments, Next: Likely Workflow, Prev: Hinting in Gnus, Up: Email Tracking +File: gnorb.info, Node: Message Attachments, Next: Registry Usage, Prev: Hinting in Gnus, Up: Email Tracking -4.5 Message Attachments +4.6 Message Attachments ======================= Gnorb simplifies the handling of attachments that you receive in emails. @@ -309,9 +357,21 @@ attach the files in the heading’s org-attach directory to the outgoing message. -File: gnorb.info, Node: Likely Workflow, Prev: Message Attachments, Up: Email Tracking +File: gnorb.info, Node: Registry Usage, Next: Likely Workflow, Prev: Message Attachments, Up: Email Tracking + +4.7 Registry Usage +================== + +You can see how many associations you’ve got stored in the registry by +calling ‘gnorb-report-tracking-usage’. This will pop up a buffer +showing how much of the registry you’re using, and offering keybindings +for ‘gnorb-flush-dead-associations’, to help Gnorb clean up after +itself. + + +File: gnorb.info, Node: Likely Workflow, Prev: Registry Usage, Up: Email Tracking -4.6 Likely Workflow +4.8 Likely Workflow =================== You receive an email from Jimmy, who wants to rent a room in your house. @@ -635,9 +695,13 @@ File: gnorb.info, Node: User Optionsxx, Prev: Viewing Org headlines relevant t 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 “¡”. + The mark used to indicate potentially relevant messages in the + Summary buffer, when ‘gnorb-gnus-summary-mark-format-letter’ is + present in the format line. Defaults to “¡”. +‘`gnorb-gnus-summary-tracked-mark'’ + The mark used to indicate already-tracked messages in the Summary + buffer, when ‘gnorb-gnus-summary-mark-format-letter’ is present in + the format line. Defaults to “&”. File: gnorb.info, Node: Suggested Keybindings, Prev: Misc Gnus, Up: Top @@ -662,8 +726,9 @@ File: gnorb.info, Node: Suggested Keybindings, Prev: Misc Gnus, Up: Top (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))))) + '(progn (org-defkey org-agenda-mode-map (kbd "C-c t") 'gnorb-org-handle-mail) + (org-defkey org-agenda-mode-map (kbd "C-c v") 'gnorb-org-popup-bbdb) + (org-defkey org-agenda-mode-map (kbd "V") 'gnorb-org-view))))) (eval-after-load "gnorb-gnus" '(progn @@ -692,31 +757,33 @@ File: gnorb.info, Node: Suggested Keybindings, Prev: Misc Gnus, Up: Top Tag Table: Node: Top194 -Node: Introduction1009 -Node: Installation2118 -Node: Setup2532 -Node: Email Tracking3899 -Node: Email-Related Commands5430 -Node: Trigger Actions8440 -Node: Viewing Tracked Messages in *Summary* Buffers9289 -Node: Hinting in Gnus10523 -Node: Message Attachments11531 -Node: Likely Workflow12713 -Node: Restoring Window Layout15518 -Node: Recent Mails From BBDB Contacts15882 -Node: BBDB posting styles16878 -Node: BBDB Org tagging17794 -Node: Misc BBDB18540 -Node: Searching for messages from BBDB contacts18753 -Node: Citing BBDB contacts19199 -Node: User Options19520 -Node: Misc Org21059 -Node: Inserting BBDB links21234 -Node: User Optionsx21489 -Node: Misc Gnus24226 -Node: Viewing Org headlines relevant to a message24439 -Node: User Optionsxx24754 -Node: Suggested Keybindings27518 +Node: Introduction1044 +Node: Installation2153 +Node: Setup2567 +Node: Email Tracking3934 +Node: Basic Usage5544 +Node: Email-Related Commands6617 +Node: Trigger Actions10112 +Node: Viewing Tracked Messages in *Summary* Buffers11064 +Node: Hinting in Gnus12551 +Node: Message Attachments13647 +Node: Registry Usage14828 +Node: Likely Workflow15279 +Node: Restoring Window Layout18079 +Node: Recent Mails From BBDB Contacts18443 +Node: BBDB posting styles19439 +Node: BBDB Org tagging20355 +Node: Misc BBDB21101 +Node: Searching for messages from BBDB contacts21314 +Node: Citing BBDB contacts21760 +Node: User Options22081 +Node: Misc Org23620 +Node: Inserting BBDB links23795 +Node: User Optionsx24050 +Node: Misc Gnus26787 +Node: Viewing Org headlines relevant to a message27000 +Node: User Optionsxx27315 +Node: Suggested Keybindings30322 End Tag Table diff --git a/gnorb.org b/gnorb.org index e19422d..58b0365 100644 --- a/gnorb.org +++ b/gnorb.org @@ -35,6 +35,9 @@ 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 +:PROPERTIES: +:ID: 9da59609-bb3c-4970-88f6-bddca18d2ad4 +:END: Loading "gnorb" will make the basic functions available. Using Gnorb for email tracking takes a bit more setup, however: @@ -68,16 +71,36 @@ 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. +You can open Gnus *Summary* buffers holding all the messages +associated with an Org subtree, and reply from there -- these groups +can be made persistent, if you like. 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. +** Basic Usage +The following sections might be a bit confusing to read if you haven't +actually tried using Gnorb. If you don't want to dive in all the way +just yet, you can just dabble your toes. First set up email tracking +as specified in [[id:9da59609-bb3c-4970-88f6-bddca18d2ad4][Setup]], then do the following: + +1. Add "%ug" somewhere appropriate in your `gnus-summary-line-format' + variable. +2. If you don't use a local archive method, add your sent message + groups to `gnorb-gnus-sent-groups' (see the docstring). +3. Use Org capture from Gnus summary buffers to create reminders for + emails you need to reply to. +4. Reply to those emails by pressing "C-c t" on the TODO heading in + either the Agenda, or in regular Org files. +5. If you ever get confused about what's associated with an Org + heading, press "C-c v" on the heading (works in either the Agenda, + or regular Org files). + +That should be enough to get started. ** Email-Related Commands Email tracking starts in one of three ways: @@ -94,11 +117,14 @@ 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 + reply to (or you call the function with a single prefix arg), Gnorb will look for mailto: or bbdb: links in the heading, and compose a new message to them. + + Calling the function with a double prefix arg will ignore all + associated messages and links, and compose a blank message. - The sent message will be associated with the Org heading, and + Once sent, the 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. @@ -106,7 +132,7 @@ There are three main email-related commands: `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. + to the message, respectively. 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. @@ -134,31 +160,45 @@ There are three main email-related commands: 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. +Message mode. + +An additional convenience command is available for use in Gnus summary +buffers: `gnorb-gnus-quick-reply'. If you don't want to go through the +whole round trip of triggering an action and then starting a new +reply, call this command on an incoming message to associate it with a +heading, start a reply, and associate your reply with the same +heading. ** 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 +there are six 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. +doing nothing else, capturing a new Org heading as a sibling to the +tracked heading, capturing a new Org heading as a child, and lastly, +doing nothing at all. -More actions will be added in the future; it's also possible to +More actions may 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 +Call `gnorb-org-view' on an Org heading to open an nnir summary buffer +showing all the messages associated with that heading and child +headings (this requires you to have added an nngnorb server to your +Gnus backends). A minor mode is 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. +If you call `gnorb-org-view' with a prefix argument, the search group +will be made persistent across Gnus sessions. You can re-run the +search and update the group contents by hitting "M-g" on the group in +the Gnus *Group* buffer. + 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 @@ -179,11 +219,12 @@ 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 "¡". +part of your `gnus-summary-line-format', articles that may be 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 "&" for messages that are already tracked, and "¡" for +messages that may be relevant. ** Message Attachments :PROPERTIES: :END: @@ -207,6 +248,12 @@ 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. +** Registry Usage +You can see how many associations you've got stored in the registry by +calling `gnorb-report-tracking-usage'. This will pop up a buffer +showing how much of the registry you're using, and offering +keybindings for `gnorb-flush-dead-associations', to help Gnorb clean +up after itself. ** 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. @@ -437,10 +484,14 @@ heading to jump to that heading. 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' :: The mark used to indicate potentially + relevant messages in the Summary buffer, when `gnorb-gnus-summary-mark-format-letter' is present in the format line. Defaults to "¡". +- `gnorb-gnus-summary-tracked-mark' :: The mark used to indicate + already-tracked 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 @@ -463,8 +514,9 @@ heading to jump to that heading. (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))))) + '(progn (org-defkey org-agenda-mode-map (kbd "C-c t") 'gnorb-org-handle-mail) + (org-defkey org-agenda-mode-map (kbd "C-c v") 'gnorb-org-popup-bbdb) + (org-defkey org-agenda-mode-map (kbd "V") 'gnorb-org-view))))) (eval-after-load "gnorb-gnus" '(progn diff --git a/gnorb.texi b/gnorb.texi index f1fccc5..cdd7576 100644 --- a/gnorb.texi +++ b/gnorb.texi @@ -42,11 +42,13 @@ Email Tracking +* Basic Usage:: * Email-Related Commands:: * Trigger Actions:: * Viewing Tracked Messages in *Summary* Buffers:: * Hinting in Gnus:: * Message Attachments:: +* Registry Usage:: * Likely Workflow:: Misc BBDB @@ -146,25 +148,57 @@ 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. +You can open Gnus *Summary* buffers holding all the messages +associated with an Org subtree, and reply from there -- these groups +can be made persistent, if you like. 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 +* Basic Usage:: * Email-Related Commands:: * Trigger Actions:: * Viewing Tracked Messages in *Summary* Buffers:: * Hinting in Gnus:: * Message Attachments:: +* Registry Usage:: * Likely Workflow:: @end menu +@node Basic Usage +@section Basic Usage + +The following sections might be a bit confusing to read if you haven't +actually tried using Gnorb. If you don't want to dive in all the way +just yet, you can just dabble your toes. First set up email tracking +as specified in @ref{Setup,Setup}, then do the following: + +@enumerate +@item +Add ``%ug'' somewhere appropriate in your `gnus-summary-line-format' +variable. +@item +If you don't use a local archive method, add your sent message +groups to `gnorb-gnus-sent-groups' (see the docstring). +@item +Use Org capture from Gnus summary buffers to create reminders for +emails you need to reply to. +@item +Reply to those emails by pressing ``C-c t'' on the TODO heading in +either the Agenda, or in regular Org files. +@item +If you ever get confused about what's associated with an Org +heading, press ``C-c v'' on the heading (works in either the Agenda, +or regular Org files). +@end enumerate + +That should be enough to get started. + @node Email-Related Commands @section Email-Related Commands @@ -190,11 +224,14 @@ There are three main email-related commands: `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 +reply to (or you call the function with a single 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 +Calling the function with a double prefix arg will ignore all +associated messages and links, and compose a blank message. + +Once sent, the 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. @@ -202,7 +239,7 @@ action on it. `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. +to the message, respectively. @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 @@ -233,7 +270,14 @@ 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. +Message mode. + +An additional convenience command is available for use in Gnus summary +buffers: `gnorb-gnus-quick-reply'. If you don't want to go through the +whole round trip of triggering an action and then starting a new +reply, call this command on an incoming message to associate it with a +heading, start a reply, and associate your reply with the same +heading. @node Trigger Actions @section Trigger Actions @@ -241,27 +285,34 @@ Message mode, respectively. 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 +there are six 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. +doing nothing else, capturing a new Org heading as a sibling to the +tracked heading, capturing a new Org heading as a child, and lastly, +doing nothing at all. -More actions will be added in the future; it's also possible to +More actions may be added in the future; it's also possible to rearrange or delete existing actions, and add your own: see the docstring of `gnorb-org-trigger-actions'. @node Viewing Tracked Messages in *Summary* Buffers @section Viewing Tracked Messages in *Summary* Buffers -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 +Call `gnorb-org-view' on an Org heading to open an nnir summary buffer +showing all the messages associated with that heading and child +headings (this requires you to have added an nngnorb server to your +Gnus backends). A minor mode is 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. +If you call `gnorb-org-view' with a prefix argument, the search group +will be made persistent across Gnus sessions. You can re-run the +search and update the group contents by hitting ``M-g'' on the group in +the Gnus *Group* buffer. + 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 @@ -281,12 +332,13 @@ 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 ``¡''. +the escape indicated by `gnorb-gnus-summary-mark-format-letter`` as +part of your `gnus-summary-line-format', articles that may be 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 ``&'' for messages that are already tracked, and ``¡'' for +messages that may be relevant. @node Message Attachments @section Message Attachments @@ -312,6 +364,15 @@ 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 Registry Usage +@section Registry Usage + +You can see how many associations you've got stored in the registry by +calling `gnorb-report-tracking-usage'. This will pop up a buffer +showing how much of the registry you're using, and offering +keybindings for `gnorb-flush-dead-associations', to help Gnorb clean +up after itself. + @node Likely Workflow @section Likely Workflow @@ -618,10 +679,15 @@ 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 +The mark used to indicate potentially +relevant messages in the Summary buffer, when `gnorb-gnus-summary-mark-format-letter' is present in the format line. Defaults to ``¡''. +@item `gnorb-gnus-summary-tracked-mark' +The mark used to indicate +already-tracked 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 @@ -645,8 +711,9 @@ line. Defaults to ``¡''. (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))))) + '(progn (org-defkey org-agenda-mode-map (kbd "C-c t") 'gnorb-org-handle-mail) + (org-defkey org-agenda-mode-map (kbd "C-c v") 'gnorb-org-popup-bbdb) + (org-defkey org-agenda-mode-map (kbd "V") 'gnorb-org-view))))) (eval-after-load "gnorb-gnus" '(progn @@ -672,5 +739,8 @@ line. Defaults to ``¡''. (define-key message-mode-map (kbd "C-c t") 'gnorb-gnus-outgoing-do-todo))) @end lisp -@c Emacs 25.0.50.8 (Org mode 8.3beta) -@bye \ No newline at end of file +@bye +@c Local Variables: +@c mode: texinfo +@c TeX-master: t +@c End: diff --git a/nngnorb.el b/nngnorb.el index eb55ee3..9d03e14 100644 --- a/nngnorb.el +++ b/nngnorb.el @@ -1,6 +1,6 @@ ;;; nngnorb.el --- Gnorb backend for Gnus -;; This file is in the public domain. +;; Copyright (C) 2014 Free Software Foundation, Inc. ;; Author: Eric Abrahamsen <e...@ericabrahamsen.net.> @@ -52,7 +52,7 @@ (make-variable-buffer-local 'nngnorb-attachment-file-list) -(gnus-declare-backend "nngnorb" 'none) +(gnus-declare-backend "nngnorb" 'post-mail 'virtual) (add-to-list 'nnir-method-default-engines '(nngnorb . gnorb)) @@ -79,14 +79,14 @@ be scanned for gnus messages, and those messages displayed." ;; 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 + (save-window-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) + (when (and (equal "5.13" gnus-version-number) (version< emacs-version "24.4")) (setq q (car q))) (cond ((string-match "id\\+\\([[:alnum:]-]+\\)$" q) (with-demoted-errors "Error: %S" @@ -142,7 +142,7 @@ be scanned for gnus messages, and those messages displayed." (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))))) + (setq msg-ids (append (delq nil rel-msg-id) msg-ids))))) (when msg-ids (dolist (id msg-ids) (let ((link (gnorb-msg-id-to-link id))) @@ -278,8 +278,12 @@ continue to provide tracking of sent messages." (message-insert-header (intern gnorb-mail-header) org-id) - (add-to-list 'message-exit-actions - 'gnorb-org-restore-after-send t)) + ;; As with elsewhere, this should be redundant with + ;; `gnorb-gnus-check-outgoing-headers.' Even if not, it + ;; should be switched to use `message-send-actions' + ;; (add-to-list 'message-exit-actions + ;; 'gnorb-org-restore-after-send t) + ) (goto-char compose-marker)) (when attachments (map-y-or-n-p @@ -309,7 +313,7 @@ the message being included in this search." (gnus-summary-article-number))) (let* ((msg-id (gnus-fetch-original-field "message-id")) (org-ids (gnus-registry-get-id-key msg-id 'gnorb-ids)) - chosen) + chosen multiple-alist) (if org-ids (progn (if (= (length org-ids) 1) @@ -317,14 +321,18 @@ the message being included in this search." (progn (gnus-registry-set-id-key msg-id 'gnorb-ids nil) (setq chosen (car org-ids))) ;; Multiple associated TODOs, prompt to choose one. + (setq multiple-alist + (mapcar + (lambda (h) + (cons (gnorb-pretty-outline h) h)) + org-ids)) (setq chosen (cdr - (org-completing-read - "Choose a TODO to disassociate from: " - (mapcar - (lambda (h) - (cons (gnorb-pretty-outline h) h)) - org-ids)))) + (assoc + (org-completing-read + "Choose a TODO to disassociate from: " + multiple-alist) + multiple-alist))) (gnus-registry-set-id-key msg-id 'gnorb-ids (remove chosen org-ids))) (message "Message disassociated from %s"