1 ;;; gnorb-utils.el --- Common utilities for all gnorb stuff.
3 ;; Copyright (C) 2014 Eric Abrahamsen
5 ;; Author: Eric Abrahamsen <eric@ericabrahamsen.net>
8 ;; This program is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation, either version 3 of the License, or
11 ;; (at your option) any later version.
13 ;; This program is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;; GNU General Public License for more details.
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
36 (mailcap-parse-mimetypes)
39 "Glue code between Gnus, Org, and BBDB."
42 (make-obsolete-variable
43 'gnorb-trigger-todo-default
44 "This variable has been superseded by
45 `gnorb-org-trigger-actions'"
46 "September 8, 2014" 'set)
48 (defun gnorb-prompt-for-bbdb-record ()
49 "Prompt the user for a BBDB record."
50 (let ((recs (bbdb-records))
52 (while (> (length recs) 1)
55 (format "Filter records by regexp (%d remaining): "
57 (mapcar 'bbdb-record-name recs)))
58 (setq recs (bbdb-search recs name name name nil nil)))
61 (error "No matching records"))))
63 (defvar gnorb-tmp-dir (make-temp-file "emacs-gnorb" t)
64 "Temporary directory where attachments etc are saved.")
66 (defvar gnorb-message-org-ids nil
67 "List of Org heading IDs from the outgoing Gnus message, used
68 to mark mail TODOs as done once the message is sent."
69 ;; The send hook either populates this, or sets it to nil, depending
70 ;; on whether the message in question has an Org id header. Then
71 ;; `gnorb-org-restore-after-send' checks for it and acts
72 ;; appropriately, then sets it to nil.
75 (defvar gnorb-window-conf nil
76 "Save window configurations here, for restoration after mails
77 are sent, or Org headings triggered.")
79 (defvar gnorb-return-marker (make-marker)
80 "Return point here after various actions, to be used together
81 with `gnorb-window-conf'.")
83 (defcustom gnorb-mail-header "X-Org-ID"
84 "Name of the mail header used to store the ID of a related Org
85 heading. Only used locally: always stripped when the mail is
90 ;;; this is just ghastly, but the value of this var is single regexp
91 ;;; group containing various header names, and we want our value
92 ;;; inside that group.
93 (eval-after-load 'message
94 `(let ((ign-headers-list
95 (split-string message-ignored-mail-headers
97 (our-val (concat gnorb-mail-header "\\")))
98 (unless (member our-val ign-headers-list)
99 (setq ign-headers-list
100 `(,@(butlast ign-headers-list 1) ,our-val
101 ,@(last ign-headers-list 1)))
102 (setq message-ignored-mail-headers
104 'identity ign-headers-list "|")))))
106 (defun gnorb-restore-layout ()
107 "Restore window layout and value of point after a Gnorb command.
109 Some Gnorb commands change the window layout (ie `gnorb-org-view'
110 or incoming email triggering). This command restores the layout
111 to what it was. Bind it to a global key, or to local keys in Org
112 and Gnus and BBDB maps."
114 (when (window-configuration-p gnorb-window-conf)
115 (set-window-configuration gnorb-window-conf)
116 (when (buffer-live-p (marker-buffer gnorb-return-marker))
117 (goto-char gnorb-return-marker))))
119 (defun gnorb-trigger-todo-action (arg &optional id)
120 "Do the actual restore action. Two main things here. First: if
121 we were in the agenda when this was called, then keep us in the
122 agenda. Then let the user choose an action from the value of
123 `gnorb-org-trigger-actions'."
124 (let ((agenda-p (eq major-mode 'org-agenda-mode))
128 gnorb-org-trigger-actions nil t)
129 gnorb-org-trigger-actions)))
130 (root-marker (make-marker)))
131 ;; Place the marker for the relevant TODO heading.
135 (org-get-at-bol 'org-hd-marker))))
136 ((derived-mode-p 'org-mode)
137 (move-marker root-marker (point-at-bol)))
141 (move-marker root-marker (point-at-bol)))))
142 ;; Query about attaching email attachments.
143 (org-with-point-at root-marker
146 (format "Attach %s to heading? "
147 (file-name-nondirectory a)))
148 (lambda (a) (org-attach-attach a nil 'mv))
149 gnorb-gnus-capture-attachments
150 '("file" "files" "attach")))
151 (setq gnorb-gnus-capture-attachments nil)
155 (gnorb-registry-make-entry
156 (plist-get gnorb-gnus-message-info :msg-id)
157 (plist-get gnorb-gnus-message-info :from)
158 (plist-get gnorb-gnus-message-info :subject)
160 (plist-get gnorb-gnus-message-info :group))))
161 ;; Handle our action.
162 (cond ((eq action 'note)
163 (org-with-point-at root-marker
164 (make-entry (org-id-get-create))
165 (call-interactively 'org-add-note)))
169 (org-with-point-at root-marker
170 (make-entry (org-id-get-create)))
171 (call-interactively 'org-agenda-todo))
172 (org-with-point-at root-marker
173 (make-entry (org-id-get-create))
174 (call-interactively 'org-todo))))
175 ((eq action 'no-associate)
177 ((eq action 'associate)
178 (org-with-point-at root-marker
179 (make-entry (org-id-get-create))))
181 (org-with-point-at root-marker
182 (make-entry (org-id-get-create))
183 (funcall action gnorb-gnus-message-info)))))))
185 (defun gnorb-pretty-outline (id &optional kw)
186 "Return pretty outline path of the Org heading indicated by ID.
188 If the KW argument is true, add the TODO keyword into the path."
189 (org-with-point-at (org-id-find id t)
190 (let ((el (org-element-at-point)))
194 (org-element-property :todo-keyword el))
196 (org-format-outline-path
199 (file-name-nondirectory
201 (org-base-buffer (current-buffer)))))
202 (org-get-outline-path)
204 (replace-regexp-in-string
205 org-bracket-link-regexp
206 "\\3" (org-element-property :raw-value el)))))))))
208 (defun gnorb-scan-links (bound &rest types)
209 "Scan from point to BOUND looking for links of type in TYPES.
211 TYPES is a list of symbols, possible values include 'bbdb, 'mail,
213 ;; this function could be refactored somewhat -- lots of code
214 ;; repetition. It also should be a little faster for when we're
215 ;; scanning for gnus links only, that's a little slow. We should
216 ;; probably use a different regexp based on the value of TYPES.
218 ;; This function should also *not* be responsible for unescaping
219 ;; links -- we don't know what they're going to be used for, and
220 ;; unescaped is safer.
221 (unless (= (point) bound)
222 (let (addr gnus mail bbdb)
223 (while (re-search-forward org-any-link-re bound t)
224 (setq addr (or (match-string-no-properties 2)
225 (match-string-no-properties 0)))
227 ((and (memq 'gnus types)
228 (string-match "^<?gnus:" addr))
229 (push (substring addr (match-end 0)) gnus))
230 ((and (memq 'mail types)
231 (string-match "^<?mailto:" addr))
232 (push (substring addr (match-end 0)) mail))
233 ((and (memq 'bbdb types)
234 (string-match "^<?bbdb:" addr))
235 (push (substring addr (match-end 0)) bbdb))))
236 `(:gnus ,(reverse gnus) :mail ,(reverse mail) :bbdb ,(reverse bbdb)))))
238 (defun gnorb-msg-id-to-link (msg-id)
239 "Given a message id, try to create a full org link to the
241 (let ((server-group (gnorb-msg-id-to-group msg-id)))
243 (org-link-escape (concat server-group "#" msg-id)))))
245 (defun gnorb-msg-id-to-group (msg-id)
246 "Given a message id, try to find the group it's in.
248 So far we're checking the registry, then the groups in
249 `gnorb-gnus-sent-groups'. Use search engines? Other clever
251 (let (candidates server-group)
253 (when gnorb-tracking-enabled
254 ;; Make a big list of all the groups where this message might
257 (append (gnus-registry-get-id-key msg-id 'group)
258 gnorb-gnus-sent-groups))
259 (while (setq server-group (pop candidates))
260 (when (and (stringp server-group)
263 "\\(nnir\\|nnvirtual\\|UNKNOWN\\)"
266 (gnus-request-head msg-id server-group)))
267 (throw 'found server-group))))
268 (when (featurep 'notmuch)
271 (defun gnorb-collect-ids (&optional id)
272 "Collect all Org IDs for a subtree.
274 Starting with the heading under point (or the heading indicated
275 by the ID argument), collect its ID property, and the IDs of all
281 (org-narrow-to-subtree)
282 (org-element-map (org-element-parse-buffer)
285 (org-element-property :ID hl))))))
287 ;; Loading the registry
289 (defvar gnorb-tracking-enabled nil
290 "Internal flag indicating whether Gnorb is successfully plugged
291 into the registry or not.")
293 (defun gnorb-tracking-initialize ()
294 "Start using the Gnus registry to track correspondences between
295 Gnus messages and Org headings. This requires that the Gnus
296 registry be in use, and should be called after the call to
297 `gnus-registry-initialize'."
298 (require 'gnorb-registry)
302 (unless (gnus-registry-install-p)
303 (user-error "Gnorb tracking requires that the Gnus registry be installed."))
304 (add-to-list 'gnus-registry-extra-entries-precious 'gnorb-ids)
305 (add-to-list 'gnus-registry-track-extra 'gnorb-ids)
306 (add-hook 'org-capture-mode-hook 'gnorb-registry-capture)
307 (add-hook 'org-capture-prepare-finalize-hook 'gnorb-registry-capture-abort-cleanup)
308 (setq gnorb-tracking-enabled t))))
310 (provide 'gnorb-utils)
311 ;;; gnorb-utils.el ends here