Merge from emacs-24; up to 2012-12-26T22:30:58Z!yamaoka@jpl.org
[bpt/emacs.git] / lisp / org / org-id.el
CommitLineData
db55f368 1;;; org-id.el --- Global identifiers for Org-mode entries
0bd48b37 2;;
ab422c4d 3;; Copyright (C) 2008-2013 Free Software Foundation, Inc.
d4d1a4ac
CD
4;;
5;; Author: Carsten Dominik <carsten at orgmode dot org>
6;; Keywords: outlines, hypermedia, calendar, wp
7;; Homepage: http://orgmode.org
d4d1a4ac
CD
8;;
9;; This file is part of GNU Emacs.
10;;
11;; GNU Emacs is free software: you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
24;;
25;;; Commentary:
26
27;; This file implements globally unique identifiers for Org-mode entries.
28;; Identifiers are stored in the entry as an :ID: property. Functions
29;; are provided that create and retrieve such identifiers, and that find
30;; entries based on the identifier.
31
621f83e4
CD
32;; Identifiers consist of a prefix (default "Org" given by the variable
33;; `org-id-prefix') and a unique part that can be created by a number
34;; of different methods, see the variable `org-id-method'.
35;; Org has a builtin method that uses a compact encoding of the creation
36;; time of the ID, with microsecond accuracy. This virtually
d4d1a4ac 37;; guarantees globally unique identifiers, even if several people are
db55f368 38;; creating IDs at the same time in files that will eventually be used
86fbb8ca
CD
39;; together.
40;;
41;; By default Org uses UUIDs as global unique identifiers.
d4d1a4ac
CD
42;;
43;; This file defines the following API:
44;;
45;; org-id-get-create
46;; Create an ID for the entry at point if it does not yet have one.
47;; Returns the ID (old or new). This function can be used
48;; interactively, with prefix argument the creation of a new ID is
49;; forced, even if there was an old one.
50;;
51;; org-id-get
52;; Get the ID property of an entry. Using appropriate arguments
53;; to the function, it can also create the ID for this entry.
54;;
55;; org-id-goto
56;; Command to go to a specific ID, this command can be used
57;; interactively.
58;;
59;; org-id-get-with-outline-path-completion
60;; Retrieve the ID of an entry, using outline path completion.
61;; This function can work for multiple files.
62;;
63;; org-id-get-with-outline-drilling
64;; Retrieve the ID of an entry, using outline path completion.
65;; This function only works for the current file.
66;;
67;; org-id-find
68;; Find the location of an entry with specific id.
69;;
70
86fbb8ca
CD
71;;; Code:
72
d4d1a4ac
CD
73(require 'org)
74
75(declare-function message-make-fqdn "message" ())
14e1337f 76(declare-function org-pop-to-buffer-same-window
e66ba1df 77 "org-compat" (&optional buffer-or-name norecord label))
d4d1a4ac
CD
78
79;;; Customization
80
81(defgroup org-id nil
82 "Options concerning global entry identifiers in Org-mode."
83 :tag "Org ID"
84 :group 'org)
85
a89c8ef0 86(define-obsolete-variable-alias
8223b1d2
BG
87 'org-link-to-org-use-id 'org-id-link-to-org-use-id "24.3")
88(defcustom org-id-link-to-org-use-id nil
89 "Non-nil means storing a link to an Org file will use entry IDs.
90
91The variable can have the following values:
92
93t Create an ID if needed to make a link to the current entry.
94
95create-if-interactive
96 If `org-store-link' is called directly (interactively, as a user
97 command), do create an ID to support the link. But when doing the
98 job for capture, only use the ID if it already exists. The
99 purpose of this setting is to avoid proliferation of unwanted
100 IDs, just because you happen to be in an Org file when you
101 call `org-capture' that automatically and preemptively creates a
102 link. If you do want to get an ID link in a capture template to
103 an entry not having an ID, create it first by explicitly creating
104 a link to it, using `C-c C-l' first.
105
106create-if-interactive-and-no-custom-id
107 Like create-if-interactive, but do not create an ID if there is
108 a CUSTOM_ID property defined in the entry.
109
110use-existing
111 Use existing ID, do not create one.
112
113nil Never use an ID to make a link, instead link using a text search for
114 the headline text."
115 :group 'org-link-store
116 :group 'org-id
117 :version "24.3"
118 :type '(choice
119 (const :tag "Create ID to make link" t)
120 (const :tag "Create if storing link interactively"
121 create-if-interactive)
122 (const :tag "Create if storing link interactively and no CUSTOM_ID is present"
123 create-if-interactive-and-no-custom-id)
124 (const :tag "Only use existing" use-existing)
125 (const :tag "Do not use ID to create link" nil)))
126
c8d0cf5c
CD
127(defcustom org-id-uuid-program "uuidgen"
128 "The uuidgen program."
129 :group 'org-id
130 :type 'string)
621f83e4 131
86fbb8ca 132(defcustom org-id-method 'uuid
db55f368
CD
133 "The method that should be used to create new IDs.
134
db55f368
CD
135An ID will consist of the optional prefix specified in `org-id-prefix',
136and a unique part created by the method this variable specifies.
621f83e4
CD
137
138Allowed values are:
139
db55f368
CD
140org Org's own internal method, using an encoding of the current time to
141 microsecond accuracy, and optionally the current domain of the
142 computer. See the variable `org-id-include-domain'.
621f83e4 143
86fbb8ca
CD
144uuid Create random (version 4) UUIDs. If the program defined in
145 `org-id-uuid-program' is available it is used to create the ID.
146 Otherwise an internal functions is used."
621f83e4
CD
147 :group 'org-id
148 :type '(choice
149 (const :tag "Org's internal method" org)
86fbb8ca 150 (const :tag "external: uuidgen" uuid)))
621f83e4
CD
151
152(defcustom org-id-prefix nil
d4d1a4ac
CD
153 "The prefix for IDs.
154
155This may be a string, or it can be nil to indicate that no prefix is required.
156When a string, the string should have no space characters as IDs are expected
157to have no space characters in them."
158 :group 'org-id
159 :type '(choice
160 (const :tag "No prefix")
161 (string :tag "Prefix")))
162
db55f368 163(defcustom org-id-include-domain nil
ed21c5c8 164 "Non-nil means add the domain name to new IDs.
db55f368 165This ensures global uniqueness of IDs, and is also suggested by
621f83e4
CD
166RFC 2445 in combination with RFC 822. This is only relevant if
167`org-id-method' is `org'. When uuidgen is used, the domain will never
db55f368
CD
168be added.
169The default is to not use this because we have no really good way to get
170the true domain, and Org entries will normally not be shared with enough
171people to make this necessary."
172 :group 'org-id
173 :type 'boolean)
174
175(defcustom org-id-track-globally t
ed21c5c8 176 "Non-nil means track IDs through files, so that links work globally.
db55f368
CD
177This work by maintaining a hash table for IDs and writing this table
178to disk when exiting Emacs. Because of this, it works best if you use
179a single Emacs process, not many.
180
181When nil, IDs are not tracked. Links to IDs will still work within
182a buffer, but not if the entry is located in another file.
183IDs can still be used if the entry with the id is in the same file as
184the link."
d4d1a4ac
CD
185 :group 'org-id
186 :type 'boolean)
187
71d35b24 188(defcustom org-id-locations-file (convert-standard-filename
db55f368
CD
189 "~/.emacs.d/.org-id-locations")
190 "The file for remembering in which file an ID was defined.
191This variable is only relevant when `org-id-track-globally' is set."
d4d1a4ac
CD
192 :group 'org-id
193 :type 'file)
194
195(defvar org-id-locations nil
3ab2c837 196 "List of files with IDs in those files.")
db55f368
CD
197
198(defvar org-id-files nil
199 "List of files that contain IDs.")
d4d1a4ac
CD
200
201(defcustom org-id-extra-files 'org-agenda-text-search-extra-files
db55f368
CD
202 "Files to be searched for IDs, besides the agenda files.
203When Org reparses files to remake the list of files and IDs it is tracking,
204it will normally scan the agenda files, the archives related to agenda files,
205any files that are listed as ID containing in the current register, and
206any Org-mode files currently visited by Emacs.
207You can list additional files here.
208This variable is only relevant when `org-id-track-globally' is set."
d4d1a4ac
CD
209 :group 'org-id
210 :type
211 '(choice
212 (symbol :tag "Variable")
213 (repeat :tag "List of files"
214 (file))))
215
db55f368 216(defcustom org-id-search-archives t
ed21c5c8 217 "Non-nil means search also the archive files of agenda files for entries.
33306645 218This is a possibility to reduce overhead, but it means that entries moved
db55f368
CD
219to the archives can no longer be found by ID.
220This variable is only relevant when `org-id-track-globally' is set."
221 :group 'org-id
222 :type 'boolean)
223
d4d1a4ac
CD
224;;; The API functions
225
226;;;###autoload
227(defun org-id-get-create (&optional force)
228 "Create an ID for the current entry and return it.
229If the entry already has an ID, just return it.
230With optional argument FORCE, force the creation of a new ID."
231 (interactive "P")
232 (when force
233 (org-entry-put (point) "ID" nil))
234 (org-id-get (point) 'create))
ff4be292 235
d4d1a4ac
CD
236(defun org-id-copy ()
237 "Copy the ID of the entry at point to the kill ring.
238Create an ID if necessary."
239 (interactive)
c8d0cf5c 240 (org-kill-new (org-id-get nil 'create)))
d4d1a4ac
CD
241
242;;;###autoload
243(defun org-id-get (&optional pom create prefix)
244 "Get the ID property of the entry at point-or-marker POM.
245If POM is nil, refer to the entry at point.
246If the entry does not have an ID, the function returns nil.
247However, when CREATE is non nil, create an ID if none is present already.
248PREFIX will be passed through to `org-id-new'.
249In any case, the ID of the entry is returned."
8d642074
CD
250 (org-with-point-at pom
251 (let ((id (org-entry-get nil "ID")))
252 (cond
253 ((and id (stringp id) (string-match "\\S-" id))
254 id)
255 (create
256 (setq id (org-id-new prefix))
257 (org-entry-put pom "ID" id)
258 (org-id-add-location id (buffer-file-name (buffer-base-buffer)))
8223b1d2 259 id)))))
d4d1a4ac 260
d4d1a4ac 261(defun org-id-get-with-outline-path-completion (&optional targets)
8a28a5b8
BG
262 "Use `outline-path-completion' to retrieve the ID of an entry.
263TARGETS may be a setting for `org-refile-targets' to define
264eligible headlines. When omitted, all headlines in the current
265file are eligible. This function returns the ID of the entry.
266If necessary, the ID is created."
d4d1a4ac 267 (let* ((org-refile-targets (or targets '((nil . (:maxlevel . 10)))))
ff4be292 268 (org-refile-use-outline-path
d4d1a4ac 269 (if (caar org-refile-targets) 'file t))
c8d0cf5c 270 (org-refile-target-verify-function nil)
3ab2c837 271 (spos (org-refile-get-location "Entry"))
ff4be292 272 (pom (and spos (move-marker (make-marker) (nth 3 spos)
d4d1a4ac
CD
273 (get-file-buffer (nth 1 spos))))))
274 (prog1 (org-id-get pom 'create)
275 (move-marker pom nil))))
276
d4d1a4ac
CD
277(defun org-id-get-with-outline-drilling (&optional targets)
278 "Use an outline-cycling interface to retrieve the ID of an entry.
279This only finds entries in the current buffer, using `org-get-location'.
280It returns the ID of the entry. If necessary, the ID is created."
281 (let* ((spos (org-get-location (current-buffer) org-goto-help))
282 (pom (and spos (move-marker (make-marker) (car spos)))))
283 (prog1 (org-id-get pom 'create)
284 (move-marker pom nil))))
285
286;;;###autoload
287(defun org-id-goto (id)
288 "Switch to the buffer containing the entry with id ID.
289Move the cursor to that entry in that buffer."
db55f368 290 (interactive "sID: ")
d4d1a4ac
CD
291 (let ((m (org-id-find id 'marker)))
292 (unless m
293 (error "Cannot find entry with ID \"%s\"" id))
e66ba1df 294 (org-pop-to-buffer-same-window (marker-buffer m))
d4d1a4ac
CD
295 (goto-char m)
296 (move-marker m nil)
ff4be292 297 (org-show-context)))
d4d1a4ac
CD
298
299;;;###autoload
300(defun org-id-find (id &optional markerp)
301 "Return the location of the entry with the id ID.
302The return value is a cons cell (file-name . position), or nil
303if there is no entry with that ID.
304With optional argument MARKERP, return the position as a new marker."
0bd48b37
CD
305 (cond
306 ((symbolp id) (setq id (symbol-name id)))
307 ((numberp id) (setq id (number-to-string id))))
d4d1a4ac
CD
308 (let ((file (org-id-find-id-file id))
309 org-agenda-new-buffers where)
310 (when file
311 (setq where (org-id-find-id-in-file id file markerp)))
312 (unless where
8223b1d2 313 (org-id-update-id-locations nil t)
d4d1a4ac
CD
314 (setq file (org-id-find-id-file id))
315 (when file
316 (setq where (org-id-find-id-in-file id file markerp))))
317 where))
318
319;;; Internal functions
320
321;; Creating new IDs
322
323(defun org-id-new (&optional prefix)
324 "Create a new globally unique ID.
325
326An ID consists of two parts separated by a colon:
327- a prefix
621f83e4 328- a unique part that will be created according to `org-id-method'.
d4d1a4ac
CD
329
330PREFIX can specify the prefix, the default is given by the variable
331`org-id-prefix'. However, if PREFIX is the symbol `none', don't use any
332prefix even if `org-id-prefix' specifies one.
333
334So a typical ID could look like \"Org:4nd91V40HI\"."
335 (let* ((prefix (if (eq prefix 'none)
621f83e4
CD
336 ""
337 (concat (or prefix org-id-prefix) ":")))
338 unique)
339 (if (equal prefix ":") (setq prefix ""))
340 (cond
86fbb8ca
CD
341 ((memq org-id-method '(uuidgen uuid))
342 (setq unique (org-trim (shell-command-to-string org-id-uuid-program)))
343 (unless (org-uuidgen-p unique)
344 (setq unique (org-id-uuid))))
621f83e4
CD
345 ((eq org-id-method 'org)
346 (let* ((etime (org-id-reverse-string (org-id-time-to-b36)))
347 (postfix (if org-id-include-domain
348 (progn
349 (require 'message)
350 (concat "@" (message-make-fqdn))))))
351 (setq unique (concat etime postfix))))
352 (t (error "Invalid `org-id-method'")))
353 (concat prefix unique)))
354
86fbb8ca
CD
355(defun org-id-uuid ()
356 "Return string with random (version 4) UUID."
357 (let ((rnd (md5 (format "%s%s%s%s%s%s%s"
0e23ef9d 358 (random)
86fbb8ca
CD
359 (current-time)
360 (user-uid)
361 (emacs-pid)
362 (user-full-name)
363 user-mail-address
364 (recent-keys)))))
365 (format "%s-%s-4%s-%s%s-%s"
366 (substring rnd 0 8)
367 (substring rnd 8 12)
368 (substring rnd 13 16)
369 (format "%x"
370 (logior
371 #b10000000
372 (logand
373 #b10111111
374 (string-to-number
375 (substring rnd 16 18) 16))))
376 (substring rnd 18 20)
377 (substring rnd 20 32))))
378
621f83e4
CD
379(defun org-id-reverse-string (s)
380 (mapconcat 'char-to-string (nreverse (string-to-list s)) ""))
381
382(defun org-id-int-to-b36-one-digit (i)
d4d1a4ac
CD
383 "Turn an integer between 0 and 61 into a single character 0..9, A..Z, a..z."
384 (cond
385 ((< i 10) (+ ?0 i))
621f83e4
CD
386 ((< i 36) (+ ?a i -10))
387 (t (error "Larger that 35"))))
d4d1a4ac 388
621f83e4 389(defun org-id-b36-to-int-one-digit (i)
d4d1a4ac
CD
390 "Turn a character 0..9, A..Z, a..z into a number 0..61.
391The input I may be a character, or a single-letter string."
392 (and (stringp i) (setq i (string-to-char i)))
393 (cond
394 ((and (>= i ?0) (<= i ?9)) (- i ?0))
621f83e4
CD
395 ((and (>= i ?a) (<= i ?z)) (+ (- i ?a) 10))
396 (t (error "Invalid b36 letter"))))
d4d1a4ac 397
621f83e4
CD
398(defun org-id-int-to-b36 (i &optional length)
399 "Convert an integer to a base-36 number represented as a string."
d4d1a4ac
CD
400 (let ((s ""))
401 (while (> i 0)
402 (setq s (concat (char-to-string
621f83e4
CD
403 (org-id-int-to-b36-one-digit (mod i 36))) s)
404 i (/ i 36)))
d4d1a4ac
CD
405 (setq length (max 1 (or length 1)))
406 (if (< (length s) length)
407 (setq s (concat (make-string (- length (length s)) ?0) s)))
408 s))
409
621f83e4
CD
410(defun org-id-b36-to-int (s)
411 "Convert a base-36 string into the corresponding integer."
d4d1a4ac 412 (let ((r 0))
621f83e4 413 (mapc (lambda (i) (setq r (+ (* r 36) (org-id-b36-to-int-one-digit i))))
d4d1a4ac
CD
414 s)
415 r))
416
621f83e4 417(defun org-id-time-to-b36 (&optional time)
d4d1a4ac
CD
418 "Encode TIME as a 10-digit string.
419This string holds the time to micro-second accuracy, and can be decoded
420using `org-id-decode'."
421 (setq time (or time (current-time)))
621f83e4
CD
422 (concat (org-id-int-to-b36 (nth 0 time) 4)
423 (org-id-int-to-b36 (nth 1 time) 4)
424 (org-id-int-to-b36 (or (nth 2 time) 0) 4)))
d4d1a4ac
CD
425
426(defun org-id-decode (id)
427 "Split ID into the prefix and the time value that was used to create it.
428The return value is (prefix . time) where PREFIX is nil or a string,
429and time is the usual three-integer representation of time."
430 (let (prefix time parts)
431 (setq parts (org-split-string id ":"))
432 (if (= 2 (length parts))
433 (setq prefix (car parts) time (nth 1 parts))
434 (setq prefix nil time (nth 0 parts)))
621f83e4
CD
435 (setq time (org-id-reverse-string time))
436 (setq time (list (org-id-b36-to-int (substring time 0 4))
437 (org-id-b36-to-int (substring time 4 8))
438 (org-id-b36-to-int (substring time 8 12))))
d4d1a4ac
CD
439 (cons prefix time)))
440
441;; Storing ID locations (files)
442
8223b1d2 443(defun org-id-update-id-locations (&optional files silent)
db55f368
CD
444 "Scan relevant files for IDs.
445Store the relation between files and corresponding IDs.
446This will scan all agenda files, all associated archives, and all
447files currently mentioned in `org-id-locations'.
448When FILES is given, scan these files instead.
33306645 449When CHECK is given, prepare detailed information about duplicate IDs."
d4d1a4ac 450 (interactive)
db55f368 451 (if (not org-id-track-globally)
f924a367 452 (error "Please turn on `org-id-track-globally' if you want to track IDs")
8bfe682a
CD
453 (let* ((org-id-search-archives
454 (or org-id-search-archives
455 (and (symbolp org-id-extra-files)
456 (symbol-value org-id-extra-files)
457 (member 'agenda-archives org-id-extra-files))))
458 (files
459 (or files
460 (append
461 ;; Agenda files and all associated archives
462 (org-agenda-files t org-id-search-archives)
463 ;; Explicit extra files
464 (if (symbolp org-id-extra-files)
465 (symbol-value org-id-extra-files)
466 org-id-extra-files)
8223b1d2 467 ;; Files associated with live org-mode buffers
8bfe682a
CD
468 (delq nil
469 (mapcar (lambda (b)
470 (with-current-buffer b
8223b1d2 471 (and (derived-mode-p 'org-mode) (buffer-file-name))))
8bfe682a
CD
472 (buffer-list)))
473 ;; All files known to have IDs
474 org-id-files)))
475 org-agenda-new-buffers
476 file nfiles tfile ids reg found id seen (ndup 0))
477 (when (member 'agenda-archives files)
478 (setq files (delq 'agenda-archives (copy-sequence files))))
db55f368
CD
479 (setq nfiles (length files))
480 (while (setq file (pop files))
8223b1d2
BG
481 (unless silent
482 (message "Finding ID locations (%d/%d files): %s"
483 (- nfiles (length files)) nfiles file))
db55f368
CD
484 (setq tfile (file-truename file))
485 (when (and (file-exists-p file) (not (member tfile seen)))
486 (push tfile seen)
487 (setq ids nil)
488 (with-current-buffer (org-get-agenda-file-buffer file)
489 (save-excursion
490 (save-restriction
491 (widen)
492 (goto-char (point-min))
493 (while (re-search-forward "^[ \t]*:ID:[ \t]+\\(\\S-+\\)[ \t]*$"
494 nil t)
495 (setq id (org-match-string-no-properties 1))
496 (if (member id found)
497 (progn
498 (message "Duplicate ID \"%s\", also in file %s"
8bfe682a
CD
499 id (or (car (delq
500 nil
501 (mapcar
502 (lambda (x)
503 (if (member id (cdr x))
504 (car x)))
505 reg)))
506 (buffer-file-name)))
db55f368
CD
507 (when (= ndup 0)
508 (ding)
509 (sit-for 2))
510 (setq ndup (1+ ndup)))
511 (push id found)
512 (push id ids)))
513 (push (cons (abbreviate-file-name file) ids) reg))))))
514 (org-release-buffers org-agenda-new-buffers)
515 (setq org-agenda-new-buffers nil)
516 (setq org-id-locations reg)
517 (setq org-id-files (mapcar 'car org-id-locations))
518 (org-id-locations-save) ;; this function can also handle the alist form
519 ;; now convert to a hash
520 (setq org-id-locations (org-id-alist-to-hash org-id-locations))
521 (if (> ndup 0)
522 (message "WARNING: %d duplicate IDs found, check *Messages* buffer" ndup)
523 (message "%d unique files scanned for IDs" (length org-id-files)))
524 org-id-locations)))
d4d1a4ac
CD
525
526(defun org-id-locations-save ()
527 "Save `org-id-locations' in `org-id-locations-file'."
ed21c5c8 528 (when (and org-id-track-globally org-id-locations)
db55f368
CD
529 (let ((out (if (hash-table-p org-id-locations)
530 (org-id-hash-to-alist org-id-locations)
531 org-id-locations)))
532 (with-temp-file org-id-locations-file
533 (print out (current-buffer))))))
d4d1a4ac
CD
534
535(defun org-id-locations-load ()
536 "Read the data from `org-id-locations-file'."
537 (setq org-id-locations nil)
db55f368
CD
538 (when org-id-track-globally
539 (with-temp-buffer
540 (condition-case nil
541 (progn
542 (insert-file-contents-literally org-id-locations-file)
543 (goto-char (point-min))
544 (setq org-id-locations (read (current-buffer))))
545 (error
8223b1d2 546 (message "Could not read org-id-values from %s. Setting it to nil."
db55f368
CD
547 org-id-locations-file))))
548 (setq org-id-files (mapcar 'car org-id-locations))
549 (setq org-id-locations (org-id-alist-to-hash org-id-locations))))
d4d1a4ac
CD
550
551(defun org-id-add-location (id file)
33306645 552 "Add the ID with location FILE to the database of ID locations."
db55f368 553 ;; Only if global tracking is on, and when the buffer has a file
33306645 554 (when (and org-id-track-globally id file)
ce4fdcb9 555 (unless org-id-locations (org-id-locations-load))
db55f368
CD
556 (puthash id (abbreviate-file-name file) org-id-locations)
557 (add-to-list 'org-id-files (abbreviate-file-name file))))
558
845fc5e5
JB
559(unless noninteractive
560 (add-hook 'kill-emacs-hook 'org-id-locations-save))
db55f368
CD
561
562(defun org-id-hash-to-alist (hash)
563 "Turn an org-id hash into an alist, so that it can be written to a file."
564 (let (res x)
565 (maphash
566 (lambda (k v)
567 (if (setq x (member v res))
568 (setcdr x (cons k (cdr x)))
569 (push (list v k) res)))
570 hash)
571 res))
572
573(defun org-id-alist-to-hash (list)
574 "Turn an org-id location list into a hash table."
575 (let ((res (make-hash-table
576 :test 'equal
577 :size (apply '+ (mapcar 'length list))))
65c439fd 578 f)
db55f368
CD
579 (mapc
580 (lambda (x)
581 (setq f (car x))
582 (mapc (lambda (i) (puthash i f res)) (cdr x)))
583 list)
584 res))
585
586(defun org-id-paste-tracker (txt &optional buffer-or-file)
587 "Update any IDs in TXT and assign BUFFER-OR-FILE to them."
588 (when org-id-track-globally
589 (save-match-data
590 (setq buffer-or-file (or buffer-or-file (current-buffer)))
591 (when (bufferp buffer-or-file)
592 (setq buffer-or-file (or (buffer-base-buffer buffer-or-file)
593 buffer-or-file))
594 (setq buffer-or-file (buffer-file-name buffer-or-file)))
595 (when buffer-or-file
596 (let ((fname (abbreviate-file-name buffer-or-file))
597 (s 0))
598 (while (string-match "^[ \t]*:ID:[ \t]+\\([^ \t\n\r]+\\)" txt s)
599 (setq s (match-end 0))
600 (org-id-add-location (match-string 1 txt) fname)))))))
d4d1a4ac
CD
601
602;; Finding entries with specified id
603
0bd48b37 604;;;###autoload
d4d1a4ac
CD
605(defun org-id-find-id-file (id)
606 "Query the id database for the file in which this ID is located."
607 (unless org-id-locations (org-id-locations-load))
ed21c5c8
CD
608 (or (and org-id-locations
609 (hash-table-p org-id-locations)
610 (gethash id org-id-locations))
db55f368
CD
611 ;; ball back on current buffer
612 (buffer-file-name (or (buffer-base-buffer (current-buffer))
613 (current-buffer)))))
d4d1a4ac
CD
614
615(defun org-id-find-id-in-file (id file &optional markerp)
616 "Return the position of the entry ID in FILE.
617If that files does not exist, or if it does not contain this ID,
618return nil.
619The position is returned as a cons cell (file-name . position). With
620optional argument MARKERP, return the position as a new marker."
65c439fd 621 (let (org-agenda-new-buffers buf pos)
d4d1a4ac
CD
622 (cond
623 ((not file) nil)
624 ((not (file-exists-p file)) nil)
625 (t (with-current-buffer (setq buf (org-get-agenda-file-buffer file))
626 (setq pos (org-find-entry-with-id id))
627 (when pos
628 (if markerp
629 (move-marker (make-marker) pos buf)
630 (cons file pos))))))))
631
db55f368
CD
632;; id link type
633
634;; Calling the following function is hard-coded into `org-store-link',
635;; so we do have to add it to `org-store-link-functions'.
636
afe98dfa 637;;;###autoload
db55f368 638(defun org-id-store-link ()
54a0dee5 639 "Store a link to the current entry, using its ID."
db55f368 640 (interactive)
8223b1d2
BG
641 (when (and (buffer-file-name (buffer-base-buffer)) (derived-mode-p 'org-mode))
642 (let* ((link (concat "id:" (org-id-get-create)))
acedf35c
CD
643 (case-fold-search nil)
644 (desc (save-excursion
645 (org-back-to-heading t)
646 (or (and (looking-at org-complex-heading-regexp)
647 (if (match-end 4)
648 (match-string 4)
649 (match-string 0)))
650 link))))
651 (org-store-link-props :link link :description desc :type "id")
652 link)))
db55f368
CD
653
654(defun org-id-open (id)
655 "Go to the entry with id ID."
656 (org-mark-ring-push)
c8d0cf5c
CD
657 (let ((m (org-id-find id 'marker))
658 cmd)
db55f368
CD
659 (unless m
660 (error "Cannot find entry with ID \"%s\"" id))
c8d0cf5c
CD
661 ;; Use a buffer-switching command in analogy to finding files
662 (setq cmd
663 (or
664 (cdr
665 (assq
666 (cdr (assq 'file org-link-frame-setup))
667 '((find-file . switch-to-buffer)
668 (find-file-other-window . switch-to-buffer-other-window)
669 (find-file-other-frame . switch-to-buffer-other-frame))))
670 'switch-to-buffer-other-window))
db55f368 671 (if (not (equal (current-buffer) (marker-buffer m)))
c8d0cf5c 672 (funcall cmd (marker-buffer m)))
db55f368
CD
673 (goto-char m)
674 (move-marker m nil)
675 (org-show-context)))
676
677(org-add-link-type "id" 'org-id-open)
678
d4d1a4ac
CD
679(provide 'org-id)
680
bdebdb64
BG
681;; Local variables:
682;; generated-autoload-file: "org-loaddefs.el"
683;; End:
684
d4d1a4ac 685;;; org-id.el ends here