;;; 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>
: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)"))
(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)
(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.
: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")
: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.
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."
: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
(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.
: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
(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.
(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."
"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
"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.")
(,(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
(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'.")
;; 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
;; --> 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 ()
(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 ()
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
`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.
"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{\"]")
(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)
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)
(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))
(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.
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).
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.
(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]*\\("
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)
(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))))))
(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)
(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)
(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"))))))
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.
(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
(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)))
(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'."
"\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))
(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