]> code.delx.au - gnu-emacs-elpa/blob - packages/gnorb/gnorb-org.el
8ac25036781de9911bf52a9df81b055a589b42e5
[gnu-emacs-elpa] / packages / gnorb / gnorb-org.el
1 ;;; gnorb-org.el --- The Org-centric functions of gnorb
2
3 ;; Copyright (C) 2014 Free Software Foundation, Inc.
4
5 ;; Author: Eric Abrahamsen <eric@ericabrahamsen.net>
6 ;; Keywords:
7
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.
12
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.
17
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/>.
20
21 ;;; Commentary:
22
23 ;;
24
25 ;;; Code:
26
27 (eval-when-compile
28 (require 'cl))
29
30 (require 'gnorb-utils)
31 (require 'cl-lib)
32
33 (defgroup gnorb-org nil
34 "The Org bits of Gnorb."
35 :tag "Gnorb Org"
36 :group 'gnorb)
37
38 (defcustom gnorb-org-after-message-setup-hook nil
39 "Hook run in a message buffer after setting up the message from
40 `gnorb-org-handle-mail' or `gnorb-org-email-subtree'."
41 :group 'gnorb-org
42 :type 'hook)
43
44 (defcustom gnorb-org-trigger-actions
45 '(("todo state" . todo)
46 ("take note" . note)
47 ("don't associate" . no-associate)
48 ("only associate" . associate)
49 ; ("capture to child" . cap-child)
50 ; ("capture to sibling" . cap-sib)
51 )
52 "List of potential actions that can be taken on headings.
53
54 When triggering an Org heading after receiving or sending a
55 message, this option lists the possible actions to take. Built-in
56 actions include:
57
58 todo state: Associate the message, and change TODO state.
59 take note: Associate the message, and take a note.
60 don't associate: Do nothing at all, don't connect the message and TODO.
61 only associate: Associate the message with this heading, do nothing else.
62 capture to child: [not yet implemented] Associate this message with a new child heading.
63 capture to sibling: [not yet implemented] Associate this message with a new sibling heading.
64
65 You can reorder this list or remove items as suits your workflow.
66 The two \"capture\" options will use the value of
67 `gnorb-gnus-new-todo-capture-key' to find the appropriate
68 template.
69
70 You can also add custom actions to the list. Actions should be a
71 cons of a string tag and a symbol indicating a custom function.
72 This function will be called on the heading in question, and
73 passed a plist containing information about the message from
74 which we're triggering."
75 :group 'gnorb-org
76 :type 'list)
77
78 (defcustom gnorb-org-msg-id-key "GNORB_MSG_ID"
79 "The name of the org property used to store the Message-IDs
80 from relevant messages. This is no longer used, and will be
81 removed soon."
82 :group 'gnorb-org
83 :type 'string)
84
85 (defcustom gnorb-org-mail-scan-scope 2
86 "Number of paragraphs to scan for mail-related links.
87
88 When handling a TODO heading with `gnorb-org-handle-mail', Gnorb
89 will typically reply to the most recent message associated with
90 this heading. If there are no such messages, or message tracking
91 is disabled entirely, or `gnorb-org-handle-mail' has been called
92 with a prefix arg, the heading and body text of the subtree under
93 point will instead be scanned for gnus:, mailto:, and bbdb:
94 links. This option controls how many paragraphs of body text to
95 scan. Set to 0 to only look in the heading.")
96
97 (make-obsolete-variable
98 'gnorb-org-mail-scan-strategies
99 "This variable has been superseded by `gnorb-org-trigger-actions'"
100 "September 12, 2014" 'set)
101
102 (make-obsolete-variable
103 'gnorb-org-mail-scan-state-changes
104 "This variable has been superseded by `gnorb-org-trigger-actions'"
105 "September 12, 2014" 'set)
106
107 (make-obsolete-variable
108 'gnorb-org-mail-scan-function
109 "This variable has been superseded by `gnorb-org-trigger-actions'"
110 "September 12, 2014" 'set)
111
112 (defcustom gnorb-org-find-candidates-match nil
113 "When scanning all org files for heading related to an incoming
114 message, this option will limit which headings will be offered as
115 target candidates. Specifically it will be used as the second
116 argument to `org-map-entries', and syntax is the same as that
117 used in an agenda tags view."
118 :group 'gnorb-org
119 :type 'symbol)
120
121 ;;;###autoload
122 (defun gnorb-org-contact-link (rec)
123 "Prompt for a BBDB record and insert a link to that record at
124 point.
125
126 There's really no reason to use this instead of regular old
127 `org-insert-link' with BBDB completion. But there might be in the
128 future!"
129 ;; this needs to handle an active region.
130 (interactive (list (gnorb-prompt-for-bbdb-record)))
131 (let* ((name (bbdb-record-name rec))
132 (link (concat "bbdb:" (org-link-escape name))))
133 (org-store-link-props :type "bbdb" :name name
134 :link link :description name)
135 (if (called-interactively-p 'any)
136 (insert (format "[[%s][%s]]" link name))
137 link)))
138
139 (defun gnorb-org-restore-after-send ()
140 "After an email is sent, go through all the org ids that might
141 have been in the outgoing message's headers and call
142 `gnorb-trigger-todo-action' on each one, then put us back where
143 we came from."
144 (delete-other-windows)
145 (dolist (id gnorb-message-org-ids)
146 (org-id-goto id)
147 (gnorb-trigger-todo-action nil id))
148 ;; this is a little unnecessary, but it may save grief
149 (setq gnorb-gnus-message-info nil)
150 (setq gnorb-message-org-ids nil)
151 (gnorb-restore-layout))
152
153 (defun gnorb-org-extract-links (&optional arg region)
154 "See if there are viable links in the subtree under point."
155 ;; We're not currently using the arg. What could we do with it?
156 (let (strings)
157 ;; If the region was active, only use the region
158 (if region
159 (push (buffer-substring (car region) (cdr region))
160 strings)
161 ;; Otherwise collect the heading text, and all the paragraph
162 ;; text.
163 (save-restriction
164 (org-narrow-to-subtree)
165 (let ((head (org-element-at-point))
166 (tree (org-element-parse-buffer)))
167 (push (org-element-property
168 :raw-value
169 head)
170 strings)
171 (org-element-map tree 'paragraph
172 (lambda (p)
173 (push (org-element-interpret-data p)
174 strings))
175 nil nil 'drawer))))
176 (when strings
177 ;; Limit number of paragraphs based on
178 ;; `gnorb-org-mail-scan-scope'
179 (setq strings
180 (cond ((eq gnorb-org-mail-scan-scope 'all)
181 strings)
182 ((numberp gnorb-org-mail-scan-scope)
183 (delq nil
184 (cl-subseq
185 strings 0 (1+ gnorb-org-mail-scan-scope))))
186 ;; We could provide more options here. 'tree vs
187 ;; 'subtree, for instance.
188 (t
189 strings)))
190 (with-temp-buffer
191 (dolist (s strings)
192 (insert s)
193 (insert "\n"))
194 (goto-char (point-min))
195 (gnorb-scan-links (point-max) 'gnus 'mail 'bbdb)))))
196
197 (defun gnorb-org-extract-mail-stuff (&optional arg region)
198 "Decide how to hande the Org heading under point as an email task.
199
200 See the docstring of `gnorb-org-handle-mail' for details."
201 (if (or (not gnorb-tracking-enabled)
202 region)
203 (gnorb-org-extract-links arg region)
204 ;; Get all the messages associated with the IDS in this subtree.
205 (let ((assoc-msg-ids
206 (delete-dups
207 (cl-mapcan
208 (lambda (id)
209 (gnorb-registry-org-id-search id))
210 (gnorb-collect-ids)))))
211 (gnorb-org-extract-mail-tracking assoc-msg-ids arg region))))
212
213 (defun gnorb-org-extract-mail-tracking (assoc-msg-ids &optional arg region)
214
215 (let* ((all-links (gnorb-org-extract-links nil region))
216 ;; The latest (by the creation-time registry key) of all the
217 ;; tracked messages that were not sent by our user.
218 (latest-msg-id
219 (when assoc-msg-ids
220 (car
221 (sort
222 (cl-remove-if
223 (lambda (m)
224 (let ((from (car (gnus-registry-get-id-key m 'sender))))
225 (or (null from)
226 (string-match-p
227 user-mail-address from)
228 (string-match-p
229 message-alternative-emails from))))
230 assoc-msg-ids)
231 (lambda (r l)
232 (time-less-p
233 (car (gnus-registry-get-id-key l 'creation-time))
234 (car (gnus-registry-get-id-key r 'creation-time))))))))
235 (msg-id-link
236 (when latest-msg-id
237 (gnorb-msg-id-to-link latest-msg-id))))
238 (cond
239 ;; If there are no tracked messages, or the user has specifically
240 ;; requested we ignore them with the prefix arg, just return the
241 ;; found links in the subtree.
242 ((or arg
243 (null msg-id-link))
244 all-links)
245 ;; Otherwise ignore the other links in the subtree, and return
246 ;; the latest message.
247 (msg-id-link
248 `(:gnus ,(list msg-id-link))))))
249
250 (defvar message-beginning-of-line)
251
252 (defun gnorb-org-setup-message
253 (&optional messages mails from cc bcc attachments text ids)
254 "Common message setup routine for other gnorb-org commands.
255 MESSAGES is a list of gnus links pointing to messages -- we
256 currently only use the first of the list. MAILS is a list of
257 email address strings suitable for inserting in the To header.
258 ATTACHMENTS is a list of filenames to attach. TEXT is a string or
259 buffer, which is inserted in the message body. IDS is one or more
260 Org heading ids, associating the outgoing message with those
261 headings."
262 (require 'gnorb-gnus)
263 (if (not messages)
264 ;; Either compose new message...
265 (compose-mail)
266 ;; ...or follow link and start reply.
267 (condition-case err
268 (gnorb-reply-to-gnus-link (car messages))
269 (error (gnorb-restore-layout)
270 (signal (car err) (cdr err)))))
271 ;; Add MAILS to message To header.
272 (when mails
273 (message-goto-to)
274 (when messages
275 (insert ", "))
276 (insert (mapconcat 'identity mails ", ")))
277 ;; Return us after message is sent.
278 (add-to-list 'message-exit-actions
279 'gnorb-org-restore-after-send t)
280 ;; Set headers from MAIL_* properties (from, cc, and bcc).
281 (cl-flet ((sh (h)
282 (when (cdr h)
283 (funcall (intern (format "message-goto-%s" (car h))))
284 (let ((message-beginning-of-line t)
285 (show-trailing-whitespace t))
286 (message-beginning-of-line)
287 (unless (bolp)
288 (kill-line))
289 (insert (cdr h))))))
290 (dolist (h `((from . ,from) (cc . ,cc) (bcc . ,bcc)))
291 (sh h)))
292 ;; attach ATTACHMENTS
293 (map-y-or-n-p
294 (lambda (a) (format "Attach %s to outgoing message? "
295 (file-name-nondirectory a)))
296 (lambda (a)
297 (mml-attach-file a (mm-default-file-encoding a)
298 nil "attachment"))
299 attachments
300 '("file" "files" "attach"))
301 ;; insert text, if any
302 (when text
303 (message-goto-body)
304 (insert"\n")
305 (if (bufferp text)
306 (insert-buffer-substring text)
307 (insert text)))
308 ;; insert org ids, if any
309 (when ids
310 (unless (listp ids)
311 (setq ids (list ids)))
312 (save-excursion
313 (save-restriction
314 (message-narrow-to-headers)
315 (dolist (i ids)
316 (goto-char (point-at-bol))
317 (open-line 1)
318 ;; this function hardly does anything
319 (message-insert-header
320 (intern gnorb-mail-header) i)))))
321 ;; put point somewhere reasonable
322 (if (or mails messages)
323 (if (not messages)
324 (message-goto-subject)
325 (message-goto-body))
326 (message-goto-to))
327 (run-hooks 'gnorb-org-after-message-setup-hook))
328
329 (defun gnorb-org-attachment-list (&optional id)
330 "Get a list of files (absolute filenames) attached to the
331 current heading, or the heading indicated by optional argument ID."
332 (when (featurep 'org-attach)
333 (let* ((attach-dir (save-excursion
334 (when id
335 (org-id-goto id))
336 (org-attach-dir t)))
337 (files
338 (mapcar
339 (lambda (f)
340 (expand-file-name f attach-dir))
341 (org-attach-file-list attach-dir))))
342 files)))
343
344 (defvar message-mode-hook)
345
346 ;;;###autoload
347 (defun gnorb-org-handle-mail (&optional arg text file)
348 "Handle current headline as a mail TODO.
349
350 How this function behaves depends on whether you're using Gnorb
351 for email tracking, also on the prefix arg, and on the active
352 region.
353
354 If tracking is enabled and there is no prefix arg, Gnorb will
355 begin a reply to the newest associated message that wasn't sent
356 by the user -- ie, the Sender header doesn't match
357 `user-mail-address' or `message-alternative-emails'.
358
359 If tracking is enabled and there is a prefix arg, ignore the
360 tracked messages and instead scan the subtree for mail-related
361 links. This means links prefixed with gnus:, mailto:, or bbdb:.
362 See `gnorb-org-mail-scan-scope' to limit the scope of this scan.
363 Do something appropriate with the resulting links.
364
365 With a double prefix arg, ignore all tracked messages and all
366 links, and compose a blank new message.
367
368 If tracking is enabled and you want to reply to a
369 specific (earlier) message in the tracking history, use
370 `gnorb-org-view' to open an nnir *Summary* buffer containing all
371 the messages, and reply to the one you want. Your reply will be
372 automatically tracked, as well.
373
374 If tracking is not enabled and you want to use a specific link in
375 the subtree as a basis for the email action, then put the region
376 around that link before you call this message."
377 (interactive "P")
378 (setq gnorb-window-conf (current-window-configuration))
379 (move-marker gnorb-return-marker (point))
380 (when (eq major-mode 'org-agenda-mode)
381 ;; If this is all the different types, we could skip the check.
382 (org-agenda-check-type t 'agenda 'timeline 'todo 'tags 'search)
383 (org-agenda-check-no-diary)
384 (let* ((marker (or (org-get-at-bol 'org-hd-marker)
385 (org-agenda-error)))
386 (buffer (marker-buffer marker))
387 (pos (marker-position marker)))
388 (switch-to-buffer buffer)
389 (widen)
390 (goto-char pos)))
391 (let ((region
392 (when (use-region-p)
393 (cons (region-beginning) (region-end)))))
394 (deactivate-mark)
395 (save-excursion
396 (unless (org-back-to-heading t)
397 (error "Not in an org item"))
398 (cl-flet ((mp (p) (org-entry-get (point) p t)))
399 ;; Double prefix means ignore everything and compose a blank
400 ;; mail.
401 (let* ((links (unless (equal arg '(16))
402 (gnorb-org-extract-mail-stuff arg region)))
403 (attachments (gnorb-org-attachment-list))
404 (from (mp "MAIL_FROM"))
405 (cc (mp "MAIL_CC"))
406 (bcc (mp "MAIL_BCC"))
407 (org-id (org-id-get-create))
408 (recs (plist-get links :bbdb))
409 (message-mode-hook (copy-sequence message-mode-hook))
410 mails)
411 (when file
412 (cons file attachments))
413 (when recs
414 (setq recs
415 (delq nil
416 (mapcar
417 (lambda (r)
418 (car (bbdb-message-search
419 (org-link-unescape r)
420 nil)))
421 recs))))
422 (when recs
423 (dolist (r recs)
424 (push (bbdb-mail-address r) mails)))
425 (when (and recs
426 gnorb-bbdb-posting-styles)
427 (add-hook 'message-mode-hook
428 (lambda ()
429 (gnorb-bbdb-configure-posting-styles (cdr recs))
430 (gnorb-bbdb-configure-posting-styles (list (car recs))))))
431 (gnorb-org-setup-message
432 (plist-get links :gnus)
433 (append mails (plist-get links :mail))
434 from cc bcc
435 attachments text org-id))))))
436
437 ;;; Email subtree
438
439 (defcustom gnorb-org-email-subtree-text-parameters nil
440 "A plist of export parameters corresponding to the EXT-PLIST
441 argument to the export functions, for use when exporting to
442 text."
443 :group 'gnorb-org
444 :type 'boolean)
445
446 (defcustom gnorb-org-email-subtree-file-parameters nil
447 "A plist of export parameters corresponding to the EXT-PLIST
448 argument to the export functions, for use when exporting to a
449 file."
450 :group 'gnorb-org
451 :type 'boolean)
452
453 (defcustom gnorb-org-email-subtree-text-options '(nil t nil t)
454 "A list of ts and nils corresponding to Org's export options,
455 to be used when exporting to text. The options, in order, are
456 async, subtreep, visible-only, and body-only."
457 :group 'gnorb-org
458 :type 'list)
459
460 (defcustom gnorb-org-email-subtree-file-options '(nil t nil nil)
461 "A list of ts and nils corresponding to Org's export options,
462 to be used when exporting to a file. The options, in order, are
463 async, subtreep, visible-only, and body-only."
464 :group 'gnorb-org
465 :type 'list)
466
467 (defcustom gnorb-org-export-extensions
468 '((latex ".tex")
469 (ascii ".txt")
470 (html ".html")
471 (org ".org")
472 (icalendar ".ics")
473 (man ".man")
474 (md ".md")
475 (odt ".odt") ; not really, though
476 (texinfo ".texi")
477 (beamer ".tex"))
478 "Correspondence between export backends and their
479 respective (usual) file extensions. Ugly way to do it, but what
480 the hey..."
481 :group 'gnorb-org)
482
483 (defvar org-export-show-temporary-export-buffer)
484
485 ;;;###autoload
486 (defun gnorb-org-email-subtree (&optional arg)
487 "Call on a subtree to export it either to a text string or a file,
488 then compose a mail message either with the exported text
489 inserted into the message body, or the exported file attached to
490 the message.
491
492 Export options default to the following: When exporting to a
493 buffer: async = nil, subtreep = t, visible-only = nil, body-only
494 = t. Options are the same for files, except body-only is set to
495 nil. Customize `gnorb-org-email-subtree-text-options' and
496 `gnorb-org-email-subtree-file-options', respectively.
497
498 Customize `gnorb-org-email-subtree-parameters' to your preferred
499 default set of parameters."
500 ;; I sure would have liked to use the built-in dispatch ui, but it's
501 ;; got too much hard-coded stuff.
502 (interactive "P")
503 (org-back-to-heading t)
504 (let* ((backend-string
505 (org-completing-read
506 "Export backend: "
507 (mapcar (lambda (b)
508 (symbol-name (org-export-backend-name b)))
509 org-export--registered-backends)
510 nil t))
511 (backend-symbol (intern backend-string))
512 (f-or-t (org-completing-read "Export as file or text? "
513 '("file" "text") nil t))
514 (org-export-show-temporary-export-buffer nil)
515 (opts (if (equal f-or-t "text")
516 gnorb-org-email-subtree-text-options
517 gnorb-org-email-subtree-file-options))
518 (result
519 (if (equal f-or-t "text")
520 (apply 'org-export-to-buffer
521 `(,backend-symbol
522 "*Gnorb Export*"
523 ,@opts
524 ,gnorb-org-email-subtree-text-parameters))
525 (apply 'org-export-to-file
526 `(,backend-symbol
527 ,(org-export-output-file-name
528 (second (assoc backend-symbol gnorb-org-export-extensions))
529 t gnorb-tmp-dir)
530 ,@opts
531 ,gnorb-org-email-subtree-file-parameters))))
532 text file)
533 (setq gnorb-window-conf (current-window-configuration))
534 (move-marker gnorb-return-marker (point))
535 (if (bufferp result)
536 (setq text result)
537 (setq file result))
538 (gnorb-org-handle-mail arg text file)))
539
540 (defcustom gnorb-org-capture-collect-link-p t
541 "Should the capture process store a link to the gnus message or
542 BBDB record under point, even if it's not part of the template?
543 You'll probably end up needing it, anyway."
544 :group 'gnorb-org)
545
546 (defun gnorb-org-capture-collect-link ()
547 (when gnorb-org-capture-collect-link-p
548 (let ((buf (org-capture-get :original-buffer)))
549 (when buf
550 (with-current-buffer buf
551 (when (memq major-mode '(gnus-summary-mode
552 gnus-article-mode
553 bbdb-mode))
554 (call-interactively 'org-store-link)))))))
555
556 (add-hook 'org-capture-mode-hook 'gnorb-org-capture-collect-link)
557
558 ;;; Agenda/BBDB popup stuff
559
560 (defcustom gnorb-org-agenda-popup-bbdb nil
561 "Should Agenda tags search pop up a BBDB buffer with matching
562 records?
563
564 Records are considered matching if they have an `org-tags' field
565 matching the current Agenda search. The name of that field can be
566 customized with `gnorb-bbdb-org-tag-field'."
567 :group 'gnorb-org)
568
569 (defcustom gnorb-org-bbdb-popup-layout 'pop-up-multi-line
570 "Default BBDB buffer layout for automatic Org Agenda display."
571 :group 'gnorb-org
572 :type '(choice (const one-line)
573 (const multi-line)
574 (const full-multi-line)
575 (symbol)))
576
577 ;;;###autoload
578 (defun gnorb-org-popup-bbdb (&optional str)
579 "In an `org-tags-view' Agenda buffer, pop up a BBDB buffer
580 showing records whose `org-tags' field matches the current tags
581 search."
582 ;; I was hoping to use `org-make-tags-matcher' directly, then snag
583 ;; the tagmatcher from the resulting value, but there doesn't seem
584 ;; to be a reliable way of only getting the tag-related returns. But
585 ;; I'd still like to use that function. So an ugly hack to first
586 ;; remove non-tag contents from the query string, and then make a
587 ;; new call to `org-make-tags-matcher'.
588 (interactive)
589 (require 'gnorb-bbdb)
590 (let (recs)
591 (cond ((and
592 (and (eq major-mode 'org-agenda-mode)
593 (eq org-agenda-type 'tags))
594 (or (called-interactively-p 'any)
595 gnorb-org-agenda-popup-bbdb))
596 (let ((todo-only nil)
597 (str (or str org-agenda-query-string))
598 (re "^&?\\([-+:]\\)?\\({[^}]+}\\|LEVEL\\([<=>]\\{1,2\\}\\)\\([0-9]+\\)\\|\\(\\(?:[[:alnum:]_]+\\(?:\\\\-\\)*\\)+\\)\\([<>=]\\{1,2\\}\\)\\({[^}]+}\\|\"[^\"]*\"\\|-?[.0-9]+\\(?:[eE][-+]?[0-9]+\\)?\\)\\|[[:alnum:]_@#%]+\\)")
599 or-terms term rest out-or acc tag-clause)
600 (setq or-terms (org-split-string str "|"))
601 (while (setq term (pop or-terms))
602 (setq acc nil)
603 (while (string-match re term)
604 (setq rest (substring term (match-end 0)))
605 (let ((sub-term (match-string 0 term)))
606 (unless (save-match-data ; this isn't a tag, don't want it
607 (string-match "\\([<>=]\\)" sub-term))
608 (push sub-term acc))
609 (setq term rest)))
610 (push (mapconcat 'identity (nreverse acc) "") out-or))
611 (setq str (mapconcat 'identity (nreverse out-or) "|"))
612 (setq tag-clause (cdr (org-make-tags-matcher str)))
613 (unless (equal str "")
614 (setq recs
615 (cl-remove-if-not
616 (lambda (r)
617 (let ((rec-tags (bbdb-record-xfield
618 r gnorb-bbdb-org-tag-field)))
619 (and rec-tags
620 (let ((tags-list (org-split-string rec-tags ":"))
621 (case-fold-search t)
622 (org-trust-scanner-tags t))
623 (eval tag-clause)))))
624 (bbdb-records))))))
625 ((eq major-mode 'org-mode)
626 (save-excursion
627 (org-back-to-heading)
628 (let ((bound (org-element-property
629 :end (org-element-at-point)))
630 desc rec)
631 (while (re-search-forward
632 org-bracket-link-analytic-regexp bound t)
633 (when (string-match-p "bbdb" (match-string 2))
634 (setq desc (match-string 5)
635 rec (bbdb-search (bbdb-records) desc desc desc)
636 recs (append recs rec))))))))
637 (if recs
638 (bbdb-display-records
639 recs gnorb-org-bbdb-popup-layout)
640 (when (get-buffer-window bbdb-buffer-name)
641 (quit-window nil
642 (get-buffer-window bbdb-buffer-name)))
643 (when (called-interactively-p 'any)
644 (message "No relevant BBDB records")))))
645
646 (if (featurep 'gnorb-bbdb)
647 (add-hook 'org-agenda-finalize-hook 'gnorb-org-popup-bbdb))
648
649 ;;; Groups from the gnorb gnus server backend
650
651 ;;;###autoload
652 (defun gnorb-org-view ()
653 "Search the subtree at point for links to gnus messages, and
654 then show them in an ephemeral group, in gnus.
655
656 This won't work unless you've added a \"nngnorb\" server to
657 your gnus select methods."
658 ;; this should also work on the active region, if there is one.
659 (interactive)
660 (require 'gnorb-gnus)
661 (setq gnorb-window-conf (current-window-configuration))
662 (move-marker gnorb-return-marker (point))
663 (when (eq major-mode 'org-agenda-mode)
664 (org-agenda-check-type t 'agenda 'timeline 'todo 'tags)
665 (org-agenda-check-no-diary)
666 (let* ((marker (or (org-get-at-bol 'org-hd-marker)
667 (org-agenda-error)))
668 (buffer (marker-buffer marker))
669 (pos (marker-position marker)))
670 (switch-to-buffer buffer)
671 (goto-char pos)
672 (org-reveal)))
673 (let (id)
674 (save-excursion
675 (org-back-to-heading)
676 (setq id (concat "id+" (org-id-get-create)))
677 (gnorb-gnus-search-messages
678 id
679 `(when (and (window-configuration-p gnorb-window-conf)
680 gnorb-return-marker)
681 (set-window-configuration gnorb-window-conf)
682 (goto-char gnorb-return-marker))))))
683
684 (provide 'gnorb-org)
685 ;;; gnorb-org.el ends here