;;; bibtex.el --- BibTeX mode for GNU Emacs
-;; Copyright (C) 1992, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003,
-;; 2004, 2005 Free Software Foundation, Inc.
+;; Copyright (C) 1992, 1994, 1995, 1996, 1997, 1998, 1999, 2001, 2002,
+;; 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
;; Author: Stefan Schoef <schoef@offis.uni-oldenburg.de>
;; Bengt Martensson <bengt@mathematik.uni-Bremen.de>
-;; Mark Shapiro <shapiro@corto.inria.fr>
+;; Marc Shapiro <marc.shapiro@acm.org>
;; Mike Newton <newton@gumby.cs.caltech.edu>
;; Aaron Larson <alarson@src.honeywell.com>
;; Dirk Herrmann <D.Herrmann@tu-bs.de>
;; Major mode for editing and validating BibTeX files.
;; Usage:
-;; See documentation for function bibtex-mode or type "\M-x describe-mode"
+;; See documentation for `bibtex-mode' or type "M-x describe-mode"
;; when you are in BibTeX mode.
;; Todo:
:group 'bibtex
:type '(choice (const :tag "None" nil)
(string :tag "Initial text")
- (function :tag "Initialize Function" :value fun)
- (other :tag "Default" t)))
+ (function :tag "Initialize Function")
+ (const :tag "Default" t)))
+(put 'bibtex-include-OPTkey 'risky-local-variable t)
(defcustom bibtex-user-optional-fields
'(("annote" "Personal annotation (ignored)"))
:group 'bibtex
:type '(repeat (group (string :tag "Field")
(string :tag "Comment")
- (option (group :inline t
- :extra-offset -4
- (choice :tag "Init" :value ""
- string
- function))))))
+ (option (choice :tag "Init"
+ (const nil) string function)))))
+(put 'bibtex-user-optional-fields 'risky-local-variable t)
(defcustom bibtex-entry-format
'(opts-or-alts required-fields numerical-fields)
numerical-fields Delete delimiters around numeral fields.
page-dashes Change double dashes in page field to single dash
(for scribe compatibility).
+whitespace Delete whitespace at the beginning and end of fields.
inherit-booktitle If entry contains a crossref field and the booktitle
field is empty, set the booktitle field to the content
of the title field of the crossreferenced entry.
delimiters Change delimiters according to variables
`bibtex-field-delimiters' and `bibtex-entry-delimiters'.
unify-case Change case of entry and field names.
+braces Enclose parts of field entries by braces according to
+ `bibtex-field-braces-alist'.
+strings Replace parts of field entries by string constants
+ according to `bibtex-field-strings-alist'.
The value t means do all of the above formatting actions.
The value nil means do no formatting at all."
(const required-fields)
(const numerical-fields)
(const page-dashes)
+ (const whitespace)
(const inherit-booktitle)
(const realign)
(const last-comma)
(const delimiters)
- (const unify-case))))
+ (const unify-case)
+ (const braces)
+ (const strings))))
+
+(defcustom bibtex-field-braces-alist nil
+ "Alist of field regexps that \\[bibtex-clean-entry] encloses by braces.
+Each element has the form (FIELDS REGEXP), where FIELDS is a list
+of BibTeX field names and REGEXP is a regexp.
+Whitespace in REGEXP will be replaced by \"[ \\t\\n]+\"."
+ :group 'bibtex
+ :type '(repeat (list (repeat (string :tag "field name"))
+ (choice (regexp :tag "regexp")
+ (sexp :tag "sexp")))))
+
+(defcustom bibtex-field-strings-alist nil
+ "Alist of regexps that \\[bibtex-clean-entry] replaces by string constants.
+Each element has the form (FIELDS REGEXP TO-STR), where FIELDS is a list
+of BibTeX field names. In FIELDS search for REGEXP, which are replaced
+by the BibTeX string constant TO-STR.
+Whitespace in REGEXP will be replaced by \"[ \\t\\n]+\"."
+ :group 'bibtex
+ :type '(repeat (list (repeat (string :tag "field name"))
+ (regexp :tag "From regexp")
+ (regexp :tag "To string constant"))))
(defcustom bibtex-clean-entry-hook nil
"List of functions to call when entry has been cleaned.
(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.
:group 'bibtex
:type 'boolean)
-(defvar bibtex-entry-field-alist
+(defcustom bibtex-entry-field-alist
'(("Article"
((("author" "Author1 [and Author2 ...] [and others]")
("title" "Title of the article (BibTeX converts it to lowercase)")
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.")
+REQUIRED or CROSSREF-REQUIRED lists."
+ :group 'bibtex
+ :type '(repeat (group (string :tag "Entry name")
+ (group (repeat :tag "Required fields"
+ (group (string :tag "Field")
+ (string :tag "Comment")
+ (option (choice :tag "Init" :value nil
+ (const nil) string function))
+ (option (choice :tag "Alternative"
+ (const :tag "No" nil)
+ (const :tag "Yes" t)))))
+ (repeat :tag "Optional fields"
+ (group (string :tag "Field")
+ (string :tag "Comment")
+ (option (choice :tag "Init" :value nil
+ (const nil) string function)))))
+ (option :extra-offset -4
+ (group (repeat :tag "Crossref: required fields"
+ (group (string :tag "Field")
+ (string :tag "Comment")
+ (option (choice :tag "Init" :value nil
+ (const nil) string function))
+ (option (choice :tag "Alternative"
+ (const :tag "No" nil)
+ (const :tag "Yes" t)))))
+ (repeat :tag "Crossref: optional fields"
+ (group (string :tag "Field")
+ (string :tag "Comment")
+ (option (choice :tag "Init" :value nil
+ (const nil) string function)))))))))
+(put 'bibtex-entry-field-alist 'risky-local-variable t)
(defcustom bibtex-comment-start "@Comment"
"String starting a BibTeX comment."
check all BibTeX files in this directory. If an element is the symbol
`bibtex-file-path', check all BibTeX files in `bibtex-file-path'."
:group 'bibtex
- :type '(repeat file))
+ :type '(repeat (choice (const :tag "bibtex-file-path" bibtex-file-path)
+ directory file)))
(defvar bibtex-file-path (getenv "BIBINPUTS")
"*Colon separated list of paths to search for `bibtex-files'.")
: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-cite-matcher-alist
+ '(("\\\\cite[ \t\n]*{\\([^}]+\\)}" . 1))
+ "Alist of rules to identify cited keys in a BibTeX entry.
+Each rule should be of the form (REGEXP . SUBEXP), where SUBEXP
+specifies which parenthesized expression in REGEXP is a cited key.
+Case is significant.
+Used by `bibtex-find-crossref' and for font-locking."
+ :group 'bibtex
+ :type '(repeat (cons (regexp :tag "Regexp")
+ (integer :tag "Number"))))
(defcustom bibtex-expand-strings nil
"If non-nil, expand strings when extracting the content of a BibTeX field."
:group 'bibtex
:type 'boolean)
-;; `bibtex-font-lock-keywords' is a user option as well, but since the
+;; `bibtex-font-lock-keywords' is a user option, too. But since the
;; patterns used to define this variable are defined in a later
;; section of this file, it is defined later.
\f
;; Internal Variables
+(defvar bibtex-field-braces-opt nil
+ "Optimized value of `bibtex-field-braces-alist'.
+Created by `bibtex-field-re-init'.
+It is a an alist with elements (FIELD . REGEXP).")
+
+(defvar bibtex-field-strings-opt nil
+ "Optimized value of `bibtex-field-strings-alist'.
+Created by `bibtex-field-re-init'.
+It is a an alist with elements (FIELD RULE1 RULE2 ...),
+where each RULE is (REGEXP . TO-STR).")
+
(defvar bibtex-pop-previous-search-point nil
"Next point where `bibtex-pop-previous' starts looking for a similar entry.")
"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.")
"Regexp matching the name of a BibTeX field.")
(defconst bibtex-name-part
- (concat ",[ \t\n]*\\(" bibtex-field-name "\\)[ \t\n]*=")
+ (concat ",[ \t\n]*\\(" bibtex-field-name "\\)")
"Regexp matching the name part of a BibTeX field.")
(defconst bibtex-reference-key "[][[:alnum:].:;?!`'/*@+|()<>&_^$-]+"
(regexp-opt (mapcar 'car bibtex-entry-field-alist)) "\\)")
"Regexp matching the name of a BibTeX entry.")
-(defvar bibtex-entry-type-whitespace
- (concat "[ \t]*" bibtex-entry-type)
- "Regexp matching the name of a BibTeX entry preceded by whitespace.")
-
-(defvar bibtex-entry-type-str
- (concat "@[ \t]*\\(?:"
- (regexp-opt (append '("String")
- (mapcar 'car bibtex-entry-field-alist))) "\\)")
- "Regexp matching the name of a BibTeX entry (including @String).")
-
(defvar bibtex-entry-head
(concat "^[ \t]*\\("
bibtex-entry-type
bibtex-reference-key "\\)?")
"Regexp matching the header line of any BibTeX entry (possibly without key).")
+(defvar bibtex-any-valid-entry-type
+ (concat "^[ \t]*@[ \t]*\\(?:"
+ (regexp-opt (append '("String" "Preamble")
+ (mapcar 'car bibtex-entry-field-alist))) "\\)")
+ "Regexp matching any valid BibTeX entry (including String and Preamble).")
+
(defconst bibtex-type-in-head 1
"Regexp subexpression number of the type part in `bibtex-entry-head'.")
(defconst bibtex-key-in-head 2
"Regexp subexpression number of the key part in `bibtex-entry-head'.")
-(defconst bibtex-empty-field-re "\\`\\(\"\"\\|{}\\)\\'"
- "Regexp matching the text part (as a string) of an empty field.")
-
(defconst bibtex-string-type "^[ \t]*\\(@[ \t]*String\\)[ \t]*[({][ \t\n]*"
"Regexp matching the name of a BibTeX String entry.")
(concat bibtex-string-type "\\(" bibtex-reference-key "\\)?")
"Regexp matching the header line of a BibTeX String entry.")
-(defconst bibtex-preamble-prefix "[ \t]*@[ \t]*Preamble[ \t]*"
- "Regexp matching the prefix part of a preamble.")
+(defconst bibtex-preamble-prefix
+ "[ \t]*\\(@[ \t]*Preamble\\)[ \t]*[({][ \t\n]*"
+ "Regexp matching the prefix part of a BibTeX Preamble entry.")
(defconst bibtex-font-lock-syntactic-keywords
`((,(concat "^[ \t]*\\(" (substring bibtex-comment-start 0 1) "\\)"
(,(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)
+ ;; cite
+ ,@(mapcar (lambda (matcher)
+ `((lambda (bound) (bibtex-font-lock-cite ',matcher bound))))
+ bibtex-cite-matcher-alist))
"*Default expressions to highlight in BibTeX mode.")
(defvar bibtex-font-lock-url-regexp
(concat "^[ \t]*"
(regexp-opt (delete-dups (mapcar 'caar bibtex-generate-url-list)) t)
"[ \t]*=[ \t]*")
- "Regexp for `bibtex-font-lock-url'.")
+ "Regexp for `bibtex-font-lock-url' derived from `bibtex-generate-url-list'.")
(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'.")
part and end position of the match. Move point to end of field name.
If `bibtex-autoadd-commas' is non-nil add missing comma at end of preceding
BibTeX field as necessary."
- (cond ((looking-at ",[ \t\n]*")
- (let ((start (point)))
- (goto-char (match-end 0))
- (when (looking-at bibtex-field-name)
- (goto-char (match-end 0))
- (list start (match-beginning 0) (match-end 0)))))
+ (cond ((looking-at bibtex-name-part)
+ (goto-char (match-end 0))
+ (list (match-beginning 0) (match-beginning 1) (match-end 0)))
;; Maybe add a missing comma.
((and bibtex-autoadd-commas
(looking-at (concat "[ \t\n]*\\(?:" bibtex-field-name
"Search forward to find a BibTeX field of name NAME.
If a syntactically correct field is found, return a pair containing
the boundaries of the name and text parts of the field. The search
-is limited by optional arg BOUND or if nil by the end of the current
-entry. Do not move point."
+is limited by optional arg BOUND. If BOUND is t the search is limited
+by the end of the current entry. Do not move point."
(save-match-data
(save-excursion
- (if bound
- ;; If the search is bounded we need not worry we could overshoot.
- ;; This is indeed the case when `bibtex-search-forward-field' is
- ;; called many times. So we optimize this part of this function.
- (let ((name-part (concat ",[ \t\n]*\\(" name "\\)[ \t\n]*=[ \t\n]*"))
- (case-fold-search t) left right)
- (while (and (not right)
- (re-search-forward name-part bound t))
- (setq left (list (match-beginning 0) (match-beginning 1)
- (match-end 1))
- ;; Don't worry that the field text could be past bound.
- right (bibtex-parse-field-text)))
- (if right (cons left right)))
- (let ((regexp (concat bibtex-name-part "\\|"
- bibtex-any-entry-maybe-empty-head))
- (case-fold-search t) bounds)
- (catch 'done
- (if (looking-at "[ \t]*@") (goto-char (match-end 0)))
- (while (and (not bounds)
- (re-search-forward regexp nil t))
- (if (match-beginning 2)
- ;; We found a new entry
- (throw 'done nil)
- ;; We found a field
- (goto-char (match-beginning 0))
- (setq bounds (bibtex-parse-field))))
- ;; Step through all fields so that we cannot overshoot.
- (while bounds
- (goto-char (bibtex-start-of-name-in-field bounds))
- (if (looking-at name) (throw 'done bounds))
- (goto-char (bibtex-end-of-field bounds))
- (setq bounds (bibtex-parse-field)))))))))
+ (if (eq bound t)
+ (let ((regexp (concat bibtex-name-part "[ \t\n]*=\\|"
+ bibtex-any-entry-maybe-empty-head))
+ (case-fold-search t) bounds)
+ (catch 'done
+ (if (looking-at "[ \t]*@") (goto-char (match-end 0)))
+ (while (and (not bounds)
+ (re-search-forward regexp nil t))
+ (if (match-beginning 2)
+ ;; We found a new entry
+ (throw 'done nil)
+ ;; We found a field
+ (goto-char (match-beginning 0))
+ (setq bounds (bibtex-parse-field))))
+ ;; Step through all fields so that we cannot overshoot.
+ (while bounds
+ (goto-char (bibtex-start-of-name-in-field bounds))
+ (if (looking-at name) (throw 'done bounds))
+ (goto-char (bibtex-end-of-field bounds))
+ (setq bounds (bibtex-parse-field)))))
+ ;; Bounded search or bound is nil (i.e. we cannot overshoot).
+ ;; Indeed, the search is bounded when `bibtex-search-forward-field'
+ ;; is called many times. So we optimize this part of this function.
+ (let ((name-part (concat ",[ \t\n]*\\(" name "\\)[ \t\n]*=[ \t\n]*"))
+ (case-fold-search t) left right)
+ (while (and (not right)
+ (re-search-forward name-part bound t))
+ (setq left (list (match-beginning 0) (match-beginning 1)
+ (match-end 1))
+ ;; Don't worry that the field text could be past bound.
+ right (bibtex-parse-field-text)))
+ (if right (cons left right)))))))
(defun bibtex-search-backward-field (name &optional bound)
"Search backward to find a BibTeX field of name NAME.
If a syntactically correct field is found, return a pair containing
the boundaries of the name and text parts of the field. The search
-is limited by the optional arg BOUND. If BOUND is nil the search is
+is limited by the optional arg BOUND. If BOUND is t the search is
limited by the beginning of the current entry. Do not move point."
(save-match-data
- (save-excursion
- (let ((name-part (concat ",[ \t\n]*\\(?:" name "\\)[ \t\n]*="))
- (case-fold-search t)
- bounds)
- (unless bound (setq bound (save-excursion (bibtex-beginning-of-entry))))
- (while (and (not bounds)
- (search-backward "," bound t)
- (looking-at name-part))
- (setq bounds (bibtex-parse-field)))
- bounds))))
+ (if (eq bound t)
+ (setq bound (save-excursion (bibtex-beginning-of-entry))))
+ (let ((name-part (concat ",[ \t\n]*\\(" name "\\)[ \t\n]*=[ \t\n]*"))
+ (case-fold-search t) left right)
+ (save-excursion
+ ;; the parsing functions are not designed for parsing backwards :-(
+ (when (search-backward "," bound t)
+ (or (save-excursion
+ (when (looking-at name-part)
+ (setq left (list (match-beginning 0) (match-beginning 1)
+ (match-end 1)))
+ (goto-char (match-end 0))
+ (setq right (bibtex-parse-field-text))))
+ (while (and (not right)
+ (re-search-backward name-part bound t))
+ (setq left (list (match-beginning 0) (match-beginning 1)
+ (match-end 1)))
+ (save-excursion
+ (goto-char (match-end 0))
+ (setq right (bibtex-parse-field-text)))))
+ (if right (cons left right)))))))
(defun bibtex-name-in-field (bounds &optional remove-opt-alt)
"Get content of name in BibTeX field defined via BOUNDS.
If `bibtex-expand-strings' is non-nil, also expand BibTeX strings."
(if content
(save-excursion
+ (goto-char (bibtex-start-of-text-in-field bounds))
(let ((epoint (bibtex-end-of-text-in-field bounds))
- content opoint temp)
- (goto-char (bibtex-start-of-text-in-field bounds))
+ content opoint)
(while (< (setq opoint (point)) epoint)
- (cond ((looking-at bibtex-field-const)
- (let ((mtch (match-string-no-properties 0)))
- (goto-char (match-end 0))
- (setq temp (if bibtex-expand-strings
- (cdr (assoc-string mtch (bibtex-strings) t)))
- content (concat content (or temp mtch)))))
-
- ((setq temp (bibtex-parse-field-string))
- (setq content (concat content (buffer-substring-no-properties
- (1+ (car temp))
- (1- (cdr temp)))))
- (goto-char (cdr temp)))
- (t (error "Malformed text field")))
+ (if (looking-at bibtex-field-const)
+ (let ((mtch (match-string-no-properties 0)))
+ (push (or (if bibtex-expand-strings
+ (cdr (assoc-string mtch (bibtex-strings) t)))
+ mtch) content)
+ (goto-char (match-end 0)))
+ (let ((bounds (bibtex-parse-field-string)))
+ (push (buffer-substring-no-properties
+ (1+ (car bounds)) (1- (cdr bounds))) content)
+ (goto-char (cdr bounds))))
(re-search-forward "\\=[ \t\n]*#[ \t\n]*" nil t))
- content))
+ (apply 'concat (nreverse content))))
(buffer-substring-no-properties (bibtex-start-of-text-in-field bounds)
(bibtex-end-of-text-in-field bounds))))
Return nil if not found.
If optional arg FOLLOW-CROSSREF is non-nil, follow crossref."
(save-excursion
- (save-restriction
- ;; We want to jump back and forth while searching FIELD
- (bibtex-narrow-to-entry)
- (goto-char (point-min))
- (let ((bounds (bibtex-search-forward-field field (point-max)))
- crossref-field)
- (cond (bounds (bibtex-text-in-field-bounds bounds t))
- ((and follow-crossref
- (progn (goto-char (point-min))
- (setq bounds (bibtex-search-forward-field
- "\\(OPT\\)?crossref" (point-max)))))
- (setq crossref-field (bibtex-text-in-field-bounds bounds t))
- (widen)
+ (let* ((end (if follow-crossref (bibtex-end-of-entry) t))
+ (beg (bibtex-beginning-of-entry)) ; move point
+ (bounds (bibtex-search-forward-field field end)))
+ (cond (bounds (bibtex-text-in-field-bounds bounds t))
+ ((and follow-crossref
+ (progn (goto-char beg)
+ (setq bounds (bibtex-search-forward-field
+ "\\(OPT\\)?crossref" end))))
+ (let ((crossref-field (bibtex-text-in-field-bounds bounds t)))
(if (bibtex-find-crossref crossref-field)
;; Do not pass FOLLOW-CROSSREF because we want
;; to follow crossrefs only one level of recursion.
(nth 1 bounds)
(match-end 0))))))
-(defun bibtex-parse-string ()
+(defun bibtex-parse-string (&optional empty-key)
"Parse a BibTeX string entry beginning at the position of point.
If a syntactically correct entry is found, return a cons pair containing
the boundaries of the reference key and text parts of the entry.
-Do not move point."
- (bibtex-parse-association 'bibtex-parse-string-prefix
- 'bibtex-parse-string-postfix))
+If EMPTY-KEY is non-nil, key may be empty. Do not move point."
+ (let ((bibtex-string-empty-key empty-key))
+ (bibtex-parse-association 'bibtex-parse-string-prefix
+ 'bibtex-parse-string-postfix)))
-(defun bibtex-search-forward-string ()
+(defun bibtex-search-forward-string (&optional empty-key)
"Search forward to find a BibTeX string entry.
If a syntactically correct entry is found, a pair containing the boundaries of
-the reference key and text parts of the string is returned. Do not move point."
+the reference key and text parts of the string is returned.
+If EMPTY-KEY is non-nil, key may be empty. Do not move point."
(save-excursion
(save-match-data
- (let ((case-fold-search t)
- boundaries)
- (while (and (not boundaries)
+ (let ((case-fold-search t) bounds)
+ (while (and (not bounds)
(search-forward-regexp bibtex-string-type nil t))
- (goto-char (match-beginning 0))
- (unless (setq boundaries (bibtex-parse-string))
- (forward-char 1)))
- boundaries))))
-
-(defun bibtex-search-backward-string ()
- "Search backward to find a BibTeX string entry.
-If a syntactically correct entry is found, a pair containing the boundaries of
-the reference key and text parts of the field is returned. Do not move point."
- (save-excursion
- (save-match-data
- (let ((case-fold-search t)
- boundaries)
- (while (and (not boundaries)
- (search-backward-regexp bibtex-string-type nil t))
- (goto-char (match-beginning 0))
- (setq boundaries (bibtex-parse-string)))
- boundaries))))
+ (save-excursion (goto-char (match-beginning 0))
+ (setq bounds (bibtex-parse-string empty-key))))
+ bounds))))
(defun bibtex-reference-key-in-string (bounds)
- "Return the key part of a BibTeX string defined via BOUNDS"
+ "Return the key part of a BibTeX string defined via BOUNDS."
(buffer-substring-no-properties (nth 1 (car bounds))
(nth 2 (car bounds))))
(or (match-string-no-properties bibtex-key-in-head)
empty))
-(defun bibtex-preamble-prefix (&optional delim)
- "Parse the prefix part of a BibTeX Preamble.
-Point must be at beginning of prefix part. If prefix is found move point
-to its end and return position of point. If optional arg DELIM is non-nil,
-move past the opening delimiter. If no preamble is found return nil."
+(defun bibtex-parse-preamble ()
+ "Parse BibTeX preamble.
+Point must be at beginning of preamble. Do not move point."
(let ((case-fold-search t))
- (re-search-forward (concat "\\=" bibtex-preamble-prefix
- (if delim "[({][ \t\n]*")) nil t)))
+ (when (looking-at bibtex-preamble-prefix)
+ (let ((start (match-beginning 0)) (pref-start (match-beginning 1))
+ (bounds (save-excursion (goto-char (match-end 0))
+ (bibtex-parse-string-postfix))))
+ (if bounds (cons (list start pref-start) bounds))))))
;; Helper Functions
(+ (count-lines 1 (point))
(if (bolp) 1 0)))
+(defun bibtex-valid-entry (&optional empty-key)
+ "Parse a valid BibTeX entry (maybe without key if EMPTY-KEY is t).
+A valid entry is a syntactical correct one with type contained in
+`bibtex-entry-field-alist'. Ignore @String and @Preamble entries.
+Return a cons pair with buffer positions of beginning and end of entry
+if a valid entry is found, nil otherwise. Do not move point.
+After a call to this function `match-data' corresponds to the header
+of the entry, see regexp `bibtex-entry-head'."
+ (let ((case-fold-search t) end)
+ (if (looking-at (if empty-key bibtex-entry-maybe-empty-head
+ bibtex-entry-head))
+ (save-excursion
+ (save-match-data
+ (goto-char (match-end 0))
+ (let ((entry-closer
+ (if (save-excursion
+ (goto-char (match-end bibtex-type-in-head))
+ (looking-at "[ \t]*("))
+ ",?[ \t\n]*)" ; entry opened with `('
+ ",?[ \t\n]*}")) ; entry opened with `{'
+ bounds)
+ (skip-chars-forward " \t\n")
+ ;; loop over all BibTeX fields
+ (while (setq bounds (bibtex-parse-field))
+ (goto-char (bibtex-end-of-field bounds)))
+ ;; This matches the infix* part.
+ (if (looking-at entry-closer) (setq end (match-end 0)))))
+ (if end (cons (match-beginning 0) end))))))
+
(defun bibtex-skip-to-valid-entry (&optional backward)
"Move point to beginning of the next valid BibTeX entry.
Do not move if we are already at beginning of a valid BibTeX entry.
entry is found, nil otherwise."
(interactive "P")
(let ((case-fold-search t)
- found)
+ found bounds)
(beginning-of-line)
;; Loop till we look at a valid entry.
(while (not (or found (if backward (bobp) (eobp))))
- (let ((pnt (point))
- bounds)
- (cond ((or (and (looking-at bibtex-entry-type-whitespace)
- (setq found (bibtex-search-entry nil nil t))
- (equal (match-beginning 0) pnt))
- (and (not bibtex-sort-ignore-string-entries)
- (setq bounds (bibtex-parse-string))
- (setq found (cons (bibtex-start-of-field bounds)
- (bibtex-end-of-string bounds)))))
- (goto-char pnt))
- (backward (re-search-backward "^[ \t]*@" nil 'move))
- (t (re-search-forward "\\=[ \t]*@" nil t) ;; don't be stuck
- (if (re-search-forward "^[ \t]*@" nil 'move)
- (goto-char (match-beginning 0)))))))
+ (cond ((setq found (or (bibtex-valid-entry)
+ (and (not bibtex-sort-ignore-string-entries)
+ (setq bounds (bibtex-parse-string))
+ (cons (bibtex-start-of-field bounds)
+ (bibtex-end-of-string bounds))))))
+ (backward (re-search-backward "^[ \t]*@" nil 'move))
+ (t (if (re-search-forward "\n\\([ \t]*@\\)" nil 'move)
+ (goto-char (match-beginning 1))))))
found))
(defun bibtex-map-entries (fun)
"Call FUN for each BibTeX entry in buffer (possibly narrowed).
FUN is called with three arguments, the key of the entry and the buffer
-positions (marker) of beginning and end of entry. Point is inside the entry.
-If `bibtex-sort-ignore-string-entries' is non-nil, FUN is not called for
-@String entries."
+positions of beginning and end of entry. Also, point is at beginning of
+entry and `match-data' corresponds to the header of the entry,
+see regexp `bibtex-entry-head'. If `bibtex-sort-ignore-string-entries'
+is non-nil, FUN is not called for @String entries."
(let ((case-fold-search t)
found)
(save-excursion
"}"
")"))
-(defun bibtex-search-entry (empty-head &optional bound noerror backward)
- "Search for a BibTeX entry (maybe without reference key if EMPTY-HEAD is t).
-BOUND and NOERROR are exactly as in `re-search-forward'. If BACKWARD
-is non-nil, search in reverse direction. Move point past the closing
-delimiter (at the beginning of entry if BACKWARD is non-nil).
-Return a cons pair with buffer positions of beginning and end of entry.
-After a call to this function `match-data' corresponds to the head part
-of the entry, see regexp `bibtex-entry-head'.
-Ignore @String and @Preamble entries."
- (let ((pnt (point))
- (entry-head-re (if empty-head
- bibtex-entry-maybe-empty-head
- bibtex-entry-head)))
- (if backward
- (let (found)
- (while (and (not found)
- (re-search-backward entry-head-re bound noerror))
- (setq found (bibtex-search-entry empty-head pnt t)))
- (cond (found
- (goto-char (match-beginning 0))
- found)
- ((not noerror) ;; yell
- (error "Backward search of BibTeX entry failed"))
- (t (if (eq noerror t) (goto-char pnt)) ;; don't move
- nil)))
- (let (found)
- (unless bound (setq bound (point-max)))
- (while (and (not found)
- (re-search-forward entry-head-re bound noerror))
- (save-match-data
- (let ((entry-closer
- (if (save-excursion
- (goto-char (match-end bibtex-type-in-head))
- (looking-at "[ \t]*("))
- ",?[ \t\n]*)" ;; entry opened with `('
- ",?[ \t\n]*}")) ;; entry opened with `{'
- bounds)
- (skip-chars-forward " \t\n" bound)
- ;; loop over all BibTeX fields
- (while (and (setq bounds (bibtex-parse-field))
- (<= (bibtex-end-of-field bounds) bound))
- (goto-char (bibtex-end-of-field bounds)))
- ;; This matches the infix* part.
- (when (and (looking-at entry-closer)
- (<= (match-end 0) bound))
- (goto-char (match-end 0))
- (setq found t)))))
- (cond (found
- (cons (match-beginning 0) (point)))
- ((not noerror) ;; yell
- (error "Search of BibTeX entry failed"))
- (t (if (eq noerror t) (goto-char pnt)) ;; don't move
- nil))))))
-
-(defun bibtex-flash-head ()
+(defun bibtex-flash-head (prompt)
"Flash at BibTeX entry head before point, if exists."
(let ((case-fold-search t)
- (pnt (point))
- flash)
+ (pnt (point)))
(save-excursion
(bibtex-beginning-of-entry)
(when (and (looking-at bibtex-any-entry-maybe-empty-head)
(< (point) pnt))
(goto-char (match-beginning bibtex-type-in-head))
- (setq flash (match-end bibtex-key-in-head))
(if (pos-visible-in-window-p (point))
- (sit-for 1)
- (message "From: %s"
- (buffer-substring (point) flash)))))))
+ (sit-for blink-matching-delay)
+ (message "%s%s" prompt (buffer-substring-no-properties
+ (point) (match-end bibtex-key-in-head))))))))
(defun bibtex-make-optional-field (field)
"Make an optional field named FIELD in current BibTeX entry."
(bibtex-skip-to-valid-entry)
(point))
-(defun bibtex-inside-field ()
- "Try to avoid point being at end of a BibTeX field."
- (end-of-line)
- (skip-chars-backward " \t")
- (if (= (preceding-char) ?,)
- (forward-char -2))
- (if (or (= (preceding-char) ?})
- (= (preceding-char) ?\"))
- (forward-char -1)))
-
-(defun bibtex-enclosing-field (&optional noerr)
+(defun bibtex-enclosing-field (&optional comma noerr)
"Search for BibTeX field enclosing point.
+For `bibtex-mode''s internal algorithms, a field begins at the comma
+following the preceding field. Usually, this is not what the user expects.
+Thus if COMMA is non-nil, the \"current field\" includes the terminating comma.
Unless NOERR is non-nil, signal an error if no enclosing field is found.
On success return bounds, nil otherwise. Do not move point."
- (let ((bounds (bibtex-search-backward-field bibtex-field-name)))
- (if (and bounds
- (<= (bibtex-start-of-field bounds) (point))
- (>= (bibtex-end-of-field bounds) (point)))
- bounds
- (unless noerr
- (error "Can't find enclosing BibTeX field")))))
-
-(defun bibtex-enclosing-entry-maybe-empty-head ()
- "Search for BibTeX entry enclosing point. Move point to end of entry.
-Beginning (but not end) of entry is given by (`match-beginning' 0)."
- (let ((case-fold-search t)
- (old-point (point)))
- (unless (re-search-backward bibtex-entry-maybe-empty-head nil t)
- (goto-char old-point)
- (error "Can't find beginning of enclosing BibTeX entry"))
- (goto-char (match-beginning bibtex-type-in-head))
- (unless (bibtex-search-entry t nil t)
- (goto-char old-point)
- (error "Can't find end of enclosing BibTeX entry"))))
-
-(defun bibtex-insert-kill (n)
- "Reinsert the Nth stretch of killed BibTeX text."
- (if (not bibtex-last-kill-command)
- (error "BibTeX kill ring is empty")
- (let* ((kr (if (eq bibtex-last-kill-command 'field)
- 'bibtex-field-kill-ring
- 'bibtex-entry-kill-ring))
- (kryp (if (eq bibtex-last-kill-command 'field)
- 'bibtex-field-kill-ring-yank-pointer
- 'bibtex-entry-kill-ring-yank-pointer))
- (current (car (set kryp (nthcdr (mod (- n (length (eval kryp)))
- (length (eval kr)))
- (eval kr))))))
- (if (eq bibtex-last-kill-command 'field)
- (progn
- (bibtex-find-text)
- (if (looking-at "[}\"]")
- (forward-char))
- (set-mark (point))
- (message "Mark set")
- (bibtex-make-field current t))
- (unless (eobp) (bibtex-beginning-of-entry))
- (set-mark (point))
- (message "Mark set")
- (insert current)))))
+ (save-excursion
+ (when comma
+ (end-of-line)
+ (skip-chars-backward " \t")
+ (if (= (preceding-char) ?,) (forward-char -1)))
+
+ (let ((bounds (bibtex-search-backward-field bibtex-field-name t)))
+ (cond ((and bounds
+ (<= (bibtex-start-of-field bounds) (point))
+ (>= (bibtex-end-of-field bounds) (point)))
+ bounds)
+ ((not noerr)
+ (error "Can't find enclosing BibTeX field"))))))
+
+(defun bibtex-beginning-first-field (&optional beg)
+ "Move point to beginning of first field.
+Optional arg BEG is beginning of entry."
+ (if beg (goto-char beg) (bibtex-beginning-of-entry))
+ (looking-at bibtex-any-entry-maybe-empty-head)
+ (goto-char (match-end 0)))
+
+(defun bibtex-insert-kill (n &optional comma)
+ "Reinsert the Nth stretch of killed BibTeX text (field or entry).
+Optional arg COMMA is as in `bibtex-enclosing-field'."
+ (unless bibtex-last-kill-command (error "BibTeX kill ring is empty"))
+ (let ((fun (lambda (kryp kr) ; adapted from `current-kill'
+ (car (set kryp (nthcdr (mod (- n (length (eval kryp)))
+ (length kr)) kr))))))
+ (if (eq bibtex-last-kill-command 'field)
+ (progn
+ ;; insert past the current field
+ (goto-char (bibtex-end-of-field (bibtex-enclosing-field comma)))
+ (push-mark)
+ (bibtex-make-field (funcall fun 'bibtex-field-kill-ring-yank-pointer
+ bibtex-field-kill-ring) t nil t))
+ ;; insert past the current entry
+ (bibtex-skip-to-valid-entry)
+ (push-mark)
+ (insert (funcall fun 'bibtex-entry-kill-ring-yank-pointer
+ bibtex-entry-kill-ring)))))
(defun bibtex-format-entry ()
"Helper function for `bibtex-clean-entry'.
crossref-key bounds alternatives-there non-empty-alternative
entry-list req-field-list field-list)
+ ;; Initialize `bibtex-field-braces-opt' and `bibtex-field-strings-opt'
+ ;; if necessary.
+ (unless bibtex-field-braces-opt
+ (setq bibtex-field-braces-opt
+ (bibtex-field-re-init bibtex-field-braces-alist 'braces)))
+ (unless bibtex-field-strings-opt
+ (setq bibtex-field-strings-opt
+ (bibtex-field-re-init bibtex-field-strings-alist 'strings)))
+
;; 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
(error "All alternatives are empty"))
;; process all fields
- (goto-char (point-min))
- (while (setq bounds (bibtex-search-forward-field
- bibtex-field-name (point-max)))
+ (bibtex-beginning-first-field (point-min))
+ (while (setq bounds (bibtex-parse-field))
(let* ((beg-field (copy-marker (bibtex-start-of-field bounds)))
(end-field (copy-marker (bibtex-end-of-field bounds) t))
(beg-name (copy-marker (bibtex-start-of-name-in-field bounds)))
deleted)
;; We have more elegant high-level functions for several
- ;; tasks done by bibtex-format-entry. However, they contain
+ ;; tasks done by `bibtex-format-entry'. However, they contain
;; quite some redundancy compared with what we need to do
;; anyway. So for speed-up we avoid using them.
"\\([\"{][0-9]+\\)[ \t\n]*--?[ \t\n]*\\([0-9]+[\"}]\\)")))
(replace-match "\\1-\\2"))
+ ;; remove whitespace at beginning and end of field
+ (when (memq 'whitespace format)
+ (goto-char beg-text)
+ (if (looking-at "\\([{\"]\\)[ \t\n]+")
+ (replace-match "\\1"))
+ (goto-char end-text)
+ (if (looking-back "[ \t\n]+\\([}\"]\\)" beg-text t)
+ (replace-match "\\1")))
+
+ ;; enclose field text by braces according to
+ ;; `bibtex-field-braces-alist'.
+ (let (case-fold-search temp) ; Case-sensitive search
+ (when (and (memq 'braces format)
+ (setq temp (cdr (assoc-string field-name
+ bibtex-field-braces-opt t))))
+ (goto-char beg-text)
+ (while (re-search-forward temp end-text t)
+ (let ((beg (match-beginning 0))
+ (bounds (bibtex-find-text-internal nil t)))
+ (unless (or (nth 4 bounds) ; string constant
+ ;; match already surrounded by braces
+ ;; (braces are inside field delimiters)
+ (and (< (point) (1- (nth 2 bounds)))
+ (< (1+ (nth 1 bounds)) beg)
+ (looking-at "}")
+ (save-excursion (goto-char (1- beg))
+ (looking-at "{"))))
+ (insert "}")
+ (goto-char beg)
+ (insert "{")))))
+
+ ;; replace field text by BibTeX string constants according to
+ ;; `bibtex-field-strings-alist'.
+ (when (and (memq 'strings format)
+ (setq temp (cdr (assoc-string field-name
+ bibtex-field-strings-opt t))))
+ (goto-char beg-text)
+ (dolist (re temp)
+ (while (re-search-forward (car re) end-text t)
+ (let ((bounds (save-match-data
+ (bibtex-find-text-internal nil t))))
+ (unless (nth 4 bounds)
+ ;; if match not at right subfield boundary...
+ (if (< (match-end 0) (1- (nth 2 bounds)))
+ (insert " # " (bibtex-field-left-delimiter))
+ (delete-char 1))
+ (replace-match (cdr re))
+ (goto-char (match-beginning 0))
+ ;; if match not at left subfield boundary...
+ (if (< (1+ (nth 1 bounds)) (match-beginning 0))
+ (insert (bibtex-field-right-delimiter) " # ")
+ (delete-backward-char 1))))))))
+
;; use book title of crossref'd entry
(if (and (memq 'inherit-booktitle format)
empty-field
(error "Alternative fields `%s' are defined %s times"
altlist found))))))
- ;; update point
- (if (looking-at (bibtex-field-right-delimiter))
- (forward-char))
-
;; update comma after last field
(if (memq 'last-comma format)
(cond ((and bibtex-comma-after-last-field
(if (memq 'realign format)
(bibtex-fill-entry))))))
+(defun bibtex-field-re-init (regexp-alist type)
+ "Calculate optimized value for bibtex-regexp-TYPE-opt.
+This value is based on bibtex-regexp-TYPE-alist. TYPE is 'braces or 'strings.
+Return optimized value to be used by `bibtex-format-entry'."
+ (setq regexp-alist
+ (mapcar (lambda (e)
+ (list (car e)
+ (replace-regexp-in-string "[ \t\n]+" "[ \t\n]+" (nth 1 e))
+ (nth 2 e))) ; nil for 'braces'.
+ regexp-alist))
+ (let (opt-list)
+ ;; Loop over field names
+ (dolist (field (delete-dups (apply 'append (mapcar 'car regexp-alist))))
+ (let (rules)
+ ;; Collect all matches we have for this field name
+ (dolist (e regexp-alist)
+ (if (assoc-string field (car e) t)
+ (push (cons (nth 1 e) (nth 2 e)) rules)))
+ (if (eq type 'braces)
+ ;; concatenate all regexps to a single regexp
+ (setq rules (concat "\\(?:" (mapconcat 'car rules "\\|") "\\)")))
+ ;; create list of replacement rules.
+ (push (cons field rules) opt-list)))
+ opt-list))
+
\f
(defun bibtex-autokey-abbrev (string len)
"Return an abbreviation of STRING with at least LEN characters.
(<= (length name-list)
(+ bibtex-autokey-names
bibtex-autokey-names-stretch)))
- ;; Take bibtex-autokey-names elements from beginning of name-list
+ ;; Take `bibtex-autokey-names' elements from beginning of name-list
(setq name-list (nreverse (nthcdr (- (length name-list)
bibtex-autokey-names)
(nreverse name-list)))
;; --> 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 word (match-string 0 titlestring)
titlestring (substring titlestring (match-end 0)))
;; Ignore words matched by one of the elements of
- ;; bibtex-autokey-titleword-ignore
+ ;; `bibtex-autokey-titleword-ignore'
(unless (let ((lst bibtex-autokey-titleword-ignore))
(while (and lst
(not (string-match (concat "\\`\\(?:" (car lst)
(<= counter bibtex-autokey-titlewords))
(push word titlewords)
(push word titlewords-extra))))
- ;; Obey bibtex-autokey-titlewords-stretch:
+ ;; Obey `bibtex-autokey-titlewords-stretch':
;; If by now we have processed all words in titlestring, we include
- ;; titlewords-extra in titlewords. Otherwise, we ignore titlewords-extra.
+ ;; titlewords-extra in titlewords. Otherwise, we ignore titlewords-extra.
(unless (string-match "\\b\\w+" titlestring)
(setq titlewords (append titlewords-extra titlewords)))
(mapconcat 'bibtex-autokey-demangle-title (nreverse titlewords)
(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.
(push (cons key t) ref-keys)))))))
(let (;; ignore @String entries because they are handled
- ;; separately by bibtex-parse-strings
+ ;; separately by `bibtex-parse-strings'
(bibtex-sort-ignore-string-entries t)
bounds)
(bibtex-map-entries
(setq bibtex-strings strings))))))
(defun bibtex-strings ()
- "Return `bibtex-strings'. Initialize this variable if necessary."
+ "Return `bibtex-strings'. Initialize this variable if necessary."
(if (listp bibtex-strings) bibtex-strings
(bibtex-parse-strings (bibtex-string-files-init))))
bibtex-buffer-last-parsed-tick)))
(save-restriction
(widen)
- ;; Output no progress messages in bibtex-parse-keys
- ;; because when in y-or-n-p that can hide the question.
+ ;; Output no progress messages in `bibtex-parse-keys'
+ ;; because when in `y-or-n-p' that can hide the question.
(if (and (listp (bibtex-parse-keys t))
- ;; update bibtex-strings
+ ;; update `bibtex-strings'
(listp (bibtex-parse-strings strings-init t)))
;; remember that parsing was successful
"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.
+ ;; Code inspired by `lisp-complete-symbol'.
(let* ((case-fold-search t)
(beg (save-excursion
(re-search-backward "[ \t{\"]")
(forward-char)
(point)))
(end (point))
- (part-of-word (buffer-substring-no-properties beg end))
- (completion (try-completion part-of-word completions)))
+ (pattern (buffer-substring-no-properties beg end))
+ (completion (try-completion pattern completions)))
(cond ((not completion)
- (error "Can't find completion for `%s'" part-of-word))
+ (error "Can't find completion for `%s'" pattern))
((eq completion t)
- part-of-word)
- ((not (string= part-of-word completion))
+ pattern)
+ ((not (string= pattern completion))
(delete-region beg end)
(insert completion)
+ ;; Don't leave around a completions buffer that's out of date.
+ (let ((win (get-buffer-window "*Completions*" 0)))
+ (if win (with-selected-window win (bury-buffer))))
completion)
(t
- (message "Making completion list...")
- (with-output-to-temp-buffer "*Completions*"
- (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
+ (let ((minibuf-is-in-use
+ (eq (minibuffer-window) (selected-window))))
+ (unless minibuf-is-in-use (message "Making completion list..."))
+ (with-output-to-temp-buffer "*Completions*"
+ (display-completion-list
+ (sort (all-completions pattern completions) 'string<) pattern))
+ (unless minibuf-is-in-use
+ (message "Making completion list...done")))
nil))))
(defun bibtex-complete-string-cleanup (str compl)
"Cleanup after inserting string STR.
Remove enclosing field delimiters for STR. Display message with
expansion of STR using expansion list COMPL."
+ ;; point is at position inside field where completion was requested
(save-excursion
(let ((abbr (cdr (if (stringp str)
(assoc-string str compl t)))))
(bibtex-find-entry key t))
(message "Ref: %s" (funcall bibtex-summary-function)))))
-(defun bibtex-copy-summary-as-kill ()
+(defun bibtex-copy-summary-as-kill (&optional arg)
"Push summery of current BibTeX entry to kill ring.
-Use `bibtex-summary-function' to generate summary."
- (interactive)
- (save-excursion
- (bibtex-beginning-of-entry)
- (if (looking-at bibtex-entry-maybe-empty-head)
- (kill-new (message "%s" (funcall bibtex-summary-function)))
- (error "No entry found"))))
+Use `bibtex-summary-function' to generate summary.
+If prefix ARG is non-nil push BibTeX entry's URL to kill ring
+that is generated by calling `bibtex-url'."
+ (interactive "P")
+ (if arg (let ((url (bibtex-url nil t)))
+ (if url (kill-new (message "%s" url))
+ (message "No URL known")))
+ (save-excursion
+ (bibtex-beginning-of-entry)
+ (if (looking-at bibtex-entry-maybe-empty-head)
+ (kill-new (message "%s" (funcall bibtex-summary-function)))
+ (error "No entry found")))))
(defun bibtex-summary ()
"Return summary of current BibTeX entry.
Used as default value of `bibtex-summary-function'."
- ;; It would be neat to customize this function. How?
+ ;; It would be neat to make this function customizable. 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))
(defun bibtex-pop (arg direction)
"Fill current field from the ARGth same field's text in DIRECTION.
Generic function used by `bibtex-pop-previous' and `bibtex-pop-next'."
- (bibtex-find-text)
- (save-excursion
- ;; parse current field
- (bibtex-inside-field)
- (let* ((case-fold-search t)
- (bounds (bibtex-enclosing-field))
- (start-old-text (bibtex-start-of-text-in-field bounds))
- (stop-old-text (bibtex-end-of-text-in-field bounds))
- (field-name (bibtex-name-in-field bounds t)))
+ ;; parse current field
+ (let* ((bounds (bibtex-enclosing-field t))
+ (start-old-field (bibtex-start-of-field bounds))
+ (start-old-text (bibtex-start-of-text-in-field bounds))
+ (end-old-text (bibtex-end-of-text-in-field bounds))
+ (field-name (bibtex-name-in-field bounds t))
+ failure)
+ (save-excursion
;; if executed several times in a row, start each search where
;; the last one was finished
- (unless (eq last-command 'bibtex-pop)
- (bibtex-enclosing-entry-maybe-empty-head)
- (setq bibtex-pop-previous-search-point (match-beginning 0)
- bibtex-pop-next-search-point (point)))
- (if (eq direction 'previous)
- (goto-char bibtex-pop-previous-search-point)
- (goto-char bibtex-pop-next-search-point))
- ;; Now search for arg'th previous/next similar field
- (let (bounds failure new-text)
- (while (and (not failure)
- (> arg 0))
- (cond ((eq direction 'previous)
- (if (setq bounds (bibtex-search-backward-field field-name))
- (goto-char (bibtex-start-of-field bounds))
- (setq failure t)))
- ((eq direction 'next)
- (if (setq bounds (bibtex-search-forward-field field-name))
- (goto-char (bibtex-end-of-field bounds))
- (setq failure t))))
- (setq arg (- arg 1)))
- (if failure
- (error "No %s matching BibTeX field"
- (if (eq direction 'previous) "previous" "next"))
- ;; Found a matching field. Remember boundaries.
- (setq bibtex-pop-previous-search-point (bibtex-start-of-field bounds)
- bibtex-pop-next-search-point (bibtex-end-of-field bounds)
- new-text (bibtex-text-in-field-bounds bounds))
- (bibtex-flash-head)
+ (cond ((eq last-command 'bibtex-pop)
+ (goto-char (if (eq direction 'previous)
+ bibtex-pop-previous-search-point
+ bibtex-pop-next-search-point)))
+ ((eq direction 'previous)
+ (bibtex-beginning-of-entry))
+ (t (bibtex-end-of-entry)))
+ ;; Search for arg'th previous/next similar field
+ (while (and (not failure)
+ (>= (setq arg (1- arg)) 0))
+ ;; The search of BibTeX fields is not bounded by entry boundaries
+ (if (eq direction 'previous)
+ (if (setq bounds (bibtex-search-backward-field field-name))
+ (goto-char (bibtex-start-of-field bounds))
+ (setq failure t))
+ (if (setq bounds (bibtex-search-forward-field field-name))
+ (goto-char (bibtex-end-of-field bounds))
+ (setq failure t))))
+ (if failure
+ (error "No %s matching BibTeX field"
+ (if (eq direction 'previous) "previous" "next"))
+ ;; Found a matching field. Remember boundaries.
+ (let ((new-text (bibtex-text-in-field-bounds bounds))
+ (nbeg (copy-marker (bibtex-start-of-field bounds)))
+ (nend (copy-marker (bibtex-end-of-field bounds))))
+ (bibtex-flash-head "From: ")
;; Go back to where we started, delete old text, and pop new.
- (goto-char stop-old-text)
- (delete-region start-old-text stop-old-text)
- (insert new-text)))))
- (bibtex-find-text)
+ (goto-char end-old-text)
+ (delete-region start-old-text end-old-text)
+ (if (= nbeg start-old-field)
+ (insert (bibtex-field-left-delimiter)
+ (bibtex-field-right-delimiter))
+ (insert new-text))
+ (setq bibtex-pop-previous-search-point (marker-position nbeg)
+ bibtex-pop-next-search-point (marker-position nend))))))
+ (bibtex-find-text nil nil nil t)
(setq this-command 'bibtex-pop))
(defun bibtex-beginning-of-field ()
(unless (looking-at field-reg)
(re-search-backward field-reg nil t))))
-(defun bibtex-font-lock-url (bound)
- "Font-lock for URLs. BOUND limits the search."
+(defun bibtex-font-lock-url (bound &optional no-button)
+ "Font-lock for URLs. BOUND limits the search.
+If NO-BUTTON is non-nil do not generate buttons."
(let ((case-fold-search t)
(pnt (point))
- field bounds start end found)
+ name bounds start end found)
(bibtex-beginning-of-field)
(while (and (not found)
(<= (point) bound)
(prog1 (re-search-forward bibtex-font-lock-url-regexp bound t)
- (setq field (match-string-no-properties 1)))
+ (setq name (match-string-no-properties 1)))
(setq bounds (bibtex-parse-field-text))
(progn
(setq start (car bounds) end (nth 1 bounds))
(setq end (1- end)))
(if (memq (char-after start) '(?\{ ?\"))
(setq start (1+ start)))
- (>= bound start)))
- (let ((lst bibtex-generate-url-list) url)
- (goto-char start)
- (while (and (not found)
- (setq url (car (pop lst))))
- (setq found (and (bibtex-string= field (car url))
- (re-search-forward (cdr url) end t)
- (>= (match-beginning 0) pnt)))))
- (goto-char end))
- (if found (bibtex-button (match-beginning 0) (match-end 0)
- 'bibtex-url (match-beginning 0)))
+ (if (< start pnt) (setq start (min pnt end)))
+ (<= start bound)))
+ (if (<= pnt start)
+ (let ((lst bibtex-generate-url-list) url)
+ (while (and (not found) (setq url (car (pop lst))))
+ (goto-char start)
+ (setq found (and (bibtex-string= name (car url))
+ (re-search-forward (cdr url) end t))))))
+ (unless found (goto-char end)))
+ (if (and found (not no-button))
+ (bibtex-button (match-beginning 0) (match-end 0)
+ 'bibtex-url (match-beginning 0)))
found))
(defun bibtex-font-lock-crossref (bound)
start t))
found))
+(defun bibtex-font-lock-cite (matcher bound)
+ "Font-lock for cited keys.
+MATCHER identifies the cited key, see `bibtex-cite-matcher-alist'.
+BOUND limits the search."
+ (let (case-fold-search)
+ (if (re-search-forward (car matcher) bound t)
+ (let ((start (match-beginning (cdr matcher)))
+ (end (match-end (cdr matcher))))
+ (bibtex-button start end 'bibtex-find-crossref
+ (buffer-substring-no-properties start end)
+ start t t)
+ t))))
+
(defun bibtex-button-action (button)
"Call BUTTON's BibTeX function."
(apply (button-get button 'bibtex-function)
(list (list nil bibtex-entry-head bibtex-key-in-head))
imenu-case-fold-search t)
(make-local-variable 'choose-completion-string-functions)
- ;; XEmacs needs easy-menu-add, Emacs does not care
+ ;; XEmacs needs `easy-menu-add', Emacs does not care
(easy-menu-add bibtex-edit-menu)
(easy-menu-add bibtex-entry-menu)
(run-mode-hooks 'bibtex-mode-hook))
(let ((e (assoc-string entry-type bibtex-entry-field-alist t))
required optional)
(unless e
- (error "BibTeX entry type %s not defined" entry-type))
+ (error "Fields for BibTeX entry type %s not defined" entry-type))
(if (and (member-ignore-case entry-type bibtex-include-OPTcrossref)
(nth 2 e))
(setq required (nth 0 (nth 2 e))
(push (list "key"
"Used for reference key creation if author and editor fields are missing"
(if (or (stringp bibtex-include-OPTkey)
- (fboundp bibtex-include-OPTkey))
+ (functionp bibtex-include-OPTkey))
bibtex-include-OPTkey))
optional))
(if (member-ignore-case entry-type bibtex-include-OPTcrossref)
(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)))))
- (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.
(key (bibtex-key-in-head))
(key-end (match-end bibtex-key-in-head))
(case-fold-search t)
+ (bibtex-sort-ignore-string-entries t)
tmp other-key other bounds)
;; The fields we want to change start right after the key.
(goto-char key-end)
(while (re-search-backward (regexp-quote other-suffix) key-end 'move)
(replace-match suffix)))))))
-(defun bibtex-print-help-message ()
- "Print helpful information about current field in current BibTeX entry."
- (interactive)
- (let* ((case-fold-search t)
- (type (save-excursion
- (bibtex-beginning-of-entry)
- (looking-at bibtex-any-entry-maybe-empty-head)
- (bibtex-type-in-head)))
- comment field-list)
- (cond ((bibtex-string= type "string")
- (message "String definition"))
- ((bibtex-string= type "preamble")
- (message "Preamble definition"))
- (t
- (setq field-list (bibtex-field-list type)
- comment
- (assoc-string (bibtex-name-in-field (bibtex-enclosing-field) t)
- (append (car field-list) (cdr field-list))
- t))
- (if comment
- (message "%s" (nth 1 comment))
- (message "No comment available"))))))
-
-(defun bibtex-make-field (field &optional move interactive)
+(defun bibtex-print-help-message (&optional field comma)
+ "Print helpful information about current FIELD in current BibTeX entry.
+Optional arg COMMA is as in `bibtex-enclosing-field'. It is t for
+interactive calls."
+ (interactive (list nil t))
+ (unless field (setq field (car (bibtex-find-text-internal nil nil comma))))
+ (if (string-match "@" field)
+ (cond ((bibtex-string= field "@string")
+ (message "String definition"))
+ ((bibtex-string= field "@preamble")
+ (message "Preamble definition"))
+ (t (message "Entry key")))
+ (let* ((case-fold-search t)
+ (type (save-excursion
+ (bibtex-beginning-of-entry)
+ (looking-at bibtex-entry-maybe-empty-head)
+ (bibtex-type-in-head)))
+ (field-list (bibtex-field-list type))
+ (comment (assoc-string field (append (car field-list)
+ (cdr field-list)) t)))
+ (if comment (message "%s" (nth 1 comment))
+ (message "No comment available")))))
+
+(defun bibtex-make-field (field &optional move interactive nodelim)
"Make a field named FIELD in current BibTeX entry.
FIELD is either a string or a list of the form
\(FIELD-NAME COMMENT-STRING INIT ALTERNATIVE-FLAG) as in
If MOVE is non-nil, move point past the present field before making
the new field. If INTERACTIVE is non-nil, move point to the end of
the new field. Otherwise move point past the new field.
-MOVE and INTERACTIVE are t when called interactively."
+MOVE and INTERACTIVE are t when called interactively.
+INIT is surrounded by field delimiters, unless NODELIM is non-nil."
(interactive
(list (let ((completion-ignore-case t)
(field-list (bibtex-field-list
(save-excursion
- (bibtex-enclosing-entry-maybe-empty-head)
+ (bibtex-beginning-of-entry)
+ (looking-at bibtex-any-entry-maybe-empty-head)
(bibtex-type-in-head)))))
(completing-read "BibTeX field name: "
(append (car field-list) (cdr field-list))
(indent-to-column (+ bibtex-entry-offset
bibtex-text-indentation)))
(let ((init (nth 2 field)))
- (insert (cond ((stringp init) init)
- ((fboundp init) (funcall init))
- (t (concat (bibtex-field-left-delimiter)
- (bibtex-field-right-delimiter))))))
+ (if (not init) (setq init "")
+ (if (functionp init) (setq init (funcall init)))
+ (unless (stringp init) (error "`%s' is not a string" init)))
+ ;; NODELIM is required by `bibtex-insert-kill'
+ (if nodelim (insert init)
+ (insert (bibtex-field-left-delimiter) init
+ (bibtex-field-right-delimiter))))
(when interactive
- (forward-char -1)
- (bibtex-print-help-message)))
+ ;; (bibtex-find-text nil nil bibtex-help-message)
+ (if (memq (preceding-char) '(?} ?\")) (forward-char -1))
+ (if bibtex-help-message (bibtex-print-help-message (car field)))))
(defun bibtex-beginning-of-entry ()
"Move to beginning of BibTeX entry (beginning of line).
Return the new location of point."
(interactive)
(let ((case-fold-search t)
- (org (point))
- (pnt (bibtex-beginning-of-entry))
- err bounds)
- (cond ((looking-at bibtex-entry-type-whitespace)
- (bibtex-search-entry t nil t)
- (unless (equal (match-beginning 0) pnt)
- (setq err t)))
- ;; @String
- ((setq bounds (bibtex-parse-string))
+ (pnt (point))
+ (_ (bibtex-beginning-of-entry))
+ (bounds (bibtex-valid-entry t)))
+ (cond (bounds (goto-char (cdr bounds))) ; regular entry
+ ;; @String or @Preamble
+ ((setq bounds (or (bibtex-parse-string t) (bibtex-parse-preamble)))
(goto-char (bibtex-end-of-string bounds)))
- ;; @Preamble
- ((bibtex-preamble-prefix t)
- (unless (bibtex-parse-string-postfix) ;; @String postfix OK
- (setq err t)))
- (t
- (if (interactive-p)
- (message "Not on a known BibTeX entry."))
- (goto-char org)))
- (when err
- (goto-char pnt)
- (error "Syntactically incorrect BibTeX entry starts here")))
- (point))
+ ((looking-at bibtex-any-valid-entry-type)
+ ;; Parsing of entry failed
+ (error "Syntactically incorrect BibTeX entry starts here"))
+ (t (if (interactive-p) (message "Not on a known BibTeX entry."))
+ (goto-char pnt)))
+ (point)))
(defun bibtex-goto-line (arg)
"Goto line ARG, counting from beginning of (narrowed) buffer."
(defun bibtex-mark-entry ()
"Put mark at beginning, point at end of current BibTeX entry."
(interactive)
- (set-mark (bibtex-beginning-of-entry))
+ (push-mark (bibtex-beginning-of-entry))
(bibtex-end-of-entry))
(defun bibtex-count-entries (&optional count-string-entries)
(interactive)
(let ((bounds (save-excursion
(bibtex-beginning-of-entry)
- (bibtex-search-forward-field "abstract"))))
+ (bibtex-search-forward-field "abstract" t))))
(if bounds
(ispell-region (bibtex-start-of-text-in-field bounds)
(bibtex-end-of-text-in-field bounds))
;; Don't search CROSSREF-KEY if we don't need it.
(if (eq bibtex-maintain-sorted-entries 'crossref)
(let ((bounds (bibtex-search-forward-field
- "\\(OPT\\)?crossref")))
+ "\\(OPT\\)?crossref" t)))
(list key
(if bounds (bibtex-text-in-field-bounds bounds t))
entry-name))
(list key nil entry-name))))))
+(defun bibtex-init-sort-entry-class-alist ()
+ "Initialize `bibtex-sort-entry-class-alist' (buffer-local)."
+ (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
-
-(defun bibtex-find-crossref (crossref-key &optional pnt split)
+ (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 noerror)
"Move point to the beginning of BibTeX entry CROSSREF-KEY.
If `bibtex-files' is non-nil, search all these files.
Otherwise the search is limited to the current buffer.
Return position of entry if CROSSREF-KEY is found or nil otherwise.
If CROSSREF-KEY is in the same buffer like current entry but before it
-an error is signaled. Optional arg PNT is the position of the referencing
-entry. It defaults to position of point. If optional arg SPLIT is non-nil,
-split window so that both the referencing and the crossrefed entry are
-displayed.
-If called interactively, CROSSREF-KEY defaults to crossref key of current
-entry and SPLIT is t."
+an error is signaled. If NOERRER is non-nil this error is suppressed.
+Optional arg PNT is the position of the referencing entry. It defaults
+to position of point. If optional arg SPLIT is non-nil, split window
+so that both the referencing and the crossrefed entry are displayed.
+
+If called interactively, CROSSREF-KEY defaults to either the crossref key
+of current entry or a key matched by `bibtex-cite-matcher-alist',
+whatever is nearer to the position of point. SPLIT is t. NOERROR is nil
+for a crossref key, t otherwise."
(interactive
- (let ((crossref-key
- (save-excursion
- (bibtex-beginning-of-entry)
- (let ((bounds (bibtex-search-forward-field "crossref")))
- (if bounds
- (bibtex-text-in-field-bounds bounds t))))))
- (list (bibtex-read-key "Find crossref key: " crossref-key t)
- (point) t)))
+ (save-excursion
+ (let* ((pnt (point))
+ (_ (bibtex-beginning-of-entry))
+ (end (cdr (bibtex-valid-entry t)))
+ (_ (unless end (error "Not inside valid entry")))
+ (beg (match-end 0)) ; set by `bibtex-valid-entry'
+ (bounds (bibtex-search-forward-field "crossref" end))
+ case-fold-search best temp crossref-key)
+ (if bounds
+ (setq crossref-key (bibtex-text-in-field-bounds bounds t)
+ best (cons (bibtex-dist pnt (bibtex-end-of-field bounds)
+ (bibtex-start-of-field bounds))
+ crossref-key)))
+ (dolist (matcher bibtex-cite-matcher-alist)
+ (goto-char beg)
+ (while (re-search-forward (car matcher) end t)
+ (setq temp (bibtex-dist pnt (match-end (cdr matcher))
+ (match-beginning (cdr matcher))))
+ ;; Accept the key closest to the position of point.
+ (if (or (not best) (< temp (car best)))
+ (setq best (cons temp (match-string-no-properties
+ (cdr matcher)))))))
+ (goto-char pnt)
+ (setq temp (bibtex-read-key "Find crossref key: " (cdr best) t))
+ (list temp (point) t (not (and crossref-key
+ (string= temp crossref-key)))))))
+
(let (buffer pos eqb)
(save-excursion
(setq pos (bibtex-find-entry crossref-key t)
(split ; called (quasi) interactively
(unless pnt (setq pnt (point)))
(goto-char pnt)
- (if eqb (select-window (split-window))
- (pop-to-buffer buffer))
- (goto-char pos)
- (bibtex-reposition-window)
- (beginning-of-line)
- (if (and eqb (> pnt pos))
- (error "The referencing entry must precede the crossrefed entry!")))
+ (if (and eqb (= pos (save-excursion (bibtex-beginning-of-entry))))
+ (message "Key `%s' is current entry" crossref-key)
+ (if eqb (select-window (split-window))
+ (pop-to-buffer buffer))
+ (goto-char pos)
+ (bibtex-reposition-window)
+ (beginning-of-line)
+ (if (and eqb (> pnt pos) (not noerror))
+ (error "The referencing entry must precede the crossrefed entry!"))))
;; `bibtex-find-crossref' is called noninteractively during
;; clean-up of an entry. Then it is not possible to check
;; whether the current entry and the crossrefed entry have
(t (set-buffer buffer) (goto-char pos)))
pos))
+(defun bibtex-dist (pos beg end)
+ "Return distance between POS and region delimited by BEG and END."
+ (cond ((and (<= beg pos) (<= pos end)) 0)
+ ((< pos beg) (- beg pos))
+ (t (- pos end))))
+
(defun bibtex-find-entry (key &optional global start display)
"Move point to the beginning of BibTeX entry named KEY.
Return position of entry if KEY is found or nil if not found.
(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)
;; if key-exist is non-nil due to the previous cond clause
;; then point will be at beginning of entry named key.
(key-exist)
- (t ; bibtex-maintain-sorted-entries is non-nil
+ (t ; `bibtex-maintain-sorted-entries' is non-nil
(let* ((case-fold-search t)
(left (save-excursion (bibtex-beginning-of-first-entry)))
(bounds (save-excursion (goto-char (point-max))
error-list syntax-error)
(save-excursion
(save-restriction
- (if mark-active
- (narrow-to-region (region-beginning) (region-end)))
+ (if mark-active (narrow-to-region (region-beginning) (region-end)))
- ;; looking if entries fit syntactical structure
+ ;; Check syntactical structure of entries
(goto-char (point-min))
(bibtex-progress-message "Checking syntactical structure")
- (let (bibtex-sort-ignore-string-entries)
- (while (re-search-forward "^[ \t]*@" nil t)
+ (let (bounds end)
+ (while (setq end (re-search-forward "^[ \t]*@" nil t))
(bibtex-progress-message)
- (forward-char -1)
- (let ((pnt (point)))
- (if (not (looking-at bibtex-entry-type-str))
- (forward-char)
- (bibtex-skip-to-valid-entry)
- (if (equal (point) pnt)
- (forward-char)
- (goto-char pnt)
- (push (cons (bibtex-current-line)
- "Syntax error (check esp. commas, braces, and quotes)")
- error-list)
- (forward-char))))))
+ (goto-char (match-beginning 0))
+ (cond ((setq bounds (bibtex-valid-entry))
+ (goto-char (cdr bounds)))
+ ((setq bounds (or (bibtex-parse-string)
+ (bibtex-parse-preamble)))
+ (goto-char (bibtex-end-of-string bounds)))
+ ((looking-at bibtex-any-valid-entry-type)
+ (push (cons (bibtex-current-line)
+ "Syntax error (check esp. commas, braces, and quotes)")
+ error-list)
+ (goto-char (match-end 0)))
+ (t (goto-char end)))))
(bibtex-progress-message 'done)
(if error-list
- ;; proceed only if there were no syntax errors.
+ ;; Continue only if there were no syntax errors.
(setq syntax-error t)
- ;; looking for duplicate keys and correct sort order
+ ;; Check for duplicate keys and correct sort order
(let (previous current key-list)
(bibtex-progress-message "Checking for duplicate keys")
(bibtex-map-entries
(lambda (key beg end)
(bibtex-progress-message)
- (goto-char beg)
(setq current (bibtex-entry-index))
(cond ((not previous))
((member key key-list)
(bibtex-map-entries
(lambda (key beg end)
(bibtex-progress-message)
- (let* ((entry-list (progn
- (goto-char beg)
- (bibtex-search-entry nil end)
- (assoc-string (bibtex-type-in-head)
- bibtex-entry-field-alist t)))
+ (let* ((entry-list (assoc-string (bibtex-type-in-head)
+ bibtex-entry-field-alist t))
(req (copy-sequence (elt (elt entry-list 1) 0)))
(creq (copy-sequence (elt (elt entry-list 2) 0)))
crossref-there bounds alt-there field)
- (goto-char beg)
- (while (setq bounds (bibtex-search-forward-field
- bibtex-field-name end))
- (goto-char (bibtex-start-of-text-in-field bounds))
+ (bibtex-beginning-first-field beg)
+ (while (setq bounds (bibtex-parse-field))
(let ((field-name (bibtex-name-in-field bounds)))
(if (and (bibtex-string= field-name "month")
;; Check only abbreviated month fields.
(push (cons (bibtex-current-line)
"Questionable month field")
error-list))
- (setq field (assoc-string field-name req t))
+ (setq field (assoc-string field-name req t)
+ req (delete field req)
+ creq (delete (assoc-string field-name creq t) creq))
(if (nth 3 field)
- (if alt-there (push (cons (bibtex-current-line)
- "More than one non-empty alternative")
- error-list)
+ (if alt-there
+ (push (cons (bibtex-current-line)
+ "More than one non-empty alternative")
+ error-list)
(setq alt-there t)))
- (setq req (delete field req)
- creq (delete (assoc-string field-name creq t) creq))
(if (bibtex-string= field-name "crossref")
- (setq crossref-there t))))
- (if crossref-there
- (setq req creq))
+ (setq crossref-there t)))
+ (goto-char (bibtex-end-of-field bounds)))
+ (if crossref-there (setq req creq))
(let (alt)
(dolist (field req)
(if (nth 3 field)
(delete-region (point-min) (point-max))
(insert "BibTeX mode command `bibtex-validate'\n"
(if syntax-error
- "Maybe undetected errors due to syntax errors. Correct and validate again.\n"
+ "Maybe undetected errors due to syntax errors. Correct and validate again.\n"
"\n"))
(dolist (err error-list)
(insert (format "%s:%d: %s\n" file (car err) (cdr err))))
(toggle-read-only 1)
(goto-line 3)) ; first error message
(display-buffer err-buf)
- ;; return nil
- nil)
+ nil) ; return `nil' (i.e., buffer is invalid)
(message "%s is syntactically correct"
(if mark-active "Region" "Buffer"))
- t)))
+ t))) ; return `t' (i.e., buffer is valid)
(defun bibtex-validate-globally (&optional strings)
"Check for duplicate keys in `bibtex-files'.
(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))))))
(toggle-read-only 1)
(goto-line 3)) ; first error message
(display-buffer err-buf)
- ;; return nil
- nil)
+ nil) ; return `nil' (i.e., buffer is invalid)
(message "No duplicate keys.")
- t)))
-
-(defun bibtex-next-field (begin)
- "Move point to end of text of next BibTeX field.
-With prefix BEGIN non-nil, move point to its beginning."
- (interactive "P")
- (bibtex-inside-field)
- (let ((start (point)))
- (condition-case ()
- (let ((bounds (bibtex-enclosing-field)))
- (goto-char (bibtex-end-of-field bounds))
- (forward-char 2))
- (error
- (goto-char start)
- (end-of-line)
- (forward-char))))
- (bibtex-find-text begin nil bibtex-help-message))
-
-(defun bibtex-find-text (&optional begin noerror help)
- "Move point to end of text of current BibTeX field.
+ t))) ; return `t' (i.e., buffer is valid)
+
+(defun bibtex-next-field (begin &optional comma)
+ "Move point to end of text of next BibTeX field or entry head.
+With prefix BEGIN non-nil, move point to its beginning. Optional arg COMMA
+is as in `bibtex-enclosing-field'. It is t for interactive calls."
+ (interactive (list current-prefix-arg t))
+ (let ((bounds (bibtex-find-text-internal t nil comma))
+ end-of-entry)
+ (if (not bounds)
+ (setq end-of-entry t)
+ (goto-char (nth 3 bounds))
+ (if (assoc-string (car bounds) '("@String" "@Preamble") t)
+ (setq end-of-entry t)
+ ;; BibTeX key or field
+ (if (looking-at ",[ \t\n]*") (goto-char (match-end 0)))
+ ;; end of entry
+ (if (looking-at "[)}][ \t\n]*") (setq end-of-entry t))))
+ (if (and end-of-entry
+ (re-search-forward bibtex-any-entry-maybe-empty-head nil t))
+ (goto-char (match-beginning 0)))
+ (bibtex-find-text begin nil bibtex-help-message)))
+
+(defun bibtex-find-text (&optional begin noerror help comma)
+ "Move point to end of text of current BibTeX field or entry head.
With optional prefix BEGIN non-nil, move point to its beginning.
Unless NOERROR is non-nil, an error is signaled if point is not
on a BibTeX field. If optional arg HELP is non-nil print help message.
-When called interactively, the value of HELP is `bibtex-help-message'."
- (interactive (list current-prefix-arg nil bibtex-help-message))
- (let ((pnt (point))
- (bounds (bibtex-find-text-internal)))
- (beginning-of-line)
+When called interactively, the value of HELP is `bibtex-help-message'.
+Optional arg COMMA is as in `bibtex-enclosing-field'. It is t for
+interactive calls."
+ (interactive (list current-prefix-arg nil bibtex-help-message t))
+ (let ((bounds (bibtex-find-text-internal t nil comma)))
(cond (bounds
(if begin
(progn (goto-char (nth 1 bounds))
(goto-char (nth 2 bounds))
(if (memq (preceding-char) '(?} ?\"))
(forward-char -1)))
- (if help (bibtex-print-help-message)))
- ((looking-at bibtex-entry-maybe-empty-head)
- (goto-char (if begin
- (match-beginning bibtex-key-in-head)
- (match-end 0))))
- (t
- (goto-char pnt)
- (unless noerror (error "Not on BibTeX field"))))))
-
-(defun bibtex-find-text-internal (&optional noerror subfield)
- "Find text part of current BibTeX field, @String or @Preamble.
-Return list (NAME START END) with field name, start and end of text
-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 and END correspond
-to the current subfield delimited by #."
+ (if help (bibtex-print-help-message (car bounds))))
+ ((not noerror) (error "Not on BibTeX field")))))
+
+(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 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))
- (_ (bibtex-inside-field))
- (bounds (bibtex-enclosing-field t))
+ (bounds (bibtex-enclosing-field comma t))
(case-fold-search t)
- (bibtex-string-empty-key t)
- name start end)
+ 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)
- start (bibtex-start-of-text-in-field bounds)
- end (bibtex-end-of-text-in-field bounds)))
+ start-text (bibtex-start-of-text-in-field bounds)
+ end-text (bibtex-end-of-text-in-field bounds)
+ end (bibtex-end-of-field bounds)))
;; @String
- ((setq bounds (bibtex-parse-string))
- (setq name "@String" ;; not a field name!
- start (bibtex-start-of-text-in-string bounds)
- end (bibtex-end-of-text-in-string bounds)))
+ ((setq bounds (bibtex-parse-string t))
+ (if (<= pnt (bibtex-end-of-string bounds))
+ (setq name "@String" ;; not a field name!
+ start-text (bibtex-start-of-text-in-string bounds)
+ end-text (bibtex-end-of-text-in-string bounds)
+ end (bibtex-end-of-string bounds))
+ (setq failure t)))
;; @Preamble
- ((and (bibtex-preamble-prefix t)
- (setq bounds (bibtex-parse-field-text)))
- (setq name "@Preamble" ;; not a field name!
- start (car bounds)
- end (nth 1 bounds)))
- (t (unless noerror (error "Not on BibTeX field"))))
- (when (and start end subfield)
- (goto-char start)
- (let (done)
+ ((setq bounds (bibtex-parse-preamble))
+ (if (<= pnt (bibtex-end-of-string bounds))
+ (setq name "@Preamble" ;; not a field name!
+ start-text (bibtex-start-of-text-in-string bounds)
+ end-text (bibtex-end-of-text-in-string bounds)
+ end (bibtex-end-of-string bounds))
+ (setq failure t)))
+ ;; BibTeX head
+ ((looking-at bibtex-entry-maybe-empty-head)
+ (goto-char (match-end 0))
+ (if comma (save-match-data
+ (re-search-forward "\\=[ \t\n]*," nil t)))
+ (if (<= pnt (point))
+ (setq name (match-string-no-properties bibtex-type-in-head)
+ start-text (or (match-beginning bibtex-key-in-head)
+ (match-end 0))
+ end-text (or (match-end bibtex-key-in-head)
+ (match-end 0))
+ end end-text
+ no-sub t) ; subfields do not make sense
+ (setq failure t)))
+ (t (setq failure t)))
+ (when (and subfield (not failure))
+ (setq failure no-sub)
+ (unless failure
+ (goto-char start-text)
(while (not done)
(if (or (prog1 (looking-at bibtex-field-const)
- (setq end (match-end 0)))
+ (setq end-text (match-end 0)
+ string-const t))
(prog1 (setq bounds (bibtex-parse-field-string))
- (setq end (cdr bounds))))
+ (setq end-text (cdr bounds)
+ string-const nil)))
(progn
- (if (and (<= start pnt) (<= pnt end))
+ (if (and (<= start-text pnt) (<= pnt end-text))
(setq done t)
- (goto-char end))
+ (goto-char end-text))
(if (looking-at "[ \t\n]*#[ \t\n]*")
- (setq start (goto-char (match-end 0)))))
- (unless noerror (error "Not on text part of BibTeX field"))
- (setq done t start nil end nil)))))
- (if (and start end)
- (list name start end)))))
-
-(defun bibtex-remove-OPT-or-ALT ()
+ (setq start-text (goto-char (match-end 0)))))
+ (setq done t failure t)))))
+ (cond ((not failure)
+ (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"))))))
+
+(defun bibtex-remove-OPT-or-ALT (&optional comma)
"Remove the string starting optional/alternative fields.
-Align text and go thereafter to end of text."
- (interactive)
- (bibtex-inside-field)
+Align text and go thereafter to end of text. Optional arg COMMA
+is as in `bibtex-enclosing-field'. It is t for interactive calls."
+ (interactive (list t))
(let ((case-fold-search t)
- (bounds (bibtex-enclosing-field)))
+ (bounds (bibtex-enclosing-field comma)))
(save-excursion
(goto-char (bibtex-start-of-name-in-field bounds))
(when (looking-at "OPT\\|ALT")
(delete-horizontal-space)
(if bibtex-align-at-equal-sign
(insert " ")
- (indent-to-column bibtex-text-indentation))))
- (bibtex-inside-field)))
-
-(defun bibtex-remove-delimiters ()
- "Remove \"\" or {} around current BibTeX field text."
- (interactive)
- ;; `bibtex-find-text-internal' issues an error message if bounds is nil.
- (let* ((bounds (bibtex-find-text-internal nil t))
- (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)))))
-
-(defun bibtex-kill-field (&optional copy-only)
+ (indent-to-column bibtex-text-indentation))))))
+
+(defun bibtex-remove-delimiters (&optional comma)
+ "Remove \"\" or {} around current BibTeX field text.
+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)))
+ (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.
With prefix arg COPY-ONLY, copy the current field to `bibtex-field-kill-ring',
-but do not actually kill it."
- (interactive "P")
+but do not actually kill it. Optional arg COMMA is as in
+`bibtex-enclosing-field'. It is t for interactive calls."
+ (interactive (list current-prefix-arg t))
(save-excursion
- (bibtex-inside-field)
(let* ((case-fold-search t)
- (bounds (bibtex-enclosing-field))
+ (bounds (bibtex-enclosing-field comma))
(end (bibtex-end-of-field bounds))
(beg (bibtex-start-of-field bounds)))
(goto-char end)
(delete-region beg end))))
(setq bibtex-last-kill-command 'field))
-(defun bibtex-copy-field-as-kill ()
- "Copy the BibTeX field at point to the kill ring."
- (interactive)
- (bibtex-kill-field t))
+(defun bibtex-copy-field-as-kill (&optional comma)
+ "Copy the BibTeX field at point to the kill ring.
+Optional arg COMMA is as in `bibtex-enclosing-field'. It is t for
+interactive calls."
+ (interactive (list t))
+ (bibtex-kill-field t comma))
(defun bibtex-kill-entry (&optional copy-only)
"Kill the entire enclosing BibTeX entry.
(beg (bibtex-beginning-of-entry))
(end (progn (bibtex-end-of-entry)
(if (re-search-forward
- bibtex-entry-maybe-empty-head nil 'move)
+ bibtex-any-entry-maybe-empty-head nil 'move)
(goto-char (match-beginning 0)))
(point))))
(push (buffer-substring-no-properties beg end)
With argument N, reinsert the Nth most recently killed BibTeX item.
See also the command \\[bibtex-yank-pop]."
(interactive "*p")
- (bibtex-insert-kill (1- n))
+ (bibtex-insert-kill (1- n) t)
(setq this-command 'bibtex-yank))
(defun bibtex-yank-pop (n)
"Replace just-yanked killed BibTeX item with a different item.
This command is allowed only immediately after a `bibtex-yank' or a
-`bibtex-yank-pop'. At such a time, the region contains a reinserted
+`bibtex-yank-pop'. In this case, the region contains a reinserted
previously killed BibTeX item. `bibtex-yank-pop' deletes that item
and inserts in its place a different killed BibTeX item.
(setq this-command 'bibtex-yank)
(let ((inhibit-read-only t))
(delete-region (point) (mark t))
- (bibtex-insert-kill n)))
-
-(defun bibtex-empty-field ()
- "Delete the text part of the current field, replace with empty text."
- (interactive)
- (bibtex-inside-field)
- (let ((bounds (bibtex-enclosing-field)))
+ (bibtex-insert-kill n t)))
+
+(defun bibtex-empty-field (&optional comma)
+ "Delete the text part of the current field, replace with empty text.
+Optional arg COMMA is as in `bibtex-enclosing-field'. It is t for
+interactive calls."
+ (interactive (list t))
+ (let ((bounds (bibtex-enclosing-field comma)))
(goto-char (bibtex-start-of-text-in-field bounds))
(delete-region (point) (bibtex-end-of-text-in-field bounds))
(insert (bibtex-field-left-delimiter)
Don't call `bibtex-clean-entry' on @Preamble entries.
At end of the cleaning process, the functions in
`bibtex-clean-entry-hook' are called with region narrowed to entry."
- ;; Opt. arg called-by-reformat is t if bibtex-clean-entry
- ;; is called by bibtex-reformat
+ ;; Opt. arg CALLED-BY-REFORMAT is t if `bibtex-clean-entry'
+ ;; is called by `bibtex-reformat'
(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
;; set key
(when (or new-key (not key))
(setq key (bibtex-generate-autokey))
- ;; Sometimes bibtex-generate-autokey returns an empty string
+ ;; Sometimes `bibtex-generate-autokey' returns an empty string
(if (or bibtex-autokey-edit-before-use (string= "" key))
(setq key (if (eq entry-type 'string)
(bibtex-read-string-key key)
(if (and (listp bibtex-strings)
(not (assoc key bibtex-strings)))
(push (cons key (bibtex-text-in-string
- (save-excursion (bibtex-parse-string)) t))
+ (bibtex-parse-string) t))
bibtex-strings)))
;; We have a normal entry.
((listp bibtex-reference-keys)
If JUSTIFY is non-nil justify as well.
If optional arg MOVE is non-nil move point to end of field."
(let ((end-field (copy-marker (bibtex-end-of-field bounds))))
- (goto-char (bibtex-start-of-field bounds))
- (if justify
- (progn
- (forward-char)
- (bibtex-delete-whitespace)
- (open-line 1)
- (forward-char)
- (indent-to-column (+ bibtex-entry-offset
- bibtex-field-indentation))
- (re-search-forward "[ \t\n]*=" end-field)
- (replace-match "=")
- (forward-char -1)
- (if bibtex-align-at-equal-sign
- (indent-to-column
- (+ bibtex-entry-offset (- bibtex-text-indentation 2)))
- (insert " "))
- (forward-char)
- (bibtex-delete-whitespace)
- (if bibtex-align-at-equal-sign
- (insert " ")
- (indent-to-column bibtex-text-indentation)))
- (re-search-forward "[ \t\n]*=[ \t\n]*" end-field))
- ;; Paragraphs within fields are not preserved. Bother?
+ (if (not justify)
+ (goto-char (bibtex-start-of-text-in-field bounds))
+ (goto-char (bibtex-start-of-field bounds))
+ (forward-char) ; leading comma
+ (bibtex-delete-whitespace)
+ (open-line 1)
+ (forward-char)
+ (indent-to-column (+ bibtex-entry-offset
+ bibtex-field-indentation))
+ (re-search-forward "[ \t\n]*=" end-field)
+ (replace-match "=")
+ (forward-char -1)
+ (if bibtex-align-at-equal-sign
+ (indent-to-column
+ (+ bibtex-entry-offset (- bibtex-text-indentation 2)))
+ (insert " "))
+ (forward-char)
+ (bibtex-delete-whitespace)
+ (if bibtex-align-at-equal-sign
+ (insert " ")
+ (indent-to-column bibtex-text-indentation)))
+ ;; Paragraphs within fields are not preserved. Bother?
(fill-region-as-paragraph (line-beginning-position) end-field
default-justification nil (point))
(if move (goto-char end-field))))
(defun bibtex-fill-field (&optional justify)
"Like \\[fill-paragraph], but fill current BibTeX field.
-Optional prefix arg JUSTIFY non-nil means justify as well.
+If optional prefix JUSTIFY is non-nil justify as well.
In BibTeX mode this function is bound to `fill-paragraph-function'."
(interactive "*P")
(let ((pnt (copy-marker (point)))
- (bounds (bibtex-enclosing-field)))
- (when bounds
- (bibtex-fill-field-bounds bounds justify)
- (goto-char pnt))))
+ (bounds (bibtex-enclosing-field t)))
+ (bibtex-fill-field-bounds bounds justify)
+ (goto-char pnt)))
(defun bibtex-fill-entry ()
"Fill current BibTeX entry.
(interactive "*")
(let ((pnt (copy-marker (point)))
(end (copy-marker (bibtex-end-of-entry)))
+ (beg (bibtex-beginning-of-entry)) ; move point
bounds)
- (bibtex-beginning-of-entry)
(bibtex-delete-whitespace)
(indent-to-column bibtex-entry-offset)
- (while (setq bounds (bibtex-search-forward-field bibtex-field-name end))
+ (bibtex-beginning-first-field beg)
+ (while (setq bounds (bibtex-parse-field))
(bibtex-fill-field-bounds bounds t t))
(if (looking-at ",")
(forward-char))
+ (skip-chars-backward " \t\n")
(bibtex-delete-whitespace)
(open-line 1)
(forward-char)
(,(concat (if bibtex-comma-after-last-field "Insert" "Remove")
" comma at end of entry? ") . 'last-comma)
("Replace double page dashes by single ones? " . 'page-dashes)
+ ("Delete whitespace at the beginning and end of fields? " . 'whitespace)
("Inherit booktitle? " . 'inherit-booktitle)
("Force delimiters? " . 'delimiters)
- ("Unify case of entry types and field names? " . 'unify-case))))))
+ ("Unify case of entry types and field names? " . 'unify-case)
+ ("Enclose parts of field entries by braces? " . 'braces)
+ ("Replace parts of field entries by string constants? " . 'strings))))))
;; Do not include required-fields because `bibtex-reformat'
;; cannot handle the error messages of `bibtex-format-entry'.
;; Use `bibtex-validate' to check for required fields.
((eq t bibtex-entry-format)
'(realign opts-or-alts numerical-fields delimiters
- last-comma page-dashes unify-case inherit-booktitle))
+ last-comma page-dashes unify-case inherit-booktitle
+ whitespace braces strings))
(t
(remove 'required-fields (push 'realign bibtex-entry-format)))))
(reformat-reference-keys
bibtex-autokey-edit-before-use)
(save-restriction
- (narrow-to-region (if mark-active (region-beginning) (point-min))
- (if mark-active (region-end) (point-max)))
+ (if mark-active (narrow-to-region (region-beginning) (region-end)))
(if (memq 'realign bibtex-entry-format)
(bibtex-realign))
(bibtex-progress-message "Formatting" 1)
(message "Starting to validate buffer...")
(sit-for 1 nil t)
(bibtex-realign)
- (message
- "If errors occur, correct them and call `bibtex-convert-alien' again")
- (sit-for 5 nil t)
- (deactivate-mark) ; So bibtex-validate works on the whole buffer.
- (when (let (bibtex-maintain-sorted-entries)
- (bibtex-validate))
+ (deactivate-mark) ; So `bibtex-validate' works on the whole buffer.
+ (if (not (let (bibtex-maintain-sorted-entries)
+ (bibtex-validate)))
+ (message "Correct errors and call `bibtex-convert-alien' again")
(message "Starting to reformat entries...")
(sit-for 2 nil t)
(bibtex-reformat read-options)
(goto-char (point-max))
- (message "Buffer is now parsable. Please save it.")))
+ (message "Buffer is now parsable. Please save it.")))
(defun bibtex-complete ()
"Complete word fragment before point according to context.
(interactive)
(let ((pnt (point))
(case-fold-search t)
- (bibtex-string-empty-key t)
bounds name compl)
(save-excursion
- (if (and (setq bounds (bibtex-enclosing-field t))
+ (if (and (setq bounds (bibtex-enclosing-field nil t))
(>= pnt (bibtex-start-of-text-in-field bounds))
(<= pnt (bibtex-end-of-text-in-field bounds)))
(setq name (bibtex-name-in-field bounds t)
;; point is in other field
(t (bibtex-strings))))
(bibtex-beginning-of-entry)
- (cond ((setq bounds (bibtex-parse-string))
+ (cond ((setq bounds (bibtex-parse-string t))
;; point is inside a @String key
(cond ((and (>= pnt (nth 1 (car bounds)))
(<= pnt (nth 2 (car bounds))))
(<= pnt (bibtex-end-of-text-in-string bounds)))
(setq compl (bibtex-strings)))))
;; point is inside a @Preamble field
- ((and (bibtex-preamble-prefix t)
- (setq bounds (bibtex-parse-field-text))
- (>= pnt (car bounds))
- (<= pnt (nth 1 bounds)))
- (setq compl (bibtex-strings)))
+ ((setq bounds (bibtex-parse-preamble))
+ (if (and (>= pnt (bibtex-start-of-text-in-string bounds))
+ (<= pnt (bibtex-end-of-text-in-string bounds)))
+ (setq compl (bibtex-strings))))
((and (looking-at bibtex-entry-maybe-empty-head)
;; point is inside a key
(or (and (match-beginning bibtex-key-in-head)
(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\). If multiple schemes match for this entry, or the same scheme
+matches more than once, use the one for which the first step's match is the
+closest to POS. The URL is passed to `browse-url' unless NO-BROWSE is t.
+Return the URL or nil if none can be generated."
(interactive)
+ (unless pos (setq pos (point)))
(save-excursion
- (if pos (goto-char pos))
+ (goto-char pos)
(bibtex-beginning-of-entry)
- ;; Always remove field delimiters
- (let ((fields-alist (bibtex-parse-entry t))
+ (let ((end (save-excursion (bibtex-end-of-entry)))
+ (fields-alist (save-excursion (bibtex-parse-entry t)))
;; Always ignore case,
(case-fold-search t)
- (lst bibtex-generate-url-list)
- field url scheme obj fmt)
- (while (setq scheme (pop lst))
- (when (and (setq field (cdr (assoc-string (caar scheme)
- fields-alist t)))
- (string-match (cdar scheme) field))
- (setq lst nil
- scheme (cdr scheme)
- url (if (null scheme) (match-string 0 field)
- (if (stringp (car scheme))
- (setq fmt (pop scheme)))
- (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))))
- ;; If the scheme is set up correctly,
- ;; we should never reach this point
- (error "Match failed: %s" field))
- (push field obj))
- (if fmt (apply 'format fmt (nreverse obj))
- (apply 'concat (nreverse obj)))))
- (browse-url (message "%s" url))))
- (unless url (message "No URL known.")))))
+ text url scheme obj fmt fl-match step)
+ ;; The return value of `bibtex-parse-entry' (i.e., FIELDS-ALIST)
+ ;; is always used to generate the URL. However, if the BibTeX
+ ;; entry contains more than one URL, we have multiple matches
+ ;; for the first step defining the generation of the URL.
+ ;; Therefore, we try to initiate the generation of the URL
+ ;; based on the match of `bibtex-font-lock-url' that is the
+ ;; closest to POS. If that fails (no match found) we try to
+ ;; initiate the generation of the URL based on the properly
+ ;; concatenated CONTENT of the field as returned by
+ ;; `bibtex-text-in-field-bounds'. The latter approach can
+ ;; differ from the former because `bibtex-font-lock-url' uses
+ ;; the buffer itself.
+ (while (bibtex-font-lock-url end t)
+ (push (list (bibtex-dist pos (match-beginning 0) (match-end 0))
+ (match-beginning 0)
+ (buffer-substring-no-properties
+ (match-beginning 0) (match-end 0)))
+ fl-match)
+ ;; `bibtex-font-lock-url' moves point to end of match.
+ (forward-char))
+ (when fl-match
+ (setq fl-match (car (sort fl-match (lambda (x y) (< (car x) (car y))))))
+ (goto-char (nth 1 fl-match))
+ (bibtex-beginning-of-field) (re-search-backward ",")
+ (let* ((bounds (bibtex-parse-field))
+ (name (bibtex-name-in-field bounds))
+ (content (bibtex-text-in-field-bounds bounds t))
+ (lst bibtex-generate-url-list))
+ ;; This match can fail when CONTENT differs from text in buffer.
+ (when (string-match (regexp-quote (nth 2 fl-match)) content)
+ ;; TEXT is the part of CONTENT that starts with the match
+ ;; of `bibtex-font-lock-url' we are looking for.
+ (setq text (substring content (match-beginning 0)))
+ (while (and (not url) (setq scheme (pop lst)))
+ ;; Verify the match of `bibtex-font-lock-url' by
+ ;; comparing with TEXT.
+ (when (and (bibtex-string= (caar scheme) name)
+ (string-match (cdar scheme) text))
+ (setq url t scheme (cdr scheme)))))))
+
+ ;; If the match of `bibtex-font-lock-url' was not approved
+ ;; parse FIELDS-ALIST, i.e., the output of `bibtex-parse-entry'.
+ (unless url
+ (let ((lst bibtex-generate-url-list))
+ (while (and (not url) (setq scheme (pop lst)))
+ (when (and (setq text (cdr (assoc-string (caar scheme)
+ fields-alist t)))
+ (string-match (cdar scheme) text))
+ (setq url t scheme (cdr scheme))))))
+
+ (when url
+ (setq url (if (null scheme) (match-string 0 text)
+ (if (stringp (car scheme))
+ (setq fmt (pop scheme)))
+ (dotimes (i (length scheme))
+ (setq step (nth i scheme))
+ ;; The first step shall use TEXT as obtained earlier.
+ (unless (= i 0)
+ (setq text (cdr (assoc-string (car step) fields-alist t))))
+ (if (string-match (nth 1 step) text)
+ (push (cond ((functionp (nth 2 step))
+ (funcall (nth 2 step) text))
+ ((numberp (nth 2 step))
+ (match-string (nth 2 step) text))
+ (t
+ (replace-match (nth 2 step) t nil text)))
+ obj)
+ ;; If SCHEME is set up correctly,
+ ;; we should never reach this point
+ (error "Match failed: %s" text)))
+ (if fmt (apply 'format fmt (nreverse obj))
+ (apply 'concat (nreverse obj)))))
+ (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