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