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