]> code.delx.au - gnu-emacs/blobdiff - lisp/org/org-id.el
Try and be more careful about propagation of lexical environment.
[gnu-emacs] / lisp / org / org-id.el
index 0623c6071a86117ed0161d53dc65df2e65d21bb2..a8004afec8a7b2146487e22d28f700d112862325 100644 (file)
@@ -1,11 +1,11 @@
 ;;; org-id.el --- Global identifiers for Org-mode entries
 ;;
-;; Copyright (C) 2008, 2009 Free Software Foundation, Inc.
+;; Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc.
 ;;
 ;; Author: Carsten Dominik <carsten at orgmode dot org>
 ;; Keywords: outlines, hypermedia, calendar, wp
 ;; Homepage: http://orgmode.org
-;; Version: 6.19a
+;; Version: 7.4
 ;;
 ;; This file is part of GNU Emacs.
 ;;
@@ -37,8 +37,9 @@
 ;; time of the ID, with microsecond accuracy.  This virtually
 ;; guarantees globally unique identifiers, even if several people are
 ;; creating IDs at the same time in files that will eventually be used
-;; together.  As an external method `uuidgen' is supported, if installed
-;; on the system.
+;; together.
+;;
+;; By default Org uses UUIDs as global unique identifiers.
 ;;
 ;; This file defines the following API:
 ;;
@@ -68,6 +69,8 @@
 ;;        Find the location of an entry with specific id.
 ;;
 
+;;; Code:
+
 (require 'org)
 
 (declare-function message-make-fqdn "message" ())
   :tag "Org ID"
   :group 'org)
 
+(defcustom org-id-uuid-program "uuidgen"
+  "The uuidgen program."
+  :group 'org-id
+  :type 'string)
 
-(defcustom org-id-method
-  (condition-case nil
-      (if (string-match "\\`[-0-9a-fA-F]\\{36\\}\\'"
-                       (org-trim (shell-command-to-string "uuidgen")))
-         'uuidgen
-       'org)
-    (error 'org))
+(defcustom org-id-method 'uuid
   "The method that should be used to create new IDs.
 
-If `uuidgen' is available on the system, it will be used as the default method.
-if not, the method `org' is used.
 An ID will consist of the optional prefix specified in `org-id-prefix',
 and a unique part created by the method this variable specifies.
 
@@ -100,11 +99,13 @@ org        Org's own internal method, using an encoding of the current time to
            microsecond accuracy, and optionally the current domain of the
            computer.  See the variable `org-id-include-domain'.
 
-uuidgen    Call the external command uuidgen."
+uuid       Create random (version 4) UUIDs.  If the program defined in
+           `org-id-uuid-program' is available it is used to create the ID.
+           Otherwise an internal functions is used."
   :group 'org-id
   :type '(choice
          (const :tag "Org's internal method" org)
-         (const :tag "external: uuidgen" uuidgen)))
+         (const :tag "external: uuidgen" uuid)))
 
 (defcustom org-id-prefix nil
   "The prefix for IDs.
@@ -118,7 +119,7 @@ to have no space characters in them."
          (string :tag "Prefix")))
 
 (defcustom org-id-include-domain nil
-  "Non-nil means, add the domain name to new IDs.
+  "Non-nil means add the domain name to new IDs.
 This ensures global uniqueness of IDs, and is also suggested by
 RFC 2445 in combination with RFC 822.  This is only relevant if
 `org-id-method' is `org'.  When uuidgen is used, the domain will never
@@ -130,7 +131,7 @@ people to make this necessary."
   :type 'boolean)
 
 (defcustom org-id-track-globally t
-  "Non-nil means, track IDs through files, so that links work globally.
+  "Non-nil means track IDs through files, so that links work globally.
 This work by maintaining a hash table for IDs and writing this table
 to disk when exiting Emacs.  Because of this, it works best if you use
 a single Emacs process, not many.
@@ -173,7 +174,7 @@ This variable is only relevant when `org-id-track-globally' is set."
            (file))))
 
 (defcustom org-id-search-archives t
-  "Non-nil means, search also the archive files of agenda files for entries.
+  "Non-nil means search also the archive files of agenda files for entries.
 This is a possibility to reduce overhead, but it means that entries moved
 to the archives can no longer be found by ID.
 This variable is only relevant when `org-id-track-globally' is set."
@@ -197,7 +198,7 @@ With optional argument FORCE, force the creation of a new ID."
   "Copy the ID of the entry at point to the kill ring.
 Create an ID if necessary."
   (interactive)
-  (kill-new (org-id-get nil 'create)))
+  (org-kill-new (org-id-get nil 'create)))
 
 ;;;###autoload
 (defun org-id-get (&optional pom create prefix)
@@ -207,16 +208,17 @@ If the entry does not have an ID, the function returns nil.
 However, when CREATE is non nil, create an ID if none is present already.
 PREFIX will be passed through to `org-id-new'.
 In any case, the ID of the entry is returned."
-  (let ((id (org-entry-get pom "ID")))
-    (cond
-     ((and id (stringp id) (string-match "\\S-" id))
-      id)
-     (create
-      (setq id (org-id-new prefix))
-      (org-entry-put pom "ID" id)
-      (org-id-add-location id (buffer-file-name (buffer-base-buffer)))
-      id)
-     (t nil))))
+  (org-with-point-at pom
+    (let ((id (org-entry-get nil "ID")))
+      (cond
+       ((and id (stringp id) (string-match "\\S-" id))
+       id)
+       (create
+       (setq id (org-id-new prefix))
+       (org-entry-put pom "ID" id)
+       (org-id-add-location id (buffer-file-name (buffer-base-buffer)))
+       id)
+       (t nil)))))
 
 ;;;###autoload
 (defun org-id-get-with-outline-path-completion (&optional targets)
@@ -228,6 +230,7 @@ It returns the ID of the entry.  If necessary, the ID is created."
   (let* ((org-refile-targets (or targets '((nil . (:maxlevel . 10)))))
         (org-refile-use-outline-path
          (if (caar org-refile-targets) 'file t))
+        (org-refile-target-verify-function nil)
         (spos (org-refile-get-location "Entry: "))
         (pom (and spos (move-marker (make-marker) (nth 3 spos)
                                     (get-file-buffer (nth 1 spos))))))
@@ -299,8 +302,10 @@ So a typical ID could look like \"Org:4nd91V40HI\"."
         unique)
     (if (equal prefix ":") (setq prefix ""))
     (cond
-     ((eq org-id-method 'uuidgen)
-      (setq unique (org-trim (shell-command-to-string "uuidgen"))))
+     ((memq org-id-method '(uuidgen uuid))
+      (setq unique (org-trim (shell-command-to-string org-id-uuid-program)))
+      (unless (org-uuidgen-p unique)
+       (setq unique (org-id-uuid))))
      ((eq org-id-method 'org)
       (let* ((etime (org-id-reverse-string (org-id-time-to-b36)))
             (postfix (if org-id-include-domain
@@ -311,6 +316,30 @@ So a typical ID could look like \"Org:4nd91V40HI\"."
      (t (error "Invalid `org-id-method'")))
     (concat prefix unique)))
 
+(defun org-id-uuid ()
+  "Return string with random (version 4) UUID."
+  (let ((rnd (md5 (format "%s%s%s%s%s%s%s"
+                         (random t)
+                         (current-time)
+                         (user-uid)
+                         (emacs-pid)
+                         (user-full-name)
+                         user-mail-address
+                         (recent-keys)))))
+    (format "%s-%s-4%s-%s%s-%s"
+           (substring rnd 0 8)
+           (substring rnd 8 12)
+           (substring rnd 13 16)
+           (format "%x"
+                   (logior
+                    #b10000000
+                    (logand
+                     #b10111111
+                     (string-to-number
+                      (substring rnd 16 18) 16))))
+           (substring rnd 18 20)
+           (substring rnd 20 32))))
+
 (defun org-id-reverse-string (s)
   (mapconcat 'char-to-string (nreverse (string-to-list s)) ""))
 
@@ -384,26 +413,33 @@ When FILES is given, scan these files instead.
 When CHECK is given, prepare detailed information about duplicate IDs."
   (interactive)
   (if (not org-id-track-globally)
-      (error "Please turn on `org-id-track-globally' if you want to track IDs.")
-    (let ((files
-          (or files
-              (append
-               ;; Agenda files and all associated archives
-               (org-agenda-files t org-id-search-archives)
-               ;; Explicit extra files
-               (if (symbolp org-id-extra-files)
-                   (symbol-value org-id-extra-files)
-                 org-id-extra-files)
+      (error "Please turn on `org-id-track-globally' if you want to track IDs")
+    (let* ((org-id-search-archives
+           (or org-id-search-archives
+               (and (symbolp org-id-extra-files)
+                    (symbol-value org-id-extra-files)
+                    (member 'agenda-archives org-id-extra-files))))
+          (files
+           (or files
+               (append
+                ;; Agenda files and all associated archives
+                (org-agenda-files t org-id-search-archives)
+                ;; Explicit extra files
+                (if (symbolp org-id-extra-files)
+                    (symbol-value org-id-extra-files)
+                  org-id-extra-files)
              ;; Files associated with live org-mode buffers
-               (delq nil
-                     (mapcar (lambda (b)
-                               (with-current-buffer b
-                                 (and (org-mode-p) (buffer-file-name))))
-                             (buffer-list)))
-               ;; All files known to have IDs
-               org-id-files)))
-         org-agenda-new-buffers
-         file nfiles tfile ids reg found id seen (ndup 0))
+                (delq nil
+                      (mapcar (lambda (b)
+                                (with-current-buffer b
+                                  (and (org-mode-p) (buffer-file-name))))
+                              (buffer-list)))
+                ;; All files known to have IDs
+                org-id-files)))
+          org-agenda-new-buffers
+          file nfiles tfile ids reg found id seen (ndup 0))
+      (when (member 'agenda-archives files)
+       (setq files (delq 'agenda-archives (copy-sequence files))))
       (setq nfiles (length files))
       (while (setq file (pop files))
        (message "Finding ID locations (%d/%d files): %s"
@@ -423,12 +459,14 @@ When CHECK is given, prepare detailed information about duplicate IDs."
                  (if (member id found)
                      (progn
                        (message "Duplicate ID \"%s\", also in file %s"
-                                id (car (delq
-                                         nil
-                                         (mapcar
-                                          (lambda (x)
-                                            (if (member id (cdr x)) (car x)))
-                                          reg))))
+                                id (or (car (delq
+                                             nil
+                                             (mapcar
+                                              (lambda (x)
+                                                (if (member id (cdr x))
+                                                    (car x)))
+                                              reg)))
+                                       (buffer-file-name)))
                        (when (= ndup 0)
                          (ding)
                          (sit-for 2))
@@ -450,7 +488,7 @@ When CHECK is given, prepare detailed information about duplicate IDs."
 
 (defun org-id-locations-save ()
   "Save `org-id-locations' in `org-id-locations-file'."
-  (when org-id-track-globally
+  (when (and org-id-track-globally org-id-locations)
     (let ((out (if (hash-table-p org-id-locations)
                   (org-id-hash-to-alist org-id-locations)
                 org-id-locations)))
@@ -499,7 +537,7 @@ When CHECK is given, prepare detailed information about duplicate IDs."
   (let ((res (make-hash-table
              :test 'equal
              :size (apply '+ (mapcar 'length list))))
-       f i)
+       f)
     (mapc
      (lambda (x)
        (setq f (car x))
@@ -529,7 +567,9 @@ When CHECK is given, prepare detailed information about duplicate IDs."
 (defun org-id-find-id-file (id)
   "Query the id database for the file in which this ID is located."
   (unless org-id-locations (org-id-locations-load))
-  (or (gethash id org-id-locations)
+  (or (and org-id-locations
+          (hash-table-p org-id-locations)
+          (gethash id org-id-locations))
       ;; ball back on current buffer
       (buffer-file-name (or (buffer-base-buffer (current-buffer))
                            (current-buffer)))))
@@ -540,7 +580,7 @@ If that files does not exist, or if it does not contain this ID,
 return nil.
 The position is returned as a cons cell (file-name . position).  With
 optional argument MARKERP, return the position as a new marker."
-  (let (org-agenda-new-buffers buf pos)
+  (let (org-agenda-new-buffers buf pos)
     (cond
      ((not file) nil)
      ((not (file-exists-p file)) nil)
@@ -556,26 +596,42 @@ optional argument MARKERP, return the position as a new marker."
 ;; Calling the following function is hard-coded into `org-store-link',
 ;; so we do have to add it to `org-store-link-functions'.
 
+;;;###autoload
 (defun org-id-store-link ()
-  "Store a link to the current entry, using it's ID."
+  "Store a link to the current entry, using its ID."
   (interactive)
-  (let* ((link (org-make-link "id:" (org-id-get-create)))
-        (desc (save-excursion
-                (org-back-to-heading t)
-                (or (and (looking-at org-complex-heading-regexp)
-                         (if (match-end 4) (match-string 4) (match-string 0)))
-                    link))))
-    (org-store-link-props :link link :description desc :type "id")
-    link))
+  (when (and (buffer-file-name (buffer-base-buffer)) (org-mode-p))
+    (let* ((link (org-make-link "id:" (org-id-get-create)))
+          (case-fold-search nil)
+          (desc (save-excursion
+                  (org-back-to-heading t)
+                  (or (and (looking-at org-complex-heading-regexp)
+                           (if (match-end 4)
+                               (match-string 4)
+                             (match-string 0)))
+                      link))))
+      (org-store-link-props :link link :description desc :type "id")
+      link)))
 
 (defun org-id-open (id)
   "Go to the entry with id ID."
   (org-mark-ring-push)
-  (let ((m (org-id-find id 'marker)))
+  (let ((m (org-id-find id 'marker))
+       cmd)
     (unless m
       (error "Cannot find entry with ID \"%s\"" id))
+    ;; Use a buffer-switching command in analogy to finding files
+    (setq cmd
+         (or
+          (cdr
+           (assq
+            (cdr (assq 'file org-link-frame-setup))
+            '((find-file . switch-to-buffer)
+              (find-file-other-window . switch-to-buffer-other-window)
+              (find-file-other-frame . switch-to-buffer-other-frame))))
+          'switch-to-buffer-other-window))
     (if (not (equal (current-buffer) (marker-buffer m)))
-       (switch-to-buffer-other-window (marker-buffer m)))
+       (funcall cmd (marker-buffer m)))
     (goto-char m)
     (move-marker m nil)
     (org-show-context)))