]> code.delx.au - gnu-emacs/blobdiff - lisp/textmodes/bibtex.el
(pr-alist-custom-set, pr-ps-utility-custom-set)
[gnu-emacs] / lisp / textmodes / bibtex.el
index fd84d749b866372280a2ca8e8b6925d2fe383716..c82f2dcf3d08c161cbf28a5a8a9ad5e9b7c65c18 100644 (file)
@@ -1,7 +1,7 @@
 ;;; bibtex.el --- BibTeX mode for GNU Emacs
 
 ;; Copyright (C) 1992, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003,
-;;   2004, 2005 Free Software Foundation, Inc.
+;;   2004, 2005, 2006 Free Software Foundation, Inc.
 
 ;; Author: Stefan Schoef <schoef@offis.uni-oldenburg.de>
 ;;      Bengt Martensson <bengt@mathematik.uni-Bremen.de>
@@ -87,7 +87,8 @@ If this is a function, call it to generate the initial field text."
   :type '(choice (const :tag "None" nil)
                  (string :tag "Initial text")
                  (function :tag "Initialize Function" :value fun)
-                 (other :tag "Default" t)))
+                 (const :tag "Default" t)))
+(put 'bibtex-include-OPTkey 'risky-local-variable t)
 
 (defcustom bibtex-user-optional-fields
   '(("annote" "Personal annotation (ignored)"))
@@ -102,6 +103,7 @@ CROSSREF-OPTIONAL lists in `bibtex-entry-field-alist' (which see)."
                                        (choice :tag "Init" :value ""
                                                string
                                                function))))))
+(put 'bibtex-user-optional-fields 'risky-local-variable t)
 
 (defcustom bibtex-entry-format
   '(opts-or-alts required-fields numerical-fields)
@@ -151,7 +153,7 @@ narrowed to just the entry."
 (defcustom bibtex-maintain-sorted-entries nil
   "If non-nil, BibTeX mode maintains all entries in sorted order.
 Allowed non-nil values are:
-plain        All entries are sorted alphabetically.
+plain or t   All entries are sorted alphabetically.
 crossref     All entries are sorted alphabetically unless an entry has a
              crossref field.  These crossrefed entries are placed in
              alphabetical order immediately preceding the main entry.
@@ -163,7 +165,10 @@ See also `bibtex-sort-ignore-string-entries'."
   :type '(choice (const nil)
                  (const plain)
                  (const crossref)
-                 (const entry-class)))
+                 (const entry-class)
+                 (const t)))
+(put 'bibtex-maintain-sorted-entries 'safe-local-variable
+     '(lambda (a) (memq a '(nil t plain crossref entry-class))))
 
 (defcustom bibtex-sort-entry-class
   '(("String")
@@ -178,6 +183,17 @@ to all entries not explicitly mentioned."
   :type '(repeat (choice :tag "Class"
                          (const :tag "catch-all" (catch-all))
                          (repeat :tag "Entry name" string))))
+(put 'bibtex-sort-entry-class 'safe-local-variable
+     (lambda (x) (let ((OK t))
+              (while (consp x)
+                (let ((y (pop x)))
+                  (while (consp y)
+                    (let ((z (pop y)))
+                      (unless (or (stringp z) (eq z 'catch-all))
+                        (setq OK nil))))
+                  (unless (null y) (setq OK nil))))
+              (unless (null x) (setq OK nil))
+              OK)))
 
 (defcustom bibtex-sort-ignore-string-entries t
   "If non-nil, BibTeX @String entries are not sort-significant.
@@ -437,6 +453,7 @@ field or a function, which is called to determine the initial content
 of the field, and ALTERNATIVE-FLAG (either nil or t) marks if the
 field is an alternative.  ALTERNATIVE-FLAG may be t only in the
 REQUIRED or CROSSREF-REQUIRED lists.")
+(put 'bibtex-entry-field-alist 'risky-local-variable t)
 
 (defcustom bibtex-comment-start "@Comment"
   "String starting a BibTeX comment."
@@ -595,7 +612,7 @@ See `bibtex-generate-autokey' for details."
   :type '(repeat (cons (regexp :tag "Old")
                        (string :tag "New"))))
 
-(defcustom bibtex-autokey-name-case-convert 'downcase
+(defcustom bibtex-autokey-name-case-convert-function 'downcase
   "Function called for each name to perform case conversion.
 See `bibtex-generate-autokey' for details."
   :group 'bibtex-autokey
@@ -604,6 +621,10 @@ See `bibtex-generate-autokey' for details."
                  (const :tag "Capitalize" capitalize)
                  (const :tag "Upcase" upcase)
                  (function :tag "Conversion function")))
+(put 'bibtex-autokey-name-case-convert-function 'safe-local-variable
+     (lambda (x) (memq x '(upcase downcase capitalize identity))))
+(defvaralias 'bibtex-autokey-name-case-convert
+  'bibtex-autokey-name-case-convert-function)
 
 (defcustom bibtex-autokey-name-length 'infty
   "Number of characters from name to incorporate into key.
@@ -666,7 +687,7 @@ See `bibtex-generate-autokey' for details."
   :group 'bibtex-autokey
   :type '(repeat regexp))
 
-(defcustom bibtex-autokey-titleword-case-convert 'downcase
+(defcustom bibtex-autokey-titleword-case-convert-function 'downcase
   "Function called for each titleword to perform case conversion.
 See `bibtex-generate-autokey' for details."
   :group 'bibtex-autokey
@@ -675,6 +696,8 @@ See `bibtex-generate-autokey' for details."
                  (const :tag "Capitalize" capitalize)
                  (const :tag "Upcase" upcase)
                  (function :tag "Conversion function")))
+(defvaralias 'bibtex-autokey-titleword-case-convert
+  'bibtex-autokey-titleword-case-convert-function)
 
 (defcustom bibtex-autokey-titleword-abbrevs nil
   "Determines exceptions to the usual abbreviation mechanism.
@@ -847,6 +870,7 @@ The following is a complex example, see http://link.aps.org/linkfaq.html.
                                (choice (string :tag "Replacement")
                                       (integer :tag "Sub-match")
                                       (function :tag "Filter"))))))))
+(put 'bibtex-generate-url-list 'risky-local-variable t)
 
 (defcustom bibtex-expand-strings nil
   "If non-nil, expand strings when extracting the content of a BibTeX field."
@@ -1049,6 +1073,7 @@ At most `bibtex-entry-kill-ring-max' items are kept here.")
   "Completion table for BibTeX string keys.
 Initialized from `bibtex-predefined-strings' and `bibtex-string-files'.")
 (make-variable-buffer-local 'bibtex-strings)
+(put 'bibtex-strings 'risky-local-variable t)
 
 (defvar bibtex-reference-keys
   (lazy-completion-table bibtex-reference-keys
@@ -1056,6 +1081,7 @@ Initialized from `bibtex-predefined-strings' and `bibtex-string-files'.")
   "Completion table for BibTeX reference keys.
 The CDRs of the elements are t for header keys and nil for crossref keys.")
 (make-variable-buffer-local 'bibtex-reference-keys)
+(put 'bibtex-reference-keys 'risky-local-variable t)
 
 (defvar bibtex-buffer-last-parsed-tick nil
   "Value of `buffer-modified-tick' last time buffer was parsed for keys.")
@@ -1162,7 +1188,7 @@ The CDRs of the elements are t for header keys and nil for crossref keys.")
     (,(concat "^[ \t]*\\(" bibtex-field-name "\\)[ \t]*=")
      1 font-lock-variable-name-face)
     ;; url
-    bibtex-font-lock-url bibtex-font-lock-crossref)
+    (bibtex-font-lock-url) (bibtex-font-lock-crossref))
   "*Default expressions to highlight in BibTeX mode.")
 
 (defvar bibtex-font-lock-url-regexp
@@ -1175,13 +1201,7 @@ The CDRs of the elements are t for header keys and nil for crossref keys.")
 (defvar bibtex-string-empty-key nil
   "If non-nil, `bibtex-parse-string' accepts empty key.")
 
-(defvar bibtex-sort-entry-class-alist
-  (let ((i -1) alist)
-    (dolist (class bibtex-sort-entry-class alist)
-      (setq i (1+ i))
-      (dolist (entry class)
-        ;; all entry names should be downcase (for ease of comparison)
-        (push (cons (if (stringp entry) (downcase entry) entry) i) alist))))
+(defvar bibtex-sort-entry-class-alist nil
   "Alist mapping entry types to their sorting index.
 Auto-generated from `bibtex-sort-entry-class'.
 Used when `bibtex-maintain-sorted-entries' is `entry-class'.")
@@ -1790,7 +1810,8 @@ Formats current entry according to variable `bibtex-entry-format'."
 
         ;; identify entry type
         (goto-char (point-min))
-        (re-search-forward bibtex-entry-type)
+        (or (re-search-forward bibtex-entry-type nil t)
+            (error "Not inside a BibTeX entry"))
         (let ((beg-type (1+ (match-beginning 0)))
               (end-type (match-end 0)))
           (setq entry-list (assoc-string (buffer-substring-no-properties
@@ -2083,7 +2104,7 @@ and `bibtex-autokey-names-stretch'."
                       ;; --> take the last token
                       (match-string 1 fullname))
                      (t (error "Name `%s' is incorrectly formed" fullname)))))
-    (funcall bibtex-autokey-name-case-convert
+    (funcall bibtex-autokey-name-case-convert-function
              (bibtex-autokey-abbrev name bibtex-autokey-name-length))))
 
 (defun bibtex-autokey-get-year ()
@@ -2145,7 +2166,7 @@ and `bibtex-autokey-titleword-length'."
       (setq alist (cdr alist)))
     (if alist
         (cdar alist)
-      (funcall bibtex-autokey-titleword-case-convert
+      (funcall bibtex-autokey-titleword-case-convert-function
                (bibtex-autokey-abbrev titleword bibtex-autokey-titleword-length)))))
 
 (defun bibtex-generate-autokey ()
@@ -2165,7 +2186,7 @@ The name part:
     take at least `bibtex-autokey-name-length' characters (truncate only
     after a consonant or at a word end).
  5. Convert all last names using the function
-    `bibtex-autokey-name-case-convert'.
+    `bibtex-autokey-name-case-convert-function'.
  6. Build the name part of the key by concatenating all abbreviated last
     names with the string `bibtex-autokey-name-separator' between any two.
     If there are more names in the name field than names used in the name
@@ -2197,7 +2218,7 @@ The title part
     `bibtex-autokey-titleword-length' characters (truncate only after
     a consonant or at a word end).
  5. Convert all title words using the function
-    `bibtex-autokey-titleword-case-convert'.
+    `bibtex-autokey-titleword-case-convert-function'.
  6. Build the title part by concatenating all abbreviated title words with
     the string `bibtex-autokey-titleword-separator' between any two.
 
@@ -2470,6 +2491,7 @@ already set."
   "Complete word fragment before point to longest prefix of COMPLETIONS.
 COMPLETIONS is an alist of strings.  If point is not after the part
 of a word, all strings are listed.  Return completion."
+  ;; Return value is used by cleanup functions.
   (let* ((case-fold-search t)
          (beg (save-excursion
                 (re-search-backward "[ \t{\"]")
@@ -2492,7 +2514,6 @@ of a word, all strings are listed.  Return completion."
              (display-completion-list (all-completions part-of-word completions)
                                      part-of-word))
            (message "Making completion list...done")
-           ;; return value is handled by choose-completion-string-functions
            nil))))
 
 (defun bibtex-complete-string-cleanup (str compl)
@@ -2529,7 +2550,7 @@ Use `bibtex-summary-function' to generate summary."
 Used as default value of `bibtex-summary-function'."
   ;; It would be neat to customize this function.  How?
   (if (looking-at bibtex-entry-maybe-empty-head)
-      (let* ((bibtex-autokey-name-case-convert 'identity)
+      (let* ((bibtex-autokey-name-case-convert-function 'identity)
              (bibtex-autokey-name-length 'infty)
              (bibtex-autokey-names 1)
              (bibtex-autokey-names-stretch 0)
@@ -2540,7 +2561,7 @@ Used as default value of `bibtex-summary-function'."
              (year (bibtex-autokey-get-year))
              (bibtex-autokey-titlewords 5)
              (bibtex-autokey-titlewords-stretch 2)
-             (bibtex-autokey-titleword-case-convert 'identity)
+             (bibtex-autokey-titleword-case-convert-function 'identity)
              (bibtex-autokey-titleword-length 5)
              (bibtex-autokey-titleword-separator " ")
              (title (bibtex-autokey-get-title))
@@ -2847,25 +2868,45 @@ is non-nil."
        (bibtex-autofill-entry))
     (run-hooks 'bibtex-add-entry-hook)))
 
-(defun bibtex-entry-update ()
+(defun bibtex-entry-update (&optional entry-type)
   "Update an existing BibTeX entry.
 In the BibTeX entry at point, make new fields for those items that may occur
-according to `bibtex-field-list', but are not yet present."
-  (interactive)
+according to `bibtex-field-list', but are not yet present.
+Also, add field delimiters to numerical fields if they are not present.
+If ENTRY-TYPE is non-nil, change first the entry type to ENTRY-TYPE.
+When called interactively with a prefix arg, query for a value of ENTRY-TYPE."
+  (interactive
+   (list (if current-prefix-arg
+             (let ((completion-ignore-case t))
+               (completing-read "New entry type: " bibtex-entry-field-alist
+                                nil t nil 'bibtex-entry-type-history)))))
   (save-excursion
     (bibtex-beginning-of-entry)
-    ;; For inserting new fields, we use the fact that
-    ;; `bibtex-parse-entry' moves point to the end of the last field.
-    (let* ((fields-alist (bibtex-parse-entry))
-           (field-list (bibtex-field-list
-                        (cdr (assoc "=type=" fields-alist)))))
-      (skip-chars-backward " \t\n")
-      (dolist (field (car field-list))
-        (unless (assoc-string (car field) fields-alist t)
-          (bibtex-make-field field)))
-      (dolist (field (cdr field-list))
-        (unless (assoc-string (car field) fields-alist t)
-          (bibtex-make-optional-field field))))))
+    (when (looking-at bibtex-entry-maybe-empty-head)
+      (goto-char (match-end 0))
+      (if entry-type
+          (save-excursion
+            (replace-match (concat "@" entry-type) nil nil nil 1))
+        (setq entry-type (bibtex-type-in-head)))
+      (let* ((field-list (bibtex-field-list entry-type))
+             (required (copy-tree (car field-list)))
+             (optional (copy-tree (cdr field-list)))
+             bounds)
+        (while (setq bounds (bibtex-parse-field))
+          (let ((fname (bibtex-name-in-field bounds t))
+                (end (copy-marker (bibtex-end-of-field bounds) t)))
+            (setq required (delete (assoc-string fname required t) required)
+                  optional (delete (assoc-string fname optional t) optional))
+            (when (string-match "\\`[0-9]+\\'"
+                                (bibtex-text-in-field-bounds bounds))
+              (goto-char (bibtex-end-of-text-in-field bounds))
+              (insert (bibtex-field-right-delimiter))
+              (goto-char (bibtex-start-of-text-in-field bounds))
+              (insert (bibtex-field-left-delimiter)))
+            (goto-char end)))
+        (skip-chars-backward " \t\n")
+        (dolist (field required) (bibtex-make-field field))
+        (dolist (field optional) (bibtex-make-optional-field field))))))
 
 (defun bibtex-parse-entry (&optional content)
   "Parse entry at point, return an alist.
@@ -3154,6 +3195,17 @@ of the head of the entry found.  Return nil if no entry found."
                       entry-name))
             (list key nil entry-name))))))
 
+(defun bibtex-init-sort-entry-class-alist ()
+  (unless (local-variable-p 'bibtex-sort-entry-class-alist)
+    (set (make-local-variable 'bibtex-sort-entry-class-alist)
+         (let ((i -1) alist)
+           (dolist (class bibtex-sort-entry-class alist)
+             (setq i (1+ i))
+             (dolist (entry class)
+               ;; All entry names should be downcase (for ease of comparison).
+               (push (cons (if (stringp entry) (downcase entry) entry) i)
+                     alist)))))))
+
 (defun bibtex-lessp (index1 index2)
   "Predicate for sorting BibTeX entries with indices INDEX1 and INDEX2.
 Each index is a list (KEY CROSSREF-KEY ENTRY-NAME).
@@ -3191,13 +3243,14 @@ If its value is nil use plain sorting.  Text outside of BibTeX entries is not
 affected.  If `bibtex-sort-ignore-string-entries' is non-nil, @String entries
 are ignored."
   (interactive)
-    (bibtex-beginning-of-first-entry) ;; needed by `sort-subr'
-    (sort-subr nil
-               'bibtex-skip-to-valid-entry ; NEXTREC function
-               'bibtex-end-of-entry        ; ENDREC function
-               'bibtex-entry-index         ; STARTKEY function
-               nil                         ; ENDKEY function
-               'bibtex-lessp))             ; PREDICATE
+  (bibtex-beginning-of-first-entry)     ; Needed by `sort-subr'
+  (bibtex-init-sort-entry-class-alist)  ; Needed by `bibtex-lessp'.
+  (sort-subr nil
+             'bibtex-skip-to-valid-entry   ; NEXTREC function
+             'bibtex-end-of-entry          ; ENDREC function
+             'bibtex-entry-index           ; STARTKEY function
+             nil                           ; ENDKEY function
+             'bibtex-lessp))               ; PREDICATE
 
 (defun bibtex-find-crossref (crossref-key &optional pnt split)
   "Move point to the beginning of BibTeX entry CROSSREF-KEY.
@@ -3274,7 +3327,7 @@ Otherwise, use `set-buffer'.  DISPLAY is t when called interactively."
               (display (message "Key `%s' not found" key)))
         found)
 
-    (let* (case-fold-search
+    (let* ((case-fold-search t)
            (pnt (save-excursion
                   (goto-char (or start (point-min)))
                   (if (re-search-forward (concat "^[ \t]*\\("
@@ -3298,6 +3351,7 @@ If `bibtex-maintain-sorted-entries' is non-nil, perform a binary
 search to look for place for KEY.  This requires that buffer is sorted,
 see `bibtex-validate'.
 Return t if preparation was successful or nil if entry KEY already exists."
+  (bibtex-init-sort-entry-class-alist)  ; Needed by `bibtex-lessp'.
   (let ((key (nth 0 index))
         key-exist)
     (cond ((or (null key)
@@ -3538,7 +3592,7 @@ Return t if test was successful, nil otherwise."
           (dolist (key (cdr (assq buffer buffer-key-list)))
             (when (assoc-string key current-keys)
               (bibtex-find-entry key)
-              (push (format "%s:%d: Duplicat key `%s' in %s\n"
+              (push (format "%s:%d: Duplicate key `%s' in %s\n"
                             (buffer-file-name) (bibtex-current-line) key
                             (abbreviate-file-name (buffer-file-name buffer)))
                     error-list))))))
@@ -3604,17 +3658,20 @@ interactive calls."
 
 (defun bibtex-find-text-internal (&optional noerror subfield comma)
   "Find text part of current BibTeX field or entry head.
-Return list (NAME START-TEXT END-TEXT END) with field or entry name,
-start and end of text and end of field or entry head, or nil if not found.
-If optional arg NOERROR is non-nil, an error message is suppressed if text
-is not found.  If optional arg SUBFIELD is non-nil START-TEXT and END-TEXT
-correspond to the current subfield delimited by #.
+Return list (NAME START-TEXT END-TEXT END STRING-CONST) with field
+or entry name, start and end of text, and end of field or entry head.
+STRING-CONST is a flag which is non-nil if current subfield delimited by #
+is a BibTeX string constant.  Return value is nil if field or entry head
+are not found.
+If optional arg NOERROR is non-nil, an error message is suppressed
+if text is not found.  If optional arg SUBFIELD is non-nil START-TEXT
+and END-TEXT correspond to the current subfield delimited by #.
 Optional arg COMMA is as in `bibtex-enclosing-field'."
   (save-excursion
     (let ((pnt (point))
           (bounds (bibtex-enclosing-field comma t))
           (case-fold-search t)
-          name start-text end-text end failure done no-sub)
+          name start-text end-text end failure done no-sub string-const)
       (bibtex-beginning-of-entry)
       (cond (bounds
              (setq name (bibtex-name-in-field bounds t)
@@ -3658,9 +3715,11 @@ Optional arg COMMA is as in `bibtex-enclosing-field'."
           (goto-char start-text)
           (while (not done)
             (if (or (prog1 (looking-at bibtex-field-const)
-                      (setq end-text (match-end 0)))
+                      (setq end-text (match-end 0)
+                            string-const t))
                     (prog1 (setq bounds (bibtex-parse-field-string))
-                      (setq end-text (cdr bounds))))
+                      (setq end-text (cdr bounds)
+                            string-const nil)))
                 (progn
                   (if (and (<= start-text pnt) (<= pnt end-text))
                       (setq done t)
@@ -3669,7 +3728,7 @@ Optional arg COMMA is as in `bibtex-enclosing-field'."
                       (setq start-text (goto-char (match-end 0)))))
               (setq done t failure t)))))
       (cond ((not failure)
-             (list name start-text end-text end))
+             (list name start-text end-text end string-const))
             ((and no-sub (not noerror))
              (error "Not on text part of BibTeX field"))
             ((not noerror) (error "Not on BibTeX field"))))))
@@ -3703,13 +3762,10 @@ is as in `bibtex-enclosing-field'.  It is t for interactive calls."
 Optional arg COMMA is as in `bibtex-enclosing-field'.  It is t for
 interactive calls."
   (interactive (list t))
-  (let* ((bounds (bibtex-find-text-internal nil t comma))
-         (start (nth 1 bounds))
-         (end (nth 2 bounds)))
-    (if (memq (char-before end) '(?\} ?\"))
-        (delete-region (1- end) end))
-    (if (memq (char-after start) '(?\{ ?\"))
-        (delete-region start (1+ start)))))
+  (let ((bounds (bibtex-find-text-internal nil t comma)))
+    (unless (nth 4 bounds)
+      (delete-region (1- (nth 2 bounds)) (nth 2 bounds))
+      (delete-region (nth 1 bounds) (1+ (nth 1 bounds))))))
 
 (defun bibtex-kill-field (&optional copy-only comma)
   "Kill the entire enclosing BibTeX field.
@@ -3844,7 +3900,8 @@ At end of the cleaning process, the functions in
   (interactive "P")
   (let ((case-fold-search t)
         (start (bibtex-beginning-of-entry))
-        (_ (looking-at bibtex-any-entry-maybe-empty-head))
+        (_ (or (looking-at bibtex-any-entry-maybe-empty-head)
+              (error "Not inside a BibTeX entry")))
         (entry-type (bibtex-type-in-head))
         (key (bibtex-key-in-head)))
     ;; formatting
@@ -4152,24 +4209,31 @@ An error is signaled if point is outside key or BibTeX field."
 
     (cond ((eq compl 'key)
            ;; key completion: no cleanup needed
+           (setq choose-completion-string-functions nil)
            (let (completion-ignore-case)
              (bibtex-complete-internal (bibtex-global-key-alist))))
 
           ((eq compl 'crossref-key)
            ;; crossref key completion
+           ;;
+           ;; If we quit the *Completions* buffer without requesting
+           ;; a completion, `choose-completion-string-functions' is still
+           ;; non-nil. Therefore, `choose-completion-string-functions' is
+           ;; always set (either to non-nil or nil) when a new completion
+           ;; is requested.
            (let (completion-ignore-case)
              (setq choose-completion-string-functions
                    (lambda (choice buffer mini-p base-size)
-                     (let ((choose-completion-string-functions nil))
-                       (choose-completion-string choice buffer base-size))
+                     (setq choose-completion-string-functions nil)
+                     (choose-completion-string choice buffer base-size)
                      (bibtex-complete-crossref-cleanup choice)
-                     ;; return t (needed by choose-completion-string-functions)
-                     t))
-             (bibtex-complete-crossref-cleanup (bibtex-complete-internal
-                                                (bibtex-global-key-alist)))))
+                     t)) ; needed by choose-completion-string-functions
+             (bibtex-complete-crossref-cleanup
+              (bibtex-complete-internal (bibtex-global-key-alist)))))
 
           ((eq compl 'string)
            ;; string key completion: no cleanup needed
+           (setq choose-completion-string-functions nil)
            (let ((completion-ignore-case t))
              (bibtex-complete-internal bibtex-strings)))
 
@@ -4178,15 +4242,15 @@ An error is signaled if point is outside key or BibTeX field."
            (let ((completion-ignore-case t))
              (setq choose-completion-string-functions
                    `(lambda (choice buffer mini-p base-size)
-                      (let ((choose-completion-string-functions nil))
-                        (choose-completion-string choice buffer base-size))
+                      (setq choose-completion-string-functions nil)
+                      (choose-completion-string choice buffer base-size)
                       (bibtex-complete-string-cleanup choice ',compl)
-                      ;; return t (needed by choose-completion-string-functions)
-                      t))
+                      t)) ; needed by choose-completion-string-functions
              (bibtex-complete-string-cleanup (bibtex-complete-internal compl)
                                              compl)))
 
-          (t (error "Point outside key or BibTeX field")))))
+          (t (setq choose-completion-string-functions nil)
+             (error "Point outside key or BibTeX field")))))
 
 (defun bibtex-Article ()
   "Insert a new BibTeX @Article entry; see also `bibtex-entry'."
@@ -4292,11 +4356,12 @@ An error is signaled if point is outside key or BibTeX field."
             "\n")
     (goto-char endpos)))
 
-(defun bibtex-url (&optional pos)
+(defun bibtex-url (&optional pos no-browse)
   "Browse a URL for the BibTeX entry at point.
 Optional POS is the location of the BibTeX entry.
 The URL is generated using the schemes defined in `bibtex-generate-url-list'
-\(see there\).  Then the URL is passed to `browse-url'."
+\(see there\).  Then the URL is passed to `browse-url' unless NO-BROWSE is nil.
+Return the URL or nil if none can be generated."
   (interactive)
   (save-excursion
     (if pos (goto-char pos))
@@ -4319,20 +4384,22 @@ The URL is generated using the schemes defined in `bibtex-generate-url-list'
                       (dolist (step scheme)
                         (setq field (cdr (assoc-string (car step) fields-alist t)))
                         (if (string-match (nth 1 step) field)
-                            (setq field (cond ((functionp (nth 2 step))
-                                               (funcall (nth 2 step) field))
-                                              ((numberp (nth 2 step))
-                                               (match-string (nth 2 step) field))
-                                              (t
-                                               (replace-match (nth 2 step) t nil field))))
+                            (push (cond ((functionp (nth 2 step))
+                                         (funcall (nth 2 step) field))
+                                        ((numberp (nth 2 step))
+                                         (match-string (nth 2 step) field))
+                                        (t
+                                         (replace-match (nth 2 step) t nil field)))
+                                  obj)
                           ;; If the scheme is set up correctly,
                           ;; we should never reach this point
-                          (error "Match failed: %s" field))
-                        (push field obj))
+                          (error "Match failed: %s" field)))
                       (if fmt (apply 'format fmt (nreverse obj))
                         (apply 'concat (nreverse obj)))))
-          (browse-url (message "%s" url))))
-      (unless url (message "No URL known.")))))
+          (if (interactive-p) (message "%s" url))
+          (unless no-browse (browse-url url))))
+      (if (and (not url) (interactive-p)) (message "No URL known."))
+      url)))
 
 \f
 ;; Make BibTeX a Feature